前语
企业应用中怎么提炼公共模块,怎么更好的扩展,怎么让更多场景复用。以下将根据vue3 ts antd进行公共表单开发
FormItemComps
将每一种表单项分别拆分,易于后期扩展和管理。
// index.ts
import FormItemInput from "./FormItemInput.vue";
import FormItemInputNumber from "./FormItemInputNumber.vue";
interface ComsType {
[key: string]: any
}
const ComponentMap: ComsType = {
"input": FormItemInput,
"input-number": FormItemInputNumber,
}
export default ComponentMap;
FormItemInput
根底的输入组件,有几个点咱们需要先理解。
- 数据呼应
- antd input props 项有很多,咱们怎么接入。
首要装备根底的表单项 name label rules等
运用 useChange 进行数据传递
运用 v-bind props.props 进行a-input的所有属性传递
// FormItemInput.vue
<template>
<a-form-item
:name="props.name"
:label="props.label"
:rules="props.rules"
>
<a-input
v-model:value="inputValue"
v-bind="props.props"
/>
</a-form-item>
</template>
<script lang="ts" setup>
// props.props 具体参数请查阅官方文档 https://antdv.com/components/input-cn/#API
import { ref, watch } from "vue";
import { AnyPropName } from "@/types";
import { useChange } from "./useFormItem";
import { formItemDefaultProps } from "./const";
interface FormItemEmits {
(e: "update:modelValue", value: any): void,
(e: "onChange", value: any, key: string): void,
}
// vue3 当时版本暂不支撑外部导入props类型界说 wait fix
interface FormItemProps {
modelValue: any, // v-model呼应值
name: string, // 数据键
label: string, // 表单项文本名
rules?: Array<AnyPropName>, // 校验规矩 同antd 表单校验规矩共同
props?: AnyPropName, // 组件额外 props 同antd组件props共同
change?: (value: any, key: string) => void, // 当时项值变化时触发
}
const emits = defineEmits<FormItemEmits>();
const props = withDefaults(defineProps<FormItemProps>(), {...formItemDefaultProps});
const inputValue = ref(props.modelValue);
watch(inputValue, value => {
useChange(emits, props, value);
})
watch(() => props.modelValue, value => {
inputValue.value = value;
})
</script>
FormItemInputNumber
数字输入组件,核心逻辑同输入组件共同,今后要扩展组件核心逻辑依然是不变的。咱们仅有需要做的便是运用不同的a-xx组件或许你的自界说组件
<template>
<a-form-item
:name="props.name"
:label="props.label"
:rules="props.rules"
>
<a-input-number
class="form-item-input-number"
v-model:value="inputValue"
v-bind="props.props"
/>
</a-form-item>
</template>
<script lang="ts" setup>
// props.props 具体参数请查阅官方文档 https://antdv.com/components/input-number-cn#API
import { ref, watch } from "vue";
import { AnyPropName } from "@/types";
import { useChange } from "./useFormItem";
import { formItemDefaultProps } from "./const";
interface FormItemEmits {
(e: "update:modelValue", value: any): void,
(e: "onChange", value: any, key: string): void,
}
// vue3 当时版本暂不支撑外部导入props类型界说 wait fix
interface FormItemProps {
modelValue: any, // v-model呼应值
name: string, // 数据键
label: string, // 表单项文本名
rules?: Array<AnyPropName>, // 校验规矩 同antd 表单校验规矩共同
props?: AnyPropName, // 组件额外 props 同antd组件props共同
change?: (value: any, key: string) => void, // 当时项值变化时触发
}
const emits = defineEmits<FormItemEmits>();
const props = withDefaults(defineProps<FormItemProps>(), {...formItemDefaultProps});
const inputValue = ref(props.modelValue);
watch(inputValue, value => {
useChange(emits, props, value);
})
watch(() => props.modelValue, value => {
inputValue.value = value;
})
</script>
<style lang="less">
.form-item-input-number{
width: 100%;
}
</style>
useChange
呼应事情逻辑是共同的,提取到公共模块。
import { FormItemProps, FormItemEmits } from "./types";
export function useChange(emits: FormItemEmits, props: FormItemProps, value: any) {
const { change, name } = props;
emits("update:modelValue", value); // v-model呼应
emits("onChange", value, name); // formSearch事情呼应
change!(value, name); // 数据项中传入的事情呼应
}
BaseForm
整合界说好的表单项,并经过数据束缚输出表单。
核心点:数据传入,修正表单内容,获取表单内容,校验提交表单。
<template>
<a-spin :spinning="loading" :tip="loadingTip" :delay="delayTime">
<a-form
ref="formRef"
name="base-form-instance"
v-bind="props"
:model="formState"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-row :gutter="24">
<template v-for="item in data" :key="item.key">
<a-col :span="item.span || colSpan">
<a-tooltip :title="item.tip" :mouseEnterDelay="0.2">
<component
@onChange="onChange"
:is="ComponentMap[item.type]"
v-model="formState[item.key]"
v-bind="item"
:name="item.key"
/>
</a-tooltip>
</a-col>
</template>
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, nextTick } from "vue";
import type { FormInstance } from "ant-design-vue";
import type { AnyPropName, FormListDatas } from "@types";
import ComponentMap from "@components/FormItemComs";
import { defaultProps } from "./const";
interface PropsType {
data: FormListDatas, // 数据源
loading?: boolean, // 是否加载中
loadingTip?: string, // 自界说加载提示案牍
delayTime?: number, // 推迟显现loading状况, 当loading状况在 delayTime 时间内完毕, 则不显现loding UI状况 单位ms
colSpan?: number // 表单项一行占多少列 n/24
labelCol?: AnyPropName, // 表单项 label 装备; https://www.antdv.com/components/grid-cn/#Col
wrapperCol?: AnyPropName // 表单项 输入控件装备; 类型同 labelCol共同
}
const emits = defineEmits(["change"]);
const props = withDefaults(defineProps<PropsType>(), { ...defaultProps }) // defineProps<>(["visible", "modelValue"]);
const { data, loading, loadingTip, delayTime, labelCol } = toRefs(props);
const formRef = ref<FormInstance>();
const formState = reactive<AnyPropName>({});
setDefaultFormState();
let timeoutValidate: any;
// 对 component 运用 onChange 的原因是组件自身有change事情这儿不进行混合; 并且change参数类型也不相同会提示报错
const onChange = (key: string, value: any): void => {
emits("change", key, value);
// 当数据变化时手动触发校验, 由于嵌套v-model导致表单主动校验会出现过错呼应 预期与实际不共同
clearTimeout(timeoutValidate);
timeoutValidate = setTimeout(() => {
formRef.value?.validateFields([key]);
}, 120)
}
function setDefaultFormState() {
data.value.forEach(v => {
if (formState[v.key] !== v.defaultValue) formState[v.key] = v.defaultValue;
});
}
// 重置表单后会触发n次 change 呼应事情
const resetFields = () => {
formRef.value?.resetFields(); // 清空表单状况; 当经过setFormState从头设置值之后,只调用resetFields一次无法重置
nextTick(() => {
setDefaultFormState(); // 也能够再调用一次 formRef.value?.resetFields(); 可是这样从头设置的 data[n].defaultValue 无法收效;
});
}
const getFormState = (payload?: string | string[]): string | object => {
const type = payload ? typeof payload : "not";
if (type === "not") return { ...formState };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (type === "string") return formState[payload];
if (Array.isArray(payload)) {
const ret: AnyPropName = {};
payload.forEach(key => {
ret[key] = formState[key]
});
return ret;
}
console.error(`传入参数类型需为 string | string[]; 当时参数${JSON.stringify(payload)}`);
return {};
};
const setFormState = (key: string | AnyPropName, value?: any) => {
const type = key ? typeof key : "not";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (type === "string") formState[key] = value;
else if (!Array.isArray(key) && type === "object") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const name in key) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
formState[name] = key[name];
}
} else {
console.error(`传入参数类型需为 string | object<{key: value}>; 当时参数${JSON.stringify(key)}`);
}
}
const submit = async (nameList?: string | string[]): Promise<AnyPropName> => {
return formRef.value!.validateFields(nameList);
}
defineExpose({ getFormState, setFormState, resetFields, submit });
</script>
结语
关于怎么运用表单请前往GitHub查看。由于这是一系列东西,文章上写出来太多反而影响阅览。