表单Form
是中后台频繁使用的组件,以下是一个基于arco design vue
组件库封装的表单组件。
这个表单组件特点:
- 所有配置都是直接继承组件库组件的
props
,无需其他文档 - 可配置展开折叠
- 支持响应式布局
- 表单项支持动态隐藏
- 插槽支持,自定义扩展
- 组件库的良好支持,封装代码简洁优雅
placeholder
无需手写,输入框允许清除默认true
- 自带
hooks
省时省力 TS
代码提示支持
数据类型结构如下
组件代码
组件自带 hooks
示例 1 折叠查询表单
演示效果
示例 2 动态隐藏表单项
演示效果
示例 3 自带 hooks 的使用
演示效果
源码
import type * as A from '@arco-design/web-vue'
export type FormType =
| 'input'
| 'select'
| 'radio-group'
| 'checkbox-group'
| 'textarea'
| 'date-picker'
| 'time-picker'
| 'input-number'
| 'rate'
| 'switch'
| 'slider'
| 'cascader'
| 'tree-select'
export type ColumnsItemPropsKey =
| keyof A.InputInstance['$props']
| keyof A.SelectInstance['$props']
| keyof A.TextareaInstance['$props']
| keyof A.DatePickerInstance['$props']
| keyof A.TimePickerInstance['$props']
| keyof A.RadioGroupInstance['$props']
| keyof A.CheckboxGroupInstance['$props']
| keyof A.InputNumberInstance['$props']
| keyof A.RateInstance['$props']
| keyof A.SwitchInstance['$props']
| keyof A.SliderInstance['$props']
| keyof A.CascaderInstance['$props']
| keyof A.TreeSelectInstance['$props']
export type ColumnsItemHide = boolean | ((form?: any) => boolean)
export interface ColumnsItem {
type: FormType
label: A.FormItemInstance['label']
field: A.FormItemInstance['field']
span?: number
col?: A.ColProps
item?: Omit<A.FormItemInstance['$props'], 'label' | 'field'>
props?:
| A.InputInstance['$props']
| A.SelectInstance['$props']
| A.TextareaInstance['$props']
| A.DatePickerInstance['$props']
| A.TimePickerInstance['$props']
| A.RadioGroupInstance['$props']
| A.CheckboxGroupInstance['$props']
| A.InputNumberInstance['$props']
| A.RateInstance['$props']
| A.SwitchInstance['$props']
| A.SliderInstance['$props']
| A.CascaderInstance['$props']
| A.TreeSelectInstance['$props']
rules?: A.FormItemInstance['$props']['rules']
options?:
| A.SelectInstance['$props']['options']
| A.RadioGroupInstance['$props']['options']
| A.CheckboxGroupInstance['$props']['options']
| A.CascaderInstance['$props']['options']
data?: A.TreeSelectInstance['$props']['data']
hide?: ColumnsItemHide
}
export interface Options {
form: Omit<A.FormInstance['$props'], 'model'>
row?: Partial<typeof import('@arco-design/web-vue')['Row']['__defaults']>
columns: ColumnsItem[]
btns?: { hide?: boolean; span?: number; col?: A.ColProps; searchBtnText?: string }
fold?: { enable?: boolean; index?: number }
}
<template>
<a-form
:auto-label-width="true"
v-bind="options.form"
ref="formRef"
:model="modelValue"
>
<a-row :gutter="14" v-bind="options.row" class="w-full">
<template v-for="(item, index) in options.columns" :key="item.field">
<a-col
v-if="!isHide(item.hide)"
:span="item.span || 12"
v-bind="item.col"
v-show="
index <= (options.fold?.index || 0) ||
(index >= (options.fold?.index || 0) && !collapsed)
"
>
<a-form-item
v-bind="item.item"
:label="item.label"
:field="item.field"
:rules="item.rules"
>
<slot :name="item.field">
<template v-if="item.type === 'input'">
<a-input
:allow-clear="true"
:placeholder="`请输入${item.label}`"
:max-length="20"
v-bind="(item.props as A.InputInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-input>
</template>
<template v-if="item.type === 'input-number'">
<a-input-number
:placeholder="`请输入${item.label}`"
v-bind="(item.props as A.InputNumberInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-input-number>
</template>
<template v-if="item.type === 'textarea'">
<a-textarea
:allow-clear="true"
:placeholder="`请填写${item.label}`"
:max-length="200"
:show-word-limit="true"
v-bind="(item.props as A.TextareaInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-textarea>
</template>
<template v-if="item.type === 'select'">
<a-select
:allow-clear="true"
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.SelectInstance['$props'])"
:options="(item.options as A.SelectInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-select>
</template>
<template v-if="item.type === 'cascader'">
<a-cascader
:allow-clear="true"
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.CascaderInstance['$props'])"
:options="(item.options as A.CascaderInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'tree-select'">
<a-tree-select
:allow-clear="true"
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.TreeSelectInstance['$props'])"
:data="(item.data as A.TreeSelectInstance['$props']['data'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
>
</a-tree-select>
</template>
<template v-if="item.type === 'radio-group'">
<a-radio-group
v-bind="(item.props as A.RadioGroupInstance['$props'])"
:options="(item.options as A.RadioGroupInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-radio-group>
</template>
<template v-if="item.type === 'checkbox-group'">
<a-checkbox-group
v-bind="(item.props as A.CheckboxGroupInstance['$props'])"
:options="(item.options as A.CheckboxGroupInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-checkbox-group>
</template>
<template v-if="item.type === 'date-picker'">
<a-date-picker
:allow-clear="true"
:placeholder="`请选择日期`"
v-bind="(item.props as A.DatePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-date-picker>
</template>
<template v-if="item.type === 'time-picker'">
<a-time-picker
:allow-clear="true"
:placeholder="`请选择时间`"
v-bind="(item.props as A.TimePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
>
</a-time-picker>
</template>
<template v-if="item.type === 'rate'">
<a-rate
:allow-clear="true"
v-bind="(item.props as A.RateInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'switch'">
<a-switch
v-bind="(item.props as A.SwitchInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'slider'">
<a-slider
v-bind="(item.props as A.SliderInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
</slot>
</a-form-item>
</a-col>
</template>
<a-col
:span="options.btns?.span || 12"
v-bind="options.btns?.col"
v-if="!options.btns?.hide"
>
<a-space wrap>
<slot name="footer">
<a-button type="primary" @click="emit('search')">
<template #icon><icon-search /></template>
<template #default>{{
options.btns?.searchBtnText || "搜索"
}}</template>
</a-button>
<a-button @click="emit('reset')">重置</a-button>
<a-button
v-if="options.fold?.enable"
type="text"
size="mini"
@click="collapsed = !collapsed"
>
<template #icon>
<icon-up v-if="!collapsed" />
<icon-down v-else />
</template>
<template #default>{{ collapsed ? "展开" : "收起" }}</template>
</a-button>
</slot>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
<script setup lang="ts">
import type { Options, ColumnsItemHide } from "./type";
import type * as A from "@arco-design/web-vue";
interface Props {
modelValue: object;
options: Options;
}
const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits<{
(e: "update:modelValue", value: any): void;
(e: "search"): void;
(e: "reset"): void;
}>();
const valueChange = (value: any, field: string) => {
emit(
"update:modelValue",
Object.assign(props.modelValue, { [field]: value })
);
};
const collapsed = ref(false);
const formRef = ref<A.FormInstance>();
defineExpose({ formRef });
const isHide = (hide?: ColumnsItemHide) => {
if (hide === undefined) return false;
if (typeof hide === "boolean") return hide;
if (typeof hide === "function") {
return hide(props.modelValue);
}
};
</script>
<style lang="scss" scoped></style>
import { reactive } from "vue";
import _ from "lodash";
import type { Options, ColumnsItem, ColumnsItemPropsKey } from "./type";
import { Message } from "@arco-design/web-vue";
export function useGiForm(initValue: Options) {
const getInitValue = () => _.cloneDeep(initValue);
const options = reactive(getInitValue());
const resetOptions = () => {
Object.assign(options, getInitValue());
};
const setValue = <T>(field: string, key: keyof ColumnsItem, value: T) => {
if (!options.columns.length) return;
const obj = options.columns.find((i) => i.field === field);
if (obj) {
obj[key] = value as never;
} else {
Message.warning(`没有这个field属性值-${field},请检查!`);
}
};
const setPropsValue = <T>(
field: string,
key: ColumnsItemPropsKey,
value: T
) => {
if (!options.columns.length) return;
const obj = options.columns.find((i) => i.field === field);
if (obj) {
if (!obj.props) {
obj.props = {};
}
obj.props[key as keyof ColumnsItem["props"]] = value as never;
} else {
Message.warning(`没有这个field属性值-${field},请检查!`);
}
};
return {
/** 配置项 */
options,
/** 重置 options */
resetOptions,
/** 设置 options.columns 某个对象属性的值 */
setValue,
/** 设置 options.columns.props 某个属性的值 */
setPropsValue,
};
}
标签:封装,Form,表单,field,keyof,props,const,type,options
From: https://www.cnblogs.com/wp-leonard/p/17840374.html