Compare commits

..

No commits in common. "c8dc40127aa88132bd34cc7a6cdd1ffb8d5b3647" and "2b5c3671177f50c3e9b885e5cfb52657f031c575" have entirely different histories.

75 changed files with 2256 additions and 923 deletions

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ import { useFormItems } from './useFormItems';
import { useFormModel } from './useFormModel';
import { useFormRef } from './useFormRef';
import { useFormSubmit } from './useFormSubmit';
import { AnFormItem, AnFormItemProps } from './FormItem';
import { AnFormItem, IAnFormItem } from './FormItem';
/**
*
@ -29,17 +29,9 @@ export const AnForm = defineComponent({
},
/**
*
* @example
* ```ts
* [{
* field: 'name',
* label: '昵称',
* setter: 'input'
* }]
* ```
*/
items: {
type: Array as PropType<AnFormItemProps[]>,
type: Array as PropType<IAnFormItem[]>,
default: () => [],
},
/**
@ -50,7 +42,7 @@ export const AnForm = defineComponent({
* ```
*/
submit: {
type: [String, Function, Object] as PropType<AnFormSubmit>,
type: [String, Function, Object] as PropType<IAnFormSubmit>,
},
/**
* Form
@ -62,7 +54,7 @@ export const AnForm = defineComponent({
* ```
*/
formProps: {
type: Object as PropType<Omit<FormInstance['$props'], 'model' | 'ref'>>,
type: Object as IAnFormProps,
},
},
emits: ['update:model'],
@ -95,8 +87,12 @@ export const AnForm = defineComponent({
export type AnFormInstance = InstanceType<typeof AnForm>;
export type AnFormProps = Pick<AnFormInstance['$props'], 'model' | 'items' | 'submit' | 'formProps'>;
export type AnFormProps = AnFormInstance['$props'];
export type AnFormSubmitFn = (model: Recordable, items: AnFormItemProps[]) => any;
export type IAnFormProps = PropType<Omit<FormInstance['$props'], 'model'>>;
export type AnFormSubmit = string | AnFormSubmitFn;
export type IAnForm = Pick<AnFormProps, 'model' | 'items' | 'submit' | 'formProps'>;
export type IAnFormSubmitFn = (model: Recordable, items: IAnFormItem[]) => any;
export type IAnFormSubmit = string | IAnFormSubmitFn;

View File

@ -8,7 +8,7 @@ import {
import { InjectionKey, PropType, provide } from 'vue';
import { SetterItem, SetterType, setterMap } from './FormSetter';
export const FormItemContextKey = Symbol('FormItemContextKey') as InjectionKey<AnFormItemFnProps>;
export const FormItemContextKey = Symbol('FormItemContextKey') as InjectionKey<IAnFormItemFnProps>;
/**
*
@ -20,14 +20,14 @@ export const AnFormItem = defineComponent({
*
*/
item: {
type: Object as PropType<AnFormItemProps>,
type: Object as PropType<IAnFormItem>,
required: true,
},
/**
*
*/
items: {
type: Array as PropType<AnFormItemProps[]>,
type: Array as PropType<IAnFormItem[]>,
required: true,
},
/**
@ -101,45 +101,46 @@ export const AnFormItem = defineComponent({
},
});
export type AnFormItemBoolFn = (args: AnFormItemFnProps) => boolean;
export type IAnFormItemBoolFn = (args: IAnFormItemFnProps) => boolean;
export type AnFormItemElemFn = (args: AnFormItemFnProps) => any;
export type IAnFormItemElemFn = (args: IAnFormItemFnProps) => any;
export type AnFormItemFnProps = { model: Recordable; item: AnFormItemProps; items: AnFormItemProps[] };
export type IAnFormItemFnProps = { model: Recordable; item: IAnFormItem; items: IAnFormItem[] };
export type AnFormItemRule = FieldRule & { disable?: AnFormItemBoolFn };
export type IAnFormItemRule = FieldRule & { disable?: IAnFormItemBoolFn };
export type AnFormItemOption = string | number | boolean | SelectOptionData | SelectOptionGroup;
export type IAnFormItemOption = string | number | boolean | SelectOptionData | SelectOptionGroup;
export type AnFormItemSlot = (props: AnFormItemFnProps) => any;
export type IAnFormItemSlot = (props: IAnFormItemFnProps) => any;
export type AnFormItemSlots = {
export type IAnFormItemSlots = {
/**
*
* @param props
* @returns
*/
default?: AnFormItemSlot;
default?: IAnFormItemSlot;
/**
*
* @param props
* @returns
*/
help?: AnFormItemSlot;
help?: IAnFormItemSlot;
/**
*
* @param props
* @returns
*/
extra?: AnFormItemSlot;
extra?: IAnFormItemSlot;
/**
*
* @param props
* @returns
*/
label?: AnFormItemSlot;
label?: IAnFormItemSlot;
};
export type AnFormItemPropsBase = {
export type IAnFormItemBase = {
/**
*
* @description
@ -161,12 +162,9 @@ export type AnFormItemPropsBase = {
/**
*
* @example
* ```ts
* ['email']
* ```
* @example ['email']
*/
rules?: AnFormItemRule[];
rules?: IAnFormItemRule[];
/**
*
@ -175,7 +173,7 @@ export type AnFormItemPropsBase = {
* (props) => Boolean(props.model.id)
* ```
*/
visible?: AnFormItemBoolFn;
visible?: IAnFormItemBoolFn;
/**
*
@ -184,42 +182,29 @@ export type AnFormItemPropsBase = {
* (props) => Boolean(props.model.id)
* ```
*/
disable?: AnFormItemBoolFn;
disable?: IAnFormItemBoolFn;
/**
*
* @description
* @example
* ```ts
* [{
* label: '方式1',
* value: 1
* }]
* [{ label: '方式1', value: 1 }]
* ```
*/
options?: AnFormItemOption[] | ((args: AnFormItemFnProps) => AnFormItemOption[] | Promise<AnFormItemOption[]>);
options?: IAnFormItemOption[] | ((args: IAnFormItemFnProps) => IAnFormItemOption[] | Promise<IAnFormItemOption[]>);
/**
*
* @example
* ```ts
* {
* hideLabel: true
* }
* ```
* @default null
*/
itemProps?: Partial<Omit<FormItemInstance['$props'], 'field' | 'label' | 'required' | 'rules' | 'disabled'>>;
/**
*
* @example
* ```tsx
* {
* help: () => <span></span>
* }
* ```
* @see 1
*/
itemSlots?: AnFormItemSlots;
itemSlots?: IAnFormItemSlots;
/**
*
@ -228,4 +213,4 @@ export type AnFormItemPropsBase = {
$init?: () => void;
};
export type AnFormItemProps = AnFormItemPropsBase & SetterItem;
export type IAnFormItem = IAnFormItemBase & SetterItem;

View File

@ -1,73 +1,41 @@
import { useVisible } from '@/hooks/useVisible';
import { Button, ButtonInstance, FormInstance, Modal } from '@arco-design/web-vue';
import { InjectionKey, PropType, Ref } from 'vue';
import { useModalSubmit } from './useModalSubmit';
import { useModalTrigger } from './useModalTrigger';
import { AnForm, AnFormInstance, AnFormProps, AnFormSubmit } from './Form';
import { AnFormItemProps } from './FormItem';
export interface AnFormModalContext {
visible: Ref<boolean>;
loading: Ref<boolean>;
formRef: Ref<AnFormInstance | null>;
open: (data: Recordable) => void;
close: () => void;
submitForm: () => any | Promise<any>;
modalTitle: () => any;
modalTrigger: () => any;
onClose: () => void;
}
export const AnFormModalContextKey = Symbol('AnFormModalContextKey') as InjectionKey<AnFormModalContext>;
import { useVisible } from "@/hooks/useVisible";
import { Button, ButtonInstance, Modal } from "@arco-design/web-vue";
import { PropType } from "vue";
import { useModalSubmit } from "./useModalSubmit";
import { useModalTrigger } from "./useModalTrigger";
import { AnForm, IAnFormProps, IAnFormSubmit } from "./Form";
import { IAnFormItem } from "./FormItem";
/**
*
*/
export const AnFormModal = defineComponent({
name: 'AnFormModal',
name: "AnFormModal",
props: {
/**
*
* @default
* ```ts
* '新增'
* ```
* @default '新增'
*/
title: {
type: [String, Function] as PropType<AnFormModalTitle>,
default: '新增',
type: [String, Function] as PropType<ModalType>,
default: "新增",
},
/**
*
* @default
* ```ts
* '新增'
* ```
* @default '新增'
*/
trigger: {
type: [Boolean, String, Function, Object] as PropType<AnFormModalTrigger>,
type: [Boolean, String, Function, Object] as PropType<ModalTrigger>,
default: true,
},
/**
* Modalprops
* @example
* ```ts
* {
* closable: true
* }
* ```
*/
modalProps: {
type: Object as PropType<Omit<InstanceType<typeof Modal>['$props'], 'visible' | 'title'>>,
type: Object as PropType<ModalProps>,
},
/**
*
* @example
* ```ts
* {
* id: undefined
* }
* ```
*/
model: {
type: Object as PropType<Recordable>,
@ -75,45 +43,27 @@ export const AnFormModal = defineComponent({
},
/**
*
* @example
* ```ts
* [{
* field: 'name',
* label: '昵称',
* setter: 'input'
* }]
* ```
*/
items: {
type: Array as PropType<AnFormItemProps[]>,
type: Array as PropType<IAnFormItem[]>,
default: () => [],
},
/**
*
* @example
* ```ts
* (model) => api.user.addUser(model)
* ```
*/
submit: {
type: [String, Function] as PropType<AnFormSubmit>,
type: [String, Function] as PropType<IAnFormSubmit>,
},
/**
* Form
* @example
* ```ts
* {
* layout: 'vertical'
* }
* ```
*/
formProps: {
type: Object as PropType<Omit<FormInstance['$props'], 'model' | 'ref'>>,
type: Object as IAnFormProps,
},
},
emits: ['update:model'],
setup(props) {
const formRef = ref<AnFormInstance | null>(null);
emits: ["update:model"],
setup(props, { slots, emit }) {
const formRef = ref<InstanceType<typeof AnForm> | null>(null);
const { visible, show, hide } = useVisible();
const { modalTrigger } = useModalTrigger(props, show);
const { loading, setLoading, submitForm } = useModalSubmit(props, formRef, visible);
@ -131,13 +81,13 @@ export const AnFormModal = defineComponent({
const onClose = () => {};
const modalTitle = () => {
if (typeof props.title === 'string') {
if (typeof props.title === "string") {
return props.title;
}
return <props.title model={props.model} items={props.items}></props.title>;
};
const context: AnFormModalContext = {
return {
visible,
loading,
formRef,
@ -148,8 +98,6 @@ export const AnFormModal = defineComponent({
modalTrigger,
onClose,
};
return context;
},
render() {
return (
@ -170,7 +118,7 @@ export const AnFormModal = defineComponent({
<AnForm
ref="formRef"
model={this.model}
onUpdate:model={v => this.$emit('update:model', v)}
onUpdate:model={(v) => this.$emit("update:model", v)}
items={this.items}
formProps={this.formProps}
></AnForm>
@ -193,21 +141,21 @@ export const AnFormModal = defineComponent({
},
});
export type AnFormModalTitle = string | ((model: Recordable, items: AnFormItemProps[]) => any);
type ModalProps = Partial<Omit<InstanceType<typeof Modal>["$props"], "visible" | "title" | "onBeforeOk">>;
export type AnFormModalTrigger =
type ModalType = string | ((model: Recordable, items: IAnFormItem[]) => any);
type ModalTrigger =
| boolean
| string
| ((model: Recordable, items: AnFormItemProps[]) => any)
| ((model: Recordable, items: IAnFormItem[]) => any)
| {
text?: string;
buttonProps?: ButtonInstance['$props'];
buttonProps?: ButtonInstance["$props"];
buttonSlots?: Recordable;
};
export type AnFormModalInstance = InstanceType<typeof AnFormModal>;
export type AnFormModalProps = Pick<
AnFormModalInstance['$props'],
'title' | 'trigger' | 'modalProps' | 'model' | 'items' | 'submit' | 'formProps'
export type FormModalProps = Pick<
InstanceType<typeof AnFormModal>["$props"],
"title" | "trigger" | "modalProps" | "model" | "items" | "submit" | "formProps"
>;

View File

@ -1,8 +1,8 @@
import { Ref } from 'vue';
import { AnFormItemProps } from './FormItem';
import { IAnFormItem } from './FormItem';
import { setterMap } from './FormSetter';
export function useFormItems(items: Ref<AnFormItemProps[]>, model: Ref<Recordable>) {
export function useFormItems(items: Ref<IAnFormItem[]>, model: Ref<Recordable>) {
const getItem = (field: string) => {
return items.value.find(i => i.field === field);
};

View File

@ -1,6 +1,6 @@
import { Message } from '@arco-design/web-vue';
import { AnFormProps } from './Form';
import { AnFormItemProps } from './FormItem';
import { IAnForm } from './Form';
import { IAnFormItem } from './FormItem';
import { cloneDeep } from 'lodash-es';
const SUBMIT_ITEM = {
@ -11,9 +11,9 @@ const SUBMIT_ITEM = {
},
};
export function useFormSubmit(props: AnFormProps, validate: any, getModel: any) {
export function useFormSubmit(props: IAnForm, validate: any, getModel: any) {
const loading = ref(false);
const submitItem = ref<AnFormItemProps | null>(null);
const submitItem = ref<IAnFormItem | null>(null);
if (props.submit) {
submitItem.value = cloneDeep(SUBMIT_ITEM);

View File

@ -23,7 +23,6 @@ export function useModalTrigger(props: any, open: () => void) {
<Button type="primary" {...internal.buttonProps} onClick={open}>
{{
...internal.buttonSlots,
icon: () => <i class="icon-park-outline-add"></i>,
default: () => internal.text,
}}
</Button>

View File

@ -1,7 +1,7 @@
import { AnForm, AnFormInstance, AnFormProps } from '../components/Form';
import { AnForm, IAnForm } from '../components/Form';
import { FormItem, useItems } from './useItems';
export type FormUseOptions = Partial<Omit<AnFormProps, 'items'>> & {
export type FormUseOptions = Partial<Omit<IAnForm, 'items'>> & {
/**
*
* @example
@ -16,37 +16,45 @@ export type FormUseOptions = Partial<Omit<AnFormProps, 'items'>> & {
items?: FormItem[];
};
export function useFormProps(options: FormUseOptions): Required<AnFormProps> {
const { model = {}, items: _items = [], submit = () => null, formProps = {} } = options;
const items = useItems(_items ?? [], model);
return {
model,
items,
submit,
formProps,
};
export function useFormProps(options: FormUseOptions) {
const _model = options.model ?? {};
const _items = options.items ?? [];
const items = useItems(_items, _model);
const props = reactive({
formProps: options.formProps ?? {},
items: items.value,
submit: options.submit,
model: _model,
});
return props;
}
/**
*
*/
export const useForm = (options: FormUseOptions) => {
const props = reactive(useFormProps(options));
const formRef = ref<AnFormInstance | null>(null);
const { items: _items = [], model: _model = {}, submit, formProps: _props = {} } = options;
const items = useItems(_items, _model);
const model = ref(_model);
const formProps = ref(_props);
const formRef = ref<InstanceType<typeof AnForm> | null>(null);
const AnFormer = () => (
<AnForm
ref={(el: any) => (formRef.value = el)}
v-model:model={props.model}
items={props.items}
submit={props.submit}
formProps={props.formProps}
v-model:model={model.value}
items={items.value}
submit={submit}
formProps={formProps.value}
></AnForm>
);
return {
component: AnFormer,
model,
items,
submit,
formProps,
formRef,
props,
};
};

View File

@ -1,49 +1,40 @@
import { AnFormModal, AnFormModalProps } from '../components/FormModal';
import { useFormProps } from './useForm';
import { FormItem } from './useItems';
import { AnFormModal, FormModalProps } from "../components/FormModal";
import { useForm } from "./useForm";
import { FormItem } from "./useItems";
export type FormModalUseOptions = Partial<Omit<AnFormModalProps, 'items'>> & {
export type FormModalUseOptions = Partial<Omit<FormModalProps, "items">> & {
items: FormItem[];
};
export function useFormModalProps(options: FormModalUseOptions): AnFormModalProps {
const { items, model, formProps } = useFormProps({ ...options, submit: undefined });
const { trigger, title, submit, modalProps } = options;
return {
model,
items,
formProps,
trigger,
title,
modalProps,
submit,
};
}
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 submit = ref(options.submit);
const formRef = computed(() => modalRef.value?.formRef);
const open = (data: Recordable = {}) => modalRef.value?.open(data);
const rawProps = useFormModalProps(options);
const props = reactive(rawProps);
const component = () => {
return (
<AnFormModal
ref={(el: any) => (modalRef.value = el)}
title={props.title}
trigger={props.title}
modalProps={props.modalProps as any}
model={props.model}
items={props.items}
formProps={props.formProps}
submit={props.submit}
title={title.value}
trigger={trigger.value}
modalProps={modalProps.value as any}
model={model.value}
items={items.value}
formProps={formProps.value}
submit={submit.value}
></AnFormModal>
);
};
return {
props,
model,
items,
formProps,
component,
modalRef,
formRef,

View File

@ -1,12 +1,12 @@
import { defaultsDeep, merge, omit } from 'lodash-es';
import { AnFormItemProps, AnFormItemPropsBase } from '../components/FormItem';
import { IAnFormItem, IAnFormItemBase } from '../components/FormItem';
import { SetterItem, setterMap } from '../components/FormSetter';
import { Rule, useRules } from './useRules';
/**
*
*/
export type FormItem = Omit<AnFormItemPropsBase, 'rules'> &
export type FormItem = Omit<IAnFormItemBase, 'rules'> &
SetterItem & {
/**
*
@ -41,7 +41,7 @@ const ITEM: Partial<FormItem> = {
};
export function useItems(list: FormItem[], model: Recordable) {
const items: AnFormItemProps[] = [];
const items = ref<IAnFormItem[]>([]);
for (const item of list) {
let target: any = defaultsDeep({}, ITEM);
@ -61,7 +61,7 @@ export function useItems(list: FormItem[], model: Recordable) {
}
model[item.field] = model[item.field] ?? item.value;
items.push(target);
items.value.push(target);
}
return items;

View File

@ -1,6 +1,6 @@
import { FieldRule } from "@arco-design/web-vue";
import { has, isString } from "lodash-es";
import { AnFormItemRule } from "../components/FormItem";
import { IAnFormItemRule } from "../components/FormItem";
/**
*
@ -56,7 +56,7 @@ export type FieldStringRule = keyof typeof FieldRuleMap;
/**
*
*/
export type Rule = FieldStringRule | AnFormItemRule;
export type Rule = FieldStringRule | IAnFormItemRule;
/**
* (TS)
@ -71,7 +71,7 @@ function defineRuleMap<T extends Record<string, FieldRule>>(ruleMap: T) {
* @returns
*/
export const useRules = <T extends { required?: boolean; rules?: Rule[] }>(item: T) => {
const data: AnFormItemRule[] = [];
const data: IAnFormItemRule[] = [];
const { required, rules } = item;
if (!has(item, "required") && !has(item, "rules")) {

View File

@ -1,16 +1,5 @@
export * from './components/Form';
export * from './components/FormItem';
export * from './components/FormModal';
export * from './components/FormSetter';
export * from './components/useFormContext';
export * from './components/useFormItems';
export * from './components/useFormModel';
export * from './components/useFormRef';
export * from './components/useFormSubmit';
export * from './components/useModalSubmit';
export * from './components/useModalTrigger';
export * from './hooks/useForm';
export * from './hooks/useFormModal';
export * from './hooks/useItems';
export * from './hooks/useRules';
export * from './setters';
export * from './components/useFormContext';
export * from './components/FormItem';
export * from './components/useFormModel';

View File

@ -1,5 +1,6 @@
import { Cascader, CascaderInstance } from '@arco-design/web-vue';
import { defineSetter, initOptions } from './util';
import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
type CascaderProps = CascaderInstance['$props'];

View File

@ -1,5 +1,6 @@
import { Select, SelectInstance } from '@arco-design/web-vue';
import { defineSetter, initOptions } from './util';
import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
type SelectProps = SelectInstance['$props'];

View File

@ -1,5 +1,6 @@
import { TreeSelect, TreeSelectInstance } from '@arco-design/web-vue';
import { defineSetter, initOptions } from './util';
import { initOptions } from '../utils/initOptions';
import { defineSetter } from './util';
type TreeSelectProps = TreeSelectInstance['$props'];

View File

@ -1,5 +1,5 @@
import { Component } from 'vue';
import { AnFormItemPropsBase, AnFormItemSlot, AnFormItemFnProps } from '../components/FormItem';
import { IAnFormItemBase, IAnFormItemSlot } from '../components/FormItem';
export interface ItemSetter<P extends object, S extends string> {
/**
@ -25,42 +25,15 @@ export interface ItemSetter<P extends object, S extends string> {
* }
* ```
*/
[key in S]?: AnFormItemSlot;
[key in S]?: IAnFormItemSlot;
};
/**
*
*/
onSetup?: (args: { model: Recordable; item: AnFormItemPropsBase; items: AnFormItemPropsBase[] }) => void;
onSetup?: (args: { model: Recordable; item: IAnFormItemBase; items: IAnFormItemBase[] }) => void;
}
export function defineSetter<P extends object, S extends string>(setter: ItemSetter<P, S>) {
return setter;
}
export function initOptions({ item, model }: AnFormItemFnProps, key: string = 'options') {
const setterProps: Recordable = item.setterProps!;
if (Array.isArray(item.options) && item.setterProps) {
setterProps[key] = item.options;
return;
}
if (typeof item.options === 'function') {
setterProps[key] = reactive([]);
item.$init = async () => {
const res = await (item as any).options({ item, model });
if (Array.isArray(res)) {
setterProps[key].splice(0);
setterProps[key].push(...res);
return;
}
const data = res?.data?.data;
if (Array.isArray(data)) {
const maped = data.map((i: any) => ({ ...i, value: i.id, label: i.name }));
setterProps[key].splice(0);
setterProps[key].push(...maped);
return;
}
};
item.$init();
}
}

View File

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

View File

@ -0,0 +1,28 @@
import { IAnFormItemFnProps } from '../components/FormItem';
export function initOptions({ item, model }: IAnFormItemFnProps, key: string = 'options') {
const setterProps: Recordable = item.setterProps!;
if (Array.isArray(item.options) && item.setterProps) {
setterProps[key] = item.options;
return;
}
if (typeof item.options === 'function') {
setterProps[key] = reactive([]);
item.$init = async () => {
const res = await (item as any).options({ item, model });
if (Array.isArray(res)) {
setterProps[key].splice(0);
setterProps[key].push(...res);
return;
}
const data = res?.data?.data;
if (Array.isArray(data)) {
const maped = data.map((i: any) => ({ ...i, value: i.id, label: i.name }));
setterProps[key].splice(0);
setterProps[key].push(...maped);
return;
}
};
item.$init();
}
}

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,34 +1,38 @@
import { AnForm, AnFormInstance, AnFormProps } from '@/components/AnForm';
import { AnFormModal, AnFormModalInstance, AnFormModalProps } from '@/components/AnForm';
import { TableColumnData, TableData, Table, Button, PaginationProps, TableInstance } from '@arco-design/web-vue';
import { isArray, isFunction, merge } from 'lodash-es';
import { InjectionKey, PropType, Ref, defineComponent, ref } from 'vue';
import { PluginContainer } from '../hooks/useTablePlugin';
import { AnForm, AnFormInstance, IAnForm } from '@/components/AnForm';
import AniEmpty from '@/components/empty/AniEmpty.vue';
import { FormModalProps } from '@/components/form';
import {
TableColumnData as BaseColumn,
TableData as BaseData,
Table as BaseTable,
Button,
PaginationProps,
} from '@arco-design/web-vue';
import { isArray, isFunction, merge } from 'lodash-es';
import { PropType, defineComponent, ref } from 'vue';
import { PluginContainer } from '../hooks/useTablePlugin';
type DataFn = (filter: { page: number; size: number; [key: string]: any }) => any | Promise<any>;
export const AnTableContextKey = Symbol('AnTableContextKey') as InjectionKey<AnTableContext>;
/**
*
*/
export const AnTable = defineComponent({
name: 'AnTable',
props: {
/**
*
*/
columns: {
type: Array as PropType<TableColumnData[]>,
default: () => [],
},
/**
*
* @description
*/
source: {
type: [Array, Function] as PropType<TableData[] | DataFn>,
data: {
type: [Array, Function] as PropType<BaseData[] | DataFn>,
},
/**
*
*/
columns: {
type: Array as PropType<BaseColumn[]>,
default: () => [],
},
/**
*
@ -40,42 +44,39 @@ export const AnTable = defineComponent({
*
*/
search: {
type: Object as PropType<AnFormProps>,
type: Object as PropType<IAnForm>,
},
/**
*
*/
create: {
type: Object as PropType<AnFormModalProps>,
type: Object as PropType<FormModalProps>,
},
/**
*
*/
modify: {
type: Object as PropType<AnFormModalProps>,
type: Object as PropType<FormModalProps>,
},
/**
* Table
*/
tableProps: {
type: Object as PropType<
Omit<TableInstance['$props'], 'ref' | 'pagination' | 'loading' | 'data' | 'onPageChange' | 'onPageSizeChange'>
>,
type: Object as PropType<InstanceType<typeof BaseTable>['$props']>,
},
/**
*
*/
pluginer: {
type: Object as PropType<PluginContainer>,
required: true,
},
},
setup(props) {
const loading = ref(false);
const renderData = ref<TableData[]>([]);
const tableRef = ref<TableInstance | null>(null);
const tableRef = ref<InstanceType<typeof BaseTable>>();
const renderData = ref<BaseData[]>([]);
const searchRef = ref<AnFormInstance | null>(null);
const createRef = ref<AnFormModalInstance | null>(null);
const modifyRef = ref<AnFormModalInstance | null>(null);
const useTablePaging = () => {
const getPaging = () => {
@ -112,16 +113,16 @@ export const AnTable = defineComponent({
const paging = getPaging();
const search = searchRef.value?.getModel() ?? {};
if (isArray(props.source)) {
if (isArray(props.data)) {
// todo
}
if (isFunction(props.source)) {
if (isFunction(props.data)) {
try {
loading.value = true;
let params = { ...search, ...paging };
params = props.pluginer?.callBeforeSearchHook(params) ?? params;
const resData = await props.source(params);
const resData = await props.data(params);
const { data = [], total = 0 } = resData?.data || {};
renderData.value = data;
setPaging({ total });
@ -143,8 +144,8 @@ export const AnTable = defineComponent({
};
watchEffect(() => {
if (Array.isArray(props.source)) {
renderData.value = props.source;
if (Array.isArray(props.data)) {
renderData.value = props.data;
resetPaging();
}
});
@ -165,13 +166,11 @@ export const AnTable = defineComponent({
loadData();
};
const context: AnTableContext = {
const state = {
loading,
renderData,
tableRef,
searchRef,
createRef,
modifyRef,
renderData,
loadData,
reload,
refresh,
@ -180,18 +179,17 @@ export const AnTable = defineComponent({
props,
};
props.pluginer?.callSetupHook(context);
props.pluginer?.callSetupHook(state);
provide(AnTableContextKey, context);
provide('ref:table', { ...state, ...props });
return context;
return state;
},
render() {
(this.columns as any).instance = this;
return (
<div class="table w-full">
<div class={`mb-3 flex gap-2 toolbar justify-between`}>
{this.create && <AnFormModal {...this.create} ref="createRef"></AnFormModal>}
{this.modify && <AnFormModal {...this.modify} trigger={false} ref="modifyRef"></AnFormModal>}
<div class={`mb-3 flex toolbar justify-between`}>
{this.pluginer?.actions && (
<div class={`flex-1 flex gap-2 items-center`}>
{this.pluginer.actions.map(Action => (
@ -199,8 +197,8 @@ export const AnTable = defineComponent({
))}
</div>
)}
{this.search && (
<div>
<div>
{this.search && (
<AnForm
ref="searchRef"
v-model:model={this.search.model}
@ -218,18 +216,14 @@ export const AnTable = defineComponent({
),
}}
</AnForm>
</div>
)}
{this.pluginer?.widgets && (
<div class="flex gap-2">
{this.pluginer.widgets.map(Widget => (
<Widget />
))}
</div>
)}
)}
</div>
<div class="flex gap-2 ml-2">
<div class="flex gap-1">{this.pluginer?.widgets && this.pluginer.widgets?.map(Widget => <Widget />)}</div>
</div>
</div>
<Table
<BaseTable
row-key="id"
bordered={false}
{...this.$attrs}
@ -246,7 +240,7 @@ export const AnTable = defineComponent({
empty: () => <AniEmpty />,
...this.$slots,
}}
</Table>
</BaseTable>
</div>
);
},
@ -255,57 +249,9 @@ export const AnTable = defineComponent({
/**
*
*/
export type AnTableInstance = InstanceType<typeof AnTable>;
export type TableInstance = InstanceType<typeof AnTable>;
/**
*
*/
export type AnTableProps = Pick<
AnTableInstance['$props'],
'source' | 'columns' | 'search' | 'paging' | 'create' | 'modify' | 'tableProps' | 'pluginer'
>;
export interface AnTableContext {
/**
*
*/
loading: Ref<boolean>;
/**
*
*/
tableRef: Ref<TableInstance | null>;
/**
*
*/
searchRef: Ref<AnFormInstance | null>;
/**
*
*/
createRef: Ref<AnFormModalInstance | null>;
/**
*
*/
modifyRef: Ref<AnFormModalInstance | null>;
/**
*
*/
renderData: Ref<TableData[]>;
/**
*
*/
loadData: () => Promise<void>;
/**
*
*/
reload: () => Promise<void>;
/**
*
*/
refresh: () => Promise<void>;
/**
*
*/
props: AnTableProps;
onPageChange: any;
onPageSizeChange: any;
}
export type TableProps = TableInstance['$props'];

View File

@ -0,0 +1,178 @@
import { Button, Checkbox, Divider, InputNumber, Popover, Scrollbar, Tag } from '@arco-design/web-vue';
import { PropType } from 'vue';
interface Item {
dataIndex: string;
enable: boolean;
autoWidth: boolean;
width: number;
editable: boolean;
title: string;
}
export const TableColumnConfig = defineComponent({
props: {
columns: {
type: Object as PropType<any[]>,
required: true,
},
},
setup(props) {
const checkAll = ref(false);
const visible = ref(false);
const items = ref<Item[]>([]);
const checked = computed(() => items.value.filter(i => i.enable));
const indeterminate = computed(() => {
const check = checked.value.length;
const total = items.value.length;
return 0 < check && check < total;
});
watch(
() => visible.value,
value => {
if (value) {
init();
} else {
}
}
);
const init = () => {
const list: Item[] = [];
for (const column of props.columns) {
list.push({
dataIndex: column.dataIndex,
title: column.title,
enable: true,
autoWidth: !column.width,
width: column.width ?? 60,
editable: !column.configable,
});
}
items.value = list;
};
const onItemChange = () => {
if (checked.value.length === 0) {
checkAll.value = false;
return;
}
if (checked.value.length === items.value.length) {
checkAll.value = true;
}
};
const onCheckAllChange = (value: any) => {
for (const item of items.value) {
if (item.editable) {
item.enable = value;
}
}
};
const onReset = () => {
visible.value = false;
};
const onConfirm = () => {
visible.value = false;
};
const onItemUp = (index: number) => {
[items.value[index - 1], items.value[index]] = [items.value[index], items.value[index - 1]];
};
const onItemDown = (index: number) => {
[items.value[index + 1], items.value[index]] = [items.value[index], items.value[index + 1]];
};
return () => (
<Popover v-model:popup-visible={visible.value} position="br" trigger="click">
{{
default: () => (
<Button class="float-right">{{ icon: () => <span class="icon-park-outline-config"></span> }}</Button>
),
content: () => (
<>
<div class="mb-1 leading-none border-b border-gray-100 pb-3"></div>
<Scrollbar outer-class="h-96 overflow-hidden" class="h-full overflow-auto">
<ul class="grid m-0 p-0 divide-y divide-gray-100 w-[700px] overflow-auto overscroll-contain">
{items.value.map((item, index) => (
<li
key={item.dataIndex}
class="group flex items-center justify-between gap-6 py-2 pr-8 select-none"
>
<div class="flex-1 flex justify-between gap-2">
<Checkbox v-model={item.enable} disabled={!item.editable} onChange={onItemChange}>
{item.title}
</Checkbox>
<span class="hidden group-hover:inline-block ml-4">
<Button
type="text"
shape="circle"
size="mini"
disabled={index == 0}
onClick={() => onItemUp(index)}
>
{{ icon: () => <i class="icon-park-outline-arrow-up"></i> }}
</Button>
<Button
type="text"
shape="circle"
size="mini"
disabled={index == items.value.length - 1}
onClick={() => onItemDown(index)}
>
{{ icon: () => <i class="icon-park-outline-arrow-down"></i> }}
</Button>
</span>
</div>
<div class="flex gap-2 items-center">
<Checkbox v-model={item.autoWidth} disabled={!item.editable}>
{{
checkbox: ({ checked }: any) => (
<Tag checked={checked} checkable={item.editable} color="blue">
</Tag>
),
}}
</Checkbox>
<Divider direction="vertical" margin={8}></Divider>
<InputNumber
size="small"
v-model={item.width}
disabled={item.autoWidth || !item.editable}
min={60}
step={10}
class="!w-20"
/>
<span class="text-gray-400"></span>
</div>
</li>
))}
</ul>
</Scrollbar>
<div class="mt-4 flex gap-2 items-center justify-between">
<div class="flex items-center">
<Checkbox indeterminate={indeterminate.value} v-model={checkAll.value} onChange={onCheckAllChange}>
</Checkbox>
<span class="text-xs text-gray-400 ml-1">
({checked.value.length}/{items.value.length})
</span>
</div>
<div class="space-x-2">
<Button onClick={onReset}></Button>
<Button type="primary" onClick={onConfirm}>
</Button>
</div>
</div>
</>
),
}}
</Popover>
);
},
});

View File

@ -1,36 +0,0 @@
import { dayjs } from '@/libs/dayjs';
import { TableColumn } from '../hooks/useTableColumn';
export function useUpdateColumn(extra: TableColumn = {}): TableColumn {
return {
title: '更新用户',
dataIndex: 'createdAt',
width: 190,
render: ({ record }) => (
<div class="flex flex-col overflow-hidden">
<span>{record.updatedBy ?? '无'}</span>
<span class="text-gray-400 text-xs truncate">
{dayjs(record.updatedAt).format()}
</span>
</div>
),
...extra,
};
}
export function useCreateColumn(extra: TableColumn = {}): TableColumn {
return {
title: '创建用户',
dataIndex: 'createdAt',
width: 190,
render: ({ record }) => (
<div class="flex flex-col overflow-hidden">
<span>{record.createdBy ?? '无'}</span>
<span class="text-gray-400 text-xs truncate">
{dayjs(record.createdAt).format()}
</span>
</div>
),
...extra,
};
}

View File

@ -1,40 +0,0 @@
import { FormModalUseOptions, useFormModalProps } from '@/components/AnForm';
export type UseCreateFormOptions = FormModalUseOptions & {
/**
*
* @description `modalProps.width` 便
* @example
* ```ts
* 580
* ```
*/
width?: number;
/**
*
* @description `formProps.class` 便
* @example
* ```ts
* 'grid grid-cols-2'
* ```
*/
formClass?: unknown;
};
export function useCreateForm(options: UseCreateFormOptions) {
if (options.width) {
if (!options.modalProps) {
(options as any).modalProps = {};
}
(options.modalProps as any).width = options.width;
delete options.width;
}
if (options.formClass) {
if (!options.formProps) {
(options as any).formProps = {};
}
options.formProps!.class = options.formClass;
delete options.formClass;
}
return useFormModalProps(options);
}

View File

@ -1,89 +1,33 @@
import { FormItem, FormModalUseOptions, useFormModalProps, AnFormModalProps } from '@/components/AnForm';
import { merge } from 'lodash-es';
import { ExtendFormItem } from './useSearchForm';
import { TableUseOptions } from './useTable';
import { cloneDeep, merge } from "lodash-es";
import { IAnFormItem } from "../../AnForm/components/FormItem";
import { FormModalProps } from "../../AnForm/components/FormModal";
import { FormModalUseOptions } from "../../AnForm/hooks/useFormModal";
import { ExtendFormItem } from "./useSearchForm";
export type ModifyForm = Omit<FormModalUseOptions, 'items' | 'trigger'> & {
/**
*
* @description `modalProps.width` 便
* @example
* ```ts
* 580
* ```
*/
width?: number;
/**
*
* @description `formProps.class` 便
* @example
* ```ts
* 'grid grid-cols-2'
* ```
*/
formClass?: unknown;
export type ModifyForm = Omit<FormModalUseOptions, "items"> & {
/**
*
* @default
* ```ts
* false
* ```
*/
extend?: boolean;
/**
*
* ```tsx
* [{
* extend: 'name', // 从 create.items 中继承
* }]
* ```
*/
items?: ExtendFormItem[];
};
export function useModifyForm(options: TableUseOptions): AnFormModalProps | undefined {
const { create, modify } = options;
if (!modify) {
return undefined;
}
let result: FormModalUseOptions = { items: [] };
if (modify.extend && create) {
result = merge({}, create);
}
result = merge(result, modify);
if (modify.items) {
const items: FormItem[] = [];
const createItemMap: Record<string, FormItem> = {};
for (const item of create?.items ?? []) {
createItemMap[item.field] = item;
}
for (let item of modify.items ?? []) {
if (item.extend) {
item = merge({}, createItemMap[item.field!] ?? {}, item);
export function useModifyForm(form: ModifyForm, create: FormModalProps) {
const { extend, items, ...rest } = form;
let result = {};
if (extend) {
cloneDeep(create ?? {});
const createItems = create.items;
const modifyItems = form.items;
if (modifyItems && createItems) {
for (let i = 0; i < modifyItems.length; i++) {
if (modifyItems[i].extend) {
modifyItems[i] = merge({}, createItems[i], modifyItems[i]);
}
}
items.push(item as any);
}
if (items.length) {
result.items = items;
}
}
if (modify.width || create?.width) {
if (!result.modalProps) {
(result as any).modalProps = {};
}
(result.modalProps as any).width = modify.width || create?.width;
}
if (modify.formClass || create?.formClass) {
if (!result.formProps) {
(result as any).formProps = {};
}
result.formProps!.class = modify.formClass || create?.formClass;
}
return useFormModalProps(result);
}

View File

@ -1,14 +1,13 @@
import { defaultsDeep, isArray, merge } from 'lodash-es';
import { AnFormProps, FormUseOptions, AnFormItemProps, FormItem, useItems } from '@/components/AnForm';
import { FormUseOptions } from '../../AnForm';
import { IAnFormItem } from '../../AnForm/components/FormItem';
import { FormItem, useItems } from '../../AnForm/hooks/useItems';
export type ExtendFormItem = Partial<
FormItem & {
/**
*
* @example
* ```ts
* 'name'
* ```
* @example 'name'
*/
extend: string;
}
@ -17,18 +16,12 @@ export type ExtendFormItem = Partial<
type SearchFormItem = ExtendFormItem & {
/**
*
* @default
* ```ts
* false
* ```
* @default false
*/
searchable?: boolean;
/**
*
* @default
* ```ts
* false
* ```
* @default false
*/
enterable?: boolean;
};
@ -36,29 +29,20 @@ type SearchFormItem = ExtendFormItem & {
export type SearchFormObject = Omit<FormUseOptions, 'items' | 'submit'> & {
/**
*
* @example
* ```ts
* [{
* extend: 'name' // 从 create.items 继承
* }]
* ```
*/
items?: SearchFormItem[];
/**
*
* @default
* ```tsx
* false
* ```
* @default false
*/
hideSearch?: boolean;
};
export type SearchForm = SearchFormObject | SearchFormItem[];
export function useSearchForm(search?: SearchForm, extendItems: AnFormItemProps[] = []): AnFormProps | undefined {
export function useSearchForm(search?: SearchForm, extendItems: IAnFormItem[] = []) {
if (!search) {
return undefined;
return ref();
}
if (isArray(search)) {
@ -66,15 +50,15 @@ export function useSearchForm(search?: SearchForm, extendItems: AnFormItemProps[
}
const { items: _items = [], hideSearch, model: _model, formProps: _formProps } = search;
const extendMap = extendItems.reduce((m, v) => ((m[v.field] = v), m), {} as Record<string, AnFormItemProps>);
const extendMap = extendItems.reduce((m, v) => ((m[v.field] = v), m), {} as Record<string, IAnFormItem>);
const props = {
items: [] as AnFormItemProps[],
const props = ref({
items: [] as IAnFormItem[],
model: _model ?? {},
formProps: defaultsDeep({}, _formProps, { layout: 'inline' }),
};
});
const defualts: Partial<AnFormItemProps> = {
const defualts: Partial<IAnFormItem> = {
setter: 'input',
itemProps: {
hideLabel: true,
@ -82,13 +66,13 @@ export function useSearchForm(search?: SearchForm, extendItems: AnFormItemProps[
setterProps: {},
};
const items: AnFormItemProps[] = [];
const items: any[] = [];
for (const _item of _items) {
const { searchable, enterable, field, extend, ...itemRest } = _item;
if ((field || extend) === 'submit' && hideSearch) {
continue;
}
let item: AnFormItemProps = defaultsDeep({}, itemRest, defualts);
let item: IAnFormItem = defaultsDeep({}, itemRest, defualts);
if (extend) {
const extendItem = extendMap[extend];
if (extendItem) {
@ -96,10 +80,10 @@ export function useSearchForm(search?: SearchForm, extendItems: AnFormItemProps[
}
}
if (searchable) {
(item as any).setterProps.onSearch = () => null;
(item as any).nodeProps.onSearch = () => null;
}
if (enterable) {
(item as any).setterProps.onPressEnter = () => null;
(item as any).nodeProps.onPressEnter = () => null;
}
if (item.setterProps) {
(item.setterProps as any).placeholder = (item.setterProps as any).placeholder ?? item.label;
@ -107,7 +91,7 @@ export function useSearchForm(search?: SearchForm, extendItems: AnFormItemProps[
items.push(item);
}
props.items = useItems(items, props.model);
props.value.items = useItems(items, props.value.model).value;
return props;
}

View File

@ -1,12 +1,11 @@
import { useFormModalProps } from '@/components/AnForm';
import { AnTable, AnTableInstance, AnTableProps } from '../components/Table';
import { ModifyForm, useModifyForm } from './useModiyForm';
import { FormModalUseOptions, useFormModal } from '../../AnForm/hooks/useFormModal';
import { AnTable, TableProps } from '../components/Table';
import { ModifyForm } from './useModiyForm';
import { SearchForm, useSearchForm } from './useSearchForm';
import { TableColumn, useTableColumns } from './useTableColumn';
import { AnTablePlugin, PluginContainer } from './useTablePlugin';
import { UseCreateFormOptions } from './useCreateForm';
export interface TableUseOptions extends Pick<AnTableProps, 'source' | 'tableProps' | 'paging'> {
export interface TableUseOptions extends Pick<TableProps, 'data' | 'tableProps' | 'paging'> {
/**
* ID
* @example
@ -28,8 +27,8 @@ export interface TableUseOptions extends Pick<AnTableProps, 'source' | 'tablePro
* @example
* ```ts
* [{
* dataIndex: 'title',
* title: '标题'
* dataIndex: 'title',
* title: '标题'
* }]
* ```
*/
@ -39,9 +38,9 @@ export interface TableUseOptions extends Pick<AnTableProps, 'source' | 'tablePro
* @example
* ```ts
* [{
* field: 'name',
* label: '用户名称',
* setter: 'input'
* field: 'name',
* label: '用户名称',
* setter: 'input'
* }]
* ```
*/
@ -57,7 +56,7 @@ export interface TableUseOptions extends Pick<AnTableProps, 'source' | 'tablePro
* }
* ```
*/
create?: UseCreateFormOptions;
create?: FormModalUseOptions;
/**
*
* @example
@ -72,54 +71,34 @@ export interface TableUseOptions extends Pick<AnTableProps, 'source' | 'tablePro
modify?: ModifyForm;
}
export function useTableProps(options: TableUseOptions): AnTableProps {
const { source, tableProps } = options;
const columns = useTableColumns(options.columns ?? []);
const paging = { hide: false, showTotal: true, showPageSize: true, ...(options.paging ?? {}) };
const search = options.search && useSearchForm(options.search);
const create = options.create && useFormModalProps(options.create);
const modify = options.modify && useModifyForm(options);
return {
tableProps,
columns,
source,
search,
paging,
create,
modify,
};
}
export function useTable(options: TableUseOptions) {
const tableRef = ref<AnTableInstance | null>(null);
if (!options.plugins) {
options.plugins = [];
}
const pluginer = new PluginContainer(options.plugins);
const pluginer = new PluginContainer(options.plugins ?? []);
options = pluginer.callOptionsHook(options);
const rawProps = useTableProps(options);
const props = reactive(rawProps);
const { columns } = useTableColumns(options.columns ?? []);
const data = ref(options.data);
const pagination = ref({ hide: false, showTotal: true, showPageSize: true, ...(options.paging ?? {}) });
const tableProps = ref(options.tableProps ?? {});
const tableRef = ref<InstanceType<typeof AnTable> | null>(null);
const searchProps = useSearchForm(options.search);
// const create = options.create && useFormModal(options.create);
const AnTabler = () => (
<AnTable
ref={(el: any) => (tableRef.value = el)}
tableProps={props.tableProps}
columns={props.columns}
source={props.source}
paging={props.paging}
search={props.search}
create={props.create as any}
modify={props.modify as any}
columns={columns.value}
data={data.value}
paging={pagination.value}
tableProps={tableProps.value}
search={searchProps.value}
pluginer={pluginer}
></AnTable>
);
return {
component: AnTabler,
columns,
tableRef,
props,
};
}

View File

@ -23,14 +23,6 @@ interface TableColumnButton {
* ```
*/
type?: 'modify' | 'delete';
/**
*
* @example
* ```ts
* '确定删除吗?'
* ```
*/
confirm?: string;
/**
*
* @example
@ -67,7 +59,7 @@ interface TableColumnButton {
* (props) => api.user.rmUser(props.record.id)
* ```
*/
onClick?: (props: any) => any | Promise<any>;
onClick?: (props: any) => void;
}
interface TableButtonColumn {
@ -79,11 +71,7 @@ interface TableButtonColumn {
*
* @example
* ```ts
* [{
* type: 'delete',
* text: '删除',
* onClick: (args) => api.user.rmUser(args.record.id)
* }]
* [{ text: '删除', onClick: (args) => api.user.rmUser(args.record.id) }]
* ```
*/
buttons: TableColumnButton[];
@ -113,25 +101,30 @@ export type TableColumn = TableColumnData &
};
export function useTableColumns(data: TableColumn[]) {
const columns: TableColumnData[] = [];
const columns = ref<TableColumnData[]>([]);
// for (let column of data) {
// if (column.type === "index") {
// column = useTableIndexColumn(column);
// }
for (let column of data) {
// if (column.type === "index") {
// column = useTableIndexColumn(column);
// }
if (column.type === 'button') {
column = useTableButtonColumn(column);
}
// if (column.type === "dropdown") {
// column = useTableDropdownColumn(column);
// }
columns.push(column);
columns.value.push(column);
}
return columns;
// if (column.type === "dropdown") {
// column = useTableDropdownColumn(column);
// }
// columns.push({ ...config.columnBase, ...column });
// }
return {
columns,
};
}
function useTableIndexColumn() {}

View File

@ -0,0 +1,179 @@
import { Button, Checkbox, Divider, InputNumber, Popover, Scrollbar, Tag } from '@arco-design/web-vue';
import { PropType } from 'vue';
interface Item {
dataIndex: string;
enable: boolean;
autoWidth: boolean;
width: number;
editable: boolean;
title: string;
}
export const TableColumnConfig = defineComponent({
props: {
columns: {
type: Object as PropType<any[]>,
required: true,
},
},
setup(props) {
const checkAll = ref(false);
const visible = ref(false);
const items = ref<Item[]>([]);
const checked = computed(() => items.value.filter(i => i.enable));
const indeterminate = computed(() => {
const check = checked.value.length;
const total = items.value.length;
return 0 < check && check < total;
});
watch(
() => visible.value,
value => {
if (value) {
init();
} else {
}
}
);
const init = () => {
const list: Item[] = [];
for (const column of props.columns) {
list.push({
dataIndex: column.dataIndex,
title: column.title,
enable: true,
autoWidth: !column.width,
width: column.width ?? 60,
editable: !column.configable,
});
}
items.value = list;
};
const onItemChange = () => {
if (checked.value.length === 0) {
checkAll.value = false;
return;
}
if (checked.value.length === items.value.length) {
checkAll.value = true;
}
};
const onCheckAllChange = (value: any) => {
for (const item of items.value) {
if (item.editable) {
item.enable = value;
}
}
};
const onReset = () => {
visible.value = false;
};
const onConfirm = () => {
visible.value = false;
};
const onItemUp = (index: number) => {
[items.value[index - 1], items.value[index]] = [items.value[index], items.value[index - 1]];
};
const onItemDown = (index: number) => {
[items.value[index + 1], items.value[index]] = [items.value[index], items.value[index + 1]];
};
return () => (
<Popover v-model:popup-visible={visible.value} position="br" trigger="click">
{{
default: () => (
<Button class="float-right">{{ icon: () => <span class="icon-park-outline-config"></span> }}</Button>
),
content: () => (
<>
<div class="mb-1 leading-none border-b border-gray-100 pb-3"></div>
<Scrollbar outer-class="h-96 overflow-hidden" class="h-full overflow-auto">
<ul class="grid m-0 p-0 divide-y divide-gray-100 w-[700px] overflow-auto overscroll-contain">
{items.value.map((item, index) => (
<li
key={item.dataIndex}
class="group flex items-center justify-between gap-6 py-2 pr-8 select-none"
>
<div class="flex-1 flex justify-between gap-2">
<Checkbox v-model={item.enable} disabled={!item.editable} onChange={onItemChange}>
{item.title}
</Checkbox>
<span class="hidden group-hover:inline-block ml-4">
<Button
type="text"
shape="circle"
size="mini"
disabled={index == 0}
onClick={() => onItemUp(index)}
>
{{ icon: () => <i class="icon-park-outline-arrow-up"></i> }}
</Button>
<Button
type="text"
shape="circle"
size="mini"
disabled={index == items.value.length - 1}
onClick={() => onItemDown(index)}
>
{{ icon: () => <i class="icon-park-outline-arrow-down"></i> }}
</Button>
</span>
</div>
<div class="flex gap-2 items-center">
<Checkbox v-model={item.autoWidth} disabled={!item.editable}>
{{
checkbox: ({ checked }: any) => (
<Tag checked={checked} checkable={item.editable} color="blue">
</Tag>
),
}}
</Checkbox>
<Divider direction="vertical" margin={8}></Divider>
<InputNumber
size="small"
v-model={item.width}
disabled={item.autoWidth || !item.editable}
min={60}
step={10}
class="!w-20"
/>
<span class="text-gray-400"></span>
</div>
</li>
))}
</ul>
</Scrollbar>
<div class="mt-4 flex gap-2 items-center justify-between">
<div class="flex items-center">
<Checkbox indeterminate={indeterminate.value} v-model={checkAll.value} onChange={onCheckAllChange}>
</Checkbox>
<span class="text-xs text-gray-400 ml-1">
({checked.value.length}/{items.value.length})
</span>
</div>
<div class="space-x-2">
<Button onClick={onReset}></Button>
<Button type="primary" onClick={onConfirm}>
</Button>
</div>
</div>
</>
),
}}
</Popover>
);
},
});

View File

@ -1,23 +1,14 @@
import { Component } from 'vue';
import { AnTableContext } from '../components/Table';
import { TableUseOptions } from './useTable';
import { TableColumn } from './useTableColumn';
import { useTableRefresh } from '../plugins/useTableRefresh';
import { useColumnConfig } from '../plugins/useTableConfig';
import { useRowFormat } from '../plugins/useRowFormat';
import { useRowDelete } from '../plugins/useRowDelete';
import { useRowModify } from '../plugins/useRowModify';
export interface AnTablePlugin {
/**
* ID()
* @example
* ```ts
* 'refresh'
* 'Plugin:Refresh'
* ```
*/
id: string;
/**
* 使
* @example
@ -26,42 +17,22 @@ export interface AnTablePlugin {
* ```
*/
provide?: Recordable;
/**
*
* @description `setup`
*/
onSetup?: (context: AnTableContext) => void;
onSetup?: (context: any) => void;
/**
*
* @description
*/
options?: (options: TableUseOptions) => TableUseOptions | null | undefined | void;
/**
*
*/
column?: (column: TableColumn) => TableColumn;
/**
*
* @example
* ```tsx
* () => <Button></Button>
* ```
*/
widget?: () => (props: any) => any | Component;
widget?: () => any;
/**
*
* @example
* ```tsx
* () => <Button></Button>
* ```
*/
action?: () => (props: any) => any | Component;
action?: () => any;
/**
*
*
@ -76,20 +47,19 @@ export class PluginContainer {
widgets: any[] = [];
constructor(private plugins: AnTablePlugin[]) {
this.plugins.unshift(useTableRefresh(), useColumnConfig(), useRowFormat(), useRowDelete(), useRowModify());
for (const plugin of plugins) {
const action = plugin.action?.();
const widget = plugin.widget?.();
if (action) {
this.actions.push(action);
}
const widget = plugin.widget?.();
if (widget) {
this.widgets.push(widget);
}
}
}
callSetupHook(context: AnTableContext) {
callSetupHook(context: any) {
for (const plugin of this.plugins) {
plugin.onSetup?.(context);
}

View File

@ -1,14 +1 @@
export * from './components/column';
export * from './components/Table';
export * from './hooks/useTable';
export * from './hooks/useTablePlugin';
export * from './hooks/useTableColumn';
export * from './hooks/useSearchForm';
export * from './hooks/useModiyForm';
export * from './plugins/useTableConfig';
export * from './plugins/useTableRefresh';
export * from './plugins/useTableSelect';
export * from './plugins/useTableDelete';
export * from './plugins/useRowDelete';
export * from './plugins/useRowModify';
export * from './plugins/useRowFormat';

View File

@ -1,7 +1,6 @@
import { Button, Checkbox, Divider, InputNumber, Popover, Scrollbar, Tag } from '@arco-design/web-vue';
import { PropType } from 'vue';
import { AnTablePlugin } from '../hooks/useTablePlugin';
import { AnTableContext } from '../components/Table';
interface Item {
dataIndex: string;
@ -184,14 +183,14 @@ export const TableColumnConfig = defineComponent({
* @description ID
*/
export function useColumnConfig(): AnTablePlugin {
let context: AnTableContext;
let context: any;
return {
id: 'columnconfig',
onSetup(ctx) {
context = ctx;
id: "columnconfig",
onSetup(args) {
context = args;
},
widget() {
return () => <TableColumnConfig columns={context.props.columns!} />;
return () => <TableColumnConfig columns={context.props.columns} />;
},
};
}

View File

@ -1,13 +1,12 @@
import { Button } from '@arco-design/web-vue';
import { AnTablePlugin } from '../hooks/useTablePlugin';
import { AnTableContext } from '../components/Table';
/**
*
* @description
*/
export function useTableRefresh(): AnTablePlugin {
let context: AnTableContext;
export function useRefresh(): AnTablePlugin {
let context: any = {};
return {
id: 'refresh',

View File

@ -1,42 +0,0 @@
import { delConfirm, delOptions } from '@/utils';
import { AnTableContext } from '../components/Table';
import { AnTablePlugin } from '../hooks/useTablePlugin';
import { Message } from '@arco-design/web-vue';
export function useRowDelete(): AnTablePlugin {
let ctx: AnTableContext;
return {
id: 'rowDelete',
onSetup(context) {
ctx = context;
},
options(options) {
for (const column of options.columns ?? []) {
if (column.type !== 'button') {
continue;
}
const btn = column.buttons.find(i => i.type === 'delete');
if (!btn) {
continue;
}
const onClick = btn.onClick;
btn.onClick = async props => {
let confirm = btn.confirm ?? {};
if (typeof confirm === 'string') {
confirm = { content: confirm };
}
delConfirm({
...delOptions,
...confirm,
async onBeforeOk() {
const res: any = await onClick?.(props);
const msg = res?.data?.message;
msg && Message.success(`提示: ${msg}`);
ctx.refresh();
},
});
};
}
},
};
}

View File

@ -1,15 +0,0 @@
import { AnTablePlugin } from '../hooks/useTablePlugin';
export function useRowFormat(): AnTablePlugin {
return {
id: 'format',
options(options) {
for (const column of options.columns ?? []) {
if (column.render) {
continue;
}
column.render = ({ record, column }) => record[column.dataIndex!] ?? '-';
}
},
};
}

View File

@ -1,28 +0,0 @@
import { AnTableContext } from '../components/Table';
import { AnTablePlugin } from '../hooks/useTablePlugin';
export function useRowModify(): AnTablePlugin {
let ctx: AnTableContext;
return {
id: 'rowModify',
onSetup(context) {
ctx = context;
},
options(options) {
for (const column of options.columns ?? []) {
if (column.type !== 'button') {
continue;
}
const btn = column.buttons.find(i => i.type === 'modify');
if (!btn) {
continue;
}
const onClick = btn.onClick;
btn.onClick = async props => {
const { modifyRef } = ctx ?? {};
modifyRef?.value?.open(props.record);
};
}
},
};
}

View File

@ -16,24 +16,13 @@ const defaults: TableUseOptions = {
},
};
interface UseTableSelectOptions {
key?: string;
mode?: 'key' | 'row' | 'both';
}
/**
*
* @description 使
*/
export function useTableSelect({ key = 'id', mode = 'key' }: UseTableSelectOptions = {}): AnTablePlugin {
const selectedKeys = ref<(string | number)[]>([]);
const selectedRows = ref<any[]>([]);
export function useSelection<T extends any>({ key = 'id', mode = 'key' } = {}): AnTablePlugin {
const selected = ref<T[]>([]);
return {
id: 'selection',
provide: {
selectedKeys,
selectedRows,
selected,
},
options(options) {
const opts: TableUseOptions = defaultsDeep({}, defaults);
@ -42,24 +31,24 @@ export function useTableSelect({ key = 'id', mode = 'key' }: UseTableSelectOptio
opts.tableProps!.rowKey = key;
}
if (mode === 'both' || mode === 'key') {
if (mode === 'key') {
opts.tableProps!.onSelectionChange = rowkeys => {
selectedKeys.value = rowkeys;
selected.value = rowkeys as any[];
};
}
if (mode === 'both' || mode === 'row') {
if (mode === 'row') {
opts.tableProps!.onSelect = (rowkeys, rowkey, record) => {
const index = selectedRows.value.findIndex((i: any) => i[key] == record[key]);
const index = selected.value.findIndex((i: any) => i[key] == record[key]);
if (index > -1) {
selectedRows.value.splice(index, 1);
selected.value.splice(index, 1);
}
};
opts.tableProps!.onSelectAll = checked => {
if (checked) {
selectedRows.value = cloneDeep([]);
selected.value = cloneDeep([]);
} else {
selectedRows.value = [];
selected.value = [];
}
};
}

View File

@ -1,48 +0,0 @@
import { Ref } from 'vue';
import { AnTableContext } from '../components/Table';
import { AnTablePlugin } from '../hooks/useTablePlugin';
import { useTableSelect } from './useTableSelect';
import { delConfirm, delOptions, sleep } from '@/utils';
import { Button, Message } from '@arco-design/web-vue';
export function useTableDelete(): AnTablePlugin {
let selected: Ref<any[]>;
let context: AnTableContext;
return {
id: 'deletemany',
options(options) {
let selectPlugin = options.plugins?.find(i => i.id === 'selection');
if (!selectPlugin) {
selectPlugin = useTableSelect();
options.plugins!.push(selectPlugin);
}
selected = selectPlugin.provide!.selectedKeys;
return options;
},
action() {
const onClick = async (props: any) => {
delConfirm({
...delOptions,
content: '危险操作,确定删除所选数据吗?',
async onBeforeOk() {
await sleep(3000);
const res: any = await onClick?.(props);
const msg = res?.data?.message;
msg && Message.success(`提示: ${msg}`);
selected.value = [];
context.refresh();
},
});
};
return props => (
<Button status="danger" disabled={!selected.value.length} onClick={() => onClick(props)}>
{{
icon: () => <i class="icon-park-outline-delete" />,
default: () => '删除',
}}
</Button>
);
},
};
}

View File

@ -0,0 +1,76 @@
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";
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 PropType<Omit<BaseFormInstance["$props"], "model">>,
},
},
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 (
<BaseForm 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>
))}
</BaseForm>
);
},
});
export type AnFormInstance = InstanceType<typeof AnForm>;
export type AnFormProps = AnFormInstance["$props"];
export type IAnForm = Pick<AnFormProps, "model" | "items" | "submit" | "formProps">;
export type IAnFormSubmit = (model: Recordable, items: IAnFormItem) => any;

View File

@ -0,0 +1,131 @@
import { FormItem as BaseFormItem, FieldRule, FormItemInstance } from "@arco-design/web-vue";
import { isFunction } from "lodash-es";
import { PropType } from "vue";
import { NodeType, NodeUnion, nodeMap } from "../nodes";
/**
*
*/
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 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} />;
}
};
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 = {
/**
*
*/
field: string;
/**
* `FormItem`
*/
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;
init?: any;
};
export type IAnFormItem = IAnFormItemBase & 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

@ -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("FormKey") as InjectionKey<FormContextInterface>;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,25 @@
import { FormInstance } from "@arco-design/web-vue";
import { FormItem, FormItemFnArg } from "./FormItem";
type FormInstanceProps = Partial<Omit<FormInstance["$props"], "model">>;
export type UseForm = {
/**
*
*/
model?: Recordable;
/**
*
*/
items?: FormItem[];
/**
*
* @description
*/
submit?: string | ((arg: any) => PromiseLike<any>);
/**
*
* @description
*/
formProps?: FormInstanceProps;
};

View File

@ -0,0 +1,92 @@
import { FormItemInstance } from "@arco-design/web-vue";
import { NodeType, NodeUnion } from "../../nodes";
import { Rule } from "../useRules";
import { IAnFormItem, IAnFormItemBoolFn, IAnFormItemElemFn } from "../../components/FormItem";
/**
*
*/
export type FormItemFnArg<T = FormItem> = {
item: T;
items: T[];
model: Recordable;
};
/**
*
*/
type BaseFormItem = {
/**
*
* @example
* ```typescript
* '[v1, v2]' => { v1, v2 }
* ```
*/
field: string;
/**
*
* @description model
*/
value?: any;
/**
*
* @description
*/
render?: NodeType | IAnFormItemElemFn;
/**
*
* @description FormItemlabel
*/
label?: string | IAnFormItemElemFn;
/**
*
* @description FormItemhelp
*/
help?: string | IAnFormItemElemFn;
/**
*
* @description FormItemextra
*/
extra?: string | IAnFormItemElemFn;
/**
*
* @description false
*/
required?: boolean;
/**
*
* @description ()
*/
rules?: Rule[];
/**
* `FormItem`
* @description fieldlabelrequiredrulesdisabled
*/
itemProps?: Omit<FormItemInstance["$props"], "field" | "label" | "required" | "rules" | "disabled">;
/**
*
* @description
*/
visible?: IAnFormItemBoolFn;
/**
*
* @description
*/
disable?: IAnFormItemBoolFn;
};
/**
*
*/
export type FormItem = BaseFormItem & NodeUnion;

View File

@ -0,0 +1,44 @@
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) => {
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);
return {
model,
items,
formProps,
submit,
};
};

View File

@ -0,0 +1,55 @@
import { defaultsDeep, merge, omit } from "lodash-es";
import { NodeType, nodeMap } from "../nodes";
import { FormItem } from "./types/FormItem";
import { useRules } from "./useRules";
import { IAnFormItem } from "../components/FormItem";
const ITEM: Partial<FormItem> = {
render: "input",
itemProps: {},
};
const SUBMIT_ITEM: FormItem = {
field: "id",
render: "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.render || typeof item.render === "string") {
const defaults = nodeMap[item.render ?? "input"];
if (defaults) {
defaultsDeep(target, defaults);
}
}
if (item.render === "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, nodeMap.submit));
}
return items;
}

View File

@ -0,0 +1,63 @@
import { cloneDeep } from "lodash-es";
export function formatModel(model: Recordable) {
const data: Recordable = {};
for (const [key, val] of Object.entries(model)) {
// 数组类型
if (/^\[.+\]$/.test(key)) {
const subkeysStr = key.replaceAll(/\s/g, "").match(/^\[(.+)\]$/)?.[1];
if (!subkeysStr) {
data[key] = val;
continue;
}
const subkeys = subkeysStr.split(",");
subkeys.forEach((subkey, index) => {
if (/(.+)?:number$/.test(subkey)) {
subkey = subkey.replace(/:number$/, "");
data[subkey] = val?.[index] && Number(val[index]);
return;
}
if (/(.+)?:boolean$/.test(subkey)) {
subkey = subkey.replace(/:boolean$/, "");
data[subkey] = val?.[index] && Boolean(val[index]);
return;
}
data[subkey] = val?.[index];
});
continue;
}
// 默认类型
data[key] = val;
}
return data;
}
/**
*
* @param initial
* @returns
*/
export function useModel(initial: Recordable) {
const model = ref<Recordable>({});
const resetModel = () => {
model.value = cloneDeep(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,
resetModel,
setModel,
getModel,
};
}

View File

@ -0,0 +1,95 @@
import { FieldRule } from "@arco-design/web-vue";
import { has, isString } from "lodash-es";
import { IAnFormItemBoolFn, 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 { required, rules } = item;
if (!has(item, "required") && !has(item, "rules")) {
return null;
}
const data: IAnFormItemRule[] = [];
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;
};

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,10 @@
import { RangePicker, RangePickerInstance } from "@arco-design/web-vue";
type Props = RangePickerInstance["$props"];
export default {
render: RangePicker,
nodeProps: {
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,38 @@
import { Select, SelectInstance, SelectOptionData } from "@arco-design/web-vue";
import { initOptions } from "../form-config";
import { Component } from "vue";
import { defineSetter } from "../utils/defineSetter";
interface Interface {
init: any;
render: Component;
nodeProps: SelectInstance["$props"];
/**
*
*/
options?: SelectOptionData[] | ((arg: any) => Recordable[] | Promise<Recordable[]>);
}
const select: Interface = {
render: Select,
init: initOptions,
nodeProps: {
placeholder: "请选择",
allowClear: true,
allowSearch: true,
options: [],
},
options: [],
};
export default select;
// export default defineSetter({
// setter: Select,
// setterProps: {
// placeholder: "请选择",
// allowClear: true,
// allowSearch: true,
// options: [],
// },
// });

View File

@ -0,0 +1,19 @@
import { Button } from "@arco-design/web-vue";
import { FormContextKey } from "../core/interface";
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: {},
};

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,44 @@
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 const nodeMap = {
input,
number,
search,
textarea,
select,
treeSelect,
time,
password,
cascader,
date,
submit,
custom,
dateRange,
};
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

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

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,14 +1,132 @@
<template>
<div class="m-4 bg-white p-4">
<user-table></user-table>
<div>{{ formatModel(emodel) }}</div>
<UpForm />
</div>
</template>
<script setup lang="tsx">
import { api } from '@/api';
import { formatModel, useForm } from '@/components/AnForm';
import { useTable } from '@/components/AnTable';
import { useSelection } from '@/components/AnTable/plugins/useSelectionPlugin';
import { useRefresh } from '@/components/AnTable/plugins/useRefreshPlugin';
import { useColumnConfig } from '@/components/AnTable/plugins/useColumnConfig';
import { Ref } from 'vue';
import { Button, Message } from '@arco-design/web-vue';
import { delConfirm, sleep } from '@/utils';
const { component: UserTable } = useTable({
plugins: [
useRefresh(),
useColumnConfig(),
(() => {
let selected: Ref<any[]>;
return {
id: 'deletemany',
options(options: any) {
let selectPlugin = options.plugins.find((i: any) => i.id === 'selection');
if (!selectPlugin) {
selectPlugin = useSelection();
options.plugins.push(selectPlugin);
}
selected = selectPlugin.provide.selected;
return options;
},
action() {
const loading = ref(false);
const onClick = async () => {
await delConfirm();
loading.value = true;
await sleep(3000);
loading.value = false;
selected.value = [];
Message.success('提示: 删除成功!');
};
return () => (
<Button
type="primary"
status="danger"
disabled={!selected.value.length}
loading={loading.value}
onClick={onClick}
>
批量删除
</Button>
);
},
};
})(),
(() => {
let selected: Ref<any[]>;
return {
id: 'export',
options(options: any) {
let selectPlugin = options.plugins.find((i: any) => i.id === 'selection');
if (!selectPlugin) {
selectPlugin = useSelection();
options.plugins.push(selectPlugin);
}
selected = selectPlugin.provide.selected;
return options;
},
action() {
const onClick = async () => {
await delConfirm('确认导出选中数据吗?');
await sleep(3000);
selected.value = [];
Message.success('提示: 删除成功!');
};
return () => (
<Button disabled={!selected.value.length} onClick={onClick}>
{{
icon: () => <i class="icon-park-outline-export"></i>,
default: () => '导出',
}}
</Button>
);
},
};
})(),
(() => {
return {
id: 'import',
action() {
const onClick = async () => {
Message.success('提示: TODO!');
};
return () => (
<Button onClick={onClick}>
{{
icon: () => <i class="icon-park-outline-import"></i>,
default: () => '导入',
}}
</Button>
);
},
};
})(),
(() => {
return {
id: 'format',
options(options) {
for (const column of options.columns ?? []) {
if (column.render) {
continue;
}
column.render = ({ record, column }) => record[column.dataIndex!] ?? '-';
}
},
};
})(),
],
data(search) {
return api.user.getUsers(search);
},
paging: {
hide: false,
},
columns: [
{
dataIndex: 'id',
@ -54,31 +172,19 @@ const { component: UserTable } = useTable({
configable: false,
buttons: [
{
type: 'modify',
text: '修改',
},
{
text: '移动',
disable: () => true,
// visible: () => false,
},
{
type: 'delete',
confirm: '确定删除吗?',
text: '删除',
onClick(props) {
const id = props.record.id;
return api.user.delUser(id);
},
disable: () => true,
},
],
},
],
source: search => {
return api.user.getUsers(search);
},
paging: {
hide: false,
},
search: [
{
field: 'username',
@ -88,23 +194,15 @@ const { component: UserTable } = useTable({
],
create: {
title: '新增',
width: 580,
modalProps: {
width: 111,
},
items: [
{
field: 'title',
label: '标题',
setter: 'input',
},
{
field: 'title2',
label: '标题',
setter: 'input',
},
{
field: 'title1',
label: '标题',
setter: 'input',
},
],
submit: async model => {
return 1;
@ -112,7 +210,105 @@ const { component: UserTable } = useTable({
},
modify: {
extend: true,
title: '修改',
},
});
const { component: UpForm, model: emodel } = useForm({
formProps: {
class: 'grid! grid-cols-2 gap-x-8',
},
items: [
{
field: 'id',
label: '输入组件',
setter: 'input',
setterSlots: {
prefix: () => <span>123</span>,
},
itemSlots: {
help: props => props.item.label,
extra: () => 'extra',
},
},
{
field: 'todo',
label: '测试',
},
{
field: 'xsa',
label: '动态渲染',
setter: 'input',
visible: props => props.model.id,
},
{
field: 'fsa',
label: '动态禁止',
setter: 'input',
disable: props => props.model.id,
},
{
field: 'sgs',
label: '校验规则',
setter: 'input',
// required: true,
rules: ['email'],
},
{
field: 'sgss',
label: '动态规则',
setter: 'input',
rules: [
{
required: true,
message: '必须项',
disable: props => !props.model.id,
},
],
},
{
field: 'num',
value: 20,
label: '数字组件',
setter: 'number',
},
{
field: 'date',
label: '日期组件',
setter: 'date',
},
{
field: '[startDate,endDate]',
label: '字段语法',
setter: 'dateRange',
},
{
field: '{value,dd}',
value: { value: 1 },
label: '下拉组件',
setter: 'select',
options: [
{
label: '测试',
value: {
value: 1,
dd: 123,
},
},
{
label: '测试2',
value: {
value: 2,
dd: 223,
},
},
],
setterProps: {
valueKey: 'value',
},
},
],
async submit(model) {
return { message: '操作成功' };
},
});
</script>

View File

@ -19,100 +19,108 @@
</template>
<script setup lang="tsx">
import { DictType, api } from '@/api';
import { useCreateColumn, useTable, useUpdateColumn } from '@/components/AnTable';
import aniGroup from './components/group.vue';
import { DictType, api } from "@/api";
import { createColumn, updateColumn, useAniTable } from "@/components";
import aniGroup from "./components/group.vue";
defineOptions({ name: "SystemDictPage" })
defineOptions({ name: 'SystemDictPage' });
const current = ref<DictType>();
const onTypeChange = (item: DictType) => {
current.value = item;
tableRef.value?.refresh();
dict.refresh();
};
const { component: DictTable, tableRef } = useTable({
const [dictTable, dict] = useAniTable({
async data(search, paging) {
return api.dict.getDicts({ ...search, ...paging, typeId: current.value?.id } as any);
},
columns: [
{
title: '字典项',
dataIndex: 'name',
title: "字典项",
dataIndex: "name",
render: ({ record }) => (
<div>
<div>
{record.name}
<span class="text-gray-400 ml-2 text-xs">{record.code}</span>
{record.name}<span class="text-gray-400 ml-2 text-xs">{record.code}</span>
</div>
<div class="text-gray-400 text-xs">{record.description}</div>
</div>
),
},
useCreateColumn(),
useUpdateColumn(),
createColumn,
updateColumn,
{
title: '操作',
type: 'button',
title: "操作",
type: "button",
width: 140,
buttons: [
{
type: 'modify',
text: '修改',
type: "modify",
text: "修改",
},
{
type: 'delete',
text: '删除',
onClick: props => {
return api.dict.delDict(props.record.id);
type: "delete",
text: "删除",
onClick: ({ record }) => {
return api.dict.delDict(record.id);
},
},
],
},
],
source(search) {
return api.dict.getDicts({ ...search, typeId: current.value?.id } as any);
},
search: {
hideSearch: true,
button: false,
items: [
{
field: 'name',
label: '名称',
setter: 'search',
field: "name",
label: "名称",
type: "search",
searchable: true,
enterable: true,
nodeProps: {
placeholder: "字典名称",
},
itemProps: {
hideLabel: true,
},
},
],
},
create: {
title: '新增字典',
width: 580,
title: "新增字典",
model: {
typeId: undefined,
},
modalProps: {
width: 580,
},
items: [
{
field: 'name',
label: '字典名',
setter: 'input',
field: "name",
label: "字典名",
type: "input",
},
{
field: 'code',
label: '字典值',
setter: 'input',
field: "code",
label: "字典值",
type: "input",
},
{
field: 'description',
label: '备注',
setter: 'textarea',
field: "description",
label: "备注",
type: "textarea",
},
],
submit: model => {
return api.dict.addDict({ ...model, typeId: current.value?.id } as any);
submit: async ({ model }) => {
return api.dict.addDict({ ...model, typeId: current.value?.id });
},
},
modify: {
extend: true,
title: '修改字典',
submit: model => {
return api.dict.setDict(model.id, { ...model, typeId: current.value?.id } as any);
title: "修改字典",
submit: async ({ model }) => {
return api.dict.setDict(model.id, { ...model, typeId: current.value?.id });
},
},
});

View File

@ -7,29 +7,22 @@ export {}
declare module '@vue/runtime-core' {
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']
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
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']
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
ADivider: typeof import('@arco-design/web-vue')['Divider']
ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
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']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
ALayout: typeof import('@arco-design/web-vue')['Layout']
@ -45,19 +38,10 @@ declare module '@vue/runtime-core' {
AModal: typeof import('@arco-design/web-vue')['Modal']
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']
ASelect: typeof import('@arco-design/web-vue')['Select']
ASpace: typeof import('@arco-design/web-vue')['Space']
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']
ATree: typeof import('@arco-design/web-vue')['Tree']
AUpload: typeof import('@arco-design/web-vue')['Upload']

View File

@ -1,27 +1,29 @@
import { Modal, ModalConfig } from '@arco-design/web-vue';
import { merge } from 'lodash-es';
import { Modal, ModalConfig } from "@arco-design/web-vue";
import { merge } from "lodash-es";
export type DelOptions = string | Partial<Omit<ModalConfig, 'onOk' | 'onCancel'>>;
type DelOptions = string | Partial<Omit<ModalConfig, "onOk" | "onCancel">>;
export const delOptions: ModalConfig = {
title: '提示',
titleAlign: 'start',
const delOptions = {
title: "提示",
titleAlign: "start",
width: 432,
content: '危险操作,确定删除该数据吗?',
content: "危险操作,确定删除该数据吗?",
maskClosable: false,
closable: false,
okText: '确定',
okText: "确定",
okButtonProps: {
status: 'danger',
status: "danger",
},
};
export const delConfirm = (config: DelOptions = {}) => {
if (typeof config === 'string') {
const delConfirm = (config: DelOptions = {}) => {
if (typeof config === "string") {
config = { content: config };
}
const merged = merge({ content: '' }, delOptions, config);
const merged = merge({}, delOptions, config);
return new Promise<void>((onOk: () => void, onCancel) => {
Modal.open({ ...merged, onOk, onCancel });
});
};
export { delConfirm };