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

View File

@ -59,14 +59,12 @@ export const AnFormItem = defineComponent({
if (Slot) {
return <Slot {...props} />;
}
const Setter = setterMap[props.item.setter as SetterType]?.render as any;
const Setter = setterMap[props.item.setter as SetterType]?.setter as any;
if (!Setter) {
return null;
}
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}
</Setter>
);
@ -152,13 +150,19 @@ export type IAnFormItemBase = {
/**
*
* @description
* @required
* @example
* ```ts
* 'username'
* ```
*/
field: string;
/**
*
* @example '昵称'
* @example
* ```ts
* '昵称'
* ```
*/
label?: string;
@ -170,19 +174,29 @@ export type IAnFormItemBase = {
/**
*
* @example (model) => Boolean(model.id)
* @example
* ```ts
* (props) => Boolean(props.model.id)
* ```
*/
visible?: IAnFormItemBoolFn;
/**
*
* @example (model) => Boolean(model.id)
* @example
* ```ts
* (props) => Boolean(props.model.id)
* ```
*/
disable?: IAnFormItemBoolFn;
/**
*
* @description
* @example
* ```ts
* [{ label: '方式1', value: 1 }]
* ```
*/
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 { PropType } from "vue";
import { IAnFormItem } from "./FormItem";
import { AnForm, IAnFormProps, IAnFormSubmit } from "./Form";
import { useModalTrigger } from "../core/useModalTrigger";
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: {
/**
*
* @default '添加'
* @default '新增'
*/
title: {
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 SetterItem = {
[key in SetterType]: Partial<
Omit<SetterMap[key], 'setter'> & {
/**
*
* @example 'input'
*/
setter: key;
/**
*
*/
setterSlots: Recordable;
/**
*
*/
setterProps: Recordable;
}
>;
}[SetterType];
export type SetterItemMap = {
[key in SetterType]: {
/**
*
* @example
* ```ts
* 'input'
* ```
*/
setter?: key;
/**
*
* @example
* ```tsx
* { type: "password" }
* ```
*/
setterProps?: SetterMap[key]['setterProps'];
/**
*
* @example
* ```tsx
* {
* help: (props) => {
* return <span>
* {props.item.label}
* </span>
* }
* }
* ```
*/
setterSlots?: SetterMap[key]['setterSlots'];
};
};
export type SetterItem = SetterItemMap[SetterType];
export { setterMap };

View File

@ -9,7 +9,7 @@ export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>)
const getItemOptions = (field: string) => {
const item = getItem(field);
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 { FormItem, useItems } from "./useItems";
export type FormUseOptions = Partial<Omit<IAnForm, "items">> & {
/**
*
* @example
* ```ts
* [{
* field: 'name',
* label: '昵称',
* setter: 'input'
* }]
* ```
*/
items?: FormItem[];
};

View File

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

View File

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

View File

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

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 {
render: DatePicker,
nodeProps: {
placeholder: "请选择",
type DateSlots = 'prefix' | 'suffixIcon' | 'iconNextDouble' | 'iconPrevDouble' | 'iconNext' | 'iconPrev' | 'cell' | 'extra';
export default defineSetter<DateProps, DateSlots>({
setter: DatePicker,
setterProps: {
placeholder: '请选择',
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 {
render: RangePicker,
nodeProps: {
type RangeSlots = "1";
export default defineSetter<RangeProps, RangeSlots>({
setter: RangePicker,
setterProps: {
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 {
render: Input,
nodeProps: {
placeholder: "请输入",
type InputSlots = "2";
export default defineSetter<InputProps, InputSlots>({
setter: Input,
setterProps: {
placeholder: '请输入',
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 {
render: InputNumber,
nodeProps: {
placeholder: "请输入",
type NumberSlots = "3";
export default defineSetter<NumberProps, NumberSlots>({
setter: InputNumber,
setterProps: {
placeholder: '请输入',
defaultValue: 0,
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 {
render: InputPassword,
nodeProps: {
placeholder: "请输入",
} as Props,
};
type PasswordSlots = "4";
export default defineSetter<PasswordProps, PasswordSlots>({
setter: InputPassword,
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 {
render: InputSearch,
nodeProps: {
placeholder: "请输入",
type SearchSlots = "5";
export default defineSetter<SearchProps, SearchSlots>({
setter: InputSearch,
setterProps: {
placeholder: '请输入',
allowClear: true,
} as Props,
};
},
});

View File

@ -1,14 +1,18 @@
import { Select, SelectInstance, SelectOptionData } from "@arco-design/web-vue";
import { initOptions } from "../utils/initOptions";
import { Select, SelectInstance } from '@arco-design/web-vue';
import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
export default {
render: Select,
init: initOptions,
nodeProps: {
placeholder: "请选择",
type SelectProps = SelectInstance['$props'];
type SelectSlots = "6";
export default defineSetter<SelectProps, SelectSlots>({
setter: Select,
onSetup: initOptions as any,
setterProps: {
placeholder: '请选择',
allowClear: true,
allowSearch: true,
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 { FormContextKey } from "../core/useFormContext";
import { defineSetter } from "./util";
export default {
render() {
export default defineSetter<{ a1?: number }, "10">({
setter() {
const { loading, submitForm, resetModel } = inject(FormContextKey)!;
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 {
render: Textarea,
nodeProps: {
placeholder: "请输入",
type TextareaSlots = "7";
export default defineSetter<TextareaProps, TextareaSlots>({
setter: Textarea,
setterProps: {
placeholder: '请输入',
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 {
render: TimePicker,
nodeProps: {
type TimeSlots = "8";
export default defineSetter<TimeProps, TimeSlots>({
setter: TimePicker,
setterProps: {
allowClear: true,
} as Props,
};
},
});

View File

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

View File

@ -1,16 +1,16 @@
import cascader from "./Cascader";
import custom from "./Custom";
import date from "./Date";
import input from "./Input";
import number from "./Number";
import password from "./Password";
import search from "./Search";
import select from "./Select";
import submit from "./Submit";
import textarea from "./Textarea";
import time from "./Time";
import treeSelect from "./TreeSelect";
import dateRange from "./DateRange";
import cascader from './Cascader';
import custom from './Custom';
import date from './Date';
import dateRange from './DateRange';
import input from './Input';
import number from './Number';
import password from './Password';
import search from './Search';
import select from './Select';
import submit from './Submit';
import textarea from './Textarea';
import time from './Time';
import treeSelect from './TreeSelect';
export default {
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 { IAnFormItem, IAnFormItemBase } from "../components/FormItem";
import { Component } from 'vue';
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;
}
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',
label: '动态渲染',
setter: 'input',
setter: 'cascader',
visible: props => props.model.id,
},
{
@ -93,7 +93,7 @@ const { component: UpForm, model: emodel } = useForm({
},
},
],
nodeProps: {
setterProps: {
valueKey: 'value',
},
},