feat: 优化表格

master
绝弹 2023-11-20 22:44:20 +08:00
parent 2b5c367117
commit db2c3005e8
11 changed files with 180 additions and 180 deletions

View File

@ -23,6 +23,7 @@ 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

@ -33,28 +33,22 @@ export function useFormProps(options: FormUseOptions) {
*
*/
export const useForm = (options: FormUseOptions) => {
const { items: _items = [], model: _model = {}, submit, formProps: _props = {} } = options;
const items = useItems(_items, _model);
const model = ref(_model);
const formProps = ref(_props);
const props = useFormProps(options);
const formRef = ref<InstanceType<typeof AnForm> | null>(null);
const AnFormer = () => (
<AnForm
ref={(el: any) => (formRef.value = el)}
v-model:model={model.value}
items={items.value}
submit={submit}
formProps={formProps.value}
v-model:model={props.model}
items={props.items}
submit={props.submit}
formProps={props.formProps}
></AnForm>
);
return {
component: AnFormer,
model,
items,
submit,
formProps,
props,
formRef,
};
};

View File

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

View File

@ -1,4 +1,5 @@
import { AnForm, AnFormInstance, IAnForm } from '@/components/AnForm';
import { AnFormModal } from '@/components/AnForm/components/FormModal';
import AniEmpty from '@/components/empty/AniEmpty.vue';
import { FormModalProps } from '@/components/form';
import {
@ -186,10 +187,10 @@ export const AnTable = defineComponent({
return state;
},
render() {
(this.columns as any).instance = this;
return (
<div class="table w-full">
<div class={`mb-3 flex toolbar justify-between`}>
<div class={`mb-3 flex gap-2 toolbar justify-between`}>
{this.create && <AnFormModal {...(this.create as any)}></AnFormModal>}
{this.pluginer?.actions && (
<div class={`flex-1 flex gap-2 items-center`}>
{this.pluginer.actions.map(Action => (
@ -218,7 +219,7 @@ export const AnTable = defineComponent({
</AnForm>
)}
</div>
<div class="flex gap-2 ml-2">
<div class="flex gap-2">
<div class="flex gap-1">{this.pluginer?.widgets && this.pluginer.widgets?.map(Widget => <Widget />)}</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
import { dayjs } from '@/libs/dayjs';
import { TableColumn } from '../hooks/useTableColumn';
export function useUpdateColumn(): TableColumn {
return {
title: '更新用户',
dataIndex: 'createdAt',
width: 190,
render({ record }) {
return (
<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>
);
},
};
}
export function useCreateColumn(): TableColumn {
return {
title: '创建用户',
dataIndex: 'createdAt',
width: 190,
render({ record }) {
return (
<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>
);
},
};
}

View File

@ -1,10 +1,10 @@
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";
import { FormItem } from '@/components/AnForm/hooks/useItems';
import { merge } from 'lodash-es';
import { FormModalUseOptions } from '../../AnForm/hooks/useFormModal';
import { ExtendFormItem } from './useSearchForm';
import { TableUseOptions } from './useTable';
export type ModifyForm = Omit<FormModalUseOptions, "items"> & {
export type ModifyForm = Omit<FormModalUseOptions, 'items'> & {
/**
*
*/
@ -15,19 +15,28 @@ export type ModifyForm = Omit<FormModalUseOptions, "items"> & {
items?: ExtendFormItem[];
};
export function useModifyForm(form: ModifyForm, create: FormModalProps) {
const { extend, items, ...rest } = form;
export function useModifyForm(options: TableUseOptions) {
const { create, modify } = options;
if (!modify) {
return null;
}
const { extend, items, ...rest } = modify;
let result = {};
if (extend) {
cloneDeep(create ?? {});
const createItems = create.items;
const modifyItems = form.items;
if (modifyItems && createItems) {
if (extend && create) {
const { items: createItems, ...createRest } = create;
const createItemMap = createItems.reduce((map, value) => {
map[value.field] = value;
return map;
}, {} as Record<string, FormItem>);
const modified: any = merge({}, createRest, rest);
const modifyItems = items;
if (modifyItems) {
for (let i = 0; i < modifyItems.length; i++) {
if (modifyItems[i].extend) {
modifyItems[i] = merge({}, createItems[i], modifyItems[i]);
modifyItems[i] = merge({}, createItemMap[modifyItems[i].field!], modifyItems[i]);
}
}
}
modified.items = modifyItems;
}
}

View File

@ -66,7 +66,7 @@ export function useSearchForm(search?: SearchForm, extendItems: IAnFormItem[] =
setterProps: {},
};
const items: any[] = [];
const items: IAnFormItem[] = [];
for (const _item of _items) {
const { searchable, enterable, field, extend, ...itemRest } = _item;
if ((field || extend) === 'submit' && hideSearch) {
@ -80,10 +80,10 @@ export function useSearchForm(search?: SearchForm, extendItems: IAnFormItem[] =
}
}
if (searchable) {
(item as any).nodeProps.onSearch = () => null;
(item as any).setterProps.onSearch = () => null;
}
if (enterable) {
(item as any).nodeProps.onPressEnter = () => null;
(item as any).setterProps.onPressEnter = () => null;
}
if (item.setterProps) {
(item.setterProps as any).placeholder = (item.setterProps as any).placeholder ?? item.label;

View File

@ -1,6 +1,6 @@
import { FormModalUseOptions, useFormModal } from '../../AnForm/hooks/useFormModal';
import { FormModalUseOptions, useFormModalProps } from '../../AnForm/hooks/useFormModal';
import { AnTable, TableProps } from '../components/Table';
import { ModifyForm } from './useModiyForm';
import { ModifyForm, useModifyForm } from './useModiyForm';
import { SearchForm, useSearchForm } from './useSearchForm';
import { TableColumn, useTableColumns } from './useTableColumn';
import { AnTablePlugin, PluginContainer } from './useTablePlugin';
@ -71,18 +71,35 @@ export interface TableUseOptions extends Pick<TableProps, 'data' | 'tableProps'
modify?: ModifyForm;
}
export function useTable(options: TableUseOptions) {
const pluginer = new PluginContainer(options.plugins ?? []);
export function useTableProps(options: TableUseOptions) {
const { columns } = useTableColumns(options.columns ?? []);
const paging = ref({ 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);
const props = reactive({
data: options.data,
tableProps: options.tableProps,
columns,
search,
paging,
create,
modify,
});
props;
}
export function useTable(options: TableUseOptions) {
const tableRef = ref<InstanceType<typeof AnTable> | null>(null);
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.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 create = options.create && useFormModalProps(options.create);
const AnTabler = () => (
<AnTable
@ -93,6 +110,7 @@ export function useTable(options: TableUseOptions) {
tableProps={tableProps.value}
search={searchProps.value}
pluginer={pluginer}
create={create as any}
></AnTable>
);

View File

@ -23,6 +23,8 @@ interface TableColumnButton {
* ```
*/
type?: 'modify' | 'delete';
confirm?: string;
/**
*
* @example

View File

@ -1,21 +1,18 @@
<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 { useRefresh } from '@/components/AnTable/plugins/useRefreshPlugin';
import { useSelection } from '@/components/AnTable/plugins/useSelectionPlugin';
import { delConfirm, sleep } from '@/utils';
import { Button, Message } from '@arco-design/web-vue';
import { Ref } from 'vue';
const { component: UserTable } = useTable({
plugins: [
@ -45,14 +42,11 @@ const { component: UserTable } = useTable({
Message.success('提示: 删除成功!');
};
return () => (
<Button
type="primary"
status="danger"
disabled={!selected.value.length}
loading={loading.value}
onClick={onClick}
>
批量删除
<Button status="danger" disabled={!selected.value.length} loading={loading.value} onClick={onClick}>
{{
icon: () => <i class="icon-park-outline-delete" />,
default: () => '删除',
}}
</Button>
);
},
@ -99,7 +93,7 @@ const { component: UserTable } = useTable({
return () => (
<Button onClick={onClick}>
{{
icon: () => <i class="icon-park-outline-import"></i>,
icon: () => <i class="icon-park-outline-code-download"></i>,
default: () => '导入',
}}
</Button>
@ -120,6 +114,29 @@ const { component: UserTable } = useTable({
},
};
})(),
(() => {
return {
id: 'rowDelete',
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 => {
await delConfirm(btn.confirm);
const res: any = await onClick?.(props);
const msg = res?.data?.message;
msg && Message.success(`提示: ${msg}`);
};
}
},
};
})(),
],
data(search) {
return api.user.getUsers(search);
@ -179,8 +196,10 @@ const { component: UserTable } = useTable({
// visible: () => false,
},
{
type: 'delete',
confirm: '确定删除吗?',
text: '删除',
disable: () => true,
// disable: () => true,
},
],
},
@ -195,7 +214,7 @@ const { component: UserTable } = useTable({
create: {
title: '新增',
modalProps: {
width: 111,
width: 580,
},
items: [
{
@ -203,6 +222,16 @@ const { component: UserTable } = useTable({
label: '标题',
setter: 'input',
},
{
field: 'title2',
label: '标题',
setter: 'input',
},
{
field: 'title1',
label: '标题',
setter: 'input',
},
],
submit: async model => {
return 1;
@ -212,105 +241,6 @@ const { component: UserTable } = useTable({
extend: true,
},
});
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>
<style scoped></style>

View File

@ -7,22 +7,24 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
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']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
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']
@ -30,20 +32,23 @@ 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']
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']
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']
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']