雪忆
2024-07-29 ecfe66a0199606241c73a52519bbe800c9aa31f8
提交 | 用户 | age
1418dc 1 <script lang="tsx">
553ee9 2   import { type Recordable, type Nullable } from '@vben/types';
1418dc 3   import type { PropType, Ref } from 'vue';
8d93e0 4   import { computed, defineComponent, toRefs, unref } from 'vue';
0bb76a 5   import {
LK 6     isComponentFormSchema,
7     type FormActionType,
8     type FormProps,
9     type FormSchemaInner as FormSchema,
10   } from '../types/form';
9c6b27 11   import type { Rule as ValidationRule } from 'ant-design-vue/lib/form/interface';
bab28a 12   import type { TableActionType } from '@/components/Table';
8d93e0 13   import { Col, Divider, Form } from 'ant-design-vue';
1418dc 14   import { componentMap } from '../componentMap';
f6147f 15   import { BasicHelp, BasicTitle } from '@/components/Basic';
bab28a 16   import { isBoolean, isFunction, isNull } from '@/utils/is';
X 17   import { getSlot } from '@/utils/helper/tsxHelper';
3de5b5 18   import {
L 19     createPlaceholderMessage,
cd71e6 20     isIncludeSimpleComponents,
3de5b5 21     NO_AUTO_LINK_COMPONENTS,
L 22     setComponentRuleType,
23   } from '../helper';
8d93e0 24   import { cloneDeep, upperFirst } from 'lodash-es';
1418dc 25   import { useItemLabelWidth } from '../hooks/useLabelWidth';
bab28a 26   import { useI18n } from '@/hooks/web/useI18n';
1418dc 27
V 28   export default defineComponent({
29     name: 'BasicFormItem',
30     inheritAttrs: false,
31     props: {
32       schema: {
33         type: Object as PropType<FormSchema>,
8b2e0f 34         default: () => ({}),
1418dc 35       },
V 36       formProps: {
37         type: Object as PropType<FormProps>,
8b2e0f 38         default: () => ({}),
1418dc 39       },
V 40       allDefaultValues: {
553ee9 41         type: Object as PropType<Recordable<any>>,
8b2e0f 42         default: () => ({}),
1418dc 43       },
V 44       formModel: {
553ee9 45         type: Object as PropType<Recordable<any>>,
8b2e0f 46         default: () => ({}),
1418dc 47       },
V 48       setFormModel: {
c7639c 49         type: Function as PropType<(key: string, value: any, schema: FormSchema) => void>,
1418dc 50         default: null,
V 51       },
52       tableAction: {
53         type: Object as PropType<TableActionType>,
54       },
55       formActionType: {
56         type: Object as PropType<FormActionType>,
57       },
58b30a 58       isAdvanced: {
59         type: Boolean,
60       },
1418dc 61     },
V 62     setup(props, { slots }) {
63       const { t } = useI18n();
64
65       const { schema, formProps } = toRefs(props) as {
66         schema: Ref<FormSchema>;
67         formProps: Ref<FormProps>;
68       };
69
aef90a 70       // 组件 CropperAvatar 的 size 属性类型为 number
X 71       // 此处补充一个兼容
72       if (schema.value.component === 'CropperAvatar' && typeof formProps.value.size === 'string') {
73         formProps.value.size = undefined;
74       }
75
1418dc 76       const itemLabelWidthProp = useItemLabelWidth(schema, formProps);
V 77
78       const getValues = computed(() => {
79         const { allDefaultValues, formModel, schema } = props;
80         const { mergeDynamicData } = props.formProps;
81         return {
82           field: schema.field,
83           model: formModel,
84           values: {
85             ...mergeDynamicData,
86             ...allDefaultValues,
87             ...formModel,
553ee9 88           } as Recordable<any>,
1418dc 89           schema: schema,
V 90         };
91       });
92
93       const getComponentsProps = computed(() => {
94         const { schema, tableAction, formModel, formActionType } = props;
47a448 95         let { componentProps = {} } = schema;
96         if (isFunction(componentProps)) {
97           componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
1418dc 98         }
cd71e6 99         if (isIncludeSimpleComponents(schema.component)) {
ed8cff 100           componentProps = Object.assign(
L 101             { type: 'horizontal' },
102             {
103               orientation: 'left',
104               plain: true,
105             },
106             componentProps,
107           );
47a448 108         }
553ee9 109         return componentProps as Recordable<any>;
1418dc 110       });
V 111
112       const getDisable = computed(() => {
113         const { disabled: globDisabled } = props.formProps;
114         const { dynamicDisabled } = props.schema;
115         const { disabled: itemDisabled = false } = unref(getComponentsProps);
116         let disabled = !!globDisabled || itemDisabled;
117         if (isBoolean(dynamicDisabled)) {
118           disabled = dynamicDisabled;
119         }
120         if (isFunction(dynamicDisabled)) {
121           disabled = dynamicDisabled(unref(getValues));
122         }
123         return disabled;
124       });
125
342328 126       const getReadonly = computed(() => {
127         const { readonly: globReadonly } = props.formProps;
128         const { dynamicReadonly } = props.schema;
129         const { readonly: itemReadonly = false } = unref(getComponentsProps);
130
131         let readonly = globReadonly || itemReadonly;
132         if (isBoolean(dynamicReadonly)) {
133           readonly = dynamicReadonly;
134         }
135         if (isFunction(dynamicReadonly)) {
136           readonly = dynamicReadonly(unref(getValues));
137         }
138         return readonly;
139       });
140
1418dc 141       function getShow(): { isShow: boolean; isIfShow: boolean } {
V 142         const { show, ifShow } = props.schema;
143         const { showAdvancedButton } = props.formProps;
144         const itemIsAdvanced = showAdvancedButton
58b30a 145           ? isBoolean(props.isAdvanced)
146             ? props.isAdvanced
1418dc 147             : true
V 148           : true;
149
150         let isShow = true;
151         let isIfShow = true;
152
153         if (isBoolean(show)) {
154           isShow = show;
155         }
156         if (isBoolean(ifShow)) {
157           isIfShow = ifShow;
158         }
159         if (isFunction(show)) {
160           isShow = show(unref(getValues));
161         }
162         if (isFunction(ifShow)) {
163           isIfShow = ifShow(unref(getValues));
164         }
165         isShow = isShow && itemIsAdvanced;
166         return { isShow, isIfShow };
167       }
553ee9 168       function handleRules(): ValidationRule[] {
1418dc 169         const {
V 170           rules: defRules = [],
171           component,
172           rulesMessageJoinLabel,
173           label,
174           dynamicRules,
175           required,
176         } = props.schema;
177         if (isFunction(dynamicRules)) {
553ee9 178           return dynamicRules(unref(getValues)) as ValidationRule[];
1418dc 179         }
V 180
553ee9 181         let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
bc82d1 182         const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
N 183
184         const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
185           ? rulesMessageJoinLabel
186           : globalRulesMessageJoinLabel;
e6a7e4 187         const assertLabel = joinLabel ? (isFunction(label) ? '' : label) : '';
a6b65b 188         const defaultMsg = component
Z 189           ? createPlaceholderMessage(component) + assertLabel
190           : assertLabel;
bc82d1 191
N 192         function validator(rule: any, value: any) {
193           const msg = rule.message || defaultMsg;
194           if (value === undefined || isNull(value)) {
195             // 空值
196             return Promise.reject(msg);
197           } else if (Array.isArray(value) && value.length === 0) {
198             // 数组类型
199             return Promise.reject(msg);
200           } else if (typeof value === 'string' && value.trim() === '') {
201             // 空字符串
202             return Promise.reject(msg);
203           } else if (
204             typeof value === 'object' &&
205             Reflect.has(value, 'checked') &&
206             Reflect.has(value, 'halfChecked') &&
207             Array.isArray(value.checked) &&
208             Array.isArray(value.halfChecked) &&
209             value.checked.length === 0 &&
210             value.halfChecked.length === 0
211           ) {
212             // 非关联选择的tree组件
213             return Promise.reject(msg);
214           }
215           return Promise.resolve();
216         }
00fca0 217         const getRequired = isFunction(required) ? required(unref(getValues)) : required;
765064 218
8d93e0 219         /*
Z 220          * 1、若设置了required属性,又没有其他的rules,就创建一个验证规则;
221          * 2、若设置了required属性,又存在其他的rules,则只rules中不存在required属性时,才添加验证required的规则
222          *     也就是说rules中的required,优先级大于required
223          */
224         if (getRequired) {
225           if (!rules || rules.length === 0) {
23b684 226             const trigger = NO_AUTO_LINK_COMPONENTS.includes(component || 'Input')
IW 227               ? 'blur'
228               : 'change';
229             rules = [{ required: getRequired, validator, trigger }];
8d93e0 230           } else {
Z 231             const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'));
232
233             if (requiredIndex === -1) {
234               rules.push({ required: getRequired, validator });
235             }
236           }
1418dc 237         }
V 238
239         const requiredRuleIndex: number = rules.findIndex(
56a966 240           (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
1418dc 241         );
1e0ede 242
1418dc 243         if (requiredRuleIndex !== -1) {
V 244           const rule = rules[requiredRuleIndex];
245           const { isShow } = getShow();
246           if (!isShow) {
247             rule.required = false;
248           }
237f41 249           if (component) {
bc82d1 250             rule.message = rule.message || defaultMsg;
1418dc 251
V 252             if (component.includes('Input') || component.includes('Textarea')) {
253               rule.whitespace = true;
254             }
cb3534 255             const valueFormat = unref(getComponentsProps)?.valueFormat;
V 256             setComponentRuleType(rule, component, valueFormat);
1418dc 257           }
V 258         }
259
260         // Maximum input length rule check
261         const characterInx = rules.findIndex((val) => val.max);
262         if (characterInx !== -1 && !rules[characterInx].validator) {
263           rules[characterInx].message =
264             rules[characterInx].message ||
553ee9 265             t('component.form.maxTip', [rules[characterInx].max] as Recordable<any>);
1418dc 266         }
V 267         return rules;
268       }
269
270       function renderComponent() {
0bb76a 271         if (!isComponentFormSchema(props.schema)) {
LK 272           return null;
273         }
1418dc 274         const {
V 275           renderComponentContent,
276           component,
277           field,
278           changeEvent = 'change',
279           valueField,
0bc01d 280           valueFormat,
1418dc 281         } = props.schema;
V 282
283         const isCheck = component && ['Switch', 'Checkbox'].includes(component);
284
285         const eventKey = `on${upperFirst(changeEvent)}`;
286
287         const on = {
553ee9 288           [eventKey]: (...args: Nullable<Recordable<any>>[]) => {
513823 289             const [e] = args;
baf406 290
1418dc 291             const target = e ? e.target : null;
0bc01d 292             let value = target ? (isCheck ? target.checked : target.value) : e;
baf406 293             if (isFunction(valueFormat)) {
C 294               value = valueFormat({ ...unref(getValues), value });
0bc01d 295             }
c7639c 296             props.setFormModel(field, value, props.schema);
43aa74 297
X 298             if (propsData[eventKey]) {
299               propsData[eventKey](...args);
300             }
1418dc 301           },
V 302         };
780a8a 303         const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
1418dc 304
2828ed 305         const { autoSetPlaceHolder, size } = props.formProps;
553ee9 306         const propsData: Recordable<any> = {
1418dc 307           allowClear: true,
V 308           size,
309           ...unref(getComponentsProps),
310           disabled: unref(getDisable),
342328 311           readonly: unref(getReadonly),
1418dc 312         };
V 313
314         const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
315         // RangePicker place is an array
316         if (isCreatePlaceholder && component !== 'RangePicker' && component) {
2d3d04 317           propsData.placeholder =
1418dc 318             unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
V 319         }
320         propsData.codeField = field;
321         propsData.formValues = unref(getValues);
322
553ee9 323         const bindValue: Recordable<any> = {
1418dc 324           [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
V 325         };
326
553ee9 327         const compAttr: Recordable<any> = {
1418dc 328           ...propsData,
V 329           ...on,
330           ...bindValue,
331         };
332
333         if (!renderComponentContent) {
334           return <Comp {...compAttr} />;
335         }
336         const compSlot = isFunction(renderComponentContent)
342328 337           ? {
338               ...renderComponentContent(unref(getValues), {
339                 disabled: unref(getDisable),
340                 readonly: unref(getReadonly),
341               }),
342             }
1418dc 343           : {
V 344               default: () => renderComponentContent,
345             };
346         return <Comp {...compAttr}>{compSlot}</Comp>;
347       }
348
349       function renderLabelHelpMessage() {
350         const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
e6a7e4 351         const getLabel = isFunction(label) ? label(unref(getValues)) : label;
1418dc 352         const renderLabel = subLabel ? (
V 353           <span>
e6a7e4 354             {getLabel} <span class="text-secondary">{subLabel}</span>
1418dc 355           </span>
V 356         ) : (
e6a7e4 357           getLabel
1418dc 358         );
2d3d04 359         const getHelpMessage = isFunction(helpMessage)
N 360           ? helpMessage(unref(getValues))
361           : helpMessage;
f455fb 362         if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
1418dc 363           return renderLabel;
V 364         }
365         return (
366           <span>
367             {renderLabel}
f455fb 368             <BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
1418dc 369           </span>
V 370         );
371       }
372
373       function renderItem() {
265627 374         const { itemProps, slot, render, field, suffix, component, prefix } = props.schema;
1418dc 375         const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
V 376         const { colon } = props.formProps;
342328 377         const opts = { disabled: unref(getDisable), readonly: unref(getReadonly) };
47a448 378         if (component === 'Divider') {
379           return (
380             <Col span={24}>
a5ff59 381               <Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
47a448 382             </Col>
383           );
f6147f 384         } else if (component === 'BasicTitle') {
385           return (
386             <Form.Item
387               labelCol={labelCol}
388               wrapperCol={wrapperCol}
389               name={field}
cfdb09 390               class={{
Z 391                 'suffix-item': !!suffix,
392                 'prefix-item': !!prefix,
265627 393               }}
f6147f 394             >
395               <BasicTitle {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</BasicTitle>
396             </Form.Item>
397           );
47a448 398         } else {
399           const getContent = () => {
400             return slot
a065de 401               ? getSlot(slots, slot, unref(getValues), opts)
47a448 402               : render
43aa74 403                 ? render(unref(getValues), opts)
X 404                 : renderComponent();
47a448 405           };
1418dc 406
47a448 407           const showSuffix = !!suffix;
408           const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
1418dc 409
265627 410           const showPrefix = !!prefix;
E 411           const getPrefix = isFunction(prefix) ? prefix(unref(getValues)) : prefix;
3de5b5 412           // TODO 自定义组件验证会出现问题,因此这里框架默认将自定义组件设置手动触发验证,如果其他组件还有此问题请手动设置autoLink=false
0bb76a 413           if (component && NO_AUTO_LINK_COMPONENTS.includes(component)) {
3de5b5 414             props.schema &&
L 415               (props.schema.itemProps! = {
416                 autoLink: false,
417                 ...props.schema.itemProps,
418               });
419           }
420
47a448 421           return (
422             <Form.Item
423               name={field}
424               colon={colon}
265627 425               class={{
E 426                 'suffix-item': showSuffix,
427                 'prefix-item': showPrefix,
428               }}
553ee9 429               {...(itemProps as Recordable<any>)}
47a448 430               label={renderLabelHelpMessage()}
431               rules={handleRules()}
432               labelCol={labelCol}
433               wrapperCol={wrapperCol}
434             >
435               <div style="display:flex">
265627 436                 {showPrefix && <span class="prefix">{getPrefix}</span>}
96ce18 437                 <div style="flex:1;">{getContent()}</div>
47a448 438                 {showSuffix && <span class="suffix">{getSuffix}</span>}
439               </div>
440             </Form.Item>
441           );
442         }
1418dc 443       }
305630 444
1418dc 445       return () => {
a6b65b 446         const { colProps = {}, colSlot, renderColContent, component, slot } = props.schema;
9e055a 447         if (!((component && componentMap.has(component)) || slot)) {
305630 448           return null;
V 449         }
1418dc 450
V 451         const { baseColProps = {} } = props.formProps;
452         const realColProps = { ...baseColProps, ...colProps };
453         const { isIfShow, isShow } = getShow();
454         const values = unref(getValues);
342328 455         const opts = { disabled: unref(getDisable), readonly: unref(getReadonly) };
305630 456
1418dc 457         const getContent = () => {
V 458           return colSlot
a065de 459             ? getSlot(slots, colSlot, values, opts)
1418dc 460             : renderColContent
43aa74 461               ? renderColContent(values, opts)
X 462               : renderItem();
1418dc 463         };
V 464
465         return (
466           isIfShow && (
467             <Col {...realColProps} v-show={isShow}>
468               {getContent()}
469             </Col>
470           )
471         );
472       };
473     },
474   });
475 </script>