@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.
- package/README.md +203 -0
- package/dist/ProForm/FormActions.vue.d.ts +98 -0
- package/dist/ProForm/ProForm.vue.d.ts +223 -0
- package/dist/ProForm/ProFormItem.vue.d.ts +39 -0
- package/dist/ProForm/index.d.ts +7 -0
- package/dist/ProForm/useForm.d.ts +28 -0
- package/dist/element-component-pro.es.js +444 -0
- package/dist/element-component-pro.es.js.map +1 -0
- package/dist/element-component-pro.umd.js +3 -0
- package/dist/element-component-pro.umd.js.map +1 -0
- package/dist/index.d.ts +769 -0
- package/dist/style.css +1 -0
- package/dist/types/index.d.ts +178 -0
- package/package.json +49 -0
- package/src/ProForm/FormActions.vue +76 -0
- package/src/ProForm/ProForm.vue +423 -0
- package/src/ProForm/ProFormItem.vue +250 -0
- package/src/ProForm/index.ts +6 -0
- package/src/ProForm/useForm.ts +114 -0
- package/src/index.ts +32 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/types/index.ts +179 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -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>
|