af-mobile-client-vue3 1.0.91 → 1.0.94

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.
@@ -1,997 +1,997 @@
1
- <script setup lang="ts">
2
- import {
3
- Area as VanArea,
4
- Calendar as VanCalendar,
5
- Checkbox as VanCheckbox,
6
- DatePicker as VanDatePicker,
7
- Field as VanField,
8
- Picker as VanPicker,
9
- PickerGroup as VanPickerGroup,
10
- Popup as VanPopup,
11
- Radio as VanRadio,
12
- RadioGroup as VanRadioGroup,
13
- Rate as VanRate,
14
- Slider as VanSlider,
15
- Stepper as VanStepper,
16
- Switch as VanSwitch,
17
- TimePicker as VanTimePicker,
18
- CheckboxGroup as vanCheckboxGroup,
19
- } from 'vant'
20
- import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
21
- import { areaList } from '@vant/area-data'
22
- import { runLogic } from '@af-mobile-client-vue3/services/api/common'
23
- import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
24
- import XSelect from '@af-mobile-client-vue3/components/core/XSelect/index.vue'
25
- import XMultiSelect from '@af-mobile-client-vue3/components/core/XMultiSelect/index.vue'
26
- import XGridDropOption from '@af-mobile-client-vue3/components/core/XGridDropOption/index.vue'
27
- import type { Numeric } from 'vant/es/utils'
28
- import { getDict } from '@af-mobile-client-vue3/utils/dictUtil'
29
- import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
30
- import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
31
- import { debounce } from 'lodash-es'
32
- import { searchToListOption, searchToOption } from '@af-mobile-client-vue3/services/v3Api'
33
-
34
- const props = defineProps({
35
- attr: {
36
- type: Object,
37
- },
38
- form: {
39
- type: Object,
40
- },
41
- datePickerFilter: {
42
- type: Function,
43
- default: () => true,
44
- },
45
- datePickerFormatter: {
46
- type: Function,
47
- default: (type, val) => val,
48
- },
49
- mode: {
50
- type: String,
51
- default: '查询',
52
- },
53
- serviceName: {
54
- type: String,
55
- default: undefined,
56
- },
57
- // 调用logic获取数据源的追加参数
58
- getDataParams: {
59
- type: Object,
60
- default: undefined,
61
- },
62
- disabled: {
63
- type: Boolean,
64
- default: false,
65
- },
66
- rules: {
67
- type: Object,
68
- default: () => {},
69
- },
70
- modelValue: {
71
- type: [String, Number, Boolean, Array, Object],
72
- default: undefined,
73
- },
74
- showLabel: {
75
- type: Boolean,
76
- default: true,
77
- },
78
- // radio/checkbox/select/mul-select 选项数据结构
79
- columnsField: {
80
- type: Object,
81
- default: () => {
82
- return { text: 'label', value: 'value' }
83
- },
84
- },
85
-
86
- })
87
-
88
- const emits = defineEmits(['update:modelValue'])
89
- // 判断并初始化防抖函数
90
- let debouncedUserLinkFunc: Function | null = null
91
- let debouncedDepLinkFunc: Function | null = null
92
-
93
- const { attr, form, mode, serviceName, getDataParams, columnsField } = props
94
- const calendarShow = ref(false)
95
- const option = ref([])
96
- const pickerValue = ref(undefined)
97
- const datePickerValue = ref(undefined)
98
- const timePickerValue = ref(undefined)
99
- const area = ref<any>(undefined)
100
- const showPicker = ref(false)
101
- const showDatePicker = ref(false)
102
- const showTimePicker = ref(false)
103
- const showArea = ref(false)
104
- const errorMessage = ref('')
105
-
106
- // 表单默认值
107
- // 输入-非查询
108
- const formInputDefaultValue = ref('')
109
- // 输入-查询
110
- const queryInputDefaultValue = ref('')
111
- // 选择-非查询
112
- const formSelectDefaultValue = ref([])
113
- // 选择-查询
114
- const querySelectDefaultValue = ref([])
115
-
116
- // eslint-disable-next-line ts/no-use-before-define
117
- const currUser = computed(() => userState.f.resources.id)
118
- // 是否展示当前项
119
- const showItem = ref(true)
120
-
121
- // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
122
- const currInst = getCurrentInstance()
123
-
124
- // 配置中心->表单项变更触发函数
125
- const dataChangeFunc = debounce(async () => {
126
- if (attr.dataChangeFunc)
127
- await executeStrFunctionByContext(currInst, attr.dataChangeFunc, [form, attr, null, mode])
128
- }, 500)
129
-
130
- // 配置中心->表单项展示函数
131
- const showFormItemFunc = debounce(async () => {
132
- if (attr.showFormItemFunc) {
133
- const obj = await executeStrFunctionByContext(currInst, attr.showFormItemFunc, [form, attr, null, mode])
134
- // 判断是 bool 还是 obj 兼容
135
- if (typeof obj === 'boolean') {
136
- showItem.value = obj
137
- }
138
- else if (obj && typeof obj === 'object') {
139
- // obj 是一个对象,并且不是数组
140
- showItem.value = obj?.show
141
- }
142
- }
143
- }, 500)
144
-
145
- const localValue = computed({
146
- get() {
147
- // if (props.modelValue !== undefined) {
148
- // return props.modelValue
149
- // }
150
- switch (attr.type) {
151
- case 'uploader':
152
- if (mode === '查询') {
153
- // console.log(querySelectDefaultValue.value)
154
- return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
155
- }
156
- else {
157
- return props.modelValue !== undefined ? props.modelValue : formSelectDefaultValue.value
158
- }
159
- case 'switch':
160
- return props.modelValue !== undefined ? props.modelValue : false
161
- case 'checkbox':
162
- case 'file':
163
- case 'image':
164
- case 'timePicker':
165
- case 'datePicker':
166
- if (mode === '查询') {
167
- // console.log(querySelectDefaultValue.value)
168
- return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
169
- }
170
- else {
171
- // console.log(formSelectDefaultValue.value)
172
- return props.modelValue !== undefined ? props.modelValue : formSelectDefaultValue.value
173
- }
174
- // case 'datePicker':
175
- // if (mode === '查询') {
176
- // // console.log(querySelectDefaultValue.value)
177
- // return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
178
- // }
179
- // else {
180
- // if (props.modelValue !== undefined) {
181
- // // 拆分日期和时间
182
- // const [dateStr, timeStr] = props.modelValue.split(' ')
183
- // // 拆分日期部分
184
- // const date = dateStr.split('-')
185
- // // 拆分时间部分
186
- // const time = timeStr.split(':')
187
- // // 赋值给 dateTimePickerValue
188
- // // eslint-disable-next-line vue/no-side-effects-in-computed-properties
189
- // dateTimePickerValue.value = {
190
- // date,
191
- // time,
192
- // }
193
- // return dateTimePickerValue.value
194
- // }
195
- // return formSelectDefaultValue.value
196
- // }
197
- case 'radio':
198
- case 'rate':
199
- case 'slider':
200
- case 'area':
201
- case 'citySelect':
202
- case 'calendar':
203
- case 'textarea':
204
- case 'intervalPicker':
205
- case 'input':
206
- case 'select':
207
- if (mode === '查询')
208
- return props.modelValue !== undefined ? props.modelValue : queryInputDefaultValue.value
209
- else
210
- return props.modelValue !== undefined ? props.modelValue : formInputDefaultValue.value
211
- case 'stepper':
212
- return props.modelValue !== undefined ? props.modelValue : 1
213
- case 'rangePicker':
214
- if (props.modelValue && Array.isArray(props.modelValue) && props.modelValue.length > 1)
215
- return `${props.modelValue[0]} ~ ${props.modelValue[1]}`
216
-
217
- else
218
- return props.modelValue
219
-
220
- default:
221
- return undefined
222
- }
223
- },
224
- set(newValue) {
225
- emits('update:modelValue', newValue)
226
- dataChangeFunc()
227
- },
228
- })
229
-
230
- // 表单校验的类型校验
231
- function formTypeCheck(attr, value) {
232
- switch (attr.rule.type) {
233
- case 'string':
234
- if (value.length === 0) {
235
- errorMessage.value = `${attr.name}必须为有效的字符串`
236
- return
237
- }
238
- break
239
- case 'number':
240
- if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/.test(value)) {
241
- errorMessage.value = `${attr.name}必须为数字`
242
- return
243
- }
244
- break
245
- case 'boolean':
246
- case 'array':
247
- case 'regexp':
248
- if (value) {
249
- errorMessage.value = ''
250
- return
251
- }
252
- break
253
- case 'integer':
254
- if (!/^-?\d+$/.test(value)) {
255
- errorMessage.value = `${attr.name}必须为整数`
256
- return
257
- }
258
- break
259
- case 'float':
260
- if (!/^-?\d+\.\d+$/.test(value)) {
261
- errorMessage.value = `${attr.name}必须为小数`
262
- return
263
- }
264
- break
265
- case 'email':
266
- if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value)) {
267
- errorMessage.value = `请输入正确的邮箱地址`
268
- return
269
- }
270
- break
271
- case 'idNumber':
272
- if (!/^(^\d{15}$|^\d{17}([0-9]|X)$)$/.test(value)) {
273
- errorMessage.value = `请输入正确的身份证号码`
274
- return
275
- }
276
- break
277
- case 'userPhone':
278
- if (!/^1[3-9]\d{9}$/.test(value)) {
279
- errorMessage.value = `请输入正确的手机号码`
280
- return
281
- }
282
- break
283
- case 'landlineNumber':
284
- if (!/^(0\d{2,3}[-\s]?)\d{7,8}$/.test(value)) {
285
- errorMessage.value = `请输入正确的座机号码`
286
- return
287
- }
288
- break
289
- case 'greaterThanZero':
290
- if (!/^(?:[1-9]\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
291
- errorMessage.value = `请输入一个大于0的数字`
292
- return
293
- }
294
- break
295
- case 'greaterThanOrEqualZero':
296
- if (!/^(?:\d+|\d*\.\d+)$/.test(value)) {
297
- errorMessage.value = `请输入一个大于等于0的数字`
298
- return
299
- }
300
- break
301
- case 'stringLength':
302
- if (!(value.length >= attr.rule.minLen && value.length < attr.rule.maxLen)) {
303
- errorMessage.value = `长度必须在${attr.rule.minLen}~${attr.rule.maxLen}之间`
304
- return
305
- }
306
- break
307
- case 'customJs':
308
- // eslint-disable-next-line no-case-declarations
309
- const funcStr = attr.rule.customValidatorFunc
310
- // 输入的数据是否符合条件
311
- // eslint-disable-next-line no-case-declarations
312
- const status = ref(true)
313
- // 提取函数体部分
314
- // eslint-disable-next-line no-case-declarations
315
- const funcBodyMatch = funcStr.match(/function\s*\(.*?\)\s*{([\s\S]*)}/)
316
- if (!funcBodyMatch)
317
- throw new Error('自定义校验函数不合法')
318
- // eslint-disable-next-line no-case-declarations
319
- const funcBody = funcBodyMatch[1].trim() // 提取函数体
320
- // 使用 new Function 创建函数
321
- // eslint-disable-next-line no-new-func,no-case-declarations
322
- const customValidatorFunc = new Function('rule', 'value', 'callback', 'form', 'attr', 'util', funcBody)
323
- // 定义 callback 函数
324
- // eslint-disable-next-line no-case-declarations
325
- const callback = (error) => {
326
- if (error) {
327
- errorMessage.value = `${error}`
328
- status.value = false // 表示有错误发生
329
- }
330
- }
331
- // 调用自定义校验函数
332
- customValidatorFunc(
333
- attr.rule,
334
- value,
335
- callback,
336
- form,
337
- attr,
338
- {}, // util 对象(可以根据需要传递)
339
- )
340
- if (!status.value)
341
- return
342
- break
343
- default:
344
- errorMessage.value = ''
345
- break
346
- }
347
- errorMessage.value = ''
348
- }
349
-
350
- onBeforeMount(() => {
351
- init()
352
- showFormItemFunc()
353
- dataChangeFunc()
354
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
355
- debouncedUserLinkFunc = debounce(() => updateResOptions('人员'), 200)
356
-
357
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
358
- debouncedDepLinkFunc = debounce(() => updateResOptions('部门'), 200)
359
- })
360
- // 是否展示表单左侧label文字
361
- const labelData = computed(() => {
362
- return props.showLabel ? attr.name : null
363
- })
364
- // 是否展示表单左侧label文字
365
- const labelAlign = computed(() => {
366
- return attr.labelAlign ? attr.labelAlign : 'top'
367
- })
368
- // 是否只读
369
- const readonly = computed(() => {
370
- return attr.addOrEdit === 'readonly' || mode === '预览'
371
- })
372
- // 提示内容
373
- const placeholder = computed(() => {
374
- if (attr.addOrEdit === 'readonly')
375
- return ' 暂无内容 ~ '
376
- else
377
- return attr.placeholder ? attr.placeholder : `请选择${attr.name}`
378
- })
379
- // 登录信息 (可以在配置的动态函数中使用 this.setupState 获取到当前组件内的全部函数和变量 例:this.setupState.userState)
380
- const userState = useUserStore().getLogin()
381
-
382
- const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
383
-
384
- function onCalendarConfirm(values) {
385
- localValue.value = [formatDate(values[0]), formatDate(values[1])]
386
- calendarShow.value = false
387
- }
388
-
389
- function init() {
390
- if (attr.keyName && typeof attr.keyName === 'string') {
391
- if (attr.keyName && attr.keyName.includes('logic@')) {
392
- getData({}, (res) => {
393
- option.value = res
394
- initRadioValue()
395
- })
396
- }
397
- else if (attr.keyName && attr.keyName.includes('config@')) {
398
- const configName = attr.keyName.substring(7)
399
- getDict(configName, (result) => {
400
- if (result)
401
- option.value = result
402
- }, serviceName)
403
- }
404
- else if (attr.keyName && attr.keyName.includes('search@')) {
405
- let source = attr.keyName.substring(7)
406
- const userid = currUser.value
407
- let roleName = 'roleName'
408
- if (source.startsWith('根据角色[') && source.endsWith(']获取人员')) {
409
- const startIndex = source.indexOf('[') + 1
410
- const endIndex = source.indexOf(']', startIndex)
411
- roleName = source.substring(startIndex, endIndex)
412
- source = '根据角色获取人员'
413
- }
414
- const searchData = { source, userid, roleName }
415
- if (source.startsWith('根据表单项[') && source.endsWith(']联动人员'))
416
- updateResOptions('人员')
417
- else if (source.startsWith('根据表单项[') && source.endsWith(']联动部门'))
418
- updateResOptions('部门')
419
- else if (attr.type === 'select' || attr.type === 'checkbox')
420
- searchToListOption(searchData, res => getDataCallback(res))
421
- else
422
- searchToOption(searchData, res => getDataCallback(res))
423
- }
424
- else {
425
- initRadioValue()
426
- }
427
- }
428
- if (attr.type === 'radio' || attr.type === 'rate' || attr.type === 'slider' || attr.type === 'area' || attr.type === 'citySelect' || attr.type === 'calendar' || attr.type === 'textarea' || attr.type === 'intervalPicker' || attr.type === 'input' || attr.type === 'select') {
429
- if (attr.formDefault)
430
- formInputDefaultValue.value = attr.formDefault
431
- if (attr.queryFormDefault)
432
- queryInputDefaultValue.value = attr.queryFormDefault
433
- }
434
-
435
- if (attr.type === 'checkbox' || attr.type === 'uploader' || attr.type === 'file' || attr.type === 'image' || attr.type === 'datePicker' || attr.type === 'timePicker') {
436
- if (attr.formDefault) {
437
- if (attr.type === 'checkbox' || attr.type === 'image' || attr.type === 'file')
438
- formSelectDefaultValue.value = attr.formDefault
439
- else
440
- formSelectDefaultValue.value.push(attr.formDefault)
441
- }
442
- if (attr.queryFormDefault) {
443
- // console.log(querySelectDefaultValue.value)
444
- querySelectDefaultValue.value.push(attr.queryFormDefault)
445
- // querySelectDefaultValue.value = attr.queryFormDefault
446
- }
447
- }
448
- }
449
-
450
- function getDataCallback(res) {
451
- option.value = res
452
- if (attr.type === 'radio')
453
- initRadioValue()
454
- }
455
-
456
- async function updateResOptions(type) {
457
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString()?.endsWith(`]联动${type}`)) {
458
- const searchData = { source: `获取${type}`, userid: currUser.value }
459
- const startIndex = attr.keyName.indexOf('[') + 1
460
- const endIndex = attr.keyName.indexOf(']', startIndex)
461
- const formModel = attr.keyName.substring(startIndex, endIndex).replace('.', '_')
462
- // console.log(form)
463
- const formModelData = form[formModel]
464
- if (formModel?.length && formModelData?.length) {
465
- await searchToListOption(searchData, (res) => {
466
- // console.log(res)
467
- // console.log(form)
468
- getDataCallback(res.filter((h) => {
469
- return formModelData['0'] === h.f_organization_id || formModelData['0'] === h.f_department_id || formModelData['0'] === h.parentid
470
- // if (formModel.indexOf('org') > -1) {
471
- // return formModelData?.includes(h.orgid || h.f_organization_id || h.parentid)
472
- // } else {
473
- // return formModelData?.includes(h?.parentid)
474
- // }
475
- }))
476
- })
477
- }
478
- }
479
- }
480
-
481
- function initRadioValue() {
482
- if ((mode === '新增' || mode === '修改') && attr.type === 'radio' && !localValue.value) {
483
- if (attr.keys && attr.keys.length > 0)
484
- localValue.value = attr.keys[0].value
485
- else if (option.value && option.value.length > 0)
486
- localValue.value = option.value[0].value
487
- }
488
- }
489
-
490
- function getData(value, callback) {
491
- if (value !== '') {
492
- const logicName = attr.keyName
493
- const logic = logicName.substring(6)
494
- // 调用logic前设置参数
495
- if (getDataParams && getDataParams[attr.model])
496
- Object.assign(value, getDataParams[attr.model])
497
-
498
- runLogic(logic, value, serviceName).then((res) => {
499
- callback(res)
500
- })
501
- }
502
- }
503
-
504
- function onPickerConfirm({ selectedOptions }) {
505
- showPicker.value = false
506
- pickerValue.value = selectedOptions[0].text
507
- emits('update:modelValue', [selectedOptions[0].text])
508
- }
509
-
510
- // 日期时间选择数据
511
- const dateTimePickerValue = ref(undefined)
512
- function showDataTimePicker() {
513
- if (props.modelValue !== undefined && props.modelValue !== '' && props.modelValue !== null) {
514
- // 拆分日期和时间
515
- const [dateStr, timeStr] = props.modelValue.split(' ')
516
- // 拆分日期部分
517
- const date = dateStr.split('-')
518
- // 拆分时间部分
519
- const time = timeStr.split(':')
520
- // 赋值给 dateTimePickerValue
521
- dateTimePickerValue.value = {
522
- date,
523
- time,
524
- }
525
- }
526
- else {
527
- dateTimePickerValue.value = {
528
- date: ['2015', '01', '01'],
529
- time: ['00', '00', '00'],
530
- }
531
- }
532
- showDatePicker.value = true
533
- }
534
-
535
- function onDatePickerConfirm({ selectedValues }) {
536
- showDatePicker.value = false
537
- localValue.value = selectedValues.join('-')
538
- }
539
-
540
- function onTimePickerConfirm({ selectedValues }) {
541
- showTimePicker.value = false
542
- timePickerValue.value = selectedValues.join(':')
543
- emits('update:modelValue', timePickerValue.value)
544
- }
545
-
546
- function onAreaConfirm({ selectedOptions }) {
547
- area.value = `${selectedOptions[0].text}-${selectedOptions[1].text}-${selectedOptions[2].text}`
548
- showArea.value = false
549
- emits('update:modelValue', [{
550
- province: selectedOptions[0].text,
551
- city: selectedOptions[1].text,
552
- district: selectedOptions[2].text,
553
- }])
554
- }
555
-
556
- function updateFile(files, _index) {
557
- files.forEach((file) => {
558
- if (file.content)
559
- delete file.content
560
- if (file.file)
561
- delete file.file
562
- if (file.objectUrl)
563
- delete file.objectUrl
564
- })
565
- localValue.value = files
566
- emits('update:modelValue', localValue.value)
567
- }
568
- // 监听表单发生变化后触发展示函数
569
- watch(() => form, (_oldVal, _newVal) => {
570
- showFormItemFunc()
571
- // 数据源来自人员联动时更新数据
572
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
573
- debouncedUserLinkFunc()
574
-
575
- // 数据源来自部门联动时更新数据
576
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
577
- debouncedDepLinkFunc()
578
- }, { deep: true })
579
-
580
- function onDateTimePickerConfirm() {
581
- showDatePicker.value = false
582
- const dateStr = dateTimePickerValue.value.date.join('-')
583
- const timeStr = dateTimePickerValue.value.time.join(':')
584
- localValue.value = `${dateStr} ${timeStr}`
585
- }
586
-
587
- function onPickerCancel() {
588
- showDatePicker.value = false
589
- }
590
- </script>
591
-
592
- <template>
593
- <div>
594
- <!-- switch开关 -->
595
- <VanField
596
- v-if="attr.type === 'switch' && showItem"
597
- name="switch"
598
- :label="labelData"
599
- :label-align="labelAlign"
600
- :input-align="attr.inputAlign ? attr.inputAlign : 'right'"
601
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
602
- >
603
- <template #input>
604
- <VanSwitch v-model="localValue" />
605
- </template>
606
- </VanField>
607
-
608
- <!-- 复选框 -->
609
- <!-- <VanField
610
- v-if="attr.type === 'checkbox'"
611
- name="checkbox"
612
- :label="labelData"
613
- >
614
- <template #input>
615
- <VanCheckbox v-model="localValue" shape="square" />
616
- </template>
617
- </VanField> -->
618
-
619
- <!-- 多选框-checkbox-复选框组 -->
620
- <template v-if="attr.type === 'checkbox' && showItem">
621
- <!-- 勾选 -->
622
- <VanField
623
- v-if="attr.showMode === 'checkbox' && mode !== '查询'"
624
- name="checkboxGroup"
625
- :label="labelData"
626
- :label-align="labelAlign"
627
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
628
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
629
- >
630
- <template #input>
631
- <van-checkbox-group v-model="localValue as any[]" direction="horizontal" shape="square" :disabled="readonly">
632
- <VanCheckbox v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :shape="rules?.[attr.model].shape" :value="item[columnsField.value]">
633
- {{ item[columnsField.text] }}
634
- </VanCheckbox>
635
- </van-checkbox-group>
636
- </template>
637
- </VanField>
638
- <VanField
639
- v-if="attr.showMode === 'checkbox' && mode === '查询'"
640
- name="checkboxGroup"
641
- :label="labelData"
642
- :label-align="labelAlign"
643
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
644
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
645
- >
646
- <template #input>
647
- <XGridDropOption
648
- v-model="(localValue as string[])"
649
- :column-num="labelData ? 3 : 4"
650
- :multiple="true"
651
- :columns="option"
652
- />
653
- </template>
654
- </VanField>
655
- <!-- 下拉 -->
656
- <XMultiSelect
657
- v-else
658
- v-model="localValue"
659
- :label="labelData"
660
- :readonly="readonly"
661
- :placeholder="placeholder"
662
- :columns="option"
663
- :option="attr.option ? attr.option : columnsField"
664
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
665
- />
666
- </template>
667
-
668
- <!-- 单选框 -->
669
- <VanField
670
- v-if="attr.type === 'radio' && mode !== '查询' && showItem"
671
- name="radio"
672
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
673
- :label="labelData"
674
- :label-align="labelAlign"
675
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
676
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
677
- >
678
- <template #input>
679
- <VanRadioGroup v-model="localValue" direction="horizontal" :disabled="readonly">
680
- <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
681
- {{ item[columnsField.text] }}
682
- </VanRadio>
683
- </VanRadioGroup>
684
- </template>
685
- </VanField>
686
-
687
- <!-- 单选框-查询 -->
688
- <VanField
689
- v-if="attr.type === 'radio' && mode === '查询' && showItem"
690
- name="radio"
691
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
692
- :label="labelData"
693
- :label-align="labelAlign"
694
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
695
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
696
- >
697
- <template #input>
698
- <XGridDropOption
699
- v-model="(localValue as string)"
700
- :column-num="labelData ? 3 : 4"
701
- :columns="option"
702
- />
703
- </template>
704
- </VanField>
705
-
706
- <!-- 步进器 -->
707
- <VanField
708
- v-if="attr.type === 'stepper' && showItem"
709
- name="stepper"
710
- :label="labelData"
711
- :label-align="labelAlign"
712
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
713
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
714
- >
715
- <template #input>
716
- <VanStepper :disabled="readonly" v-model="(localValue as string)" />
717
- </template>
718
- </VanField>
719
-
720
- <!-- 评分 -->
721
- <VanField
722
- v-if="attr.type === 'rate' && showItem"
723
- name="rate"
724
- :label="labelData"
725
- :label-align="labelAlign"
726
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
727
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
728
- >
729
- <template #input>
730
- <VanRate :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" v-model="(localValue as number)" />
731
- </template>
732
- </VanField>
733
-
734
- <!-- 滑块 -->
735
- <VanField
736
- v-if="attr.type === 'slider' && showItem"
737
- name="slider"
738
- :label="labelData"
739
- :label-align="labelAlign"
740
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
741
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
742
- >
743
- <template #input>
744
- <VanSlider :readonly="readonly" v-model="(localValue as number)" />
745
- </template>
746
- </VanField>
747
-
748
- <!-- 图片文件上传 -->
749
- <VanField
750
- v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
751
- name="uploader"
752
- :label="labelData"
753
- :label-align="labelAlign"
754
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
755
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
756
- >
757
- <template #input>
758
- <!-- <van-uploader v-model="localValue" /> -->
759
- <Uploader
760
- upload-mode="server"
761
- :image-list="(localValue as any[])"
762
- authority="admin"
763
- @update-file-list="updateFile"
764
- />
765
- </template>
766
- </VanField>
767
-
768
- <!-- 选择器 -->
769
- <VanField
770
- v-if="attr.type === 'picker' && showItem"
771
- v-model="pickerValue"
772
- name="picker"
773
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
774
- :label="labelData"
775
- :label-align="labelAlign"
776
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
777
- readonly
778
- is-link
779
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
780
- @click="readonly ? null : showPicker = true"
781
- />
782
- <VanPopup v-model:show="showPicker" round position="bottom" teleport="body">
783
- <VanPicker
784
- v-model="(localValue as Numeric[])"
785
- :title="attr.name"
786
- :columns="attr.selectKey"
787
- :readonly="readonly"
788
- :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
789
- :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
790
- :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
791
- @cancel="showPicker = false"
792
- @confirm="onPickerConfirm"
793
- />
794
- </VanPopup>
795
-
796
- <!-- 日历选择-查询 -->
797
- <VanField
798
- v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
799
- v-model="(localValue as string | number)"
800
- is-link
801
- readonly
802
- name="rangePicker"
803
- :label="labelData"
804
- :label-align="labelAlign"
805
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
806
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
807
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
808
- @click="calendarShow = true"
809
- />
810
- <VanCalendar
811
- v-model:show="calendarShow"
812
- switch-mode="year-month"
813
- allow-same-day
814
- type="range"
815
- teleport="body"
816
- :show-confirm="attr.showConfirm"
817
- @confirm="onCalendarConfirm"
818
- />
819
-
820
- <!-- 日期选择-非查询 -->
821
- <VanField
822
- v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
823
- v-model="(localValue as string | number)"
824
- name="datePicker"
825
- :label="labelData"
826
- :label-align="labelAlign"
827
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
828
- readonly
829
- :is-link="true"
830
- :placeholder="placeholder"
831
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
832
- @click="readonly ? null : showDataTimePicker()"
833
- />
834
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body">
835
- <VanPickerGroup
836
- :title="attr.name"
837
- :tabs="['选择日期', '选择时间']"
838
- next-step-text="下一步"
839
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
840
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
841
- @confirm="onDateTimePickerConfirm"
842
- @cancel="onPickerCancel"
843
- >
844
- <VanDatePicker
845
- v-model="dateTimePickerValue.date"
846
- :columns-type="attr.columnsType ? attr.columnsType : ['year', 'month', 'day']"
847
- />
848
- <VanTimePicker
849
- v-model="dateTimePickerValue.time"
850
- :columns-type="['hour', 'minute', 'second']"
851
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
852
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
853
- />
854
- </VanPickerGroup>
855
- </VanPopup>
856
-
857
- <!-- 日期选择-查询 -->
858
- <VanField
859
- v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
860
- v-model="(localValue as string | number)"
861
- name="datePicker"
862
- :label="labelData"
863
- :label-align="labelAlign"
864
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
865
- readonly
866
- :is-link="true"
867
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
868
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
869
- @click="showDatePicker = true"
870
- />
871
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body">
872
- <VanPickerGroup
873
- :title="attr.name"
874
- :tabs="['选择日期', '选择时间']"
875
- next-step-text="下一步"
876
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
877
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
878
- @confirm="onDateTimePickerConfirm"
879
- @cancel="onPickerCancel"
880
- >
881
- <VanDatePicker
882
- v-model="dateTimePickerValue.date"
883
- :columns-type="attr.columnsType ? attr.columnsType : ['year', 'month', 'day']"
884
- :readonly="attr.readonly ? attr.readonly : false"
885
- />
886
- <VanTimePicker
887
- v-model="dateTimePickerValue.time"
888
- :columns-type="['hour', 'minute', 'second']"
889
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
890
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
891
- :readonly="attr.readonly ? attr.readonly : false"
892
- />
893
- </VanPickerGroup>
894
- </VanPopup>
895
-
896
- <!-- 时间选择 -->
897
- <VanField
898
- v-if="attr.type === 'timePicker' && showItem"
899
- v-model="timePickerValue"
900
- name="timePicker"
901
- is-link
902
- readonly
903
- :placeholder="attr.placeholder"
904
- :label="labelData"
905
- :label-align="labelAlign"
906
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
907
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
908
- @click="showTimePicker = true"
909
- />
910
- <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body">
911
- <VanTimePicker
912
- v-model="localValue as string[]"
913
- :title="attr.name"
914
- :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
915
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
916
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
917
- :readonly="readonly"
918
- @cancel="showTimePicker = false"
919
- @confirm="onTimePickerConfirm"
920
- />
921
- </VanPopup>
922
-
923
- <!-- 省市区选择 -->
924
- <VanField
925
- v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
926
- v-model="area"
927
- name="area"
928
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
929
- is-link
930
- readonly
931
- :label="labelData"
932
- :label-align="labelAlign"
933
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
934
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
935
- @click="readonly ? null : showArea = true"
936
- />
937
- <VanPopup v-model:show="showArea" position="bottom" teleport="body">
938
- <VanArea
939
- v-model="localValue as string" :title="attr.name" :area-list="areaList"
940
- @confirm="onAreaConfirm"
941
- @cancel="showArea = false"
942
- />
943
- </VanPopup>
944
-
945
- <!-- 单选下拉列表 -->
946
- <XSelect
947
- v-if="attr.type === 'select' && showItem"
948
- v-model="localValue"
949
- :label="labelData"
950
- :readonly="readonly"
951
- clearable
952
- :placeholder="placeholder"
953
- :columns="option"
954
- :option="attr.option ? attr.option : columnsField"
955
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
956
- />
957
-
958
- <!-- 文本区域 -->
959
- <VanField
960
- v-if="attr.type === 'textarea' && showItem"
961
- v-model="(localValue as string)"
962
- rows="3"
963
- autosize
964
- :label="labelData"
965
- :label-align="labelAlign"
966
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
967
- type="textarea"
968
- :readonly="readonly"
969
- :maxlength="attr.maxlength ? attr.maxlength : 200"
970
- :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
971
- show-word-limit
972
- label-align="top"
973
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
974
- />
975
-
976
- <!-- 文本输入框 -->
977
- <VanField
978
- v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
979
- v-model="(localValue as string)"
980
- :label="labelData"
981
- :label-align="labelAlign"
982
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
983
- :type="attr.type"
984
- :readonly="readonly"
985
- :disabled="attr.disabled"
986
- :placeholder="placeholder"
987
- :error-message="errorMessage"
988
- :clearable="attr.clearable"
989
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
990
- @blur="() => formTypeCheck(attr, localValue as string)"
991
- />
992
- </div>
993
- </template>
994
-
995
- <style scoped>
996
-
997
- </style>
1
+ <script setup lang="ts">
2
+ import {
3
+ Area as VanArea,
4
+ Calendar as VanCalendar,
5
+ Checkbox as VanCheckbox,
6
+ DatePicker as VanDatePicker,
7
+ Field as VanField,
8
+ Picker as VanPicker,
9
+ PickerGroup as VanPickerGroup,
10
+ Popup as VanPopup,
11
+ Radio as VanRadio,
12
+ RadioGroup as VanRadioGroup,
13
+ Rate as VanRate,
14
+ Slider as VanSlider,
15
+ Stepper as VanStepper,
16
+ Switch as VanSwitch,
17
+ TimePicker as VanTimePicker,
18
+ CheckboxGroup as vanCheckboxGroup,
19
+ } from 'vant'
20
+ import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
21
+ import { areaList } from '@vant/area-data'
22
+ import { runLogic } from '@af-mobile-client-vue3/services/api/common'
23
+ import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
24
+ import XSelect from '@af-mobile-client-vue3/components/core/XSelect/index.vue'
25
+ import XMultiSelect from '@af-mobile-client-vue3/components/core/XMultiSelect/index.vue'
26
+ import XGridDropOption from '@af-mobile-client-vue3/components/core/XGridDropOption/index.vue'
27
+ import type { Numeric } from 'vant/es/utils'
28
+ import { getDict } from '@af-mobile-client-vue3/utils/dictUtil'
29
+ import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
30
+ import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
31
+ import { debounce } from 'lodash-es'
32
+ import { searchToListOption, searchToOption } from '@af-mobile-client-vue3/services/v3Api'
33
+
34
+ const props = defineProps({
35
+ attr: {
36
+ type: Object,
37
+ },
38
+ form: {
39
+ type: Object,
40
+ },
41
+ datePickerFilter: {
42
+ type: Function,
43
+ default: () => true,
44
+ },
45
+ datePickerFormatter: {
46
+ type: Function,
47
+ default: (type, val) => val,
48
+ },
49
+ mode: {
50
+ type: String,
51
+ default: '查询',
52
+ },
53
+ serviceName: {
54
+ type: String,
55
+ default: undefined,
56
+ },
57
+ // 调用logic获取数据源的追加参数
58
+ getDataParams: {
59
+ type: Object,
60
+ default: undefined,
61
+ },
62
+ disabled: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
66
+ rules: {
67
+ type: Object,
68
+ default: () => {},
69
+ },
70
+ modelValue: {
71
+ type: [String, Number, Boolean, Array, Object],
72
+ default: undefined,
73
+ },
74
+ showLabel: {
75
+ type: Boolean,
76
+ default: true,
77
+ },
78
+ // radio/checkbox/select/mul-select 选项数据结构
79
+ columnsField: {
80
+ type: Object,
81
+ default: () => {
82
+ return { text: 'label', value: 'value' }
83
+ },
84
+ },
85
+
86
+ })
87
+
88
+ const emits = defineEmits(['update:modelValue'])
89
+ // 判断并初始化防抖函数
90
+ let debouncedUserLinkFunc: Function | null = null
91
+ let debouncedDepLinkFunc: Function | null = null
92
+
93
+ const { attr, form, mode, serviceName, getDataParams, columnsField } = props
94
+ const calendarShow = ref(false)
95
+ const option = ref([])
96
+ const pickerValue = ref(undefined)
97
+ const datePickerValue = ref(undefined)
98
+ const timePickerValue = ref(undefined)
99
+ const area = ref<any>(undefined)
100
+ const showPicker = ref(false)
101
+ const showDatePicker = ref(false)
102
+ const showTimePicker = ref(false)
103
+ const showArea = ref(false)
104
+ const errorMessage = ref('')
105
+
106
+ // 表单默认值
107
+ // 输入-非查询
108
+ const formInputDefaultValue = ref('')
109
+ // 输入-查询
110
+ const queryInputDefaultValue = ref('')
111
+ // 选择-非查询
112
+ const formSelectDefaultValue = ref([])
113
+ // 选择-查询
114
+ const querySelectDefaultValue = ref([])
115
+
116
+ // eslint-disable-next-line ts/no-use-before-define
117
+ const currUser = computed(() => userState.f.resources.id)
118
+ // 是否展示当前项
119
+ const showItem = ref(true)
120
+
121
+ // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
122
+ const currInst = getCurrentInstance()
123
+
124
+ // 配置中心->表单项变更触发函数
125
+ const dataChangeFunc = debounce(async () => {
126
+ if (attr.dataChangeFunc)
127
+ await executeStrFunctionByContext(currInst, attr.dataChangeFunc, [form, attr, null, mode])
128
+ }, 500)
129
+
130
+ // 配置中心->表单项展示函数
131
+ const showFormItemFunc = debounce(async () => {
132
+ if (attr.showFormItemFunc) {
133
+ const obj = await executeStrFunctionByContext(currInst, attr.showFormItemFunc, [form, attr, null, mode])
134
+ // 判断是 bool 还是 obj 兼容
135
+ if (typeof obj === 'boolean') {
136
+ showItem.value = obj
137
+ }
138
+ else if (obj && typeof obj === 'object') {
139
+ // obj 是一个对象,并且不是数组
140
+ showItem.value = obj?.show
141
+ }
142
+ }
143
+ }, 500)
144
+
145
+ const localValue = computed({
146
+ get() {
147
+ // if (props.modelValue !== undefined) {
148
+ // return props.modelValue
149
+ // }
150
+ switch (attr.type) {
151
+ case 'uploader':
152
+ if (mode === '查询') {
153
+ // console.log(querySelectDefaultValue.value)
154
+ return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
155
+ }
156
+ else {
157
+ return props.modelValue !== undefined ? props.modelValue : formSelectDefaultValue.value
158
+ }
159
+ case 'switch':
160
+ return props.modelValue !== undefined ? props.modelValue : false
161
+ case 'checkbox':
162
+ case 'file':
163
+ case 'image':
164
+ case 'timePicker':
165
+ case 'datePicker':
166
+ if (mode === '查询') {
167
+ // console.log(querySelectDefaultValue.value)
168
+ return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
169
+ }
170
+ else {
171
+ // console.log(formSelectDefaultValue.value)
172
+ return props.modelValue !== undefined ? props.modelValue : formSelectDefaultValue.value
173
+ }
174
+ // case 'datePicker':
175
+ // if (mode === '查询') {
176
+ // // console.log(querySelectDefaultValue.value)
177
+ // return props.modelValue !== undefined ? props.modelValue : querySelectDefaultValue.value
178
+ // }
179
+ // else {
180
+ // if (props.modelValue !== undefined) {
181
+ // // 拆分日期和时间
182
+ // const [dateStr, timeStr] = props.modelValue.split(' ')
183
+ // // 拆分日期部分
184
+ // const date = dateStr.split('-')
185
+ // // 拆分时间部分
186
+ // const time = timeStr.split(':')
187
+ // // 赋值给 dateTimePickerValue
188
+ // // eslint-disable-next-line vue/no-side-effects-in-computed-properties
189
+ // dateTimePickerValue.value = {
190
+ // date,
191
+ // time,
192
+ // }
193
+ // return dateTimePickerValue.value
194
+ // }
195
+ // return formSelectDefaultValue.value
196
+ // }
197
+ case 'radio':
198
+ case 'rate':
199
+ case 'slider':
200
+ case 'area':
201
+ case 'citySelect':
202
+ case 'calendar':
203
+ case 'textarea':
204
+ case 'intervalPicker':
205
+ case 'input':
206
+ case 'select':
207
+ if (mode === '查询')
208
+ return props.modelValue !== undefined ? props.modelValue : queryInputDefaultValue.value
209
+ else
210
+ return props.modelValue !== undefined ? props.modelValue : formInputDefaultValue.value
211
+ case 'stepper':
212
+ return props.modelValue !== undefined ? props.modelValue : 1
213
+ case 'rangePicker':
214
+ if (props.modelValue && Array.isArray(props.modelValue) && props.modelValue.length > 1)
215
+ return `${props.modelValue[0]} ~ ${props.modelValue[1]}`
216
+
217
+ else
218
+ return props.modelValue
219
+
220
+ default:
221
+ return undefined
222
+ }
223
+ },
224
+ set(newValue) {
225
+ emits('update:modelValue', newValue)
226
+ dataChangeFunc()
227
+ },
228
+ })
229
+
230
+ // 表单校验的类型校验
231
+ function formTypeCheck(attr, value) {
232
+ switch (attr.rule.type) {
233
+ case 'string':
234
+ if (value.length === 0) {
235
+ errorMessage.value = `${attr.name}必须为有效的字符串`
236
+ return
237
+ }
238
+ break
239
+ case 'number':
240
+ if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/.test(value)) {
241
+ errorMessage.value = `${attr.name}必须为数字`
242
+ return
243
+ }
244
+ break
245
+ case 'boolean':
246
+ case 'array':
247
+ case 'regexp':
248
+ if (value) {
249
+ errorMessage.value = ''
250
+ return
251
+ }
252
+ break
253
+ case 'integer':
254
+ if (!/^-?\d+$/.test(value)) {
255
+ errorMessage.value = `${attr.name}必须为整数`
256
+ return
257
+ }
258
+ break
259
+ case 'float':
260
+ if (!/^-?\d+\.\d+$/.test(value)) {
261
+ errorMessage.value = `${attr.name}必须为小数`
262
+ return
263
+ }
264
+ break
265
+ case 'email':
266
+ if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value)) {
267
+ errorMessage.value = `请输入正确的邮箱地址`
268
+ return
269
+ }
270
+ break
271
+ case 'idNumber':
272
+ if (!/^(^\d{15}$|^\d{17}([0-9]|X)$)$/.test(value)) {
273
+ errorMessage.value = `请输入正确的身份证号码`
274
+ return
275
+ }
276
+ break
277
+ case 'userPhone':
278
+ if (!/^1[3-9]\d{9}$/.test(value)) {
279
+ errorMessage.value = `请输入正确的手机号码`
280
+ return
281
+ }
282
+ break
283
+ case 'landlineNumber':
284
+ if (!/^(0\d{2,3}[-\s]?)\d{7,8}$/.test(value)) {
285
+ errorMessage.value = `请输入正确的座机号码`
286
+ return
287
+ }
288
+ break
289
+ case 'greaterThanZero':
290
+ if (!/^(?:[1-9]\d*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) {
291
+ errorMessage.value = `请输入一个大于0的数字`
292
+ return
293
+ }
294
+ break
295
+ case 'greaterThanOrEqualZero':
296
+ if (!/^(?:\d+|\d*\.\d+)$/.test(value)) {
297
+ errorMessage.value = `请输入一个大于等于0的数字`
298
+ return
299
+ }
300
+ break
301
+ case 'stringLength':
302
+ if (!(value.length >= attr.rule.minLen && value.length < attr.rule.maxLen)) {
303
+ errorMessage.value = `长度必须在${attr.rule.minLen}~${attr.rule.maxLen}之间`
304
+ return
305
+ }
306
+ break
307
+ case 'customJs':
308
+ // eslint-disable-next-line no-case-declarations
309
+ const funcStr = attr.rule.customValidatorFunc
310
+ // 输入的数据是否符合条件
311
+ // eslint-disable-next-line no-case-declarations
312
+ const status = ref(true)
313
+ // 提取函数体部分
314
+ // eslint-disable-next-line no-case-declarations
315
+ const funcBodyMatch = funcStr.match(/function\s*\(.*?\)\s*{([\s\S]*)}/)
316
+ if (!funcBodyMatch)
317
+ throw new Error('自定义校验函数不合法')
318
+ // eslint-disable-next-line no-case-declarations
319
+ const funcBody = funcBodyMatch[1].trim() // 提取函数体
320
+ // 使用 new Function 创建函数
321
+ // eslint-disable-next-line no-new-func,no-case-declarations
322
+ const customValidatorFunc = new Function('rule', 'value', 'callback', 'form', 'attr', 'util', funcBody)
323
+ // 定义 callback 函数
324
+ // eslint-disable-next-line no-case-declarations
325
+ const callback = (error) => {
326
+ if (error) {
327
+ errorMessage.value = `${error}`
328
+ status.value = false // 表示有错误发生
329
+ }
330
+ }
331
+ // 调用自定义校验函数
332
+ customValidatorFunc(
333
+ attr.rule,
334
+ value,
335
+ callback,
336
+ form,
337
+ attr,
338
+ {}, // util 对象(可以根据需要传递)
339
+ )
340
+ if (!status.value)
341
+ return
342
+ break
343
+ default:
344
+ errorMessage.value = ''
345
+ break
346
+ }
347
+ errorMessage.value = ''
348
+ }
349
+
350
+ onBeforeMount(() => {
351
+ init()
352
+ showFormItemFunc()
353
+ dataChangeFunc()
354
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
355
+ debouncedUserLinkFunc = debounce(() => updateResOptions('人员'), 200)
356
+
357
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
358
+ debouncedDepLinkFunc = debounce(() => updateResOptions('部门'), 200)
359
+ })
360
+ // 是否展示表单左侧label文字
361
+ const labelData = computed(() => {
362
+ return props.showLabel ? attr.name : null
363
+ })
364
+ // 是否展示表单左侧label文字
365
+ const labelAlign = computed(() => {
366
+ return attr.labelAlign ? attr.labelAlign : 'top'
367
+ })
368
+ // 是否只读
369
+ const readonly = computed(() => {
370
+ return attr.addOrEdit === 'readonly' || mode === '预览'
371
+ })
372
+ // 提示内容
373
+ const placeholder = computed(() => {
374
+ if (attr.addOrEdit === 'readonly')
375
+ return ' 暂无内容 ~ '
376
+ else
377
+ return attr.placeholder ? attr.placeholder : `请选择${attr.name}`
378
+ })
379
+ // 登录信息 (可以在配置的动态函数中使用 this.setupState 获取到当前组件内的全部函数和变量 例:this.setupState.userState)
380
+ const userState = useUserStore().getLogin()
381
+
382
+ const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
383
+
384
+ function onCalendarConfirm(values) {
385
+ localValue.value = [formatDate(values[0]), formatDate(values[1])]
386
+ calendarShow.value = false
387
+ }
388
+
389
+ function init() {
390
+ if (attr.keyName && typeof attr.keyName === 'string') {
391
+ if (attr.keyName && attr.keyName.includes('logic@')) {
392
+ getData({}, (res) => {
393
+ option.value = res
394
+ initRadioValue()
395
+ })
396
+ }
397
+ else if (attr.keyName && attr.keyName.includes('config@')) {
398
+ const configName = attr.keyName.substring(7)
399
+ getDict(configName, (result) => {
400
+ if (result)
401
+ option.value = result
402
+ }, serviceName)
403
+ }
404
+ else if (attr.keyName && attr.keyName.includes('search@')) {
405
+ let source = attr.keyName.substring(7)
406
+ const userid = currUser.value
407
+ let roleName = 'roleName'
408
+ if (source.startsWith('根据角色[') && source.endsWith(']获取人员')) {
409
+ const startIndex = source.indexOf('[') + 1
410
+ const endIndex = source.indexOf(']', startIndex)
411
+ roleName = source.substring(startIndex, endIndex)
412
+ source = '根据角色获取人员'
413
+ }
414
+ const searchData = { source, userid, roleName }
415
+ if (source.startsWith('根据表单项[') && source.endsWith(']联动人员'))
416
+ updateResOptions('人员')
417
+ else if (source.startsWith('根据表单项[') && source.endsWith(']联动部门'))
418
+ updateResOptions('部门')
419
+ else if (attr.type === 'select' || attr.type === 'checkbox')
420
+ searchToListOption(searchData, res => getDataCallback(res))
421
+ else
422
+ searchToOption(searchData, res => getDataCallback(res))
423
+ }
424
+ else {
425
+ initRadioValue()
426
+ }
427
+ }
428
+ if (attr.type === 'radio' || attr.type === 'rate' || attr.type === 'slider' || attr.type === 'area' || attr.type === 'citySelect' || attr.type === 'calendar' || attr.type === 'textarea' || attr.type === 'intervalPicker' || attr.type === 'input' || attr.type === 'select') {
429
+ if (attr.formDefault)
430
+ formInputDefaultValue.value = attr.formDefault
431
+ if (attr.queryFormDefault)
432
+ queryInputDefaultValue.value = attr.queryFormDefault
433
+ }
434
+
435
+ if (attr.type === 'checkbox' || attr.type === 'uploader' || attr.type === 'file' || attr.type === 'image' || attr.type === 'datePicker' || attr.type === 'timePicker') {
436
+ if (attr.formDefault) {
437
+ if (attr.type === 'checkbox' || attr.type === 'image' || attr.type === 'file')
438
+ formSelectDefaultValue.value = attr.formDefault
439
+ else
440
+ formSelectDefaultValue.value.push(attr.formDefault)
441
+ }
442
+ if (attr.queryFormDefault) {
443
+ // console.log(querySelectDefaultValue.value)
444
+ querySelectDefaultValue.value.push(attr.queryFormDefault)
445
+ // querySelectDefaultValue.value = attr.queryFormDefault
446
+ }
447
+ }
448
+ }
449
+
450
+ function getDataCallback(res) {
451
+ option.value = res
452
+ if (attr.type === 'radio')
453
+ initRadioValue()
454
+ }
455
+
456
+ async function updateResOptions(type) {
457
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString()?.endsWith(`]联动${type}`)) {
458
+ const searchData = { source: `获取${type}`, userid: currUser.value }
459
+ const startIndex = attr.keyName.indexOf('[') + 1
460
+ const endIndex = attr.keyName.indexOf(']', startIndex)
461
+ const formModel = attr.keyName.substring(startIndex, endIndex).replace('.', '_')
462
+ // console.log(form)
463
+ const formModelData = form[formModel]
464
+ if (formModel?.length && formModelData?.length) {
465
+ await searchToListOption(searchData, (res) => {
466
+ // console.log(res)
467
+ // console.log(form)
468
+ getDataCallback(res.filter((h) => {
469
+ return formModelData['0'] === h.f_organization_id || formModelData['0'] === h.f_department_id || formModelData['0'] === h.parentid
470
+ // if (formModel.indexOf('org') > -1) {
471
+ // return formModelData?.includes(h.orgid || h.f_organization_id || h.parentid)
472
+ // } else {
473
+ // return formModelData?.includes(h?.parentid)
474
+ // }
475
+ }))
476
+ })
477
+ }
478
+ }
479
+ }
480
+
481
+ function initRadioValue() {
482
+ if ((mode === '新增' || mode === '修改') && attr.type === 'radio' && !localValue.value) {
483
+ if (attr.keys && attr.keys.length > 0)
484
+ localValue.value = attr.keys[0].value
485
+ else if (option.value && option.value.length > 0)
486
+ localValue.value = option.value[0].value
487
+ }
488
+ }
489
+
490
+ function getData(value, callback) {
491
+ if (value !== '') {
492
+ const logicName = attr.keyName
493
+ const logic = logicName.substring(6)
494
+ // 调用logic前设置参数
495
+ if (getDataParams && getDataParams[attr.model])
496
+ Object.assign(value, getDataParams[attr.model])
497
+
498
+ runLogic(logic, value, serviceName).then((res) => {
499
+ callback(res)
500
+ })
501
+ }
502
+ }
503
+
504
+ function onPickerConfirm({ selectedOptions }) {
505
+ showPicker.value = false
506
+ pickerValue.value = selectedOptions[0].text
507
+ emits('update:modelValue', [selectedOptions[0].text])
508
+ }
509
+
510
+ // 日期时间选择数据
511
+ const dateTimePickerValue = ref(undefined)
512
+ function showDataTimePicker() {
513
+ if (props.modelValue !== undefined && props.modelValue !== '' && props.modelValue !== null) {
514
+ // 拆分日期和时间
515
+ const [dateStr, timeStr] = props.modelValue.split(' ')
516
+ // 拆分日期部分
517
+ const date = dateStr.split('-')
518
+ // 拆分时间部分
519
+ const time = timeStr.split(':')
520
+ // 赋值给 dateTimePickerValue
521
+ dateTimePickerValue.value = {
522
+ date,
523
+ time,
524
+ }
525
+ }
526
+ else {
527
+ dateTimePickerValue.value = {
528
+ date: ['2015', '01', '01'],
529
+ time: ['00', '00', '00'],
530
+ }
531
+ }
532
+ showDatePicker.value = true
533
+ }
534
+
535
+ function onDatePickerConfirm({ selectedValues }) {
536
+ showDatePicker.value = false
537
+ localValue.value = selectedValues.join('-')
538
+ }
539
+
540
+ function onTimePickerConfirm({ selectedValues }) {
541
+ showTimePicker.value = false
542
+ timePickerValue.value = selectedValues.join(':')
543
+ emits('update:modelValue', timePickerValue.value)
544
+ }
545
+
546
+ function onAreaConfirm({ selectedOptions }) {
547
+ area.value = `${selectedOptions[0].text}-${selectedOptions[1].text}-${selectedOptions[2].text}`
548
+ showArea.value = false
549
+ emits('update:modelValue', [{
550
+ province: selectedOptions[0].text,
551
+ city: selectedOptions[1].text,
552
+ district: selectedOptions[2].text,
553
+ }])
554
+ }
555
+
556
+ function updateFile(files, _index) {
557
+ files.forEach((file) => {
558
+ if (file.content)
559
+ delete file.content
560
+ if (file.file)
561
+ delete file.file
562
+ if (file.objectUrl)
563
+ delete file.objectUrl
564
+ })
565
+ localValue.value = files
566
+ emits('update:modelValue', localValue.value)
567
+ }
568
+ // 监听表单发生变化后触发展示函数
569
+ watch(() => form, (_oldVal, _newVal) => {
570
+ showFormItemFunc()
571
+ // 数据源来自人员联动时更新数据
572
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
573
+ debouncedUserLinkFunc()
574
+
575
+ // 数据源来自部门联动时更新数据
576
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
577
+ debouncedDepLinkFunc()
578
+ }, { deep: true })
579
+
580
+ function onDateTimePickerConfirm() {
581
+ showDatePicker.value = false
582
+ const dateStr = dateTimePickerValue.value.date.join('-')
583
+ const timeStr = dateTimePickerValue.value.time.join(':')
584
+ localValue.value = `${dateStr} ${timeStr}`
585
+ }
586
+
587
+ function onPickerCancel() {
588
+ showDatePicker.value = false
589
+ }
590
+ </script>
591
+
592
+ <template>
593
+ <div>
594
+ <!-- switch开关 -->
595
+ <VanField
596
+ v-if="attr.type === 'switch' && showItem"
597
+ name="switch"
598
+ :label="labelData"
599
+ :label-align="labelAlign"
600
+ :input-align="attr.inputAlign ? attr.inputAlign : 'right'"
601
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
602
+ >
603
+ <template #input>
604
+ <VanSwitch v-model="localValue" />
605
+ </template>
606
+ </VanField>
607
+
608
+ <!-- 复选框 -->
609
+ <!-- <VanField
610
+ v-if="attr.type === 'checkbox'"
611
+ name="checkbox"
612
+ :label="labelData"
613
+ >
614
+ <template #input>
615
+ <VanCheckbox v-model="localValue" shape="square" />
616
+ </template>
617
+ </VanField> -->
618
+
619
+ <!-- 多选框-checkbox-复选框组 -->
620
+ <template v-if="attr.type === 'checkbox' && showItem">
621
+ <!-- 勾选 -->
622
+ <VanField
623
+ v-if="attr.showMode === 'checkbox' && mode !== '查询'"
624
+ name="checkboxGroup"
625
+ :label="labelData"
626
+ :label-align="labelAlign"
627
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
628
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
629
+ >
630
+ <template #input>
631
+ <van-checkbox-group v-model="localValue as any[]" direction="horizontal" shape="square" :disabled="readonly">
632
+ <VanCheckbox v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :shape="rules?.[attr.model].shape" :value="item[columnsField.value]">
633
+ {{ item[columnsField.text] }}
634
+ </VanCheckbox>
635
+ </van-checkbox-group>
636
+ </template>
637
+ </VanField>
638
+ <VanField
639
+ v-if="attr.showMode === 'checkbox' && mode === '查询'"
640
+ name="checkboxGroup"
641
+ :label="labelData"
642
+ :label-align="labelAlign"
643
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
644
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
645
+ >
646
+ <template #input>
647
+ <XGridDropOption
648
+ v-model="(localValue as string[])"
649
+ :column-num="labelData ? 3 : 4"
650
+ :multiple="true"
651
+ :columns="option"
652
+ />
653
+ </template>
654
+ </VanField>
655
+ <!-- 下拉 -->
656
+ <XMultiSelect
657
+ v-else
658
+ v-model="localValue"
659
+ :label="labelData"
660
+ :readonly="readonly"
661
+ :placeholder="placeholder"
662
+ :columns="option"
663
+ :option="attr.option ? attr.option : columnsField"
664
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
665
+ />
666
+ </template>
667
+
668
+ <!-- 单选框 -->
669
+ <VanField
670
+ v-if="attr.type === 'radio' && mode !== '查询' && showItem"
671
+ name="radio"
672
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
673
+ :label="labelData"
674
+ :label-align="labelAlign"
675
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
676
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
677
+ >
678
+ <template #input>
679
+ <VanRadioGroup v-model="localValue" direction="horizontal" :disabled="readonly">
680
+ <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
681
+ {{ item[columnsField.text] }}
682
+ </VanRadio>
683
+ </VanRadioGroup>
684
+ </template>
685
+ </VanField>
686
+
687
+ <!-- 单选框-查询 -->
688
+ <VanField
689
+ v-if="attr.type === 'radio' && mode === '查询' && showItem"
690
+ name="radio"
691
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
692
+ :label="labelData"
693
+ :label-align="labelAlign"
694
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
695
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
696
+ >
697
+ <template #input>
698
+ <XGridDropOption
699
+ v-model="(localValue as string)"
700
+ :column-num="labelData ? 3 : 4"
701
+ :columns="option"
702
+ />
703
+ </template>
704
+ </VanField>
705
+
706
+ <!-- 步进器 -->
707
+ <VanField
708
+ v-if="attr.type === 'stepper' && showItem"
709
+ name="stepper"
710
+ :label="labelData"
711
+ :label-align="labelAlign"
712
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
713
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
714
+ >
715
+ <template #input>
716
+ <VanStepper :disabled="readonly" v-model="(localValue as string)" />
717
+ </template>
718
+ </VanField>
719
+
720
+ <!-- 评分 -->
721
+ <VanField
722
+ v-if="attr.type === 'rate' && showItem"
723
+ name="rate"
724
+ :label="labelData"
725
+ :label-align="labelAlign"
726
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
727
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
728
+ >
729
+ <template #input>
730
+ <VanRate :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" v-model="(localValue as number)" />
731
+ </template>
732
+ </VanField>
733
+
734
+ <!-- 滑块 -->
735
+ <VanField
736
+ v-if="attr.type === 'slider' && showItem"
737
+ name="slider"
738
+ :label="labelData"
739
+ :label-align="labelAlign"
740
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
741
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
742
+ >
743
+ <template #input>
744
+ <VanSlider :readonly="readonly" v-model="(localValue as number)" />
745
+ </template>
746
+ </VanField>
747
+
748
+ <!-- 图片文件上传 -->
749
+ <VanField
750
+ v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
751
+ name="uploader"
752
+ :label="labelData"
753
+ :label-align="labelAlign"
754
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
755
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
756
+ >
757
+ <template #input>
758
+ <!-- <van-uploader v-model="localValue" /> -->
759
+ <Uploader
760
+ upload-mode="server"
761
+ :image-list="(localValue as any[])"
762
+ authority="admin"
763
+ @update-file-list="updateFile"
764
+ />
765
+ </template>
766
+ </VanField>
767
+
768
+ <!-- 选择器 -->
769
+ <VanField
770
+ v-if="attr.type === 'picker' && showItem"
771
+ v-model="pickerValue"
772
+ name="picker"
773
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
774
+ :label="labelData"
775
+ :label-align="labelAlign"
776
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
777
+ readonly
778
+ is-link
779
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
780
+ @click="readonly ? null : showPicker = true"
781
+ />
782
+ <VanPopup v-model:show="showPicker" round position="bottom" teleport="body">
783
+ <VanPicker
784
+ v-model="(localValue as Numeric[])"
785
+ :title="attr.name"
786
+ :columns="attr.selectKey"
787
+ :readonly="readonly"
788
+ :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
789
+ :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
790
+ :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
791
+ @cancel="showPicker = false"
792
+ @confirm="onPickerConfirm"
793
+ />
794
+ </VanPopup>
795
+
796
+ <!-- 日历选择-查询 -->
797
+ <VanField
798
+ v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
799
+ v-model="(localValue as string | number)"
800
+ is-link
801
+ readonly
802
+ name="rangePicker"
803
+ :label="labelData"
804
+ :label-align="labelAlign"
805
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
806
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
807
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
808
+ @click="calendarShow = true"
809
+ />
810
+ <VanCalendar
811
+ v-model:show="calendarShow"
812
+ switch-mode="year-month"
813
+ allow-same-day
814
+ type="range"
815
+ teleport="body"
816
+ :show-confirm="attr.showConfirm"
817
+ @confirm="onCalendarConfirm"
818
+ />
819
+
820
+ <!-- 日期选择-非查询 -->
821
+ <VanField
822
+ v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
823
+ v-model="(localValue as string | number)"
824
+ name="datePicker"
825
+ :label="labelData"
826
+ :label-align="labelAlign"
827
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
828
+ readonly
829
+ :is-link="true"
830
+ :placeholder="placeholder"
831
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
832
+ @click="readonly ? null : showDataTimePicker()"
833
+ />
834
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body">
835
+ <VanPickerGroup
836
+ :title="attr.name"
837
+ :tabs="['选择日期', '选择时间']"
838
+ next-step-text="下一步"
839
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
840
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
841
+ @confirm="onDateTimePickerConfirm"
842
+ @cancel="onPickerCancel"
843
+ >
844
+ <VanDatePicker
845
+ v-model="dateTimePickerValue.date"
846
+ :columns-type="attr.columnsType ? attr.columnsType : ['year', 'month', 'day']"
847
+ />
848
+ <VanTimePicker
849
+ v-model="dateTimePickerValue.time"
850
+ :columns-type="['hour', 'minute', 'second']"
851
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
852
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
853
+ />
854
+ </VanPickerGroup>
855
+ </VanPopup>
856
+
857
+ <!-- 日期选择-查询 -->
858
+ <VanField
859
+ v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
860
+ v-model="(localValue as string | number)"
861
+ name="datePicker"
862
+ :label="labelData"
863
+ :label-align="labelAlign"
864
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
865
+ readonly
866
+ :is-link="true"
867
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
868
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
869
+ @click="showDatePicker = true"
870
+ />
871
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body">
872
+ <VanPickerGroup
873
+ :title="attr.name"
874
+ :tabs="['选择日期', '选择时间']"
875
+ next-step-text="下一步"
876
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
877
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
878
+ @confirm="onDateTimePickerConfirm"
879
+ @cancel="onPickerCancel"
880
+ >
881
+ <VanDatePicker
882
+ v-model="dateTimePickerValue.date"
883
+ :columns-type="attr.columnsType ? attr.columnsType : ['year', 'month', 'day']"
884
+ :readonly="attr.readonly ? attr.readonly : false"
885
+ />
886
+ <VanTimePicker
887
+ v-model="dateTimePickerValue.time"
888
+ :columns-type="['hour', 'minute', 'second']"
889
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
890
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
891
+ :readonly="attr.readonly ? attr.readonly : false"
892
+ />
893
+ </VanPickerGroup>
894
+ </VanPopup>
895
+
896
+ <!-- 时间选择 -->
897
+ <VanField
898
+ v-if="attr.type === 'timePicker' && showItem"
899
+ v-model="timePickerValue"
900
+ name="timePicker"
901
+ is-link
902
+ readonly
903
+ :placeholder="attr.placeholder"
904
+ :label="labelData"
905
+ :label-align="labelAlign"
906
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
907
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
908
+ @click="showTimePicker = true"
909
+ />
910
+ <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body">
911
+ <VanTimePicker
912
+ v-model="localValue as string[]"
913
+ :title="attr.name"
914
+ :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
915
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
916
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
917
+ :readonly="readonly"
918
+ @cancel="showTimePicker = false"
919
+ @confirm="onTimePickerConfirm"
920
+ />
921
+ </VanPopup>
922
+
923
+ <!-- 省市区选择 -->
924
+ <VanField
925
+ v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
926
+ v-model="area"
927
+ name="area"
928
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
929
+ is-link
930
+ readonly
931
+ :label="labelData"
932
+ :label-align="labelAlign"
933
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
934
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
935
+ @click="readonly ? null : showArea = true"
936
+ />
937
+ <VanPopup v-model:show="showArea" position="bottom" teleport="body">
938
+ <VanArea
939
+ v-model="localValue as string" :title="attr.name" :area-list="areaList"
940
+ @confirm="onAreaConfirm"
941
+ @cancel="showArea = false"
942
+ />
943
+ </VanPopup>
944
+
945
+ <!-- 单选下拉列表 -->
946
+ <XSelect
947
+ v-if="attr.type === 'select' && showItem"
948
+ v-model="localValue"
949
+ :label="labelData"
950
+ :readonly="readonly"
951
+ clearable
952
+ :placeholder="placeholder"
953
+ :columns="option"
954
+ :option="attr.option ? attr.option : columnsField"
955
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
956
+ />
957
+
958
+ <!-- 文本区域 -->
959
+ <VanField
960
+ v-if="attr.type === 'textarea' && showItem"
961
+ v-model="(localValue as string)"
962
+ rows="3"
963
+ autosize
964
+ :label="labelData"
965
+ :label-align="labelAlign"
966
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
967
+ type="textarea"
968
+ :readonly="readonly"
969
+ :maxlength="attr.maxlength ? attr.maxlength : 200"
970
+ :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
971
+ show-word-limit
972
+ label-align="top"
973
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
974
+ />
975
+
976
+ <!-- 文本输入框 -->
977
+ <VanField
978
+ v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
979
+ v-model="(localValue as string)"
980
+ :label="labelData"
981
+ :label-align="labelAlign"
982
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
983
+ :type="attr.type"
984
+ :readonly="readonly"
985
+ :disabled="attr.disabled"
986
+ :placeholder="placeholder"
987
+ :error-message="errorMessage"
988
+ :clearable="attr.clearable"
989
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
990
+ @blur="() => formTypeCheck(attr, localValue as string)"
991
+ />
992
+ </div>
993
+ </template>
994
+
995
+ <style scoped>
996
+
997
+ </style>