| <script lang="tsx"> |
| import type { PropType, Ref } from 'vue'; |
| import { computed, defineComponent, toRefs, unref } from 'vue'; |
| import type { FormActionType, FormProps, FormSchema } from '../types/form'; |
| import type { RuleObject } from 'ant-design-vue/lib/form'; |
| import type { TableActionType } from '/@/components/Table'; |
| import { Col, Divider, Form } from 'ant-design-vue'; |
| import { componentMap } from '../componentMap'; |
| import { BasicHelp } from '/@/components/Basic'; |
| import { isBoolean, isFunction, isNull } from '/@/utils/is'; |
| import { getSlot } from '/@/utils/helper/tsxHelper'; |
| import { createPlaceholderMessage, setComponentRuleType } from '../helper'; |
| import { cloneDeep, upperFirst } from 'lodash-es'; |
| import { useItemLabelWidth } from '../hooks/useLabelWidth'; |
| import { useI18n } from '/@/hooks/web/useI18n'; |
| |
| export default defineComponent({ |
| name: 'BasicFormItem', |
| inheritAttrs: false, |
| props: { |
| schema: { |
| type: Object as PropType<FormSchema>, |
| default: () => ({}), |
| }, |
| formProps: { |
| type: Object as PropType<FormProps>, |
| default: () => ({}), |
| }, |
| allDefaultValues: { |
| type: Object as PropType<Recordable>, |
| default: () => ({}), |
| }, |
| formModel: { |
| type: Object as PropType<Recordable>, |
| default: () => ({}), |
| }, |
| setFormModel: { |
| type: Function as PropType<(key: string, value: any) => void>, |
| default: null, |
| }, |
| tableAction: { |
| type: Object as PropType<TableActionType>, |
| }, |
| formActionType: { |
| type: Object as PropType<FormActionType>, |
| }, |
| }, |
| setup(props, { slots }) { |
| const { t } = useI18n(); |
| |
| const { schema, formProps } = toRefs(props) as { |
| schema: Ref<FormSchema>; |
| formProps: Ref<FormProps>; |
| }; |
| |
| const itemLabelWidthProp = useItemLabelWidth(schema, formProps); |
| |
| const getValues = computed(() => { |
| const { allDefaultValues, formModel, schema } = props; |
| const { mergeDynamicData } = props.formProps; |
| return { |
| field: schema.field, |
| model: formModel, |
| values: { |
| ...mergeDynamicData, |
| ...allDefaultValues, |
| ...formModel, |
| } as Recordable, |
| schema: schema, |
| }; |
| }); |
| |
| const getComponentsProps = computed(() => { |
| const { schema, tableAction, formModel, formActionType } = props; |
| let { componentProps = {} } = schema; |
| if (isFunction(componentProps)) { |
| componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {}; |
| } |
| if (schema.component === 'Divider') { |
| componentProps = Object.assign({ type: 'horizontal' }, componentProps, { |
| orientation: 'left', |
| plain: true, |
| }); |
| } |
| return componentProps as Recordable; |
| }); |
| |
| const getDisable = computed(() => { |
| const { disabled: globDisabled } = props.formProps; |
| const { dynamicDisabled } = props.schema; |
| const { disabled: itemDisabled = false } = unref(getComponentsProps); |
| let disabled = !!globDisabled || itemDisabled; |
| if (isBoolean(dynamicDisabled)) { |
| disabled = dynamicDisabled; |
| } |
| if (isFunction(dynamicDisabled)) { |
| disabled = dynamicDisabled(unref(getValues)); |
| } |
| return disabled; |
| }); |
| |
| function getShow(): { isShow: boolean; isIfShow: boolean } { |
| const { show, ifShow } = props.schema; |
| const { showAdvancedButton } = props.formProps; |
| const itemIsAdvanced = showAdvancedButton |
| ? isBoolean(props.schema.isAdvanced) |
| ? props.schema.isAdvanced |
| : true |
| : true; |
| |
| let isShow = true; |
| let isIfShow = true; |
| |
| if (isBoolean(show)) { |
| isShow = show; |
| } |
| if (isBoolean(ifShow)) { |
| isIfShow = ifShow; |
| } |
| if (isFunction(show)) { |
| isShow = show(unref(getValues)); |
| } |
| if (isFunction(ifShow)) { |
| isIfShow = ifShow(unref(getValues)); |
| } |
| isShow = isShow && itemIsAdvanced; |
| return { isShow, isIfShow }; |
| } |
| |
| function handleRules(): RuleObject[] { |
| const { |
| rules: defRules = [], |
| component, |
| rulesMessageJoinLabel, |
| label, |
| dynamicRules, |
| required, |
| } = props.schema; |
| |
| if (isFunction(dynamicRules)) { |
| return dynamicRules(unref(getValues)) as RuleObject[]; |
| } |
| |
| let rules: RuleObject[] = cloneDeep(defRules) as RuleObject[]; |
| const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps; |
| |
| const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') |
| ? rulesMessageJoinLabel |
| : globalRulesMessageJoinLabel; |
| const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`; |
| |
| function validator(rule: any, value: any) { |
| const msg = rule.message || defaultMsg; |
| if (value === undefined || isNull(value)) { |
| // null |
| return Promise.reject(msg); |
| } else if (Array.isArray(value) && value.length === 0) { |
| // Array |
| return Promise.reject(msg); |
| } else if (typeof value === 'string' && value.trim() === '') { |
| // An empty string |
| return Promise.reject(msg); |
| } else if ( |
| typeof value === 'object' && |
| Reflect.has(value, 'checked') && |
| Reflect.has(value, 'halfChecked') && |
| Array.isArray(value.checked) && |
| Array.isArray(value.halfChecked) && |
| value.checked.length === 0 && |
| value.halfChecked.length === 0 |
| ) { |
| // A non-associative selected tree component |
| return Promise.reject(msg); |
| } |
| return Promise.resolve(); |
| } |
| |
| const getRequired = isFunction(required) ? required(unref(getValues)) : required; |
| |
| /* |
| * 1、If the required attribute is set and there are no other rules, a validation rule is created; |
| * 2、If the required attribute is set and there are other rules, the rule to verify the required is added only if the required attribute does not exist in the rules, that is, the required in the rules takes precedence greater than required |
| */ |
| if (getRequired) { |
| if (!rules || rules.length === 0) { |
| rules = [{ required: getRequired, validator }]; |
| } else { |
| const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required')); |
| |
| if (requiredIndex === -1) { |
| rules.push({ required: getRequired, validator }); |
| } |
| } |
| } |
| |
| const requiredRuleIndex: number = rules.findIndex( |
| (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'), |
| ); |
| |
| if (requiredRuleIndex !== -1) { |
| const rule = rules[requiredRuleIndex]; |
| const { isShow } = getShow(); |
| if (!isShow) { |
| rule.required = false; |
| } |
| if (component) { |
| if (!Reflect.has(rule, 'type')) { |
| rule.type = component === 'InputNumber' ? 'number' : 'string'; |
| } |
| |
| rule.message = rule.message || defaultMsg; |
| |
| if (component.includes('Input') || component.includes('Textarea')) { |
| rule.whitespace = true; |
| } |
| const valueFormat = unref(getComponentsProps)?.valueFormat; |
| setComponentRuleType(rule, component, valueFormat); |
| } |
| } |
| |
| // Maximum input length rule check |
| const characterInx = rules.findIndex((val) => val.max); |
| if (characterInx !== -1 && !rules[characterInx].validator) { |
| rules[characterInx].message = |
| rules[characterInx].message || |
| t('component.form.maxTip', [rules[characterInx].max] as Recordable); |
| } |
| return rules; |
| } |
| |
| function renderComponent() { |
| const { |
| renderComponentContent, |
| component, |
| field, |
| changeEvent = 'change', |
| valueField, |
| } = props.schema; |
| |
| const isCheck = component && ['Switch', 'Checkbox'].includes(component); |
| |
| const eventKey = `on${upperFirst(changeEvent)}`; |
| |
| const on = { |
| [eventKey]: (...args: Nullable<Recordable>[]) => { |
| const [e] = args; |
| if (propsData[eventKey]) { |
| propsData[eventKey](...args); |
| } |
| const target = e ? e.target : null; |
| const value = target ? (isCheck ? target.checked : target.value) : e; |
| props.setFormModel(field, value); |
| }, |
| }; |
| const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>; |
| |
| const { autoSetPlaceHolder, size } = props.formProps; |
| const propsData: Recordable = { |
| allowClear: true, |
| getPopupContainer: (trigger: Element) => trigger.parentNode, |
| size, |
| ...unref(getComponentsProps), |
| disabled: unref(getDisable), |
| }; |
| |
| const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder; |
| // RangePicker place is an array |
| if (isCreatePlaceholder && component !== 'RangePicker' && component) { |
| propsData.placeholder = |
| unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component); |
| } |
| propsData.codeField = field; |
| propsData.formValues = unref(getValues); |
| |
| const bindValue: Recordable = { |
| [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field], |
| }; |
| |
| const compAttr: Recordable = { |
| ...propsData, |
| ...on, |
| ...bindValue, |
| }; |
| |
| if (!renderComponentContent) { |
| return <Comp {...compAttr} />; |
| } |
| const compSlot = isFunction(renderComponentContent) |
| ? { ...renderComponentContent(unref(getValues)) } |
| : { |
| default: () => renderComponentContent, |
| }; |
| return <Comp {...compAttr}>{compSlot}</Comp>; |
| } |
| |
| function renderLabelHelpMessage() { |
| const { label, helpMessage, helpComponentProps, subLabel } = props.schema; |
| const renderLabel = subLabel ? ( |
| <span> |
| {label} <span class="text-secondary">{subLabel}</span> |
| </span> |
| ) : ( |
| label |
| ); |
| const getHelpMessage = isFunction(helpMessage) |
| ? helpMessage(unref(getValues)) |
| : helpMessage; |
| if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) { |
| return renderLabel; |
| } |
| return ( |
| <span> |
| {renderLabel} |
| <BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} /> |
| </span> |
| ); |
| } |
| |
| function renderItem() { |
| const { itemProps, slot, render, field, suffix, beforeItem, afterItem, component } = |
| props.schema; |
| const { labelCol, wrapperCol } = unref(itemLabelWidthProp); |
| const { colon } = props.formProps; |
| |
| if (component === 'Divider') { |
| return ( |
| <Col span={24}> |
| <Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider> |
| </Col> |
| ); |
| } else { |
| const getContent = () => { |
| return slot |
| ? getSlot(slots, slot, unref(getValues)) |
| : render |
| ? render(unref(getValues)) |
| : renderComponent(); |
| }; |
| |
| const showSuffix = !!suffix; |
| const showBeforeItem = !!beforeItem; |
| const showAfterItem = !!afterItem; |
| const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix; |
| const getBeforeItem = isFunction(beforeItem) ? beforeItem(unref(getValues)) : beforeItem; |
| const getAfterItem = isFunction(afterItem) ? afterItem(unref(getValues)) : afterItem; |
| |
| return ( |
| <Form.Item |
| name={field} |
| colon={colon} |
| class={{ 'suffix-item': showSuffix }} |
| {...(itemProps as Recordable)} |
| label={renderLabelHelpMessage()} |
| rules={handleRules()} |
| labelCol={labelCol} |
| wrapperCol={wrapperCol} |
| > |
| {showBeforeItem && <div class="extra">{getBeforeItem}</div>} |
| <div style="display:flex"> |
| <div class="w-full" style="flex:1;"> |
| {getContent()} |
| </div> |
| {showSuffix && <span class="suffix">{getSuffix}</span>} |
| </div> |
| {showAfterItem && <div class="extra">{getAfterItem}</div>} |
| </Form.Item> |
| ); |
| } |
| } |
| |
| return () => { |
| const { colProps = {}, colSlot, renderColContent, component } = props.schema; |
| if (!componentMap.has(component)) { |
| return null; |
| } |
| |
| const { baseColProps = {} } = props.formProps; |
| const realColProps = { ...baseColProps, ...colProps }; |
| const { isIfShow, isShow } = getShow(); |
| const values = unref(getValues); |
| const getContent = () => { |
| return colSlot |
| ? getSlot(slots, colSlot, values) |
| : renderColContent |
| ? renderColContent(values) |
| : renderItem(); |
| }; |
| |
| return ( |
| isIfShow && ( |
| <Col {...realColProps} v-show={isShow}> |
| {getContent()} |
| </Col> |
| ) |
| ); |
| }; |
| }, |
| }); |
| </script> |