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">
6           <img :src="logo" alt="logo"> Form Generator
7           <a class="github" href="https://github.com/JakHuang/form-generator" target="_blank">
8             <img src="https://github.githubassets.com/pinned-octocat.svg" alt>
9           </a>
10         </div>
11       </div>
12       <el-scrollbar class="left-scrollbar">
13         <div class="components-list">
14           <div v-for="(item, listIndex) in leftComponents" :key="listIndex">
15             <div class="components-title">
16               <svg-icon icon-class="component" />
17               {{ item.title }}
18             </div>
19             <draggable
20               class="components-draggable"
21               :list="item.list"
22               :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
23               :clone="cloneComponent"
24               draggable=".components-item"
25               :sort="false"
26               @end="onEnd"
27             >
28               <div
29                 v-for="(element, index) in item.list"
30                 :key="index"
31                 class="components-item"
32                 @click="addComponent(element)"
33               >
34                 <div class="components-body">
35                   <svg-icon :icon-class="element.__config__.tagIcon" />
36                   {{ element.__config__.label }}
37                 </div>
38               </div>
39             </draggable>
40           </div>
41         </div>
42       </el-scrollbar>
43     </div>
44
45     <div class="center-board">
46       <div class="action-bar">
47 <!--        <el-button icon="el-icon-video-play" type="text" @click="run">-->
48 <!--          运行-->
49 <!--        </el-button>-->
50         <el-button icon="el-icon-view" type="text" @click="showJson">
51           查看json
52         </el-button>
53         <el-button icon="el-icon-download" type="text" @click="download">
54           导出vue文件
55         </el-button>
56         <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">
57           复制代码
58         </el-button>
59         <el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty">
60           清空
61         </el-button>
62       </div>
63       <el-scrollbar class="center-scrollbar">
64         <el-row class="center-board-row" :gutter="formConf.gutter">
65           <el-form
66             :size="formConf.size"
67             :label-position="formConf.labelPosition"
68             :disabled="formConf.disabled"
69             :label-width="formConf.labelWidth + 'px'"
70           >
71             <draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup">
72               <draggable-item
73                 v-for="(item, index) in drawingList"
74                 :key="item.renderKey"
75                 :drawing-list="drawingList"
76                 :current-item="item"
77                 :index="index"
78                 :active-id="activeId"
79                 :form-conf="formConf"
80                 @activeItem="activeFormItem"
81                 @copyItem="drawingItemCopy"
82                 @deleteItem="drawingItemDelete"
83               />
84             </draggable>
85             <div v-show="!drawingList.length" class="empty-info">
86               从左侧拖入或点选组件进行表单设计
87             </div>
88           </el-form>
89         </el-row>
90       </el-scrollbar>
91     </div>
92
93     <right-panel
94       :active-data="activeData"
95       :form-conf="formConf"
96       :show-field="!!drawingList.length"
97       @tag-change="tagChange"
98       @fetch-data="fetchData"
99     />
100
101     <form-drawer
102       :visible.sync="drawerVisible"
103       :form-data="formData"
104       size="100%"
105       :generate-conf="generateConf"
106     />
107     <json-drawer
108       size="60%"
109       :visible.sync="jsonDrawerVisible"
110       :json-str="JSON.stringify(formData)"
111       @refresh="refreshJson"
112     />
113     <code-type-dialog
114       :visible.sync="dialogVisible"
115       title="选择生成类型"
116       :show-file-name="showFileName"
117       @confirm="generate"
118     />
119     <input id="copyNode" type="hidden">
120   </div>
121 </template>
122
123 <script>
124 import draggable from 'vuedraggable'
125 import { debounce } from 'throttle-debounce'
126 import { saveAs } from 'file-saver'
127 import ClipboardJS from 'clipboard'
128 import render from '@/components/render/render'
129 import FormDrawer from './FormDrawer'
130 import JsonDrawer from './JsonDrawer'
131 import RightPanel from './RightPanel'
132 import {
133   inputComponents, selectComponents, layoutComponents, formConf
134 } from '@/components/generator/config'
135 import {
136   beautifierConf, titleCase, deepClone
137 } from '@/utils'
138 import {
139   makeUpHtml, vueTemplate, vueScript, cssStyle
140 } from '@/components/generator/html'
141 import { makeUpJs } from '@/components/generator/js'
142 import { makeUpCss } from '@/components/generator/css'
143 import drawingDefalut from '@/components/generator/drawingDefalut'
144 import logo from '@/assets/logo/logo.png'
145 import CodeTypeDialog from './CodeTypeDialog'
146 import DraggableItem from './DraggableItem'
147 import {
148   getDrawingList, saveDrawingList, getIdGlobal, saveIdGlobal, getFormConf
149 } from '@/utils/db'
150 import loadBeautifier from '@/utils/loadBeautifier'
151
152 let beautifier
153 const emptyActiveData = { style: {}, autosize: {} }
154 let oldActiveId
155 let tempActiveData
156 const drawingListInDB = getDrawingList()
157 const formConfInDB = getFormConf()
158 const idGlobal = getIdGlobal()
159
160 export default {
161   name: "InfraBuild",
162   components: {
163     draggable,
164     render,
165     FormDrawer,
166     JsonDrawer,
167     RightPanel,
168     CodeTypeDialog,
169     DraggableItem
170   },
171   data() {
172     return {
173       logo,
174       idGlobal,
175       formConf,
176       inputComponents,
177       selectComponents,
178       layoutComponents,
179       labelWidth: 100,
180       drawingList: drawingDefalut,
181       drawingData: {},
182       activeId: drawingDefalut[0].formId,
183       drawerVisible: false,
184       formData: {},
185       dialogVisible: false,
186       jsonDrawerVisible: false,
187       generateConf: null,
188       showFileName: false,
189       activeData: drawingDefalut[0],
190       saveDrawingListDebounce: debounce(340, saveDrawingList),
191       saveIdGlobalDebounce: debounce(340, saveIdGlobal),
192       leftComponents: [
193         {
194           title: '输入型组件',
195           list: inputComponents
196         },
197         {
198           title: '选择型组件',
199           list: selectComponents
200         },
201         {
202           title: '布局型组件',
203           list: layoutComponents
204         }
205       ]
206     }
207   },
208   computed: {
209   },
210   watch: {
211     // eslint-disable-next-line func-names
212     'activeData.__config__.label': function (val, oldVal) {
213       if (
214         this.activeData.placeholder === undefined
215         || !this.activeData.__config__.tag
216         || oldActiveId !== this.activeId
217       ) {
218         return
219       }
220       this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
221     },
222     activeId: {
223       handler(val) {
224         oldActiveId = val
225       },
226       immediate: true
227     },
228     drawingList: {
229       handler(val) {
230         this.saveDrawingListDebounce(val)
231         if (val.length === 0) this.idGlobal = 100
232       },
233       deep: true
234     },
235     idGlobal: {
236       handler(val) {
237         this.saveIdGlobalDebounce(val)
238       },
239       immediate: true
240     }
241   },
242   mounted() {
243     if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
244       this.drawingList = drawingListInDB
245     } else {
246       this.drawingList = drawingDefalut
247     }
248     this.activeFormItem(this.drawingList[0])
249     if (formConfInDB) {
250       this.formConf = formConfInDB
251     }
252     loadBeautifier(btf => {
253       beautifier = btf
254     })
255     const clipboard = new ClipboardJS('#copyNode', {
256       text: trigger => {
257         const codeStr = this.generateCode()
258         this.$notify({
259           title: '成功',
260           message: '代码已复制到剪切板,可粘贴。',
261           type: 'success'
262         })
263         return codeStr
264       }
265     })
266     clipboard.on('error', e => {
267       this.$message.error('代码复制失败')
268     })
269   },
270   methods: {
271     setObjectValueReduce(obj, strKeys, data) {
272       const arr = strKeys.split('.')
273       arr.reduce((pre, item, i) => {
274         if (arr.length === i + 1) {
275           pre[item] = data
276         } else if (pre[item]===undefined) {
277           pre[item] = {}
278         }
279         return pre[item]
280       }, obj)
281     },
282     setRespData(component, resp) {
283       const { dataPath, renderKey, dataConsumer } = component.__config__
284       if (!dataPath || !dataConsumer) return
285       const respData = dataPath.split('.').reduce((pre, item) => pre[item], resp)
286
287       // 将请求回来的数据,赋值到指定属性。
288       // 以el-tabel为例,根据Element文档,应该将数据赋值给el-tabel的data属性,所以dataConsumer的值应为'data';
289       // 此时赋值代码可写成 component[dataConsumer] = respData;
290       // 但为支持更深层级的赋值(如:dataConsumer的值为'options.data'),使用setObjectValueReduce
291       this.setObjectValueReduce(component, dataConsumer, respData)
292       const i = this.drawingList.findIndex(item => item.__config__.renderKey === renderKey)
293       if (i > -1) this.$set(this.drawingList, i, component)
294     },
295     fetchData(component) {
296       const { dataType, method, url } = component.__config__
297       if (dataType === 'dynamic' && method && url) {
298         this.setLoading(component, true)
299         this.$axios({
300           method,
301           url
302         }).then(resp => {
303           this.setLoading(component, false)
304           this.setRespData(component, resp)
305         })
306       }
307     },
308     setLoading(component, val) {
309       const { directives } = component
310       if (Array.isArray(directives)) {
311         const t = directives.find(d => d.name === 'loading')
312         if (t) t.value = val
313       }
314     },
315     activeFormItem(currentItem) {
316       this.activeData = currentItem
317       this.activeId = currentItem.__config__.formId
318     },
319     onEnd(obj) {
320       if (obj.from !== obj.to) {
321         this.fetchData(tempActiveData)
322         this.activeData = tempActiveData
323         this.activeId = this.idGlobal
324       }
325     },
326     addComponent(item) {
327       const clone = this.cloneComponent(item)
328       this.fetchData(clone)
329       this.drawingList.push(clone)
330       this.activeFormItem(clone)
331     },
332     cloneComponent(origin) {
333       const clone = deepClone(origin)
334       const config = clone.__config__
335       config.span = this.formConf.span // 生成代码时,会根据span做精简判断
336       this.createIdAndKey(clone)
337       clone.placeholder !== undefined && (clone.placeholder += config.label)
338       tempActiveData = clone
339       return tempActiveData
340     },
341     createIdAndKey(item) {
342       const config = item.__config__
343       config.formId = ++this.idGlobal
344       config.renderKey = `${config.formId}${+new Date()}` // 改变renderKey后可以实现强制更新组件
345       if (config.layout === 'colFormItem') {
346         item.__vModel__ = `field${this.idGlobal}`
347       } else if (config.layout === 'rowFormItem') {
348         config.componentName = `row${this.idGlobal}`
349         !Array.isArray(config.children) && (config.children = [])
350         delete config.label // rowFormItem无需配置label属性
351       }
352       if (Array.isArray(config.children)) {
353         config.children = config.children.map(childItem => this.createIdAndKey(childItem))
354       }
355       return item
356     },
357     AssembleFormData() {
358       this.formData = {
359         fields: deepClone(this.drawingList),
360         ...this.formConf
361       }
362     },
363     generate(data) {
364       const func = this[`exec${titleCase(this.operationType)}`]
365       this.generateConf = data
366       func && func(data)
367     },
368     execRun(data) {
369       this.AssembleFormData()
370       this.drawerVisible = true
371     },
372     execDownload(data) {
373       const codeStr = this.generateCode()
374       const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
375       saveAs(blob, data.fileName)
376     },
377     execCopy(data) {
378       document.getElementById('copyNode').click()
379     },
380     empty() {
381       this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
382         () => {
383           this.drawingList = []
384           this.idGlobal = 100
385         }).catch(() => {});
386     },
387     drawingItemCopy(item, list) {
388       let clone = deepClone(item)
389       clone = this.createIdAndKey(clone)
390       list.push(clone)
391       this.activeFormItem(clone)
392     },
393     drawingItemDelete(index, list) {
394       list.splice(index, 1)
395       this.$nextTick(() => {
396         const len = this.drawingList.length
397         if (len) {
398           this.activeFormItem(this.drawingList[len - 1])
399         }
400       })
401     },
402     generateCode() {
403       const { type } = this.generateConf
404       this.AssembleFormData()
405       const script = vueScript(makeUpJs(this.formData, type))
406       const html = vueTemplate(makeUpHtml(this.formData, type))
407       const css = cssStyle(makeUpCss(this.formData))
408       return beautifier.html(html + script + css, beautifierConf.html)
409     },
410     showJson() {
411       this.AssembleFormData()
412       this.jsonDrawerVisible = true
413     },
414     download() {
415       this.dialogVisible = true
416       this.showFileName = true
417       this.operationType = 'download'
418     },
419     run() {
420       this.dialogVisible = true
421       this.showFileName = false
422       this.operationType = 'run'
423     },
424     copy() {
425       this.dialogVisible = true
426       this.showFileName = false
427       this.operationType = 'copy'
428     },
429     tagChange(newTag) {
430       newTag = this.cloneComponent(newTag)
431       const config = newTag.__config__
432       newTag.__vModel__ = this.activeData.__vModel__
433       config.formId = this.activeId
434       config.span = this.activeData.__config__.span
435       this.activeData.__config__.tag = config.tag
436       this.activeData.__config__.tagIcon = config.tagIcon
437       this.activeData.__config__.document = config.document
438       if (typeof this.activeData.__config__.defaultValue === typeof config.defaultValue) {
439         config.defaultValue = this.activeData.__config__.defaultValue
440       }
441       Object.keys(newTag).forEach(key => {
442         if (this.activeData[key] !== undefined) {
443           newTag[key] = this.activeData[key]
444         }
445       })
446       this.activeData = newTag
447       this.updateDrawingList(newTag, this.drawingList)
448     },
449     updateDrawingList(newTag, list) {
450       const index = list.findIndex(item => item.__config__.formId === this.activeId)
451       if (index > -1) {
452         list.splice(index, 1, newTag)
453       } else {
454         list.forEach(item => {
455           if (Array.isArray(item.__config__.children)) this.updateDrawingList(newTag, item.__config__.children)
456         })
457       }
458     },
459     refreshJson(data) {
460       this.drawingList = deepClone(data.fields)
461       delete data.fields
462       this.formConf = data
463     }
464   }
465 }
466 </script>
467
468 <style lang='scss'>
469 @import '@/styles/home';
470 </style>