af-mobile-client-vue3 1.0.87 → 1.0.90

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