feat: table 封装

This commit is contained in:
RubyLiu 2023-05-26 19:00:11 +08:00 committed by rubylliu
parent 7cc038b157
commit 576d684a85
25 changed files with 526 additions and 16 deletions

View File

@ -5,7 +5,7 @@
<link <link
rel="shortcut icon" rel="shortcut icon"
type="image/x-icon" type="image/x-icon"
href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico" href="/src/assets/favicon.ico"
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MeterSphere</title> <title>MeterSphere</title>

View File

@ -37,7 +37,7 @@
"@7polo/kity": "2.0.8", "@7polo/kity": "2.0.8",
"@7polo/kityminder-core": "1.4.53", "@7polo/kityminder-core": "1.4.53",
"@arco-design/web-vue": "^2.46.0", "@arco-design/web-vue": "^2.46.0",
"@arco-themes/vue-ms-theme-default": "^0.0.2", "@arco-themes/vue-ms-theme-default": "^0.0.7",
"@form-create/arco-design": "^3.1.21", "@form-create/arco-design": "^3.1.21",
"@vueuse/core": "^9.13.0", "@vueuse/core": "^9.13.0",
"ace-builds": "^1.21.1", "ace-builds": "^1.21.1",

View File

@ -0,0 +1,14 @@
import MSR from '@/api/http/index';
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
import { QueryParams } from '@/components/ms-table/useTable';
import { ApiTestListI } from '@/models/api-test';
import CommonReponse from '@/models/common';
export function getTableList(params: QueryParams) {
const { current, pageSize } = params;
return MSR.get<CommonReponse<ApiTestListI>>({ url: `${GetApiTestList}/${current}/${pageSize}` });
}
export function getlist() {
return MSR.get<CommonReponse<ApiTestListI>>({ url: GetApiTestListUrl });
}

View File

@ -0,0 +1,2 @@
export const GetApiTestListUrl = '/mock/list/menu';
export const GetApiTestList = '/mock/api-test/define/list/';

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,15 @@
<template>
<div class="ms-base-tale">
<a-table v-bind="$attrs">
<template v-for="(item, key, i) in slots" :key="i" #[key]="{ record, rowIndex, column }">
<slot :name="key" v-bind="{ rowIndex, record, column }"></slot>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
import { useSlots } from 'vue';
const slots = useSlots();
</script>

View File

@ -0,0 +1,3 @@
export default {
msTable: {},
};

View File

@ -0,0 +1,3 @@
export default {
msTable: {},
};

View File

@ -0,0 +1,49 @@
import { TableColumnData, TableData, TableDraggable } from '@arco-design/web-vue';
export interface MsPaginationI {
pageSize?: number;
total?: number;
current?: number;
}
// 表格属性
export interface MsTabelProps extends MsPaginationI {
// 表格列 - 详见 TableColumn https://arco.design/web-vue/components/table-column;
columns: TableColumnData[];
// 表格数据 - 详见 TableData https://arco.design/web-vue/components/table-data;
data: TableData[];
// 表格尺寸
size?: 'small' | 'default' | 'large';
// 表格是否可滚动
scroll?: {
x?: number | string;
y?: number | string;
};
// 表格是否可拖拽
draggable?: TableDraggable;
// 表格是否可编辑
editable?: boolean;
// 表格是否可筛选
filterable?: boolean;
// 表格是否可排序
sortable?: boolean;
// 表格是否可选中
selectable?: boolean;
// 表格是否可展开
expandable?: boolean;
// 表格是否可固定表头
fixedHeader?: boolean;
// 表格是否可固定列
fixedColumns?: boolean;
// rowKey
rowKey?: string;
// loading
loading?: boolean;
bordered?: boolean;
// pagination
pagination?: MsPaginationI;
[key: string]: any;
}
export type MsTableData = TableData[];
export type MsTableColumn = TableColumnData[];

View File

@ -0,0 +1,133 @@
// 核心的封装方法,详细参数看文档 https://arco.design/vue/component/table
// hook/table-props.ts
import { ref } from 'vue';
import { MsTabelProps, MsTableData, MsTableColumn } from './type';
import CommonReponse from '@/models/common';
import { ApiTestListI } from '@/models/api-test';
export interface Pagination {
current: number;
pageSize: number;
total: number;
showPageSize: boolean;
}
export interface QueryParams {
current: number;
pageSize: number;
[key: string]: any;
}
type GetListFunc = (v: QueryParams) => Promise<CommonReponse<ApiTestListI>>;
export default function useTbleProps(loadListFunc: GetListFunc, props?: MsTabelProps) {
const defaultProps: MsTabelProps = {
'bordered': true,
'size': 'small',
'scroll': { y: 550, x: '100%' },
'expandable': false,
'loading': true,
'data': [] as MsTableData,
'columns': [] as MsTableColumn,
'pagination': {
current: 1,
pageSize: 20,
total: 0,
showPageSize: true,
} as Pagination,
'draggable': { type: 'handle' },
'row-key': 'id',
...props,
};
// 属性组
const propsRes = ref(defaultProps);
// 加载效果
const setLoading = (status: boolean) => {
propsRes.value.loading = status;
};
/**
*
* @param current //当前页数
* @param total //总页数默认是0条可选
* @param fetchData ,
*/
interface SetPaginationPrams {
current: number;
total?: number;
}
const setPagination = ({ current, total }: SetPaginationPrams) => {
if (propsRes.value.pagination) {
propsRes.value.pagination.current = current;
if (total) propsRes.value.pagination.total = total;
}
};
// 单独设置默认属性
const setProps = (params: MsTabelProps) => {
Object.keys(params).forEach((key) => {
defaultProps[key] = params[key];
});
};
// 设置请求参数,如果出了分页参数还有搜索参数,在模板页面调用此方法,可以加入参数
const loadListParams = ref<object>({});
const setLoadListParams = (params?: object) => {
loadListParams.value = params || {};
};
// 加载分页列表数据
const loadList = async () => {
const { current, pageSize } = propsRes.value.pagination as Pagination;
setLoading(true);
const { data } = await loadListFunc({
current,
pageSize,
...loadListParams.value,
});
propsRes.value.data = data.list as unknown as MsTableData;
setPagination({ current: data.current, total: data.total });
setLoading(false);
return data;
};
// 事件触发组
const propsEvent = ref({
// 排序触发
sorterChange: (dataIndex: string, direction: string) => {
// eslint-disable-next-line no-console
console.log(dataIndex, direction);
},
// 分页触发
pageChange: (current: number) => {
setPagination({ current });
loadList();
},
// 修改每页显示条数
pageSizeChange: (pageSize: number) => {
if (propsRes.value.pagination) {
propsRes.value.pagination.pageSize = pageSize;
}
loadList();
},
change: (_data: MsTableData) => {
if (propsRes.value.draggable) {
// eslint-disable-next-line vue/require-explicit-emits
propsRes.value.data = _data;
}
},
});
return {
propsRes,
propsEvent,
setProps,
setLoading,
loadList,
setPagination,
setLoadListParams,
};
}

View File

@ -16,15 +16,6 @@
<Menu v-if="topMenu"></Menu> <Menu v-if="topMenu"></Menu>
</div> </div>
<ul class="right-side"> <ul class="right-side">
<!-- <li>
<a-tooltip :content="$t('settings.search')">
<a-button class="nav-btn" type="outline" :shape="'circle'">
<template #icon>
<icon-search />
</template>
</a-button>
</a-tooltip>
</li> -->
<li> <li>
<a-tooltip :content="$t('settings.language')"> <a-tooltip :content="$t('settings.language')">
<a-button class="nav-btn" type="outline" :shape="'circle'" @click="setDropDownVisible"> <a-button class="nav-btn" type="outline" :shape="'circle'" @click="setDropDownVisible">

View File

@ -6,11 +6,13 @@ import minder from '@/components/minder-editor/locale/en-US';
import localeLogin from '@/views/login/locale/en-US'; import localeLogin from '@/views/login/locale/en-US';
import localeWorkplace from '@/views/dashboard/workplace/locale/en-US'; import localeWorkplace from '@/views/dashboard/workplace/locale/en-US';
import localeThemebox from '@/components/theme-box/locale/en-US'; import localeThemebox from '@/components/theme-box/locale/en-US';
import localeApiTest from '@/views/api-test/locale/en-US';
export default { export default {
message: { message: {
'menu.component': 'component hub', 'menu.component': 'component hub',
'menu.component.demo': 'component demo', 'menu.component.demo': 'component demo',
'menu.apitest': 'Api Test',
'menu.dashboard': 'Dashboard', 'menu.dashboard': 'Dashboard',
'menu.minder': 'Minder', 'menu.minder': 'Minder',
'menu.server.dashboard': 'Dashboard-Server', 'menu.server.dashboard': 'Dashboard-Server',
@ -31,6 +33,7 @@ export default {
...localeWorkplace, ...localeWorkplace,
...minder, ...minder,
...localeThemebox, ...localeThemebox,
...localeApiTest,
}, },
dayjsLocale, dayjsLocale,
dayjsLocaleName: 'en-US', dayjsLocaleName: 'en-US',

View File

@ -6,11 +6,13 @@ import minder from '@/components/minder-editor/locale/zh-CN';
import localeLogin from '@/views/login/locale/zh-CN'; import localeLogin from '@/views/login/locale/zh-CN';
import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN'; import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
import localeThemebox from '@/components/theme-box/locale/zh-CN'; import localeThemebox from '@/components/theme-box/locale/zh-CN';
import localeApiTest from '@/views/api-test/locale/zh-CN';
export default { export default {
message: { message: {
'menu.component': '组件库', 'menu.component': '组件库',
'menu.component.demo': '组件示例', 'menu.component.demo': '组件示例',
'menu.apitest': '接口测试',
'menu.dashboard': '仪表盘', 'menu.dashboard': '仪表盘',
'menu.minder': '脑图', 'menu.minder': '脑图',
'menu.server.dashboard': '仪表盘-服务端', 'menu.server.dashboard': '仪表盘-服务端',
@ -31,6 +33,7 @@ export default {
...localeWorkplace, ...localeWorkplace,
...minder, ...minder,
...localeThemebox, ...localeThemebox,
...localeApiTest,
}, },
dayjsLocale, dayjsLocale,
dayjsLocaleName: 'zh-CN', dayjsLocaleName: 'zh-CN',

View File

@ -0,0 +1,117 @@
import Mock from 'mockjs';
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
const getList = () => {
return {
data: {
list: [
{
id: 'e7bd7179-d63a-43a5-1a65-218473ee69ca',
projectId: '1a0666f0-2cb8-436b-8bfa-7be43497061c',
name: 'ceshi',
method: 'GET',
modulePath: '/未规划接口',
environmentId: '',
schedule: null,
status: 'Underway',
moduleId: '0432d873-c16e-4d15-bef9-76f0cf971db6',
userId: 'admin',
createTime: 1680770595817,
updateTime: 1680770604939,
protocol: 'HTTP',
path: '/s',
num: 100012,
tags: '',
originalState: null,
createUser: 'Administrator',
caseTotal: '1',
caseStatus: 'SUCCESS',
casePassingRate: '100.00%',
deleteTime: null,
deleteUserId: null,
order: null,
refId: 'e7bd7179-d63a-43a5-1a65-218473ee69ca',
versionId: 'cd41a097-2483-409b-83ea-a17256b032b7',
latest: true,
toBeUpdated: null,
toBeUpdateTime: null,
description: null,
request:
'{"type":"HTTPSamplerProxy","clazzName":"io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy","id":"e7bd7179-d63a-43a5-1a65-218473ee69ca","resourceId":null,"name":"ceshi","label":null,"referenced":null,"active":false,"index":null,"enable":true,"refType":null,"hashTree":[{"type":"Assertions","clazzName":"io.metersphere.api.dto.definition.request.assertions.MsAssertions","id":"54fe1204-0213-4aa0-01a7-552170d36190","resourceId":"5ec0cc23-9642-4dc5-aaaa-cb3f41cc0a2e","name":null,"label":null,"referenced":null,"active":false,"index":null,"enable":true,"refType":null,"hashTree":null,"projectId":null,"isMockEnvironment":false,"environmentId":null,"pluginId":null,"stepName":null,"parent":null,"scenarioAss":false,"regex":[],"jsonPath":[],"jsr223":[],"xpath2":[],"duration":{"enable":true,"type":"Duration","value":0,"valid":false},"document":{"enable":true,"type":"JSON","data":{"jsonFollowAPI":"false","xmlFollowAPI":"false","json":[],"xml":[],"assertionName":null}},"mockEnvironment":false}],"projectId":null,"isMockEnvironment":false,"environmentId":null,"pluginId":null,"stepName":null,"parent":null,"protocol":"HTTP","domain":null,"port":null,"method":"GET","path":"/s","connectTimeout":"60000","responseTimeout":"60000","headers":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"file":false,"valid":false}],"body":{"type":"KeyValue","raw":null,"format":null,"kvs":[],"binary":[],"jsonSchema":null,"tmpFilePath":null,"valid":false,"kv":false,"xml":false,"json":false},"rest":[],"url":null,"followRedirects":true,"autoRedirects":false,"doMultipartPost":false,"useEnvironment":null,"arguments":[{"name":null,"value":null,"type":"text","files":null,"description":null,"contentType":"text/plain","enable":true,"urlEncode":false,"required":false,"min":0,"max":0,"file":false,"valid":false}],"authManager":null,"isRefEnvironment":null,"alias":null,"customizeReq":false,"implementation":null,"mockEnvironment":false}',
response:
'{"id":null,"name":null,"enable":null,"type":"HTTP","headers":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"file":false,"valid":false}],"statusCode":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"file":false,"valid":false}],"body":{"type":"KeyValue","raw":null,"format":null,"kvs":[],"binary":[],"jsonSchema":null,"tmpFilePath":null,"valid":false,"kv":false,"xml":false,"json":false}}',
remark: '',
projectName: 'JIra',
userName: 'Administrator',
scenarioTotal: 0,
deleteUser: null,
scenarioIds: null,
caseType: 'apiCase',
apiType: null,
versionName: 'v1.0.0',
versionEnable: true,
updated: false,
fields: null,
},
{
id: '937be890-79bb-1b68-e03e-7d37a8b0a607',
projectId: '1a0666f0-2cb8-436b-8bfa-7be43497061c',
name: 'copy_copy_copy_copy_copy_asfasdf',
method: 'GET',
modulePath: '/未规划接口',
environmentId: '',
schedule: null,
status: 'Underway',
moduleId: '0432d873-c16e-4d15-bef9-76f0cf971db6',
userId: 'admin',
createTime: 1671009286163,
updateTime: 1672816917472,
protocol: 'HTTP',
path: '/safdddsafdfsdafsadfVBBBVV',
num: 100006,
tags: '',
originalState: null,
createUser: 'Administrator',
caseTotal: '0',
caseStatus: '-',
casePassingRate: '-',
deleteTime: null,
deleteUserId: null,
order: null,
refId: '937be890-79bb-1b68-e03e-7d37a8b0a607',
versionId: 'cd41a097-2483-409b-83ea-a17256b032b7',
latest: true,
toBeUpdated: null,
toBeUpdateTime: null,
description: null,
request:
'{"type":"HTTPSamplerProxy","clazzName":"io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy","id":"937be890-79bb-1b68-e03e-7d37a8b0a607","resourceId":null,"name":"copy_copy_copy_copy_copy_asfasdf","label":null,"referenced":null,"active":false,"index":null,"enable":true,"refType":null,"hashTree":[{"type":"Assertions","clazzName":"io.metersphere.api.dto.definition.request.assertions.MsAssertions","id":"956ee64d-ddf0-2cab-b390-896e12a84848","resourceId":"0cf5742b-b699-425b-b966-6e19a09d58c9","name":null,"label":null,"referenced":null,"active":false,"index":null,"enable":true,"refType":null,"hashTree":null,"projectId":null,"isMockEnvironment":false,"environmentId":null,"pluginId":null,"stepName":null,"parent":null,"scenarioAss":false,"regex":[],"jsonPath":[],"jsr223":[],"xpath2":[],"duration":{"enable":true,"type":"Duration","value":0,"valid":false},"document":{"enable":true,"type":"JSON","data":{"jsonFollowAPI":"false","xmlFollowAPI":"false","json":[],"xml":[],"assertionName":null}},"mockEnvironment":false}],"projectId":null,"isMockEnvironment":false,"environmentId":null,"pluginId":null,"stepName":null,"parent":null,"protocol":"HTTP","domain":null,"port":null,"method":"GET","path":"/safdddsafdfsdafsadfVBBBVV","connectTimeout":"60000","responseTimeout":"60000","headers":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"valid":false,"file":false}],"body":{"type":"KeyValue","raw":null,"format":null,"kvs":[],"binary":[],"jsonSchema":null,"tmpFilePath":null,"oldKV":true,"kv":false,"xml":false,"json":false,"valid":false},"rest":[],"url":null,"followRedirects":true,"autoRedirects":false,"doMultipartPost":false,"useEnvironment":null,"arguments":[{"name":"a","value":null,"type":"text","files":null,"description":null,"contentType":"text/plain","enable":true,"urlEncode":false,"required":false,"min":null,"max":null,"valid":true,"file":false},{"name":null,"value":null,"type":"text","files":null,"description":null,"contentType":"text/plain","enable":true,"urlEncode":false,"required":false,"min":null,"max":null,"valid":false,"file":false}],"authManager":null,"isRefEnvironment":null,"alias":null,"customizeReq":false,"implementation":null,"mockEnvironment":false}',
response:
'{"id":null,"name":null,"enable":null,"type":"HTTP","headers":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"valid":false,"file":false}],"statusCode":[{"name":"","value":"","type":null,"files":null,"description":null,"contentType":null,"enable":true,"urlEncode":false,"required":true,"min":null,"max":null,"valid":false,"file":false}],"body":{"type":"KeyValue","raw":null,"format":null,"kvs":[],"binary":[],"jsonSchema":null,"tmpFilePath":null,"oldKV":true,"kv":false,"xml":false,"json":false,"valid":false}}',
remark: '',
projectName: 'JIra',
userName: 'Administrator',
scenarioTotal: 0,
deleteUser: null,
scenarioIds: null,
caseType: 'apiCase',
apiType: null,
versionName: 'v1.0.0',
versionEnable: true,
updated: false,
fields: null,
},
],
total: 2,
current: 1,
},
};
};
setupMock({
setup: () => {
Mock.mock(new RegExp('/mock/api-test/define/list/'), () => {
return successResponseWrap(getList());
});
},
});

View File

@ -2,6 +2,7 @@ import Mock from 'mockjs';
import './user'; import './user';
import './message-box'; import './message-box';
import './api-test';
import '@/views/dashboard/workplace/mock'; import '@/views/dashboard/workplace/mock';

View File

@ -0,0 +1,22 @@
export interface ListItemI {
id: number;
type: string;
receiver: string;
title: string;
status: string;
createTime: number | string;
operator: string;
operation: string;
resourceId: string;
resourceType: string;
resourceName: string;
content: string;
}
export interface ApiTestListI {
[x: string]: any;
pageSize: number;
total: number;
current: number;
list: ListItemI;
}

View File

@ -0,0 +1,5 @@
export default interface CommonReponse<T> {
code: number;
message: string;
data: T;
}

View File

@ -14,7 +14,7 @@ const DASHBOARD: AppRouteRecordRaw = {
{ {
path: 'workplace', path: 'workplace',
name: 'Workplace', name: 'Workplace',
component: () => import('@/views/dashboard/workplace/index.vue'), component: () => import('@/views/api-test/index.vue'),
meta: { meta: {
locale: 'menu.dashboard.workplace', locale: 'menu.dashboard.workplace',
roles: ['*'], roles: ['*'],

View File

View File

@ -0,0 +1,100 @@
<template>
<div class="my-container">
<div class="mb-10">表格</div>
<ms-base-table v-bind="propsRes" v-on="propsEvent">
<template #createTime="{ record }">
{{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</ms-base-table>
</div>
<a-divider />
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import MsBaseTable from '@/components/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/ms-table/type';
import useTable from '@/components/ms-table/useTable';
import { getTableList } from '@/api/modules/api-test/index';
import dayjs from 'dayjs';
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
},
{
title: '接口名称',
dataIndex: 'name',
},
{
title: '请求类型',
dataIndex: 'method',
},
{
title: '责任人',
dataIndex: 'username',
},
{
title: '路径',
dataIndex: 'path',
},
{
title: '标签',
dataIndex: 'tags',
},
{
title: '更新时间',
slotName: 'updataTime',
},
{
title: '用例数',
dataIndex: 'caseTotal',
},
{
title: '用例状态',
dataIndex: 'caseStatus',
},
{
title: '用例通过率',
dataIndex: 'casePassingRate',
},
{
title: '接口状态',
dataIndex: 'status',
},
{
title: '创建时间',
slotName: 'createTime',
},
{
title: '描述',
dataIndex: 'description',
},
{
title: '操作',
slotName: 'action',
fixed: 'right',
width: 200,
},
];
const { propsRes, propsEvent, loadList } = useTable(getTableList, { columns, data: [] });
const fetchData = async () => {
await loadList();
};
onMounted(() => {
fetchData();
});
</script>
<style>
.my-container {
padding: 16px 20px;
padding-bottom: 0;
height: 100vh;
background-color: #ffffff;
}
</style>

View File

@ -0,0 +1,20 @@
export default {
apiTest: {
id: 'ID',
name: 'Api name',
method: 'Requset Method',
path: 'Request Path',
description: 'Api description',
createTime: 'Create time',
updateTime: 'Update time',
operation: 'Operation',
addApiTest: 'Add Api',
editApiTest: 'Edit api',
deleteApiTest: 'Delete api',
deleteApiTestConfirm: 'Are you sure to delete this api?',
deleteApiTestSuccess: 'Delete api success!',
deleteApiTestError: 'Delete api failed, please try again!',
addApiTestSuccess: 'Add api success!',
addApiTestError: 'Add api failed, please try again!',
},
};

View File

@ -0,0 +1,20 @@
export default {
apiTest: {
id: 'ID',
name: '接口名称',
method: '请求方式',
path: '请求路径',
description: '接口描述',
createTime: '创建时间',
updateTime: '更新时间',
operation: '操作',
addApiTest: '新增接口',
editApiTest: '编辑接口',
deleteApiTest: '删除接口',
deleteApiTestConfirm: '确定删除该接口吗?',
deleteApiTestSuccess: '删除接口成功!',
deleteApiTestError: '删除接口失败,请重试!',
addApiTestSuccess: '新增接口成功!',
addApiTestError: '新增接口失败,请重试!',
},
};

View File

@ -24,10 +24,6 @@
</a-radio-group> </a-radio-group>
</div> </div>
<a-divider /> <a-divider />
<div class="mt-10">
<a-button type="primary">按钮</a-button>
<a-switch />
</div>
</div> </div>
</template> </template>

13
frontend/types/table.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
export interface Pagination {
current: number;
pageSize: number;
total: number;
showPageSize: boolean;
}
export interface PaginationRes<T> {
current: number;
pageSize: number;
total: number;
list: T[];
}