提交 | 用户 | 时间
|
820397
|
1 |
<script lang="tsx"> |
H |
2 |
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue' |
|
3 |
import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus' |
|
4 |
import { componentMap } from './componentMap' |
|
5 |
import { propTypes } from '@/utils/propTypes' |
|
6 |
import { getSlot } from '@/utils/tsxHelper' |
|
7 |
import { |
|
8 |
initModel, |
|
9 |
setComponentProps, |
|
10 |
setFormItemSlots, |
|
11 |
setGridProp, |
|
12 |
setItemComponentSlots, |
|
13 |
setTextPlaceholder |
|
14 |
} from './helper' |
|
15 |
import { useRenderSelect } from './components/useRenderSelect' |
|
16 |
import { useRenderRadio } from './components/useRenderRadio' |
|
17 |
import { useRenderCheckbox } from './components/useRenderCheckbox' |
|
18 |
import { useDesign } from '@/hooks/web/useDesign' |
|
19 |
import { findIndex } from '@/utils' |
|
20 |
import { set } from 'lodash-es' |
|
21 |
import { FormProps } from './types' |
|
22 |
import { Icon } from '@/components/Icon' |
|
23 |
import { FormSchema, FormSetPropsType } from '@/types/form' |
|
24 |
|
|
25 |
const { getPrefixCls } = useDesign() |
|
26 |
|
|
27 |
const prefixCls = getPrefixCls('form') |
|
28 |
|
|
29 |
export default defineComponent({ |
|
30 |
// eslint-disable-next-line vue/no-reserved-component-names |
|
31 |
name: 'Form', |
|
32 |
props: { |
|
33 |
// 生成Form的布局结构数组 |
|
34 |
schema: { |
|
35 |
type: Array as PropType<FormSchema[]>, |
|
36 |
default: () => [] |
|
37 |
}, |
|
38 |
// 是否需要栅格布局 |
|
39 |
// update by 芋艿:将 true 改成 false,因为项目更常用这种方式 |
|
40 |
isCol: propTypes.bool.def(false), |
|
41 |
// 表单数据对象 |
|
42 |
model: { |
|
43 |
type: Object as PropType<Recordable>, |
|
44 |
default: () => ({}) |
|
45 |
}, |
|
46 |
// 是否自动设置placeholder |
|
47 |
autoSetPlaceholder: propTypes.bool.def(true), |
|
48 |
// 是否自定义内容 |
|
49 |
isCustom: propTypes.bool.def(false), |
|
50 |
// 表单label宽度 |
|
51 |
labelWidth: propTypes.oneOfType([String, Number]).def('auto'), |
|
52 |
// 是否 loading 数据中 add by 芋艿 |
|
53 |
vLoading: propTypes.bool.def(false) |
|
54 |
}, |
|
55 |
emits: ['register'], |
|
56 |
setup(props, { slots, expose, emit }) { |
|
57 |
// element form 实例 |
|
58 |
const elFormRef = ref<ComponentRef<typeof ElForm>>() |
|
59 |
|
|
60 |
// useForm传入的props |
|
61 |
const outsideProps = ref<FormProps>({}) |
|
62 |
|
|
63 |
const mergeProps = ref<FormProps>({}) |
|
64 |
|
|
65 |
const getProps = computed(() => { |
|
66 |
const propsObj = { ...props } |
|
67 |
Object.assign(propsObj, unref(mergeProps)) |
|
68 |
return propsObj |
|
69 |
}) |
|
70 |
|
|
71 |
// 表单数据 |
|
72 |
const formModel = ref<Recordable>({}) |
|
73 |
|
|
74 |
onMounted(() => { |
|
75 |
emit('register', unref(elFormRef)?.$parent, unref(elFormRef)) |
|
76 |
}) |
|
77 |
|
|
78 |
// 对表单赋值 |
|
79 |
const setValues = (data: Recordable = {}) => { |
|
80 |
formModel.value = Object.assign(unref(formModel), data) |
|
81 |
} |
|
82 |
|
|
83 |
const setProps = (props: FormProps = {}) => { |
|
84 |
mergeProps.value = Object.assign(unref(mergeProps), props) |
|
85 |
outsideProps.value = props |
|
86 |
} |
|
87 |
|
|
88 |
const delSchema = (field: string) => { |
|
89 |
const { schema } = unref(getProps) |
|
90 |
|
|
91 |
const index = findIndex(schema, (v: FormSchema) => v.field === field) |
|
92 |
if (index > -1) { |
|
93 |
schema.splice(index, 1) |
|
94 |
} |
|
95 |
} |
|
96 |
|
|
97 |
const addSchema = (formSchema: FormSchema, index?: number) => { |
|
98 |
const { schema } = unref(getProps) |
|
99 |
if (index !== void 0) { |
|
100 |
schema.splice(index, 0, formSchema) |
|
101 |
return |
|
102 |
} |
|
103 |
schema.push(formSchema) |
|
104 |
} |
|
105 |
|
|
106 |
const setSchema = (schemaProps: FormSetPropsType[]) => { |
|
107 |
const { schema } = unref(getProps) |
|
108 |
for (const v of schema) { |
|
109 |
for (const item of schemaProps) { |
|
110 |
if (v.field === item.field) { |
|
111 |
set(v, item.path, item.value) |
|
112 |
} |
|
113 |
} |
|
114 |
} |
|
115 |
} |
|
116 |
|
|
117 |
const getElFormRef = (): ComponentRef<typeof ElForm> => { |
|
118 |
return unref(elFormRef) as ComponentRef<typeof ElForm> |
|
119 |
} |
|
120 |
|
|
121 |
expose({ |
|
122 |
setValues, |
|
123 |
formModel, |
|
124 |
setProps, |
|
125 |
delSchema, |
|
126 |
addSchema, |
|
127 |
setSchema, |
|
128 |
getElFormRef |
|
129 |
}) |
|
130 |
|
|
131 |
// 监听表单结构化数组,重新生成formModel |
|
132 |
watch( |
|
133 |
() => unref(getProps).schema, |
|
134 |
(schema = []) => { |
|
135 |
formModel.value = initModel(schema, unref(formModel)) |
|
136 |
}, |
|
137 |
{ |
|
138 |
immediate: true, |
|
139 |
deep: true |
|
140 |
} |
|
141 |
) |
|
142 |
|
|
143 |
// 渲染包裹标签,是否使用栅格布局 |
|
144 |
const renderWrap = () => { |
|
145 |
const { isCol } = unref(getProps) |
|
146 |
const content = isCol ? ( |
|
147 |
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow> |
|
148 |
) : ( |
|
149 |
renderFormItemWrap() |
|
150 |
) |
|
151 |
return content |
|
152 |
} |
|
153 |
|
|
154 |
// 是否要渲染el-col |
|
155 |
const renderFormItemWrap = () => { |
|
156 |
// hidden属性表示隐藏,不做渲染 |
|
157 |
const { schema = [], isCol } = unref(getProps) |
|
158 |
|
|
159 |
return schema |
|
160 |
.filter((v) => !v.hidden) |
|
161 |
.map((item) => { |
|
162 |
// 如果是 Divider 组件,需要自己占用一行 |
|
163 |
const isDivider = item.component === 'Divider' |
|
164 |
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent> |
|
165 |
return isDivider ? ( |
|
166 |
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com> |
|
167 |
) : isCol ? ( |
|
168 |
// 如果需要栅格,需要包裹 ElCol |
|
169 |
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol> |
|
170 |
) : ( |
|
171 |
renderFormItem(item) |
|
172 |
) |
|
173 |
}) |
|
174 |
} |
|
175 |
|
|
176 |
// 渲染formItem |
|
177 |
const renderFormItem = (item: FormSchema) => { |
|
178 |
// 单独给只有options属性的组件做判断 |
|
179 |
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer'] |
|
180 |
const slotsMap: Recordable = { |
|
181 |
...setItemComponentSlots(slots, item?.componentProps?.slots, item.field) |
|
182 |
} |
|
183 |
if ( |
|
184 |
item?.component !== 'SelectV2' && |
|
185 |
item?.component !== 'Cascader' && |
|
186 |
item?.componentProps?.options |
|
187 |
) { |
|
188 |
slotsMap.default = () => renderOptions(item) |
|
189 |
} |
|
190 |
|
|
191 |
const formItemSlots: Recordable = setFormItemSlots(slots, item.field) |
|
192 |
// 如果有 labelMessage,自动使用插槽渲染 |
|
193 |
if (item?.labelMessage) { |
|
194 |
formItemSlots.label = () => { |
|
195 |
return ( |
|
196 |
<> |
|
197 |
<span>{item.label}</span> |
|
198 |
<ElTooltip placement="right" raw-content> |
|
199 |
{{ |
|
200 |
content: () => <span v-dompurify-html={item.labelMessage}></span>, |
|
201 |
default: () => ( |
|
202 |
<Icon |
|
203 |
icon="ep:warning" |
|
204 |
size={16} |
|
205 |
color="var(--el-color-primary)" |
|
206 |
class="relative top-1px ml-2px" |
|
207 |
></Icon> |
|
208 |
) |
|
209 |
}} |
|
210 |
</ElTooltip> |
|
211 |
</> |
|
212 |
) |
|
213 |
} |
|
214 |
} |
|
215 |
return ( |
|
216 |
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}> |
|
217 |
{{ |
|
218 |
...formItemSlots, |
|
219 |
default: () => { |
|
220 |
const Com = componentMap[item.component as string] as ReturnType< |
|
221 |
typeof defineComponent |
|
222 |
> |
|
223 |
|
|
224 |
const { autoSetPlaceholder } = unref(getProps) |
|
225 |
|
|
226 |
return slots[item.field] ? ( |
|
227 |
getSlot(slots, item.field, formModel.value) |
|
228 |
) : ( |
|
229 |
<Com |
|
230 |
vModel={formModel.value[item.field]} |
|
231 |
{...(autoSetPlaceholder && setTextPlaceholder(item))} |
|
232 |
{...setComponentProps(item)} |
|
233 |
style={item.componentProps?.style} |
|
234 |
{...(notRenderOptions.includes(item?.component as string) && |
|
235 |
item?.componentProps?.options |
|
236 |
? { options: item?.componentProps?.options || [] } |
|
237 |
: {})} |
|
238 |
> |
|
239 |
{{ ...slotsMap }} |
|
240 |
</Com> |
|
241 |
) |
|
242 |
} |
|
243 |
}} |
|
244 |
</ElFormItem> |
|
245 |
) |
|
246 |
} |
|
247 |
|
|
248 |
// 渲染options |
|
249 |
const renderOptions = (item: FormSchema) => { |
|
250 |
switch (item.component) { |
|
251 |
case 'Select': |
|
252 |
case 'SelectV2': |
|
253 |
const { renderSelectOptions } = useRenderSelect(slots) |
|
254 |
return renderSelectOptions(item) |
|
255 |
case 'Radio': |
|
256 |
case 'RadioButton': |
|
257 |
const { renderRadioOptions } = useRenderRadio() |
|
258 |
return renderRadioOptions(item) |
|
259 |
case 'Checkbox': |
|
260 |
case 'CheckboxButton': |
|
261 |
const { renderCheckboxOptions } = useRenderCheckbox() |
|
262 |
return renderCheckboxOptions(item) |
|
263 |
default: |
|
264 |
break |
|
265 |
} |
|
266 |
} |
|
267 |
|
|
268 |
// 过滤传入Form组件的属性 |
|
269 |
const getFormBindValue = () => { |
|
270 |
// 避免在标签上出现多余的属性 |
|
271 |
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model'] |
|
272 |
const props = { ...unref(getProps) } |
|
273 |
for (const key in props) { |
|
274 |
if (delKeys.indexOf(key) !== -1) { |
|
275 |
delete props[key] |
|
276 |
} |
|
277 |
} |
|
278 |
return props |
|
279 |
} |
|
280 |
|
|
281 |
return () => ( |
|
282 |
<ElForm |
|
283 |
ref={elFormRef} |
|
284 |
{...getFormBindValue()} |
|
285 |
model={props.isCustom ? props.model : formModel} |
|
286 |
class={prefixCls} |
|
287 |
v-loading={props.vLoading} |
|
288 |
> |
|
289 |
{{ |
|
290 |
// 如果需要自定义,就什么都不渲染,而是提供默认插槽 |
|
291 |
default: () => { |
|
292 |
const { isCustom } = unref(getProps) |
|
293 |
return isCustom ? getSlot(slots, 'default') : renderWrap() |
|
294 |
} |
|
295 |
}} |
|
296 |
</ElForm> |
|
297 |
) |
|
298 |
} |
|
299 |
}) |
|
300 |
</script> |
|
301 |
|
|
302 |
<style lang="scss" scoped> |
|
303 |
.#{$elNamespace}-form.#{$namespace}-form .#{$elNamespace}-row { |
|
304 |
margin-right: 0 !important; |
|
305 |
margin-left: 0 !important; |
|
306 |
} |
|
307 |
</style> |