feat: 高级列表
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
294
src/components/EnhancedProTable/index.tsx
Normal file
294
src/components/EnhancedProTable/index.tsx
Normal 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;
|
||||
113
src/components/EnhancedProTable/types.ts
Normal file
113
src/components/EnhancedProTable/types.ts
Normal 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>;
|
||||
};
|
||||
}
|
||||
@@ -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
34
src/constants/icons.tsx
Normal 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;
|
||||
116
src/hooks/antd/useEnhancedTable.ts
Normal file
116
src/hooks/antd/useEnhancedTable.ts
Normal 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 };
|
||||
@@ -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;
|
||||
|
||||
@@ -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("登录失败,请检查网络或稍后重试!");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
/// <reference path="./typings.d.ts" />
|
||||
import { request } from "@umijs/max";
|
||||
|
||||
/** 获取菜单页面的表 GET /product/category/categoryList */
|
||||
|
||||
@@ -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 || {}),
|
||||
}
|
||||
|
||||
53
src/services/prodApi/typings.d.ts
vendored
53
src/services/prodApi/typings.d.ts
vendored
@@ -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[];
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface PermissionAssignRoleMenuReqVO {
|
||||
export interface PermissionAssignRoleDataScopeReqVO {
|
||||
roleId: number;
|
||||
dataScope: number;
|
||||
dataScopeDeptIds: number[];
|
||||
dataScopeDeptIds: React.Key[];
|
||||
}
|
||||
|
||||
// export async function postProductProdRestoreProdList(
|
||||
|
||||
91
src/services/system/tenant/list.ts
Normal file
91
src/services/system/tenant/list.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
282
src/utils/antd/tableConfigFactory.tsx
Normal file
282
src/utils/antd/tableConfigFactory.tsx
Normal 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 };
|
||||
};
|
||||
58
src/utils/antd/tableHelpers.ts
Normal file
58
src/utils/antd/tableHelpers.ts
Normal 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)}`;
|
||||
};
|
||||
Reference in New Issue
Block a user