blob: 9bbb5023fa6eff001dcebd015ebdaa54d9a5b304 [file] [log] [blame]
<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>