feat: 添加表格插件系统
自动部署 / build (push) Failing after 58s Details

master
luoer 2023-11-17 17:38:34 +08:00
parent 1f7c1a95b3
commit 2b5c367117
15 changed files with 977 additions and 132 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,6 +69,10 @@ interface TableButtonColumn {
type: 'button';
/**
*
* @example
* ```ts
* [{ text: '删除', onClick: (args) => api.user.rmUser(args.record.id) }]
* ```
*/
buttons: TableColumnButton[];
}

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

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

View File

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

View File

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

View File

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

View File

@ -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({

View File

@ -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']