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