13 changed files with 399 additions and 375 deletions
@ -1,337 +0,0 @@ |
|||
import type { PropType, Ref } from 'vue'; |
|||
import type { FormActionType, FormProps } from '../types/form'; |
|||
import type { FormSchema } from '../types/form'; |
|||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; |
|||
import type { TableActionType } from '/@/components/Table'; |
|||
|
|||
import { defineComponent, computed, unref, toRefs } from 'vue'; |
|||
import { Form, Col } from 'ant-design-vue'; |
|||
import { componentMap } from '../componentMap'; |
|||
import { BasicHelp } from '/@/components/Basic'; |
|||
|
|||
import { isBoolean, isFunction } from '/@/utils/is'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import { createPlaceholderMessage, setComponentRuleType } from '../helper'; |
|||
import { upperFirst, cloneDeep } 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; |
|||
const { componentProps = {} } = schema; |
|||
if (!isFunction(componentProps)) { |
|||
return componentProps; |
|||
} |
|||
return componentProps({ schema, tableAction, formModel, formActionType }) ?? {}; |
|||
}); |
|||
|
|||
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(): ValidationRule[] { |
|||
const { |
|||
rules: defRules = [], |
|||
component, |
|||
rulesMessageJoinLabel, |
|||
label, |
|||
dynamicRules, |
|||
required, |
|||
} = props.schema; |
|||
|
|||
if (isFunction(dynamicRules)) { |
|||
return dynamicRules(unref(getValues)) as ValidationRule[]; |
|||
} |
|||
|
|||
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]; |
|||
|
|||
if ((!rules || rules.length === 0) && required) { |
|||
rules = [{ required, type: 'string' }]; |
|||
} |
|||
|
|||
const requiredRuleIndex: number = rules.findIndex( |
|||
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator') |
|||
); |
|||
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps; |
|||
if (requiredRuleIndex !== -1) { |
|||
const rule = rules[requiredRuleIndex]; |
|||
const { isShow } = getShow(); |
|||
if (!isShow) { |
|||
rule.required = false; |
|||
} |
|||
if (rule.required && component) { |
|||
if (!Reflect.has(rule, 'type')) { |
|||
rule.type = 'string'; |
|||
} |
|||
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') |
|||
? rulesMessageJoinLabel |
|||
: globalRulesMessageJoinLabel; |
|||
|
|||
rule.message = |
|||
rule.message || createPlaceholderMessage(component) + `${joinLabel ? label : ''}`; |
|||
|
|||
if (component.includes('Input') || component.includes('Textarea')) { |
|||
rule.whitespace = true; |
|||
} |
|||
|
|||
setComponentRuleType(rule, component); |
|||
} |
|||
} |
|||
|
|||
// 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]: (e: Nullable<Recordable>) => { |
|||
if (propsData[eventKey]) { |
|||
propsData[eventKey](e); |
|||
} |
|||
|
|||
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 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; |
|||
let placeholder; |
|||
// RangePicker place is an array
|
|||
if (isCreatePlaceholder && component !== 'RangePicker' && component) { |
|||
placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component); |
|||
} |
|||
propsData.placeholder = placeholder; |
|||
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 style="color:#00000073">{subLabel}</span> |
|||
</span> |
|||
) : ( |
|||
label |
|||
); |
|||
if (!helpMessage || (Array.isArray(helpMessage) && helpMessage.length === 0)) { |
|||
return renderLabel; |
|||
} |
|||
return ( |
|||
<span> |
|||
{renderLabel} |
|||
<BasicHelp placement="top" class="mx-1" text={helpMessage} {...helpComponentProps} /> |
|||
</span> |
|||
); |
|||
} |
|||
|
|||
function renderItem() { |
|||
const { itemProps, slot, render, field, suffix } = props.schema; |
|||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp); |
|||
const { colon } = props.formProps; |
|||
|
|||
const getContent = () => { |
|||
return slot |
|||
? getSlot(slots, slot, unref(getValues)) |
|||
: render |
|||
? render(unref(getValues)) |
|||
: renderComponent(); |
|||
}; |
|||
|
|||
const showSuffix = !!suffix; |
|||
|
|||
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix; |
|||
|
|||
return ( |
|||
<Form.Item |
|||
name={field} |
|||
colon={colon} |
|||
class={{ 'suffix-item': showSuffix }} |
|||
{...(itemProps as Recordable)} |
|||
label={renderLabelHelpMessage()} |
|||
rules={handleRules()} |
|||
labelCol={labelCol} |
|||
wrapperCol={wrapperCol} |
|||
> |
|||
<> |
|||
{getContent()} |
|||
{showSuffix && <span class="suffix">{getSuffix}</span>} |
|||
</> |
|||
</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> |
|||
) |
|||
); |
|||
}; |
|||
}, |
|||
}); |
|||
@ -0,0 +1,340 @@ |
|||
<script lang="tsx"> |
|||
import type { PropType, Ref } from 'vue'; |
|||
import type { FormActionType, FormProps } from '../types/form'; |
|||
import type { FormSchema } from '../types/form'; |
|||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; |
|||
import type { TableActionType } from '/@/components/Table'; |
|||
|
|||
import { defineComponent, computed, unref, toRefs } from 'vue'; |
|||
import { Form, Col } from 'ant-design-vue'; |
|||
import { componentMap } from '../componentMap'; |
|||
import { BasicHelp } from '/@/components/Basic'; |
|||
|
|||
import { isBoolean, isFunction } from '/@/utils/is'; |
|||
import { getSlot } from '/@/utils/helper/tsxHelper'; |
|||
import { createPlaceholderMessage, setComponentRuleType } from '../helper'; |
|||
import { upperFirst, cloneDeep } 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; |
|||
const { componentProps = {} } = schema; |
|||
if (!isFunction(componentProps)) { |
|||
return componentProps; |
|||
} |
|||
return componentProps({ schema, tableAction, formModel, formActionType }) ?? {}; |
|||
}); |
|||
|
|||
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(): ValidationRule[] { |
|||
const { |
|||
rules: defRules = [], |
|||
component, |
|||
rulesMessageJoinLabel, |
|||
label, |
|||
dynamicRules, |
|||
required, |
|||
} = props.schema; |
|||
|
|||
if (isFunction(dynamicRules)) { |
|||
return dynamicRules(unref(getValues)) as ValidationRule[]; |
|||
} |
|||
|
|||
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]; |
|||
|
|||
if ((!rules || rules.length === 0) && required) { |
|||
rules = [{ required, type: 'string' }]; |
|||
} |
|||
|
|||
const requiredRuleIndex: number = rules.findIndex( |
|||
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator') |
|||
); |
|||
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps; |
|||
if (requiredRuleIndex !== -1) { |
|||
const rule = rules[requiredRuleIndex]; |
|||
const { isShow } = getShow(); |
|||
if (!isShow) { |
|||
rule.required = false; |
|||
} |
|||
if (rule.required && component) { |
|||
if (!Reflect.has(rule, 'type')) { |
|||
rule.type = 'string'; |
|||
} |
|||
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') |
|||
? rulesMessageJoinLabel |
|||
: globalRulesMessageJoinLabel; |
|||
|
|||
rule.message = |
|||
rule.message || createPlaceholderMessage(component) + `${joinLabel ? label : ''}`; |
|||
|
|||
if (component.includes('Input') || component.includes('Textarea')) { |
|||
rule.whitespace = true; |
|||
} |
|||
|
|||
setComponentRuleType(rule, component); |
|||
} |
|||
} |
|||
|
|||
// 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]: (e: Nullable<Recordable>) => { |
|||
if (propsData[eventKey]) { |
|||
propsData[eventKey](e); |
|||
} |
|||
|
|||
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 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; |
|||
let placeholder; |
|||
// RangePicker place is an array |
|||
if (isCreatePlaceholder && component !== 'RangePicker' && component) { |
|||
placeholder = |
|||
unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component); |
|||
} |
|||
propsData.placeholder = placeholder; |
|||
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 style="color:#00000073">{subLabel}</span> |
|||
</span> |
|||
) : ( |
|||
label |
|||
); |
|||
if (!helpMessage || (Array.isArray(helpMessage) && helpMessage.length === 0)) { |
|||
return renderLabel; |
|||
} |
|||
return ( |
|||
<span> |
|||
{renderLabel} |
|||
<BasicHelp placement="top" class="mx-1" text={helpMessage} {...helpComponentProps} /> |
|||
</span> |
|||
); |
|||
} |
|||
|
|||
function renderItem() { |
|||
const { itemProps, slot, render, field, suffix } = props.schema; |
|||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp); |
|||
const { colon } = props.formProps; |
|||
|
|||
const getContent = () => { |
|||
return slot |
|||
? getSlot(slots, slot, unref(getValues)) |
|||
: render |
|||
? render(unref(getValues)) |
|||
: renderComponent(); |
|||
}; |
|||
|
|||
const showSuffix = !!suffix; |
|||
|
|||
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix; |
|||
|
|||
return ( |
|||
<Form.Item |
|||
name={field} |
|||
colon={colon} |
|||
class={{ 'suffix-item': showSuffix }} |
|||
{...(itemProps as Recordable)} |
|||
label={renderLabelHelpMessage()} |
|||
rules={handleRules()} |
|||
labelCol={labelCol} |
|||
wrapperCol={wrapperCol} |
|||
> |
|||
<> |
|||
{getContent()} |
|||
{showSuffix && <span class="suffix">{getSuffix}</span>} |
|||
</> |
|||
</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> |
|||
Loading…
Reference in new issue