feat: table列选择器

This commit is contained in:
rubyliu 2023-07-19 22:58:47 +08:00 committed by fit2-zhao
parent a8ee2d4621
commit 61bae0ec14
21 changed files with 419 additions and 47 deletions

View File

@ -37,7 +37,7 @@
"@7polo/kity": "2.0.8",
"@7polo/kityminder-core": "1.4.53",
"@arco-design/web-vue": "^2.48.0",
"@arco-themes/vue-ms-theme-default": "^0.0.17",
"@arco-themes/vue-ms-theme-default": "^0.0.19",
"@form-create/arco-design": "^3.1.21",
"@vueuse/core": "^9.13.0",
"ace-builds": "^1.22.0",

View File

@ -6,13 +6,15 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, onMounted } from 'vue';
import enUS from '@arco-design/web-vue/es/locale/lang/en-us';
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
import GlobalSetting from '@/components/pure/global-setting/index.vue';
import useLocale from '@/locale/useLocale';
import { useTableStore } from './store';
const { currentLocale } = useLocale();
const tableStore = useTableStore();
const locale = computed(() => {
switch (currentLocale.value) {
case 'zh-CN':
@ -23,4 +25,7 @@
return zhCN;
}
});
onMounted(() => {
tableStore.initColumn();
});
</script>

View File

@ -464,3 +464,11 @@
background-color: var(--color-text-input-border);
}
}
/** 开关 **/
.arco-switch {
background: var(--color-fill-4);
}
.arco-switch-checked {
background: rgb(var(--primary-6)) !important;
}

View File

@ -14,7 +14,15 @@
@selection-change="(e) => selectionChange(e, true)"
>
<template #columns>
<a-table-column v-for="(item, key) in props.columns" :key="key" v-bind="item" :title="t(item.title as string)">
<a-table-column v-for="(item, idx) in columns" :key="idx">
<template #title>
<div v-if="attrs.showSetting && idx === columns.length - 1" class="column-selector">
<div class="title">{{ t(item.title as string) }}</div>
<ColumnSelector :table-key="(attrs.tableKey as string)" @close="handleColumnSelectorClose" />
</div>
<slot v-else-if="item.titleSlotName" :name="item.titleSlotName" />
<div v-else class="title">{{ t(item.title as string) }}</div>
</template>
<template #cell="{ column, record, rowIndex }">
<slot v-if="item.slotName" :name="item.slotName" v-bind="{ record, rowIndex, column }"></slot>
<template v-else>{{ record[item.dataIndex as string] }}</template>
@ -36,18 +44,28 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { useAttrs, computed, ref, onMounted } from 'vue';
import { useTableStore } from '@/store';
import selectAll from './select-all.vue';
import { MsTableProps, SelectAllEnum, MsPaginationI, BatchActionParams, BatchActionConfig } from './type';
import {
MsTableProps,
SelectAllEnum,
MsPaginationI,
BatchActionParams,
BatchActionConfig,
MsTableColumn,
} from './type';
import BatchAction from './batchAction.vue';
import type { TableColumnData, TableData } from '@arco-design/web-vue';
import type { TableData } from '@arco-design/web-vue';
import ColumnSelector from './columnSelector.vue';
const batchleft = ref('10px');
const { t } = useI18n();
const tableStore = useTableStore();
const columns = ref<MsTableColumn>([]);
const props = defineProps<{
selectedKeys?: (string | number)[];
actionConfig?: BatchActionConfig;
columns?: TableColumnData[];
noDisable?: boolean;
}>();
const emit = defineEmits<{
@ -55,11 +73,9 @@
(e: 'batchAction', value: BatchActionParams): void;
}>();
const isSelectAll = ref(false);
const attrs = useAttrs();
// -
const selectCurrent = ref(0);
const attrs = useAttrs();
const { rowKey, pagination }: Partial<MsTableProps> = attrs;
// -
@ -71,6 +87,10 @@
}
return data ? data.length : 20;
});
const initColumn = () => {
columns.value = tableStore.getShowInTableColumns(attrs.tableKey as string);
};
// change
const selectionChange = (arr: (string | number)[], setCurrentSelect: boolean) => {
emit('selectedChange', arr);
@ -117,10 +137,14 @@
case 'mini':
return '10px';
default:
return '10px';
return '8px';
}
};
const handleColumnSelectorClose = () => {
initColumn();
};
function getRowClass(record: TableData) {
if (!record.raw.enable && !props.noDisable) {
return 'ms-table-row-disabled';
@ -128,6 +152,7 @@
}
onMounted(() => {
initColumn();
batchleft.value = getBatchLeft();
});
</script>
@ -139,10 +164,18 @@
position: absolute;
top: 3px;
left: v-bind(batchleft);
z-index: 100;
z-index: 99;
border-radius: 2px;
line-height: 40px;
cursor: pointer;
}
.column-selector {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.title {
color: var(--color-text-3);
}
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<icon-settings class="icon" @click="handleClick" />
<MsDrawer
:width="480"
:visible="visible"
unmount-on-close
:footer="false"
:title="t('msTable.columnSetting.display')"
:descriptions="[]"
@cancel="handleCancel"
>
<div class="ms-table-column-seletor">
<div>
<span>{{ t('msTable.columnSetting.mode') }}</span>
<icon-question-circle class="ml-1" />
</div>
<a-radio-group :model-value="currentMode" class="ml-[14px]" type="button" @change="handleModeChange">
<a-radio value="drawer">
<div class="mode-button">
<MsIcon :class="{ 'active-color': currentMode === 'drawer' }" type="icon-icon_drawer" />
<span class="mode-button-title">{{ t('msTable.columnSetting.drawer') }}</span>
</div>
</a-radio>
<a-radio value="new_window">
<div class="mode-button">
<MsIcon :class="{ 'active-color': currentMode === 'new_window' }" type="icon-icon_into-item_outlined" />
<span class="mode-button-title">{{ t('msTable.columnSetting.newWindow') }}</span>
</div>
</a-radio>
</a-radio-group>
<a-divider />
<div class="flex items-center justify-between">
<div>{{ t('msTable.columnSetting.header') }}</div>
<MsButton :disabled="!hasChange" @click="handleReset">{{ t('msTable.columnSetting.resetDefault') }}</MsButton>
</div>
<div class="flex-col">
<div v-for="(item, idx) in firstColumns" :key="item.dataIndex" class="column-item">
<div>{{ t(item.title as string) }}</div>
<a-switch size="small" :model-value="item.showInTable" @change="handleFisrtColumnChange(idx)" />
</div>
</div>
<a-divider orientation="center" class="non-sort">{{ t('msTable.columnSetting.nonSort') }}</a-divider>
<Draggable tag="div" :list="secondColumns" class="list-group" handle=".handle" item-key="dateIndex">
<template #item="{ element, index }">
<div class="column-drag-item">
<div class="flex items-center">
<icon-drag-dot-vertical class="handle" />
<div class="ml-[8px]">{{ t(element.title as string) }}</div>
</div>
<a-switch size="small" :model-value="element.showInTable" @change="handleSecondColumnChange(index)" />
</div>
</template>
</Draggable>
</div>
</MsDrawer>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { onMounted, ref } from 'vue';
import { useTableStore } from '@/store';
import { MsTableColumn } from './type';
import MsButton from '@/components/pure/ms-button/index.vue';
import { TableOpenDetailMode } from '@/store/modules/ms-table/types';
import Draggable from 'vuedraggable';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
const tableStore = useTableStore();
const { t } = useI18n();
const currentMode = ref('');
//
const firstColumns = ref<MsTableColumn>([]);
//
const secondColumns = ref<MsTableColumn>([]);
//
const hasChange = ref(false);
const emit = defineEmits<{
(e: 'close'): void;
}>();
const props = defineProps<{
tableKey: string;
}>();
const visible = ref(false);
const handleClick = () => {
visible.value = true;
};
const handleCancel = () => {
tableStore.setColumns(
props.tableKey,
[...firstColumns.value, ...secondColumns.value],
currentMode.value as TableOpenDetailMode
);
visible.value = false;
emit('close');
};
const loadColumn = (key: string) => {
const { nonSortableColumns: noSort, couldSortableColumns: couldSort } = tableStore.getColumns(key);
firstColumns.value = noSort;
secondColumns.value = couldSort;
};
const handleReset = () => {
loadColumn(props.tableKey);
};
const handleFisrtColumnChange = (idx: number) => {
const item = firstColumns.value[idx];
item.showInTable = !item.showInTable;
hasChange.value = true;
};
const handleSecondColumnChange = (idx: number) => {
const item = secondColumns.value[idx];
item.showInTable = !item.showInTable;
hasChange.value = true;
};
const handleModeChange = (value: string | number | boolean) => {
currentMode.value = value as string;
tableStore.setMode(props.tableKey, value as TableOpenDetailMode);
};
onMounted(() => {
if (props.tableKey) {
currentMode.value = tableStore.getMode(props.tableKey);
loadColumn(props.tableKey);
}
});
</script>
<style lang="less" scoped>
.icon {
margin-left: 16px;
color: var(--color-text-4);
background-color: var(--color-text-10);
}
.mode-button {
display: flex;
flex-flow: row nowrap;
align-items: center;
.active-color {
color: var(--color-primary-5);
}
.mode-button-title {
margin-left: 4px;
}
}
.column-item {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 8px 12px 8px 36px;
}
.column-drag-item {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
}
:deep(.arco-divider-text) {
padding: 0 8px;
color: var(--color-text-4);
}
.non-sort {
font-size: 12px;
}
</style>

View File

@ -15,5 +15,14 @@ export default {
addPublic: '添加到公用用例库',
clear: 'clear',
},
columnSetting: {
display: 'Table Display Settings',
mode: 'Mode Settings',
drawer: 'Drawer',
newWindow: 'New Window',
header: 'Header Settings',
resetDefault: 'Reset default',
nonSort: 'The above properties cannot be sorted',
},
},
};

View File

@ -15,5 +15,14 @@ export default {
addPublic: '添加到公用用例库',
clear: '清空',
},
columnSetting: {
display: '表格显示设置',
mode: '模式设置',
drawer: '抽屉',
newWindow: '新窗口',
header: '表头设置',
resetDefault: '恢复默认',
nonSort: '以上属性不可排序',
},
},
};

View File

@ -7,10 +7,18 @@ export interface MsPaginationI {
showPageSize: boolean;
}
export interface MsTableColumnData extends TableColumnData {
// 是否可排序
showDrag?: boolean;
// 是否展示在表格上
showInTable?: boolean;
}
// 表格属性
export interface MsTableProps {
// 表格key, 用于存储表格列配置
tableKey: string;
// 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column;
columns: TableColumnData[];
columns: MsTableColumnData[];
// 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data;
data: TableData[];
// 表格尺寸
@ -33,8 +41,6 @@ export interface MsTableProps {
selectable?: boolean;
// 展示自定义全选
showSelectAll?: boolean;
// 表格是否可展开
expandable?: boolean;
// 表格是否可固定表头
fixedHeader?: boolean;
// 表格是否可固定列
@ -44,8 +50,10 @@ export interface MsTableProps {
// loading
loading?: boolean;
bordered?: boolean;
// pagination
// 分页配置
pagination: MsPaginationI | boolean;
// 展示列表选择按钮
showSetting?: boolean;
[key: string]: any;
}
@ -57,7 +65,7 @@ export interface MsTableSelectAll {
}
export type MsTableData = TableData[];
export type MsTableColumn = TableColumnData[];
export type MsTableColumn = MsTableColumnData[];
export type MSTableChangeExtra = TableChangeExtra;
// eslint-disable-next-line no-shadow

View File

@ -28,6 +28,7 @@ export default function useTableProps(
};
const defaultProps: MsTableProps = {
tableKey: '',
bordered: true,
showPagination: true,
size: 'small',
@ -49,6 +50,7 @@ export default function useTableProps(
selectedAll: false,
enableDrag: false,
showSelectAll: true,
showSetting: true,
...props,
};
@ -163,6 +165,14 @@ export default function useTableProps(
}
};
// 重置页码和条数
const resetPagination = () => {
if (propsRes.value.pagination && typeof propsRes.value.pagination === 'object') {
propsRes.value.pagination.current = 1;
propsRes.value.pagination.pageSize = appStore.pageSize;
}
};
// 事件触发组
const propsEvent = ref({
// 排序触发
@ -216,5 +226,6 @@ export default function useTableProps(
setPagination,
setLoadListParams,
setKeyword,
resetPagination,
};
}

View File

@ -0,0 +1,11 @@
export enum TableOpenDetailModeEnum {
DRAWER = 'drawer',
NEW_WINDOW = 'new_window',
}
export enum TableModuleEnum {
USERGROUPINDEX = 'userGroupIndex',
}
export enum TableKeyEnum {
USERGROUPUSER = 'userGroupUser',
}

View File

@ -4,8 +4,10 @@ import useAppStore from './modules/app';
import useVisitStore from './modules/app/visit';
import useUserStore from './modules/user';
import { debouncePlugin } from './plugins';
import useUserGroupStore from './modules/setting/usergroup';
import useTableStore from './modules/ms-table';
const pinia = createPinia().use(debouncePlugin).use(piniaPluginPersistedstate);
export { useAppStore, useUserStore, useVisitStore };
export { useAppStore, useUserStore, useVisitStore, useUserGroupStore, useTableStore };
export default pinia;

View File

@ -22,6 +22,7 @@ export interface AppState {
breadcrumbList: BreadcrumbItem[];
currentOrgId: string;
currentProjectId: string;
pageSize: number;
[key: string]: unknown;
}

View File

@ -0,0 +1,58 @@
import { defineStore } from 'pinia';
import { MsTableSelectorItem, MsTableState, TableOpenDetailMode } from './types';
import userGroupUsercolumns from './module/setting/system/usergroup';
import { TableKeyEnum } from '@/enums/tableEnum';
import { MsTableColumn } from '@/components/pure/ms-table/type';
const msTableStore = defineStore('msTable', {
// 开启数据持久化
persist: true,
state: (): MsTableState => ({
selectorColumnMap: new Map<string, MsTableSelectorItem>(),
}),
actions: {
initColumn() {
const tmpMap = new Map<string, MsTableSelectorItem>();
tmpMap.set(TableKeyEnum.USERGROUPUSER, {
mode: 'drawer',
column: userGroupUsercolumns,
});
this.selectorColumnMap = tmpMap;
},
getMode(key: string): string {
if (this.selectorColumnMap.has(key)) {
return this.selectorColumnMap.get(key)?.mode || '';
}
return '';
},
setMode(key: string, mode: TableOpenDetailMode) {
if (this.selectorColumnMap.has(key)) {
const item = this.selectorColumnMap.get(key);
if (item) {
item.mode = mode;
}
}
},
getColumns(key: string): { nonSortableColumns: MsTableColumn; couldSortableColumns: MsTableColumn } {
if (this.selectorColumnMap.has(key)) {
const tmpArr = this.selectorColumnMap.get(key)?.column || [];
const nonSortableColumns = tmpArr.filter((item) => !item.showDrag);
const couldSortableColumns = tmpArr.filter((item) => item.showDrag);
return { nonSortableColumns, couldSortableColumns };
}
return { nonSortableColumns: [], couldSortableColumns: [] };
},
setColumns(key: string, columns: MsTableColumn, mode: TableOpenDetailMode) {
this.selectorColumnMap.set(key, { mode, column: columns });
},
getShowInTableColumns(key: string): MsTableColumn {
if (this.selectorColumnMap?.has(key)) {
const tmpArr = this.selectorColumnMap.get(key)?.column;
return tmpArr?.filter((item) => item.showInTable) || [];
}
return [];
},
},
});
export default msTableStore;

View File

@ -0,0 +1,31 @@
import { MsTableColumn } from '@/components/pure/ms-table/type';
const userGroupUsercolumns: MsTableColumn = [
{
title: 'system.userGroup.name',
dataIndex: 'name',
showDrag: false,
showInTable: true,
},
{
title: 'system.userGroup.email',
dataIndex: 'email',
showDrag: false,
showInTable: true,
},
{
title: 'system.userGroup.phone',
dataIndex: 'email',
showDrag: true,
showInTable: true,
},
{
title: 'system.userGroup.operation',
slotName: 'action',
fixed: 'right',
width: 200,
showDrag: true,
showInTable: true,
},
];
export default userGroupUsercolumns;

View File

@ -0,0 +1,13 @@
import { MsTableColumn } from '@/components/pure/ms-table/type';
export type TableOpenDetailMode = 'drawer' | 'new_window';
export interface MsTableSelectorItem {
// 详情打开模式
mode: TableOpenDetailMode;
// 列配置
column: MsTableColumn;
}
export interface MsTableState {
selectorColumnMap: Map<string, MsTableSelectorItem>;
}

View File

@ -1,5 +1,5 @@
<template>
<div class="banner-wrap mt-40">
<div class="banner-wrap">
<img class="img w-567px m-auto block" :src="bannerImage" />
</div>
</template>
@ -10,6 +10,7 @@
<style lang="less" scoped>
.banner-wrap {
padding-top: 160px;
height: 760px;
.img {
height: 365px;

View File

@ -2,7 +2,7 @@
<div class="relative">
<a-table
:span-method="dataSpanMethod"
:scroll="{ y: '860px', x: '1440px' }"
:scroll="{ y: '860px', x: '800px' }"
:data="tableData"
:loading="loading"
:bordered="{ wrapper: true, cell: true }"

View File

@ -1,7 +1,7 @@
<template>
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
<template #action="{ record }">
<ms-button type="link" @click="handleRemove(record)">{{ t('system.userGroup.remove') }}</ms-button>
<ms-button @click="handleRemove(record)">{{ t('system.userGroup.remove') }}</ms-button>
</template>
</MsBaseTable>
</template>
@ -10,38 +10,19 @@
import { useI18n } from '@/hooks/useI18n';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useUserGroupStore from '@/store/modules/setting/usergroup';
import { useUserGroupStore } from '@/store';
import { watchEffect } from 'vue';
import { postUserByUserGroup, deleteUserFromUserGroup } from '@/api/modules/setting/usergroup';
import { UserTableItem } from '@/models/setting/usergroup';
import { TableKeyEnum } from '@/enums/tableEnum';
import MsButton from '@/components/pure/ms-button/index.vue';
const { t } = useI18n();
const store = useUserGroupStore();
const columns: MsTableColumn = [
{
title: 'system.userGroup.name',
dataIndex: 'name',
},
{
title: 'system.userGroup.email',
dataIndex: 'email',
},
{
title: 'system.userGroup.phone',
dataIndex: 'email',
},
{
title: 'system.userGroup.operation',
slotName: 'action',
fixed: 'right',
width: 200,
},
];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(postUserByUserGroup, {
columns,
scroll: { y: 750, x: 2000 },
tableKey: TableKeyEnum.USERGROUPUSER,
scroll: { y: 750, x: '600px' },
selectable: true,
});
const fetchData = async () => {
@ -58,4 +39,3 @@
}
});
</script>
@/models/setting/usergroup @/api/modules/setting/usergroup

View File

@ -57,18 +57,26 @@ export default {
},
permission: {
system_user_role: {
name: 'Name',
name: 'User role',
read: 'Read',
add: 'Add',
update: 'Update',
delete: 'Delete',
},
system_test_resource_pool: {
name: 'Name',
name: 'Resource pool',
read: 'Read',
add: 'Add',
update: 'Update',
delete: 'Delete',
},
system_organization_project: {
name: 'Organization project',
read: 'Read',
add: 'Add',
update: 'Update',
delete: 'Delete',
recover: 'Recover',
},
},
};

View File

@ -69,5 +69,13 @@ export default {
update: '更新',
delete: '删除',
},
system_organization_project: {
name: '组织项目',
read: '读取',
add: '添加',
update: '更新',
delete: '删除',
recover: '恢复',
},
},
};