houzhongjian
2024-07-11 759b1c71011abd6b58c37d2566f3f3c208c2f1b2
提交 | 用户 | 时间
759b1c 1 <script>
H 2 import { deepClone } from '@/utils/index'
3 import render from '@/components/render/render.js'
4 import {getAccessToken} from "@/utils/auth";
5
6 const ruleTrigger = {
7   'el-input': 'blur',
8   'el-input-number': 'blur',
9   'el-select': 'change',
10   'el-radio-group': 'change',
11   'el-checkbox-group': 'change',
12   'el-cascader': 'change',
13   'el-time-picker': 'change',
14   'el-date-picker': 'change',
15   'el-rate': 'change'
16 }
17
18 const layouts = {
19   colFormItem(h, scheme) {
20     const config = scheme.__config__
21     const listeners = buildListeners.call(this, scheme)
22
23     let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
24     if (config.showLabel === false) labelWidth = '0'
25     return (
26       <el-col span={config.span}>
27         <el-form-item label-width={labelWidth} prop={scheme.__vModel__}
28           label={config.showLabel ? config.label : ''}>
29           <render conf={scheme} on={listeners} />
30         </el-form-item>
31       </el-col>
32     )
33   },
34   rowFormItem(h, scheme) {
35     let child = renderChildren.apply(this, arguments)
36     if (scheme.type === 'flex') {
37       child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
38               {child}
39             </el-row>
40     }
41     return (
42       <el-col span={scheme.span}>
43         <el-row gutter={scheme.gutter}>
44           {child}
45         </el-row>
46       </el-col>
47     )
48   }
49 }
50
51 function renderFrom(h) {
52   const { formConfCopy } = this
53
54   return (
55     <el-row gutter={formConfCopy.gutter}>
56       <el-form
57         size={formConfCopy.size}
58         label-position={formConfCopy.labelPosition}
59         disabled={formConfCopy.disabled}
60         label-width={`${formConfCopy.labelWidth}px`}
61         ref={formConfCopy.formRef}
62         // model不能直接赋值 https://github.com/vuejs/jsx/issues/49#issuecomment-472013664
63         props={{ model: this[formConfCopy.formModel] }}
64         rules={this[formConfCopy.formRules]}
65       >
66         {renderFormItem.call(this, h, formConfCopy.fields)}
67         {formConfCopy.formBtns && formBtns.call(this, h)}
68       </el-form>
69     </el-row>
70   )
71 }
72
73 function formBtns(h) {
74   return <el-col>
75     <el-form-item size="large">
76       <el-button type="primary" onClick={this.submitForm}>提交</el-button>
77       <el-button onClick={this.resetForm}>重置</el-button>
78     </el-form-item>
79   </el-col>
80 }
81
82 function renderFormItem(h, elementList) {
83   const that = this
84   const data = this[this.formConf.formModel]
85   // const formRef = that.$refs[that.formConf.formRef] // 这里直接添加有问题,此时还找不到表单 $refs
86   return elementList.map(scheme => {
87     const config = scheme.__config__
88     const layout = layouts[config.layout]
89
90     // edit by iailab,解决 el-upload 上传的问题
91     // 参考 https://github.com/JakHuang/form-generator/blob/master/src/components/parser/example/Index.vue 实现
92     const vModel = scheme.__vModel__
93     const val = data[vModel]
94     if (scheme.__config__.tag === 'el-upload') {
95       // 回显图片
96       scheme['file-list'] = (val || []).map(url => ({ name: url, url }))
97       // 上传地址 + 请求头
98       scheme.action = process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload"
99       scheme.headers = { Authorization: "Bearer " + getAccessToken() }
100       // 注意 on-success 不能绑定箭头函数!!!
101       scheme['on-success'] = function (response, file, fileList) {
102         if (response.code !== 0) {
103           return;
104         }
105         // 添加到 data 中
106         const prev = data[vModel] || []
107         this.$set(data, vModel, [
108           ...prev,
109           response.data
110         ])
111         // 发起表单校验
112         that.$refs[that.formConf.formRef].validateField(vModel)
113       }
114       // 注意 on-remove 不能绑定箭头函数!!!
115       scheme['on-remove'] = function (file, fileList) {
116         // 移除从 data 中
117         const prev = data[vModel] || []
118         const index = prev.indexOf(file.response.data)
119         if (index === -1) {
120           return
121         }
122         prev.splice(index, 1) // 直接移除即可,无需重复 set,因为 array 是引用
123         // 发起表单校验
124         that.$refs[that.formConf.formRef].validateField(vModel)
125       }
126     }
127
128     if (layout) {
129       return layout.call(this, h, scheme)
130     }
131     throw new Error(`没有与${config.layout}匹配的layout`)
132   })
133 }
134
135 function renderChildren(h, scheme) {
136   const config = scheme.__config__
137   if (!Array.isArray(config.children)) return null
138   return renderFormItem.call(this, h, config.children)
139 }
140
141 function setValue(event, config, scheme) {
142   this.$set(config, 'defaultValue', event)
143   this.$set(this[this.formConf.formModel], scheme.__vModel__, event)
144 }
145
146 function buildListeners(scheme) {
147   const config = scheme.__config__
148   const methods = this.formConf.__methods__ || {}
149   const listeners = {}
150
151   // 给__methods__中的方法绑定this和event
152   Object.keys(methods).forEach(key => {
153     listeners[key] = event => methods[key].call(this, event)
154   })
155   // 响应 render.js 中的 vModel $emit('input', val)
156   listeners.input = event => setValue.call(this, event, config, scheme)
157
158   return listeners
159 }
160
161 export default {
162   components: {
163     render
164   },
165   props: {
166     formConf: {
167       type: Object,
168       required: true
169     }
170   },
171   data() {
172     const data = {
173       formConfCopy: deepClone(this.formConf),
174       [this.formConf.formModel]: {},
175       [this.formConf.formRules]: {}
176     }
177     this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
178     this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
179     return data
180   },
181   methods: {
182     initFormData(componentList, formData) {
183       componentList.forEach(cur => {
184         const config = cur.__config__
185         if (cur.__vModel__) formData[cur.__vModel__] = config.defaultValue
186         if (config.children) this.initFormData(config.children, formData)
187       })
188     },
189     buildRules(componentList, rules) {
190       componentList.forEach(cur => {
191         const config = cur.__config__
192         if (Array.isArray(config.regList)) {
193           if (config.required) {
194             const required = { required: config.required, message: cur.placeholder }
195             if (Array.isArray(config.defaultValue)) {
196               required.type = 'array'
197               required.message = `请至少选择一个${config.label}`
198             }
199             required.message === undefined && (required.message = `${config.label}不能为空`)
200             config.regList.push(required)
201           }
202           rules[cur.__vModel__] = config.regList.map(item => {
203             item.pattern && (item.pattern = eval(item.pattern))
204             item.trigger = ruleTrigger && ruleTrigger[config.tag]
205             return item
206           })
207         }
208         if (config.children) this.buildRules(config.children, rules)
209       })
210     },
211     resetForm() {
212       this.formConfCopy = deepClone(this.formConf)
213       this.$refs[this.formConf.formRef].resetFields()
214     },
215     submitForm() {
216       this.$refs[this.formConf.formRef].validate(valid => {
217         if (!valid) return false
218         // 触发 submit 事件
219         // update by iailab
220         // this.$emit('submit', this[this.formConf.formModel])
221         this.$emit('submit', {
222           conf: this.formConfCopy,
223           values: this[this.formConf.formModel]
224         })
225         return true
226       })
227     }
228   },
229   render(h) {
230     return renderFrom.call(this, h)
231   }
232 }
233 </script>