接第一部分
1、在tools目录下建立formdesigner的index.vue,主要是获取参数,调用formdesigner组件,内容如下:
<template>
<div>
<form-designer ref="formDesigner" :queryId="routeQueryId" v-model="form.fdForm"></form-designer>
</div>
</template>
<script>
export default {
name: 'designerExapmle',
data() {
return {
form: {
fdForm: ''
},
routeQueryId: '',
}
},
created() {
this.routeQueryId = this.$route.query && this.$route.query.formId;
},
mounted() {
},
}
</script>
<style>
</style>
2、组件里的formdesigner.vue主要是获取form信息,同时赋值,再调用designer组件,并传递相关参数,具体代码如下:
<template>
<div class="container">
<div class="left-board">
<div class="d-logo-wrapper">
<div class="d-logo">
Form designer
</div>
</div>
<el-scrollbar class="left-scrollbar">
<!--左侧组件列表-->
<div class="components-list">
<div class="components-title">
常用组件
</div>
<draggable
class="components-draggable"
:list="formItems"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@start="onStart"
@end="onEnd"
>
<div
v-for="(element, index) in formItems" :key="index" class="components-item"
@click="addComponent(element)"
>
<div class="components-body" :class="{ 'dynamicTable-tips': dynamicTableExist(element)}">
<icon :code="element.compIcon" :text="element.compName"/>
</div>
</div>
</draggable>
<div class="components-title">
布局组件
</div>
<draggable
class="components-draggable"
:list="layoutFormItems"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@start="onStart"
@end="onEnd"
>
<div
v-for="(element, index) in layoutFormItems" :key="index" class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<icon :code="element.compIcon" :text="element.compName"/>
</div>
</div>
</draggable>
<div class="components-title">
辅助组件
</div>
<draggable
class="components-draggable"
:list="assistFormItems"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@start="onStart"
@end="onEnd"
>
<div
v-for="(element, index) in assistFormItems" :key="index" class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<icon :code="element.compIcon" :text="element.compName"/>
</div>
</div>
</draggable>
</div>
</el-scrollbar>
</div>
<designer ref="designer" :queryId="routeQueryId" :list="designList" :formConfig="formConfig" :inform="inform" @clear="designList = []" @updateJSON="handlerUpdateJSON" :activeData="activeData"/>
</div>
</template>
<script>
/**
* 1.0版本
*/
import draggable from "vuedraggable";
import {formItems,assistFormItems,layoutFormItems} from "./custom/itemList";
import designer from "./designer";
import icon from "./icon";
import {getSimpleId,setTableId} from "./utils/IdGenerate";
import formConf from "./custom/formConf";
import {dynamicTableAllowedItems} from "./custom/formConf";
import {getForm, addForm, updateForm} from "@/api/workflow/form";
let tempActiveData;
export default {
name:"formDesigner",
components:{
draggable
,icon,
designer
},
data() {
return {
formItems:formItems,
assistFormItems:assistFormItems,
layoutFormItems:layoutFormItems,
designList:[],
activeData:{},
formConfig:formConf,
routeQueryId:'',
inform:[],
}
},
props:{
value:{
type:String,
default:''
},
queryId:{
type:String,
default:''
}
},
created() {// add by nbacheng 2023-09-10
const that = this;
const id = that.queryId;
console.log("formDesigner mounted queryId",that.queryId);
if (id) {
getForm(id).then(res =>{
console.log("getForm res=",res);
const content = JSON.parse(res.data.content);
that.formConfig = content.config;
that.designList = content.list;
that.inform = res.data;
})
}
},
mounted() {
},
methods: {
addComponent(element){
},
cloneComponent(origin){
const clone = JSON.parse(JSON.stringify(origin))
if (!clone.layout) clone.layout = 'colItem'
if (clone.layout === 'colItem'||clone.layout === 'dynamicItem') {
let uId = "fd_"+getSimpleId();
clone.id = uId;
clone._id = uId;
tempActiveData = clone;
}else if (clone.layout === 'rowItem'){
let uId = "row_"+getSimpleId();
clone.id = uId;
clone._id = uId;
tempActiveData = clone;
}else if(clone.layout === 'tableItem'){
let uId = "table_"+getSimpleId();
clone.id = uId;
clone._id = uId;
//增加td默认的id
setTableId(clone);
tempActiveData = clone;
}
this.$refs.designer.activeItem = tempActiveData;
},
onStart(obj){
},
onEnd(obj){
if(obj.from !== obj.to){
this.activeData = tempActiveData;
this.$refs.designer.activeItem = this.activeData;
if(obj.to.className.indexOf('row-drag')<0){
this.designList.splice(obj.newIndex,0,this.activeData);
}
}else{
this.$refs.designer.activeItem = {};
}
},
getFormData(){
return this.formData;
},
handlerUpdateJSON(json){
const jsonObject = JSON.parse(json);
this.designList = [];
this.designList = this.designList.concat(jsonObject.list);
this.formConfig = Object.assign({}, jsonObject.config);
this.$refs['designer'].changeFormConfig(this.formConfig);
}
},
computed:{
formData:function(){
const list = this.designList;
const config = this.formConfig;
let formData = {};
formData.list = list;
formData.config = config;
console.log("formDesigner formData=",formData);
return JSON.stringify(formData);
//this.$emit('input',JSON.stringify(formData));
},
dynamicTableExist(){
return function(element){
return this.formConfig.dynamicTableAllowed
&&this.designList.filter(item=>item.compType === 'dynamicTable').length>0
&&dynamicTableAllowedItems.includes(element.compType);
}
}
},
watch:{
value(newVal){
if(newVal !==''){
const formData = JSON.parse(newVal);
this.designList= formData.list;
this.formConfig = formData.config;
}
}
}
}
</script>
<style scoped>
.container{
padding:0px
}
.dynamicTable-tips{
border:1px solid#F08080
}
</style>
3、组件里的designer.vue主要增加一个保存按钮与相关方法,具体代码如下:
<template>
<!--中间面板-->
<div class="center-board" >
<div class="action-bar">
<el-button icon="el-icon-plus" type="text" @click="save">
保存
</el-button>
<el-button icon="el-icon-view" type="text" @click="preview">
预览
</el-button>
<el-button icon="el-icon-view" type="text" @click="view">
查看
</el-button>
<el-button icon="el-icon-tickets" type="text" @click="viewJSON">
JSON
</el-button>
<el-button icon="el-icon-s-tools" type="text" @click="setting">
设置
</el-button>
<el-button class="delete-btn" icon="el-icon-delete-solid" type="text" @click="clear">
清空
</el-button>
<el-button icon="el-icon-question" type="text" @click="help">
帮助
</el-button>
</div>
<el-scrollbar class="center-scrollbar">
<el-row class="center-board-row" :gutter="formConf.gutter">
<el-form
:size="formConf.size"
:label-position="formConf.labelPosition"
:disabled="formConf.disabled"
:label-width="formConf.labelWidth + 'px'"
class="design-form"
>
<draggable
class="drawing-board center-board-row"
:list="list"
:animation="100"
group="componentsGroup"
>
<template v-for="element in list">
<el-row
:gutter="element.gutter"
class="drawing-item"
>
<design-item
:model="element"
:activeItem="activeItem"
@rowItemRollBack="handlerRollBack"
@onActiveItemChange="handlerActiveItemChange"
@copyItem="handlerItemCopy"
@deleteItem="handlerItemDelete"
/>
</el-row>
</template>
</draggable>
<div v-show="infoShow" class="empty-info">
<el-empty description="从左侧拖拽添加控件"></el-empty>
</div>
</el-form>
</el-row>
</el-scrollbar>
<config-panel :activeItem="activeItem" :itemList="list" :formConf="formConf"/>
<!-- 设计器配置弹出框 -->
<el-dialog :visible.sync="formConfVisible" width="50%" top="30px" :center="true">
<el-tabs v-model="activeName">
<el-tab-pane label="表单配置" name="formConf">
<el-form ref="formConf" :model="formConf" label-width="100px">
<el-form-item label="表单名">
<el-input class="input" v-model="formConf.formRef"></el-input>
</el-form-item>
<el-form-item label="表单模型">
<el-input class="input" v-model="formConf.formModel"></el-input>
</el-form-item>
<el-form-item label="校验模型">
<el-input class="input" v-model="formConf.formRules"></el-input>
</el-form-item>
<el-form-item label="表单尺寸">
<el-radio-group v-model="formConf.size">
<el-radio-button label="medium">中等</el-radio-button>
<el-radio-button label="small">较小</el-radio-button>
<el-radio-button label="mini">迷你</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="标签对齐">
<el-radio-group v-model="formConf.labelPosition">
<el-radio-button label="right">右对齐</el-radio-button>
<el-radio-button label="left">左对齐</el-radio-button>
<el-radio-button label="top">顶部对齐</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="标签宽度">
<el-input-number v-model="formConf.labelWidth" :min="60" :max="140"></el-input-number>
</el-form-item>
<el-form-item label="栅格间隔">
<el-input-number v-model="formConf.gutter" :min="0" :max="30"></el-input-number>
</el-form-item>
<el-form-item label="动态表格支持组件高亮显示">
<el-switch v-model="formConfig.dynamicTableAllowed"></el-switch>
</el-form-item>
<el-form-item label="禁用表单">
<el-switch v-model="formConf.disabled"></el-switch>
</el-form-item>
<el-form-item label="表单样式表">
<css-codemirror ref="cssCodemirror" :formConf="formConf"></css-codemirror>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- <el-tab-pane label="提交前" name="fourth">开发中...</el-tab-pane> -->
</el-tabs>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="handlerSaveFormConf">确 定</el-button>
</span>
</el-dialog>
<el-dialog :visible.sync="previewVisible" width="70%" title="预览">
<preview :itemList="itemList" :formConf="formConf" v-if="previewVisible"/>
</el-dialog>
<el-dialog :visible.sync="JSONVisible" width="70%" title="JSON" center :close-on-click-modal="false">
<codemirror v-model="viewCode" :options="options"/>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="handlerSetJson()">确 定</el-button>
</span>
</el-dialog>
<!--表单配置详情 add by nbacheng 2022-09-05-->
<el-dialog :title="formTitle" :visible.sync="formOpen" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="表单名称" prop="formName">
<el-input v-model="form.formName" placeholder="请输入表单名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import draggable from "vuedraggable";
import configPanel from './configPanel'
import designItem from './designItem'
import {getSimpleId} from "./utils/IdGenerate";
import { isLayout, isTable, inTable,jsonClone } from "./utils/index";
import formConf from "./custom/formConf";
import preview from "./preview";
import {codemirror} from 'vue-codemirror';
// 核心样式
import 'codemirror/lib/codemirror.css';
// 引入主题后还需要在 options 中指定主题才会生效
import 'codemirror/theme/dracula.css';
import 'codemirror/mode/javascript/javascript'
import cssCodemirror from '../components/custom/css/cssCodemirror'
import {addForm, updateForm} from "@/api/workflow/form";
export default {
name:"designer",
components:{
draggable,
configPanel,
designItem,
preview,
codemirror,
cssCodemirror
},
props:{
list: {
type: Array,
default:[]
},
formConfig:{
type:Object,
default:formConf
},
inform: {
type:Array,
default:[]
},
queryId: {
type:String,
default:''
}
},
provide(){
return{
getContext:this
}
},
data() {
return {
formConf:formConf,
activeItem:{},
lastActiveItem:{},
formConfVisible:false,
previewVisible:false,
JSONVisible:false,
itemList:[],
activeName:'formConf',
editorCode:'',
viewCode:'',
formOpen: false,
formTitle: "",
// 表单参数
form: {
formId: null,
formName: null,
content: null,
remark: null
},
// 表单校验
rules: {},
// 默认配置
options: {
tabSize: 2, // 缩进格式
theme: 'dracula', // 主题,对应主题库 JS 需要提前引入
lineNumbers: true, // 显示行号
line: true,
styleActiveLine: true, // 高亮选中行
hintOptions: {
completeSingle: true // 当匹配只有一项的时候是否自动补全
}
}
}
},
mounted() {
},
methods: {
save(){// add by nbacheng 2022-09-05
console.log("save inform=",this.inform);
if (this.inform.formId) {
this.form.formId = this.inform.formId;
this.form.formName = this.inform.formName;
this.form.remark = this.inform.remark;
}
/** 表单保存基本信息 */
this.formData = {
list: this.list,
config: this.formConf
}
console.log("save this.formData=",this.formData);
this.form.content = JSON.stringify(this.formData);
this.formOpen = true;
if (this.inform.formId) {
this.formTitle = "修改表单";
}
else {
this.formTitle = "添加表单";
}
console.log("save form=",this.form);
},
changeFormConfig(formConfig){
this.formConf = formConfig;
},
preview(){
const clone = JSON.parse(JSON.stringify(this.list))
this.itemList = clone;
this.previewVisible= true;
},
viewJSON(){
this.viewCode = this.code;
this.JSONVisible = true;
},
// 表单重置
reset() {
this.form = {
formId: null,
formName: null,
content: null,
remark: null
};
this.resetForm("form");
},
// 取消按钮
cancel() {
this.formOpen = false;
this.reset();
},
/** 保存表单信息 */
submitForm(){
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.formId != null) {
updateForm(this.form).then(response => {
this.$message.success("修改成功");
});
} else {
addForm(this.form).then(response => {
this.$message.success("新增成功");
});
}
this.list = []
this.idGlobal = 100
this.formOpen = false;
// 关闭当前标签页并返回上个页面
this.$store.dispatch("tagsView/delView", this.$route);
this.$router.go(-1)
}
});
},
view(){
localStorage.setItem("formValue",this.code);
window.open('#/view');
},
setting(){
this.formConfVisible = true;
},
clear(){
this.$confirm('此操作将清空整个表单,是否继续?').then(() => {
this.$emit('clear');
})
},
help(){
window.open('https://gitee.com/wurong19870715/formDesigner')
},
handlerActiveItemChange(obj){
this.lastActiveItem = this.activeItem;
this.activeItem = obj;
},
handlerItemCopy(origin,parent){
if(isLayout(origin)){ //row
const clone = jsonClone(origin);
const uId = "row_"+getSimpleId();
console.log(uId);
clone.id = uId;
clone._id = uId;
clone.columns.map((column)=>{
let itemList = [];
column.list.map((item)=>{
const cloneItem = jsonClone(item);
const uId = "fd_"+getSimpleId();
cloneItem.id = uId;
cloneItem._id = uId;
itemList.push(cloneItem);
})
column.list = [];
column.list = itemList;
})
this.list.push(clone);
this.handlerActiveItemChange(clone);
}else if(isTable(origin)){ //表格布局
const clone = jsonClone(origin);
const uId = "table_"+getSimpleId();
clone.id = uId;
clone._id = uId;
clone.layoutArray.map((tr)=>{
tr.map(td=>{
let itemList = [];
td.id=getSimpleId();
td.columns.map((item,i)=>{
const cloneItem = jsonClone(item);
const uId = "fd_"+getSimpleId();
cloneItem.id = uId;
cloneItem._id = uId;
itemList.push(cloneItem);
})
td.columns = [];
td.columns = itemList;
});
})
this.list.push(clone);
this.handlerActiveItemChange(clone);
}else{ //如果是普通组件,需要判断他是否再布局组件下。
if(parent){
if (inTable(parent)) { //增加表格组件的支持
if (parent.columns.some(item => item.id === origin.id)) {
const clone = jsonClone(origin);
const uId = "fd_" + getSimpleId();
clone.id = uId;
clone._id = uId;
parent.columns.push(clone);
this.handlerActiveItemChange(clone);
}
} else {
parent.columns.map((column) => {
if (column.list.some(item => item.id === origin.id)) {
const clone = jsonClone(origin);
const uId = "fd_" + getSimpleId();
clone.id = uId;
clone._id = uId;
column.list.push(clone);
this.handlerActiveItemChange(clone);
}
})
}
}else{
const clone = jsonClone(origin);
const uId = "fd_"+getSimpleId();
clone.id = uId;
clone._id = uId;
this.list.push(clone);
this.handlerActiveItemChange(clone);
}
}
},
handlerItemDelete(origin,parent){
if (isLayout(origin) || isTable(origin)){ //如果是布局组件,则直接删除
const index = this.list.findIndex(item=>item.id === origin.id);
this.list.splice(index,1);
}else{ //如果不是布局组件,则先判断是不是再布局内部,如果不是,则直接删除就可以,如果是,则要在布局内部删除
if(parent){
if (inTable(parent)){ //增加表格组件的支持
const colIndex = parent.columns.findIndex(item => item.id === origin.id);
if (colIndex > -1) {
parent.columns.splice(colIndex, 1);
}
}else{
parent.columns.map((column) => {
const colIndex = column.list.findIndex(item => item.id === origin.id);
if (colIndex > -1) {
column.list.splice(colIndex, 1);
}
})
}
}else{
const index = this.list.findIndex(item=>item.id === origin.id);
this.list.splice(index,1);
}
}
},
handlerSaveFormConf(){
this.$refs.cssCodemirror.setClassStyle()
this.formConfVisible = false
},
handlerRollBack(rowItem,oldIndex){ //还原
this.list.splice(oldIndex,0,rowItem);
},
handlerSetJson(){
this.$emit('updateJSON',this.viewCode);
this.JSONVisible = false;
},
},
computed:{
infoShow() {
return this.list.length<1;
},
code() {
let json = {};
json.config = this.formConf;
json.list = this.list;
return JSON.stringify(json,null,4);
}
},
watch: {
activeItem (newValue,oldValue) {
this.lastActiveItem = oldValue;
}
}
}
</script>
<style scoped>
.el-rate{
display:inline-block;
}
.center-scrollbar >>> .el-scrollbar__bar.is-horizontal {
display: none;
}
.center-scrollbar >>> .el-scrollbar__wrap{
overflow-x: hidden;
}
.empty-info >>> .el-empty__description p{
color: #ccb1ea;
font-size:16px;
}
.drawing-board >>> .el-radio.is-bordered+.el-radio.is-bordered{
margin-left:0px;
}
.drawing-board >>> .el-checkbox.is-bordered+.el-checkbox.is-bordered{
margin-left:0px;
}
</style>
<style lang="scss">
@import "./style/designer.scss";
</style>
<style>
@import "./style/designer.css";
</style>