feat: 临时提交

master
绝弹 2023-11-13 22:01:00 +08:00
parent 71baafecc7
commit 51e287c747
37 changed files with 754 additions and 273 deletions

View File

@ -0,0 +1,79 @@
import { Form as BaseForm, FormInstance as BaseFormInstance } from "@arco-design/web-vue";
import { PropType } from "vue";
import { FormContextKey } from "../core/interface";
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 { SubmitFn } from "./types/Form";
/**
*
*/
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<SubmitFn>,
},
/**
* Form
*/
formProps: {
type: Object as PropType<Omit<BaseFormInstance["$props"], "model">>,
},
},
setup(props, { slots }) {
const model = computed(() => props.model);
const items = computed(() => props.items);
const submit = computed(() => props.submit);
const formRefes = useFormRef();
const formModel = useFormModel(model);
const formItems = useFormItems(items, model);
const formSubmit = useFormSubmit({ items, model, validate: formRefes.validate, submit });
const context = {
slots,
...formModel,
...formItems,
...formRefes,
...formSubmit,
};
provide(FormContextKey, context);
return context;
},
render() {
return (
<BaseForm layout="vertical" {...this.$attrs} {...this.formProps} ref="formRef" model={this.model}>
{this.items.map((item) => (
<AnFormItem item={item} items={this.items} model={this.model}></AnFormItem>
))}
</BaseForm>
);
},
});
export type AnFormInstance = InstanceType<typeof AnForm>;
export type AnFormProps = AnFormInstance["$props"];
export type IAnForm = Pick<AnFormProps, "model" | "items" | "submit" | "formProps">;

View File

@ -0,0 +1,143 @@
import { FormItem as BaseFormItem, FormItemInstance } from "@arco-design/web-vue";
import { isFunction } from "lodash-es";
import { PropType } from "vue";
import { FieldObjectRule } from "../hooks/useRules";
import { NodeType, NodeUnion, nodeMap } from "../nodes";
import { strOrFnRender } from "../utils/strOrFnRender";
/**
*
*/
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)));
/**
*
*/
const disabled = computed(() => Boolean(props.item.disable?.(props)));
/**
*
*/
const render = () => {
let render = (props.item as any).render;
if (!render) {
return null;
}
if (typeof render === "string") {
render = nodeMap[render as NodeType]?.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} />;
}
};
/**
*
*/
const label = strOrFnRender(props.item.label, props);
/**
*
*/
const help = strOrFnRender(props.item.help, props);
/**
*
*/
const extra = strOrFnRender(props.item.extra, props);
return () => {
if (props.item.visible && !props.item.visible(props)) {
return null;
}
return (
<BaseFormItem {...props.item.itemProps} rules={rules.value} disabled={disabled.value} field={props.item.field}>
{{ default: render, label, help, extra }}
</BaseFormItem>
);
};
},
});
type FormItemFnArg<T = IAnFormItem> = {
item: T;
items: T[];
model: Record<string, any>;
};
type FormItemBase = {
/**
*
*/
field: string;
/**
* `FormItem`
*/
itemProps?: Partial<Omit<FormItemInstance["$props"], "field" | "label" | "required" | "rules" | "disabled">>;
/**
*
*/
rules?: FieldObjectRule<IAnFormItem>[];
/**
*
*/
visible?: (arg: FormItemFnArg) => boolean;
/**
*
*/
disable?: (arg: FormItemFnArg) => boolean;
/**
*
*/
label?: string | ((args: FormItemFnArg) => any);
/**
*
*/
help?: string | ((args: FormItemFnArg) => any);
/**
*
*/
extra?: string | ((args: FormItemFnArg) => any);
};
export type IAnFormItem = FormItemBase & NodeUnion;

View File

@ -0,0 +1,16 @@
import { nodeMap, NodeMap, NodeType } from "../nodes";
type NodeUnion = {
[key in NodeType]: Partial<
NodeMap[key] & {
/**
*
*/
type: key;
}
>;
}[NodeType];
export { nodeMap };
export type { NodeMap, NodeType, NodeUnion };

View File

@ -0,0 +1,110 @@
import { FieldRule, FormInstance, FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
import { InjectionKey, Ref } from "vue";
import { NodeUnion } from "../../nodes";
/**
*
*/
export type FormItemFnArg<T = AppFormItem> = {
item: T;
items: T[];
model: Recordable;
};
/**
*
*/
type BaseFormItem = {
/**
* `FormItem`
* @description fieldlabelrequiredrulesdisabled
*/
itemProps: Omit<FormItemInstance["$props"], "field" | "label" | "required" | "rules" | "disabled">;
/**
*
* @description
*/
visible?: (arg: FormItemFnArg) => boolean;
/**
*
* @description
*/
disable?: (arg: FormItemFnArg) => boolean;
/**
*
* @description ,
*/
options?: SelectOptionData[] | ((arg: FormItemFnArg) => PromiseLike<Recordable[]>);
};
/**
*
*/
type BaseFormItemSlots = {
/**
*
* @description
*/
render: (args: FormItemFnArg) => any;
/**
*
* @description FormItemlabel
*/
label?: string | ((args: FormItemFnArg) => any);
/**
*
* @description FormItemhelp
*/
help?: string | ((args: FormItemFnArg) => any);
/**
*
* @description FormItemextra
*/
extra?: string | ((args: FormItemFnArg) => any);
};
/**
*
*/
type BaseFormItemRules = {
/**
*
* @description ()
*/
rules?: FieldRule<AppFormItem>[];
};
/**
*
*/
type BaseFormItemModel = {
/**
*
* @example
* ```typescript
* '[v1,v2]' => { v1: 1, v2: 2 }
* ```
*/
field: string;
};
/**
*
*/
export type AppFormItem = BaseFormItem & BaseFormItemModel & BaseFormItemRules & BaseFormItemSlots & NodeUnion;
export type SubmitFn = (arg: { model: Recordable; items: AppFormItem[] }) => PromiseLike<void | { message?: string }>;
interface FormContext {
loading: Ref<boolean>;
formRef: Ref<FormInstance | null>;
submitForm: () => PromiseLike<any>;
}
export const FormKey = Symbol("AppnifyForm") as InjectionKey<FormContext>;

View File

@ -1,9 +1,14 @@
import { InjectionKey } from "vue"; import { InjectionKey } from "vue";
import { FormRef } from "./useFormRef";
import { FormSubmit } from "./useFormSubmit";
import { FormItems } from "./useFormItems"; import { FormItems } from "./useFormItems";
import { FormModel } from "./useFormModel"; import { FormModel } from "./useFormModel";
import { FormRef } from "./useFormRef";
import { FormSubmit } from "./useFormSubmit";
export type FormContextInterface = FormModel & FormItems & FormRef & FormSubmit; export type FormContextInterface = FormModel &
FormItems &
FormRef &
FormSubmit & {
slots: Recordable;
};
export const FormContext = Symbol("FormKey") as InjectionKey<FormContextInterface>; export const FormContextKey = Symbol("FormKey") as InjectionKey<FormContextInterface>;

View File

@ -1,7 +1,7 @@
import { Ref } from "vue"; import { Ref } from "vue";
import { FormItem } from "../hooks/types/FormItem"; import { IAnFormItem } from "../components/FormItem";
export function useFormItems(items: Ref<FormItem[]>, model: Ref<Recordable>) { export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>) {
const getItem = (field: string) => { const getItem = (field: string) => {
return items.value.find((i) => i.field === field); return items.value.find((i) => i.field === field);
}; };

View File

@ -1,9 +1,9 @@
import { Ref } from "vue";
import { FormItem } from "../hooks/types/FormItem";
import { FormInstance, Message } from "@arco-design/web-vue"; import { FormInstance, Message } from "@arco-design/web-vue";
import { Ref } from "vue";
import { IFormItem } from "../components/FormItem";
interface Options { interface Options {
items: Ref<FormItem[]>; items: Ref<IFormItem[]>;
model: Ref<Recordable>; model: Ref<Recordable>;
submit: Ref<Function | undefined>; submit: Ref<Function | undefined>;
validate: FormInstance["validate"]; validate: FormInstance["validate"];

View File

@ -1,90 +1,80 @@
import { FormItem as BaseFormItem, FormItemInstance, SelectOptionData } from "@arco-design/web-vue"; import { FormItem as BaseFormItem, FieldRule, FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
import { NodeUnion, nodeMap } from "./form-node"; import { NodeType, NodeUnion, nodeMap } from "./form-node";
import { FieldObjectRule, FieldRuleMap, Rule } from "./form-rules"; import { RuleMap } from "./form-rules";
import { PropType } from "vue";
import { strOrFnRender } from "./util"; export type FieldStringRule = keyof typeof RuleMap;
export type FieldObjectRule = FieldRule & {
disable?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean;
};
export type FieldRuleType = FieldStringRule | FieldObjectRule;
/** /**
* *
*/ */
export const FormItem = defineComponent({ export const FormItem = (props: any, { emit }: any) => {
name: "AppnifyFormItem", const { item } = props;
props: { const args = {
/** ...props,
* field: item.field,
*/ };
item: {
type: Object as PropType<IFormItem>,
required: true,
},
/**
*
*/
items: {
type: Array as PropType<IFormItem[]>,
required: true,
},
/**
*
*/
model: {
type: Object as PropType<Recordable>,
required: true,
},
},
setup(props) {
/**
*
*/
const rules = computed(() => props.item.rules?.filter((i) => !i.disable?.(props)));
/** const rules = computed(() => {
* const result = [];
*/ if (item.required) {
const disabled = computed(() => Boolean(props.item.disable?.(props))); result.push(RuleMap.required);
}
item.rules?.forEach((rule: any) => {
if (typeof rule === "string") {
result.push(RuleMap[rule as FieldStringRule]);
return;
}
if (!rule.disable) {
result.push(rule);
return;
}
if (!rule.disable({ model: props.model, item, items: props.items })) {
result.push(rule);
}
});
return result;
});
if (props.item.visible && !props.item.visible(props as any)) { const disabled = computed(() => {
if (item.disable === undefined) {
return false;
}
if (typeof item.disable === "function") {
return item.disable(args);
}
return item.disable;
});
if (item.visible && !item.visible(args)) {
return null; return null;
} }
/** return (
* <BaseFormItem {...item.itemProps} rules={rules.value} disabled={disabled.value} field={item.field}>
*/ {{
const render = () => { default: () => {
const Item = props.item.component as any; if (item.component) {
if (props.item.type === "custom") { return <item.component {...item.nodeProps} model={props.model} item={props.item} />;
return <Item {...props.item.nodeProps} items={props.items} model={props.model} item={props.item} />;
} }
return <Item {...props.item.nodeProps} v-model={props.model[props.item.field]} />; const comp = nodeMap[item.type as NodeType]?.component;
}; if (!comp) {
return null;
/** }
* if (item.type === "submit") {
*/ return <comp loading={props.loading} onSubmit={() => emit("submit")} onCancel={emit("cancel")} />;
const label = strOrFnRender(props.item.label, props); }
return <comp v-model={props.model[item.field]} {...item.nodeProps} />;
/** },
* label: item.label && (() => (typeof item.label === "string" ? item.label : item.label?.(args))),
*/ help: item.help && (() => (typeof item.help === "string" ? item.help : item.help?.(args))),
const help = strOrFnRender(props.item.help, props); extra: item.extra && (() => (typeof item.extra === "string" ? item.extra : item.extra?.(args))),
}}
/**
*
*/
const extra = strOrFnRender(props.item.extra, props);
return () => (
<BaseFormItem {...props.item.itemProps} rules={rules.value} disabled={disabled.value} field={props.item.field}>
{{ default: render, label, help, extra }}
</BaseFormItem> </BaseFormItem>
); );
},
});
type FormItemFnArg<T = IFormItem> = {
item: T;
items: T[];
model: Record<string, any>;
}; };
type FormItemBase = { type FormItemBase = {
@ -115,7 +105,7 @@ type FormItemBase = {
* *
* @description FormItemlabel * @description FormItemlabel
*/ */
label?: string | ((args: FormItemFnArg) => any); label?: string | ((args: { item: IFormItem; model: Record<string, any> }) => any);
/** /**
* `FormItem` * `FormItem`
@ -146,45 +136,45 @@ type FormItemBase = {
*``` *```
* @see https://arco.design/vue/component/form#FieldRule * @see https://arco.design/vue/component/form#FieldRule
*/ */
rules?: FieldObjectRule<IFormItem>[]; rules?: FieldRuleType[];
/** /**
* *
* @description * @description
*/ */
visible?: (arg: FormItemFnArg) => boolean; visible?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean;
/** /**
* *
* @description * @description
*/ */
disable?: (arg: FormItemFnArg) => boolean; disable?: (arg: { item: IFormItem; model: Record<string, any> }) => boolean;
/** /**
* *
* @description , * @description ,
*/ */
options?: SelectOptionData[] | ((arg: FormItemFnArg) => Promise<any>); options?: SelectOptionData[] | ((arg: { item: IFormItem; model: Record<string, any> }) => Promise<any>);
/** /**
* *
* @description * @description
*/ */
component?: (args: FormItemFnArg) => any; component?: (args: { item: IFormItem; model: Record<string, any>; field: string }) => any;
/** /**
* *
* @description FormItemhelp * @description FormItemhelp
* @see https://arco.design/vue/component/form#form-item%20Slots * @see https://arco.design/vue/component/form#form-item%20Slots
*/ */
help?: string | ((args: FormItemFnArg) => any); help?: string | ((args: { item: IFormItem; model: Record<string, any> }) => any);
/** /**
* *
* @description FormItemextra * @description FormItemextra
* @see https://arco.design/vue/component/form#form-item%20Slots * @see https://arco.design/vue/component/form#form-item%20Slots
*/ */
extra?: string | ((args: FormItemFnArg) => any); extra?: string | ((args: { item: IFormItem; model: Record<string, any> }) => any);
}; };
export type IFormItem = FormItemBase & NodeUnion; export type IFormItem = FormItemBase & NodeUnion;

View File

@ -224,7 +224,7 @@ export type NodeUnion = {
/** /**
* `input` * `input`
*/ */
type?: key; type: key;
/** /**
* `type` * `type`
*/ */

View File

@ -1,10 +1,8 @@
import { FieldRule } from "@arco-design/web-vue"; import { FieldRule } from "@arco-design/web-vue";
import { isString } from "lodash-es";
/** const defineRuleMap = <T extends Record<string, FieldRule>>(ruleMap: T) => ruleMap;
*
*/ export const RuleMap = defineRuleMap({
export const FieldRuleMap = defineRuleMap({
required: { required: {
required: true, required: true,
message: "该项不能为空", message: "该项不能为空",
@ -46,47 +44,3 @@ export const FieldRuleMap = defineRuleMap({
message: "至少包含大写字母、小写字母、数字和特殊字符", message: "至少包含大写字母、小写字母、数字和特殊字符",
}, },
}); });
/**
* ()
*/
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>;
/**
* (TS)
*/
function defineRuleMap<T extends Record<string, FieldRule>>(ruleMap: T) {
return ruleMap;
}
/**
*
* @param item
* @returns
*/
export const useFieldRules = <T extends { required?: boolean; rules?: Rule<any>[] }>(item: T) => {
const rules: FieldObjectRule<T>[] = [];
if (item.required) {
rules.push(FieldRuleMap.required);
}
for (const rule of item.rules ?? []) {
if (isString(rule)) {
rules.push(FieldRuleMap[rule]);
} else {
rules.push(rule);
}
}
return rules;
};

View File

@ -1,9 +1,9 @@
import { Form as BaseForm, FormInstance as BaseFormInstance, Message } from "@arco-design/web-vue"; import { Form as BaseForm, FormInstance as BaseFormInstance, Message } from "@arco-design/web-vue";
import { assign, cloneDeep, defaultsDeep, merge } from "lodash-es"; import { assign, cloneDeep, defaultsDeep } from "lodash-es";
import { PropType } from "vue"; import { PropType } from "vue";
import { FormItem, IFormItem } from "./form-item";
import { nodeMap } from "./form-node";
import { config } from "./form-config"; import { config } from "./form-config";
import { FormItem, IFormItem } from "./form-item";
import { NodeType, nodeMap } from "./form-node";
type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>; type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Promise<any>;
@ -11,13 +11,13 @@ type SubmitFn = (arg: { model: Record<string, any>; items: IFormItem[] }) => Pro
* *
*/ */
export const Form = defineComponent({ export const Form = defineComponent({
name: "AppnifyForm", name: "Form",
props: { props: {
/** /**
* *
*/ */
model: { model: {
type: Object as PropType<Recordable>, type: Object as PropType<Record<any, any>>,
default: () => reactive({}), default: () => reactive({}),
}, },
/** /**
@ -45,11 +45,11 @@ export const Form = defineComponent({
const formRef = ref<InstanceType<typeof BaseForm>>(); const formRef = ref<InstanceType<typeof BaseForm>>();
const loading = ref(false); const loading = ref(false);
for (const item of props.items) { props.items.forEach((item: any) => {
const node = nodeMap[item.type] as any; const node = nodeMap[item.type as NodeType];
defaultsDeep(item, { nodeProps: node?.nodeProps ?? {} }); defaultsDeep(item, { nodeProps: node?.nodeProps ?? {} });
(node as any)?.init?.({ item, model: props.model }); (node as any)?.init?.({ item, model: props.model });
} });
const getItem = (field: string) => { const getItem = (field: string) => {
return props.items.find((item) => item.field === field); return props.items.find((item) => item.field === field);
@ -64,7 +64,7 @@ export const Form = defineComponent({
}; };
const resetModel = () => { const resetModel = () => {
assign(props.model, merge({}, model)); assign(props.model, model);
}; };
const submitForm = async () => { const submitForm = async () => {
@ -84,7 +84,7 @@ export const Form = defineComponent({
} }
}; };
const injected = { return {
formRef, formRef,
loading, loading,
getItem, getItem,
@ -93,10 +93,6 @@ export const Form = defineComponent({
setModel, setModel,
getModel, getModel,
}; };
provide("form1", injected);
return injected;
}, },
render() { render() {
(this.items as any).instance = this; (this.items as any).instance = this;
@ -108,9 +104,9 @@ export const Form = defineComponent({
}; };
return ( return (
<BaseForm layout="vertical" {...this.$attrs} {...this.formProps} ref="formRef" model={this.model}> <BaseForm ref="formRef" layout="vertical" model={this.model} {...this.$attrs} {...this.formProps}>
{this.items.map((item) => ( {this.items.map((item) => (
<FormItem item={item} {...props}></FormItem> <FormItem loading={this.loading} onSubmit={this.submitForm} item={item} {...props}></FormItem>
))} ))}
</BaseForm> </BaseForm>
); );

View File

@ -1,6 +1,6 @@
import { FormItemInstance, SelectOptionData } from "@arco-design/web-vue"; import { FormItemInstance, SelectOptionData } from "@arco-design/web-vue";
import { NodeType, NodeUnion } from "../../nodes";
import { Rule } from "../useRules"; import { Rule } from "../useRules";
import { NodeUnion } from "../../form-node";
/** /**
* *
@ -48,7 +48,7 @@ type BaseFormItemSlots = {
* *
* @description * @description
*/ */
render?: (args: FormItemFnArg) => any; render?: NodeType | ((args: FormItemFnArg) => any);
/** /**
* *

View File

@ -1,22 +1,19 @@
import { useModel } from "./useModel";
import { useItems } from "./useItems";
import { UseOptions } from "./interface";
import { UseForm } from "./types/Form"; import { UseForm } from "./types/Form";
import { useItems } from "./useItems";
/** /**
* *
*/ */
export const useForm = <T extends UseForm>(options: T) => { export const useForm = (options: UseForm) => {
const initModel = options.model ?? {}; const { model: _model = {}, items: _items = [], submit, formProps: _formProps } = options;
const { items, updateItemOptions } = useItems(options.items ?? [], initModel, Boolean(options.submit)); const items = ref(useItems(_items, _model, Boolean(options.submit)))
const { model, resetModel, setModel, getModel } = useModel(initModel); const model = ref(_model);
const formProps = ref(_formProps);
return { return {
model, model,
items, items,
resetModel, submit,
setModel, formProps,
getModel,
updateItemOptions,
}; };
}; };

View File

@ -1,28 +1,28 @@
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import { nodeMap } from "../nodes";
import { FormItem } from "./types/FormItem"; import { FormItem } from "./types/FormItem";
import { nodeMap } from "../form-node";
import { useRules } from "./useRules"; import { useRules } from "./useRules";
const ITEM: Partial<FormItem> = { const ITEM: Partial<FormItem> = {
type: "input", render: "input",
}; };
const SUBMIT_ITEM: FormItem = { const SUBMIT_ITEM: FormItem = {
field: "id", field: "id",
type: "submit", render: "submit",
itemProps: { itemProps: {
hideLabel: true, hideLabel: true,
}, },
}; };
export function useItems(list: FormItem[], model: Recordable, submit: boolean) { export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
const items = ref<FormItem[]>([]); const items = [];
let hasSubmit = false; let hasSubmit = false;
for (const item of list) { for (const item of list) {
let target: Recordable = merge({}, nodeMap[item.type ?? "input"]); let target: Recordable = merge({}, nodeMap[typeof item.render === "string" ? item.render : "input"]);
if (item.type === "submit") { if (item.render === "submit") {
target = merge(item, SUBMIT_ITEM); target = merge(item, SUBMIT_ITEM);
hasSubmit = true; hasSubmit = true;
} }
@ -31,22 +31,12 @@ export function useItems(list: FormItem[], model: Recordable, submit: boolean) {
target.rules = useRules(item); target.rules = useRules(item);
model[item.field] = model[item.field] ?? item.initial; model[item.field] = model[item.field] ?? item.initial;
items.value.push(target as any); items.push(target as any);
} }
if (submit && !hasSubmit) { if (submit && !hasSubmit) {
items.value.push(merge({}, SUBMIT_ITEM)); items.push(merge({}, SUBMIT_ITEM));
} }
const updateItemOptions = (field: string) => { return items;
const item = items.value.find((i) => i.field === field);
if (item) {
(item as any)._updateOptions?.();
}
};
return {
items,
updateItemOptions,
};
} }

View File

@ -1,6 +1,6 @@
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
function formatModel(model: Recordable) { export function formatModel(model: Recordable) {
const data: Recordable = {}; const data: Recordable = {};
for (const [key, val] of Object.entries(model)) { for (const [key, val] of Object.entries(model)) {
// 数组类型 // 数组类型

View File

@ -83,7 +83,9 @@ export const useRules = <T extends { required?: boolean; rules?: Rule<any>[] }>(
} }
for (const rule of item.rules ?? []) { for (const rule of item.rules ?? []) {
if (isString(rule)) { if (isString(rule)) {
if (FieldRuleMap[rule]) {
rules.push(FieldRuleMap[rule]); rules.push(FieldRuleMap[rule]);
}
} else { } else {
rules.push(rule); rules.push(rule);
} }

View File

@ -0,0 +1,14 @@
import { Cascader, CascaderInstance } from "@arco-design/web-vue";
import { initOptions } from "../form-config";
type Props = CascaderInstance["$props"];
export default {
render: Cascader,
init: initOptions,
nodeProps: {
placeholder: "请选择",
allowClear: true,
expandTrigger: "hover",
} as Props,
};

View File

@ -0,0 +1,6 @@
export default {
render: () => {
return "1";
},
nodeProps: {},
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -0,0 +1,15 @@
import { Select, SelectInstance } from "@arco-design/web-vue";
import { initOptions } from "../form-config";
type Props = SelectInstance["$props"];
export default {
render: Select,
init: initOptions,
nodeProps: {
placeholder: "请选择",
allowClear: true,
allowSearch: true,
options: [],
} as Props,
};

View File

@ -0,0 +1,17 @@
import { Button } from "@arco-design/web-vue";
export default {
render: (props: any, { emit }: any) => {
return (
<>
<Button type="primary" loading={props.loading} onClick={() => emit("submit")} class="mr-3">
</Button>
{/* <Button loading={props.loading} onClick={() => emit("cancel")}>
</Button> */}
</>
);
},
nodeProps: {},
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -0,0 +1,15 @@
import { TreeSelect, TreeSelectInstance } from "@arco-design/web-vue";
import { initOptions } from "../form-config";
type Props = TreeSelectInstance["$props"];
export default {
render: TreeSelect,
init: (arg: any) => initOptions(arg, "data"),
nodeProps: {
placeholder: "请选择",
allowClear: true,
allowSearch: true,
options: [],
} as Props,
};

View File

@ -0,0 +1,42 @@
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";
export const nodeMap = {
input,
number,
search,
textarea,
select,
treeSelect,
time,
password,
cascader,
date,
submit,
custom,
};
export type NodeMap = typeof nodeMap;
export type NodeType = keyof NodeMap;
export type NodeUnion = {
[key in NodeType]: Partial<
Omit<NodeMap[key], "render"> & {
/**
*
*/
render: key | ((...args: any[]) => any);
}
>;
}[NodeType];

View File

@ -1,7 +1,6 @@
import { FormInstance } from "@arco-design/web-vue"; import { FormInstance } from "@arco-design/web-vue";
import { merge } from "lodash-es";
import { IFormItem } from "./form-item"; import { IFormItem } from "./form-item";
import { useModel } from "./hooks/useModel";
import { useItems } from "./hooks/useItems";
export type Options = { export type Options = {
/** /**
@ -27,16 +26,34 @@ export type Options = {
* @see src/components/form/use-form.tsx * @see src/components/form/use-form.tsx
*/ */
export const useForm = (options: Options) => { export const useForm = (options: Options) => {
const initModel = options.model ?? {}; const { model: _model = {} } = options;
const { items, updateItemOptions } = useItems(options.items, initModel, Boolean(options.submit)); const model: Record<string, any> = { id: undefined, ..._model };
const { model, resetModel, setModel, getModel } = useModel(initModel); const items: IFormItem[] = [];
return { for (const item of options.items) {
model, if (!item.nodeProps) {
items, item.nodeProps = {} as any;
resetModel, }
setModel, model[item.field] = model[item.field] ?? item.initial;
getModel, items.push(item);
updateItemOptions, }
};
if (options.submit) {
const submit = items.find((item) => item.type === "submit") || {};
items.push(
merge(
{},
{
field: "id",
type: "submit",
itemProps: {
hideLabel: true,
},
},
submit
) as any
);
}
return reactive({ ...options, model, items }) as any;
}; };

View File

@ -32,21 +32,3 @@ export function setModel(model: any, data: Record<string, any>) {
} }
} }
} }
/**
*
* @param value
* @param arg
* @returns
*/
export function strOrFnRender(value?: string | Function, arg?: any) {
if (typeof value === "string") {
return () => value;
}
if (typeof value === "function") {
return () => value(arg);
}
return null;
}
export const falsy = () => false;

View File

@ -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;
}

View File

@ -1,16 +1,11 @@
import { import AniEmpty from "@/components/empty/AniEmpty.vue";
TableColumnData as BaseColumn, import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
TableData as BaseData, import { merge } from "lodash-es";
Table as BaseTable,
PaginationProps,
} from "@arco-design/web-vue";
import { cloneDeep, isBoolean, isObject, merge } from "lodash-es";
import { PropType, computed, defineComponent, reactive, ref } from "vue"; import { PropType, computed, defineComponent, reactive, ref } from "vue";
import AniEmpty from "../empty/AniEmpty.vue";
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form"; import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
import { config } from "./table.config"; import { config } from "./table.config";
type DataFn = (search: Record<string, any>, paging: { page: number; size: number }) => PromiseLike<any>; type DataFn = (search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>;
/** /**
* *
@ -37,7 +32,8 @@ export const Table = defineComponent({
* *
*/ */
pagination: { pagination: {
type: [Boolean, Object] as PropType<boolean | PaginationProps>, type: Object as PropType<any>,
default: () => reactive(config.pagination),
}, },
/** /**
* *
@ -77,7 +73,6 @@ export const Table = defineComponent({
const createRef = ref<FormModalInstance>(); const createRef = ref<FormModalInstance>();
const modifyRef = ref<FormModalInstance>(); const modifyRef = ref<FormModalInstance>();
const renderData = ref<BaseData[]>([]); const renderData = ref<BaseData[]>([]);
const paging = ref<PaginationProps>(merge({}, isObject(props.pagination) ? props.pagination : config.pagination));
const inlined = computed(() => (props.search?.items?.length ?? 0) <= config.searchInlineCount); const inlined = computed(() => (props.search?.items?.length ?? 0) <= config.searchInlineCount);
const reloadData = () => loadData({ current: 1, pageSize: 10 }); const reloadData = () => loadData({ current: 1, pageSize: 10 });
const openModifyModal = (data: any) => modifyRef.value?.open(data); const openModifyModal = (data: any) => modifyRef.value?.open(data);
@ -86,8 +81,9 @@ export const Table = defineComponent({
* *
* @param pagination * @param pagination
*/ */
const loadData = async (pagination: Partial<PaginationProps> = {}) => { const loadData = async (pagination: Partial<any> = {}) => {
const { current: page = 1, pageSize: size = 10 } = { ...paging.value, ...pagination }; const merged = { ...props.pagination, ...pagination };
const paging = { page: merged.current, size: merged.pageSize };
const model = searchRef.value?.getModel() ?? {}; const model = searchRef.value?.getModel() ?? {};
// 本地加载 // 本地加载
@ -102,21 +98,21 @@ export const Table = defineComponent({
}); });
}); });
renderData.value = data; renderData.value = data;
paging.value.total = renderData.value.length; props.pagination.total = renderData.value.length;
paging.value.current = 1; props.pagination.current = 1;
} }
// 远程加载 // 远程加载
if (typeof props.data === "function") { if (typeof props.data === "function") {
try { try {
loading.value = true; loading.value = true;
const resData = await props.data(model, { page, size }); const resData = await props.data(model, paging);
const { data = [], total = 0 } = resData?.data || {}; const { data = [], total = 0 } = resData?.data || {};
renderData.value = data; renderData.value = data;
paging.value.total = total; props.pagination.total = total;
paging.value.current = page; props.pagination.current = paging.page;
} catch (e) { } catch (e) {
console.log(e); // todo
} finally { } finally {
loading.value = false; loading.value = false;
} }
@ -126,8 +122,8 @@ export const Table = defineComponent({
watchEffect(() => { watchEffect(() => {
if (Array.isArray(props.data)) { if (Array.isArray(props.data)) {
renderData.value = props.data; renderData.value = props.data;
paging.value.total = props.data.length; props.pagination.total = props.data.length;
paging.value.current = 1; props.pagination.current = 1;
} }
}); });
@ -147,7 +143,6 @@ export const Table = defineComponent({
createRef, createRef,
modifyRef, modifyRef,
renderData, renderData,
paging,
loadData, loadData,
reloadData, reloadData,
openModifyModal, openModifyModal,
@ -182,10 +177,7 @@ export const Table = defineComponent({
)} )}
{this.$slots.action?.()} {this.$slots.action?.()}
</div> </div>
<div> <div>{this.inlined && <Form ref="searchRef" {...this.search}></Form>}</div>
{this.inlined && <Form ref="searchRef" {...this.search}></Form>}
{this.$slots.tool?.(this.renderData)}
</div>
</div> </div>
<BaseTable <BaseTable
@ -195,7 +187,7 @@ export const Table = defineComponent({
{...this.$attrs} {...this.$attrs}
{...this.tableProps} {...this.tableProps}
loading={this.loading} loading={this.loading}
pagination={this.pagination && this.paging} pagination={this.pagination}
data={this.renderData} data={this.renderData}
columns={this.columns} columns={this.columns}
onPageChange={(current: number) => this.loadData({ current })} onPageChange={(current: number) => this.loadData({ current })}

View File

@ -61,7 +61,7 @@
</template> </template>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<keep-alive :include="menuStore.cacheAppNames"> <keep-alive :include="menuStore.cacheAppNames">
<component :is="Component"></component> <component v-if="!appStore.pageLoding" :is="Component"></component>
</keep-alive> </keep-alive>
</router-view> </router-view>
</a-spin> </a-spin>
@ -73,11 +73,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useAppStore } from "@/store"; import { useAppStore } from "@/store";
import { useMenuStore } from "@/store/menu";
import { Message } from "@arco-design/web-vue"; import { Message } from "@arco-design/web-vue";
import { IconSync } from "@arco-design/web-vue/es/icon"; import { IconSync } from "@arco-design/web-vue/es/icon";
import Menu from "./components/menu.vue"; import Menu from "./components/menu.vue";
import userDropdown from "./components/userDropdown.vue"; import userDropdown from "./components/userDropdown.vue";
import { useMenuStore } from "@/store/menu";
defineOptions({ name: "LayoutPage" }); defineOptions({ name: "LayoutPage" });

View File

@ -1,17 +1,34 @@
<template> <template>
<div class="m-4"> <div class="m-4">
<a-card :bordered="false"> <div class="border-2 border-green-500 px-2 w-40 text-3xl text-green-500">AR K056</div>
<template #title> <AnForm :model="model" :items="items" :submit="submit" :form-props="formProps"></AnForm>
<div class="flex items-center">
<i class="icon-park-outline-config mr-2"></i>
系统参数
</div>
</template>
</a-card>
</div> </div>
</template> </template>
<script setup lang="tsx"></script> <script setup lang="tsx">
import { AnForm } from "@/components/form/components/Form";
import { useForm } from "@/components/form/hooks/useForm";
const { model, items, submit, formProps } = useForm({
model: {},
items: [
{
field: "id",
render: "input",
},
{
field: 'tod',
render: 'select',
options: [
{
label: '测试',
value: 1
}
]
}
],
});
</script>
<style scoped></style> <style scoped></style>

View File

@ -30,7 +30,6 @@ declare module '@vue/runtime-core' {
AImagePreview: typeof import('@arco-design/web-vue')['ImagePreview'] AImagePreview: typeof import('@arco-design/web-vue')['ImagePreview']
AInput: typeof import('@arco-design/web-vue')['Input'] AInput: typeof import('@arco-design/web-vue')['Input']
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber'] AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
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']
@ -51,7 +50,6 @@ declare module '@vue/runtime-core' {
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup'] 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'] ASelect: typeof import('@arco-design/web-vue')['Select']
ASpace: typeof import('@arco-design/web-vue')['Space']
ASpin: typeof import('@arco-design/web-vue')['Spin'] ASpin: typeof import('@arco-design/web-vue')['Spin']
ASwitch: typeof import('@arco-design/web-vue')['Switch'] ASwitch: typeof import('@arco-design/web-vue')['Switch']
ATabPane: typeof import('@arco-design/web-vue')['TabPane'] ATabPane: typeof import('@arco-design/web-vue')['TabPane']
@ -68,7 +66,6 @@ declare module '@vue/runtime-core' {
DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default'] DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default']
Editor: typeof import('./../components/editor/components/Editor.vue')['default'] Editor: typeof import('./../components/editor/components/Editor.vue')['default']
EditorPreview: typeof import('./../components/editor/components/EditorPreview.vue')['default'] EditorPreview: typeof import('./../components/editor/components/EditorPreview.vue')['default']
Empty: typeof import('./../components/empty/index.vue')['default']
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default'] ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
InputColor: typeof import('./../components/editor/components/InputColor.vue')['default'] InputColor: typeof import('./../components/editor/components/InputColor.vue')['default']
InputImage: typeof import('./../components/editor/components/InputImage.vue')['default'] InputImage: typeof import('./../components/editor/components/InputImage.vue')['default']