feat: 临时提交
parent
51e287c747
commit
72fd7eba25
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Form, FormInstance } from "@arco-design/web-vue";
|
||||||
|
import { PropType } from "vue";
|
||||||
|
import { FormContextKey } from "../core/useFormContext";
|
||||||
|
import { useFormItems } from "../core/useFormItems";
|
||||||
|
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单组件
|
||||||
|
*/
|
||||||
|
export const AnForm = defineComponent({
|
||||||
|
name: "AnForm",
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 表单数据
|
||||||
|
*/
|
||||||
|
model: {
|
||||||
|
type: Object as PropType<Recordable>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items: {
|
||||||
|
type: Array as PropType<IAnFormItem[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
|
submit: {
|
||||||
|
type: Function as PropType<IAnFormSubmit>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传给Form组件的参数
|
||||||
|
*/
|
||||||
|
formProps: {
|
||||||
|
type: Object as IAnFormProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:model"],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const model = useVModel(props, "model", emit);
|
||||||
|
const items = computed(() => props.items);
|
||||||
|
const submit = computed(() => props.submit);
|
||||||
|
const formRefes = useFormRef();
|
||||||
|
const formModel = useFormModel(model, formRefes.clearValidate);
|
||||||
|
const formItems = useFormItems(items, model);
|
||||||
|
const formSubmit = useFormSubmit({ items, model, validate: formRefes.validate, submit }, formModel.getModel);
|
||||||
|
const context = { slots, ...formModel, ...formItems, ...formRefes, ...formSubmit };
|
||||||
|
|
||||||
|
provide(FormContextKey, context);
|
||||||
|
return context;
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Form layout="vertical" {...this.$attrs} {...this.formProps} ref="formRef" model={this.model}>
|
||||||
|
{this.items.map((item) => (
|
||||||
|
<AnFormItem key={item.field} item={item} items={this.items} model={this.model}></AnFormItem>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AnFormInstance = InstanceType<typeof AnForm>;
|
||||||
|
|
||||||
|
export type AnFormProps = AnFormInstance["$props"];
|
||||||
|
|
||||||
|
export type IAnFormProps = PropType<Omit<FormInstance["$props"], "model">>;
|
||||||
|
|
||||||
|
export type IAnForm = Pick<AnFormProps, "model" | "items" | "submit" | "formProps">;
|
||||||
|
|
||||||
|
export type IAnFormSubmit = (model: Recordable, items: IAnFormItem) => any;
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { FormItem as BaseFormItem, FieldRule, FormItemInstance } from "@arco-design/web-vue";
|
||||||
|
import { isFunction } from "lodash-es";
|
||||||
|
import { PropType } from "vue";
|
||||||
|
import { SetterItem, SetterType, setterMap } from "./FormSetter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
export const AnFormItem = defineComponent({
|
||||||
|
name: "AnFormItem",
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
item: {
|
||||||
|
type: Object as PropType<IAnFormItem>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单项数组
|
||||||
|
*/
|
||||||
|
items: {
|
||||||
|
type: Array as PropType<IAnFormItem[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单数据
|
||||||
|
*/
|
||||||
|
model: {
|
||||||
|
type: Object as PropType<Recordable>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const rules = computed(() => props.item.rules?.filter((i) => !i.disable?.(props.model, props.item, props.items)));
|
||||||
|
const disabled = computed(() => Boolean(props.item.disable?.(props.model, props.item, props.items)));
|
||||||
|
const label = strOrFnRender(props.item.label, props);
|
||||||
|
const help = strOrFnRender(props.item.help, props);
|
||||||
|
const extra = strOrFnRender(props.item.extra, props);
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
let render = props.item.setter as any;
|
||||||
|
if (!render) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof render === "string") {
|
||||||
|
render = setterMap[render as SetterType]?.render;
|
||||||
|
if (!render) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <render {...props.item.nodeProps} v-model={props.model[props.item.field]} />;
|
||||||
|
}
|
||||||
|
if (isFunction(render)) {
|
||||||
|
return <render {...props.item.nodeProps} items={props.items} model={props.model} item={props.item} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (props.item.visible && !props.item.visible(props.model, props.item, props.items)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<BaseFormItem {...props.item.itemProps} rules={rules.value} disabled={disabled.value} field={props.item.field}>
|
||||||
|
{{ default: render, label, help, extra }}
|
||||||
|
</BaseFormItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function strOrFnRender(fn: any, ...args: any[]) {
|
||||||
|
if (typeof fn === "string") {
|
||||||
|
return () => fn;
|
||||||
|
}
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
return fn(...args);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IAnFormItemBoolFn = (model: Recordable, item: IAnFormItem, items: IAnFormItem[]) => boolean;
|
||||||
|
|
||||||
|
export type IAnFormItemElemFn = (model: Recordable, item: IAnFormItem, items: IAnFormItem[]) => any;
|
||||||
|
|
||||||
|
export type IAnFormItemRule = FieldRule & { disable?: IAnFormItemBoolFn };
|
||||||
|
|
||||||
|
export type IAnFormItemBase = {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
* @description 请保持唯一,支持特殊语法
|
||||||
|
* @required
|
||||||
|
*/
|
||||||
|
field: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 透传的表单项参数
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
itemProps?: Partial<Omit<FormItemInstance["$props"], "field" | "label" | "required" | "rules" | "disabled">>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验规则
|
||||||
|
*/
|
||||||
|
rules?: IAnFormItemRule[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可见
|
||||||
|
*/
|
||||||
|
visible?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
*/
|
||||||
|
disable?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签名
|
||||||
|
*/
|
||||||
|
label?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帮助提示
|
||||||
|
*/
|
||||||
|
help?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 额外内容
|
||||||
|
*/
|
||||||
|
extra?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
options?: any;
|
||||||
|
|
||||||
|
init?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IAnFormItem = IAnFormItemBase & SetterItem;
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import { Button, ButtonInstance, Form, FormInstance, Modal } from "@arco-design/web-vue";
|
||||||
|
import { PropType } from "vue";
|
||||||
|
import { FormContextKey } from "../core/useFormContext";
|
||||||
|
import { useFormItems } from "../core/useFormItems";
|
||||||
|
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";
|
||||||
|
import { AnForm, IAnFormProps, IAnFormSubmit } from "./Form";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单组件
|
||||||
|
*/
|
||||||
|
export const AnFormModal = defineComponent({
|
||||||
|
name: "AnFormModal",
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 弹窗标题
|
||||||
|
* @default '添加'
|
||||||
|
*/
|
||||||
|
title: {
|
||||||
|
type: [String, Function] as PropType<ModalType>,
|
||||||
|
default: "添加",
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触发元素
|
||||||
|
*/
|
||||||
|
trigger: {
|
||||||
|
type: [Boolean, Function, Object] as PropType<ModalTrigger>,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传递给Modal组件的props
|
||||||
|
*/
|
||||||
|
modalProps: {
|
||||||
|
type: Object as PropType<ModalProps>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单数据
|
||||||
|
*/
|
||||||
|
model: {
|
||||||
|
type: Object as PropType<Recordable>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items: {
|
||||||
|
type: Array as PropType<IAnFormItem[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
|
submit: {
|
||||||
|
type: Function as PropType<IAnFormSubmit>,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传给Form组件的参数
|
||||||
|
*/
|
||||||
|
formProps: {
|
||||||
|
type: Object as IAnFormProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["update:model"],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
const visible = ref(false);
|
||||||
|
const formRef = ref<InstanceType<typeof AnForm> | null>(null);
|
||||||
|
|
||||||
|
const modalTitle = () => {
|
||||||
|
if (typeof props.title === "string") {
|
||||||
|
return props.title;
|
||||||
|
}
|
||||||
|
return <props.title model={props.model} items={props.items}></props.title>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalTrigger = () => {
|
||||||
|
if (!props.trigger) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof props.trigger === "boolean") {
|
||||||
|
return <Button onClick={() => (visible.value = true)}>打开</Button>;
|
||||||
|
}
|
||||||
|
if (typeof props.trigger === "object") {
|
||||||
|
return (
|
||||||
|
<Button {...props.trigger} onClick={() => (visible.value = true)}>
|
||||||
|
打开
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <props.trigger model={props.model} items={props.items}></props.trigger>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
visible,
|
||||||
|
formRef,
|
||||||
|
modalTitle,
|
||||||
|
modalTrigger,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<this.modalTrigger></this.modalTrigger>
|
||||||
|
<Modal titleAlign="start" closable={false} {...this.$attrs} {...this.modalProps} v-model:visible={this.visible}>
|
||||||
|
{{
|
||||||
|
title: this.modalTitle,
|
||||||
|
default: () => (
|
||||||
|
<AnForm
|
||||||
|
model={this.model}
|
||||||
|
onUpdate:model={(v) => this.$emit("update:model", v)}
|
||||||
|
items={this.items}
|
||||||
|
submit={this.submit}
|
||||||
|
formProps={this.formProps}
|
||||||
|
></AnForm>
|
||||||
|
),
|
||||||
|
...this.$slots,
|
||||||
|
}}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type ModalProps = Omit<InstanceType<typeof Modal>["$props"], "visible" | "title" | "onBeforeOk">;
|
||||||
|
|
||||||
|
type ModalType = string | ((model: Recordable, items: IAnFormItem[]) => any);
|
||||||
|
|
||||||
|
type ModalTrigger =
|
||||||
|
| boolean
|
||||||
|
| ((model: Recordable, items: IAnFormItem[]) => any)
|
||||||
|
| {
|
||||||
|
text?: string;
|
||||||
|
buttonProps?: ButtonInstance["$props"];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormModalProps = Pick<
|
||||||
|
InstanceType<typeof AnFormModal>["$props"],
|
||||||
|
"title" | "trigger" | "modalProps" | "model" | "items" | "submit" | "formProps"
|
||||||
|
>;
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import setterMap from "../setters";
|
||||||
|
|
||||||
|
export type SetterMap = typeof setterMap;
|
||||||
|
|
||||||
|
export type SetterType = keyof SetterMap;
|
||||||
|
|
||||||
|
export type SetterItem = {
|
||||||
|
[key in SetterType]: Partial<
|
||||||
|
Omit<SetterMap[key], "setter"> & {
|
||||||
|
/**
|
||||||
|
* 组件类型
|
||||||
|
*/
|
||||||
|
setter: key;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}[SetterType];
|
||||||
|
|
||||||
|
export { setterMap };
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { InjectionKey } from "vue";
|
||||||
|
import { FormItems } from "./useFormItems";
|
||||||
|
import { FormModel } from "./useFormModel";
|
||||||
|
import { FormRef } from "./useFormRef";
|
||||||
|
import { FormSubmit } from "./useFormSubmit";
|
||||||
|
|
||||||
|
export type FormContextInterface = FormModel &
|
||||||
|
FormItems &
|
||||||
|
FormRef &
|
||||||
|
FormSubmit & {
|
||||||
|
slots: Recordable;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormContextKey = Symbol("FormContextKey") as InjectionKey<FormContextInterface>;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Ref } from "vue";
|
||||||
|
import { IAnFormItem } from "../components/FormItem";
|
||||||
|
|
||||||
|
export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>) {
|
||||||
|
const getItem = (field: string) => {
|
||||||
|
return items.value.find((i) => i.field === field);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemOptions = (field: string) => {
|
||||||
|
const item = getItem(field);
|
||||||
|
if (item) {
|
||||||
|
return (item.nodeProps as any)?.options;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initItemOptions = (field: string) => {
|
||||||
|
const item = getItem(field);
|
||||||
|
item && item.init?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const initItems = () => {
|
||||||
|
for (const item of items.value) {
|
||||||
|
item.init?.({ item, model: model.value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initItem = (field: string) => {
|
||||||
|
const item = getItem(field);
|
||||||
|
item && item.init?.({ item, model: model.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initItems();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
getItem,
|
||||||
|
initItem,
|
||||||
|
initItems,
|
||||||
|
getItemOptions,
|
||||||
|
initItemOptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormItems = ReturnType<typeof useFormItems>;
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { cloneDeep } from "lodash-es";
|
||||||
|
import { Ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单数据管理
|
||||||
|
* @param initial 初始值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useFormModel(model: Ref<Recordable>, clearValidate: any) {
|
||||||
|
const initial = cloneDeep(model.value);
|
||||||
|
|
||||||
|
const resetModel = () => {
|
||||||
|
model.value = cloneDeep(initial);
|
||||||
|
clearValidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialModel = () => {
|
||||||
|
return initial;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setModel = (data: Recordable) => {
|
||||||
|
for (const key of Object.keys(model.value)) {
|
||||||
|
model.value[key] = data[key];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getModel = () => {
|
||||||
|
return formatModel(model.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
model,
|
||||||
|
getInitialModel,
|
||||||
|
resetModel,
|
||||||
|
setModel,
|
||||||
|
getModel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormModel = ReturnType<typeof useFormModel>;
|
||||||
|
|
||||||
|
export function formatModel(model: Recordable) {
|
||||||
|
const data: Recordable = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(model)) {
|
||||||
|
if (/^\[.+\]$/.test(key)) {
|
||||||
|
formatModelArray(key, value, data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^\{.+\}$/.test(key)) {
|
||||||
|
formatModelObject(key, value, data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatModelArray(key: string, value: any, data: Recordable) {
|
||||||
|
let field = key.replaceAll(/\s/g, "");
|
||||||
|
field = field.match(/^\[(.+)\]$/)?.[1] ?? "";
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
data[key] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = field.split(",");
|
||||||
|
keys.forEach((k, i) => {
|
||||||
|
if (/(.+)?:number$/.test(k)) {
|
||||||
|
k = k.replace(/:number$/, "");
|
||||||
|
data[k] = value?.[i] && Number(value[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (/(.+)?:boolean$/.test(k)) {
|
||||||
|
k = k.replace(/:boolean$/, "");
|
||||||
|
data[k] = value?.[i] && Boolean(value[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[k] = value?.[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatModelObject(key: string, value: any, data: Recordable) {
|
||||||
|
let field = key.replaceAll(/\s/g, "");
|
||||||
|
field = field.match(/^\{(.+)\}$/)?.[1] ?? "";
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
data[key] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = field.split(",");
|
||||||
|
keys.forEach((k, i) => {
|
||||||
|
if (/(.+)?:number$/.test(k)) {
|
||||||
|
k = k.replace(/:number$/, "");
|
||||||
|
data[k] = value?.[i] && Number(value[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (/(.+)?:boolean$/.test(k)) {
|
||||||
|
k = k.replace(/:boolean$/, "");
|
||||||
|
data[k] = value?.[i] && Boolean(value[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[k] = value?.[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { FormInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
export function useFormRef() {
|
||||||
|
/**
|
||||||
|
* 原始表单实例
|
||||||
|
*/
|
||||||
|
const formRef = ref<FormInstance | null>(null);
|
||||||
|
|
||||||
|
type Validate = FormInstance["validate"];
|
||||||
|
type ValidateField = FormInstance["validateField"];
|
||||||
|
type ResetFields = FormInstance["resetFields"];
|
||||||
|
type ClearValidate = FormInstance["clearValidate"];
|
||||||
|
type SetFields = FormInstance["setFields"];
|
||||||
|
type ScrollToField = FormInstance["scrollToField"];
|
||||||
|
|
||||||
|
const validate: Validate = async (...args) => formRef.value?.validate(...args);
|
||||||
|
const validateField: ValidateField = async (...args) => formRef.value?.validateField(...args);
|
||||||
|
const resetFields: ResetFields = (...args) => formRef.value?.resetFields(...args);
|
||||||
|
const clearValidate: ClearValidate = (...args) => formRef.value?.clearValidate(...args);
|
||||||
|
const setFields: SetFields = (...args) => formRef.value?.setFields(...args);
|
||||||
|
const scrollToField: ScrollToField = (...args) => formRef.value?.scrollToField(...args);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formRef,
|
||||||
|
validate,
|
||||||
|
validateField,
|
||||||
|
resetFields,
|
||||||
|
clearValidate,
|
||||||
|
setFields,
|
||||||
|
scrollToField,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormRef = ReturnType<typeof useFormRef>;
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { FormInstance, Message } from "@arco-design/web-vue";
|
||||||
|
import { Ref } from "vue";
|
||||||
|
import { IAnFormItem } from "../components/FormItem";
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
items: Ref<IAnFormItem[]>;
|
||||||
|
model: Ref<Recordable>;
|
||||||
|
submit: Ref<Function | undefined>;
|
||||||
|
validate: FormInstance["validate"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFormSubmit(options: Options, getModel: any) {
|
||||||
|
const { items, submit, validate } = options;
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置loading
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
const setLoading = (value: boolean) => {
|
||||||
|
loading.value = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
|
const submitForm = async () => {
|
||||||
|
if (await validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const data = getModel();
|
||||||
|
const res = await submit.value?.(data, items.value);
|
||||||
|
const msg = res?.data?.message;
|
||||||
|
msg && Message.success(`提示: ${msg}`);
|
||||||
|
} catch {
|
||||||
|
console.log();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消提交
|
||||||
|
*/
|
||||||
|
const cancelForm = () => {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
setLoading,
|
||||||
|
submitForm,
|
||||||
|
cancelForm,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormSubmit = ReturnType<typeof useFormSubmit>;
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { FormItem, useItems } from "./useItems";
|
||||||
|
import { AnForm, IAnForm } from "../components/Form";
|
||||||
|
|
||||||
|
export type UseForm = Partial<Omit<IAnForm, "items">> & {
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items?: FormItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建表单组件的参数
|
||||||
|
*/
|
||||||
|
export const useForm = (options: UseForm) => {
|
||||||
|
const { items: _items = [], model: _model = {}, submit, formProps: _props = {} } = options;
|
||||||
|
const items = useItems(_items, _model, Boolean(options.submit));
|
||||||
|
const model = ref(_model);
|
||||||
|
const formProps = ref(_props);
|
||||||
|
const instance = ref<InstanceType<typeof AnForm> | null>(null);
|
||||||
|
|
||||||
|
const component = () => {
|
||||||
|
const onUpdateModel = (value: Recordable) => {
|
||||||
|
model.value = value;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AnForm
|
||||||
|
ref={(el: any) => (instance.value = el)}
|
||||||
|
model={model.value}
|
||||||
|
onUpdate:model={onUpdateModel}
|
||||||
|
items={items.value}
|
||||||
|
submit={submit}
|
||||||
|
formProps={formProps.value}
|
||||||
|
></AnForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
instance,
|
||||||
|
model,
|
||||||
|
items,
|
||||||
|
formProps,
|
||||||
|
submit,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { AnFormModal, FormModalProps } from "../components/FormModal";
|
||||||
|
import { UseForm, useForm } from "./useForm";
|
||||||
|
import { FormItem } from "./useItems";
|
||||||
|
|
||||||
|
type FormModalUseOptions = Partial<Omit<FormModalProps, "items">> & {
|
||||||
|
items: FormItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useFormModal(options: FormModalUseOptions) {
|
||||||
|
const { model, items, formProps } = useForm({ ...options, submit: undefined });
|
||||||
|
const trigger = ref(options.trigger);
|
||||||
|
const title = ref(options.title);
|
||||||
|
const modalProps = ref(options.modalProps);
|
||||||
|
const modalRef = ref<InstanceType<typeof AnFormModal> | null>(null);
|
||||||
|
const formRef = computed(() => modalRef.value?.formRef);
|
||||||
|
|
||||||
|
const component = () => {
|
||||||
|
return (
|
||||||
|
<AnFormModal
|
||||||
|
ref={(el: any) => (modalRef.value = el)}
|
||||||
|
title={title.value}
|
||||||
|
trigger={trigger.value}
|
||||||
|
modalProps={modalProps.value}
|
||||||
|
model={model.value}
|
||||||
|
items={items.value}
|
||||||
|
formProps={formProps.value}
|
||||||
|
></AnFormModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
model,
|
||||||
|
items,
|
||||||
|
formProps,
|
||||||
|
component,
|
||||||
|
modalRef,
|
||||||
|
formRef,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { defaultsDeep, merge, omit } from "lodash-es";
|
||||||
|
import { Rule, useRules } from "./useRules";
|
||||||
|
import { IAnFormItem } from "../components/FormItem";
|
||||||
|
import { setterMap } from "../components/FormSetter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单项数据
|
||||||
|
*/
|
||||||
|
export type FormItem = Omit<IAnFormItem, "rules"> & {
|
||||||
|
/**
|
||||||
|
* 默认值
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
value?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否必填
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
required?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验规则
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
rules?: Rule[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ITEM: Partial<FormItem> = {
|
||||||
|
setter: "input",
|
||||||
|
itemProps: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUBMIT_ITEM: FormItem = {
|
||||||
|
field: "id",
|
||||||
|
setter: "submit",
|
||||||
|
itemProps: {
|
||||||
|
hideLabel: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
|
||||||
|
const items = ref<IAnFormItem[]>([]);
|
||||||
|
let hasSubmit = false;
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
|
let target: any = defaultsDeep({}, ITEM);
|
||||||
|
|
||||||
|
if (!item.setter || typeof item.setter === "string") {
|
||||||
|
const defaults = setterMap[item.setter ?? "input"];
|
||||||
|
if (defaults) {
|
||||||
|
defaultsDeep(target, defaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.setter === "submit") {
|
||||||
|
target = merge(target, SUBMIT_ITEM);
|
||||||
|
hasSubmit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = merge(target, omit(item, ["required", "rules"]));
|
||||||
|
|
||||||
|
const rules = useRules(item);
|
||||||
|
if (rules) {
|
||||||
|
target.rules = rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
model[item.field] = model[item.field] ?? item.value;
|
||||||
|
items.value.push(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (submit && !hasSubmit) {
|
||||||
|
items.value.push(defaultsDeep({}, SUBMIT_ITEM, setterMap.submit));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { FieldRule } from "@arco-design/web-vue";
|
||||||
|
import { has, isString } from "lodash-es";
|
||||||
|
import { IAnFormItemRule } from "../components/FormItem";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置规则
|
||||||
|
*/
|
||||||
|
export const FieldRuleMap = defineRuleMap({
|
||||||
|
required: {
|
||||||
|
required: true,
|
||||||
|
message: "该项不能为空",
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
type: "string",
|
||||||
|
message: "请输入字符串",
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
type: "number",
|
||||||
|
message: "请输入数字",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: "email",
|
||||||
|
message: "邮箱格式错误,示例: xx@abc.com",
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: "url",
|
||||||
|
message: "URL格式错误, 示例: www.abc.com",
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
type: "ip",
|
||||||
|
message: "IP格式错误, 示例: 101.10.10.30",
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
match: /^(?:(?:\+|00)86)?1\d{10}$/,
|
||||||
|
message: "手机格式错误, 示例(11位): 15912345678",
|
||||||
|
},
|
||||||
|
idcard: {
|
||||||
|
match: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/,
|
||||||
|
message: "身份证格式错误, 长度为15或18位",
|
||||||
|
},
|
||||||
|
alphabet: {
|
||||||
|
match: /^[a-zA-Z]\w{4,15}$/,
|
||||||
|
message: "请输入英文字母, 长度为4~15位",
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
match: /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/,
|
||||||
|
message: "至少包含大写字母、小写字母、数字和特殊字符",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串形式(枚举)
|
||||||
|
*/
|
||||||
|
export type FieldStringRule = keyof typeof FieldRuleMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整类型
|
||||||
|
*/
|
||||||
|
export type Rule = FieldStringRule | IAnFormItemRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助手函数(获得TS提示)
|
||||||
|
*/
|
||||||
|
function defineRuleMap<T extends Record<string, FieldRule>>(ruleMap: T) {
|
||||||
|
return ruleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表单规则
|
||||||
|
* @param item 表单项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useRules = <T extends { required?: boolean; rules?: Rule[] }>(item: T) => {
|
||||||
|
const data: IAnFormItemRule[] = [];
|
||||||
|
const { required, rules } = item;
|
||||||
|
|
||||||
|
if (!has(item, "required") && !has(item, "rules")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required) {
|
||||||
|
data.push(FieldRuleMap.required);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rule of rules ?? []) {
|
||||||
|
if (isString(rule)) {
|
||||||
|
if (FieldRuleMap[rule]) {
|
||||||
|
data.push(FieldRuleMap[rule]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.push(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./components/Form";
|
||||||
|
export * from "./hooks/useForm";
|
||||||
|
export * from "./core/useFormContext";
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Cascader, CascaderInstance } from "@arco-design/web-vue";
|
||||||
|
import { initOptions } from "../utils/initOptions";
|
||||||
|
|
||||||
|
type Props = CascaderInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: Cascader,
|
||||||
|
init: initOptions,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请选择",
|
||||||
|
allowClear: true,
|
||||||
|
expandTrigger: "hover",
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
render: () => {
|
||||||
|
return "1";
|
||||||
|
},
|
||||||
|
nodeProps: {},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { DatePicker, DatePickerInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = DatePickerInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: DatePicker,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请选择",
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { RangePicker, RangePickerInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = RangePickerInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: RangePicker,
|
||||||
|
nodeProps: {
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Input, InputInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = InputInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: Input,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请输入",
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { InputInstance, InputNumber, InputNumberInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = InputInstance["$props"] & InputNumberInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: InputNumber,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请输入",
|
||||||
|
defaultValue: 0,
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { InputInstance, InputPassword, InputPasswordInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = InputInstance["$props"] & InputPasswordInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: InputPassword,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请输入",
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { InputInstance, InputSearch, InputSearchInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = InputInstance["$props"] & InputSearchInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: InputSearch,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请输入",
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Select, SelectInstance, SelectOptionData } from "@arco-design/web-vue";
|
||||||
|
import { initOptions } from "../utils/initOptions";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: Select,
|
||||||
|
init: initOptions,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请选择",
|
||||||
|
allowClear: true,
|
||||||
|
allowSearch: true,
|
||||||
|
options: [],
|
||||||
|
} as SelectInstance["$props"],
|
||||||
|
options: [] as SelectOptionData[] | ((arg: any) => Recordable[] | Promise<Recordable[]>) | undefined,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Button } from "@arco-design/web-vue";
|
||||||
|
import { FormContextKey } from "../core/useFormContext";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render() {
|
||||||
|
const { loading, submitForm, resetModel } = inject(FormContextKey)!;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type="primary" loading={loading.value} onClick={submitForm} class="mr-3">
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
|
<Button disabled={loading.value} onClick={resetModel}>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
nodeProps: {},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { InputInstance, Textarea, TextareaInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = InputInstance["$props"] & TextareaInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: Textarea,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请输入",
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { TimePicker, TimePickerInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = TimePickerInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: TimePicker,
|
||||||
|
nodeProps: {
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { TreeSelect, TreeSelectInstance } from "@arco-design/web-vue";
|
||||||
|
import { initOptions } from "../utils/initOptions";
|
||||||
|
|
||||||
|
type Props = TreeSelectInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: TreeSelect,
|
||||||
|
init: (arg: any) => initOptions(arg, "data"),
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: "请选择",
|
||||||
|
allowClear: true,
|
||||||
|
allowSearch: true,
|
||||||
|
options: [],
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input,
|
||||||
|
number,
|
||||||
|
search,
|
||||||
|
textarea,
|
||||||
|
select,
|
||||||
|
treeSelect,
|
||||||
|
time,
|
||||||
|
password,
|
||||||
|
cascader,
|
||||||
|
date,
|
||||||
|
submit,
|
||||||
|
custom,
|
||||||
|
dateRange,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Component } from "vue";
|
||||||
|
import { IAnFormItem, IAnFormItemBase } from "../components/FormItem";
|
||||||
|
|
||||||
|
interface Setter<T extends Component, P = T extends new (...args: any) => any ? InstanceType<T>["$props"] : any> {
|
||||||
|
/**
|
||||||
|
* 输入组件
|
||||||
|
*/
|
||||||
|
component: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入组件参数
|
||||||
|
*/
|
||||||
|
componentProps?: P;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化钩子
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
export function initOptions({ item, model }: any, key = "options") {
|
||||||
|
if (Array.isArray(item.options)) {
|
||||||
|
item.nodeProps[key] = item.options;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.options && typeof item.options === "object") {
|
||||||
|
const { value, source } = item.options;
|
||||||
|
item._updateOptions = async () => {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof item.options === "function") {
|
||||||
|
const loadData = item.options;
|
||||||
|
item.nodeProps[key] = reactive([]);
|
||||||
|
item._updateOptions = async () => {
|
||||||
|
let data = await loadData({ item, model });
|
||||||
|
if (Array.isArray(data?.data?.data)) {
|
||||||
|
data = data.data.data.map((i: any) => ({
|
||||||
|
...i,
|
||||||
|
label: i.name,
|
||||||
|
value: i.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
item.nodeProps[key].splice(0);
|
||||||
|
item.nodeProps[key].push(...data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
item._updateOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export function strOrFnRender(fn: any, options: any) {
|
||||||
|
if (typeof fn === "string") {
|
||||||
|
return () => fn;
|
||||||
|
}
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
return fn(options);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ 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 { SubmitFn } from "./types/Form";
|
import { SubmitFn } from "./types/Form";
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单组件
|
* 表单组件
|
||||||
|
|
@ -32,7 +33,7 @@ export const AnForm = defineComponent({
|
||||||
* 提交表单
|
* 提交表单
|
||||||
*/
|
*/
|
||||||
submit: {
|
submit: {
|
||||||
type: Function as PropType<SubmitFn>,
|
type: Function as PropType<IAnFormSubmit>,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 传给Form组件的参数
|
* 传给Form组件的参数
|
||||||
|
|
@ -41,22 +42,16 @@ export const AnForm = defineComponent({
|
||||||
type: Object as PropType<Omit<BaseFormInstance["$props"], "model">>,
|
type: Object as PropType<Omit<BaseFormInstance["$props"], "model">>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
emits: ["update:model"],
|
||||||
const model = computed(() => props.model);
|
setup(props, { slots, emit }) {
|
||||||
|
const model = useVModel(props, "model", emit);
|
||||||
const items = computed(() => props.items);
|
const items = computed(() => props.items);
|
||||||
const submit = computed(() => props.submit);
|
const submit = computed(() => props.submit);
|
||||||
const formRefes = useFormRef();
|
const formRefes = useFormRef();
|
||||||
const formModel = useFormModel(model);
|
const formModel = useFormModel(model, formRefes.clearValidate);
|
||||||
const formItems = useFormItems(items, model);
|
const formItems = useFormItems(items, model);
|
||||||
const formSubmit = useFormSubmit({ items, model, validate: formRefes.validate, submit });
|
const formSubmit = useFormSubmit({ items, model, validate: formRefes.validate, submit }, formModel.getModel);
|
||||||
|
const context = { slots, ...formModel, ...formItems, ...formRefes, ...formSubmit };
|
||||||
const context = {
|
|
||||||
slots,
|
|
||||||
...formModel,
|
|
||||||
...formItems,
|
|
||||||
...formRefes,
|
|
||||||
...formSubmit,
|
|
||||||
};
|
|
||||||
|
|
||||||
provide(FormContextKey, context);
|
provide(FormContextKey, context);
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -65,7 +60,7 @@ export const AnForm = defineComponent({
|
||||||
return (
|
return (
|
||||||
<BaseForm layout="vertical" {...this.$attrs} {...this.formProps} ref="formRef" model={this.model}>
|
<BaseForm layout="vertical" {...this.$attrs} {...this.formProps} ref="formRef" model={this.model}>
|
||||||
{this.items.map((item) => (
|
{this.items.map((item) => (
|
||||||
<AnFormItem item={item} items={this.items} model={this.model}></AnFormItem>
|
<AnFormItem key={item.field} item={item} items={this.items} model={this.model}></AnFormItem>
|
||||||
))}
|
))}
|
||||||
</BaseForm>
|
</BaseForm>
|
||||||
);
|
);
|
||||||
|
|
@ -77,3 +72,5 @@ export type AnFormInstance = InstanceType<typeof AnForm>;
|
||||||
export type AnFormProps = AnFormInstance["$props"];
|
export type AnFormProps = AnFormInstance["$props"];
|
||||||
|
|
||||||
export type IAnForm = Pick<AnFormProps, "model" | "items" | "submit" | "formProps">;
|
export type IAnForm = Pick<AnFormProps, "model" | "items" | "submit" | "formProps">;
|
||||||
|
|
||||||
|
export type IAnFormSubmit = (model: Recordable, items: IAnFormItem) => any;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { FormItem as BaseFormItem, FormItemInstance } from "@arco-design/web-vue";
|
import { FormItem as BaseFormItem, FieldRule, FormItemInstance } from "@arco-design/web-vue";
|
||||||
import { isFunction } from "lodash-es";
|
import { isFunction } from "lodash-es";
|
||||||
import { PropType } from "vue";
|
import { PropType } from "vue";
|
||||||
import { FieldObjectRule } from "../hooks/useRules";
|
|
||||||
import { NodeType, NodeUnion, nodeMap } from "../nodes";
|
import { NodeType, NodeUnion, nodeMap } from "../nodes";
|
||||||
import { strOrFnRender } from "../utils/strOrFnRender";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单项
|
* 表单项
|
||||||
|
|
@ -34,19 +32,12 @@ export const AnFormItem = defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
/**
|
const rules = computed(() => props.item.rules?.filter((i) => !i.disable?.(props.model, props.item, props.items)));
|
||||||
* 校验规则
|
const disabled = computed(() => Boolean(props.item.disable?.(props.model, props.item, props.items)));
|
||||||
*/
|
const label = strOrFnRender(props.item.label, props);
|
||||||
const rules = computed(() => props.item.rules?.filter((i) => !i.disable?.(props)));
|
const help = strOrFnRender(props.item.help, props);
|
||||||
|
const extra = strOrFnRender(props.item.extra, props);
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否禁用
|
|
||||||
*/
|
|
||||||
const disabled = computed(() => Boolean(props.item.disable?.(props)));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染函数
|
|
||||||
*/
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
let render = (props.item as any).render;
|
let render = (props.item as any).render;
|
||||||
if (!render) {
|
if (!render) {
|
||||||
|
|
@ -64,23 +55,8 @@ export const AnFormItem = defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 标签渲染
|
|
||||||
*/
|
|
||||||
const label = strOrFnRender(props.item.label, props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 帮助信息渲染函数
|
|
||||||
*/
|
|
||||||
const help = strOrFnRender(props.item.help, props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 额外信息渲染函数
|
|
||||||
*/
|
|
||||||
const extra = strOrFnRender(props.item.extra, props);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (props.item.visible && !props.item.visible(props)) {
|
if (props.item.visible && !props.item.visible(props.model, props.item, props.items)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
@ -92,13 +68,23 @@ export const AnFormItem = defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormItemFnArg<T = IAnFormItem> = {
|
export function strOrFnRender(fn: any, ...args: any[]) {
|
||||||
item: T;
|
if (typeof fn === "string") {
|
||||||
items: T[];
|
return () => fn;
|
||||||
model: Record<string, any>;
|
}
|
||||||
};
|
if (typeof fn === "function") {
|
||||||
|
return fn(...args);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
type FormItemBase = {
|
export type IAnFormItemBoolFn = (model: Recordable, item: IAnFormItem, items: IAnFormItem[]) => boolean;
|
||||||
|
|
||||||
|
export type IAnFormItemElemFn = (model: Recordable, item: IAnFormItem, items: IAnFormItem[]) => any;
|
||||||
|
|
||||||
|
export type IAnFormItemRule = FieldRule & { disable?: IAnFormItemBoolFn };
|
||||||
|
|
||||||
|
export type IAnFormItemBase = {
|
||||||
/**
|
/**
|
||||||
* 字段名,用于表单、校验和输入框绑定,支持特殊语法。
|
* 字段名,用于表单、校验和输入框绑定,支持特殊语法。
|
||||||
*/
|
*/
|
||||||
|
|
@ -112,32 +98,34 @@ type FormItemBase = {
|
||||||
/**
|
/**
|
||||||
* 校验规则数组
|
* 校验规则数组
|
||||||
*/
|
*/
|
||||||
rules?: FieldObjectRule<IAnFormItem>[];
|
rules?: IAnFormItemRule[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否可见
|
* 是否可见
|
||||||
*/
|
*/
|
||||||
visible?: (arg: FormItemFnArg) => boolean;
|
visible?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否禁用
|
* 是否禁用
|
||||||
*/
|
*/
|
||||||
disable?: (arg: FormItemFnArg) => boolean;
|
disable?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签名
|
* 标签名
|
||||||
*/
|
*/
|
||||||
label?: string | ((args: FormItemFnArg) => any);
|
label?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 帮助提示
|
* 帮助提示
|
||||||
*/
|
*/
|
||||||
help?: string | ((args: FormItemFnArg) => any);
|
help?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 额外内容
|
* 额外内容
|
||||||
*/
|
*/
|
||||||
extra?: string | ((args: FormItemFnArg) => any);
|
extra?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
init?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IAnFormItem = FormItemBase & NodeUnion;
|
export type IAnFormItem = IAnFormItemBase & NodeUnion;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { InjectionKey } from "vue";
|
||||||
|
import { FormItems } from "./useFormItems";
|
||||||
|
import { FormModel } from "./useFormModel";
|
||||||
|
import { FormRef } from "./useFormRef";
|
||||||
|
import { FormSubmit } from "./useFormSubmit";
|
||||||
|
|
||||||
|
export type FormContextInterface = FormModel &
|
||||||
|
FormItems &
|
||||||
|
FormRef &
|
||||||
|
FormSubmit & {
|
||||||
|
slots: Recordable;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormContextKey = Symbol("FormContextKey") as InjectionKey<FormContextInterface>;
|
||||||
|
|
||||||
|
export function useFormContext() {
|
||||||
|
const context = inject(FormContextKey);
|
||||||
|
if (!context) {
|
||||||
|
throw Error("useFormContext musb be used in AnForm children!");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
@ -15,18 +15,18 @@ export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>)
|
||||||
|
|
||||||
const initItemOptions = (field: string) => {
|
const initItemOptions = (field: string) => {
|
||||||
const item = getItem(field);
|
const item = getItem(field);
|
||||||
item && item.initial?.();
|
item && item.init?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
const initItems = () => {
|
const initItems = () => {
|
||||||
for (const item of items.value) {
|
for (const item of items.value) {
|
||||||
item.initial?.(item, model);
|
item.init?.({ item, model: model.value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initItem = (field: string) => {
|
const initItem = (field: string) => {
|
||||||
const item = getItem(field);
|
const item = getItem(field);
|
||||||
item && item.initial(item, model);
|
item && item.init?.({ item, model: model.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ import { Ref } from "vue";
|
||||||
* @param initial 初始值
|
* @param initial 初始值
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useFormModel(model: Ref<Recordable>) {
|
export function useFormModel(model: Ref<Recordable>, clearValidate: any) {
|
||||||
const initial = cloneDeep(model.value);
|
const initial = cloneDeep(model.value);
|
||||||
|
|
||||||
const resetModel = () => {
|
const resetModel = () => {
|
||||||
model.value = cloneDeep(initial);
|
model.value = cloneDeep(initial);
|
||||||
|
clearValidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInitialModel = () => {
|
const getInitialModel = () => {
|
||||||
|
|
@ -38,7 +39,7 @@ export function useFormModel(model: Ref<Recordable>) {
|
||||||
|
|
||||||
export type FormModel = ReturnType<typeof useFormModel>;
|
export type FormModel = ReturnType<typeof useFormModel>;
|
||||||
|
|
||||||
function formatModel(model: Recordable) {
|
export function formatModel(model: Recordable) {
|
||||||
const data: Recordable = {};
|
const data: Recordable = {};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(model)) {
|
for (const [key, value] of Object.entries(model)) {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { FormInstance, Message } from "@arco-design/web-vue";
|
import { FormInstance, Message } from "@arco-design/web-vue";
|
||||||
import { Ref } from "vue";
|
import { Ref } from "vue";
|
||||||
import { IFormItem } from "../components/FormItem";
|
import { IAnFormItem } from "../components/FormItem";
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
items: Ref<IFormItem[]>;
|
items: Ref<IAnFormItem[]>;
|
||||||
model: Ref<Recordable>;
|
model: Ref<Recordable>;
|
||||||
submit: Ref<Function | undefined>;
|
submit: Ref<Function | undefined>;
|
||||||
validate: FormInstance["validate"];
|
validate: FormInstance["validate"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormSubmit(options: Options) {
|
export function useFormSubmit(options: Options, getModel: any) {
|
||||||
const { model, items, submit, validate } = options;
|
const { items, submit, validate } = options;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,7 +30,8 @@ export function useFormSubmit(options: Options) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await submit.value?.(model.value, items.value);
|
const data = getModel();
|
||||||
|
const res = await submit.value?.(data, items.value);
|
||||||
const msg = res?.data?.message;
|
const msg = res?.data?.message;
|
||||||
msg && Message.success(`提示: ${msg}`);
|
msg && Message.success(`提示: ${msg}`);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
export const config = {
|
export const config = {
|
||||||
|
item: {
|
||||||
|
defaults: {
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 获取API错误信息
|
* 获取API错误信息
|
||||||
*/
|
*/
|
||||||
|
|
@ -79,10 +84,12 @@ export const config = {
|
||||||
export function initOptions({ item, model }: any, key = "options") {
|
export function initOptions({ item, model }: any, key = "options") {
|
||||||
if (Array.isArray(item.options)) {
|
if (Array.isArray(item.options)) {
|
||||||
item.nodeProps[key] = item.options;
|
item.nodeProps[key] = item.options;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (item.options && typeof item.options === "object") {
|
if (item.options && typeof item.options === "object") {
|
||||||
const { value, source } = item.options;
|
const { value, source } = item.options;
|
||||||
item._updateOptions = async () => {};
|
item._updateOptions = async () => {};
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (typeof item.options === "function") {
|
if (typeof item.options === "function") {
|
||||||
const loadData = item.options;
|
const loadData = item.options;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
import { FormInstance } from "@arco-design/web-vue";
|
import { FormInstance } from "@arco-design/web-vue";
|
||||||
import { FormItem, FormItemFnArg } from "./FormItem";
|
import { FormItem, FormItemFnArg } from "./FormItem";
|
||||||
|
|
||||||
|
type FormInstanceProps = Partial<Omit<FormInstance["$props"], "model">>;
|
||||||
|
|
||||||
export type UseForm = {
|
export type UseForm = {
|
||||||
/**
|
/**
|
||||||
* 表单数据模型
|
* 表单数据
|
||||||
*/
|
*/
|
||||||
model?: Recordable;
|
model?: Recordable;
|
||||||
/**
|
/**
|
||||||
* 表单项数组
|
* 表单项
|
||||||
*/
|
*/
|
||||||
items?: FormItem[];
|
items?: FormItem[];
|
||||||
/**
|
/**
|
||||||
* 提交表单
|
* 提交表单
|
||||||
|
* @description 支持请求地址和请求函数
|
||||||
*/
|
*/
|
||||||
submit?: (arg: Omit<FormItemFnArg, "item">) => PromiseLike<any>;
|
submit?: string | ((arg: any) => PromiseLike<any>);
|
||||||
/**
|
/**
|
||||||
* 表单实例属性
|
* 实例属性
|
||||||
|
* @description 透传给表单组件的参数
|
||||||
*/
|
*/
|
||||||
formProps?: Partial<FormInstance["$props"]>;
|
formProps?: FormInstanceProps;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
|
import { FormItemInstance } from "@arco-design/web-vue";
|
||||||
import { NodeType, NodeUnion } from "../../nodes";
|
import { NodeType, NodeUnion } from "../../nodes";
|
||||||
import { Rule } from "../useRules";
|
import { Rule } from "../useRules";
|
||||||
|
import { IAnFormItem, IAnFormItemBoolFn, IAnFormItemElemFn } from "../../components/FormItem";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数参数
|
* 函数参数
|
||||||
|
|
@ -12,9 +13,60 @@ export type FormItemFnArg<T = FormItem> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单项基础
|
* 表单项数据
|
||||||
*/
|
*/
|
||||||
type BaseFormItem = {
|
type BaseFormItem = {
|
||||||
|
/**
|
||||||
|
* 字段名,特殊语法在提交时会自动转换。
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* '[v1, v2]' => { v1, v2 }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
field: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始值
|
||||||
|
* @description 若指定该参数,将覆盖model中的同名属性。
|
||||||
|
*/
|
||||||
|
value?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染函数
|
||||||
|
* @description 用于自定义表单项内容
|
||||||
|
*/
|
||||||
|
render?: NodeType | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签名
|
||||||
|
* @description 同FormItem组件的label属性
|
||||||
|
*/
|
||||||
|
label?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帮助提示
|
||||||
|
* @description 同FormItem组件的help插槽
|
||||||
|
*/
|
||||||
|
help?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 额外内容
|
||||||
|
* @description 同FormItem组件的extra插槽
|
||||||
|
*/
|
||||||
|
extra?: string | IAnFormItemElemFn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否必填
|
||||||
|
* @description 默认值为false
|
||||||
|
*/
|
||||||
|
required?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验规则
|
||||||
|
* @description 支持字符串(内置)、对象形式
|
||||||
|
*/
|
||||||
|
rules?: Rule[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 传递给`FormItem`组件的参数
|
* 传递给`FormItem`组件的参数
|
||||||
* @description 部分属性会不可用,如field、label、required、rules、disabled等
|
* @description 部分属性会不可用,如field、label、required、rules、disabled等
|
||||||
|
|
@ -25,91 +77,16 @@ type BaseFormItem = {
|
||||||
* 是否可见
|
* 是否可见
|
||||||
* @description 动态控制表单项是否可见
|
* @description 动态控制表单项是否可见
|
||||||
*/
|
*/
|
||||||
visible?: (arg: FormItemFnArg) => boolean;
|
visible?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否禁用
|
* 是否禁用
|
||||||
* @description 动态控制表单项是否禁用
|
* @description 动态控制表单项是否禁用
|
||||||
*/
|
*/
|
||||||
disable?: (arg: FormItemFnArg) => boolean;
|
disable?: IAnFormItemBoolFn;
|
||||||
|
|
||||||
/**
|
|
||||||
* 选项,数组或者函数
|
|
||||||
* @description 用于下拉框、单选框、多选框等组件, 支持动态加载
|
|
||||||
*/
|
|
||||||
options?: SelectOptionData[] | ((arg: FormItemFnArg) => PromiseLike<Recordable[]>);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单项插槽
|
|
||||||
*/
|
|
||||||
type BaseFormItemSlots = {
|
|
||||||
/**
|
|
||||||
* 渲染函数
|
|
||||||
* @description 用于自定义表单项内容
|
|
||||||
*/
|
|
||||||
render?: NodeType | ((args: FormItemFnArg) => any);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标签名
|
|
||||||
* @description 同FormItem组件的label属性
|
|
||||||
*/
|
|
||||||
label?: string | ((args: FormItemFnArg) => any);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 帮助提示
|
|
||||||
* @description 同FormItem组件的help插槽
|
|
||||||
* @see https://arco.design/vue/component/form#form-item%20Slots
|
|
||||||
*/
|
|
||||||
help?: string | ((args: FormItemFnArg) => any);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 额外内容
|
|
||||||
* @description 同FormItem组件的extra插槽
|
|
||||||
* @see https://arco.design/vue/component/form#form-item%20Slots
|
|
||||||
*/
|
|
||||||
extra?: string | ((args: FormItemFnArg) => any);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单项校验
|
|
||||||
*/
|
|
||||||
type BaseFormItemRules = {
|
|
||||||
/**
|
|
||||||
* 是否必填
|
|
||||||
* @description 默认值为false
|
|
||||||
*/
|
|
||||||
required?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验规则
|
|
||||||
* @description 支持字符串(内置)、对象形式
|
|
||||||
* @see https://arco.design/vue/component/form#FieldRule
|
|
||||||
*/
|
|
||||||
rules?: Rule<FormItem>[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单项数据
|
|
||||||
*/
|
|
||||||
type BaseFormItemModel = {
|
|
||||||
/**
|
|
||||||
* 字段名,特殊语法在提交时会自动转换。
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* '[v1,v2]' => { v1: 1, v2: 2 }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
field: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始值
|
|
||||||
* @description 若指定该参数,将覆盖model中的同名属性。
|
|
||||||
*/
|
|
||||||
initial?: any;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单项
|
* 表单项
|
||||||
*/
|
*/
|
||||||
export type FormItem = BaseFormItem & BaseFormItemModel & BaseFormItemRules & BaseFormItemSlots & NodeUnion;
|
export type FormItem = BaseFormItem & NodeUnion;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,44 @@
|
||||||
import { UseForm } from "./types/Form";
|
|
||||||
import { useItems } from "./useItems";
|
import { useItems } from "./useItems";
|
||||||
|
import { FormInstance } from "@arco-design/web-vue";
|
||||||
|
import { FormItem } from "./types/FormItem";
|
||||||
|
import { IAnForm, IAnFormSubmit } from "../components/Form";
|
||||||
|
|
||||||
|
type FormInstanceProps = Partial<Omit<FormInstance["$props"], "model">>;
|
||||||
|
|
||||||
|
export type UseForm = {
|
||||||
|
/**
|
||||||
|
* 表单数据
|
||||||
|
*/
|
||||||
|
model?: Recordable;
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items?: FormItem[];
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
* @description 支持请求地址和请求函数
|
||||||
|
*/
|
||||||
|
submit?: IAnFormSubmit;
|
||||||
|
/**
|
||||||
|
* 实例属性
|
||||||
|
* @description 透传给表单组件的参数
|
||||||
|
*/
|
||||||
|
formProps?: FormInstanceProps;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建表单组件的参数
|
* 构建表单组件的参数
|
||||||
*/
|
*/
|
||||||
export const useForm = (options: UseForm) => {
|
export const useForm = (options: UseForm) => {
|
||||||
const { model: _model = {}, items: _items = [], submit, formProps: _formProps } = options;
|
const { items: _items = [], model: _model = {}, submit, formProps: _props = {} } = options;
|
||||||
const items = ref(useItems(_items, _model, Boolean(options.submit)))
|
const items = useItems(_items, _model, Boolean(options.submit));
|
||||||
const model = ref(_model);
|
const model = ref(_model);
|
||||||
const formProps = ref(_formProps);
|
const formProps = ref(_props);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
model,
|
model,
|
||||||
items,
|
items,
|
||||||
submit,
|
|
||||||
formProps,
|
formProps,
|
||||||
|
submit,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { merge } from "lodash-es";
|
import { defaultsDeep, merge, omit } from "lodash-es";
|
||||||
import { nodeMap } from "../nodes";
|
import { NodeType, nodeMap } from "../nodes";
|
||||||
import { FormItem } from "./types/FormItem";
|
import { FormItem } from "./types/FormItem";
|
||||||
import { useRules } from "./useRules";
|
import { useRules } from "./useRules";
|
||||||
|
import { IAnFormItem } from "../components/FormItem";
|
||||||
|
|
||||||
const ITEM: Partial<FormItem> = {
|
const ITEM: Partial<FormItem> = {
|
||||||
render: "input",
|
render: "input",
|
||||||
|
itemProps: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUBMIT_ITEM: FormItem = {
|
const SUBMIT_ITEM: FormItem = {
|
||||||
|
|
@ -16,26 +18,37 @@ const SUBMIT_ITEM: FormItem = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
|
export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
|
||||||
const items = [];
|
const items = ref<IAnFormItem[]>([]);
|
||||||
let hasSubmit = false;
|
let hasSubmit = false;
|
||||||
|
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
let target: Recordable = merge({}, nodeMap[typeof item.render === "string" ? item.render : "input"]);
|
let target: any = defaultsDeep({}, ITEM);
|
||||||
|
|
||||||
|
if (!item.render || typeof item.render === "string") {
|
||||||
|
const defaults = nodeMap[item.render ?? "input"];
|
||||||
|
if (defaults) {
|
||||||
|
defaultsDeep(target, defaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.render === "submit") {
|
if (item.render === "submit") {
|
||||||
target = merge(item, SUBMIT_ITEM);
|
target = merge(target, SUBMIT_ITEM);
|
||||||
hasSubmit = true;
|
hasSubmit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
target = merge(item, item);
|
target = merge(target, omit(item, ["required", "rules"]));
|
||||||
target.rules = useRules(item);
|
|
||||||
|
|
||||||
model[item.field] = model[item.field] ?? item.initial;
|
const rules = useRules(item);
|
||||||
items.push(target as any);
|
if (rules) {
|
||||||
|
target.rules = rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
model[item.field] = model[item.field] ?? item.value;
|
||||||
|
items.value.push(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submit && !hasSubmit) {
|
if (submit && !hasSubmit) {
|
||||||
items.push(merge({}, SUBMIT_ITEM));
|
items.value.push(defaultsDeep({}, SUBMIT_ITEM, nodeMap.submit));
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FieldRule } from "@arco-design/web-vue";
|
import { FieldRule } from "@arco-design/web-vue";
|
||||||
import { isString } from "lodash-es";
|
import { has, isString } from "lodash-es";
|
||||||
|
import { IAnFormItemBoolFn, IAnFormItemRule } from "../components/FormItem";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 内置规则
|
* 内置规则
|
||||||
|
|
@ -52,17 +53,10 @@ export const FieldRuleMap = defineRuleMap({
|
||||||
*/
|
*/
|
||||||
export type FieldStringRule = keyof typeof FieldRuleMap;
|
export type FieldStringRule = keyof typeof FieldRuleMap;
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象形式
|
|
||||||
*/
|
|
||||||
export type FieldObjectRule<T> = FieldRule & {
|
|
||||||
disable?: (arg: { item: T; model: Record<string, any> }) => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完整类型
|
* 完整类型
|
||||||
*/
|
*/
|
||||||
export type Rule<T> = FieldStringRule | FieldObjectRule<T>;
|
export type Rule = FieldStringRule | IAnFormItemRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 助手函数(获得TS提示)
|
* 助手函数(获得TS提示)
|
||||||
|
|
@ -76,19 +70,26 @@ function defineRuleMap<T extends Record<string, FieldRule>>(ruleMap: T) {
|
||||||
* @param item 表单项
|
* @param item 表单项
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const useRules = <T extends { required?: boolean; rules?: Rule<any>[] }>(item: T) => {
|
export const useRules = <T extends { required?: boolean; rules?: Rule[] }>(item: T) => {
|
||||||
const rules: FieldObjectRule<T>[] = [];
|
const { required, rules } = item;
|
||||||
if (item.required) {
|
if (!has(item, "required") && !has(item, "rules")) {
|
||||||
rules.push(FieldRuleMap.required);
|
return null;
|
||||||
}
|
}
|
||||||
for (const rule of item.rules ?? []) {
|
|
||||||
|
const data: IAnFormItemRule[] = [];
|
||||||
|
if (required) {
|
||||||
|
data.push(FieldRuleMap.required);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rule of rules ?? []) {
|
||||||
if (isString(rule)) {
|
if (isString(rule)) {
|
||||||
if (FieldRuleMap[rule]) {
|
if (FieldRuleMap[rule]) {
|
||||||
rules.push(FieldRuleMap[rule]);
|
data.push(FieldRuleMap[rule]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rules.push(rule);
|
data.push(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rules;
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ type Props = DatePickerInstance["$props"];
|
||||||
export default {
|
export default {
|
||||||
render: DatePicker,
|
render: DatePicker,
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
placeholder: "请输入",
|
placeholder: "请选择",
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
} as Props,
|
} as Props,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { RangePicker, RangePickerInstance } from "@arco-design/web-vue";
|
||||||
|
|
||||||
|
type Props = RangePickerInstance["$props"];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
render: RangePicker,
|
||||||
|
nodeProps: {
|
||||||
|
allowClear: true,
|
||||||
|
} as Props,
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
import { Select, SelectInstance } from "@arco-design/web-vue";
|
import { Select, SelectInstance, SelectOptionData } from "@arco-design/web-vue";
|
||||||
import { initOptions } from "../form-config";
|
import { initOptions } from "../form-config";
|
||||||
|
import { Component } from "vue";
|
||||||
|
import { defineSetter } from "../utils/defineSetter";
|
||||||
|
|
||||||
type Props = SelectInstance["$props"];
|
interface Interface {
|
||||||
|
init: any;
|
||||||
|
render: Component;
|
||||||
|
nodeProps: SelectInstance["$props"];
|
||||||
|
/**
|
||||||
|
* 选项
|
||||||
|
*/
|
||||||
|
options?: SelectOptionData[] | ((arg: any) => Recordable[] | Promise<Recordable[]>);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
const select: Interface = {
|
||||||
render: Select,
|
render: Select,
|
||||||
init: initOptions,
|
init: initOptions,
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
|
|
@ -11,5 +21,18 @@ export default {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
allowSearch: true,
|
allowSearch: true,
|
||||||
options: [],
|
options: [],
|
||||||
} as Props,
|
},
|
||||||
|
options: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default select;
|
||||||
|
|
||||||
|
// export default defineSetter({
|
||||||
|
// setter: Select,
|
||||||
|
// setterProps: {
|
||||||
|
// placeholder: "请选择",
|
||||||
|
// allowClear: true,
|
||||||
|
// allowSearch: true,
|
||||||
|
// options: [],
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { Button } from "@arco-design/web-vue";
|
import { Button } from "@arco-design/web-vue";
|
||||||
|
import { FormContextKey } from "../core/interface";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
render: (props: any, { emit }: any) => {
|
render() {
|
||||||
|
const { loading, submitForm, resetModel } = inject(FormContextKey)!;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="primary" loading={props.loading} onClick={() => emit("submit")} class="mr-3">
|
<Button type="primary" loading={loading.value} onClick={submitForm} class="mr-3">
|
||||||
立即提交
|
提交
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button loading={props.loading} onClick={() => emit("cancel")}>
|
<Button disabled={loading.value} onClick={resetModel}>
|
||||||
重置
|
重置
|
||||||
</Button> */}
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import submit from "./Submit";
|
||||||
import textarea from "./Textarea";
|
import textarea from "./Textarea";
|
||||||
import time from "./Time";
|
import time from "./Time";
|
||||||
import treeSelect from "./TreeSelect";
|
import treeSelect from "./TreeSelect";
|
||||||
|
import dateRange from "./DateRange";
|
||||||
|
|
||||||
export const nodeMap = {
|
export const nodeMap = {
|
||||||
input,
|
input,
|
||||||
|
|
@ -24,6 +25,7 @@ export const nodeMap = {
|
||||||
date,
|
date,
|
||||||
submit,
|
submit,
|
||||||
custom,
|
custom,
|
||||||
|
dateRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeMap = typeof nodeMap;
|
export type NodeMap = typeof nodeMap;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Component } from "vue";
|
||||||
|
import { FormItem } from "../hooks/types/FormItem";
|
||||||
|
|
||||||
|
interface Setter<T extends Component, P = T extends new (...args: any) => any ? InstanceType<T>["$props"] : any> {
|
||||||
|
/**
|
||||||
|
* 输入组件
|
||||||
|
*/
|
||||||
|
setter: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入组件参数
|
||||||
|
*/
|
||||||
|
setterProps?: P;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化钩子
|
||||||
|
* @param model 表单数据
|
||||||
|
* @param item 表单项
|
||||||
|
* @param items 表单项列表
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
onSetup?: (model: Recordable, item: FormItem, items: FormItem[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineSetter<T extends Component>(options: Setter<T>): Setter<T> {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,94 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="m-4">
|
<div class="m-4 bg-white p-4">
|
||||||
<div class="border-2 border-green-500 px-2 w-40 text-3xl text-green-500">AR K056</div>
|
<div class="border-2 border-green-500 px-2 w-40 text-3xl text-green-500 mb-4">AR K056</div>
|
||||||
<AnForm :model="model" :items="items" :submit="submit" :form-props="formProps"></AnForm>
|
<TestForm></TestForm>
|
||||||
|
<div>{{ formatModel(model) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { AnForm } from "@/components/form/components/Form";
|
import { useFormModal } from "@/components/AnForm/hooks/useFormModal";
|
||||||
import { useForm } from "@/components/form/hooks/useForm";
|
import { formatModel } from "@/components/form/core/useFormModel";
|
||||||
|
import { sleep } from "@/utils";
|
||||||
|
|
||||||
const { model, items, submit, formProps } = useForm({
|
const { component: TestForm, model } = useFormModal({
|
||||||
model: {},
|
trigger: true,
|
||||||
|
modalProps: {
|
||||||
|
width: 880
|
||||||
|
},
|
||||||
|
formProps: {
|
||||||
|
class: 'grid! grid-cols-2 gap-x-4'
|
||||||
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
field: "id",
|
field: "id",
|
||||||
render: "input",
|
label: "输入组件",
|
||||||
|
setter: "input",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'tod',
|
field: "xsa",
|
||||||
render: 'select',
|
label: "动态渲染",
|
||||||
|
setter: "input",
|
||||||
|
visible: (model) => model.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "fsa",
|
||||||
|
label: "动态禁止",
|
||||||
|
setter: "input",
|
||||||
|
disable: (model) => model.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "sgs",
|
||||||
|
label: "校验规则",
|
||||||
|
setter: "input",
|
||||||
|
required: true,
|
||||||
|
rules: ["email"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "sgss",
|
||||||
|
label: "动态规则",
|
||||||
|
setter: "input",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "必须项",
|
||||||
|
disable: (model) => !model.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "num",
|
||||||
|
value: 20,
|
||||||
|
label: "数字组件",
|
||||||
|
setter: "number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "date",
|
||||||
|
label: "日期组件",
|
||||||
|
setter: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "[startDate, endDate]",
|
||||||
|
label: "字段语法",
|
||||||
|
setter: "dateRange",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "tod",
|
||||||
|
value: 1,
|
||||||
|
label: "下拉组件",
|
||||||
|
setter: "select",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: '测试',
|
label: "测试",
|
||||||
value: 1
|
value: 1,
|
||||||
}
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async submit(model) {
|
||||||
|
await sleep(3000);
|
||||||
|
return { message: "操作成功" };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,58 +7,28 @@ export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AAlert: typeof import('@arco-design/web-vue')['Alert']
|
|
||||||
AAutoComplete: typeof import('@arco-design/web-vue')['AutoComplete']
|
|
||||||
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
||||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||||
ACard: typeof import('@arco-design/web-vue')['Card']
|
|
||||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
|
||||||
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
|
||||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||||
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
|
|
||||||
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||||
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
|
|
||||||
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
||||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
|
||||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
|
||||||
AImage: typeof import('@arco-design/web-vue')['Image']
|
|
||||||
AImagePreview: typeof import('@arco-design/web-vue')['ImagePreview']
|
|
||||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
|
||||||
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
|
|
||||||
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
||||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||||
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
|
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
|
||||||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||||
AList: typeof import('@arco-design/web-vue')['List']
|
|
||||||
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
|
||||||
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
|
||||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
|
||||||
AniEmpty: typeof import('./../components/empty/AniEmpty.vue')['default']
|
AniEmpty: typeof import('./../components/empty/AniEmpty.vue')['default']
|
||||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
|
||||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
|
||||||
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
|
||||||
ARadio: typeof import('@arco-design/web-vue')['Radio']
|
|
||||||
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
|
|
||||||
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
||||||
ASelect: typeof import('@arco-design/web-vue')['Select']
|
|
||||||
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||||
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
|
||||||
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
|
||||||
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
|
||||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
|
||||||
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
|
|
||||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||||
ATree: typeof import('@arco-design/web-vue')['Tree']
|
|
||||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
|
||||||
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
||||||
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
||||||
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
|
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue