refactor(projects): finish refactor useTable and apply

This commit is contained in:
Soybean 2024-03-21 19:38:29 +08:00
parent 4158a72bd8
commit 3fd15e5649
14 changed files with 264 additions and 843 deletions

View File

@ -2,8 +2,8 @@ import useBoolean from './use-boolean';
import useLoading from './use-loading';
import useContext from './use-context';
import useSvgIconRender from './use-svg-icon-render';
import useTable from './use-table';
import useHookTable from './use-table';
export { useBoolean, useLoading, useContext, useSvgIconRender, useTable };
export { useBoolean, useLoading, useContext, useSvgIconRender, useHookTable };
export * from './use-table';

View File

@ -7,18 +7,16 @@ export type MaybePromise<T> = T | Promise<T>;
export type ApiFn = (args: any) => Promise<unknown>;
export type TableData = Record<string, unknown>;
export type TableColumn = Record<string, any>;
export type TableColumnCheck = {
key: string;
title: string;
checked: boolean;
};
export type TableDataWithIndex<T> = T & { index: number };
export type TransformedData<T> = {
data: T[];
data: TableDataWithIndex<T>[];
pageNum: number;
pageSize: number;
total: number;
@ -61,7 +59,7 @@ export type TableConfig<A extends ApiFn, T, C> = {
immediate?: boolean;
};
export default function useTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
const { loading, startLoading, endLoading } = useLoading();
const { bool: empty, setBool: setEmpty } = useBoolean();

View File

@ -1,226 +0,0 @@
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
import type { Ref } from 'vue';
import type { PaginationProps } from 'naive-ui';
import { useBoolean, useTable } from '@sa/hooks';
import { useAppStore } from '@/store/modules/app';
import { $t } from '@/locales';
type TableData = NaiveUI.TableData;
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
type TableColumn<T> = NaiveUI.TableColumn<T>;
export function useNaiveTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
const scope = effectScope();
const appStore = useAppStore();
const { apiFn, apiParams, immediate } = config;
const SELECTION_KEY = '__selection__';
const {
loading,
empty,
data,
columns,
columnChecks,
reloadColumns,
getData,
searchParams,
updateSearchParams,
resetSearchParams
} = useTable<A, GetTableData<A>, TableColumn<GetTableData<A>>>({
apiFn,
apiParams,
columns: config.columns,
transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
return {
data: records,
pageNum: current,
pageSize: size,
total
};
},
getColumnChecks: cols => {
const checks: NaiveUI.TableColumnCheck[] = [];
cols.forEach(column => {
if (isTableColumnHasKey(column)) {
checks.push({
key: column.key as string,
title: column.title as string,
checked: true
});
} else if (column.type === 'selection') {
checks.push({
key: SELECTION_KEY,
title: $t('common.check'),
checked: true
});
}
});
return checks;
},
getColumns: (cols, checks) => {
const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
cols.forEach(column => {
if (isTableColumnHasKey(column)) {
columnMap.set(column.key as string, column);
} else if (column.type === 'selection') {
columnMap.set(SELECTION_KEY, column);
}
});
const filteredColumns = checks
.filter(item => item.checked)
.map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
return filteredColumns;
},
onFetched: async transformed => {
const { pageNum, pageSize, total } = transformed;
updatePagination({
page: pageNum,
pageSize,
itemCount: total
});
},
immediate
});
const pagination: PaginationProps = reactive({
page: 1,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 15, 20, 25, 30],
onUpdatePage: async (page: number) => {
pagination.page = page;
updateSearchParams({
current: page,
size: pagination.pageSize!
});
getData();
},
onUpdatePageSize: async (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
updateSearchParams({
current: pagination.page,
size: pageSize
});
getData();
}
});
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
const mobilePagination = computed(() => {
const p: PaginationProps = {
...pagination,
pageSlot: appStore.isMobile ? 3 : 9
};
return p;
});
function updatePagination(update: Partial<PaginationProps>) {
Object.assign(pagination, update);
}
scope.run(() => {
watch(
() => appStore.locale,
() => {
reloadColumns();
}
);
});
onScopeDispose(() => {
scope.stop();
});
return {
loading,
empty,
data,
columns,
columnChecks,
reloadColumns,
pagination,
mobilePagination,
updatePagination,
getData,
searchParams,
updateSearchParams,
resetSearchParams
};
}
export function useNaiveTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
const operateType = ref<NaiveUI.TableOperateType>('add');
function handleAdd() {
operateType.value = 'add';
openDrawer();
}
/** the editing row data */
const editingData: Ref<T | null> = ref(null);
function handleEdit(id: number) {
operateType.value = 'edit';
editingData.value = data.value.find(item => item.id === id) || null;
openDrawer();
}
const checkedRowKeys = ref<string[]>([]);
/** the hook after the batch delete operation is completed */
async function onBatchDeleted() {
window.$message?.success($t('common.deleteSuccess'));
checkedRowKeys.value = [];
await getData();
}
/** the hook after the delete operation is completed */
async function onDeleted() {
window.$message?.success($t('common.deleteSuccess'));
await getData();
}
return {
drawerVisible,
operateType,
handleAdd,
editingData,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted,
closeDrawer
};
}
export function getNaiveTableIndex(pagination: PaginationProps, index: number) {
const { page = 1, pageSize = 10 } = pagination;
return (page - 1) * pageSize + index + 1;
}
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
}

View File

@ -1,151 +1,144 @@
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
import type { Ref } from 'vue';
import type { DataTableBaseColumn, DataTableExpandColumn, DataTableSelectionColumn, PaginationProps } from 'naive-ui';
import type { TableColumnGroup } from 'naive-ui/es/data-table/src/interface';
import { useBoolean, useLoading } from '@sa/hooks';
import type { PaginationProps } from 'naive-ui';
import { useBoolean, useHookTable } from '@sa/hooks';
import { useAppStore } from '@/store/modules/app';
import { $t } from '@/locales';
type BaseData = Record<string, unknown>;
type TableData = NaiveUI.TableData;
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
type TableColumn<T> = NaiveUI.TableColumn<T>;
type ApiFn = (args: any) => Promise<unknown>;
export type TableColumn<T extends BaseData = BaseData, CustomColumnKey = never> =
| (Omit<TableColumnGroup<T>, 'key'> & { key: keyof T | CustomColumnKey })
| (Omit<DataTableBaseColumn<T>, 'key'> & { key: keyof T | CustomColumnKey })
| DataTableSelectionColumn<T>
| DataTableExpandColumn<T>;
export type TransformedData<TableData extends BaseData = BaseData> = {
data: TableData[];
pageNum: number;
pageSize: number;
total: number;
};
/** transform api response to table data */
type Transformer<TableData extends BaseData = BaseData, Response = NonNullable<unknown>> = (
response: Response
) => TransformedData<TableData>;
/** table config */
export type TableConfig<TableData extends BaseData = BaseData, Fn extends ApiFn = ApiFn, CustomColumnKey = never> = {
/** api function to get table data */
apiFn: Fn;
/** api params */
apiParams?: Parameters<Fn>[0];
/** transform api response to table data */
transformer: Transformer<TableData, Awaited<ReturnType<Fn>>>;
/** pagination */
pagination?: PaginationProps;
/**
* callback when pagination changed
*
* @param pagination
*/
onPaginationChanged?: (pagination: PaginationProps) => void | Promise<void>;
/**
* whether to get data immediately
*
* @default true
*/
immediate?: boolean;
/** columns factory */
columns: () => TableColumn<TableData, CustomColumnKey>[];
};
/** filter columns */
export type FilteredColumn = {
key: string;
title: string;
checked: boolean;
};
export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomColumnKey = never>(
config: TableConfig<TableData, Fn, CustomColumnKey>
) {
export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
const scope = effectScope();
const appStore = useAppStore();
const { loading, startLoading, endLoading } = useLoading();
const { bool: empty, setBool: setEmpty } = useBoolean();
const { apiFn, apiParams, immediate } = config;
const { apiFn, apiParams, transformer, onPaginationChanged, immediate = true } = config;
const SELECTION_KEY = '__selection__';
const searchParams: NonNullable<Parameters<Fn>[0]> = reactive({ ...apiParams });
const {
loading,
empty,
data,
columns,
columnChecks,
reloadColumns,
getData,
searchParams,
updateSearchParams,
resetSearchParams
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
apiFn,
apiParams,
columns: config.columns,
transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
const { columns, filteredColumns, reloadColumns } = useTableColumn(config.columns);
const recordsWithIndex = records.map((item, index) => {
return {
...item,
index: (current - 1) * size + index + 1
};
});
const data: Ref<TableData[]> = ref([]);
return {
data: recordsWithIndex,
pageNum: current,
pageSize: size,
total
};
},
getColumnChecks: cols => {
const checks: NaiveUI.TableColumnCheck[] = [];
const pagination = reactive({
cols.forEach(column => {
if (isTableColumnHasKey(column)) {
checks.push({
key: column.key as string,
title: column.title as string,
checked: true
});
} else if (column.type === 'selection') {
checks.push({
key: SELECTION_KEY,
title: $t('common.check'),
checked: true
});
}
});
return checks;
},
getColumns: (cols, checks) => {
const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
cols.forEach(column => {
if (isTableColumnHasKey(column)) {
columnMap.set(column.key as string, column);
} else if (column.type === 'selection') {
columnMap.set(SELECTION_KEY, column);
}
});
const filteredColumns = checks
.filter(item => item.checked)
.map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
return filteredColumns;
},
onFetched: async transformed => {
const { pageNum, pageSize, total } = transformed;
updatePagination({
page: pageNum,
pageSize,
itemCount: total
});
},
immediate
});
const pagination: PaginationProps = reactive({
page: 1,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 15, 20, 25, 30],
// Fix Naive Pagination's outdated API
onUpdatePage: async (page: number) => {
pagination.page = page;
await onPaginationChanged?.(pagination);
updateSearchParams({
current: page,
size: pagination.pageSize!
});
getData();
},
onUpdatePageSize: async (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
await onPaginationChanged?.(pagination);
},
...config.pagination
}) as PaginationProps;
updateSearchParams({
current: pagination.page,
size: pageSize
});
function updatePagination(update: Partial<PaginationProps>) {
Object.assign(pagination, update);
}
async function getData() {
startLoading();
const formattedParams = formatSearchParams(searchParams);
const response = await apiFn(formattedParams);
const { data: tableData, pageNum, pageSize, total } = transformer(response as Awaited<ReturnType<Fn>>);
data.value = tableData;
setEmpty(tableData.length === 0);
updatePagination({ page: pageNum, pageSize, itemCount: total });
endLoading();
}
function formatSearchParams(params: Record<string, unknown>) {
const formattedParams: Record<string, unknown> = {};
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined) {
formattedParams[key] = value;
getData();
}
});
return formattedParams;
}
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
const mobilePagination = computed(() => {
const p: PaginationProps = {
...pagination,
pageSlot: appStore.isMobile ? 3 : 9
};
/**
* update search params
*
* @param params
*/
function updateSearchParams(params: Partial<Parameters<Fn>[0]>) {
Object.assign(searchParams, params);
}
return p;
});
/** reset search params */
function resetSearchParams() {
Object.assign(searchParams, apiParams);
}
if (immediate) {
getData();
function updatePagination(update: Partial<PaginationProps>) {
Object.assign(pagination, update);
}
scope.run(() => {
@ -166,9 +159,10 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
empty,
data,
columns,
filteredColumns,
columnChecks,
reloadColumns,
pagination,
mobilePagination,
updatePagination,
getData,
searchParams,
@ -177,62 +171,58 @@ export function useTable<TableData extends BaseData, Fn extends ApiFn, CustomCol
};
}
function useTableColumn<TableData extends BaseData, CustomColumnKey = never>(
factory: () => TableColumn<TableData, CustomColumnKey>[]
) {
const SELECTION_KEY = '__selection__';
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
const allColumns = ref(factory()) as Ref<TableColumn<TableData, CustomColumnKey>[]>;
const operateType = ref<NaiveUI.TableOperateType>('add');
const filteredColumns: Ref<FilteredColumn[]> = ref(getFilteredColumns(factory()));
const columns = computed(() => getColumns());
function reloadColumns() {
allColumns.value = factory();
filteredColumns.value = getFilteredColumns(factory());
function handleAdd() {
operateType.value = 'add';
openDrawer();
}
function getFilteredColumns(aColumns: TableColumn<TableData, CustomColumnKey>[]) {
const cols: FilteredColumn[] = [];
/** the editing row data */
const editingData: Ref<T | null> = ref(null);
aColumns.forEach(column => {
if (column.type === undefined) {
cols.push({
key: column.key as string,
title: column.title as string,
checked: true
});
function handleEdit(id: T['id']) {
operateType.value = 'edit';
editingData.value = data.value.find(item => item.id === id) || null;
openDrawer();
}
if (column.type === 'selection') {
cols.push({
key: SELECTION_KEY,
title: $t('common.check'),
checked: true
});
}
});
/** the checked row keys of table */
const checkedRowKeys = ref<string[]>([]);
return cols;
/** the hook after the batch delete operation is completed */
async function onBatchDeleted() {
window.$message?.success($t('common.deleteSuccess'));
checkedRowKeys.value = [];
await getData();
}
function getColumns() {
const cols = filteredColumns.value
.filter(column => column.checked)
.map(column => {
if (column.key === SELECTION_KEY) {
return allColumns.value.find(col => col.type === 'selection');
}
return allColumns.value.find(col => (col as DataTableBaseColumn).key === column.key);
});
/** the hook after the delete operation is completed */
async function onDeleted() {
window.$message?.success($t('common.deleteSuccess'));
return cols as TableColumn<TableData, CustomColumnKey>[];
await getData();
}
return {
columns,
reloadColumns,
filteredColumns
drawerVisible,
operateType,
handleAdd,
editingData,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted,
closeDrawer
};
}
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
}

View File

@ -30,10 +30,22 @@ export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
});
}
/** get menu list */
export function fetchGetMenuList() {
/**
* get menu list
*
* @deprecated this will removed in next version 1.1.0
*/
export function fetchGetMenuListV1() {
return request<Api.SystemManage.Menu[]>({
url: '/systemManage/getMenuList',
method: 'get'
});
}
/** get menu list */
export function fetchGetMenuList() {
return request<Api.SystemManage.MenuList>({
url: '/systemManage/getMenuList/v2',
method: 'get'
});
}

View File

@ -219,5 +219,8 @@ declare namespace Api {
/** children menu */
children?: Menu[];
}>;
/** menu list */
type MenuList = Common.PaginatingQueryRecord<Menu>;
}
}

View File

@ -8,9 +8,15 @@ declare namespace NaiveUI {
type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
type PaginationProps = import('naive-ui').PaginationProps;
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
type TableDataWithIndex<T> = import('@sa/hooks').TableDataWithIndex<T>;
type FlatResponseData<T> = import('@sa/axios').FlatResponseData<T>;
type CustomColumnKey = 'index' | 'operate';
/**
* the custom column key
*
* if you want to add a custom column, you should add a key to this type
*/
type CustomColumnKey = 'operate';
type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | CustomColumnKey };
@ -35,7 +41,7 @@ declare namespace NaiveUI {
type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
type NaiveTableConfig<A extends TableApiFn> = Pick<
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<GetTableData<A>>>,
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>,
'apiFn' | 'apiParams' | 'columns' | 'immediate'
>;
}

View File

@ -1,37 +1,21 @@
<script setup lang="tsx">
import { ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { fetchGetMenuList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue';
import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
import MenuOperateDrawer from './modules/menu-operate-drawer.vue';
const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
const wrapperRef = ref<HTMLElement | null>(null);
const { columns, filteredColumns, data, loading, pagination, getData } = useTable<
Api.SystemManage.Menu,
typeof fetchGetMenuList,
'index' | 'operate'
>({
const { columns, columnChecks, data, loading, pagination, getData } = useTable({
apiFn: fetchGetMenuList,
transformer: res => {
const menus = res.data || [];
return {
data: menus,
pageNum: 1,
pageSize: 10,
total: menus.length
};
},
columns: () => [
{
type: 'selection',
@ -163,7 +147,7 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
{$t('page.manage.menu.addChildMenu')}
</NButton>
)}
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
@ -182,50 +166,40 @@ const { columns, filteredColumns, data, loading, pagination, getData } = useTabl
]
});
const operateType = ref<OperateType>('add');
function handleAdd() {
operateType.value = 'add';
openDrawer();
}
const checkedRowKeys = ref<string[]>([]);
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
window.$message?.success($t('common.deleteSuccess'));
checkedRowKeys.value = [];
onBatchDeleted();
}
getData();
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
function handleAddChildMenu(id: number) {
console.log('id: ', id);
operateType.value = 'add';
openDrawer();
}
/** the editing row data */
const editingData = ref<Api.SystemManage.Menu | null>(null);
function handleEdit(id: number) {
operateType.value = 'edit';
editingData.value = data.value.find(item => item.id === id) || null;
openDrawer();
}
async function handleDelete(id: number) {
// request
console.log(id);
window.$message?.success($t('common.deleteSuccess'));
getData();
handleAdd();
}
function getRowKey(row: Api.SystemManage.Menu) {
return row.id;
function edit(id: number) {
handleEdit(id);
}
</script>
@ -234,7 +208,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="filteredColumns"
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@ -250,7 +224,7 @@ function getRowKey(row: Api.SystemManage.Menu) {
:flex-height="!appStore.isMobile"
:scroll-x="1088"
:loading="loading"
:row-key="getRowKey"
:row-key="row => row.id"
remote
:pagination="pagination"
class="sm:h-full"

View File

@ -11,17 +11,9 @@ defineOptions({
name: 'MenuOperateDrawer'
});
/**
* the type of operation
*
* - add: add user
* - edit: edit user
*/
export type OperateType = 'add' | 'edit';
interface Props {
/** the type of operation */
operateType: OperateType;
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.SystemManage.Menu | null;
}
@ -42,7 +34,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<OperateType, string> = {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.manage.menu.addMenu'),
edit: $t('page.manage.menu.editMenu')
};

View File

@ -1,30 +1,16 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import type { PaginationProps } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { fetchGetRoleList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import { enableStatusRecord } from '@/constants/business';
import RoleOperateDrawer, { type OperateType } from './modules/role-operate-drawer.vue';
import RoleOperateDrawer from './modules/role-operate-drawer.vue';
import RoleSearch from './modules/role-search.vue';
const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
const {
columns,
filteredColumns,
data,
loading,
pagination,
getData,
searchParams,
updateSearchParams,
resetSearchParams
} = useTable<Api.SystemManage.Role, typeof fetchGetRoleList, 'index' | 'operate'>({
const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetRoleList,
apiParams: {
current: 1,
@ -35,26 +21,6 @@ const {
roleName: null,
roleCode: null
},
transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
return {
data: records,
pageNum: current,
pageSize: size,
total
};
},
onPaginationChanged(pg) {
const { page, pageSize } = pg;
updateSearchParams({
current: page,
size: pageSize
});
getData();
},
columns: () => [
{
type: 'selection',
@ -64,7 +30,6 @@ const {
{
key: 'index',
title: $t('common.index'),
render: (_, index): string => getIndex(index),
width: 64,
align: 'center'
},
@ -112,7 +77,7 @@ const {
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
@ -131,60 +96,34 @@ const {
]
});
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
const mobilePagination = computed(() => {
const p: PaginationProps = {
...pagination,
pageSlot: appStore.isMobile ? 3 : 9
};
return p;
});
const operateType = ref<OperateType>('add');
function handleAdd() {
operateType.value = 'add';
openDrawer();
}
const checkedRowKeys = ref<string[]>([]);
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
window.$message?.success($t('common.deleteSuccess'));
checkedRowKeys.value = [];
getData();
onBatchDeleted();
}
/** the editing row data */
const editingData = ref<Api.SystemManage.Role | null>(null);
function handleEdit(id: number) {
operateType.value = 'edit';
editingData.value = data.value.find(item => item.id === id) || null;
openDrawer();
}
async function handleDelete(id: number) {
function handleDelete(id: number) {
// request
console.log(id);
window.$message?.success($t('common.deleteSuccess'));
getData();
onDeleted();
}
function getIndex(index: number) {
const { page = 0, pageSize = 10 } = pagination;
return String((page - 1) * pageSize + index + 1);
}
function getRowKey(row: Api.SystemManage.User) {
return row.id;
function edit(id: number) {
handleEdit(id);
}
</script>
@ -194,7 +133,7 @@ function getRowKey(row: Api.SystemManage.User) {
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="filteredColumns"
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@ -211,7 +150,7 @@ function getRowKey(row: Api.SystemManage.User) {
:scroll-x="702"
:loading="loading"
remote
:row-key="getRowKey"
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>

View File

@ -8,17 +8,9 @@ defineOptions({
name: 'RoleOperateDrawer'
});
/**
* the type of operation
*
* - add: add role
* - edit: edit role
*/
export type OperateType = 'add' | 'edit';
interface Props {
/** the type of operation */
operateType: OperateType;
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.SystemManage.Role | null;
}
@ -39,7 +31,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<OperateType, string> = {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.manage.role.addRole'),
edit: $t('page.manage.role.editRole')
};

View File

@ -1,30 +1,16 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import type { PaginationProps } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { fetchGetUserList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import UserOperateDrawer, { type OperateType } from './modules/user-operate-drawer.vue';
import { useTable, useTableOperate } from '@/hooks/common/table';
import UserOperateDrawer from './modules/user-operate-drawer.vue';
import UserSearch from './modules/user-search.vue';
const appStore = useAppStore();
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
const {
columns,
filteredColumns,
data,
loading,
pagination,
getData,
searchParams,
updateSearchParams,
resetSearchParams
} = useTable<Api.SystemManage.User, typeof fetchGetUserList, 'index' | 'operate'>({
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetUserList,
apiParams: {
current: 1,
@ -38,26 +24,6 @@ const {
userPhone: null,
userEmail: null
},
transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
return {
data: records,
pageNum: current,
pageSize: size,
total
};
},
onPaginationChanged(pg) {
const { page, pageSize } = pg;
updateSearchParams({
current: page,
size: pageSize
});
getData();
},
columns: () => [
{
type: 'selection',
@ -67,7 +33,6 @@ const {
{
key: 'index',
title: $t('common.index'),
render: (_, index): string => getIndex(index),
align: 'center',
width: 64
},
@ -142,7 +107,7 @@ const {
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row.id)}>
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
@ -161,60 +126,34 @@ const {
]
});
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
const mobilePagination = computed(() => {
const p: PaginationProps = {
...pagination,
pageSlot: appStore.isMobile ? 3 : 9
};
return p;
});
const operateType = ref<OperateType>('add');
function handleAdd() {
operateType.value = 'add';
openDrawer();
}
const checkedRowKeys = ref<string[]>([]);
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
window.$message?.success($t('common.deleteSuccess'));
checkedRowKeys.value = [];
getData();
onBatchDeleted();
}
/** the editing row data */
const editingData = ref<Api.SystemManage.User | null>(null);
function handleEdit(id: number) {
operateType.value = 'edit';
editingData.value = data.value.find(item => item.id === id) || null;
openDrawer();
}
async function handleDelete(id: number) {
function handleDelete(id: number) {
// request
console.log(id);
window.$message?.success($t('common.deleteSuccess'));
getData();
onDeleted();
}
function getIndex(index: number) {
const { page = 0, pageSize = 10 } = pagination;
return String((page - 1) * pageSize + index + 1);
}
function getRowKey(row: Api.SystemManage.User) {
return row.id;
function edit(id: number) {
handleEdit(id);
}
</script>
@ -224,7 +163,7 @@ function getRowKey(row: Api.SystemManage.User) {
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="filteredColumns"
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@ -241,7 +180,7 @@ function getRowKey(row: Api.SystemManage.User) {
:scroll-x="962"
:loading="loading"
remote
:row-key="getRowKey"
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>

View File

@ -1,190 +0,0 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { fetchGetUserList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { getNaiveTableIndex, useNaiveTable, useNaiveTableOperate } from '@/hooks/common/naive-table';
import UserOperateDrawer from './modules/user-operate-drawer.vue';
import UserSearch from './modules/user-search.vue';
const appStore = useAppStore();
const { columns, columnChecks, data, getData, loading, pagination, mobilePagination, searchParams, resetSearchParams } =
useNaiveTable({
apiFn: fetchGetUserList,
apiParams: {
current: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
status: null,
userName: null,
userGender: null,
nickName: null,
userPhone: null,
userEmail: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
render: (_, index): number => getNaiveTableIndex(pagination, index),
align: 'center',
width: 64
},
{
key: 'userName',
title: $t('page.manage.user.userName'),
align: 'center',
minWidth: 100
},
{
key: 'userGender',
title: $t('page.manage.user.userGender'),
align: 'center',
width: 100,
render: row => {
if (row.userGender === null) {
return null;
}
const tagMap: Record<Api.SystemManage.UserGender, NaiveUI.ThemeColor> = {
1: 'primary',
2: 'error'
};
const label = $t(userGenderRecord[row.userGender]);
return <NTag type={tagMap[row.userGender]}>{label}</NTag>;
}
},
{
key: 'nickName',
title: $t('page.manage.user.nickName'),
align: 'center',
minWidth: 100
},
{
key: 'userPhone',
title: $t('page.manage.user.userPhone'),
align: 'center',
width: 120
},
{
key: 'userEmail',
title: $t('page.manage.user.userEmail'),
align: 'center',
minWidth: 200
},
{
key: 'status',
title: $t('page.manage.user.userStatus'),
align: 'center',
width: 100,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useNaiveTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
function edit(id: number) {
handleEdit(id);
}
</script>
<template>
<div class="min-h-500px flex-vertical-stretch gap-16px overflow-hidden <sm:overflow-auto">
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<UserOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getData"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@ -9,17 +9,9 @@ defineOptions({
name: 'UserOperateDrawer'
});
/**
* the type of operation
*
* - add: add user
* - edit: edit user
*/
export type OperateType = 'add' | 'edit';
interface Props {
/** the type of operation */
operateType: OperateType;
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.SystemManage.User | null;
}
@ -40,7 +32,7 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<OperateType, string> = {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.manage.user.addUser'),
edit: $t('page.manage.user.editUser')
};