gi-component 0.0.26 → 0.0.27

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,426 +1,423 @@
1
- <template>
2
- <ElForm ref="formRef" :class="getClass" v-bind="formProps" :model="props.modelValue">
3
- <Grid class="w-full" :col-gap="12" v-bind="props.gridProps" :collapsed="collapsed">
4
- <template v-for="(item, index) in props.columns">
5
- <GridItem v-if="item.type === 'title'" :key="`title${index}`" :span="100">
6
- <ElFormItem label-width="0">
7
- <el-alert :class="b('form-item__title')" :title="typeof item.label === 'string' ? item.label : ''"
8
- type="info" :closable="false" />
9
- </ElFormItem>
10
- </GridItem>
11
-
12
- <template v-else>
13
- <GridItem v-if="!isHide(item)" :key="item.field + index" v-bind="item.gridItemProps || props.gridItemProps"
14
- :span="item.span
15
- || item.gridItemProps?.span
16
- || props?.gridItemProps?.span
17
- ">
18
- <ElFormItem :key="item.field + index" :prop="item.field" :label="item.label" :rules="getFormItemRules(item)"
19
- v-bind="item.formItemProps">
20
- <template v-if="item?.labelRender" #label>
21
- <component :is="item.labelRender"></component>
22
- </template>
23
- <div v-if="item.type === 'slot'" class="w-full">
24
- <slot :name="item.field" :item="item"></slot>
25
- </div>
26
- <template v-else>
27
- <div :class="b('form-item__content')">
28
- <div :class="b('form-item__component')">
29
- <component :is="CompMap[item.type] || item.type" :disabled="isDisabled(item)" class="w-full"
30
- v-bind="getComponentBindProps(item)" :model-value="props.modelValue[item.fieldName || item.field]
31
- " @update:model-value="updateModelValue($event, item)">
32
- <template v-for="(slotValue, slotKey) in item?.slots || {}" :key="slotKey" #[slotKey]="scope">
33
- <template v-if="typeof slotValue === 'string'">
34
- {{ slotValue }}
35
- </template>
36
- <template v-else-if="slotValue">
37
- <component :is="slotValue(scope)"></component>
38
- </template>
39
- </template>
40
- </component>
41
- <ElText v-if="item.tip" :class="b('form-item__tip')" type="info" size="small">
42
- {{ item.tip }}
43
- </ElText>
44
- </div>
45
- <!-- 额外信息 -->
46
- <div v-if="item.extra" :class="b('form-item__extra')">
47
- <template v-if="typeof item.extra === 'string'">
48
- <ElText type="info" size="small">
49
- {{
50
- item.extra
51
- }}
52
- </ElText>
53
- </template>
54
- <template v-else-if="item.extra">
55
- <component :is="item.extra"></component>
56
- </template>
57
- </div>
58
- </div>
59
- </template>
60
- </ElFormItem>
61
- </GridItem>
62
- </template>
63
- </template>
64
-
65
- <GridItem v-if="props.search" :suffix="props.search" :span="props?.gridItemProps?.span">
66
- <ElSpace :class="b('form__search-btns')">
67
- <ElButton type="primary" @click="emit('search')">
68
- {{ searchText }}
69
- </ElButton>
70
- <ElButton @click="emit('reset')"> 重置 </ElButton>
71
- <ElButton v-if="!props.hideFoldBtn" class="form__fold-btn" type="primary"
72
- :icon="collapsed ? ArrowDown : ArrowUp" text size="small" @click="collapsed = !collapsed">
73
- {{ collapsed ? '展开' : '收起' }}
74
- </ElButton>
75
- </ElSpace>
76
- </GridItem>
77
- </Grid>
78
- </ElForm>
79
- </template>
80
-
81
- <script lang="tsx" setup>
82
- import type { FormInstance } from 'element-plus'
83
- import type { FormColumnItem, FormColumnType, FormProps } from './type'
84
- import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
85
- import * as El from 'element-plus'
86
- import { ElButton, ElForm, ElFormItem, ElSpace, ElText } from 'element-plus'
87
- import {
88
- computed,
89
- getCurrentInstance,
90
- onMounted,
91
- ref,
92
- toRaw,
93
- useAttrs,
94
- watch
95
- } from 'vue'
96
- import { useBemClass } from '../../../hooks'
97
- import { Grid, GridItem } from '../../grid'
98
- import InputSearch from '../../input-search'
99
-
100
- const props = withDefaults(defineProps<FormProps>(), {
101
- columns: () => [],
102
- labelWidth: 'auto',
103
- scrollToError: true,
104
- showMessage: true,
105
- gridItemProps: () => ({ span: { xs: 24, sm: 24, md: 12 } }), // xs, sm, md, lg, xl, xxl
106
- search: false,
107
- searchText: '查询',
108
- hideFoldBtn: false,
109
- defaultCollapsed: undefined,
110
- fc: () => ({})
111
- })
112
-
113
- const emit = defineEmits<{
114
- (e: 'update:modelValue', value: any): void
115
- (e: 'search'): void
116
- (e: 'reset'): void
117
- }>()
118
-
119
- const attrs = useAttrs()
120
- const { b } = useBemClass()
121
- const collapsed = ref(props?.defaultCollapsed ?? props.search)
122
- const instance = getCurrentInstance()
123
-
124
- const globalConfig = instance?.appContext.config.globalProperties.$config
125
- const clearable = globalConfig?.clearable ?? false
126
- // 字典数据存储
127
- const dictData = ref<Record<string, any[]>>({})
128
-
129
- /** 组件静态配置 */
130
- const STATIC_PROPS = new Map([
131
- ['input', { clearable, maxlength: 20 }],
132
- [
133
- 'textarea',
134
- { clearable, type: 'textarea', maxlength: 200, showWordLimit: true }
135
- ],
136
- ['input-number', {}],
137
- ['input-tag', { clearable }],
138
- ['select', { clearable }],
139
- ['select-v2', { clearable }],
140
- ['tree-select', { clearable }],
141
- ['cascader', { clearable }],
142
- ['slider', {}],
143
- ['switch', {}],
144
- ['rate', {}],
145
- ['checkbox-group', {}],
146
- ['checkbox', {}],
147
- ['radio-group', {}],
148
- ['radio', {}],
149
- ['date-picker', { clearable }],
150
- ['time-picker', { clearable }],
151
- ['time-select', { clearable }],
152
- ['color-picker', {}],
153
- ['transfer', {}],
154
- ['autocomplete', {}],
155
- ['upload', {}],
156
- ['title', {}]
157
- ])
158
-
159
- // 获取字典数据
160
- const loadDictData = async () => {
161
- const dictCodes: string[] = []
162
- // 收集所有需要的字典编码
163
- props.columns?.forEach((item) => {
164
- if (item.dictCode) {
165
- dictCodes.push(item.dictCode)
166
- }
167
- })
168
- if (!dictCodes.length) return
169
- if (!globalConfig?.dictRequest) {
170
- return El.ElMessage.error('请配置全局字典请求方法dictRequest')
171
- }
172
- try {
173
- // 使用Promise.all并行处理所有字典请求
174
- const dictResponses = await Promise.all(
175
- dictCodes.map((code) =>
176
- globalConfig
177
- .dictRequest(code)
178
- .then((response: any) => ({ code, response }))
179
- )
180
- )
181
- // 处理所有响应结果
182
- dictResponses.forEach(({ code, response }) => {
183
- dictData.value[code] = response
184
- })
185
- } catch (error) {
186
- console.error('获取字典数据失败:', error)
187
- El.ElMessage.error('获取字典数据失败')
188
- }
189
- }
190
-
191
- // 组件挂载时获取字典数据
192
- onMounted(() => {
193
- loadDictData()
194
- })
195
-
196
- /** 获取占位文本 */
197
- const getPlaceholder = (item: FormColumnItem) => {
198
- if (!item.type) return undefined
199
- if (['input', 'input-number', 'input-tag'].includes(item.type)) {
200
- return `请输入${item.label}`
201
- }
202
- if (['textarea'].includes(item.type)) {
203
- return `请填写${item.label}`
204
- }
205
- if (
206
- [
207
- 'select',
208
- 'select-v2',
209
- 'tree-select',
210
- 'cascader',
211
- 'time-select',
212
- 'input-search'
213
- ].includes(item.type)
214
- ) {
215
- return `请选择${item.label}`
216
- }
217
- if (['date-picker'].includes(item.type)) {
218
- return `请选择日期`
219
- }
220
- if (['time-picker'].includes(item.type)) {
221
- return `请选择时间`
222
- }
223
- return undefined
224
- }
225
-
226
- // 组件的默认props配置
227
- function getComponentBindProps(item: FormColumnItem) {
228
- // 获取默认配置
229
- const defaultProps: any = STATIC_PROPS.get(item.type) || {}
230
- defaultProps.placeholder = getPlaceholder(item)
231
- if (item.type === 'date-picker') {
232
- defaultProps.valueFormat = item?.props?.type === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'
233
- }
234
- // 如果配置了dictCode且存在对应的字典数据,设置options
235
- if (item.dictCode && dictData.value[item.dictCode]) {
236
- defaultProps.options = dictData.value[item.dictCode]
237
- }
238
- // 合并默认配置和自定义配置
239
- return { ...defaultProps, ...(item?.props || {}) }
240
- }
241
-
242
- const formProps = computed(() => {
243
- return {
244
- ...attrs,
245
- ...props,
246
- columns: undefined,
247
- gridProps: undefined,
248
- gridItemProps: undefined,
249
- search: undefined,
250
- searchText: undefined,
251
- hideFoldBtn: undefined,
252
- defaultCollapsed: undefined,
253
- modelValue: undefined,
254
- fc: undefined
255
- }
256
- })
257
-
258
- const getClass = computed(() => {
259
- const arr: string[] = [b('form')]
260
- if (props.search) {
261
- arr.push(b('form--search'))
262
- }
263
- return arr.join(' ')
264
- })
265
-
266
- const CompMap: Record<Exclude<FormColumnType, 'slot'>, any> = {
267
- 'input': El.ElInput,
268
- 'textarea': El.ElInput,
269
- 'input-number': El.ElInputNumber,
270
- 'input-tag': El.ElInputTag,
271
- 'input-search': InputSearch,
272
- 'select': El.ElSelect,
273
- 'select-v2': El.ElSelectV2,
274
- 'tree-select': El.ElTreeSelect,
275
- 'cascader': El.ElCascader,
276
- 'slider': El.ElSlider,
277
- 'switch': El.ElSwitch,
278
- 'rate': El.ElRate,
279
- 'checkbox-group': El.ElCheckboxGroup,
280
- 'checkbox': El.ElCheckbox,
281
- 'radio-group': El.ElRadioGroup,
282
- 'radio': El.ElRadio,
283
- 'date-picker': El.ElDatePicker,
284
- 'time-picker': El.ElTimePicker,
285
- 'time-select': El.ElTimeSelect,
286
- 'color-picker': El.ElColorPicker,
287
- 'transfer': El.ElTransfer,
288
- 'autocomplete': El.ElAutocomplete,
289
- 'upload': El.ElUpload,
290
- 'title': El.ElAlert
291
- }
292
-
293
- const formRef = ref<FormInstance>()
294
-
295
- /** 表单项校验规则 */
296
- function getFormItemRules(item: FormColumnItem) {
297
- if (item.required) {
298
- return [
299
- { required: true, message: `${item.label}为必填项` },
300
- ...(Array.isArray(item.rules) ? item.rules : [])
301
- ]
302
- }
303
- if (props.fc?.[item.field]?.required) {
304
- return [
305
- {
306
- required: props.fc?.[item.field]?.required,
307
- message: `${item.label}为必填项`
308
- },
309
- ...(Array.isArray(item.rules) ? item.rules : [])
310
- ]
311
- }
312
- return item.rules
313
- }
314
-
315
- /** 隐藏表单项 */
316
- function isHide(item: FormColumnItem) {
317
- if (typeof item.hide === 'boolean') return item.hide
318
- if (typeof item.hide === 'function') {
319
- return item.hide(props.modelValue)
320
- }
321
- if (props.fc?.[item.field]?.hidden) return true
322
- if (item.hide === undefined) return false
323
- }
324
-
325
- /** 禁用表单项 */
326
- function isDisabled(item: FormColumnItem) {
327
- if (item?.props?.disabled !== undefined) return item?.props?.disabled
328
- if (props.fc?.[item.field]?.disabled === true) return true
329
- return false
330
- }
331
-
332
- /** 表单数据更新 */
333
- function updateModelValue(value: any, item: FormColumnItem) {
334
- emit(
335
- 'update:modelValue',
336
- Object.assign(props.modelValue, { [item.field]: value })
337
- )
338
- }
339
-
340
- if (import.meta.env.DEV) {
341
- watch(
342
- () => props.modelValue,
343
- () => {
344
- // eslint-disable-next-line no-console
345
- console.log('form', toRaw(props.modelValue))
346
- },
347
- { deep: true }
348
- )
349
- }
350
-
351
- defineExpose({ formRef })
352
- </script>
353
-
354
- <style lang="scss" scoped>
355
- @use '../../../styles/var.scss' as a;
356
-
357
- .el-form {
358
- width: 100%;
359
- }
360
-
361
- :deep(.el-form-item) {
362
- align-items: center;
363
-
364
- .el-form-item__label {
365
- height: inherit;
366
- line-height: inherit;
367
- }
368
- }
369
-
370
- :deep(.hide-label) {
371
-
372
- // 隐藏el-form-item__label才能完整占满插槽宽度
373
- .el-form-item__label {
374
- display: none;
375
- }
376
- }
377
-
378
- .#{a.$prefix}-form {
379
- &-item {
380
- &__content {
381
- width: 100%;
382
- display: flex;
383
- }
384
-
385
- &__component {
386
- flex: 1;
387
- }
388
-
389
- &__tip {
390
- line-height: 1.5;
391
- color: var(--el-color-info-light-3);
392
- }
393
-
394
- &__extra {
395
- margin-left: 6px;
396
- }
397
- }
398
-
399
- &__search-btns {
400
- margin-bottom: 8px;
401
- }
402
- }
403
-
404
- .#{a.$prefix}-form--search {
405
- :deep(.el-form-item) {
406
- margin-bottom: 8px;
407
- }
408
- }
409
-
410
- :deep(.w-full) {
411
- width: 100%;
412
-
413
- .el-date-editor {
414
- width: 100%;
415
- }
416
- }
417
-
418
- :deep(.#{a.$prefix}-form-item__title) {
419
- border-radius: 0;
420
-
421
- .el-alert__title {
422
- color: var(--el-text-color-primary);
423
- font-weight: 600;
424
- }
425
- }
426
- </style>
1
+ <template>
2
+ <ElForm ref="formRef" :class="getClass" v-bind="formProps" :model="props.modelValue">
3
+ <Grid class="w-full" :col-gap="12" v-bind="props.gridProps" :collapsed="collapsed">
4
+ <template v-for="(item, index) in props.columns">
5
+ <GridItem v-if="item.type === 'title'" :key="`title${index}`" :span="24">
6
+ <ElFormItem label-width="0">
7
+ <el-alert :class="b('form-item__title')" :title="typeof item.label === 'string' ? item.label : ''"
8
+ type="info" :closable="false" />
9
+ </ElFormItem>
10
+ </GridItem>
11
+
12
+ <template v-else>
13
+ <GridItem v-if="!isHide(item)" :key="item.field + index" v-bind="item.gridItemProps || props.gridItemProps"
14
+ :span="item.span || item.gridItemProps?.span || props?.gridItemProps?.span">
15
+ <ElFormItem :key="item.field + index" :prop="item.field" :label="item.label" :rules="getFormItemRules(item)"
16
+ v-bind="item.formItemProps">
17
+ <template v-if="item?.labelRender" #label>
18
+ <component :is="item.labelRender"></component>
19
+ </template>
20
+ <div v-if="item.type === 'slot'" class="w-full">
21
+ <slot :name="item.field" :item="item"></slot>
22
+ </div>
23
+ <template v-else>
24
+ <div :class="b('form-item__content')">
25
+ <div :class="b('form-item__component')">
26
+ <component :is="CompMap[item.type] || item.type" :disabled="isDisabled(item)" class="w-full"
27
+ v-bind="getComponentBindProps(item)" :model-value="props.modelValue[item.fieldName || item.field]"
28
+ @update:model-value="updateModelValue($event, item)">
29
+ <template v-for="(slotValue, slotKey) in item?.slots || {}" :key="slotKey" #[slotKey]="scope">
30
+ <template v-if="typeof slotValue === 'string'">
31
+ {{ slotValue }}
32
+ </template>
33
+ <template v-else-if="slotValue">
34
+ <component :is="slotValue(scope)"></component>
35
+ </template>
36
+ </template>
37
+ </component>
38
+ <ElText v-if="item.tip" :class="b('form-item__tip')" type="info" size="small">
39
+ {{ item.tip }}
40
+ </ElText>
41
+ </div>
42
+ <!-- 额外信息 -->
43
+ <div v-if="item.extra" :class="b('form-item__extra')">
44
+ <template v-if="typeof item.extra === 'string'">
45
+ <ElText type="info" size="small">
46
+ {{
47
+ item.extra
48
+ }}
49
+ </ElText>
50
+ </template>
51
+ <template v-else-if="item.extra">
52
+ <component :is="item.extra"></component>
53
+ </template>
54
+ </div>
55
+ </div>
56
+ </template>
57
+ </ElFormItem>
58
+ </GridItem>
59
+ </template>
60
+ </template>
61
+
62
+ <GridItem v-if="props.search" :suffix="props.search" :span="props?.gridItemProps?.span">
63
+ <ElSpace :class="b('form__search-btns')">
64
+ <ElButton type="primary" @click="emit('search')">
65
+ {{ searchText }}
66
+ </ElButton>
67
+ <ElButton @click="emit('reset')"> 重置 </ElButton>
68
+ <ElButton v-if="!props.hideFoldBtn" class="form__fold-btn" type="primary"
69
+ :icon="collapsed ? ArrowDown : ArrowUp" text size="small" @click="collapsed = !collapsed">
70
+ {{ collapsed ? '展开' : '收起' }}
71
+ </ElButton>
72
+ </ElSpace>
73
+ </GridItem>
74
+ </Grid>
75
+ </ElForm>
76
+ </template>
77
+
78
+ <script lang="tsx" setup>
79
+ import type { FormInstance } from 'element-plus'
80
+ import type { FormColumnItem, FormColumnType, FormProps } from './type'
81
+ import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
82
+ import { ElButton, ElForm, ElFormItem, ElMessage, ElSpace, ElText } from 'element-plus'
83
+ import * as El from 'element-plus'
84
+ import {
85
+ computed,
86
+ getCurrentInstance,
87
+ onMounted,
88
+ ref,
89
+ toRaw,
90
+ useAttrs,
91
+ watch
92
+ } from 'vue'
93
+ import { useBemClass } from '../../../hooks'
94
+ import { Grid, GridItem } from '../../grid'
95
+ import InputSearch from '../../input-search'
96
+
97
+ const props = withDefaults(defineProps<FormProps>(), {
98
+ columns: () => [],
99
+ labelWidth: 'auto',
100
+ scrollToError: true,
101
+ showMessage: true,
102
+ gridItemProps: () => ({ span: { xs: 24, sm: 24, md: 12 } }), // xs, sm, md, lg, xl, xxl
103
+ search: false,
104
+ searchText: '查询',
105
+ hideFoldBtn: false,
106
+ defaultCollapsed: undefined,
107
+ fc: () => ({})
108
+ })
109
+
110
+ const emit = defineEmits<{
111
+ (e: 'update:modelValue', value: any): void
112
+ (e: 'search'): void
113
+ (e: 'reset'): void
114
+ }>()
115
+
116
+ const attrs = useAttrs()
117
+ const { b } = useBemClass()
118
+ const collapsed = ref(props?.defaultCollapsed ?? props.search)
119
+ const instance = getCurrentInstance()
120
+
121
+ const globalConfig = instance?.appContext.config.globalProperties.$config
122
+ const clearable = globalConfig?.clearable ?? false
123
+ // 字典数据存储
124
+ const dictData = ref<Record<string, any[]>>({})
125
+
126
+ /** 组件静态配置 */
127
+ const STATIC_PROPS = new Map([
128
+ ['input', { clearable, maxlength: 20 }],
129
+ [
130
+ 'textarea',
131
+ { clearable, type: 'textarea', maxlength: 200, showWordLimit: true }
132
+ ],
133
+ ['input-number', {}],
134
+ ['input-tag', { clearable }],
135
+ ['select', { clearable }],
136
+ ['select-v2', { clearable }],
137
+ ['tree-select', { clearable }],
138
+ ['cascader', { clearable }],
139
+ ['slider', {}],
140
+ ['switch', {}],
141
+ ['rate', {}],
142
+ ['checkbox-group', {}],
143
+ ['checkbox', {}],
144
+ ['radio-group', {}],
145
+ ['radio', {}],
146
+ ['date-picker', { clearable }],
147
+ ['time-picker', { clearable }],
148
+ ['time-select', { clearable }],
149
+ ['color-picker', {}],
150
+ ['transfer', {}],
151
+ ['autocomplete', {}],
152
+ ['upload', {}],
153
+ ['title', {}]
154
+ ])
155
+
156
+ /** 占位符文本映射 */
157
+ const PLACEHOLDER_MAP = new Map<FormColumnType, (label?: string) => string | undefined>([
158
+ ['input', (label) => `请输入${label}`],
159
+ ['input-number', (label) => `请输入${label}`],
160
+ ['input-tag', (label) => `请输入${label}`],
161
+ ['textarea', (label) => `请填写${label}`],
162
+ ['select', (label) => `请选择${label}`],
163
+ ['select-v2', (label) => `请选择${label}`],
164
+ ['tree-select', (label) => `请选择${label}`],
165
+ ['cascader', (label) => `请选择${label}`],
166
+ ['time-select', (label) => `请选择${label}`],
167
+ ['input-search', (label) => `请选择${label}`],
168
+ ['date-picker', () => '请选择日期'],
169
+ ['time-picker', () => '请选择时间']
170
+ ])
171
+
172
+ /** 获取字典数据 */
173
+ const loadDictData = async () => {
174
+ const dictCodes = props.columns
175
+ ?.filter((item) => item.dictCode)
176
+ .map((item) => item.dictCode!)
177
+ .filter((code, index, arr) => arr.indexOf(code) === index) // 去重
178
+
179
+ if (!dictCodes?.length) return
180
+ if (!globalConfig?.dictRequest) {
181
+ ElMessage.error('请配置全局字典请求方法dictRequest')
182
+ return
183
+ }
184
+ try {
185
+ // 使用Promise.all并行处理所有字典请求
186
+ const dictResponses = await Promise.all(
187
+ dictCodes.map((code) =>
188
+ globalConfig
189
+ .dictRequest(code)
190
+ .then((response: any) => ({ code, response }))
191
+ .catch((error: any) => {
192
+ console.error(`获取字典 ${code} 失败:`, error)
193
+ return { code, response: [] }
194
+ })
195
+ )
196
+ )
197
+ // 处理所有响应结果
198
+ dictResponses.forEach(({ code, response }) => {
199
+ dictData.value[code] = response
200
+ })
201
+ } catch (error) {
202
+ console.error('获取字典数据失败:', error)
203
+ ElMessage.error('获取字典数据失败')
204
+ }
205
+ }
206
+
207
+ // 组件挂载时获取字典数据
208
+ onMounted(() => {
209
+ loadDictData()
210
+ })
211
+
212
+ /** 获取占位文本 */
213
+ const getPlaceholder = (item: FormColumnItem): string | undefined => {
214
+ if (!item.type) return undefined
215
+ const placeholderFn = PLACEHOLDER_MAP.get(item.type)
216
+ return placeholderFn ? placeholderFn(item.label) : undefined
217
+ }
218
+
219
+ /** 组件的默认props配置 */
220
+ function getComponentBindProps(item: FormColumnItem) {
221
+ // 获取默认配置
222
+ const defaultProps: Record<string, any> = { ...(STATIC_PROPS.get(item.type) || {}) }
223
+ const placeholder = getPlaceholder(item)
224
+ if (placeholder) {
225
+ defaultProps.placeholder = placeholder
226
+ }
227
+ // 日期选择器格式化
228
+ if (item.type === 'date-picker') {
229
+ defaultProps.valueFormat = item?.props?.type === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'
230
+ }
231
+ // 如果配置了dictCode且存在对应的字典数据,设置options
232
+ if (item.dictCode && dictData.value[item.dictCode]) {
233
+ defaultProps.options = dictData.value[item.dictCode]
234
+ }
235
+ // 合并默认配置和自定义配置
236
+ return { ...defaultProps, ...(item?.props || {}) }
237
+ }
238
+
239
+ const formProps = computed(() => {
240
+ const {
241
+ columns,
242
+ gridProps,
243
+ gridItemProps,
244
+ search,
245
+ searchText,
246
+ hideFoldBtn,
247
+ defaultCollapsed,
248
+ modelValue,
249
+ fc,
250
+ ...restProps
251
+ } = props
252
+ return { ...attrs, ...restProps }
253
+ })
254
+
255
+ const getClass = computed(() => {
256
+ const arr: string[] = [b('form')]
257
+ if (props.search) {
258
+ arr.push(b('form--search'))
259
+ }
260
+ return arr.join(' ')
261
+ })
262
+
263
+ const CompMap: Record<Exclude<FormColumnType, 'slot'>, any> = {
264
+ 'input': El.ElInput,
265
+ 'textarea': El.ElInput,
266
+ 'input-number': El.ElInputNumber,
267
+ 'input-tag': El.ElInputTag,
268
+ 'input-search': InputSearch,
269
+ 'select': El.ElSelect,
270
+ 'select-v2': El.ElSelectV2,
271
+ 'tree-select': El.ElTreeSelect,
272
+ 'cascader': El.ElCascader,
273
+ 'slider': El.ElSlider,
274
+ 'switch': El.ElSwitch,
275
+ 'rate': El.ElRate,
276
+ 'checkbox-group': El.ElCheckboxGroup,
277
+ 'checkbox': El.ElCheckbox,
278
+ 'radio-group': El.ElRadioGroup,
279
+ 'radio': El.ElRadio,
280
+ 'date-picker': El.ElDatePicker,
281
+ 'time-picker': El.ElTimePicker,
282
+ 'time-select': El.ElTimeSelect,
283
+ 'color-picker': El.ElColorPicker,
284
+ 'transfer': El.ElTransfer,
285
+ 'autocomplete': El.ElAutocomplete,
286
+ 'upload': El.ElUpload,
287
+ 'title': El.ElAlert
288
+ }
289
+
290
+ const formRef = ref<FormInstance>()
291
+
292
+ /** 表单项校验规则 */
293
+ function getFormItemRules(item: FormColumnItem) {
294
+ const rules = Array.isArray(item.rules) ? item.rules : []
295
+ const requiredMessage = `${item.label}为必填项`
296
+
297
+ if (item.required) {
298
+ return [
299
+ { required: true, message: requiredMessage },
300
+ ...rules
301
+ ]
302
+ }
303
+ if (props.fc?.[item.field]?.required) {
304
+ return [
305
+ {
306
+ required: props.fc[item.field].required,
307
+ message: requiredMessage
308
+ },
309
+ ...rules
310
+ ]
311
+ }
312
+ return item.rules
313
+ }
314
+
315
+ /** 隐藏表单项 */
316
+ function isHide(item: FormColumnItem): boolean {
317
+ if (typeof item.hide === 'boolean') return item.hide
318
+ if (typeof item.hide === 'function') {
319
+ return item.hide(props.modelValue)
320
+ }
321
+ if (props.fc?.[item.field]?.hidden) return true
322
+ return false
323
+ }
324
+
325
+ /** 禁用表单项 */
326
+ function isDisabled(item: FormColumnItem) {
327
+ if (item?.props?.disabled !== undefined) return item?.props?.disabled
328
+ if (props.fc?.[item.field]?.disabled === true) return true
329
+ return false
330
+ }
331
+
332
+ /** 表单数据更新 */
333
+ function updateModelValue(value: any, item: FormColumnItem) {
334
+ emit('update:modelValue', { ...props.modelValue, [item.field]: value })
335
+ }
336
+
337
+ if (import.meta.env.DEV) {
338
+ watch(
339
+ () => props.modelValue,
340
+ () => {
341
+ // eslint-disable-next-line no-console
342
+ console.log('form', toRaw(props.modelValue))
343
+ },
344
+ { deep: true }
345
+ )
346
+ }
347
+
348
+ defineExpose({ formRef })
349
+ </script>
350
+
351
+ <style lang="scss" scoped>
352
+ @use '../../../styles/var.scss' as a;
353
+
354
+ .el-form {
355
+ width: 100%;
356
+ }
357
+
358
+ :deep(.el-form-item) {
359
+ align-items: center;
360
+
361
+ .el-form-item__label {
362
+ height: inherit;
363
+ line-height: inherit;
364
+ }
365
+ }
366
+
367
+ :deep(.hide-label) {
368
+
369
+ // 隐藏el-form-item__label才能完整占满插槽宽度
370
+ .el-form-item__label {
371
+ display: none;
372
+ }
373
+ }
374
+
375
+ .#{a.$prefix}-form {
376
+ &-item {
377
+ &__content {
378
+ width: 100%;
379
+ display: flex;
380
+ }
381
+
382
+ &__component {
383
+ flex: 1;
384
+ }
385
+
386
+ &__tip {
387
+ line-height: 1.5;
388
+ color: var(--el-color-info-light-3);
389
+ }
390
+
391
+ &__extra {
392
+ margin-left: 6px;
393
+ }
394
+ }
395
+
396
+ &__search-btns {
397
+ margin-bottom: 8px;
398
+ }
399
+ }
400
+
401
+ .#{a.$prefix}-form--search {
402
+ :deep(.el-form-item) {
403
+ margin-bottom: 8px;
404
+ }
405
+ }
406
+
407
+ :deep(.w-full) {
408
+ width: 100%;
409
+
410
+ .el-date-editor {
411
+ width: 100%;
412
+ }
413
+ }
414
+
415
+ :deep(.#{a.$prefix}-form-item__title) {
416
+ border-radius: 0;
417
+
418
+ .el-alert__title {
419
+ color: var(--el-text-color-primary);
420
+ font-weight: 600;
421
+ }
422
+ }
423
+ </style>