feat: 表格跨页选择

This commit is contained in:
RubyLiu 2023-09-20 19:04:34 +08:00 committed by 刘瑞斌
parent 97db41771b
commit be7d73ada4
5 changed files with 209 additions and 193 deletions

View File

@ -0,0 +1,21 @@
<template>
<a-checkbox :model-value="checked" @change="handleChange"></a-checkbox>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
const props = defineProps<{
value: boolean;
}>();
const emit = defineEmits<{
(e: 'change', value: boolean): void;
}>();
const checked = ref(false);
const handleChange = (v: boolean | (string | number | boolean)[]) => {
emit('change', v as boolean);
};
watchEffect(() => {
checked.value = props.value;
});
</script>

View File

@ -1,25 +1,32 @@
<template> <template>
<div class="ms-base-table"> <div class="ms-base-table">
<select-all
v-if="attrs.selectable && attrs.showSelectAll"
class="custom-action"
:total="selectTotal"
:current="selectCurrent"
@change="handleChange"
/>
<a-table <a-table
v-bind="$attrs" v-bind="$attrs"
:row-class="getRowClass" :row-class="getRowClass"
:selected-keys="props.selectedKeys"
:span-method="spanMethod" :span-method="spanMethod"
:columns="currentColumns" :columns="currentColumns"
@selection-change="(e) => selectionChange(e, true)"
@sorter-change="(dataIndex: string,direction: string) => handleSortChange(dataIndex, direction)" @sorter-change="(dataIndex: string,direction: string) => handleSortChange(dataIndex, direction)"
> >
<template #optional="{ rowIndex, record }"> <template #optional="{ rowIndex, record }">
<slot name="optional" v-bind="{ rowIndex, record }" /> <slot name="optional" v-bind="{ rowIndex, record }" />
</template> </template>
<template #columns> <template #columns>
<a-table-column v-if="attrs.selectable && props.selectedKeys" :width="60">
<template #title>
<select-all
:total="selectTotal"
:current="selectCurrent"
:type="attrs.selectorType as ('checkbox' | 'radio')"
@change="handleSelectAllChange"
/>
</template>
<template #cell="{ record }">
<MsCheckbox
:value="props.selectedKeys.has(record[rowKey || 'id'])"
@change="rowSelectChange(record[rowKey || 'id'])"
/>
</template>
</a-table-column>
<a-table-column <a-table-column
v-for="(item, idx) in currentColumns" v-for="(item, idx) in currentColumns"
:key="idx" :key="idx"
@ -57,16 +64,16 @@
<div class="flex flex-row flex-nowrap items-center"> <div class="flex flex-row flex-nowrap items-center">
<template v-if="item.dataIndex === SpecialColumnEnum.ENABLE"> <template v-if="item.dataIndex === SpecialColumnEnum.ENABLE">
<slot name="enable" v-bind="{ record }"> <slot name="enable" v-bind="{ record }">
<template v-if="record.enable"> <div v-if="record.enable" class="flex flex-row flex-nowrap items-center gap-[2px]">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" /> <icon-check-circle-fill class="text-[rgb(var(--success-6))]" />
{{ item.enableTitle ? t(item.enableTitle) : t('msTable.enable') }} <div>{{ item.enableTitle ? t(item.enableTitle) : t('msTable.enable') }}</div>
</template> </div>
<template v-else> <div v-else class="flex flex-row flex-nowrap items-center gap-[2px]">
<MsIcon type="icon-icon_disable" class="mr-[2px] text-[var(--color-text-4)]" /> <MsIcon type="icon-icon_disable" class="text-[var(--color-text-4)]" />
<span class="text-[var(--color-text-1)]"> <div class="text-[var(--color-text-1)]">
{{ item.disableTitle ? t(item.disableTitle) : t('msTable.disable') }} {{ item.disableTitle ? t(item.disableTitle) : t('msTable.disable') }}
</span> </div>
</template> </div>
</slot> </slot>
</template> </template>
<template v-else-if="item.isTag"> <template v-else-if="item.isTag">
@ -146,10 +153,10 @@
:select-row-count="selectCurrent" :select-row-count="selectCurrent"
:action-config="props.actionConfig" :action-config="props.actionConfig"
@batch-action="(item: BatchActionParams) => emit('batchAction', item)" @batch-action="(item: BatchActionParams) => emit('batchAction', item)"
@clear="selectionChange([], true)" @clear="emit('clearSelector')"
/> />
<ms-pagination <ms-pagination
v-else-if="attrs.showPagination" v-if="attrs.showPagination"
size="small" size="small"
v-bind="attrs.msPagination" v-bind="attrs.msPagination"
hide-on-single-page hide-on-single-page
@ -171,17 +178,20 @@
import ColumnSelector from './columnSelector.vue'; import ColumnSelector from './columnSelector.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import type { MsTableProps, MsPaginationI, BatchActionParams, BatchActionConfig, MsTableColumn } from './type'; import type { MsTableProps, BatchActionParams, BatchActionConfig, MsTableColumn, MsPaginationI } from './type';
import type { TableData } from '@arco-design/web-vue'; import type { TableData } from '@arco-design/web-vue';
import MsCheckbox from '../ms-checkbox/MsCheckbox.vue';
const batchleft = ref('10px'); const batchleft = ref('10px');
const { t } = useI18n(); const { t } = useI18n();
const tableStore = useTableStore(); const tableStore = useTableStore();
const appStore = useAppStore();
const currentColumns = ref<MsTableColumn>([]); const currentColumns = ref<MsTableColumn>([]);
const appStore = useAppStore();
const props = defineProps<{ const props = defineProps<{
selectedKeys?: (string | number)[]; selectedKeys: Set<string>;
excludeKeys: Set<string>;
selectorStatus: SelectAllEnum;
actionConfig?: BatchActionConfig; actionConfig?: BatchActionConfig;
noDisable?: boolean; noDisable?: boolean;
showSetting?: boolean; showSetting?: boolean;
@ -189,19 +199,35 @@
spanMethod?: (params: { record: TableData; rowIndex: number; columnIndex: number }) => void; spanMethod?: (params: { record: TableData; rowIndex: number; columnIndex: number }) => void;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'selectedChange', value: (string | number)[]): void;
(e: 'batchAction', value: BatchActionParams): void; (e: 'batchAction', value: BatchActionParams): void;
(e: 'pageChange', value: number): void; (e: 'pageChange', value: number): void;
(e: 'pageSizeChange', value: number): void; (e: 'pageSizeChange', value: number): void;
(e: 'rowNameChange', value: TableData): void; (e: 'rowNameChange', value: TableData): void;
(e: 'rowSelectChange', key: string): void;
(e: 'selectAllChange', value: SelectAllEnum): void;
(e: 'sorterChange', value: { [key: string]: string }): void; (e: 'sorterChange', value: { [key: string]: string }): void;
(e: 'clearSelector'): void;
}>(); }>();
const isSelectAll = ref(false);
const attrs = useAttrs(); const attrs = useAttrs();
// -
const selectCurrent = ref(0);
// - // -
const selectTotal = ref(0); const selectTotal = computed(() => {
const { selectorStatus } = props;
if (selectorStatus === SelectAllEnum.ALL) {
return (attrs.msPagination as MsPaginationI)?.total || appStore.pageSize;
}
return (attrs.msPagination as MsPaginationI).pageSize || appStore.pageSize;
});
// -
const selectCurrent = computed(() => {
const { selectorStatus, excludeKeys, selectedKeys } = props;
if (selectorStatus === SelectAllEnum.ALL) {
return selectTotal.value - excludeKeys.size;
}
return selectedKeys.size;
});
// Active // Active
const editActiveKey = ref<string>(''); const editActiveKey = ref<string>('');
// inputRef // inputRef
@ -233,15 +259,6 @@
return undefined; return undefined;
}); });
const setSelectAllTotal = (isAll: boolean) => {
const { data, msPagination }: Partial<MsTableProps<any>> = attrs;
if (isAll) {
selectTotal.value = msPagination?.total || data?.length || appStore.pageSize;
} else {
selectTotal.value = data ? data.length : appStore.pageSize;
}
};
const initColumn = () => { const initColumn = () => {
let tmpArr: MsTableColumn = []; let tmpArr: MsTableColumn = [];
if (props.showSetting) { if (props.showSetting) {
@ -252,41 +269,13 @@
currentColumns.value = tmpArr; currentColumns.value = tmpArr;
}; };
//
const selectionChange = (arr: (string | number)[], setCurrentSelect: boolean, isAll = false) => {
setSelectAllTotal(isAll);
emit('selectedChange', arr);
if (setCurrentSelect) {
selectCurrent.value = arr.length;
}
};
// change // change
const handleChange = (v: string) => { const handleSelectAllChange = (v: SelectAllEnum) => {
const { data, msPagination }: Partial<MsTableProps<any>> = attrs; emit('selectAllChange', v);
isSelectAll.value = v === SelectAllEnum.ALL; };
if (v === SelectAllEnum.NONE) { // change
selectionChange([], true); const rowSelectChange = (key: string) => {
} emit('rowSelectChange', key);
if (v === SelectAllEnum.CURRENT) {
if (data && data.length > 0) {
selectionChange(
data.map((item: any) => item[rowKey || 'id']),
true
);
}
}
if (v === SelectAllEnum.ALL) {
const { total } = msPagination as MsPaginationI;
if (data && data.length > 0) {
selectionChange(
data.map((item: any) => item[rowKey || 'id']),
false,
true
);
selectCurrent.value = total;
}
}
}; };
// change // change
@ -376,15 +365,6 @@
<style lang="less" scoped> <style lang="less" scoped>
.ms-base-table { .ms-base-table {
position: relative; position: relative;
.custom-action {
position: absolute;
top: 13px;
left: v-bind(batchleft);
z-index: 99;
border-radius: 2px;
line-height: 40px;
cursor: pointer;
}
.batch-action { .batch-action {
justify-content: flex-start; justify-content: flex-start;
} }
@ -425,11 +405,6 @@
background: none; background: none;
} }
} }
:deep(.ms-table-select-all) {
.arco-checkbox {
margin-left: 0.5rem;
}
}
:deep(.arco-checkbox-icon) { :deep(.arco-checkbox-icon) {
width: 16px; width: 16px;
height: 16px; height: 16px;

View File

@ -20,18 +20,12 @@
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'change', value: string): void; (e: 'change', value: SelectAllEnum): void;
}>(); }>();
const props = defineProps({ const props = withDefaults(defineProps<{ current: number; total: number; type: 'checkbox' | 'radio' }>(), {
current: { current: 0,
type: Number, total: 0,
default: 0,
},
total: {
type: Number,
default: 0,
},
}); });
const checked = ref(false); const checked = ref(false);
@ -44,14 +38,14 @@
} else if (props.current < props.total) { } else if (props.current < props.total) {
checked.value = false; checked.value = false;
indeterminate.value = true; indeterminate.value = true;
} else if (props.current >= props.total) { } else if (props.current === props.total) {
checked.value = true; checked.value = true;
indeterminate.value = false; indeterminate.value = false;
} }
}); });
const handleSelect = (v: string | number | Record<string, any> | undefined) => { const handleSelect = (v: string | number | Record<string, any> | undefined) => {
emit('change', v as string); emit('change', v as SelectAllEnum);
}; };
const handleCheckChange = () => { const handleCheckChange = () => {

View File

@ -1,4 +1,4 @@
import { ColumnEditTypeEnum } from '@/enums/tableEnum'; import { ColumnEditTypeEnum, SelectAllEnum } from '@/enums/tableEnum';
import { TableColumnData, TableData, TableDraggable, TableChangeExtra } from '@arco-design/web-vue'; import { TableColumnData, TableData, TableDraggable, TableChangeExtra } from '@arco-design/web-vue';
export interface MsPaginationI { export interface MsPaginationI {
@ -42,56 +42,37 @@ export type MsTableDataItem<T> = T & {
export interface MsTableProps<T> { export interface MsTableProps<T> {
// 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data; // 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data;
data: MsTableDataItem<T>[]; data: MsTableDataItem<T>[];
// 表格key, 用于存储表格列配置 tableKey: string; // 表格key, 用于存储表格列配置,pageSize等
tableKey: string; rowKey: string; // 表格行的key rowId
// 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column; // 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column;
columns: MsTableColumnData[]; columns: MsTableColumnData[];
// 表格尺寸 size?: 'mini' | 'small' | 'default' | 'large'; // 表格尺寸
size?: 'mini' | 'small' | 'default' | 'large';
// 表格是否可滚动
scroll?: { scroll?: {
x?: number | string; x?: number | string;
y?: number | string; y?: number | string;
maxHeight?: number | string; maxHeight?: number | string;
minWidth?: number | string; minWidth?: number | string;
}; }; // 表格是否可滚动
// 表格是否可拖拽 heightUsed?: number; // 已经使用的高度
enableDrag?: boolean; enableDrag?: boolean; // 表格是否可拖拽
draggable?: TableDraggable; draggable?: TableDraggable;
// 表格是否可编辑 /** 选择器相关 */
editable?: boolean; selectable?: boolean; // 是否显示选择器
// 表格是否可筛选 selectorType: 'none' | 'checkbox' | 'radio'; // 选择器类型
filterable?: boolean; selectedKeys: Set<string>; // 选中的key
// 表格是否可排序 excludeKeys: Set<string>; // 排除的key
sortable?: boolean; selectorStatus: SelectAllEnum; // 选择器状态
// 表格是否可选中 /** end */
selectable?: boolean; loading?: boolean; // 加载效果
// 展示自定义全选 bordered?: boolean; // 是否显示边框
showSelectAll?: boolean;
// 表格是否可固定表头
fixedHeader?: boolean;
// 表格是否可固定列
fixedColumns?: boolean;
// rowKey
rowKey?: string;
// loading
loading?: boolean;
bordered?: boolean;
msPagination?: MsPaginationI; msPagination?: MsPaginationI;
// 展示列表选择按钮 showSetting?: boolean; // 展示列表选择按钮
showSetting?: boolean; pageSimple?: boolean; // 分页是否是简单模式
// 分页是否是简单模式 noDisable?: boolean; // 是否展示禁用的行
pageSimple?: boolean; tableErrorStatus?: MsTableErrorStatus; // 表格的错误状态默认为false
// 是否展示禁用的行 debug?: boolean; // debug模式开启后会打印表格所有state
noDisable?: boolean; showFirstOperation?: boolean; // 是否展示第一行的操作
// 表格的错误状态默认为false
tableErrorStatus?: MsTableErrorStatus;
// debug模式开启后会打印表格所有state
debug?: boolean;
// 是否展示第一行的操作
showFirstOperation?: boolean;
// 已经使用的高度
heightUsed?: number;
[key: string]: any; [key: string]: any;
} }
@ -124,3 +105,8 @@ export interface BatchActionConfig {
export interface renamePopconfirmVisibleType { export interface renamePopconfirmVisibleType {
[key: string]: boolean; [key: string]: boolean;
} }
export interface SetPaginationPrams {
current: number;
total?: number;
}

View File

@ -6,7 +6,8 @@ import dayjs from 'dayjs';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import type { TableData } from '@arco-design/web-vue'; import type { TableData } from '@arco-design/web-vue';
import type { TableQueryParams, CommonList } from '@/models/common'; import type { TableQueryParams, CommonList } from '@/models/common';
import type { MsTableProps, MsTableDataItem, MsTableColumn, MsTableErrorStatus } from './type'; import { SelectAllEnum } from '@/enums/tableEnum';
import type { MsTableProps, MsTableDataItem, MsTableColumn, MsTableErrorStatus, SetPaginationPrams } from './type';
export interface Pagination { export interface Pagination {
current: number; current: number;
@ -24,39 +25,38 @@ export default function useTableProps<T>(
// 编辑操作的保存回调函数 // 编辑操作的保存回调函数
saveCallBack?: (item: T) => Promise<any> saveCallBack?: (item: T) => Promise<any>
) { ) {
// 行选择
const rowSelection = {
type: 'checkbox',
showCheckedAll: false,
};
const defaultProps: MsTableProps<T> = { const defaultProps: MsTableProps<T> = {
tableKey: '', tableKey: '', // 缓存pageSize 或 column 的 key
bordered: true, bordered: true, // 是否显示边框
showPagination: true, showPagination: true, // 是否显示分页
size: 'default', size: 'default', // 表格大小
heightUsed: 294, heightUsed: 294, // 表格所在的页面已经使用的高度
scroll: { x: 1400, y: appStore.innerHeight - 294 }, scroll: { x: 1400, y: appStore.innerHeight - 294 }, // 表格滚动配置
checkable: true, loading: false, // 加载效果
loading: false, data: [], // 表格数据
data: [], /**
*
* showSetting为true时,TableStore.initColumnsk(tableKey: string, column: MsTableColumn)
* showSetting为false时
*/
columns: [] as MsTableColumn, columns: [] as MsTableColumn,
rowKey: 'id', rowKey: 'id', // 表格行的key
selectedKeys: [], /** 选择器相关 */
selectedAll: false, rowSelection: null, // 禁用表格默认的选择器
enableDrag: false, selectable: false, // 是否显示选择器
showSelectAll: true, selectorType: 'checkbox', // 选择器类型
showSetting: false, selectedKeys: new Set<string>(), // 选中的key
excludeKeys: new Set<string>(), // 排除的key
selectorStatus: SelectAllEnum.NONE, // 选择器状态
/** end */
enableDrag: false, // 是否可拖拽
showSetting: false, // 是否展示列选择器
columnResizable: true, columnResizable: true,
// 禁用 arco-table 的分页 pagination: false, // 禁用 arco-table 的分页
pagination: false, pageSimple: false, // 简易分页模式
// 简易分页模式 tableErrorStatus: false, // 表格的错误状态
pageSimple: false, debug: false, // debug 模式
// 表格的错误状态 showFirstOperation: false, // 展示第一行的操作
tableErrorStatus: false,
debug: false,
// 展示第一行的操作
showFirstOperation: false,
...props, ...props,
}; };
@ -96,11 +96,6 @@ export default function useTableProps<T>(
}; };
} }
// 是否可选中
if (propsRes.value.selectable) {
propsRes.value.rowSelection = rowSelection;
}
// 是否可拖拽 // 是否可拖拽
if (propsRes.value.enableDrag) { if (propsRes.value.enableDrag) {
propsRes.value.draggable = { type: 'handle' }; propsRes.value.draggable = { type: 'handle' };
@ -126,13 +121,7 @@ export default function useTableProps<T>(
* *
* @param current //当前页数 * @param current //当前页数
* @param total //总页数默认是0条可选 * @param total //总页数默认是0条可选
* @param fetchData ,
*/ */
interface SetPaginationPrams {
current: number;
total?: number;
}
const setPagination = ({ current, total }: SetPaginationPrams) => { const setPagination = ({ current, total }: SetPaginationPrams) => {
if (propsRes.value.msPagination && typeof propsRes.value.msPagination === 'object') { if (propsRes.value.msPagination && typeof propsRes.value.msPagination === 'object') {
propsRes.value.msPagination.current = current; propsRes.value.msPagination.current = current;
@ -161,9 +150,19 @@ export default function useTableProps<T>(
keyword.value = v; keyword.value = v;
}; };
// 给表格设置选中项 - add rowKey to selectedKeys
const setTableSelected = (key: string) => {
const { selectedKeys } = propsRes.value;
if (!selectedKeys.has(key)) {
selectedKeys.add(key);
}
propsRes.value.selectedKeys = selectedKeys;
};
// 加载分页列表数据 // 加载分页列表数据
const loadList = async () => { const loadList = async () => {
const { current, pageSize } = propsRes.value.msPagination as Pagination; const { current, pageSize } = propsRes.value.msPagination as Pagination;
const { rowKey, selectorStatus, excludeKeys } = propsRes.value;
setLoading(true); setLoading(true);
try { try {
const data = await loadListFunc({ const data = await loadListFunc({
@ -185,6 +184,12 @@ export default function useTableProps<T>(
if (dataTransform) { if (dataTransform) {
item = dataTransform(item); item = dataTransform(item);
} }
if (selectorStatus === SelectAllEnum.ALL) {
if (!excludeKeys.has(item[rowKey])) {
setTableSelected(item[rowKey]);
}
}
return item; return item;
}); });
if (data.total === 0) { if (data.total === 0) {
@ -201,7 +206,7 @@ export default function useTableProps<T>(
// debug 模式下打印属性 // debug 模式下打印属性
if (propsRes.value.debug && import.meta.env.DEV) { if (propsRes.value.debug && import.meta.env.DEV) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('Table propsRes: ', propsRes.value); // console.log('Table propsRes: ', propsRes.value);
} }
} }
}; };
@ -214,6 +219,13 @@ export default function useTableProps<T>(
} }
}; };
// 重置选择器
const resetSelector = () => {
propsRes.value.selectedKeys.clear();
propsRes.value.excludeKeys.clear();
propsRes.value.selectorStatus = SelectAllEnum.NONE;
};
// 事件触发组 // 事件触发组
const propsEvent = ref({ const propsEvent = ref({
// 排序触发 // 排序触发
@ -243,17 +255,6 @@ export default function useTableProps<T>(
} }
loadList(); loadList();
}, },
// 选择触发
selectedChange: (arr: (string | number)[]) => {
if (arr.length === 0) {
propsRes.value.showPagination = true;
propsRes.value.msPagination = oldPagination.value;
} else {
oldPagination.value = propsRes.value.msPagination as Pagination;
propsRes.value.showPagination = false;
}
propsRes.value.selectedKeys = arr;
},
change: (_data: MsTableDataItem<T>[]) => { change: (_data: MsTableDataItem<T>[]) => {
if (propsRes.value.draggable && _data instanceof Array) { if (propsRes.value.draggable && _data instanceof Array) {
(propsRes.value.data as MsTableDataItem<T>[]) = _data; (propsRes.value.data as MsTableDataItem<T>[]) = _data;
@ -269,6 +270,45 @@ export default function useTableProps<T>(
resetSort: () => { resetSort: () => {
sortItem.value = {}; sortItem.value = {};
}, },
// 重置筛选
clearSelector: () => {
resetSelector();
},
// 表格SelectAll change
selectAllChange: (v: SelectAllEnum) => {
propsRes.value.selectorStatus = v;
const { data, rowKey, selectedKeys } = propsRes.value;
if (v === SelectAllEnum.NONE) {
// 清空选中项
resetSelector();
} else {
data.forEach((item) => {
if (item[rowKey] && !selectedKeys.has(item[rowKey])) {
selectedKeys.add(item[rowKey]);
}
});
propsRes.value.selectedKeys = selectedKeys;
}
},
// 表格行的选中/取消事件
rowSelectChange: (key: string) => {
const { selectedKeys, excludeKeys } = propsRes.value;
if (selectedKeys.has(key)) {
// 当前已选中,取消选中
selectedKeys.delete(key);
excludeKeys.add(key);
} else {
// 当前未选中,选中
selectedKeys.add(key);
if (excludeKeys.has(key)) {
excludeKeys.delete(key);
}
}
propsRes.value.selectedKeys = selectedKeys;
propsRes.value.excludeKeys = excludeKeys;
},
}); });
watchEffect(() => { watchEffect(() => {