feat: 优化表单提示

master
绝弹 2023-11-15 21:52:12 +08:00
parent eeed362320
commit 17c695d065
24 changed files with 337 additions and 198 deletions

View File

@ -1,4 +1,5 @@
import { Form, FormInstance, FormItem } from "@arco-design/web-vue"; import { Form, FormInstance } from "@arco-design/web-vue";
import { useVModel } from "@vueuse/core";
import { PropType } from "vue"; import { PropType } from "vue";
import { FormContextKey } from "../core/useFormContext"; import { FormContextKey } from "../core/useFormContext";
import { useFormItems } from "../core/useFormItems"; import { useFormItems } from "../core/useFormItems";
@ -6,7 +7,6 @@ import { useFormModel } from "../core/useFormModel";
import { useFormRef } from "../core/useFormRef"; import { useFormRef } from "../core/useFormRef";
import { useFormSubmit } from "../core/useFormSubmit"; import { useFormSubmit } from "../core/useFormSubmit";
import { AnFormItem, IAnFormItem } from "./FormItem"; import { AnFormItem, IAnFormItem } from "./FormItem";
import { useVModel } from "@vueuse/core";
/** /**
* *
@ -16,6 +16,12 @@ export const AnForm = defineComponent({
props: { props: {
/** /**
* *
* @example
* ```ts
* {
* id: undefined
* }
* ```
*/ */
model: { model: {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
@ -30,12 +36,24 @@ export const AnForm = defineComponent({
}, },
/** /**
* *
* @example
* ```ts
* (model) => {
* return api.user.addUser(model)
* }
* ```
*/ */
submit: { submit: {
type: [String, Function, Object] as PropType<IAnFormSubmit>, type: [String, Function, Object] as PropType<IAnFormSubmit>,
}, },
/** /**
* Form * Form
* @exmaple
* ```ts
* {
* layout: 'vertical'
* }
* ```
*/ */
formProps: { formProps: {
type: Object as IAnFormProps, type: Object as IAnFormProps,

View File

@ -59,14 +59,12 @@ export const AnFormItem = defineComponent({
if (Slot) { if (Slot) {
return <Slot {...props} />; return <Slot {...props} />;
} }
const Setter = setterMap[props.item.setter as SetterType]?.setter as any;
const Setter = setterMap[props.item.setter as SetterType]?.render as any;
if (!Setter) { if (!Setter) {
return null; return null;
} }
return ( return (
<Setter {...props.item.nodeProps} v-model={props.model[props.item.field]}> <Setter {...props.item.setterProps} v-model={props.model[props.item.field]}>
{setterSlots.value} {setterSlots.value}
</Setter> </Setter>
); );
@ -152,13 +150,19 @@ export type IAnFormItemBase = {
/** /**
* *
* @description * @description
* @required * @example
* ```ts
* 'username'
* ```
*/ */
field: string; field: string;
/** /**
* *
* @example '昵称' * @example
* ```ts
* '昵称'
* ```
*/ */
label?: string; label?: string;
@ -170,19 +174,29 @@ export type IAnFormItemBase = {
/** /**
* *
* @example (model) => Boolean(model.id) * @example
* ```ts
* (props) => Boolean(props.model.id)
* ```
*/ */
visible?: IAnFormItemBoolFn; visible?: IAnFormItemBoolFn;
/** /**
* *
* @example (model) => Boolean(model.id) * @example
* ```ts
* (props) => Boolean(props.model.id)
* ```
*/ */
disable?: IAnFormItemBoolFn; disable?: IAnFormItemBoolFn;
/** /**
* *
* @description * @description
* @example
* ```ts
* [{ label: '方式1', value: 1 }]
* ```
*/ */
options?: IAnFormItemOption[] | ((args: IAnFormItemFnProps) => IAnFormItemOption[] | Promise<IAnFormItemOption[]>); options?: IAnFormItemOption[] | ((args: IAnFormItemFnProps) => IAnFormItemOption[] | Promise<IAnFormItemOption[]>);

View File

@ -1,10 +1,10 @@
import { useVisible } from "@/hooks/useVisible";
import { Button, ButtonInstance, Modal } from "@arco-design/web-vue"; import { Button, ButtonInstance, Modal } from "@arco-design/web-vue";
import { PropType } from "vue"; import { PropType } from "vue";
import { IAnFormItem } from "./FormItem";
import { AnForm, IAnFormProps, IAnFormSubmit } from "./Form";
import { useModalTrigger } from "../core/useModalTrigger";
import { useModalSubmit } from "../core/useModalSubmit"; import { useModalSubmit } from "../core/useModalSubmit";
import { useVisible } from "@/hooks/useVisible"; import { useModalTrigger } from "../core/useModalTrigger";
import { AnForm, IAnFormProps, IAnFormSubmit } from "./Form";
import { IAnFormItem } from "./FormItem";
/** /**
* *
@ -14,11 +14,11 @@ export const AnFormModal = defineComponent({
props: { props: {
/** /**
* *
* @default '添加' * @default '新增'
*/ */
title: { title: {
type: [String, Function] as PropType<ModalType>, type: [String, Function] as PropType<ModalType>,
default: "添加", default: "新增",
}, },
/** /**
* *

View File

@ -4,24 +4,42 @@ export type SetterMap = typeof setterMap;
export type SetterType = keyof SetterMap; export type SetterType = keyof SetterMap;
export type SetterItem = { export type SetterItemMap = {
[key in SetterType]: Partial< [key in SetterType]: {
Omit<SetterMap[key], 'setter'> & {
/** /**
* *
* @example 'input' * @example
* ```ts
* 'input'
* ```
*/ */
setter: key; setter?: key;
/**
*
*/
setterSlots: Recordable;
/** /**
* *
* @example
* ```tsx
* { type: "password" }
* ```
*/ */
setterProps: Recordable; setterProps?: SetterMap[key]['setterProps'];
} /**
>; *
}[SetterType]; * @example
* ```tsx
* {
* help: (props) => {
* return <span>
* {props.item.label}
* </span>
* }
* }
* ```
*/
setterSlots?: SetterMap[key]['setterSlots'];
};
};
export type SetterItem = SetterItemMap[SetterType];
export { setterMap }; export { setterMap };

View File

@ -9,7 +9,7 @@ export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>)
const getItemOptions = (field: string) => { const getItemOptions = (field: string) => {
const item = getItem(field); const item = getItem(field);
if (item) { if (item) {
return (item.nodeProps as any)?.options; return (item.setterProps as any)?.options;
} }
}; };

View File

@ -1,9 +1,17 @@
import { FormItem, useItems } from "./useItems";
import { AnForm, IAnForm } from "../components/Form"; import { AnForm, IAnForm } from "../components/Form";
import { FormItem, useItems } from "./useItems";
export type FormUseOptions = Partial<Omit<IAnForm, "items">> & { export type FormUseOptions = Partial<Omit<IAnForm, "items">> & {
/** /**
* *
* @example
* ```ts
* [{
* field: 'name',
* label: '昵称',
* setter: 'input'
* }]
* ```
*/ */
items?: FormItem[]; items?: FormItem[];
}; };

View File

@ -1,39 +1,49 @@
import { defaultsDeep, merge, omit } from "lodash-es"; import { defaultsDeep, merge, omit } from 'lodash-es';
import { Rule, useRules } from "./useRules"; import { IAnFormItem, IAnFormItemBase } from '../components/FormItem';
import { IAnFormItem } from "../components/FormItem"; import { SetterItem, setterMap } from '../components/FormSetter';
import { setterMap } from "../components/FormSetter"; import { Rule, useRules } from './useRules';
/** /**
* *
*/ */
export type FormItem = Omit<IAnFormItem, "rules"> & { export type FormItem = Omit<IAnFormItemBase, 'rules'> &
SetterItem & {
/** /**
* *
* @example 1 * @example
* ```ts
* '1'
* ```
*/ */
value?: any; value?: any;
/** /**
* *
* @default false * @default
* ```ts
* false
* ```
*/ */
required?: boolean; required?: boolean;
/** /**
* *
* @example ['email'] * @example
* ```ts
* ['email']
* ```
*/ */
rules?: Rule[]; rules?: Rule[];
}; };
const ITEM: Partial<FormItem> = { const ITEM: Partial<FormItem> = {
setter: "input", setter: 'input',
itemProps: {}, itemProps: {},
}; };
const SUBMIT_ITEM: FormItem = { const SUBMIT_ITEM: FormItem = {
field: "id", field: 'id',
setter: "submit", setter: 'submit',
itemProps: { itemProps: {
hideLabel: true, hideLabel: true,
}, },
@ -46,19 +56,19 @@ export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
for (const item of list) { for (const item of list) {
let target: any = defaultsDeep({}, ITEM); let target: any = defaultsDeep({}, ITEM);
if (!item.setter || typeof item.setter === "string") { if (!item.setter || typeof item.setter === 'string') {
const defaults = setterMap[item.setter ?? "input"]; const defaults = setterMap[item.setter ?? 'input'];
if (defaults) { if (defaults) {
defaultsDeep(target, defaults); defaultsDeep(target, defaults);
} }
} }
if (item.setter === "submit") { if (item.setter === 'submit') {
target = merge(target, SUBMIT_ITEM); target = merge(target, SUBMIT_ITEM);
hasSubmit = true; hasSubmit = true;
} }
target = merge(target, omit(item, ["required", "rules"])); target = merge(target, omit(item, ['required', 'rules']));
const rules = useRules(item); const rules = useRules(item);
if (rules) { if (rules) {

View File

@ -1,14 +1,17 @@
import { Cascader, CascaderInstance } from "@arco-design/web-vue"; import { Cascader, CascaderInstance } from '@arco-design/web-vue';
import { initOptions } from "../utils/initOptions"; import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
type Props = CascaderInstance["$props"]; type CascaderProps = CascaderInstance['$props'];
export default { type CascaderSlots = 'label' | 'prefix' | 'arrowIcon' | 'loadingIcon' | 'searchIcon' | 'empty' | 'option';
render: Cascader,
init: initOptions, export default defineSetter<CascaderProps, CascaderSlots>({
nodeProps: { setter: Cascader,
placeholder: "请选择", setterProps: {
placeholder: '请选择',
allowClear: true, allowClear: true,
expandTrigger: "hover", expandTrigger: 'hover',
} as Props, },
}; onSetup: initOptions as any,
});

View File

@ -1,6 +1,5 @@
export default { import { defineSetter } from './util';
render: () => {
return "1"; export default defineSetter<{ a: number }, '11'>({
}, setter: () => '1',
nodeProps: {}, });
};

View File

@ -1,11 +1,14 @@
import { DatePicker, DatePickerInstance } from "@arco-design/web-vue"; import { DatePicker, DatePickerInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = DatePickerInstance["$props"]; type DateProps = DatePickerInstance['$props'];
export default { type DateSlots = 'prefix' | 'suffixIcon' | 'iconNextDouble' | 'iconPrevDouble' | 'iconNext' | 'iconPrev' | 'cell' | 'extra';
render: DatePicker,
nodeProps: { export default defineSetter<DateProps, DateSlots>({
placeholder: "请选择", setter: DatePicker,
setterProps: {
placeholder: '请选择',
allowClear: true, allowClear: true,
} as Props, } as any,
}; });

View File

@ -1,10 +1,13 @@
import { RangePicker, RangePickerInstance } from "@arco-design/web-vue"; import { RangePicker, RangePickerInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = RangePickerInstance["$props"]; type RangeProps = RangePickerInstance['$props'];
export default { type RangeSlots = "1";
render: RangePicker,
nodeProps: { export default defineSetter<RangeProps, RangeSlots>({
setter: RangePicker,
setterProps: {
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,11 +1,14 @@
import { Input, InputInstance } from "@arco-design/web-vue"; import { Input, InputInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = InputInstance["$props"]; type InputProps = InputInstance['$props'];
export default { type InputSlots = "2";
render: Input,
nodeProps: { export default defineSetter<InputProps, InputSlots>({
placeholder: "请输入", setter: Input,
setterProps: {
placeholder: '请输入',
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,12 +1,15 @@
import { InputInstance, InputNumber, InputNumberInstance } from "@arco-design/web-vue"; import { InputInstance, InputNumber, InputNumberInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = InputInstance["$props"] & InputNumberInstance["$props"]; type NumberProps = InputInstance['$props'] | InputNumberInstance['$props'];
export default { type NumberSlots = "3";
render: InputNumber,
nodeProps: { export default defineSetter<NumberProps, NumberSlots>({
placeholder: "请输入", setter: InputNumber,
setterProps: {
placeholder: '请输入',
defaultValue: 0, defaultValue: 0,
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,10 +1,13 @@
import { InputInstance, InputPassword, InputPasswordInstance } from "@arco-design/web-vue"; import { InputInstance, InputPassword, InputPasswordInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = InputInstance["$props"] & InputPasswordInstance["$props"]; type PasswordProps = InputInstance['$props'] & InputPasswordInstance['$props'];
export default { type PasswordSlots = "4";
render: InputPassword,
nodeProps: { export default defineSetter<PasswordProps, PasswordSlots>({
placeholder: "请输入", setter: InputPassword,
} as Props, setterProps: {
}; placeholder: '请输入',
},
});

View File

@ -1,11 +1,14 @@
import { InputInstance, InputSearch, InputSearchInstance } from "@arco-design/web-vue"; import { InputInstance, InputSearch, InputSearchInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = InputInstance["$props"] & InputSearchInstance["$props"]; type SearchProps = InputInstance['$props'] & InputSearchInstance['$props'];
export default { type SearchSlots = "5";
render: InputSearch,
nodeProps: { export default defineSetter<SearchProps, SearchSlots>({
placeholder: "请输入", setter: InputSearch,
setterProps: {
placeholder: '请输入',
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,14 +1,18 @@
import { Select, SelectInstance, SelectOptionData } from "@arco-design/web-vue"; import { Select, SelectInstance } from '@arco-design/web-vue';
import { initOptions } from "../utils/initOptions"; import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
export default { type SelectProps = SelectInstance['$props'];
render: Select,
init: initOptions, type SelectSlots = "6";
nodeProps: {
placeholder: "请选择", export default defineSetter<SelectProps, SelectSlots>({
setter: Select,
onSetup: initOptions as any,
setterProps: {
placeholder: '请选择',
allowClear: true, allowClear: true,
allowSearch: true, allowSearch: true,
options: [], options: [],
} as SelectInstance["$props"], },
options: [] as SelectOptionData[] | ((arg: any) => Recordable[] | Promise<Recordable[]>) | undefined, });
};

View File

@ -1,8 +1,9 @@
import { Button } from "@arco-design/web-vue"; import { Button } from "@arco-design/web-vue";
import { FormContextKey } from "../core/useFormContext"; import { FormContextKey } from "../core/useFormContext";
import { defineSetter } from "./util";
export default { export default defineSetter<{ a1?: number }, "10">({
render() { setter() {
const { loading, submitForm, resetModel } = inject(FormContextKey)!; const { loading, submitForm, resetModel } = inject(FormContextKey)!;
return ( return (
<> <>
@ -15,5 +16,5 @@ export default {
</> </>
); );
}, },
nodeProps: {}, setterProps: {},
}; });

View File

@ -1,11 +1,14 @@
import { InputInstance, Textarea, TextareaInstance } from "@arco-design/web-vue"; import { InputInstance, Textarea, TextareaInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = InputInstance["$props"] & TextareaInstance["$props"]; type TextareaProps = InputInstance['$props'] & TextareaInstance['$props'];
export default { type TextareaSlots = "7";
render: Textarea,
nodeProps: { export default defineSetter<TextareaProps, TextareaSlots>({
placeholder: "请输入", setter: Textarea,
setterProps: {
placeholder: '请输入',
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,10 +1,13 @@
import { TimePicker, TimePickerInstance } from "@arco-design/web-vue"; import { TimePicker, TimePickerInstance } from '@arco-design/web-vue';
import { defineSetter } from './util';
type Props = TimePickerInstance["$props"]; type TimeProps = TimePickerInstance['$props'];
export default { type TimeSlots = "8";
render: TimePicker,
nodeProps: { export default defineSetter<TimeProps, TimeSlots>({
setter: TimePicker,
setterProps: {
allowClear: true, allowClear: true,
} as Props, },
}; });

View File

@ -1,15 +1,17 @@
import { TreeSelect, TreeSelectInstance } from "@arco-design/web-vue"; import { TreeSelect, TreeSelectInstance } from '@arco-design/web-vue';
import { initOptions } from "../utils/initOptions"; import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
type Props = TreeSelectInstance["$props"]; type TreeSelectProps = TreeSelectInstance['$props'];
export default { type TreeSelectSlots = "9";
render: TreeSelect,
init: (arg: any) => initOptions(arg, "data"), export default defineSetter<TreeSelectProps, TreeSelectSlots>({
nodeProps: { setter: TreeSelect,
placeholder: "请选择", onSetup: (arg: any) => initOptions(arg, 'data') as any,
setterProps: {
placeholder: '请选择',
allowClear: true, allowClear: true,
allowSearch: true, allowSearch: true,
options: [], },
} as Props, });
};

View File

@ -1,16 +1,16 @@
import cascader from "./Cascader"; import cascader from './Cascader';
import custom from "./Custom"; import custom from './Custom';
import date from "./Date"; import date from './Date';
import input from "./Input"; import dateRange from './DateRange';
import number from "./Number"; import input from './Input';
import password from "./Password"; import number from './Number';
import search from "./Search"; import password from './Password';
import select from "./Select"; import search from './Search';
import submit from "./Submit"; import select from './Select';
import textarea from "./Textarea"; import submit from './Submit';
import time from "./Time"; import textarea from './Textarea';
import treeSelect from "./TreeSelect"; import time from './Time';
import dateRange from "./DateRange"; import treeSelect from './TreeSelect';
export default { export default {
input, input,

View File

@ -0,0 +1,39 @@
import { Component } from 'vue';
import { IAnFormItemBase, IAnFormItemSlot } from '../components/FormItem';
export interface ItemSetter<P extends object, S extends string> {
/**
*
*/
setter: Component;
/**
*
*/
setterProps?: P;
/**
*
*/
setterSlots?: {
/**
*
* @example
* ```tsx
* (props) => {
* return <span>{props.item.label}</span>
* }
* ```
*/
[key in S]?: IAnFormItemSlot;
};
/**
*
*/
onSetup?: (model: Recordable, item: IAnFormItemBase, items: IAnFormItemBase[]) => void;
}
export function defineSetter<P extends object, S extends string>(setter: ItemSetter<P, S>) {
return setter;
}

View File

@ -1,27 +1,26 @@
import { Component } from "vue"; import { Component } from 'vue';
import { IAnFormItem, IAnFormItemBase } from "../components/FormItem"; import { IAnFormItemBase, IAnFormItemSlot } from '../components/FormItem';
interface Setter<T extends Component, P = T extends new (...args: any) => any ? InstanceType<T>["$props"] : any> { export interface ItemSetter<P extends object, S extends string> {
/** /**
* *
*/ */
component: T; setter: Component;
/** /**
* *
*/ */
componentProps?: P; setterProps?: P;
/**
*
*/
setterSlots?: {
[key in S]?: IAnFormItemSlot;
};
/** /**
* *
* @param model
* @param item
* @param items
* @returns
*/ */
onSetup?: (model: Recordable, item: IAnFormItemBase, items: IAnFormItemBase[]) => void; onSetup?: (model: Recordable, item: IAnFormItemBase, items: IAnFormItemBase[]) => void;
} }
export function defineSetter<T extends Component>(options: Setter<T>): Setter<T> {
return options;
}

View File

@ -28,7 +28,7 @@ const { component: UpForm, model: emodel } = useForm({
{ {
field: 'xsa', field: 'xsa',
label: '动态渲染', label: '动态渲染',
setter: 'input', setter: 'cascader',
visible: props => props.model.id, visible: props => props.model.id,
}, },
{ {
@ -93,7 +93,7 @@ const { component: UpForm, model: emodel } = useForm({
}, },
}, },
], ],
nodeProps: { setterProps: {
valueKey: 'value', valueKey: 'value',
}, },
}, },