feat: 添加表格插件系统
自动部署 / build (push) Failing after 58s
Details
自动部署 / build (push) Failing after 58s
Details
parent
1f7c1a95b3
commit
2b5c367117
|
|
@ -76,9 +76,10 @@ export const AnForm = defineComponent({
|
|||
{this.items.map(item => (
|
||||
<AnFormItem key={item.field} item={item} items={this.items} model={this.model}></AnFormItem>
|
||||
))}
|
||||
{this.submit && this.submitItem && (
|
||||
<AnFormItem item={this.submitItem} items={this.items} model={this.model}></AnFormItem>
|
||||
)}
|
||||
{this.$slots.submit?.(this.model, this.validate) ||
|
||||
(this.submit && this.submitItem && (
|
||||
<AnFormItem item={this.submitItem} items={this.items} model={this.model}></AnFormItem>
|
||||
))}
|
||||
</Form>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,18 +6,29 @@ export type FormUseOptions = Partial<Omit<IAnForm, 'items'>> & {
|
|||
* 表单项
|
||||
* @example
|
||||
* ```ts
|
||||
* [
|
||||
* {
|
||||
* [{
|
||||
* field: 'name',
|
||||
* label: '昵称',
|
||||
* setter: 'input'
|
||||
* }
|
||||
* ]
|
||||
* }]
|
||||
* ```
|
||||
*/
|
||||
items?: FormItem[];
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建表单组件的参数
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IAnForm } from '@/components/AnForm';
|
||||
import { AnForm, AnFormInstance, IAnForm } from '@/components/AnForm';
|
||||
import AniEmpty from '@/components/empty/AniEmpty.vue';
|
||||
import { FormModalProps } from '@/components/form';
|
||||
import {
|
||||
|
|
@ -8,9 +8,9 @@ import {
|
|||
Button,
|
||||
PaginationProps,
|
||||
} from '@arco-design/web-vue';
|
||||
import { merge } from 'lodash-es';
|
||||
import { isArray, isFunction, merge } from 'lodash-es';
|
||||
import { PropType, defineComponent, ref } from 'vue';
|
||||
import { TableColumnConfig } from './TableColumnConfig';
|
||||
import { PluginContainer } from '../hooks/useTablePlugin';
|
||||
|
||||
type DataFn = (filter: { page: number; size: number; [key: string]: any }) => any | Promise<any>;
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export const AnTable = defineComponent({
|
|||
/**
|
||||
* 分页配置
|
||||
*/
|
||||
pagination: {
|
||||
paging: {
|
||||
type: Object as PropType<PaginationProps & { hide?: boolean }>,
|
||||
},
|
||||
/**
|
||||
|
|
@ -64,24 +64,31 @@ export const AnTable = defineComponent({
|
|||
tableProps: {
|
||||
type: Object as PropType<InstanceType<typeof BaseTable>['$props']>,
|
||||
},
|
||||
/**
|
||||
* 插件列表
|
||||
*/
|
||||
pluginer: {
|
||||
type: Object as PropType<PluginContainer>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const loading = ref(false);
|
||||
const tableRef = ref<InstanceType<typeof BaseTable>>();
|
||||
const renderData = ref<BaseData[]>([]);
|
||||
const reloadData = () => loadData();
|
||||
const searchRef = ref<AnFormInstance | null>(null);
|
||||
|
||||
const useTablePaging = () => {
|
||||
const getPaging = () => {
|
||||
return {
|
||||
page: props.pagination?.current ?? 1,
|
||||
size: props.pagination?.pageSize ?? 10,
|
||||
page: props.paging?.current ?? 1,
|
||||
size: props.paging?.pageSize ?? 10,
|
||||
};
|
||||
};
|
||||
|
||||
const setPaging = (paging: PaginationProps) => {
|
||||
if (props.pagination) {
|
||||
merge(props.pagination, paging);
|
||||
if (props.paging) {
|
||||
merge(props.paging, paging);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -98,16 +105,24 @@ export const AnTable = defineComponent({
|
|||
|
||||
const { getPaging, setPaging, resetPaging } = useTablePaging();
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
* @param pagination 自定义分页
|
||||
*/
|
||||
const loadData = async () => {
|
||||
if (await searchRef.value?.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const paging = getPaging();
|
||||
if (typeof props.data === 'function') {
|
||||
const search = searchRef.value?.getModel() ?? {};
|
||||
|
||||
if (isArray(props.data)) {
|
||||
// todo
|
||||
}
|
||||
|
||||
if (isFunction(props.data)) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const resData = await props.data({ ...paging });
|
||||
let params = { ...search, ...paging };
|
||||
params = props.pluginer?.callBeforeSearchHook(params) ?? params;
|
||||
const resData = await props.data(params);
|
||||
const { data = [], total = 0 } = resData?.data || {};
|
||||
renderData.value = data;
|
||||
setPaging({ total });
|
||||
|
|
@ -119,6 +134,15 @@ export const AnTable = defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
setPaging({ current: 1, pageSize: 10 });
|
||||
return loadData();
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
return loadData();
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (Array.isArray(props.data)) {
|
||||
renderData.value = props.data;
|
||||
|
|
@ -131,11 +155,13 @@ export const AnTable = defineComponent({
|
|||
});
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
props.pluginer?.callPageChangeHook(page);
|
||||
setPaging({ current: page });
|
||||
loadData();
|
||||
};
|
||||
|
||||
const onPageSizeChange = (size: number) => {
|
||||
props.pluginer?.callSizeChangeHook(size);
|
||||
setPaging({ current: 1, pageSize: size });
|
||||
loadData();
|
||||
};
|
||||
|
|
@ -143,13 +169,18 @@ export const AnTable = defineComponent({
|
|||
const state = {
|
||||
loading,
|
||||
tableRef,
|
||||
searchRef,
|
||||
renderData,
|
||||
loadData,
|
||||
reloadData,
|
||||
reload,
|
||||
refresh,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
props,
|
||||
};
|
||||
|
||||
props.pluginer?.callSetupHook(state);
|
||||
|
||||
provide('ref:table', { ...state, ...props });
|
||||
|
||||
return state;
|
||||
|
|
@ -159,16 +190,36 @@ export const AnTable = defineComponent({
|
|||
return (
|
||||
<div class="table w-full">
|
||||
<div class={`mb-3 flex toolbar justify-between`}>
|
||||
<div class={`flex-1 flex gap-2 `}>
|
||||
<Button type='primary'>{{ icon: () => <i class="icon-park-outline-add"></i>, default: () => '新增' }}</Button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex gap-1">
|
||||
<Button loading={this.loading} onClick={this.loadData}>
|
||||
{{ icon: () => <span class="icon-park-outline-redo"></span> }}
|
||||
</Button>
|
||||
<TableColumnConfig columns={this.columns} />
|
||||
{this.pluginer?.actions && (
|
||||
<div class={`flex-1 flex gap-2 items-center`}>
|
||||
{this.pluginer.actions.map(Action => (
|
||||
<Action />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{this.search && (
|
||||
<AnForm
|
||||
ref="searchRef"
|
||||
v-model:model={this.search.model}
|
||||
items={this.search.items}
|
||||
formProps={this.search.formProps}
|
||||
>
|
||||
{{
|
||||
submit: () => (
|
||||
<Button type="primary" loading={this.loading} onClick={this.reload}>
|
||||
{{
|
||||
default: () => '查询',
|
||||
icon: () => <i class="icon-park-outline-search"></i>,
|
||||
}}
|
||||
</Button>
|
||||
),
|
||||
}}
|
||||
</AnForm>
|
||||
)}
|
||||
</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>
|
||||
|
||||
|
|
@ -179,7 +230,7 @@ export const AnTable = defineComponent({
|
|||
{...this.tableProps}
|
||||
ref="tableRef"
|
||||
loading={this.loading}
|
||||
pagination={this.pagination?.hide ? false : this.pagination}
|
||||
pagination={this.paging?.hide ? false : this.paging}
|
||||
data={this.renderData}
|
||||
columns={this.columns}
|
||||
onPageChange={this.onPageChange}
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
import { merge } from "lodash-es";
|
||||
import { FormUseOptions } from "../../AnForm";
|
||||
import { IAnFormItem } from "../../AnForm/components/FormItem";
|
||||
import { FormItem } from "../../AnForm/hooks/useItems";
|
||||
|
||||
export type ExtendFormItem = Partial<
|
||||
FormItem & {
|
||||
/**
|
||||
* 从新建弹窗继承表单项
|
||||
* @example 'name'
|
||||
*/
|
||||
extend: string;
|
||||
}
|
||||
>;
|
||||
|
||||
type SearchFormItem = ExtendFormItem & {
|
||||
/**
|
||||
* 是否点击图标后进行搜索
|
||||
* @default false
|
||||
*/
|
||||
searchable?: boolean;
|
||||
/**
|
||||
* 是否回车后进行查询
|
||||
* @default false
|
||||
*/
|
||||
enterable?: boolean;
|
||||
};
|
||||
|
||||
export type SearchForm = Omit<FormUseOptions, "items"> & {
|
||||
/**
|
||||
* 搜索表单项
|
||||
*/
|
||||
items?: SearchFormItem[];
|
||||
/**
|
||||
* 是否隐藏查询按钮
|
||||
* @default false
|
||||
*/
|
||||
hideSearch?: boolean;
|
||||
};
|
||||
|
||||
export function useSearchForm(search: SearchForm, extendItems: IAnFormItem[] = []) {
|
||||
const data: any[] = [];
|
||||
const { items = [], hideSearch, ...rest } = search;
|
||||
|
||||
for (const item of items) {
|
||||
const { searchable, enterable, ...itemRest } = item;
|
||||
let _item;
|
||||
if (item.extend) {
|
||||
const extend = extendItems.find((i) => i.field === item.extend);
|
||||
if (extend) {
|
||||
_item = merge({}, extend, itemRest);
|
||||
}
|
||||
}
|
||||
if (searchable) {
|
||||
(item as any).nodeProps.onSearch = () => null;
|
||||
}
|
||||
if (enterable) {
|
||||
(item as any).nodeProps.onPressEnter = () => null;
|
||||
}
|
||||
data.push(_item);
|
||||
}
|
||||
|
||||
if (hideSearch) {
|
||||
const index = data.findIndex((i) => i.type === "submit");
|
||||
if (index > -1) {
|
||||
data.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
items: data,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import { defaultsDeep, isArray, merge } from 'lodash-es';
|
||||
import { FormUseOptions } from '../../AnForm';
|
||||
import { IAnFormItem } from '../../AnForm/components/FormItem';
|
||||
import { FormItem, useItems } from '../../AnForm/hooks/useItems';
|
||||
|
||||
export type ExtendFormItem = Partial<
|
||||
FormItem & {
|
||||
/**
|
||||
* 从新建弹窗继承表单项
|
||||
* @example 'name'
|
||||
*/
|
||||
extend: string;
|
||||
}
|
||||
>;
|
||||
|
||||
type SearchFormItem = ExtendFormItem & {
|
||||
/**
|
||||
* 是否点击图标后进行搜索
|
||||
* @default false
|
||||
*/
|
||||
searchable?: boolean;
|
||||
/**
|
||||
* 是否回车后进行查询
|
||||
* @default false
|
||||
*/
|
||||
enterable?: boolean;
|
||||
};
|
||||
|
||||
export type SearchFormObject = Omit<FormUseOptions, 'items' | 'submit'> & {
|
||||
/**
|
||||
* 搜索表单项
|
||||
*/
|
||||
items?: SearchFormItem[];
|
||||
/**
|
||||
* 是否隐藏查询按钮
|
||||
* @default false
|
||||
*/
|
||||
hideSearch?: boolean;
|
||||
};
|
||||
|
||||
export type SearchForm = SearchFormObject | SearchFormItem[];
|
||||
|
||||
export function useSearchForm(search?: SearchForm, extendItems: IAnFormItem[] = []) {
|
||||
if (!search) {
|
||||
return ref();
|
||||
}
|
||||
|
||||
if (isArray(search)) {
|
||||
search = { items: search };
|
||||
}
|
||||
|
||||
const { items: _items = [], hideSearch, model: _model, formProps: _formProps } = search;
|
||||
const extendMap = extendItems.reduce((m, v) => ((m[v.field] = v), m), {} as Record<string, IAnFormItem>);
|
||||
|
||||
const props = ref({
|
||||
items: [] as IAnFormItem[],
|
||||
model: _model ?? {},
|
||||
formProps: defaultsDeep({}, _formProps, { layout: 'inline' }),
|
||||
});
|
||||
|
||||
const defualts: Partial<IAnFormItem> = {
|
||||
setter: 'input',
|
||||
itemProps: {
|
||||
hideLabel: true,
|
||||
},
|
||||
setterProps: {},
|
||||
};
|
||||
|
||||
const items: any[] = [];
|
||||
for (const _item of _items) {
|
||||
const { searchable, enterable, field, extend, ...itemRest } = _item;
|
||||
if ((field || extend) === 'submit' && hideSearch) {
|
||||
continue;
|
||||
}
|
||||
let item: IAnFormItem = defaultsDeep({}, itemRest, defualts);
|
||||
if (extend) {
|
||||
const extendItem = extendMap[extend];
|
||||
if (extendItem) {
|
||||
item = merge({}, extendItem, itemRest);
|
||||
}
|
||||
}
|
||||
if (searchable) {
|
||||
(item as any).nodeProps.onSearch = () => null;
|
||||
}
|
||||
if (enterable) {
|
||||
(item as any).nodeProps.onPressEnter = () => null;
|
||||
}
|
||||
if (item.setterProps) {
|
||||
(item.setterProps as any).placeholder = (item.setterProps as any).placeholder ?? item.label;
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
props.value.items = useItems(items, props.value.model).value;
|
||||
|
||||
return props;
|
||||
}
|
||||
|
|
@ -1,50 +1,98 @@
|
|||
import { FormModalUseOptions } from '../../AnForm/hooks/useFormModal';
|
||||
import { FormModalUseOptions, useFormModal } from '../../AnForm/hooks/useFormModal';
|
||||
import { AnTable, TableProps } from '../components/Table';
|
||||
import { ModifyForm } from './useModiyForm';
|
||||
import { SearchForm } from './useSearchForm';
|
||||
import { SearchForm, useSearchForm } from './useSearchForm';
|
||||
import { TableColumn, useTableColumns } from './useTableColumn';
|
||||
import { AnTablePlugin, PluginContainer } from './useTablePlugin';
|
||||
|
||||
export interface TableUseOptions extends Pick<TableProps, 'data' | 'tableProps' | 'pagination'> {
|
||||
export interface TableUseOptions extends Pick<TableProps, 'data' | 'tableProps' | 'paging'> {
|
||||
/**
|
||||
* 唯一ID
|
||||
* @example
|
||||
* ```ts
|
||||
* 'UserTable'
|
||||
* ```
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 插件列表
|
||||
* @example
|
||||
* ```ts
|
||||
* [useRefresh()]
|
||||
* ```
|
||||
*/
|
||||
plugins?: AnTablePlugin[];
|
||||
/**
|
||||
* 表格列
|
||||
* @example
|
||||
* ```ts
|
||||
* [{
|
||||
* dataIndex: 'title',
|
||||
* title: '标题'
|
||||
* }]
|
||||
* ```
|
||||
*/
|
||||
columns?: TableColumn[];
|
||||
/**
|
||||
* 搜索表单
|
||||
* @example
|
||||
* ```ts
|
||||
* [{
|
||||
* field: 'name',
|
||||
* label: '用户名称',
|
||||
* setter: 'input'
|
||||
* }]
|
||||
* ```
|
||||
*/
|
||||
search?: SearchForm;
|
||||
/**
|
||||
* 新建弹窗
|
||||
* @example
|
||||
* ```ts
|
||||
* {
|
||||
* title: '添加用户',
|
||||
* items: [],
|
||||
* submit: (model) => {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
create?: FormModalUseOptions;
|
||||
/**
|
||||
* 新建弹窗
|
||||
* 修改弹窗
|
||||
* @example
|
||||
* ```ts
|
||||
* {
|
||||
* extend: true, // 基于新建弹窗扩展
|
||||
* title: '修改用户',
|
||||
* submit: (model) => {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
modify?: ModifyForm;
|
||||
/**
|
||||
* 详情弹窗
|
||||
*/
|
||||
detail?: any;
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
delete?: any;
|
||||
}
|
||||
|
||||
export function useTable(options: TableUseOptions) {
|
||||
const pluginer = new PluginContainer(options.plugins ?? []);
|
||||
|
||||
options = pluginer.callOptionsHook(options);
|
||||
|
||||
const { columns } = useTableColumns(options.columns ?? []);
|
||||
const data = ref(options.data);
|
||||
const pagination = ref({ hide: false, showTotal: true, showPageSize: true, ...(options.pagination ?? {}) });
|
||||
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)}
|
||||
columns={columns.value}
|
||||
data={data.value}
|
||||
pagination={pagination.value}
|
||||
paging={pagination.value}
|
||||
tableProps={tableProps.value}
|
||||
search={searchProps.value}
|
||||
pluginer={pluginer}
|
||||
></AnTable>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ interface TableButtonColumn {
|
|||
type: 'button';
|
||||
/**
|
||||
* 按钮列表
|
||||
* @example
|
||||
* ```ts
|
||||
* [{ text: '删除', onClick: (args) => api.user.rmUser(args.record.id) }]
|
||||
* ```
|
||||
*/
|
||||
buttons: TableColumnButton[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { TableUseOptions } from './useTable';
|
||||
|
||||
export interface AnTablePlugin {
|
||||
/**
|
||||
* 插件ID(唯一)
|
||||
* @example
|
||||
* ```ts
|
||||
* 'Plugin:Refresh'
|
||||
* ```
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 提供给其他插件使用的变量
|
||||
* @example
|
||||
* ```ts
|
||||
* { isOk: true }
|
||||
* ```
|
||||
*/
|
||||
provide?: Recordable;
|
||||
/**
|
||||
* 组件钩子
|
||||
*/
|
||||
onSetup?: (context: any) => void;
|
||||
/**
|
||||
* 钩子
|
||||
*/
|
||||
options?: (options: TableUseOptions) => TableUseOptions | null | undefined | void;
|
||||
/**
|
||||
* 添加部件栏组件
|
||||
*/
|
||||
widget?: () => any;
|
||||
/**
|
||||
* 添加操作栏组件
|
||||
*/
|
||||
action?: () => any;
|
||||
/**
|
||||
* 搜索前处理
|
||||
*
|
||||
*/
|
||||
onBeforeSearch?: (args: { page: number; size: number; [key: string]: any }) => Recordable | null | undefined | void;
|
||||
onPageChange?: (page: number) => void;
|
||||
onSizeChange?: (size: number) => void;
|
||||
}
|
||||
|
||||
export class PluginContainer {
|
||||
actions: any[] = [];
|
||||
widgets: any[] = [];
|
||||
|
||||
constructor(private plugins: AnTablePlugin[]) {
|
||||
for (const plugin of plugins) {
|
||||
const action = plugin.action?.();
|
||||
const widget = plugin.widget?.();
|
||||
if (action) {
|
||||
this.actions.push(action);
|
||||
}
|
||||
if (widget) {
|
||||
this.widgets.push(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callSetupHook(context: any) {
|
||||
for (const plugin of this.plugins) {
|
||||
plugin.onSetup?.(context);
|
||||
}
|
||||
}
|
||||
|
||||
callOptionsHook(options: any) {
|
||||
for (const plugin of this.plugins) {
|
||||
options = plugin.options?.(options) ?? options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
callActionHook(options: any) {
|
||||
for (const plugin of this.plugins) {
|
||||
options = plugin.options?.(options) ?? options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
callWidgetHook(options: any) {
|
||||
for (const plugin of this.plugins) {
|
||||
options = plugin.options?.(options) ?? options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
callBeforeSearchHook(options: any) {
|
||||
for (const plugin of this.plugins) {
|
||||
options = plugin.onBeforeSearch?.(options) ?? options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
callPageChangeHook(page: number) {
|
||||
for (const plugin of this.plugins) {
|
||||
plugin.onPageChange?.(page);
|
||||
}
|
||||
}
|
||||
|
||||
callSizeChangeHook(page: number) {
|
||||
for (const plugin of this.plugins) {
|
||||
plugin.onPageChange?.(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
import { Button, Checkbox, Divider, InputNumber, Popover, Scrollbar, Tag } from '@arco-design/web-vue';
|
||||
import { PropType } from 'vue';
|
||||
import { AnTablePlugin } from '../hooks/useTablePlugin';
|
||||
|
||||
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>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 插件:表格列配置
|
||||
* @description 配置ID将缓存结果在本地
|
||||
*/
|
||||
export function useColumnConfig(): AnTablePlugin {
|
||||
let context: any;
|
||||
return {
|
||||
id: "columnconfig",
|
||||
onSetup(args) {
|
||||
context = args;
|
||||
},
|
||||
widget() {
|
||||
return () => <TableColumnConfig columns={context.props.columns} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Button } from '@arco-design/web-vue';
|
||||
import { AnTablePlugin } from '../hooks/useTablePlugin';
|
||||
|
||||
/**
|
||||
* 插件:添加刷新按钮
|
||||
* @description 位于搜索栏附近
|
||||
*/
|
||||
export function useRefresh(): AnTablePlugin {
|
||||
let context: any = {};
|
||||
|
||||
return {
|
||||
id: 'refresh',
|
||||
onSetup(ctx) {
|
||||
context = ctx;
|
||||
},
|
||||
widget() {
|
||||
return () => {
|
||||
const { loading, refresh } = context;
|
||||
return (
|
||||
<Button disabled={loading.value} onClick={refresh}>
|
||||
{{
|
||||
icon: () => <span class="icon-park-outline-redo"></span>,
|
||||
}}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { cloneDeep, defaultsDeep, merge } from 'lodash-es';
|
||||
import { TableUseOptions } from '../hooks/useTable';
|
||||
import { AnTablePlugin } from '../hooks/useTablePlugin';
|
||||
|
||||
// declare module '@/components/AnTable/hooks/useTable' {
|
||||
// interface TableUseOptions {
|
||||
// todo?: string;
|
||||
// }
|
||||
// }
|
||||
|
||||
const defaults: TableUseOptions = {
|
||||
tableProps: {
|
||||
rowSelection: {
|
||||
showCheckedAll: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function useSelection<T extends any>({ key = 'id', mode = 'key' } = {}): AnTablePlugin {
|
||||
const selected = ref<T[]>([]);
|
||||
|
||||
return {
|
||||
id: 'selection',
|
||||
provide: {
|
||||
selected,
|
||||
},
|
||||
options(options) {
|
||||
const opts: TableUseOptions = defaultsDeep({}, defaults);
|
||||
|
||||
if (!opts.tableProps!.rowKey) {
|
||||
opts.tableProps!.rowKey = key;
|
||||
}
|
||||
|
||||
if (mode === 'key') {
|
||||
opts.tableProps!.onSelectionChange = rowkeys => {
|
||||
selected.value = rowkeys as any[];
|
||||
};
|
||||
}
|
||||
|
||||
if (mode === 'row') {
|
||||
opts.tableProps!.onSelect = (rowkeys, rowkey, record) => {
|
||||
const index = selected.value.findIndex((i: any) => i[key] == record[key]);
|
||||
if (index > -1) {
|
||||
selected.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
opts.tableProps!.onSelectAll = checked => {
|
||||
if (checked) {
|
||||
selected.value = cloneDeep([]);
|
||||
} else {
|
||||
selected.value = [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return merge(options, opts);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="m-4 bg-white p-4">
|
||||
<div class="border-2 border-green-500 px-2 w-40 text-3xl text-green-500 mb-4">AR K056</div>
|
||||
<user-table></user-table>
|
||||
<div>{{ formatModel(emodel) }}</div>
|
||||
<UpForm />
|
||||
|
|
@ -11,12 +10,121 @@
|
|||
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);
|
||||
},
|
||||
pagination: {
|
||||
paging: {
|
||||
hide: false,
|
||||
},
|
||||
columns: [
|
||||
|
|
@ -29,10 +137,6 @@ const { component: UserTable } = useTable({
|
|||
dataIndex: 'nickname',
|
||||
title: '用户名称',
|
||||
},
|
||||
// {
|
||||
// dataIndex: 'description',
|
||||
// title: '用户描述',
|
||||
// },
|
||||
{
|
||||
dataIndex: 'username',
|
||||
title: '登录账号',
|
||||
|
|
@ -64,23 +168,49 @@ const { component: UserTable } = useTable({
|
|||
{
|
||||
title: '操作',
|
||||
type: 'button',
|
||||
width: 140,
|
||||
width: 180,
|
||||
configable: false,
|
||||
buttons: [
|
||||
{
|
||||
text: '修改',
|
||||
},
|
||||
{
|
||||
text: '修改',
|
||||
visible: () => false,
|
||||
text: '移动',
|
||||
// visible: () => false,
|
||||
},
|
||||
{
|
||||
text: '修改',
|
||||
text: '删除',
|
||||
disable: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
search: [
|
||||
{
|
||||
field: 'username',
|
||||
label: '用户名称',
|
||||
setter: 'input',
|
||||
},
|
||||
],
|
||||
create: {
|
||||
title: '新增',
|
||||
modalProps: {
|
||||
width: 111,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: 'title',
|
||||
label: '标题',
|
||||
setter: 'input',
|
||||
},
|
||||
],
|
||||
submit: async model => {
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
modify: {
|
||||
extend: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { component: UpForm, model: emodel } = useForm({
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
|
|||
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']
|
||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||
|
|
@ -20,8 +21,8 @@ declare module '@vue/runtime-core' {
|
|||
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']
|
||||
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']
|
||||
|
|
@ -29,15 +30,20 @@ declare module '@vue/runtime-core' {
|
|||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||
AList: typeof import('@arco-design/web-vue')['List']
|
||||
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
||||
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||
AniEmpty: typeof import('./../components/empty/AniEmpty.vue')['default']
|
||||
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||
ATree: typeof import('@arco-design/web-vue')['Tree']
|
||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
||||
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
||||
|
|
|
|||
Loading…
Reference in New Issue