feat: 优化表格列设置

master
绝弹 2023-11-17 09:02:23 +08:00
parent 85b781d946
commit 1f7c1a95b3
6 changed files with 289 additions and 80 deletions

View File

@ -8,8 +8,9 @@ import {
Button,
PaginationProps,
} from '@arco-design/web-vue';
import { isObject, merge } from 'lodash-es';
import { merge } from 'lodash-es';
import { PropType, defineComponent, ref } from 'vue';
import { TableColumnConfig } from './TableColumnConfig';
type DataFn = (filter: { page: number; size: number; [key: string]: any }) => any | Promise<any>;
@ -158,13 +159,15 @@ 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 `}>TODO</div>
<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 disabled={this.loading} onClick={this.loadData}>
<Button loading={this.loading} onClick={this.loadData}>
{{ icon: () => <span class="icon-park-outline-redo"></span> }}
</Button>
<Button>{{ icon: () => <span class="icon-park-outline-config"></span> }}</Button>
<TableColumnConfig columns={this.columns} />
</div>
</div>
</div>

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

@ -85,7 +85,16 @@ interface TableDropdownColumn {
}
export type TableColumn = TableColumnData &
(TableIndexColumn | TableBaseColumn | TableButtonColumn | TableDropdownColumn);
(TableIndexColumn | TableBaseColumn | TableButtonColumn | TableDropdownColumn) & {
/**
*
* @example
* ```ts
* true
* ```
*/
configable?: boolean;
};
export function useTableColumns(data: TableColumn[]) {
const columns = ref<TableColumnData[]>([]);

View File

@ -1,5 +1,5 @@
<template>
<a-popover position="br" trigger="click">
<a-popover v-model:popup-visible="visible" position="br" trigger="click">
<a-button class="float-right">设置</a-button>
<template #content>
<div class="mb-1 leading-none border-b border-gray-100 pb-3">设置表格列</div>
@ -11,24 +11,24 @@
class="group flex items-center justify-between py-2 pr-8 select-none"
>
<div class="flex gap-2">
<a-checkbox v-model="item.enable" :disabled="item.disable" size="large" @change="onItemChange">
<a-checkbox v-model="item.enable" :disabled="!item.editable" size="large" @change="onItemChange">
{{ item.dataIndex }}
</a-checkbox>
<span class="hidden group-hover:inline-block ml-4">
<i v-show="!item.disable" class="icon-park-outline-drag cursor-move"></i>
<i v-show="!item.editable" class="icon-park-outline-drag cursor-move"></i>
</span>
</div>
<div class="flex gap-2 items-center">
<a-checkbox v-model="item.autoWidth" :disabled="item.disable">
<a-checkbox v-model="item.autoWidth" :disabled="!item.editable">
<template #checkbox="{ checked }">
<a-tag :checked="checked" :checkable="!item.disable" color="blue">自适应</a-tag>
<a-tag :checked="checked" :checkable="item.editable" color="blue">自适应</a-tag>
</template>
</a-checkbox>
<a-divider direction="vertical" :margin="8"></a-divider>
<a-input-number
size="small"
v-model="item.width"
:disabled="item.autoWidth || item.disable"
:disabled="item.autoWidth || !item.editable"
:min="60"
:step="10"
class="!w-20"
@ -40,20 +40,14 @@
</a-scrollbar>
<div class="mt-4 flex gap-2 items-center justify-between">
<div class="flex items-center">
<a-checkbox
:indeterminate="indeterminate"
v-model="checkAll"
@change="(v: any) => items.forEach(i => i.enable = v)"
>
全选
</a-checkbox>
<a-checkbox :indeterminate="indeterminate" v-model="checkAll" @change="onCheckAllChange"> </a-checkbox>
<span class="text-xs text-gray-400 ml-1">
({{ items.filter(i => i.enable).length }}/{{ items.length }})
</span>
</div>
<div class="space-x-2">
<a-button>重置</a-button>
<a-button type="primary">确定</a-button>
<a-button @click="onReset"></a-button>
<a-button type="primary" @click="onConfirm"></a-button>
</div>
</div>
</template>
@ -63,46 +57,48 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
const items = ref(
Array(10)
.fill(0)
.map((v, i) => {
return {
dataIndex: `测试${i}`,
enable: true,
autoWidth: false,
width: 80,
disable: false,
};
})
);
items.value.unshift({
dataIndex: '顺序',
enable: true,
autoWidth: false,
width: 80,
disable: true,
});
items.value.push({
dataIndex: '操作',
enable: true,
autoWidth: false,
width: 80,
disable: true,
});
interface Item {
dataIndex: string;
enable: boolean;
autoWidth: boolean;
width: number;
editable: boolean;
}
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;
});
const checked = computed(() => {
return items.value.filter(i => i.enable);
onMounted(() => {
items.value.push({
dataIndex: '顺序',
enable: true,
autoWidth: false,
width: 80,
editable: false,
});
for (let i = 1; i <= 10; i++) {
items.value.push({
dataIndex: `测试${i}`,
enable: true,
autoWidth: false,
width: 80,
editable: true,
});
}
items.value.push({
dataIndex: '操作',
enable: true,
autoWidth: false,
width: 80,
editable: false,
});
});
const onItemChange = () => {
@ -114,6 +110,22 @@ const onItemChange = () => {
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;
};
</script>
<style scoped></style>

View File

@ -1,7 +1,6 @@
<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>
<column-configer></column-configer>
<user-table></user-table>
<div>{{ formatModel(emodel) }}</div>
<UpForm />
@ -10,10 +9,8 @@
<script setup lang="tsx">
import { api } from '@/api';
import { useForm } from '@/components/AnForm';
import { formatModel } from '@/components/AnForm';
import { formatModel, useForm } from '@/components/AnForm';
import { useTable } from '@/components/AnTable';
import ColumnConfiger from './components/ColumnConfiger.vue';
const { component: UserTable } = useTable({
data(search) {
@ -26,15 +23,49 @@ const { component: UserTable } = useTable({
{
dataIndex: 'id',
title: 'ID',
configable: false,
},
{
dataIndex: 'nickname',
title: '用户名称',
},
// {
// dataIndex: 'description',
// title: '',
// },
{
dataIndex: 'username',
title: '登录账号',
},
{
dataIndex: 'email',
title: '邮箱',
},
{
dataIndex: 'phone',
title: '手机号码',
},
{
dataIndex: 'createdBy',
title: '创建人',
},
{
dataIndex: 'createdAt',
title: '创建时间',
},
{
dataIndex: 'updatedBy',
title: '更新人',
},
{
dataIndex: 'updatedAt',
title: '更新时间',
},
{
title: '操作',
type: 'button',
width: 140,
configable: false,
buttons: [
{
text: '修改',

View File

@ -7,27 +7,19 @@ 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']
@ -37,31 +29,15 @@ 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']
ATable: typeof import('@arco-design/web-vue')['Table']
ATableColumn: typeof import('@arco-design/web-vue')['TableColumn']
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']
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']