houzhongjian
2024-07-11 759b1c71011abd6b58c37d2566f3f3c208c2f1b2
提交 | 用户 | 时间
759b1c 1 <template>
H 2   <div class="container">
3     <div class="left-board">
4       <div class="logo-wrapper">
5         <div class="logo">流程表单</div>
6       </div>
7       <el-scrollbar class="left-scrollbar">
8         <!-- 左边:表单项 -->
9         <div class="components-list">
10           <div v-for="(item, listIndex) in leftComponents" :key="listIndex">
11             <div class="components-title">
12               <svg-icon icon-class="component" />
13               {{ item.title }}
14             </div>
15             <draggable
16               class="components-draggable"
17               :list="item.list"
18               :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
19               :clone="cloneComponent"
20               draggable=".components-item"
21               :sort="false"
22               @end="onEnd"
23             >
24               <div
25                 v-for="(element, index) in item.list"
26                 :key="index"
27                 class="components-item"
28                 @click="addComponent(element)"
29               >
30                 <div class="components-body">
31                   <svg-icon :icon-class="element.__config__.tagIcon" />
32                   {{ element.__config__.label }}
33                 </div>
34               </div>
35             </draggable>
36           </div>
37
38           <!-- 左边:动态表单 -->
39           <el-form ref="form" :model="form" :rules="rules" label-width="80px">
40             <el-form-item label="表单名" prop="name">
41               <el-input v-model="form.name" placeholder="请输入表单名" />
42             </el-form-item>
43             <el-form-item label="开启状态" prop="status">
44               <el-radio-group v-model="form.status">
45                 <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
46                           :key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
47               </el-radio-group>
48             </el-form-item>
49             <el-form-item label="备注" prop="remark">
50               <el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
51             </el-form-item>
52           </el-form>
53         </div>
54       </el-scrollbar>
55     </div>
56
57     <div class="center-board">
58       <div class="action-bar">
59         <el-button icon="el-icon-check" type="text" @click="save">保存</el-button>
60         <!--        <el-button icon="el-icon-video-play" type="text" @click="run">-->
61 <!--          运行-->
62 <!--        </el-button>-->
63         <el-button icon="el-icon-view" type="text" @click="showJson">
64           查看json
65         </el-button>
66 <!--        <el-button icon="el-icon-download" type="text" @click="download">-->
67 <!--          导出vue文件-->
68 <!--        </el-button>-->
69 <!--        <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">-->
70 <!--          复制代码-->
71 <!--        </el-button>-->
72         <el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty">
73           清空
74         </el-button>
75       </div>
76
77       <!-- 中间,表单项 -->
78       <el-scrollbar class="center-scrollbar">
79         <el-row class="center-board-row" :gutter="formConf.gutter">
80           <el-form
81             :size="formConf.size"
82             :label-position="formConf.labelPosition"
83             :disabled="formConf.disabled"
84             :label-width="formConf.labelWidth + 'px'"
85           >
86             <draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup">
87               <draggable-item
88                 v-for="(item, index) in drawingList"
89                 :key="item.renderKey"
90                 :drawing-list="drawingList"
91                 :current-item="item"
92                 :index="index"
93                 :active-id="activeId"
94                 :form-conf="formConf"
95                 @activeItem="activeFormItem"
96                 @copyItem="drawingItemCopy"
97                 @deleteItem="drawingItemDelete"
98               />
99             </draggable>
100             <div v-show="!drawingList.length" class="empty-info">
101               从左侧拖入或点选组件进行表单设计
102             </div>
103           </el-form>
104         </el-row>
105       </el-scrollbar>
106     </div>
107
108     <!-- 右边:组件属性/表单属性 -->
109     <right-panel
110       :active-data="activeData"
111       :form-conf="formConf"
112       :show-field="!!drawingList.length"
113       @tag-change="tagChange"
114       @fetch-data="fetchData"
115     />
116
117 <!--    <form-drawer-->
118 <!--      :visible.sync="drawerVisible"-->
119 <!--      :form-data="formData"-->
120 <!--      size="100%"-->
121 <!--      :generate-conf="generateConf"-->
122 <!--    />-->
123
124     <json-drawer
125       size="60%"
126       :visible.sync="jsonDrawerVisible"
127       :json-str="JSON.stringify(formData)"
128       @refresh="refreshJson"
129     />
130
131 <!--    <code-type-dialog-->
132 <!--      :visible.sync="dialogVisible"-->
133 <!--      title="选择生成类型"-->
134 <!--      :show-file-name="showFileName"-->
135 <!--      @confirm="generate"-->
136 <!--    />-->
137
138 <!--    <input id="copyNode" type="hidden">-->
139   </div>
140 </template>
141
142 <script>
143 import draggable from 'vuedraggable'
144 import { debounce } from 'throttle-debounce'
145 import { saveAs } from 'file-saver'
146 import ClipboardJS from 'clipboard'
147 import render from '@/components/render/render'
148 import FormDrawer from '@/views/infra/build/FormDrawer'
149 import JsonDrawer from '@/views/infra/build/JsonDrawer'
150 import RightPanel from '@/views/infra/build/RightPanel'
151 import {
152   inputComponents, selectComponents, layoutComponents, formConf
153 } from '@/components/generator/config'
154 import {beautifierConf, titleCase, deepClone, isObjectObject} from '@/utils'
155 import {
156   makeUpHtml, vueTemplate, vueScript, cssStyle
157 } from '@/components/generator/html'
158 import { makeUpJs } from '@/components/generator/js'
159 import { makeUpCss } from '@/components/generator/css'
160 import drawingDefalut from '@/components/generator/drawingDefalut'
161 import logo from '@/assets/logo/logo.png'
162 import CodeTypeDialog from '@/views/infra/build/CodeTypeDialog'
163 import DraggableItem from '@/views/infra/build/DraggableItem'
164 import {
165   getDrawingList, saveDrawingList, getIdGlobal, saveIdGlobal, getFormConf
166 } from '@/utils/db'
167 import loadBeautifier from '@/utils/loadBeautifier'
168 import {CommonStatusEnum} from "@/utils/constants";
169 import {createForm, getForm, updateForm} from "@/api/bpm/form";
170 import {decodeFields} from "@/utils/formGenerator";
171
172 let beautifier
173 const emptyActiveData = { style: {}, autosize: {} }
174 let oldActiveId
175 let tempActiveData
176 const drawingListInDB = getDrawingList()
177 const formConfInDB = getFormConf()
178 const idGlobal = getIdGlobal()
179
180 export default {
181   components: {
182     draggable,
183     render,
184     FormDrawer,
185     JsonDrawer,
186     RightPanel,
187     CodeTypeDialog,
188     DraggableItem
189   },
190   data() {
191     return {
192       logo,
193       idGlobal,
194       formConf,
195       inputComponents,
196       selectComponents,
197       layoutComponents,
198       labelWidth: 100,
199       // drawingList: drawingDefalut,
200       drawingData: {}, // 生成后的表单数据
201       activeId: drawingDefalut[0].__config__.formId,
202
203       drawingList: [], // 表单项的数组
204       // activeId: undefined,
205       // activeData: {},
206
207       drawerVisible: false,
208       formData: {},
209       dialogVisible: false,
210       jsonDrawerVisible: false,
211       generateConf: null,
212       showFileName: false,
213       activeData: drawingDefalut[0], // 右边编辑器激活的表单项
214       saveDrawingListDebounce: debounce(340, saveDrawingList),
215       saveIdGlobalDebounce: debounce(340, saveIdGlobal),
216       leftComponents: [
217         {
218           title: '输入型组件',
219           list: inputComponents
220         },
221         {
222           title: '选择型组件',
223           list: selectComponents
224         },
225         {
226           title: '布局型组件',
227           list: layoutComponents
228         }
229       ],
230
231       // 表单参数
232       form: {
233         status: CommonStatusEnum.ENABLE,
234       },
235       // 表单校验
236       rules: {
237         name: [{ required: true, message: "表单名不能为空", trigger: "blur" }],
238         status: [{ required: true, message: "开启状态不能为空", trigger: "blur" }],
239       }
240     }
241   },
242   computed: {
243   },
244   watch: {
245     // eslint-disable-next-line func-names
246     'activeData.__config__.label': function (val, oldVal) {
247       if (
248         this.activeData.placeholder === undefined
249         || !this.activeData.__config__.tag
250         || oldActiveId !== this.activeId
251       ) {
252         return
253       }
254       this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
255     },
256     activeId: {
257       handler(val) {
258         oldActiveId = val
259       },
260       immediate: true
261     },
262     drawingList: {
263       handler(val) {
264         this.saveDrawingListDebounce(val)
265         if (val.length === 0) this.idGlobal = 100
266       },
267       deep: true
268     },
269     idGlobal: {
270       handler(val) {
271         this.saveIdGlobalDebounce(val)
272       },
273       immediate: true
274     }
275   },
276   mounted() {
277     // 【add by iailab】不读缓存
278     // if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
279     //   this.drawingList = drawingListInDB
280     // } else {
281     //   this.drawingList = drawingDefalut
282     // }
283     // this.activeFormItem(this.drawingList[0])
284     // if (formConfInDB) {
285     //   this.formConf = formConfInDB
286     // }
287     loadBeautifier(btf => {
288       beautifier = btf
289     })
290     const clipboard = new ClipboardJS('#copyNode', {
291       text: trigger => {
292         const codeStr = this.generateCode()
293         this.$notify({
294           title: '成功',
295           message: '代码已复制到剪切板,可粘贴。',
296           type: 'success'
297         })
298         return codeStr
299       }
300     })
301     clipboard.on('error', e => {
302       this.$message.error('代码复制失败')
303     })
304   },
305   created() {
306     // 读取表单配置
307     const formId = this.$route.query && this.$route.query.formId
308     if (formId) {
309       getForm(formId).then(response => {
310         const data = response.data
311         this.form = {
312           id: data.id,
313           name: data.name,
314           status: data.status,
315           remark: data.remark
316         }
317         this.formConf = JSON.parse(data.conf)
318         this.drawingList = decodeFields(data.fields)
319         // 设置激活的表单项
320         this.activeData = this.drawingList[0]
321         this.activeId = this.activeData.__config__.formId
322         // 设置 idGlobal,避免重复
323         this.idGlobal += this.drawingList.length
324       });
325     }
326   },
327   methods: {
328     setObjectValueReduce(obj, strKeys, data) {
329       const arr = strKeys.split('.')
330       arr.reduce((pre, item, i) => {
331         if (arr.length === i + 1) {
332           pre[item] = data
333         } else if (!isObjectObject(pre[item])) {
334           pre[item] = {}
335         }
336         return pre[item]
337       }, obj)
338     },
339     setRespData(component, resp) {
340       const { dataPath, renderKey, dataConsumer } = component.__config__
341       if (!dataPath || !dataConsumer) return
342       const respData = dataPath.split('.').reduce((pre, item) => pre[item], resp)
343
344       // 将请求回来的数据,赋值到指定属性。
345       // 以el-tabel为例,根据Element文档,应该将数据赋值给el-tabel的data属性,所以dataConsumer的值应为'data';
346       // 此时赋值代码可写成 component[dataConsumer] = respData;
347       // 但为支持更深层级的赋值(如:dataConsumer的值为'options.data'),使用setObjectValueReduce
348       this.setObjectValueReduce(component, dataConsumer, respData)
349       const i = this.drawingList.findIndex(item => item.__config__.renderKey === renderKey)
350       if (i > -1) this.$set(this.drawingList, i, component)
351     },
352     fetchData(component) {
353       const { dataType, method, url } = component.__config__
354       if (dataType === 'dynamic' && method && url) {
355         this.setLoading(component, true)
356         this.$axios({
357           method,
358           url
359         }).then(resp => {
360           this.setLoading(component, false)
361           this.setRespData(component, resp.data)
362         })
363       }
364     },
365     setLoading(component, val) {
366       const { directives } = component
367       if (Array.isArray(directives)) {
368         const t = directives.find(d => d.name === 'loading')
369         if (t) t.value = val
370       }
371     },
372     activeFormItem(currentItem) {
373       this.activeData = currentItem
374       this.activeId = currentItem.__config__.formId
375     },
376     onEnd(obj) {
377       if (obj.from !== obj.to) {
378         this.fetchData(tempActiveData)
379         this.activeData = tempActiveData
380         this.activeId = this.idGlobal
381       }
382     },
383     addComponent(item) {
384       const clone = this.cloneComponent(item)
385       this.fetchData(clone)
386       this.drawingList.push(clone)
387       this.activeFormItem(clone)
388     },
389     cloneComponent(origin) {
390       const clone = deepClone(origin)
391       const config = clone.__config__
392       config.span = this.formConf.span // 生成代码时,会根据span做精简判断
393       this.createIdAndKey(clone)
394       clone.placeholder !== undefined && (clone.placeholder += config.label)
395       tempActiveData = clone
396       return tempActiveData
397     },
398     createIdAndKey(item) {
399       const config = item.__config__
400       config.formId = ++this.idGlobal
401       config.renderKey = `${config.formId}${+new Date()}` // 改变renderKey后可以实现强制更新组件
402       if (config.layout === 'colFormItem') {
403         item.__vModel__ = `field${this.idGlobal}`
404       } else if (config.layout === 'rowFormItem') {
405         config.componentName = `row${this.idGlobal}`
406         !Array.isArray(config.children) && (config.children = [])
407         delete config.label // rowFormItem无需配置label属性
408       }
409       if (Array.isArray(config.children)) {
410         config.children = config.children.map(childItem => this.createIdAndKey(childItem))
411       }
412       return item
413     },
414     // 获得表单数据
415     AssembleFormData() {
416       this.formData = {
417         fields: deepClone(this.drawingList),
418         ...this.formConf
419       }
420     },
421     save() {
422       // this.AssembleFormData()
423       // console.log(this.formData)
424       this.$refs["form"].validate(valid => {
425         if (!valid) {
426           return;
427         }
428         const form = {
429           conf: JSON.stringify(this.formConf), // 表单配置
430           fields: this.encodeFields(), // 表单项的数组
431           ...this.form // 表单名等
432         }
433         // 修改的提交
434         if (this.form.id != null) {
435           updateForm(form).then(response => {
436             this.$modal.msgSuccess("修改成功");
437             this.close()
438           });
439           return;
440         }
441         // 添加的提交
442         createForm(form).then(response => {
443           this.$modal.msgSuccess("新增成功");
444           this.close()
445         });
446       });
447     },
448     /** 关闭按钮 */
449     close() {
450       this.$tab.closeOpenPage({ path: "/bpm/manager/form" });
451     },
452     encodeFields() {
453       const fields = []
454       this.drawingList.forEach(item => {
455         fields.push(JSON.stringify(item))
456       })
457       return fields
458     },
459     generate(data) {
460       const func = this[`exec${titleCase(this.operationType)}`]
461       this.generateConf = data
462       func && func(data)
463     },
464     execRun(data) {
465       this.AssembleFormData()
466       this.drawerVisible = true
467     },
468     execDownload(data) {
469       const codeStr = this.generateCode()
470       const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
471       saveAs(blob, data.fileName)
472     },
473     execCopy(data) {
474       document.getElementById('copyNode').click()
475     },
476     empty() {
477       this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
478         () => {
479           this.drawingList = []
480           this.idGlobal = 100
481         }
482       )
483     },
484     drawingItemCopy(item, list) {
485       let clone = deepClone(item)
486       clone = this.createIdAndKey(clone)
487       list.push(clone)
488       this.activeFormItem(clone)
489     },
490     drawingItemDelete(index, list) {
491       list.splice(index, 1)
492       this.$nextTick(() => {
493         const len = this.drawingList.length
494         if (len) {
495           this.activeFormItem(this.drawingList[len - 1])
496         }
497       })
498     },
499     generateCode() {
500       const { type } = this.generateConf
501       this.AssembleFormData()
502       const script = vueScript(makeUpJs(this.formData, type))
503       const html = vueTemplate(makeUpHtml(this.formData, type))
504       const css = cssStyle(makeUpCss(this.formData))
505       return beautifier.html(html + script + css, beautifierConf.html)
506     },
507     showJson() {
508       this.AssembleFormData()
509       this.jsonDrawerVisible = true
510     },
511     download() {
512       this.dialogVisible = true
513       this.showFileName = true
514       this.operationType = 'download'
515     },
516     run() {
517       this.dialogVisible = true
518       this.showFileName = false
519       this.operationType = 'run'
520     },
521     copy() {
522       this.dialogVisible = true
523       this.showFileName = false
524       this.operationType = 'copy'
525     },
526     tagChange(newTag) {
527       newTag = this.cloneComponent(newTag)
528       const config = newTag.__config__
529       newTag.__vModel__ = this.activeData.__vModel__
530       config.formId = this.activeId
531       config.span = this.activeData.__config__.span
532       this.activeData.__config__.tag = config.tag
533       this.activeData.__config__.tagIcon = config.tagIcon
534       this.activeData.__config__.document = config.document
535       if (typeof this.activeData.__config__.defaultValue === typeof config.defaultValue) {
536         config.defaultValue = this.activeData.__config__.defaultValue
537       }
538       Object.keys(newTag).forEach(key => {
539         if (this.activeData[key] !== undefined) {
540           newTag[key] = this.activeData[key]
541         }
542       })
543       this.activeData = newTag
544       this.updateDrawingList(newTag, this.drawingList)
545     },
546     updateDrawingList(newTag, list) {
547       const index = list.findIndex(item => item.__config__.formId === this.activeId)
548       if (index > -1) {
549         list.splice(index, 1, newTag)
550       } else {
551         list.forEach(item => {
552           if (Array.isArray(item.__config__.children)) this.updateDrawingList(newTag, item.__config__.children)
553         })
554       }
555     },
556     refreshJson(data) {
557       this.drawingList = deepClone(data.fields)
558       delete data.fields
559       this.formConf = data
560     }
561   }
562 }
563 </script>
564
565 <style lang='scss'>
566 @import '@/styles/home';
567 </style>