feat: 高级列表

This commit is contained in:
2025-09-13 17:56:13 +08:00
parent e42e1c01fb
commit 9d5a289929
18 changed files with 1301 additions and 6739 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@ export async function getInitialState(): Promise<{
if (!token) {
throw new Error("No token found");
}
const { data } = await getInfo();
const data = await getInfo();
wsCache.set(CACHE_KEY.USER, data);
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
@@ -71,13 +71,14 @@ export async function getInitialState(): Promise<{
if (getAccessToken() && !currentUser) {
fetchUserInfo();
}
console.log(111);
return {
fetchUserInfo,
currentUser,
settings: defaultSettings as Partial<LayoutSettings>,
};
}
return {
fetchUserInfo,
settings: defaultSettings as Partial<LayoutSettings>,
@@ -183,7 +184,6 @@ export const request: RequestConfig = {
// 获取存储在本地的 token 和 tenantId
const token = getAccessToken();
const tenantId = getTenantId(); // 默认租户ID为1
console.log(tenantId);
// 设置统一的请求头
const headers: Record<string, string> = {
...options.headers,
@@ -201,6 +201,7 @@ export const request: RequestConfig = {
// umi 4 使用 modifyRoutes
export function patchClientRoutes({ routes }: { routes: any }) {
const { wsCache } = useCache();
console.log(2222);
const globalMenus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
const routerIndex = routes.findIndex((item: any) => item.path === "/");
const parentId = routes[routerIndex].id;

View File

@@ -0,0 +1,294 @@
// components/EnhancedProTable/EnhancedProTable.tsx
import React, { useRef, useState, useCallback, useMemo } from "react";
import {
ProTable,
ProColumns,
ActionType,
TableDropdown,
ParamsType,
} from "@ant-design/pro-components";
import { Button, Space } from "antd";
import {
EnhancedProTableProps,
BaseRecord,
TableAction,
ToolbarAction,
} from "./types";
import {
buildTableDropdownMenuItems,
handleTableDropdownSelect,
formatPaginationTotal,
} from "@/utils/antd/tableHelpers";
function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
props: EnhancedProTableProps<T, U>
) {
const {
columns: originalColumns,
request,
actions = [],
toolbarActions = [],
permissions = [],
checkPermission = () => true,
showIndex = true,
showSelection = true,
showActions = true,
maxActionCount = 2,
onAdd,
onEdit,
onDelete,
onView,
onExport,
customToolbarRender,
customActionRender,
...restProps
} = props;
const actionRef = useRef<ActionType>(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<T[]>([]);
// 权限检查
const hasPermission = useCallback(
(permission?: string) => {
if (!permission) return true;
return checkPermission(permission);
},
[checkPermission]
);
// 过滤有权限的操作
const filteredActions = useMemo(() => {
return actions.filter((action) => hasPermission(action.permission));
}, [actions, hasPermission]);
const filteredToolbarActions = useMemo(() => {
return toolbarActions.filter((action) => hasPermission(action.permission));
}, [toolbarActions, hasPermission]);
// 构建操作列
const buildActionColumn = useCallback((): ProColumns<T> => {
if (!showActions || filteredActions.length === 0) {
return {} as ProColumns<T>;
}
return {
title: "操作",
valueType: "option",
key: "actions",
fixed: "right",
width: Math.min(filteredActions.length * 60 + 50, 200),
render: (_, record, index, action) => {
// 过滤可见的操作
const visibleActions = filteredActions.filter(
(actionItem) => !actionItem.visible || actionItem.visible(record)
);
if (visibleActions.length === 0) return null;
// 显示的操作按钮
const displayActions = visibleActions.slice(0, maxActionCount);
// 更多操作
const moreActions = visibleActions.slice(maxActionCount);
const actionElements: React.ReactNode[] = displayActions.map(
(actionItem) => (
<a
key={actionItem.key}
onClick={() => actionItem.onClick(record, action)}
style={{
color: actionItem.danger ? "#ff4d4f" : undefined,
}}
>
{actionItem.icon} {actionItem.label}
</a>
)
);
// 如果有更多操作,添加下拉菜单
if (moreActions.length > 0) {
const menuItems = buildTableDropdownMenuItems(
moreActions.map((actionItem) => ({
key: actionItem.key,
label: actionItem.label,
icon: actionItem.icon,
danger: actionItem.danger,
disabled: actionItem.disabled?.(record) || false,
}))
);
actionElements.push(
<TableDropdown
key="more"
onSelect={(key) => {
handleTableDropdownSelect(key, moreActions, record, action);
action?.reload();
}}
menus={menuItems}
/>
);
}
// 自定义操作渲染
if (customActionRender) {
return customActionRender(record, actionElements);
}
return actionElements;
},
};
}, [showActions, filteredActions, maxActionCount, customActionRender]);
// 构建列配置
const enhancedColumns = useMemo(() => {
const columns: ProColumns<T>[] = [];
// 添加序号列
if (showIndex) {
columns.push({
title: "序号",
dataIndex: "index",
valueType: "indexBorder",
width: 48,
fixed: "left",
});
}
// 添加原始列
columns.push(...originalColumns);
// 添加操作列
const actionColumn = buildActionColumn();
if (actionColumn.title) {
columns.push(actionColumn);
}
return columns;
}, [originalColumns, showIndex, buildActionColumn]);
// 工具栏渲染
const toolBarRender = useCallback(() => {
const defaultActions = filteredToolbarActions.map((action) => (
<Button
key={action.key}
type={action.type}
danger={action.danger}
icon={action.icon}
disabled={
action.disabled ||
(action.needSelection && selectedRowKeys.length === 0)
}
onClick={() =>
action.onClick(selectedRowKeys as number[], selectedRows)
}
>
{action.label}
</Button>
));
if (customToolbarRender) {
return customToolbarRender(defaultActions);
}
return defaultActions;
}, [
filteredToolbarActions,
selectedRowKeys,
selectedRows,
customToolbarRender,
]);
// 行选择配置
const rowSelection = useMemo(() => {
if (!showSelection) return undefined;
return {
selectedRowKeys,
onChange: (keys: React.Key[], rows: T[]) => {
setSelectedRowKeys(keys);
setSelectedRows(rows);
},
getCheckboxProps: (record: T) => ({
name: record.id?.toString(),
}),
};
}, [showSelection, selectedRowKeys]);
// 表格提醒渲染
const tableAlertRender = useCallback(
({ selectedRowKeys, onCleanSelected }: any) => {
if (!showSelection || selectedRowKeys.length === 0) return false;
return (
<Space size={24}>
<span>
{selectedRowKeys.length}
<a style={{ marginLeft: 8 }} onClick={onCleanSelected}>
</a>
</span>
</Space>
);
},
[showSelection]
);
// 表格提醒操作渲染
const tableAlertOptionRender = useCallback(() => {
if (!showSelection || selectedRowKeys.length === 0) return false;
const batchActions = filteredToolbarActions.filter(
(action) => action.needSelection
);
if (batchActions.length === 0) return false;
return (
<Space size={16}>
{batchActions.map((action) => (
<a
key={action.key}
onClick={() =>
action.onClick(selectedRowKeys as number[], selectedRows)
}
>
{action.label}
</a>
))}
</Space>
);
}, [showSelection, selectedRowKeys, selectedRows, filteredToolbarActions]);
return (
<ProTable<T, U>
{...restProps}
columns={enhancedColumns}
actionRef={actionRef}
request={request}
rowKey="id"
rowSelection={rowSelection}
toolBarRender={toolBarRender}
tableAlertRender={tableAlertRender}
tableAlertOptionRender={tableAlertOptionRender}
search={{
labelWidth: "auto",
...restProps.search,
}}
options={{
fullScreen: true,
reload: true,
setting: true,
density: true,
...restProps.options,
}}
pagination={{
showSizeChanger: true,
showQuickJumper: true,
showTotal: formatPaginationTotal,
...restProps.pagination,
}}
/>
);
}
export default EnhancedProTable;

View File

@@ -0,0 +1,113 @@
// components/EnhancedProTable/types.ts
import {
ProTableProps,
ProColumns,
ActionType,
} from "@ant-design/pro-components";
import { ButtonProps } from "antd";
import React from "react";
export interface BaseRecord {
id: number;
[key: string]: any;
}
export interface ApiResponse<T> {
data: T[];
total: number;
success: boolean;
message?: string;
}
export interface TableDropdownMenuItem {
key: string;
name: string;
label?: React.ReactNode;
icon?: React.ReactNode;
danger?: boolean;
disabled?: boolean;
}
export interface TableAction<T = any> {
key: string;
label: string;
icon?: React.ReactNode;
type?: ButtonProps["type"];
danger?: boolean;
disabled?: (record: T) => boolean;
visible?: (record: T) => boolean;
onClick: (record: T, action?: ActionType) => void;
permission?: string;
}
export interface ToolbarAction {
key: string;
label: string;
icon?: React.ReactNode;
type?: ButtonProps["type"];
danger?: boolean;
disabled?: boolean;
onClick: (selectedKeys: React.Key[], selectedRows: any[]) => void;
permission?: string;
needSelection?: boolean;
}
export interface EnhancedProTableProps<T extends BaseRecord, U = any>
extends Omit<ProTableProps<T, U>, "columns" | "request" | "toolBarRender"> {
columns: ProColumns<T>[];
request?: (
params: U & { current?: number; pageSize?: number }
) => Promise<ApiResponse<T>>;
actions?: TableAction<T>[];
toolbarActions?: ToolbarAction[];
permissions?: string[];
checkPermission?: (permission: string) => boolean;
showIndex?: boolean;
showSelection?: boolean;
showActions?: boolean;
maxActionCount?: number;
onAdd?: () => void;
onEdit?: (record: T) => void;
onDelete?: (record: T) => void;
onView?: (record: T) => void;
onExport?: (selectedRows: T[]) => void;
customToolbarRender?: (
defaultActions: React.ReactNode[]
) => React.ReactNode[];
customActionRender?: (
record: T,
defaultActions: React.ReactNode[]
) => React.ReactNode[];
}
export interface UseEnhancedTableOptions<T> {
onAdd?: () => void;
onEdit?: (record: T) => void;
onDelete?: (record: T) => Promise<boolean>;
onView?: (record: T) => void;
onExport?: (selectedRows: T[]) => void;
onBatchDelete?: (selectedKeys: React.Key[]) => Promise<boolean>;
}
export interface UseEnhancedTableReturn<T> {
actionRef: React.MutableRefObject<ActionType | undefined>;
selectedRowKeys: React.Key[];
selectedRows: T[];
loading: boolean;
setSelectedRowKeys: (keys: React.Key[]) => void;
setSelectedRows: (rows: T[]) => void;
reload: () => void;
reset: () => void;
clearSelection: () => void;
handleDelete: (record: T) => Promise<void>;
handleBatchDelete: (keys: React.Key[]) => Promise<void>;
handleExport: (rows: T[]) => void;
actions: {
add?: () => void;
edit?: (record: T) => void;
delete: (record: T) => Promise<void>;
view?: (record: T) => void;
export: (rows: T[]) => void;
batchDelete: (keys: React.Key[]) => Promise<void>;
};
}

View File

@@ -1,31 +1,31 @@
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components';
import React from 'react';
import { GithubOutlined } from "@ant-design/icons";
import { DefaultFooter } from "@ant-design/pro-components";
import React from "react";
const Footer: React.FC = () => {
return (
<DefaultFooter
style={{
background: 'none',
background: "none",
}}
copyright="Powered by Ant Desgin"
copyright="20250923"
links={[
{
key: 'Ant Design Pro',
title: 'Ant Design Pro',
href: 'https://pro.ant.design',
key: "by",
title: "百业到家",
href: "/welcome",
blankTarget: true,
},
{
key: 'github',
key: "github",
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
href: "https://github.com/ant-design/ant-design-pro",
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
key: "Ant Design",
title: "Ant Design",
href: "https://ant.design",
blankTarget: true,
},
]}

34
src/constants/icons.tsx Normal file
View File

@@ -0,0 +1,34 @@
// constants/icons.tsx
import {
EditOutlined,
DeleteOutlined,
EyeOutlined,
PlusOutlined,
ExportOutlined,
DownloadOutlined,
UploadOutlined,
SearchOutlined,
ReloadOutlined,
SettingOutlined,
MoreOutlined,
CopyOutlined,
UserOutlined,
} from "@ant-design/icons";
export const ICONS = {
edit: <EditOutlined />,
delete: <DeleteOutlined />,
view: <EyeOutlined />,
add: <PlusOutlined />,
export: <ExportOutlined />,
download: <DownloadOutlined />,
upload: <UploadOutlined />,
search: <SearchOutlined />,
reload: <ReloadOutlined />,
setting: <SettingOutlined />,
more: <MoreOutlined />,
copy: <CopyOutlined />,
user: <UserOutlined />,
} as const;
export type IconType = keyof typeof ICONS;

View File

@@ -0,0 +1,116 @@
// hooks/useEnhancedTable.ts
import { useRef, useState, useCallback } from "react";
import { ActionType } from "@ant-design/pro-components";
import { message } from "antd";
import {
UseEnhancedTableOptions,
UseEnhancedTableReturn,
} from "@/components/EnhancedProTable/types";
export function useEnhancedTable<T>(
options: UseEnhancedTableOptions<T> = {}
): UseEnhancedTableReturn<T> {
const actionRef = useRef<ActionType>(undefined);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const { onAdd, onEdit, onDelete, onView, onExport, onBatchDelete } = options;
// 刷新表格
const reload = useCallback(() => {
actionRef.current?.reload();
}, []);
// 重置表格
const reset = useCallback(() => {
actionRef.current?.reset?.();
setSelectedRowKeys([]);
setSelectedRows([]);
}, []);
// 清空选择
const clearSelection = useCallback(() => {
setSelectedRowKeys([]);
setSelectedRows([]);
}, []);
// 处理删除
const handleDelete = useCallback(
async (record: T) => {
if (!onDelete) return;
setLoading(true);
try {
const success = await onDelete(record);
if (success) {
message.success("删除成功");
reload();
}
} finally {
setLoading(false);
}
},
[onDelete, reload]
);
// 处理批量删除
const handleBatchDelete = useCallback(
async (keys: React.Key[]) => {
if (!onBatchDelete || keys.length === 0) return;
setLoading(true);
try {
const success = await onBatchDelete(keys);
if (success) {
message.success("批量删除成功");
clearSelection();
reload();
}
} catch (error) {
message.error("批量删除失败");
} finally {
setLoading(false);
}
},
[onBatchDelete, clearSelection, reload]
);
// 处理导出
const handleExport = useCallback(
(rows: T[]) => {
if (!onExport) return;
if (rows.length === 0) {
message.warning("请选择要导出的数据");
return;
}
onExport(rows);
},
[onExport]
);
return {
actionRef,
selectedRowKeys,
selectedRows,
loading,
setSelectedRowKeys,
setSelectedRows,
reload,
reset,
clearSelection,
handleDelete,
handleBatchDelete,
handleExport,
actions: {
add: onAdd,
edit: onEdit,
delete: handleDelete,
view: onView,
export: handleExport,
batchDelete: handleBatchDelete,
},
};
}
export type { UseEnhancedTableOptions, UseEnhancedTableReturn };

View File

@@ -1,5 +1,109 @@
import EnhancedProTable from "@/components/EnhancedProTable";
import {
getTenantPage,
deleteTenant,
type TenantPageReqVO,
type TenantVO,
} from "@/services/system/tenant/list";
import { PlusOutlined } from "@ant-design/icons";
import {
ProCoreActionType,
ProTable,
TableDropdown,
type ProColumns,
} from "@ant-design/pro-components";
import { Button, Space } from "antd";
import {
createOrderTableConfig,
createCommonActions,
createCommonToolbarActions,
} from "@/utils/antd/tableConfigFactory";
import { useEnhancedTable } from "@/hooks/antd/useEnhancedTable";
const TenantList = () => {
return <div>TenantList</div>;
const columns: ProColumns<TenantVO>[] = [
{
title: "租户编号",
dataIndex: "id",
tip: "租户编号",
},
{
title: "租户名",
dataIndex: "name",
ellipsis: true, // 超长省略
tip: "租户名", // 提示信息
},
{
title: "状态",
dataIndex: "status",
valueType: "select",
valueEnum: {
all: { text: "全部", status: "Default" },
open: { text: "未解决", status: "Error" },
closed: { text: "已解决", status: "Success" },
},
},
{
title: "创建时间",
dataIndex: "created_at",
valueType: "dateTime",
hideInSearch: true, // 在搜索表单中隐藏
},
];
const onFetch = async (
params: TenantPageReqVO & {
pageSize?: number;
current?: number;
}
) => {
const data = await getTenantPage({
pageNo: params.current,
pageSize: params.pageSize,
});
console.log(data);
return {
data: data.list,
success: true,
total: data.total,
};
};
const { actionRef, selectedRowKeys, selectedRows, actions } =
useEnhancedTable<TenantVO>({
onEdit: (record) => {
console.log("编辑订单:", record);
},
onDelete: async (record) => {
await deleteTenant(record.id);
return true;
},
});
const tableActions = createCommonActions<TenantVO>({
onView: actions.view,
onEdit: actions.edit,
onDelete: actions.delete,
});
const toolbarActions = createCommonToolbarActions({
onExport: actions.export,
});
return (
<div>
<EnhancedProTable<TenantVO>
columns={columns}
request={onFetch}
actions={tableActions}
toolbarActions={toolbarActions}
headerTitle="订单管理"
showIndex={false}
// showSelection
showActions
maxActionCount={3}
/>
</div>
);
};
export default TenantList;

View File

@@ -37,8 +37,8 @@ const Page = () => {
const navigate = useNavigate();
// 获取租户 ID
const getTenantId = async (name: string) => {
const res = await getTenantIdByName({ name });
authUtil.setTenantId(res.data);
const data = await getTenantIdByName({ name });
authUtil.setTenantId(data);
};
const fetchUserInfo = async () => {
@@ -69,19 +69,14 @@ const Page = () => {
}
// 调用登录接口
const res = await login(params);
if (res.code === 0) {
// 登录成功
messageApi.success("登录成功!");
// 跳转到首页
authUtil.setToken(res.data);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
navigate(urlParams.get("redirect") || "/");
} else {
// 登录失败
messageApi.error(res.msg || "登录失败,请重试!");
}
const data = await login(params);
// 登录成功
messageApi.success("登录成功!");
// 跳转到首页
authUtil.setToken(data);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
navigate(urlParams.get("redirect") || "/");
} catch (error) {
messageApi.error("登录失败,请检查网络或稍后重试!");
}

View File

@@ -2,6 +2,13 @@
import type { RequestConfig } from "@umijs/max";
import { message, notification } from "antd";
import { deleteUserCache } from "@/hooks/web/useCache";
import {
getAccessToken,
getRefreshToken,
getTenantId,
setToken,
} from "./utils/auth";
import { request } from "@umijs/max";
const tenantEnable = process.env.VITE_APP_TENANT_ENABLE;
// const { result_code, base_url, request_timeout } = config;
// 错误处理方案: 错误类型
@@ -16,9 +23,8 @@ enum ErrorShowType {
interface ResponseStructure {
success: boolean;
data: any;
errorCode?: number;
errorMessage?: string;
showType?: ErrorShowType;
code?: number;
msg?: string;
}
/**
@@ -26,63 +32,76 @@ interface ResponseStructure {
* pro 自带的错误处理, 可以在这里做自己的改动
* @doc https://umijs.org/docs/max/request#配置
*/
const refreshToken = async () => {
return await request("/system/auth/refresh-token", {
method: "POST",
params: { refreshToken: getRefreshToken() },
});
};
const handleAuthorized = () => {
// const { t } = useI18n()
// if (!isRelogin.show) {
// // 如果已经到登录页面则不进行弹窗提示
// if (window.location.href.includes('login')) {
// return
// }
// isRelogin.show = true
// ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
// showCancelButton: false,
// closeOnClickModal: false,
// showClose: false,
// closeOnPressEscape: false,
// confirmButtonText: t('login.relogin'),
// type: 'warning'
// }).then(() => {
// resetRouter() // 重置静态路由表
// deleteUserCache() // 删除用户缓存
// removeToken()
// isRelogin.show = false
// // 干掉token后再走一次路由让它过router.beforeEach的校验
// window.location.href = window.location.href
// })
// }
// return Promise.reject(t('sys.api.timeoutMessage'))
};
// 是否正在刷新中
let isRefreshToken = false;
// 请求队列
let requestList: any[] = [];
export const errorConfig: RequestConfig = {
// 错误处理: umi@3 的错误处理方案。
errorConfig: {
// 错误抛出
errorThrower: (res) => {
const { success, data, errorCode, errorMessage, showType } =
res as unknown as ResponseStructure;
const { success, data, code, msg } = res as unknown as ResponseStructure;
if (!success) {
const error: any = new Error(errorMessage);
const error: any = new Error(msg);
error.name = "BizError";
error.info = { errorCode, errorMessage, showType, data };
error.info = { code, msg, data };
throw error; // 抛出自制的错误
}
},
// 错误接收及处理
errorHandler: (error: any, opts: any) => {
errorHandler: async (error: any, opts: any) => {
if (opts?.skipErrorHandler) throw error;
// 我们的 errorThrower 抛出的错误。
console.log("errorHandler", error);
const errorInfo: ResponseStructure | undefined = error.info;
if (error.name === "BizError") {
const errorInfo: ResponseStructure | undefined = error.info;
if (errorInfo) {
const { errorMessage, errorCode } = errorInfo;
switch (errorInfo.showType) {
case ErrorShowType.SILENT:
// do nothing
break;
case ErrorShowType.WARN_MESSAGE:
message.warning(errorMessage);
break;
case ErrorShowType.ERROR_MESSAGE:
message.error(errorMessage);
break;
case ErrorShowType.NOTIFICATION:
notification.open({
description: errorMessage,
message: errorCode,
});
break;
case ErrorShowType.REDIRECT:
// TODO: redirect
break;
default:
message.error(errorMessage);
}
const { msg, code } = errorInfo;
message.error(msg);
}
} else if (error.response) {
// Axios 的错误
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
message.error(`Response status:${error.response.status}`);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
message.error("None response! Please retry.");
} else {
// 发送请求时出了点问题
message.error("Request error, please retry.");
message.error("发送请求时出了点问题:" + error.msg);
}
},
},
@@ -98,14 +117,61 @@ export const errorConfig: RequestConfig = {
// 响应拦截器
responseInterceptors: [
(response) => {
// 拦截响应数据,进行个性化处理
async (response) => {
const { data } = response as unknown as ResponseStructure;
const config = response.config;
const { code } = data;
// 发送请求时出了点问题
if (code === 401) {
if (!isRefreshToken) {
isRefreshToken = true;
// 1. 如果获取不到刷新令牌,则只能执行登出操作
if (!getRefreshToken()) {
return handleAuthorized();
}
// 2. 进行刷新访问令牌
try {
const refreshTokenRes = await refreshToken();
console.log("刷新成功", refreshTokenRes);
// 2.1 刷新成功,则回放队列的请求 + 当前请求
setToken(refreshTokenRes);
console.log(getAccessToken());
config.headers!.Authorization = "Bearer " + getAccessToken();
requestList.forEach((cb: any) => {
cb();
});
} catch (e) {
// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
// 2.2 刷新失败,只回放队列的请求
requestList.forEach((cb: any) => {
cb();
});
// 提示是否要登出。即不回放当前请求!不然会形成递归
return handleAuthorized();
} finally {
requestList = [];
isRefreshToken = false;
}
return;
} else {
//添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {
config.headers!.Authorization = "Bearer " + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(request(config.url!, config));
});
});
}
}
if (data.code !== 0) {
return Promise.reject(data);
}
if (data?.success === false) {
message.error("请求失败!");
return Promise.reject(data);
}
return response;
return data;
},
],
};

View File

@@ -19,7 +19,7 @@ export async function login(
body: API.UserLoginVO,
options?: { [key: string]: any }
) {
return request<IResponse<API.TokenType>>("/system/auth/login", {
return request<API.TokenType>("/system/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -84,16 +84,13 @@ export async function loginOut() {
// return request.get({ url: "/system/auth/get-permission-info" });
// };
export async function getInfo(options?: { [key: string]: any }) {
return request<IResponse<API.UserInfoVO>>(
"/system/auth/get-permission-info",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
...(options || {}),
}
);
return request<API.UserInfoVO>("/system/auth/get-permission-info", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
...(options || {}),
});
}
//获取登录验证码
// export const sendSmsCode = (data: SmsCodeVO) => {

View File

@@ -1,5 +1,5 @@
// @ts-ignore
/* eslint-disable */
/// <reference path="./typings.d.ts" />
import { request } from "@umijs/max";
/** 获取菜单页面的表 GET /product/category/categoryList */

View File

@@ -1,5 +1,5 @@
// @ts-ignore
/* eslint-disable */
/// <reference path="./typings.d.ts" />
import { request } from "@umijs/max";
/** 创建商品 创建商品 POST /prod/create */
@@ -88,12 +88,9 @@ export async function getProductProdGetProdRecycleBinPageList(
{
method: "GET",
params: {
// pageNo has a default value: 1
pageNo: "1",
// pageSize has a default value: 10
pageSize: "10",
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
@@ -127,12 +124,9 @@ export async function getProductProdPage(
return request<API.CommonResultPageResultProdListVO>("/product/prod/page", {
method: "GET",
params: {
// pageNo has a default value: 1
pageNo: "1",
// pageSize has a default value: 10
pageSize: "10",
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
});
@@ -299,12 +293,9 @@ export async function getProductSkuGetPropRecycleBinList(
{
method: "GET",
params: {
// pageNo has a default value: 1
pageNo: "1",
// pageSize has a default value: 10
pageSize: "10",
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
@@ -337,12 +328,9 @@ export async function getProductSkuGetSkuPageList(
{
method: "GET",
params: {
// pageNo has a default value: 1
pageNo: "1",
// pageSize has a default value: 10
pageSize: "10",
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
@@ -375,12 +363,9 @@ export async function getProductSkuGetSkuRecycleBinPageList(
{
method: "GET",
params: {
// pageNo has a default value: 1
pageNo: "1",
// pageSize has a default value: 10
pageSize: "10",
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}

View File

@@ -202,6 +202,15 @@ declare namespace API {
msg?: string;
};
type CommonResultPageResultTradeOrderPageItemRespVO = {
/** 错误码 */
code?: number;
/** 返回数据 */
data?: PageResultTradeOrderPageItemRespVO;
/** 错误提示,用户可阅读 */
msg?: string;
};
type CommonResultProdServiceVO = {
/** 错误码 */
code?: number;
@@ -371,6 +380,43 @@ declare namespace API {
skuId?: number;
};
type getTradeOrderGetDetailParams = {
id: number;
};
type getTradeOrderPageParams = {
/** 页码,从 1 开始", example = "1 */
pageNo: number;
/** 每页条数,最大值为 100" */
pageSize: number;
/** 订单号示例88888888 */
orderNum?: string;
/** 用户编号示例1024 */
userId?: number;
/** 用户昵称,示例:小王 */
userNickname?: string;
/** 用户手机号,示例:小王 */
userMobile?: string;
/** 配送方式示例1 */
deliveryType?: number;
/** 发货物流公司编号示例1 */
logisticsId?: number;
/** 自提门店编号,示例:[1,2] */
pickUpStoreIds?: string;
/** 自提核销码示例12345678 */
pickUpVerifyCode?: string;
/** 订单类型示例1 */
type?: number;
/** 订单状态示例1 */
status?: number;
/** 支付渠道示例wx_lite */
payChannelCode?: string;
/** 创建时间 */
createTime?: string[];
/** 订单来源示例10 */
terminal?: number;
};
type Item = {
/** 编号 - 必填示例1 */
id?: number;
@@ -497,6 +543,13 @@ declare namespace API {
total?: number;
};
type PageResultTradeOrderPageItemRespVO = {
/** 数据 */
list?: TradeOrderPageItemRespVO[];
/** 总量 */
total?: number;
};
type postProductProdRestoreProdListParams = {
/** 商品id集合 */
ids: number[];

View File

@@ -13,7 +13,7 @@ export interface PermissionAssignRoleMenuReqVO {
export interface PermissionAssignRoleDataScopeReqVO {
roleId: number;
dataScope: number;
dataScopeDeptIds: number[];
dataScopeDeptIds: React.Key[];
}
// export async function postProductProdRestoreProdList(

View File

@@ -0,0 +1,91 @@
import { request } from "@umijs/max";
export interface TenantVO {
id: number;
name: string;
contactName: string;
contactMobile: string;
status: number;
domain: string;
packageId: number;
username: string;
password: string;
expireTime: Date;
accountCount: number;
createTime: Date;
}
export interface TenantPageReqVO extends PageParam {
name?: string;
contactName?: string;
contactMobile?: string;
status?: number;
createTime?: Date[];
}
export interface TenantExportReqVO {
name?: string;
contactName?: string;
contactMobile?: string;
status?: number;
createTime?: Date[];
}
// 查询租户列表
// export const getTenantPage = (params: TenantPageReqVO) => {
// return request.get({ url: '/system/tenant/page', params })
// }
export async function getTenantPage(params: TenantPageReqVO) {
return request("/system/tenant/page", {
method: "GET",
params,
});
}
// 查询租户详情
// export const getTenant = (id: number) => {
// return request.get({ url: "/system/tenant/get?id=" + id });
// };
export async function getTenant(id: number) {
return request("/system/tenant/get", {
method: "GET",
params: { id },
});
}
// 新增租户
// export const createTenant = (data: TenantVO) => {
// return request.post({ url: "/system/tenant/create", data });
// };
export async function createTenant(data: TenantVO) {
return request("/system/tenant/create", {
method: "POST",
data,
});
}
// 修改租户
// export const updateTenant = (data: TenantVO) => {
// return request.put({ url: "/system/tenant/update", data });
// };
export async function updateTenant(data: TenantVO) {
return request("/system/tenant/update", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data,
});
}
// 删除租户
// export const deleteTenant = (id: number) => {
// return request.delete({ url: "/system/tenant/delete?id=" + id });
// };
export async function deleteTenant(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
id: number
) {
return request("/system/tenant/delete", {
method: "DELETE",
params: {
id,
},
});
}

View File

@@ -0,0 +1,282 @@
// utils/tableConfigFactory.tsx
import React from "react";
import { ProColumns } from "@ant-design/pro-components";
import { Tag } from "antd";
import { ICONS, IconType } from "@/constants/icons";
import {
TableAction,
ToolbarAction,
} from "@/components/EnhancedProTable/types";
/**
* 创建操作配置的辅助函数
*/
const createAction = <T extends { id: number }>(
key: string,
label: string,
iconType: IconType,
onClick: (record: T) => void,
options: Partial<TableAction<T>> = {}
): TableAction<T> => ({
key,
label,
// icon: ICONS[iconType],
onClick,
...options,
});
/**
* 创建工具栏操作的辅助函数
*/
const createToolbarAction = (
key: string,
label: string,
iconType: IconType,
onClick: (selectedKeys: React.Key[], selectedRows: any[]) => void,
options: Partial<ToolbarAction> = {}
): ToolbarAction => ({
key,
label,
// icon: ICONS[iconType],
onClick,
...options,
});
/**
* 通用列配置工厂
*/
export const createCommonColumns = <T extends Record<string, any>>(): Partial<
ProColumns<T>
>[] => [
{
title: "创建时间",
dataIndex: "created_at",
valueType: "dateTime",
sorter: true,
hideInSearch: true,
width: 180,
},
{
title: "更新时间",
dataIndex: "updated_at",
valueType: "dateTime",
hideInSearch: true,
hideInTable: true,
width: 180,
},
{
title: "状态",
dataIndex: "status",
valueType: "select",
valueEnum: {
active: { text: "启用", status: "Success" },
inactive: { text: "禁用", status: "Error" },
pending: { text: "待审核", status: "Processing" },
},
},
];
/**
* 通用操作配置工厂
*/
export const createCommonActions = <T extends { id: number }>(handlers: {
onEdit?: (record: T) => void;
onDelete?: (record: T) => void;
onView?: (record: T) => void;
onCopy?: (record: T) => void;
}): TableAction<T>[] => {
const actions: TableAction<T>[] = [];
if (handlers.onView) {
actions.push(createAction("view", "查看", "view", handlers.onView));
}
if (handlers.onEdit) {
actions.push(
createAction("edit", "编辑", "edit", handlers.onEdit, {
permission: "edit",
})
);
}
if (handlers.onCopy) {
actions.push(
createAction("copy", "复制", "copy", handlers.onCopy, {
permission: "copy",
})
);
}
if (handlers.onDelete) {
actions.push(
createAction("delete", "删除", "delete", handlers.onDelete, {
danger: true,
permission: "delete",
})
);
}
return actions;
};
/**
* 通用工具栏操作工厂
*/
export const createCommonToolbarActions = (handlers: {
onAdd?: () => void;
onExport?: (selectedRows: any[]) => void;
onBatchDelete?: (selectedKeys: React.Key[]) => void;
onImport?: () => void;
}): ToolbarAction[] => {
const actions: ToolbarAction[] = [];
if (handlers.onAdd) {
actions.push(
createToolbarAction("add", "新建", "add", handlers.onAdd, {
type: "primary",
permission: "add",
})
);
}
if (handlers.onImport) {
actions.push(
createToolbarAction("import", "导入", "upload", handlers.onImport, {
permission: "import",
})
);
}
if (handlers.onExport) {
actions.push(
createToolbarAction(
"export",
"导出",
"export",
(_, selectedRows) => handlers.onExport!(selectedRows),
{
needSelection: true,
}
)
);
}
if (handlers.onBatchDelete) {
actions.push(
createToolbarAction(
"batchDelete",
"批量删除",
"delete",
(selectedKeys) => handlers.onBatchDelete!(selectedKeys),
{
danger: true,
needSelection: true,
permission: "delete",
}
)
);
}
return actions;
};
/**
* 用户表格配置
*/
export const createUserTableConfig = () => {
const columns: ProColumns<any>[] = [
{
title: "头像",
dataIndex: "avatar",
valueType: "avatar",
hideInSearch: true,
width: 64,
},
{
title: "用户名",
dataIndex: "username",
copyable: true,
ellipsis: true,
formItemProps: {
rules: [
{ required: true, message: "请输入用户名" },
{ min: 2, max: 20, message: "用户名长度在 2 到 20 个字符" },
],
},
},
{
title: "邮箱",
dataIndex: "email",
copyable: true,
formItemProps: {
rules: [
{ required: true, message: "请输入邮箱" },
{ type: "email", message: "请输入正确的邮箱格式" },
],
},
},
{
title: "手机号",
dataIndex: "phone",
copyable: true,
hideInTable: true,
},
{
title: "角色",
dataIndex: "roles",
hideInSearch: true,
render: (_, record) => (
<>
{record.roles?.map((role: string) => (
<Tag key={role} color="blue">
{role}
</Tag>
))}
</>
),
},
...createCommonColumns(),
];
return { columns };
};
/**
* 订单表格配置
*/
export const createOrderTableConfig = () => {
const columns: ProColumns<any>[] = [
{
title: "订单号",
dataIndex: "order_no",
copyable: true,
ellipsis: true,
},
{
title: "客户名称",
dataIndex: "customer_name",
ellipsis: true,
},
{
title: "订单金额",
dataIndex: "amount",
valueType: "money",
sorter: true,
},
{
title: "订单状态",
dataIndex: "status",
valueType: "select",
valueEnum: {
pending: { text: "待付款", status: "Warning" },
paid: { text: "已付款", status: "Processing" },
shipped: { text: "已发货", status: "Success" },
completed: { text: "已完成", status: "Success" },
cancelled: { text: "已取消", status: "Error" },
},
},
...createCommonColumns(),
];
return { columns };
};

View File

@@ -0,0 +1,58 @@
// utils/tableHelpers.ts
import { TableDropdownMenuItem } from "@/components/EnhancedProTable/types";
/**
* 构建 TableDropdown 菜单项
*/
export const buildTableDropdownMenuItems = (
actions: Array<{
key: string;
label: string;
icon?: React.ReactNode;
danger?: boolean;
disabled?: boolean;
}>
): TableDropdownMenuItem[] => {
return actions.map((action) => ({
key: action.key,
name: action.label,
icon: action.icon,
danger: action.danger,
disabled: action.disabled,
}));
};
/**
* 处理 TableDropdown 选择事件
*/
export const handleTableDropdownSelect = <T>(
key: string | number,
actions: Array<{
key: string;
onClick: (record: T, action?: any) => void;
}>,
record: T,
action?: any
) => {
const selectedAction = actions.find((item) => item.key === key);
if (selectedAction) {
selectedAction.onClick(record, action);
}
};
/**
* 格式化分页显示文本
*/
export const formatPaginationTotal = (
total: number,
range: [number, number]
) => {
return `${range[0]}-${range[1]} 条/总共 ${total}`;
};
/**
* 生成唯一的表格键
*/
export const generateTableKey = (prefix: string) => {
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
};