@aspire-ui/element-component-pro 1.0.0

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.
@@ -0,0 +1,423 @@
1
+ <template>
2
+ <div ref="formWrapRef" class="ecp-pro-form">
3
+ <el-form class="ecp-pro-form" ref="formRef" :model="formModel" :rules="formRules" :label-width="effectiveProps.labelWidth"
4
+ :label-position="effectiveProps.labelPosition" :size="effectiveProps.size" :disabled="effectiveProps.disabled"
5
+ v-bind="$attrs" v-on="formListeners">
6
+ <slot name="formHeader" />
7
+ <el-row :gutter="effectiveProps.gutter" :style="effectiveProps.baseRowStyle">
8
+ <template v-for="schema in displaySchemas">
9
+ <el-col v-if="shouldShow(schema)" :key="schema.field" v-bind="getColProps(schema)"
10
+ :offset="schema.colProps?.offset ?? effectiveProps.baseColProps?.offset ?? 0" :data-field="schema.field">
11
+ <ProFormItem :schema="schema" :form-model="formModel" :form-disabled="effectiveProps.disabled"
12
+ :auto-placeholder="effectiveProps.autoSetPlaceholder" :form-action-type="formActionRef">
13
+ <template v-if="slots[getSlotName(schema)]">
14
+ <slot :name="getSlotName(schema)" :model="formModel" :schema="schema" :field="schema.field"
15
+ :values="formModel" />
16
+ </template>
17
+ </ProFormItem>
18
+ </el-col>
19
+ </template>
20
+ <el-col class="ecp-pro-form_col" v-if="effectiveProps.showActionButtonGroup"
21
+ v-bind="hasMoreFields ? { span: 24 } : effectiveProps.actionColOptions || { span: 6 }">
22
+ <FormActions :show-action-button-group="effectiveProps.showActionButtonGroup"
23
+ :show-submit-button="effectiveProps.showSubmitButton" :show-reset-button="effectiveProps.showResetButton"
24
+ :submit-button-text="effectiveProps.submitButtonText" :reset-button-text="effectiveProps.resetButtonText"
25
+ :submit-button-icon="effectiveProps.submitButtonIcon" :reset-button-icon="effectiveProps.resetButtonIcon"
26
+ :submit-loading="submitLoading" :show-advanced-button="effectiveProps.showAdvancedButton"
27
+ :has-more-fields="hasMoreFields" :collapsed="collapsed" :action-col-options="effectiveActionColOptions"
28
+ @submit="handleSubmit" @reset="handleReset" @toggle="collapsed = !collapsed">
29
+ <template slot="submitBefore">
30
+ <slot name="submitBefore" />
31
+ </template>
32
+ <template slot="resetBefore">
33
+ <slot name="resetBefore" />
34
+ </template>
35
+ <template slot="advanceBefore">
36
+ <slot name="advanceBefore" />
37
+ </template>
38
+ <template slot="advanceAfter">
39
+ <slot name="advanceAfter" />
40
+ </template>
41
+ <template slot="actions">
42
+ <slot name="actions" />
43
+ </template>
44
+ </FormActions>
45
+ </el-col>
46
+ </el-row>
47
+ <el-button v-if="effectiveProps.showAdvancedButton && hasMoreFields" type="text" class="ecp-form-actions__advance"
48
+ @click="collapsed = !collapsed">
49
+ <i class="el-icon-d-arrow-left" :class="collapsed ? 'down' : 'up'" />
50
+ {{ collapsed ? '展开' : '收起' }}
51
+ </el-button>
52
+ <slot name="formFooter" />
53
+ </el-form>
54
+ </div>
55
+ </template>
56
+
57
+ <script setup lang="ts">
58
+ import { ref, computed, watch, useSlots, onMounted, onUnmounted } from 'vue'
59
+ import ProFormItem from './ProFormItem.vue'
60
+ import FormActions from './FormActions.vue'
61
+ import type { ProFormSchema, ProFormProps, FormActionType, ColEx, ScrollToFieldOptions, FormListeners } from '../types'
62
+
63
+ const props = withDefaults(
64
+ defineProps<{
65
+ schemas?: ProFormSchema[]
66
+ initialValues?: Record<string, unknown>
67
+ labelWidth?: string
68
+ labelPosition?: 'left' | 'right' | 'top'
69
+ gutter?: number
70
+ size?: 'medium' | 'small' | 'large'
71
+ disabled?: boolean
72
+ baseColProps?: ColEx
73
+ baseRowStyle?: Record<string, string | number>
74
+ autoSetPlaceholder?: boolean
75
+ showSubmitButton?: boolean
76
+ showResetButton?: boolean
77
+ submitButtonText?: string
78
+ resetButtonText?: string
79
+ submitButtonIcon?: string
80
+ resetButtonIcon?: string
81
+ showActionButtonGroup?: boolean
82
+ actionColOptions?: ColEx
83
+ showAdvancedButton?: boolean
84
+ autoAdvancedLine?: number
85
+ alwaysShowLines?: number
86
+ submitFunc?: () => Promise<void>
87
+ resetFunc?: () => Promise<void>
88
+ submitOnReset?: boolean
89
+ formListeners?: FormListeners
90
+ }>(),
91
+ {
92
+ labelWidth: '120px',
93
+ labelPosition: 'right',
94
+ gutter: 24,
95
+ size: 'medium',
96
+ autoSetPlaceholder: true,
97
+ showSubmitButton: true,
98
+ showResetButton: true,
99
+ submitButtonText: '提交',
100
+ resetButtonText: '重置',
101
+ submitButtonIcon: 'el-icon-search',
102
+ resetButtonIcon: 'el-icon-refresh-left',
103
+ showActionButtonGroup: true,
104
+ showAdvancedButton: false,
105
+ autoAdvancedLine: 3,
106
+ alwaysShowLines: 1,
107
+ baseColProps: () => ({ xs: 24, sm: 12, md: 12, lg: 8, xl: 6 }),
108
+ actionColOptions: () => ({ xs: 24, sm: 12, md: 12, lg: 8, xl: 6 }),
109
+ submitOnReset: false,
110
+ }
111
+ )
112
+
113
+ const emit = defineEmits<{
114
+ (e: 'submit', values: Record<string, unknown>): void
115
+ (e: 'reset'): void
116
+ (e: 'register', formAction: FormActionType): void
117
+ }>()
118
+ const slots = useSlots()
119
+ const formRef = ref()
120
+ const formWrapRef = ref()
121
+ const submitLoading = ref(false)
122
+ const collapsed = ref(true)
123
+ const formModel = ref<Record<string, unknown>>({})
124
+ const formRules = ref<Record<string, unknown[]>>({})
125
+ const innerSchemas = ref<ProFormSchema[]>([])
126
+ const innerProps = ref<Partial<ProFormProps>>({})
127
+
128
+ /** Element UI 栅格断点 (px) */
129
+ const BREAKPOINTS = { xl: 1920, lg: 1200, md: 992, sm: 768 }
130
+
131
+ /** 根据当前视口宽度获取 ColEx 的有效 span(与 el-col 断点逻辑一致) */
132
+ const getEffectiveSpan = (colProps?: ColEx | null, baseColProps?: ColEx | null, width?: number): number => {
133
+ const w = width ?? (typeof window !== 'undefined' ? window.innerWidth : 1920)
134
+ const col = colProps ?? {}
135
+ const base = baseColProps ?? {}
136
+ const fallback = base.span ?? 8
137
+ if (w >= BREAKPOINTS.xl) return col.xl ?? base.xl ?? col.lg ?? base.lg ?? col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
138
+ if (w >= BREAKPOINTS.lg) return col.lg ?? base.lg ?? col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
139
+ if (w >= BREAKPOINTS.md) return col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
140
+ if (w >= BREAKPOINTS.sm) return col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
141
+ // if (w < BREAKPOINTS.sm) return col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
142
+ return col.xs ?? base.xs ?? col.span ?? fallback
143
+ }
144
+
145
+ const effectiveProps = computed(() => ({ ...props, ...innerProps.value }))
146
+ const effectiveActionColOptions = computed(() => effectiveProps.value.actionColOptions ?? { span: 24 })
147
+
148
+ /** 当前视口宽度,用于响应式计算 span */
149
+ const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920)
150
+
151
+ /** 计算前 maxLines 行能容纳的 schema 数量(考虑栅格断点,每个 field 可有自己的 span) */
152
+ const getVisibleSchemaCount = (
153
+ schemas: ProFormSchema[],
154
+ baseColProps: ColEx | undefined,
155
+ maxLines: number,
156
+ width: number
157
+ ) => {
158
+ let remaining = 24
159
+ let rows = 1
160
+ let count = 0
161
+ for (const schema of schemas) {
162
+ const span = getEffectiveSpan(schema.colProps, baseColProps, width)
163
+ if (span > remaining) {
164
+ rows++
165
+ if (rows > maxLines) break
166
+ remaining = 24 - span
167
+ } else {
168
+ remaining -= span
169
+ }
170
+ count++
171
+ }
172
+ return count
173
+ }
174
+
175
+ const hasMoreFields = computed(() => {
176
+ const schemas = innerSchemas.value.filter((s) => shouldShow(s))
177
+
178
+ if (!effectiveProps.value.showAdvancedButton) return false
179
+ const lines = effectiveProps.value.alwaysShowLines ?? 1
180
+ const baseColProps = effectiveProps.value.baseColProps
181
+ const maxVisible = getVisibleSchemaCount(schemas, baseColProps, lines, windowWidth.value)
182
+ console.log(schemas.length, maxVisible)
183
+ return schemas.length > maxVisible
184
+ })
185
+
186
+ const formListeners = computed((): FormListeners => {
187
+ return effectiveProps.value.formListeners ?? {}
188
+ })
189
+
190
+ const displaySchemas = computed(() => {
191
+ const schemas = innerSchemas.value.filter((s) => {
192
+ return shouldShow(s)
193
+ })
194
+ if (!effectiveProps.value.showAdvancedButton || !collapsed.value) return schemas
195
+ const lines = effectiveProps.value.alwaysShowLines ?? 1
196
+ const baseColProps = effectiveProps.value.baseColProps
197
+ const maxVisible = getVisibleSchemaCount(schemas, baseColProps, lines, windowWidth.value)
198
+ return schemas.slice(0, maxVisible)
199
+ })
200
+
201
+ const shouldShow = (schema: ProFormSchema) => {
202
+ let ifShow = true
203
+ let show = true
204
+ if (typeof schema.ifShow === 'function') {
205
+ ifShow = schema.ifShow({ schema, values: formModel.value, model: formModel.value, field: schema.field })
206
+ }
207
+ if (typeof schema.ifShow === 'boolean') {
208
+ ifShow = schema.ifShow
209
+ }
210
+ if (typeof schema.show === 'function') {
211
+ show = schema.show({ schema, values: formModel.value, model: formModel.value, field: schema.field })
212
+ }
213
+ if (typeof schema.show === 'boolean') {
214
+ show = schema.show
215
+ }
216
+ return ifShow && show
217
+ }
218
+ const getColProps = (schema: ProFormSchema) => {
219
+ return schema.colProps ?? effectiveProps.value.baseColProps ?? {}
220
+ }
221
+ const getSlotName = (schema: ProFormSchema) => schema.slot || schema.field
222
+
223
+ const initForm = () => {
224
+ const model: Record<string, unknown> = {}
225
+ const rules: Record<string, unknown[]> = {}
226
+ const initialValues = effectiveProps.value.initialValues ?? props.initialValues
227
+ innerSchemas.value.forEach((schema) => {
228
+ model[schema.field] = schema.defaultValue ?? initialValues?.[schema.field]
229
+ if (schema.rules?.length) rules[schema.field] = schema.rules
230
+ })
231
+ formModel.value = { ...formModel.value, ...model }
232
+ formRules.value = rules
233
+ }
234
+
235
+ const filterByIfShow = (values: Record<string, unknown>) => {
236
+ const result = { ...values }
237
+ innerSchemas.value.forEach((schema) => {
238
+ const ifShow = schema.ifShow
239
+ if (ifShow === undefined) return
240
+ const visible = typeof ifShow === 'boolean' ? ifShow : ifShow({ schema, values, model: values, field: schema.field })
241
+ if (!visible) delete result[schema.field]
242
+ })
243
+ return result
244
+ }
245
+
246
+ const processFieldMapToTime = (values: Record<string, unknown>) => {
247
+ const filtered = filterByIfShow(values)
248
+ const fieldMap = innerProps.value.fieldMapToTime
249
+ if (!fieldMap?.length) return filtered
250
+ const result = { ...filtered }
251
+ fieldMap.forEach(([field, [startKey, endKey]]) => {
252
+ const val = result[field]
253
+ if (Array.isArray(val) && val.length === 2) {
254
+ delete result[field]
255
+ ; (result as Record<string, unknown>)[startKey] = val[0]
256
+ ; (result as Record<string, unknown>)[endKey] = val[1]
257
+ }
258
+ })
259
+ return result
260
+ }
261
+
262
+ const handleSubmit = async () => {
263
+ try {
264
+ await formRef.value?.validate()
265
+ if (effectiveProps.value.submitFunc) {
266
+ await effectiveProps.value.submitFunc()
267
+ } else {
268
+ submitLoading.value = true
269
+ emit('submit', processFieldMapToTime({ ...formModel.value }))
270
+ }
271
+ } catch (e) {
272
+ console.error('Form validation failed:', e)
273
+ } finally {
274
+ submitLoading.value = false
275
+ }
276
+ }
277
+
278
+ const handleReset = async () => {
279
+ if (effectiveProps.value.resetFunc) {
280
+ await effectiveProps.value.resetFunc()
281
+ } else {
282
+ formRef.value?.resetFields()
283
+ initForm()
284
+ emit('reset')
285
+ if (effectiveProps.value.submitOnReset) await handleSubmit()
286
+ }
287
+ }
288
+
289
+ const setFieldsValue = (values: Record<string, unknown>) => {
290
+ formModel.value = { ...formModel.value, ...values }
291
+ return Promise.resolve()
292
+ }
293
+
294
+ const getFieldsValue = () => processFieldMapToTime({ ...formModel.value })
295
+
296
+ const resetFields = async () => {
297
+ formRef.value?.resetFields()
298
+ initForm()
299
+ }
300
+
301
+ const validate = (nameList?: string[]) =>
302
+ formRef.value?.validate(nameList) ?? Promise.resolve()
303
+
304
+ const validateFields = (nameList?: string[]) => {
305
+ if (!formRef.value) return Promise.resolve()
306
+ if (!nameList?.length) return formRef.value.validate()
307
+ return Promise.all(nameList.map((prop) => new Promise((resolve, reject) => {
308
+ formRef.value.validateField(prop, (valid: boolean) => (valid ? resolve(undefined) : reject(new Error('Validation failed'))))
309
+ })))
310
+ }
311
+
312
+ const scrollToField = async (name: string, options?: ScrollToFieldOptions) => {
313
+ const el = formWrapRef.value?.querySelector(`[data-field="${name}"]`)
314
+ if (el) {
315
+ el.scrollIntoView({ behavior: options?.behavior ?? 'smooth', block: options?.block ?? 'nearest' })
316
+ }
317
+ return Promise.resolve()
318
+ }
319
+
320
+ const clearValidate = (name?: string | string[]) => {
321
+ formRef.value?.clearValidate(name)
322
+ }
323
+
324
+ const updateSchema = async (data: Partial<ProFormSchema> | Partial<ProFormSchema>[]) => {
325
+ const list = Array.isArray(data) ? data : [data]
326
+ list.forEach((item) => {
327
+ const idx = innerSchemas.value.findIndex((s) => s.field === item.field)
328
+ if (idx >= 0) innerSchemas.value[idx] = { ...innerSchemas.value[idx], ...item }
329
+ })
330
+ }
331
+
332
+ const appendSchemaByField = async (schema: ProFormSchema, prefixField?: string, first?: boolean) => {
333
+ if (first) innerSchemas.value.unshift(schema)
334
+ else if (prefixField) {
335
+ const idx = innerSchemas.value.findIndex((s) => s.field === prefixField)
336
+ innerSchemas.value.splice(idx + 1, 0, schema)
337
+ } else innerSchemas.value.push(schema)
338
+ initForm()
339
+ }
340
+
341
+ const removeSchemaByField = async (field: string | string[]) => {
342
+ const fields = Array.isArray(field) ? field : [field]
343
+ innerSchemas.value = innerSchemas.value.filter((s) => !fields.includes(s.field))
344
+ }
345
+
346
+ const setProps = async (formProps: Partial<ProFormProps>) => {
347
+ innerProps.value = { ...innerProps.value, ...formProps }
348
+ if (formProps.schemas) {
349
+ innerSchemas.value = [...formProps.schemas]
350
+ debugger
351
+ initForm()
352
+ }
353
+ }
354
+
355
+ const formActionRef: FormActionType = {
356
+ getFieldsValue,
357
+ setFieldsValue,
358
+ resetFields,
359
+ validate,
360
+ validateFields,
361
+ submit: handleSubmit,
362
+ scrollToField,
363
+ clearValidate,
364
+ updateSchema,
365
+ appendSchemaByField,
366
+ removeSchemaByField,
367
+ setProps,
368
+ }
369
+
370
+ defineExpose(formActionRef)
371
+
372
+ const syncSchemas = () => {
373
+ innerSchemas.value = [...(props.schemas ?? [])]
374
+ initForm()
375
+ }
376
+
377
+ const handleResize = () => {
378
+ if (typeof window !== 'undefined') windowWidth.value = window.innerWidth
379
+ }
380
+
381
+ onMounted(() => {
382
+ syncSchemas()
383
+ emit('register', formActionRef)
384
+ if (typeof window !== 'undefined') window.addEventListener('resize', handleResize)
385
+ })
386
+
387
+ onUnmounted(() => {
388
+ if (typeof window !== 'undefined') window.removeEventListener('resize', handleResize)
389
+ })
390
+
391
+ watch(() => [props.schemas, props.initialValues], syncSchemas, { deep: true })
392
+ </script>
393
+
394
+ <style scoped>
395
+ .ecp-pro-form {
396
+ padding: 16px;
397
+ position: relative;
398
+ }
399
+
400
+ .ecp-pro-form__advance {
401
+ margin-bottom: 16px;
402
+ }
403
+
404
+ .ecp-pro-form_col {
405
+ position: relative;
406
+ float: right;
407
+ }
408
+
409
+ .el-icon-d-arrow-left.up {
410
+ transform: rotate(90deg);
411
+ }
412
+
413
+ .el-icon-d-arrow-left.down {
414
+ transform: rotate(-90deg);
415
+ }
416
+
417
+ .ecp-form-actions__advance {
418
+ position: absolute;
419
+ bottom: 0;
420
+ left: 50%;
421
+ transform: translate(-50%, -50%);
422
+ }
423
+ </style>
@@ -0,0 +1,250 @@
1
+ <template>
2
+ <el-form-item
3
+ v-if="shouldRender"
4
+ v-show="shouldShow"
5
+ :prop="schema.field"
6
+ :required="schema.required"
7
+ :rules="effectiveRules"
8
+ >
9
+ <template slot="label">
10
+ <span>{{ schema.label }}</span>
11
+ <el-tooltip
12
+ v-if="schema.helpMessage"
13
+ placement="top"
14
+ effect="light"
15
+ v-bind="schema.helpComponentProps || {}"
16
+ >
17
+ <template slot="content">
18
+ <template v-if="Array.isArray(schema.helpMessage)">
19
+ <div v-for="(msg, i) in schema.helpMessage" :key="i" class="ecp-pro-form-item__help-item">
20
+ {{ msg }}
21
+ </div>
22
+ </template>
23
+ <span v-else>{{ schema.helpMessage }}</span>
24
+ </template>
25
+ <i class="el-icon-question ecp-pro-form-item__help-icon" />
26
+ </el-tooltip>
27
+ </template>
28
+ <!-- render 自定义渲染 -->
29
+ <template v-if="schema.render">
30
+ <component :is="renderComponent" />
31
+ </template>
32
+ <!-- slot 自定义插槽(由 ProForm 传入 default slot) -->
33
+ <slot v-else-if="hasSlot" :model="formModel" :schema="schema" :field="schema.field" :values="formModel" />
34
+ <!-- 默认组件渲染 -->
35
+ <template v-else>
36
+ <el-input
37
+ v-if="schema.component === 'input' || !schema.component"
38
+ v-model="formModel[schema.field]"
39
+ :placeholder="schema.placeholder || (autoPlaceholder ? `请输入${schema.label}` : undefined)"
40
+ :disabled="effectiveDisabled"
41
+ v-bind="effectiveComponentProps"
42
+ v-on="effectiveComponentListeners"
43
+ />
44
+ <el-input-number
45
+ v-else-if="schema.component === 'input-number'"
46
+ v-model="formModel[schema.field]"
47
+ :placeholder="schema.placeholder"
48
+ :disabled="effectiveDisabled"
49
+ v-bind="effectiveComponentProps"
50
+ v-on="effectiveComponentListeners"
51
+ />
52
+ <el-select
53
+ class="ecp-pro-form-item__select"
54
+ v-else-if="schema.component === 'select'"
55
+ v-model="formModel[schema.field]"
56
+ :placeholder="schema.placeholder || (autoPlaceholder ? `请选择${schema.label}` : undefined)"
57
+ :disabled="effectiveDisabled"
58
+ v-bind="effectiveComponentProps"
59
+ v-on="effectiveComponentListeners"
60
+ >
61
+ <el-option
62
+ v-for="opt in (getOptions(effectiveComponentProps) || [])"
63
+ :key="String(opt.value)"
64
+ :label="opt.label"
65
+ :value="opt.value"
66
+ />
67
+ </el-select>
68
+ <el-date-picker
69
+ v-else-if="schema.component === 'date-picker'"
70
+ v-model="formModel[schema.field]"
71
+ :placeholder="schema.placeholder || (autoPlaceholder ? `请选择${schema.label}` : undefined)"
72
+ :disabled="effectiveDisabled"
73
+ v-bind="effectiveComponentProps"
74
+ v-on="effectiveComponentListeners"
75
+ />
76
+ <el-date-picker
77
+ v-else-if="schema.component === 'date-range'"
78
+ v-model="formModel[schema.field]"
79
+ type="daterange"
80
+ range-separator="至"
81
+ start-placeholder="开始日期"
82
+ end-placeholder="结束日期"
83
+ value-format="yyyy-MM-dd"
84
+ :disabled="effectiveDisabled"
85
+ v-bind="effectiveComponentProps"
86
+ v-on="effectiveComponentListeners"
87
+ />
88
+ <el-switch
89
+ v-else-if="schema.component === 'switch'"
90
+ v-model="formModel[schema.field]"
91
+ :disabled="effectiveDisabled"
92
+ v-bind="effectiveComponentProps"
93
+ v-on="effectiveComponentListeners"
94
+ />
95
+ <el-cascader
96
+ v-else-if="schema.component === 'cascader'"
97
+ v-model="formModel[schema.field]"
98
+ :placeholder="schema.placeholder || (autoPlaceholder ? `请选择${schema.label}` : undefined)"
99
+ :disabled="effectiveDisabled"
100
+ v-bind="effectiveComponentProps"
101
+ v-on="effectiveComponentListeners"
102
+ />
103
+ <el-checkbox-group
104
+ v-else-if="schema.component === 'checkbox'"
105
+ v-model="formModel[schema.field]"
106
+ :disabled="effectiveDisabled"
107
+ v-bind="effectiveComponentProps"
108
+ v-on="effectiveComponentListeners"
109
+ >
110
+ <el-checkbox
111
+ v-for="opt in (getOptions(effectiveComponentProps) || [])"
112
+ :key="String(opt.value)"
113
+ :label="opt.value"
114
+ >
115
+ {{ opt.label }}
116
+ </el-checkbox>
117
+ </el-checkbox-group>
118
+ <el-radio-group
119
+ v-else-if="schema.component === 'radio'"
120
+ v-model="formModel[schema.field]"
121
+ :disabled="effectiveDisabled"
122
+ v-bind="effectiveComponentProps"
123
+ v-on="effectiveComponentListeners"
124
+ >
125
+ <el-radio
126
+ v-for="opt in (getOptions(effectiveComponentProps) || [])"
127
+ :key="String(opt.value)"
128
+ :label="opt.value"
129
+ >
130
+ {{ opt.label }}
131
+ </el-radio>
132
+ </el-radio-group>
133
+ </template>
134
+ </el-form-item>
135
+ </template>
136
+
137
+ <script setup lang="ts">
138
+ import { computed, useSlots, h } from 'vue'
139
+ import type { ProFormSchema, RenderCallbackParams } from '../types'
140
+
141
+ const props = defineProps<{
142
+ schema: ProFormSchema
143
+ formModel: Record<string, unknown>
144
+ formDisabled?: boolean
145
+ autoPlaceholder?: boolean
146
+ formActionType?: import('../types').FormActionType
147
+ }>()
148
+
149
+ const slots = useSlots()
150
+
151
+ const renderParams = computed<RenderCallbackParams>(() => ({
152
+ schema: props.schema,
153
+ values: props.formModel,
154
+ model: props.formModel,
155
+ field: props.schema.field,
156
+ }))
157
+
158
+ const shouldRender = computed(() => {
159
+ const ifShow = props.schema.ifShow
160
+ if (ifShow === undefined) return true
161
+ if (typeof ifShow === 'boolean') return ifShow
162
+ return ifShow(renderParams.value)
163
+ })
164
+
165
+ const shouldShow = computed(() => {
166
+ const show = props.schema.show
167
+ if (show === undefined) return true
168
+ if (typeof show === 'boolean') return show
169
+ return show(renderParams.value)
170
+ })
171
+
172
+ const effectiveDisabled = computed(() => {
173
+ if (props.formDisabled) return true
174
+ const dyn = props.schema.dynamicDisabled
175
+ if (dyn === undefined) return false
176
+ if (typeof dyn === 'boolean') return dyn
177
+ return dyn(renderParams.value)
178
+ })
179
+
180
+ const effectiveRules = computed(() => {
181
+ const dyn = props.schema.dynamicRules
182
+ if (!dyn) return props.schema.rules
183
+ if (Array.isArray(dyn)) return dyn
184
+ return dyn(renderParams.value)
185
+ })
186
+
187
+ const effectiveComponentPropsAndListeners = computed(() => {
188
+ const cp = props.schema.componentProps
189
+ if (!cp) return { props: {}, listeners: {} }
190
+ const raw = typeof cp === 'function'
191
+ ? cp({
192
+ ...renderParams.value,
193
+ formActionType: props.formActionType,
194
+ })
195
+ : { ...cp }
196
+ const propsOnly: Record<string, unknown> = {}
197
+ const listeners: Record<string, (...args: unknown[]) => unknown> = {}
198
+ for (const [key, value] of Object.entries(raw)) {
199
+ if (key.length > 2 && /^on[A-Za-z]/.test(key) && typeof value === 'function') {
200
+ const eventName = key.slice(2).charAt(0).toLowerCase() + key.slice(3)
201
+ listeners[eventName] = value as (...args: unknown[]) => unknown
202
+ } else {
203
+ propsOnly[key] = value
204
+ }
205
+ }
206
+ return { props: propsOnly, listeners }
207
+ })
208
+
209
+ const effectiveComponentProps = computed(() => effectiveComponentPropsAndListeners.value.props)
210
+ const effectiveComponentListeners = computed(() => effectiveComponentPropsAndListeners.value.listeners)
211
+
212
+ const hasSlot = computed(() => !!slots.default)
213
+
214
+ const getOptions = (props: Record<string, unknown>): Array<{ label: string; value: unknown }> | undefined => {
215
+ const opts = props?.options
216
+ return Array.isArray(opts) ? opts : undefined
217
+ }
218
+
219
+ const renderComponent = computed(() => {
220
+ const renderFn = props.schema.render
221
+ if (!renderFn) return null
222
+ return {
223
+ render() {
224
+ const result = renderFn(renderParams.value)
225
+ if (Array.isArray(result)) {
226
+ return h('span', result)
227
+ }
228
+ return result
229
+ },
230
+ }
231
+ })
232
+ </script>
233
+
234
+ <style scoped>
235
+ .ecp-pro-form-item__help-icon {
236
+ margin-left: 4px;
237
+ color: #909399;
238
+ cursor: help;
239
+ font-size: 14px;
240
+ }
241
+ .ecp-pro-form-item__help-icon:hover {
242
+ color: #409eff;
243
+ }
244
+ .ecp-pro-form-item__help-item {
245
+ margin-bottom: 4px;
246
+ }
247
+ .ecp-pro-form-item__help-item:last-child {
248
+ margin-bottom: 0;
249
+ }
250
+ </style>
@@ -0,0 +1,6 @@
1
+ import ProForm from './ProForm.vue'
2
+ import ProFormItem from './ProFormItem.vue'
3
+ import FormActions from './FormActions.vue'
4
+ import { useForm } from './useForm'
5
+ export { ProForm, ProFormItem, FormActions, useForm }
6
+ export default ProForm