el-plus-crud 0.0.49 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +19 -17
  2. package/dist/el-plus-crud.mjs +3605 -3453
  3. package/example/App.vue +1 -1
  4. package/lib/components/el-plus-form/ElPlusForm.vue +775 -771
  5. package/lib/components/el-plus-form/ElPlusFormDialog.vue +1 -1
  6. package/lib/components/el-plus-form/components/ElPlusFormArea.vue +62 -62
  7. package/lib/components/el-plus-form/components/ElPlusFormBtn.vue +103 -103
  8. package/lib/components/el-plus-form/components/ElPlusFormBtns.vue +140 -134
  9. package/lib/components/el-plus-form/components/ElPlusFormCascader.vue +64 -64
  10. package/lib/components/el-plus-form/components/ElPlusFormCascaderPanel.vue +54 -54
  11. package/lib/components/el-plus-form/components/ElPlusFormCheckbox.vue +45 -45
  12. package/lib/components/el-plus-form/components/ElPlusFormCheckboxButton.vue +45 -45
  13. package/lib/components/el-plus-form/components/ElPlusFormColor.vue +39 -39
  14. package/lib/components/el-plus-form/components/ElPlusFormDate.vue +40 -40
  15. package/lib/components/el-plus-form/components/ElPlusFormDaterange.vue +47 -47
  16. package/lib/components/el-plus-form/components/ElPlusFormDatetime.vue +41 -41
  17. package/lib/components/el-plus-form/components/ElPlusFormDatetimerange.vue +47 -47
  18. package/lib/components/el-plus-form/components/ElPlusFormInput.vue +62 -62
  19. package/lib/components/el-plus-form/components/ElPlusFormLink.vue +282 -282
  20. package/lib/components/el-plus-form/components/ElPlusFormLkuser.vue +490 -490
  21. package/lib/components/el-plus-form/components/ElPlusFormNbinput.vue +54 -54
  22. package/lib/components/el-plus-form/components/ElPlusFormNumber.vue +147 -147
  23. package/lib/components/el-plus-form/components/ElPlusFormPassword.vue +44 -44
  24. package/lib/components/el-plus-form/components/ElPlusFormQuickInput.vue +96 -96
  25. package/lib/components/el-plus-form/components/ElPlusFormRadio.vue +68 -68
  26. package/lib/components/el-plus-form/components/ElPlusFormRate.vue +54 -54
  27. package/lib/components/el-plus-form/components/ElPlusFormSelect.vue +162 -162
  28. package/lib/components/el-plus-form/components/ElPlusFormSlider.vue +39 -39
  29. package/lib/components/el-plus-form/components/ElPlusFormSwitch.vue +39 -39
  30. package/lib/components/el-plus-form/components/ElPlusFormTextarea.vue +50 -50
  31. package/lib/components/el-plus-form/components/ElPlusFormTransfer.vue +45 -45
  32. package/lib/components/el-plus-form/components/ElPlusFormTree.vue +77 -77
  33. package/lib/components/el-plus-form/components/ElPlusFormTreeSelect.vue +60 -60
  34. package/lib/components/el-plus-form/components/ElPlusFormUpbtn.vue +144 -0
  35. package/lib/components/el-plus-form/components/ElPlusFormUpload.vue +369 -369
  36. package/lib/components/el-plus-form/mixins/index.ts +2 -2
  37. package/lib/components/el-plus-table/components/header.vue +283 -260
  38. package/lib/components-list.ts +2 -0
  39. package/package.json +1 -1
  40. package/types/formList.d.ts +447 -446
@@ -1,771 +1,775 @@
1
- <template>
2
- <div :style="formLayout">
3
- <slot name="top" :formData="props.modelValue"></slot>
4
- <div :style="{ display: 'flex', justifyContent: isDialog ? 'center' : '' }">
5
- <el-form class="el-plus-form-panel" ref="refElPlusForm" :model="props.modelValue" @submit.prevent="handleSubmitForm" v-bind="computedFormAttrs">
6
- <el-row :gutter="10" v-for="(formList, index) in attrMapToTableList" :key="index" :style="{ marginRight: isTable ? '20px' : 0 }">
7
- <el-col v-for="(formItem, y) in formList" :key="index + '-' + y + '-' + formItem.field" :xs="24" :sm="24" :md="formItem.colspan && formItem.colspan >= column ? 24 : column >= 2 ? 12 : 24" :lg="formItem.colspan && formItem.colspan >= column ? 24 : Math.floor((24 / column) * (formItem.colspan || 1))" :xl="formItem.colspan && formItem.colspan >= column ? 24 : Math.floor((24 / column) * (formItem.colspan || 1))">
8
- <div v-if="formItem._vif" class="el-plus-form-column-panel" :style="{ 'justify-content': isTable ? 'flex-end' : 'flex-start' }">
9
- <el-form-item style="min-height: 40px; display: flex" :label="showLabel && formItem.showLabel !== false ? formItem._label : null" :label-width="formItem.labelWidth || labelWidth || (isDialog ? '100px' : '120px')" :prop="formItem.field" :style="{ width: formItem._attrs?.width || formItem.width || (isTable ? '150px' : '100%') }">
10
- <component style="min-width: 80px; width: 100%" :is="formItem._type" :formData="props.modelValue" :disabled="formItem._disabled ?? disabled ?? false" :readonly="readonly ?? false" v-bind="formItem._attrs" :desc="formItem" :ref="setComponentRef" :field="formItem.field" v-model="props.modelValue[formItem.field || '']" :isTable="isTable" @validateThis="() => handelValidateThis(formItem.field || '')"></component>
11
- <div class="el-plus-form-tip" v-if="formItem._tip" v-html="formItem._tip" />
12
- </el-form-item>
13
- </div>
14
- </el-col>
15
- </el-row>
16
- <slot name="row"></slot>
17
- </el-form>
18
- </div>
19
- <slot name="default"></slot>
20
- <el-form>
21
- <slot :data="props.modelValue" name="form-btn">
22
- <div v-if="btnList && btnList.length > 0" style="display: flex; justify-content: center">
23
- <template v-for="(btn, index) of btnList" :key="index">
24
- <ElPlusFormBtn v-bind="btn"></ElPlusFormBtn>
25
- </template>
26
- </div>
27
- </slot>
28
- <slot name="bottom" :formData="props.modelValue"></slot>
29
- </el-form>
30
- <slot name="form-footer" :formData="props.modelValue"></slot>
31
- </div>
32
- </template>
33
- <script lang="ts">
34
- export default {
35
- name: 'ElPlusForm',
36
- inheritAttrs: false,
37
- customOptions: {}
38
- }
39
- </script>
40
- <script lang="ts" setup>
41
- import { ref, computed, useAttrs, nextTick, onMounted, watch, inject } from 'vue'
42
- import { throttle } from 'lodash'
43
- import { castArray, isMobile } from './util'
44
- import * as validates from './util/validate'
45
- import { typeList } from './components/index'
46
- import ElPlusFormBtn from './components/ElPlusFormBtn.vue'
47
- import { cloneDeep } from 'lodash'
48
-
49
- // IFormProps定义
50
- export interface IFormProps {
51
- // 表单描述
52
- formDesc?: IFormDesc | null
53
- // 表单数据
54
- modelValue?: { [key: string]: any } | {}
55
- // 表单自身属性
56
- formAttrs?: { [key: string]: any }
57
- // 校检规则
58
- rules?: { [key: string]: any } | null
59
- // 提交状态
60
- isLoading?: boolean
61
- // 提交函数
62
- requestFn?: Function | null
63
- // 表单更新的函数
64
- updateFn?: Function | null
65
- // 是否显示底部按钮组
66
- showBtns?: boolean
67
- // 是否显示submit按钮
68
- showSubmit?: boolean
69
- // 提交按钮文本
70
- submitBtnText?: string
71
- // 是否显示 cancel 取消按钮
72
- // 默认值: isDialog = true 时, 默认值为 true
73
- showCancel?: boolean
74
- // 取消按钮文本
75
- cancelBtnText?: string
76
- // 是否显示重置按钮
77
- showReset?: boolean
78
- // 重置按钮文本
79
- resetBtnText?: string
80
- // 是否显示label
81
- showLabel?: boolean
82
- // label宽度
83
- labelWidth?: number | string
84
- // 全局禁用表单
85
- disabled?: boolean
86
- // 全局的readonly
87
- readonly?: boolean
88
- // 是否为弹窗
89
- isDialog?: boolean
90
- // options 的请求方法
91
- optionsFn?: Function | null
92
- // 表单全局size
93
- size?: string
94
- // 表单列 默认1
95
- column?: number
96
- // 是否是列表头部的表单
97
- isTable?: boolean
98
- // 唯一标识符。默认为id
99
- idKey?: string
100
- // 最大宽度
101
- maxWidth?: string
102
- // 比如 beforeValidate, beforeRequest, success, requestError, requestEnd
103
- // 其他钩子 直接放到attrs里面去了
104
- }
105
-
106
- const defaultConf = inject('defaultConf') as ICRUDConfig
107
- // 定义全局的format对象
108
- const elPlusFormFormat = inject('format') as any
109
-
110
- const emits = defineEmits(['request', 'reset', 'cancel'])
111
- const props = withDefaults(defineProps<IFormProps>(), {
112
- // 表单描述
113
- formDesc: null,
114
- // 表单数据
115
- modelValue: () => {
116
- return {}
117
- },
118
- // 表单自身属性
119
- formAttrs: () => {
120
- return {}
121
- },
122
- // 校检规则
123
- rules: null,
124
- // 提交状态
125
- isLoading: false,
126
- // 提交函数
127
- requestFn: null,
128
- // 表单更新的函数
129
- updateFn: null,
130
- // 是否显示按钮
131
- showBtns: true,
132
- // 是否显示submit按钮
133
- showSubmit: true,
134
- // 提交按钮文本
135
- submitBtnText: '提交',
136
- // 是否显示 cancel 取消按钮
137
- // 默认值: isDialog = true 时, 默认值为 true
138
- showCancel: false,
139
- // 取消按钮
140
- cancelBtnText: '取消',
141
- // 是否显示重置按钮
142
- showReset: true,
143
- // 重置按钮
144
- resetBtnText: '重置',
145
- // 是否显示标签
146
- showLabel: true,
147
- // 标签宽度
148
- labelWidth: '',
149
- // 全局禁用表单
150
- disabled: false,
151
- // 全局的readonly
152
- readonly: false,
153
- // 是否为弹窗
154
- isDialog: false,
155
- // options 的请求方法
156
- optionsFn: null,
157
- // 表单全局size
158
- size: 'small',
159
- // 表单列 默认1
160
- column: 1,
161
- // 是否是列表头部的表单
162
- isTable: false,
163
- // 唯一标识符。默认为id
164
- idKey: 'id'
165
- // 其他钩子 直接放到attrs里面去了
166
- // 比如 beforeValidate, beforeRequest, success, requestError, requestEnd
167
- })
168
-
169
- const refElPlusForm = ref()
170
-
171
- // 是否开启全局监听
172
- const isOpenListen = ref(false)
173
-
174
- const tempAttr = useAttrs()
175
-
176
- // 每个表单项的ref
177
- const fieldRefs_ = ref([] as Array<any>)
178
-
179
- // loading
180
- const innerIsLoading = ref(false)
181
-
182
- let oldFormData = null as any
183
-
184
- const size = computed(() => props.size || defaultConf.size)
185
- // 组件名称列表
186
- const compTypeList = [...typeList, ...(defaultConf.form?.comList || [])]
187
-
188
- // 整体的布局方式
189
- const formLayout = computed(() => ({ display: 'flex', flexDirection: props.isTable ? 'row' : 'column' })) as any
190
-
191
- // 表单的Attrs
192
- const computedFormAttrs = computed(() => {
193
- return {
194
- scrollToError: true,
195
- ...props.formAttrs,
196
- labelWidth: props.labelWidth === 'auto' ? (props.isDialog ? '100px' : '120px') : parseInt(props.labelWidth + '') + 'px',
197
- // validateOnRuleChange: false,
198
- disabled: props.disabled || innerIsLoading.value,
199
- rules: computedRules,
200
- labelPosition: isMobile() ? 'top' : 'right',
201
- style: {
202
- width: props.maxWidth || (props.isTable ? '100%' : props.isDialog ? '80%' : '1000px'),
203
- paddingRight: props.isTable ? '0' : '20px'
204
- }
205
- }
206
- })
207
-
208
- // 合并校验规则
209
- const computedRules = computed(() => {
210
- // 首先拿到表单总体传入的rules
211
- const tempRules = props.rules || []
212
- // 遍历属性描述对象,看看里面有没有校验规则
213
- if (props.formDesc) {
214
- Object.keys(props.formDesc).map((field: any) => {
215
- if (!tempRules[field]) tempRules[field] = []
216
- if (props.formDesc) {
217
- if (props.formDesc[field].rules) {
218
- if (typeof props.formDesc[field].rules === 'string') {
219
- // string的话,就行默认校验规则中取
220
- tempRules[field].push(...(validates as any)[props.formDesc[field].rules as string])
221
- } else {
222
- // 查看总体规则中是否已经含有了该属性的校验
223
- castArray(props.formDesc[field].rules).map((item: any) => {
224
- tempRules[field].push(item)
225
- })
226
- }
227
- } else if (props.formDesc[field].required || props.formDesc[field].require) {
228
- // 如果直接指定 required || require,手动添加校验信息
229
- //
230
- let rules = 'notAllBlank'
231
- switch (props.formDesc[field].type) {
232
- case 'upload':
233
- case 'select':
234
- case 'password':
235
- case 'textarea':
236
- case 'editor':
237
- rules = props.formDesc[field].type || ''
238
- break
239
- case 'cascader':
240
- case 'tselect':
241
- case 'linkuser':
242
- case 'radio':
243
- rules = 'select'
244
- break
245
- }
246
- tempRules[field].push(...(validates as any)[rules])
247
- }
248
- }
249
- })
250
- }
251
- return tempRules
252
- })
253
-
254
- // 属性对象转数组
255
- const attrMapToTableList = computed(() => {
256
- const formLayoutRows = [] as Array<Array<IFormDescItem>>
257
- if (props.formDesc) {
258
- let tempData = [] as Array<IFormDescItem>
259
- for (const key in props.formDesc) {
260
- tempData.push({ ...props.formDesc[key], field: key })
261
- }
262
- // 这里处理一下layout的布局-渲染
263
- let rowItemList = [] as Array<IFormDescItem>
264
- let tempCount = 0
265
- tempData.map((item) => {
266
- if (item._vif) {
267
- rowItemList.push(item)
268
- tempCount++
269
- if (item.colspan) {
270
- tempCount += item.colspan - 1
271
- }
272
- if (tempCount >= props.column) {
273
- // 一行结束
274
- formLayoutRows.push(rowItemList)
275
- rowItemList = [] as Array<IFormDescItem>
276
- tempCount = 0
277
- return
278
- }
279
- }
280
- })
281
- if (rowItemList.length > 0) {
282
- formLayoutRows.push(rowItemList)
283
- }
284
- }
285
- return formLayoutRows
286
- })
287
-
288
- // 整体初始化属性
289
- const initFormAttrs = throttle(() => {
290
- if (props.formDesc) {
291
- Object.keys(props.formDesc).forEach((field) => {
292
- if (props.formDesc) {
293
- const formItem = props.formDesc[field]
294
- if (formItem && formItem.type) {
295
- // 设置 _type
296
- formItem._type = compTypeList.includes(formItem.type.toLowerCase()) ? 'el-plus-form-' + formItem.type : formItem.type
297
- // 触发 v-if 显示 / 隐藏 设置_vif
298
- formItem._vif = handelKeyValue(formItem, 'vif', field, !formItem.isBlank ?? true)
299
- // 触发 disabled 禁用 / 启用 设置_disabled
300
- formItem._disabled = handelKeyValue(formItem, 'disabled', field, props.disabled ?? false)
301
- // 动态属性 attrs
302
- const tempAttr = {} as any
303
- // if ((typeof formItem.attrs === 'object' && formItem.attrs?.options) || formItem.options) {
304
- // tempAttr.options = (formItem.attrs as any)?.options || formItem.options
305
- // }
306
- formItem._attrs = Object.assign({}, handelKeyValue(formItem, 'attrs', field), tempAttr)
307
- // 动态options
308
- // formItem._options = handelKeyValue(formItem, 'options', field)
309
- // 动态 _label
310
- formItem._label = handelKeyValue(formItem, 'label', field)
311
- // 动态 prop
312
- // formItem._prop = handelKeyValue(formItem, 'prop', field);
313
- // 动态 _tip
314
- formItem._tip = handelKeyValue(formItem, 'tip', field)
315
- // 单独处理下上传
316
- if (!formItem._tip && !formItem.noTip && formItem.type === 'upload') {
317
- formItem._tip = `最多上传${formItem.multiple ? formItem.limit || 20 : 1}${formItem.upType === 'file' ? '个文件' : '张图片'}`
318
- }
319
- // 这里初始化一下默认值
320
- if (formItem.default !== undefined && formItem.default !== null && props.modelValue[field] === undefined) {
321
- props.modelValue[field] = formItem.default
322
- }
323
- // 这里初始化一下默认选中项
324
- if (formItem.defaultItem !== undefined && formItem.defaultItem !== null && props.modelValue[field] === undefined) {
325
- props.modelValue[field] = formItem.defaultItem.value
326
- }
327
- // 这里格式化一下数据
328
- // if (formItem.format) {
329
- // if (typeof formItem.format === 'string') {
330
- // props.modelValue[field] = elPlusFormFormat[formItem.format](props.modelValue[field], props.modelValue)
331
- // } else if (typeof formItem.format === 'function') {
332
- // props.modelValue[field] = (formItem as any).format(props.modelValue[field], props.modelValue)
333
- // } else {
334
- // // console.log('未知的格式化类型:', formItem.format)
335
- // }
336
- // }
337
- } else if (formItem && formItem.isBlank) {
338
- // 触发 v-if 显示 / 隐藏 设置_vif
339
- formItem._vif = handelKeyValue(formItem, 'vif', '', true)
340
- }
341
- }
342
- })
343
- }
344
- }, 500)
345
-
346
- // 表单底部按钮
347
- const btnList = computed(() => {
348
- let btnList_ = [] as any[]
349
- if (!props.showBtns) {
350
- return btnList_
351
- }
352
- // 取消按钮
353
- if (props.showCancel) {
354
- btnList_.push({
355
- field: '_reset_btn',
356
- desc: {
357
- label: props.cancelBtnText || '取消',
358
- size: size.value,
359
- disabled: innerIsLoading.value,
360
- on: { click: () => emits('cancel') }
361
- }
362
- })
363
- }
364
- // 重置按钮
365
- if (props.showReset) {
366
- btnList_.push({
367
- field: '_reset_btn',
368
- desc: {
369
- label: props.resetBtnText || '重置',
370
- confirm: `确定要${props.resetBtnText || '重置'}?`,
371
- size: size.value,
372
- disabled: innerIsLoading.value,
373
- on: { click: reset }
374
- }
375
- })
376
- }
377
- // 提交按钮
378
- if (props.showSubmit) {
379
- btnList_.push({
380
- field: '_reset_btn',
381
- desc: {
382
- label: props.submitBtnText || '提交',
383
- size: size.value,
384
- type: 'primary',
385
- loading: props.isLoading || innerIsLoading.value,
386
- on: { click: handleSubmitForm }
387
- }
388
- })
389
- }
390
- return btnList_
391
- })
392
-
393
- // 处理属性
394
- const handelKeyValue = (formItem: IFormDescItem, key: string, field: string, defVal: number | boolean | string | null = null) => {
395
- if (typeof formItem[key] === 'function') {
396
- // 如果有方法类型的判断,则需要启用动态监测
397
- isOpenListen.value = true
398
- return formItem[key](props.modelValue, props.modelValue[field])
399
- } else if (typeof formItem[key] === 'boolean') {
400
- return formItem[key] ?? false
401
- } else if (typeof formItem[key] === 'string') {
402
- return formItem[key] ?? ''
403
- } else {
404
- return formItem[key] ?? defVal
405
- }
406
- }
407
-
408
- // 验证表单
409
- const validateForm = () => {
410
- return new Promise((resolve: any, reject: any) => {
411
- if (computedRules.value) {
412
- // 当传递了验证规则
413
- refElPlusForm.value.validate((valid: Boolean, invalidFields: any) => {
414
- if (valid) {
415
- // 验证通过
416
- resolve()
417
- } else {
418
- // 显示错误
419
- reject(invalidFields)
420
- }
421
- })
422
- } else {
423
- resolve()
424
- }
425
- })
426
- }
427
-
428
- // 设置子组件的ref-重置form的时候需要用到
429
- const setComponentRef = (el: any) => {
430
- if (!el) return
431
- if (fieldRefs_.value.filter((item: any) => item.field === el.field).length <= 0) {
432
- fieldRefs_.value.push(el)
433
- }
434
- }
435
-
436
- /**
437
- * 处理单个表单值
438
- * @param val
439
- */
440
- const handelValToForm = (desc: IFormDescItem, field: string, val: any) => {
441
- const result = {} as { [key: string]: any }
442
- if (!desc) return { [field]: val }
443
- if (!desc._vif || val === undefined || val === null) return result
444
- if (desc.type === 'category') {
445
- if (val === null) val = []
446
- // 如果是直接显示结果
447
- if (desc.checkStrictly === true) {
448
- result.categoryId = val[2] ?? val[1] ?? val[0] ?? ''
449
- } else {
450
- for (let i = 0; i < 3; i++) {
451
- result['categoryId' + (i + 1)] = val[i] ?? ''
452
- }
453
- }
454
- } else if (desc.type === 'area') {
455
- if (val === null) val = [null, null, null, null]
456
- const [pid, cid, zid, sid] = val
457
- if (desc.checkStrictly) {
458
- result[field] = sid || zid || cid || pid || null
459
- } else {
460
- result.provinceId = pid || -1
461
- result.cityId = cid || -1
462
- result.zoneId = zid || -1
463
- result.streetId = sid || -1
464
- }
465
- } else if (desc.type === 'daterange') {
466
- if (val && val.length === 2) {
467
- result.startTime = val[0]
468
- if (typeof val[1] === 'string') {
469
- result.endTime = new Date(val[1]).getTime()
470
- } else {
471
- result.endTime = val[1]
472
- }
473
- result.endTime = result.endTime + (24 * 60 * 60 - 1) * 1000
474
- // 再处理一下时间戳
475
- result.startTime = elPlusFormFormat.time(result.startTime, 3)
476
- result.endTime = elPlusFormFormat.time(result.endTime, 3)
477
- }
478
- } else if (desc.type === 'datetimerange') {
479
- if (val && val.length === 2) {
480
- // 处理一下时间戳
481
- result.startTime = elPlusFormFormat.time(val[0], 3)
482
- result.endTime = elPlusFormFormat.time(val[1], 3)
483
- }
484
- } else if (desc.type === 'linkuser') {
485
- const [userIds, deptIds, userNames, deptNames] = val
486
- result.userIds = userIds
487
- result.deptIds = deptIds
488
- result.userNames = userNames
489
- result.deptNames = deptNames
490
- } else {
491
- // 这里处理下通用表单的数据类型
492
- switch (desc.type) {
493
- case 'checkbox':
494
- if (Array.isArray(val)) {
495
- val = val.join(',')
496
- }
497
- break
498
- }
499
- result[field] = val
500
- }
501
- return result
502
- }
503
-
504
- /**
505
- * 获取处理过后的表单数据
506
- */
507
- const getFormData = () => {
508
- const tempData = {} as { [key: string]: any }
509
- if (props.formDesc) {
510
- // 循环获取表单数据
511
- Object.keys(props.modelValue).map((key) => {
512
- if (['provinceId', 'cityId', 'zoneId', 'streetId'].indexOf(key) >= 0) return
513
- if (props.formDesc) {
514
- Object.assign(tempData, handelValToForm(props.formDesc[key], key, props.modelValue[key]))
515
- }
516
- })
517
- }
518
- return tempData
519
- }
520
-
521
- /**
522
- * 刷新验证码图片
523
- */
524
- const changeValidImg = () => {
525
- fieldRefs_.value.map((item) => {
526
- if (item.CID) {
527
- item.changeValidImg()
528
- }
529
- })
530
- }
531
-
532
- /**
533
- * 提交表单
534
- */
535
- const handleSubmitForm = async () => {
536
- let postData = getFormData()
537
- try {
538
- // 校验表单事件处理
539
- if (tempAttr.beforeValidate) {
540
- const isPass = await (tempAttr.beforeValidate as Function)(postData)
541
- if (isPass === false) return
542
- }
543
- await validateForm()
544
- for (const field in postData) {
545
- // 去除下划线开头的参数
546
- if (field.indexOf('_') === 0) {
547
- delete postData[field]
548
- continue
549
- }
550
- if (props.formDesc) {
551
- const formItem = props.formDesc[field]
552
- // valueFormatter的处理
553
- if (formItem && formItem.valueFormat) {
554
- postData[field] = formItem.valueFormat(postData[field], postData)
555
- }
556
- // 处理带有true或者false值
557
- if (postData[field] === true || postData[field] === false) {
558
- postData[field] = +postData[field]
559
- }
560
- }
561
- }
562
-
563
- // 提交数据前的通知
564
- if (tempAttr.beforeRequest) {
565
- const beforeRequestData = await (tempAttr.beforeRequest as Function)(postData)
566
- if (beforeRequestData === false) return
567
- if (typeof beforeRequestData === 'object') {
568
- postData = beforeRequestData
569
- }
570
- }
571
- if (props.requestFn) {
572
- // 在内部调用请求
573
- if (innerIsLoading.value) return
574
- innerIsLoading.value = true
575
- try {
576
- let response = {}
577
- if (props.updateFn && postData && (postData as any)[props.idKey]) {
578
- response = await props.updateFn(postData)
579
- } else {
580
- response = await props.requestFn(postData)
581
- }
582
- nextTick(() => {
583
- if (tempAttr.success && typeof tempAttr.success === 'function') {
584
- tempAttr.success({ response, formData: props.modelValue, callback: () => (innerIsLoading.value = false) } as IFormBack)
585
- }
586
- })
587
- } catch (error) {
588
- // 如果用户有处理异常的方法了
589
- if (tempAttr.requestError && typeof tempAttr.requestError === 'function') {
590
- tempAttr.requestError(error)
591
- } else {
592
- // 处理异常情况
593
- if (error instanceof Error) {
594
- // 返回的是Error类型, 则进行解析
595
- try {
596
- const msg = JSON.parse(error.message)
597
- if (msg instanceof Object) {
598
- // innerFormError.value = msg;
599
- }
600
- // eslint-disable-next-line
601
- } catch {}
602
- } else if (error instanceof Object) {
603
- // 返回的是对象类型, 则直接设置
604
- // innerFormError = error;
605
- }
606
- if (tempAttr.requestError && typeof tempAttr.requestError === 'function') {
607
- tempAttr.requestError()
608
- }
609
- }
610
- // 报错了这里恢复
611
- innerIsLoading.value = false
612
- } finally {
613
- if (!props.isDialog) {
614
- innerIsLoading.value = false
615
- }
616
- if (tempAttr.requestEnd && typeof tempAttr.requestEnd === 'function') {
617
- tempAttr.requestEnd()
618
- }
619
- }
620
- } else {
621
- // 在外部用户自己处理请求
622
- if (props.isLoading) return
623
- emits('request', postData)
624
- }
625
- } catch (error) {
626
- // eslint-disable-next-line no-console
627
- console.log('error: ', error)
628
- }
629
- }
630
-
631
- /**
632
- * 重置,并清除校验
633
- */
634
- const reset = () => {
635
- // refElPlusForm.value.resetFields();
636
- // 重置
637
- Object.keys(props.modelValue).map((key) => {
638
- props.modelValue[key] = oldFormData ? oldFormData[key] : (props.formDesc && props.formDesc[key] && props.formDesc[key].default) ?? null
639
- })
640
- setTimeout(() => {
641
- // 清空校验
642
- clearValid()
643
- }, 100)
644
- // 通知外部
645
- emits('reset')
646
- }
647
-
648
- /**
649
- * 清空校验
650
- */
651
- const clearValid = () => {
652
- refElPlusForm.value?.clearValidate()
653
- }
654
-
655
- /**
656
- * 清理
657
- */
658
- const clear = () => {
659
- // 这里进行数据清空
660
- if (props.modelValue) {
661
- Object.keys(props.modelValue).map((key) => {
662
- props.modelValue[key] = null
663
- })
664
- }
665
- // 重置校验
666
- nextTick(() => {
667
- setTimeout(() => {
668
- clearValid()
669
- }, 10)
670
- })
671
- }
672
-
673
- /**
674
- * 单独校验一下字段
675
- * @param field
676
- */
677
- function handelValidateThis(field: string) {
678
- refElPlusForm.value.validateField(field, (err: any) => {
679
- // console.log('err: ', err)
680
- })
681
- }
682
-
683
- /**
684
- * 刷新表单
685
- */
686
- function refresh() {
687
- // 重新刷新表单
688
- initFormAttrs()
689
- // 重置校验
690
- nextTick(() => {
691
- clearValid()
692
- })
693
- }
694
-
695
- // 监听Desc改变
696
- watch(
697
- () => props.formDesc,
698
- (formDesc) => {
699
- if (formDesc) {
700
- // 检查联动
701
- initFormAttrs()
702
- }
703
- }
704
- )
705
-
706
- // 深度监听data改变
707
- watch(
708
- () => props.modelValue,
709
- (data) => {
710
- if (!oldFormData || (isOpenListen.value && data && JSON.stringify(data) !== JSON.stringify(oldFormData))) {
711
- // 检查联动
712
- if (JSON.stringify(data) !== JSON.stringify(oldFormData)) {
713
- oldFormData = cloneDeep(data)
714
- initFormAttrs()
715
- }
716
- }
717
- },
718
- { deep: true }
719
- )
720
-
721
- onMounted(async () => {
722
- // 检查联动
723
- if (!isOpenListen.value) {
724
- initFormAttrs()
725
- }
726
- nextTick(() => {
727
- clearValid()
728
- })
729
- })
730
-
731
- // 暴露对外方法
732
- defineExpose({ submit: handleSubmitForm, getData: getFormData, validate: validateForm, reset, clearValid, clear, changeValidImg, refresh })
733
- </script>
734
- <style lang="scss">
735
- .el-plus-form-panel {
736
- .el-plus-form--inline .el-plus-form-btns {
737
- width: auto;
738
- }
739
-
740
- .el-form--inline .el-form-item {
741
- margin-right: 12px !important;
742
- }
743
-
744
- .el-plus-form-col--break {
745
- clear: both;
746
- }
747
-
748
- .el-plus-form-tip {
749
- color: #909399;
750
- line-height: 1.5em;
751
- margin-top: 3px;
752
- }
753
-
754
- .el-plus-form-tip code {
755
- padding: 2px 4px;
756
- font-size: 90%;
757
- color: #c7254e;
758
- background-color: #f9f2f4;
759
- border-radius: 4px;
760
- }
761
- .el-plus-form-column-panel {
762
- & > .el-form-item--default {
763
- margin-bottom: 18px !important;
764
- & > .el-form-item__label {
765
- line-height: 40px;
766
- margin-bottom: 0;
767
- }
768
- }
769
- }
770
- }
771
- </style>
1
+ <template>
2
+ <div :style="formLayout">
3
+ <slot name="top" :formData="props.modelValue"></slot>
4
+ <div :style="{ display: 'flex', justifyContent: isDialog ? 'center' : '' }">
5
+ <el-form class="el-plus-form-panel" ref="refElPlusForm" :model="props.modelValue" @submit.prevent="handleSubmitForm" v-bind="computedFormAttrs">
6
+ <el-row :gutter="10" v-for="(formList, index) in attrMapToTableList" :key="index" :style="{ marginRight: isTable ? '20px' : 0 }">
7
+ <el-col v-for="(formItem, y) in formList" :key="index + '-' + y + '-' + formItem.field" :xs="24" :sm="24" :md="formItem.colspan && formItem.colspan >= column ? 24 : column >= 2 ? 12 : 24" :lg="formItem.colspan && formItem.colspan >= column ? 24 : Math.floor((24 / column) * (formItem.colspan || 1))" :xl="formItem.colspan && formItem.colspan >= column ? 24 : Math.floor((24 / column) * (formItem.colspan || 1))">
8
+ <div v-if="formItem._vif" class="el-plus-form-column-panel" :style="{ 'justify-content': isTable ? 'flex-end' : 'flex-start' }">
9
+ <el-form-item style="min-height: 40px; display: flex" :label="showLabel && formItem.showLabel !== false ? formItem._label : null" :label-width="formItem.labelWidth || labelWidth || (isDialog ? '100px' : '120px')" :prop="formItem.field" :style="{ width: formItem._attrs?.width || formItem.width || (isTable ? '150px' : '100%') }">
10
+ <component style="min-width: 80px; width: 100%" :is="formItem._type" :formData="props.modelValue" :disabled="formItem._disabled ?? disabled ?? false" :readonly="readonly ?? false" v-bind="formItem._attrs" :desc="formItem" :ref="setComponentRef" :field="formItem.field" v-model="props.modelValue[formItem.field || '']" :isTable="isTable" @validateThis="() => handelValidateThis(formItem.field || '')"></component>
11
+ <div class="el-plus-form-tip" v-if="formItem._tip" v-html="formItem._tip" />
12
+ </el-form-item>
13
+ </div>
14
+ </el-col>
15
+ </el-row>
16
+ <slot name="row"></slot>
17
+ </el-form>
18
+ </div>
19
+ <slot name="default"></slot>
20
+ <el-form>
21
+ <slot :data="props.modelValue" name="form-btn">
22
+ <div v-if="btnList && btnList.length > 0" style="display: flex; justify-content: center">
23
+ <template v-for="(btn, index) of btnList" :key="index">
24
+ <ElPlusFormBtn v-bind="btn"></ElPlusFormBtn>
25
+ </template>
26
+ </div>
27
+ </slot>
28
+ <slot name="bottom" :formData="props.modelValue"></slot>
29
+ </el-form>
30
+ <slot name="form-footer" :formData="props.modelValue"></slot>
31
+ </div>
32
+ </template>
33
+ <script lang="ts">
34
+ export default {
35
+ name: 'ElPlusForm',
36
+ inheritAttrs: false,
37
+ customOptions: {}
38
+ }
39
+ </script>
40
+ <script lang="ts" setup>
41
+ import { ref, computed, useAttrs, nextTick, onMounted, watch, inject } from 'vue'
42
+ import { throttle } from 'lodash'
43
+ import { castArray, isMobile } from './util'
44
+ import * as validates from './util/validate'
45
+ import { typeList } from './components/index'
46
+ import ElPlusFormBtn from './components/ElPlusFormBtn.vue'
47
+ import { cloneDeep } from 'lodash'
48
+
49
+ // IFormProps定义
50
+ export interface IFormProps {
51
+ // 表单描述
52
+ formDesc?: IFormDesc | null
53
+ // 表单数据
54
+ modelValue?: { [key: string]: any } | {}
55
+ // 表单自身属性
56
+ formAttrs?: { [key: string]: any }
57
+ // 校检规则
58
+ rules?: { [key: string]: any } | null
59
+ // 提交状态
60
+ isLoading?: boolean
61
+ // 提交函数
62
+ requestFn?: Function | null
63
+ // 表单更新的函数
64
+ updateFn?: Function | null
65
+ // 是否显示底部按钮组
66
+ showBtns?: boolean
67
+ // 是否显示submit按钮
68
+ showSubmit?: boolean
69
+ // 提交按钮文本
70
+ submitBtnText?: string
71
+ // 是否显示 cancel 取消按钮
72
+ // 默认值: isDialog = true 时, 默认值为 true
73
+ showCancel?: boolean
74
+ // 取消按钮文本
75
+ cancelBtnText?: string
76
+ // 是否显示重置按钮
77
+ showReset?: boolean
78
+ // 重置按钮文本
79
+ resetBtnText?: string
80
+ // 是否显示label
81
+ showLabel?: boolean
82
+ // label宽度
83
+ labelWidth?: number | string
84
+ // 全局禁用表单
85
+ disabled?: boolean
86
+ // 全局的readonly
87
+ readonly?: boolean
88
+ // 是否为弹窗
89
+ isDialog?: boolean
90
+ // options 的请求方法
91
+ optionsFn?: Function | null
92
+ // 表单全局size
93
+ size?: string
94
+ // 表单列 默认1
95
+ column?: number
96
+ // 是否是列表头部的表单
97
+ isTable?: boolean
98
+ // 唯一标识符。默认为id
99
+ idKey?: string
100
+ // 最大宽度
101
+ maxWidth?: string
102
+ // 比如 beforeValidate, beforeRequest, success, requestError, requestEnd
103
+ // 其他钩子 直接放到attrs里面去了
104
+ }
105
+
106
+ const defaultConf = inject('defaultConf') as ICRUDConfig
107
+ // 定义全局的format对象
108
+ const elPlusFormFormat = inject('format') as any
109
+
110
+ const emits = defineEmits(['request', 'reset', 'cancel'])
111
+ const props = withDefaults(defineProps<IFormProps>(), {
112
+ // 表单描述
113
+ formDesc: null,
114
+ // 表单数据
115
+ modelValue: () => {
116
+ return {}
117
+ },
118
+ // 表单自身属性
119
+ formAttrs: () => {
120
+ return {}
121
+ },
122
+ // 校检规则
123
+ rules: null,
124
+ // 提交状态
125
+ isLoading: false,
126
+ // 提交函数
127
+ requestFn: null,
128
+ // 表单更新的函数
129
+ updateFn: null,
130
+ // 是否显示按钮
131
+ showBtns: true,
132
+ // 是否显示submit按钮
133
+ showSubmit: true,
134
+ // 提交按钮文本
135
+ submitBtnText: '提交',
136
+ // 是否显示 cancel 取消按钮
137
+ // 默认值: isDialog = true 时, 默认值为 true
138
+ showCancel: false,
139
+ // 取消按钮
140
+ cancelBtnText: '取消',
141
+ // 是否显示重置按钮
142
+ showReset: true,
143
+ // 重置按钮
144
+ resetBtnText: '重置',
145
+ // 是否显示标签
146
+ showLabel: true,
147
+ // 标签宽度
148
+ labelWidth: '',
149
+ // 全局禁用表单
150
+ disabled: false,
151
+ // 全局的readonly
152
+ readonly: false,
153
+ // 是否为弹窗
154
+ isDialog: false,
155
+ // options 的请求方法
156
+ optionsFn: null,
157
+ // 表单全局size
158
+ size: 'small',
159
+ // 表单列 默认1
160
+ column: 1,
161
+ // 是否是列表头部的表单
162
+ isTable: false,
163
+ // 唯一标识符。默认为id
164
+ idKey: 'id'
165
+ // 其他钩子 直接放到attrs里面去了
166
+ // 比如 beforeValidate, beforeRequest, success, requestError, requestEnd
167
+ })
168
+
169
+ const refElPlusForm = ref()
170
+
171
+ // 是否开启全局监听
172
+ const isOpenListen = ref(false)
173
+
174
+ const tempAttr = useAttrs()
175
+
176
+ // 每个表单项的ref
177
+ const fieldRefs_ = ref([] as Array<any>)
178
+
179
+ // loading
180
+ const innerIsLoading = ref(false)
181
+
182
+ let oldFormData = null as any
183
+
184
+ const size = computed(() => props.size || defaultConf.size)
185
+ // 组件名称列表
186
+ const compTypeList = [...typeList, ...(defaultConf.form?.comList || [])]
187
+
188
+ // 整体的布局方式
189
+ const formLayout = computed(() => ({ display: 'flex', flexDirection: props.isTable ? 'row' : 'column' })) as any
190
+
191
+ // 表单的Attrs
192
+ const computedFormAttrs = computed(() => {
193
+ return {
194
+ scrollToError: true,
195
+ ...props.formAttrs,
196
+ labelWidth: props.labelWidth === 'auto' ? (props.isDialog ? '100px' : '120px') : parseInt(props.labelWidth + '') + 'px',
197
+ // validateOnRuleChange: false,
198
+ disabled: props.disabled || innerIsLoading.value,
199
+ rules: computedRules,
200
+ labelPosition: isMobile() ? 'top' : 'right',
201
+ style: {
202
+ width: props.maxWidth || (props.isTable ? '100%' : props.isDialog ? '80%' : '1000px'),
203
+ paddingRight: props.isTable ? '0' : '20px'
204
+ }
205
+ }
206
+ })
207
+
208
+ // 合并校验规则
209
+ const computedRules = computed(() => {
210
+ // 首先拿到表单总体传入的rules
211
+ const tempRules = props.rules || []
212
+ // 遍历属性描述对象,看看里面有没有校验规则
213
+ if (props.formDesc) {
214
+ Object.keys(props.formDesc).map((field: any) => {
215
+ if (!tempRules[field]) tempRules[field] = []
216
+ if (props.formDesc) {
217
+ if (props.formDesc[field].rules) {
218
+ if (typeof props.formDesc[field].rules === 'string') {
219
+ // string的话,就行默认校验规则中取
220
+ tempRules[field].push(...(validates as any)[props.formDesc[field].rules as string])
221
+ } else {
222
+ // 查看总体规则中是否已经含有了该属性的校验
223
+ castArray(props.formDesc[field].rules).map((item: any) => {
224
+ tempRules[field].push(item)
225
+ })
226
+ }
227
+ } else if (props.formDesc[field].required || props.formDesc[field].require) {
228
+ // 如果直接指定 required || require,手动添加校验信息
229
+ //
230
+ let rules = 'notAllBlank'
231
+ switch (props.formDesc[field].type) {
232
+ case 'upload':
233
+ case 'select':
234
+ case 'password':
235
+ case 'textarea':
236
+ case 'editor':
237
+ rules = props.formDesc[field].type || ''
238
+ break
239
+ case 'cascader':
240
+ case 'tselect':
241
+ case 'linkuser':
242
+ case 'radio':
243
+ rules = 'select'
244
+ break
245
+ }
246
+ tempRules[field].push(...(validates as any)[rules])
247
+ }
248
+ }
249
+ })
250
+ }
251
+ return tempRules
252
+ })
253
+
254
+ // 属性对象转数组
255
+ const attrMapToTableList = computed(() => {
256
+ const formLayoutRows = [] as Array<Array<IFormDescItem>>
257
+ if (props.formDesc) {
258
+ let tempData = [] as Array<IFormDescItem>
259
+ for (const key in props.formDesc) {
260
+ tempData.push({ ...props.formDesc[key], field: key })
261
+ }
262
+ // 这里处理一下layout的布局-渲染
263
+ let rowItemList = [] as Array<IFormDescItem>
264
+ let tempCount = 0
265
+ tempData.map((item) => {
266
+ if (item._vif) {
267
+ rowItemList.push(item)
268
+ tempCount++
269
+ if (item.colspan) {
270
+ tempCount += item.colspan - 1
271
+ }
272
+ if (tempCount >= props.column) {
273
+ // 一行结束
274
+ formLayoutRows.push(rowItemList)
275
+ rowItemList = [] as Array<IFormDescItem>
276
+ tempCount = 0
277
+ return
278
+ }
279
+ }
280
+ })
281
+ if (rowItemList.length > 0) {
282
+ formLayoutRows.push(rowItemList)
283
+ }
284
+ }
285
+ return formLayoutRows
286
+ })
287
+
288
+ // 整体初始化属性
289
+ const initFormAttrs = throttle(() => {
290
+ if (props.formDesc) {
291
+ Object.keys(props.formDesc).forEach((field) => {
292
+ if (props.formDesc) {
293
+ const formItem = props.formDesc[field]
294
+ if (formItem && formItem.type) {
295
+ // 设置 _type
296
+ formItem._type = compTypeList.includes(formItem.type.toLowerCase()) ? 'el-plus-form-' + formItem.type : formItem.type
297
+ // 触发 v-if 显示 / 隐藏 设置_vif
298
+ formItem._vif = handelKeyValue(formItem, 'vif', field, !formItem.isBlank ?? true)
299
+ // 触发 disabled 禁用 / 启用 设置_disabled
300
+ formItem._disabled = handelKeyValue(formItem, 'disabled', field, props.disabled ?? false)
301
+ // 动态属性 attrs
302
+ const tempAttr = {} as any
303
+ // if ((typeof formItem.attrs === 'object' && formItem.attrs?.options) || formItem.options) {
304
+ // tempAttr.options = (formItem.attrs as any)?.options || formItem.options
305
+ // }
306
+ formItem._attrs = Object.assign({}, handelKeyValue(formItem, 'attrs', field), tempAttr)
307
+ // 动态options
308
+ // formItem._options = handelKeyValue(formItem, 'options', field)
309
+ // 动态 _label
310
+ formItem._label = handelKeyValue(formItem, 'label', field)
311
+ // 动态 prop
312
+ // formItem._prop = handelKeyValue(formItem, 'prop', field);
313
+ // 动态 _tip
314
+ formItem._tip = handelKeyValue(formItem, 'tip', field)
315
+ // 单独处理下上传
316
+ if (!formItem._tip && !formItem.noTip && formItem.type === 'upload') {
317
+ formItem._tip = `最多上传${formItem.multiple ? formItem.limit || 20 : 1}${formItem.upType === 'file' ? '个文件' : '张图片'}`
318
+ }
319
+ // 这里初始化一下默认值
320
+ if (formItem.default !== undefined && formItem.default !== null && props.modelValue[field] === undefined) {
321
+ props.modelValue[field] = formItem.default
322
+ }
323
+ // 这里初始化一下默认选中项
324
+ if (formItem.defaultItem !== undefined && formItem.defaultItem !== null && props.modelValue[field] === undefined) {
325
+ props.modelValue[field] = formItem.defaultItem.value
326
+ }
327
+ // 这里格式化一下数据
328
+ // if (formItem.format) {
329
+ // if (typeof formItem.format === 'string') {
330
+ // props.modelValue[field] = elPlusFormFormat[formItem.format](props.modelValue[field], props.modelValue)
331
+ // } else if (typeof formItem.format === 'function') {
332
+ // props.modelValue[field] = (formItem as any).format(props.modelValue[field], props.modelValue)
333
+ // } else {
334
+ // // console.log('未知的格式化类型:', formItem.format)
335
+ // }
336
+ // }
337
+ } else if (formItem && formItem.isBlank) {
338
+ // 触发 v-if 显示 / 隐藏 设置_vif
339
+ formItem._vif = handelKeyValue(formItem, 'vif', '', true)
340
+ }
341
+ }
342
+ })
343
+ }
344
+ }, 500)
345
+
346
+ // 表单底部按钮
347
+ const btnList = computed(() => {
348
+ let btnList_ = [] as any[]
349
+ if (!props.showBtns) {
350
+ return btnList_
351
+ }
352
+ // 取消按钮
353
+ if (props.showCancel) {
354
+ btnList_.push({
355
+ field: '_reset_btn',
356
+ desc: {
357
+ label: props.cancelBtnText || '取消',
358
+ size: size.value,
359
+ disabled: innerIsLoading.value,
360
+ on: { click: () => emits('cancel') }
361
+ }
362
+ })
363
+ }
364
+ // 重置按钮
365
+ if (props.showReset) {
366
+ btnList_.push({
367
+ field: '_reset_btn',
368
+ desc: {
369
+ label: props.resetBtnText || '重置',
370
+ confirm: `确定要${props.resetBtnText || '重置'}?`,
371
+ size: size.value,
372
+ disabled: innerIsLoading.value,
373
+ on: { click: reset }
374
+ }
375
+ })
376
+ }
377
+ // 提交按钮
378
+ if (props.showSubmit) {
379
+ btnList_.push({
380
+ field: '_reset_btn',
381
+ desc: {
382
+ label: props.submitBtnText || '提交',
383
+ size: size.value,
384
+ type: 'primary',
385
+ loading: props.isLoading || innerIsLoading.value,
386
+ on: { click: handleSubmitForm }
387
+ }
388
+ })
389
+ }
390
+ return btnList_
391
+ })
392
+
393
+ // 处理属性
394
+ const handelKeyValue = (formItem: IFormDescItem, key: string, field: string, defVal: number | boolean | string | null = null) => {
395
+ if (typeof formItem[key] === 'function') {
396
+ // 如果有方法类型的判断,则需要启用动态监测
397
+ isOpenListen.value = true
398
+ return formItem[key](props.modelValue, props.modelValue[field])
399
+ } else if (typeof formItem[key] === 'boolean') {
400
+ return formItem[key] ?? false
401
+ } else if (typeof formItem[key] === 'string') {
402
+ return formItem[key] ?? ''
403
+ } else {
404
+ return formItem[key] ?? defVal
405
+ }
406
+ }
407
+
408
+ // 验证表单
409
+ const validateForm = () => {
410
+ return new Promise((resolve: any, reject: any) => {
411
+ if (computedRules.value) {
412
+ // 当传递了验证规则
413
+ refElPlusForm.value.validate((valid: Boolean, invalidFields: any) => {
414
+ if (valid) {
415
+ // 验证通过
416
+ resolve()
417
+ } else {
418
+ // 显示错误
419
+ reject(invalidFields)
420
+ }
421
+ })
422
+ } else {
423
+ resolve()
424
+ }
425
+ })
426
+ }
427
+
428
+ // 设置子组件的ref-重置form的时候需要用到
429
+ const setComponentRef = (el: any) => {
430
+ if (!el) return
431
+ if (fieldRefs_.value.filter((item: any) => item.field === el.field).length <= 0) {
432
+ fieldRefs_.value.push(el)
433
+ }
434
+ }
435
+
436
+ /**
437
+ * 处理单个表单值
438
+ * @param val
439
+ */
440
+ const handelValToForm = (desc: IFormDescItem, field: string, val: any) => {
441
+ const result = {} as { [key: string]: any }
442
+ if (!desc) return { [field]: val }
443
+ if (!desc._vif || val === undefined || val === null) return result
444
+ if (desc.type === 'category') {
445
+ if (val === null) val = []
446
+ // 如果是直接显示结果
447
+ if (desc.checkStrictly === true) {
448
+ result.categoryId = val[2] ?? val[1] ?? val[0] ?? ''
449
+ } else {
450
+ for (let i = 0; i < 3; i++) {
451
+ result['categoryId' + (i + 1)] = val[i] ?? ''
452
+ }
453
+ }
454
+ } else if (desc.type === 'area') {
455
+ if (val === null) val = [null, null, null, null]
456
+ const [pid, cid, zid, sid] = val
457
+ if (desc.checkStrictly) {
458
+ result[field] = sid || zid || cid || pid || null
459
+ } else {
460
+ result.provinceId = pid || -1
461
+ result.cityId = cid || -1
462
+ result.zoneId = zid || -1
463
+ result.streetId = sid || -1
464
+ }
465
+ } else if (desc.type === 'daterange') {
466
+ if (val && val.length === 2) {
467
+ result.startTime = val[0]
468
+ if (typeof val[1] === 'string') {
469
+ result.endTime = new Date(val[1]).getTime()
470
+ } else {
471
+ result.endTime = val[1]
472
+ }
473
+ result.endTime = result.endTime + (24 * 60 * 60 - 1) * 1000
474
+ // 再处理一下时间戳
475
+ result.startTime = elPlusFormFormat.time(result.startTime, 3)
476
+ result.endTime = elPlusFormFormat.time(result.endTime, 3)
477
+ }
478
+ } else if (desc.type === 'datetimerange') {
479
+ if (val && val.length === 2) {
480
+ // 处理一下时间戳
481
+ result.startTime = elPlusFormFormat.time(val[0], 3)
482
+ result.endTime = elPlusFormFormat.time(val[1], 3)
483
+ }
484
+ } else if (desc.type === 'linkuser') {
485
+ const [userIds, deptIds, userNames, deptNames] = val
486
+ result.userIds = userIds
487
+ result.deptIds = deptIds
488
+ result.userNames = userNames
489
+ result.deptNames = deptNames
490
+ } else {
491
+ // 这里处理下通用表单的数据类型
492
+ switch (desc.type) {
493
+ case 'checkbox':
494
+ if (Array.isArray(val)) {
495
+ val = val.join(',')
496
+ }
497
+ break
498
+ }
499
+ result[field] = val
500
+ }
501
+ return result
502
+ }
503
+
504
+ /**
505
+ * 获取处理过后的表单数据
506
+ */
507
+ const getFormData = () => {
508
+ const tempData = {} as { [key: string]: any }
509
+ if (props.formDesc) {
510
+ // 循环获取表单数据
511
+ Object.keys(props.modelValue).map((key) => {
512
+ if (['provinceId', 'cityId', 'zoneId', 'streetId'].indexOf(key) >= 0) return
513
+ if (props.formDesc) {
514
+ Object.assign(tempData, handelValToForm(props.formDesc[key], key, props.modelValue[key]))
515
+ }
516
+ })
517
+ }
518
+ return tempData
519
+ }
520
+
521
+ /**
522
+ * 刷新验证码图片
523
+ */
524
+ const changeValidImg = () => {
525
+ fieldRefs_.value.map((item) => {
526
+ if (item.CID) {
527
+ item.changeValidImg()
528
+ }
529
+ })
530
+ }
531
+
532
+ /**
533
+ * 提交表单
534
+ */
535
+ const handleSubmitForm = async () => {
536
+ let postData = getFormData()
537
+ try {
538
+ // 校验表单事件处理
539
+ if (tempAttr.beforeValidate) {
540
+ const isPass = await (tempAttr.beforeValidate as Function)(postData)
541
+ if (isPass === false) return
542
+ }
543
+ await validateForm()
544
+ for (const field in postData) {
545
+ // 去除下划线开头的参数
546
+ if (field.indexOf('_') === 0) {
547
+ delete postData[field]
548
+ continue
549
+ }
550
+ if (props.formDesc) {
551
+ const formItem = props.formDesc[field]
552
+ // valueFormatter的处理
553
+ if (formItem && formItem.valueFormat) {
554
+ postData[field] = formItem.valueFormat(postData[field], postData)
555
+ }
556
+ // 处理带有true或者false值
557
+ if (postData[field] === true || postData[field] === false) {
558
+ postData[field] = +postData[field]
559
+ }
560
+ }
561
+ }
562
+
563
+ // 提交数据前的通知
564
+ if (tempAttr.beforeRequest) {
565
+ const beforeRequestData = await (tempAttr.beforeRequest as Function)(postData)
566
+ if (beforeRequestData === false) return
567
+ if (typeof beforeRequestData === 'object') {
568
+ postData = beforeRequestData
569
+ }
570
+ }
571
+ if (props.requestFn) {
572
+ // 在内部调用请求
573
+ if (innerIsLoading.value) return
574
+ innerIsLoading.value = true
575
+ try {
576
+ let response = {}
577
+ if (props.updateFn && postData && (postData as any)[props.idKey]) {
578
+ response = await props.updateFn(postData)
579
+ } else {
580
+ response = await props.requestFn(postData)
581
+ }
582
+ nextTick(() => {
583
+ if (tempAttr.success && typeof tempAttr.success === 'function') {
584
+ tempAttr.success({
585
+ response,
586
+ formData: props.modelValue,
587
+ callBack: () => (innerIsLoading.value = false)
588
+ } as IFormBack)
589
+ }
590
+ })
591
+ } catch (error) {
592
+ // 如果用户有处理异常的方法了
593
+ if (tempAttr.requestError && typeof tempAttr.requestError === 'function') {
594
+ tempAttr.requestError(error)
595
+ } else {
596
+ // 处理异常情况
597
+ if (error instanceof Error) {
598
+ // 返回的是Error类型, 则进行解析
599
+ try {
600
+ const msg = JSON.parse(error.message)
601
+ if (msg instanceof Object) {
602
+ // innerFormError.value = msg;
603
+ }
604
+ // eslint-disable-next-line
605
+ } catch {}
606
+ } else if (error instanceof Object) {
607
+ // 返回的是对象类型, 则直接设置
608
+ // innerFormError = error;
609
+ }
610
+ if (tempAttr.requestError && typeof tempAttr.requestError === 'function') {
611
+ tempAttr.requestError()
612
+ }
613
+ }
614
+ // 报错了这里恢复
615
+ innerIsLoading.value = false
616
+ } finally {
617
+ if (!props.isDialog) {
618
+ innerIsLoading.value = false
619
+ }
620
+ if (tempAttr.requestEnd && typeof tempAttr.requestEnd === 'function') {
621
+ tempAttr.requestEnd()
622
+ }
623
+ }
624
+ } else {
625
+ // 在外部用户自己处理请求
626
+ if (props.isLoading) return
627
+ emits('request', postData)
628
+ }
629
+ } catch (error) {
630
+ // eslint-disable-next-line no-console
631
+ console.log('error: ', error)
632
+ }
633
+ }
634
+
635
+ /**
636
+ * 重置,并清除校验
637
+ */
638
+ const reset = () => {
639
+ // refElPlusForm.value.resetFields();
640
+ // 重置
641
+ Object.keys(props.modelValue).map((key) => {
642
+ props.modelValue[key] = oldFormData ? oldFormData[key] : (props.formDesc && props.formDesc[key] && props.formDesc[key].default) ?? null
643
+ })
644
+ setTimeout(() => {
645
+ // 清空校验
646
+ clearValid()
647
+ }, 100)
648
+ // 通知外部
649
+ emits('reset')
650
+ }
651
+
652
+ /**
653
+ * 清空校验
654
+ */
655
+ const clearValid = () => {
656
+ refElPlusForm.value?.clearValidate()
657
+ }
658
+
659
+ /**
660
+ * 清理
661
+ */
662
+ const clear = () => {
663
+ // 这里进行数据清空
664
+ if (props.modelValue) {
665
+ Object.keys(props.modelValue).map((key) => {
666
+ props.modelValue[key] = null
667
+ })
668
+ }
669
+ // 重置校验
670
+ nextTick(() => {
671
+ setTimeout(() => {
672
+ clearValid()
673
+ }, 10)
674
+ })
675
+ }
676
+
677
+ /**
678
+ * 单独校验一下字段
679
+ * @param field
680
+ */
681
+ function handelValidateThis(field: string) {
682
+ refElPlusForm.value.validateField(field, (err: any) => {
683
+ // console.log('err: ', err)
684
+ })
685
+ }
686
+
687
+ /**
688
+ * 刷新表单
689
+ */
690
+ function refresh() {
691
+ // 重新刷新表单
692
+ initFormAttrs()
693
+ // 重置校验
694
+ nextTick(() => {
695
+ clearValid()
696
+ })
697
+ }
698
+
699
+ // 监听Desc改变
700
+ watch(
701
+ () => props.formDesc,
702
+ (formDesc) => {
703
+ if (formDesc) {
704
+ // 检查联动
705
+ initFormAttrs()
706
+ }
707
+ }
708
+ )
709
+
710
+ // 深度监听data改变
711
+ watch(
712
+ () => props.modelValue,
713
+ (data) => {
714
+ if (!oldFormData || (isOpenListen.value && data && JSON.stringify(data) !== JSON.stringify(oldFormData))) {
715
+ // 检查联动
716
+ if (JSON.stringify(data) !== JSON.stringify(oldFormData)) {
717
+ oldFormData = cloneDeep(data)
718
+ initFormAttrs()
719
+ }
720
+ }
721
+ },
722
+ { deep: true }
723
+ )
724
+
725
+ onMounted(async () => {
726
+ // 检查联动
727
+ if (!isOpenListen.value) {
728
+ initFormAttrs()
729
+ }
730
+ nextTick(() => {
731
+ clearValid()
732
+ })
733
+ })
734
+
735
+ // 暴露对外方法
736
+ defineExpose({ submit: handleSubmitForm, getData: getFormData, validate: validateForm, reset, clearValid, clear, changeValidImg, refresh })
737
+ </script>
738
+ <style lang="scss">
739
+ .el-plus-form-panel {
740
+ .el-plus-form--inline .el-plus-form-btns {
741
+ width: auto;
742
+ }
743
+
744
+ .el-form--inline .el-form-item {
745
+ margin-right: 12px !important;
746
+ }
747
+
748
+ .el-plus-form-col--break {
749
+ clear: both;
750
+ }
751
+
752
+ .el-plus-form-tip {
753
+ color: #909399;
754
+ line-height: 1.5em;
755
+ margin-top: 3px;
756
+ }
757
+
758
+ .el-plus-form-tip code {
759
+ padding: 2px 4px;
760
+ font-size: 90%;
761
+ color: #c7254e;
762
+ background-color: #f9f2f4;
763
+ border-radius: 4px;
764
+ }
765
+ .el-plus-form-column-panel {
766
+ & > .el-form-item--default {
767
+ margin-bottom: 18px !important;
768
+ & > .el-form-item__label {
769
+ line-height: 40px;
770
+ margin-bottom: 0;
771
+ }
772
+ }
773
+ }
774
+ }
775
+ </style>