feat: menu

This commit is contained in:
2025-09-17 10:41:00 +08:00
parent 9d5a289929
commit aada97ed22
27 changed files with 973 additions and 527 deletions

View File

@@ -7,6 +7,7 @@ permissions:
jobs: jobs:
build: build:
if: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:

View File

@@ -152,13 +152,12 @@ export default defineConfig({
* @doc https://pro.ant.design/zh-cn/docs/openapi/ * @doc https://pro.ant.design/zh-cn/docs/openapi/
*/ */
openAPI: [ openAPI: [
{ // {
requestLibPath: "import { request } from '@umijs/max'", // requestLibPath: "import { request } from '@umijs/max'",
// schemaPath: join(__dirname, "oneapi/prodapi.json"),
schemaPath: join(__dirname, "oneapi/prodapi.json"), // mock: false,
mock: false, // projectName: "prodApi",
projectName: "prodApi", // },
},
// {schemaPath: "./docs/apifox-api.json", // {schemaPath: "./docs/apifox-api.json",
// requestLibPath: "import { request } from '@umijs/max'", // requestLibPath: "import { request } from '@umijs/max'",
// schemaPath: join(__dirname, "oneapi.json"), // schemaPath: join(__dirname, "oneapi.json"),

View File

@@ -1,5 +1,11 @@
import React, { Children, Component, JSX, Suspense } from "react"; import React, {
import { Spin } from "antd"; Children,
Component,
createContext,
JSX,
Suspense,
} from "react";
import { Modal, Spin } from "antd";
import type { Settings as LayoutSettings } from "@ant-design/pro-components"; import type { Settings as LayoutSettings } from "@ant-design/pro-components";
import { SettingDrawer } from "@ant-design/pro-components"; import { SettingDrawer } from "@ant-design/pro-components";
import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max"; import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max";
@@ -197,6 +203,34 @@ export const request: RequestConfig = {
return { url, options: { ...options, headers } }; return { url, options: { ...options, headers } };
}, },
], ],
// 添加参数序列化配置
paramsSerializer: (params) => {
const searchParams = new URLSearchParams();
const appendParams = (key: string, value: any) => {
if (Array.isArray(value)) {
// 特殊处理 createTime 数组,转换为 createTime[0] 和 createTime[1] 格式
if (key === "createTime") {
value.forEach((val, index) => {
searchParams.append(`${key}[${index}]`, val);
});
} else {
// 其他数组参数保持默认行为
value.forEach((val) => {
searchParams.append(`${key}[]`, val);
});
}
} else if (value !== null && value !== undefined) {
searchParams.append(key, String(value));
}
};
Object.keys(params).forEach((key) => {
appendParams(key, params[key]);
});
return searchParams.toString();
},
}; };
// umi 4 使用 modifyRoutes // umi 4 使用 modifyRoutes
export function patchClientRoutes({ routes }: { routes: any }) { export function patchClientRoutes({ routes }: { routes: any }) {

View File

@@ -0,0 +1,122 @@
import React, { useImperativeHandle, forwardRef } from "react";
import { DrawerForm } from "@ant-design/pro-components";
import type { ProFormColumnsType } from "@ant-design/pro-components";
import { BetaSchemaForm } from "@ant-design/pro-components";
import { Button, Drawer, Space, Typography } from "antd";
import { CloseOutlined } from "@ant-design/icons";
const { Title } = Typography;
interface ConfigurableDrawerFormProps {
title?: string;
columns: ProFormColumnsType[];
onSubmit?: (values: any) => Promise<boolean>;
initialValues?: Record<string, any>;
width?: number;
}
export interface ConfigurableDrawerFormRef {
open: (data?: Record<string, any>) => void;
close: () => void;
}
const ConfigurableDrawerForm = forwardRef<
ConfigurableDrawerFormRef,
ConfigurableDrawerFormProps
>(({ title = "表单", columns, onSubmit, initialValues, width = 600 }, ref) => {
const [open, setOpen] = React.useState(false);
const [formData, setFormData] = React.useState(initialValues || {});
const [loading, setLoading] = React.useState<boolean>(false);
// 添加表单实例引用
const formRef = React.useRef<any>(null);
useImperativeHandle(ref, () => ({
open: (data) => {
if (data) {
setFormData(data);
}
console.log("open");
setOpen(true);
},
close: () => setOpen(false),
}));
const handleSubmit = async () => {
try {
if (onSubmit) {
await formRef.current?.validateFields();
setLoading(true);
const values = formRef.current?.getFieldsValue();
const success = await onSubmit(values);
if (success) {
setOpen(false);
return true;
}
return false;
}
return true;
} finally {
setLoading(false);
}
};
const customHeader = (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0 0px 16px 0px",
borderBottom: "1px solid #f0f0f0",
marginBottom: "16px",
}}
>
<Title level={4} style={{ margin: 0 }}>
{title}
</Title>
<Button
type="text"
icon={<CloseOutlined />}
onClick={() => setOpen(false)}
style={{
border: "none",
boxShadow: "none",
}}
/>
</div>
);
return (
<Drawer
title={title}
styles={{
header: {
textAlign: "left",
position: "relative",
},
}}
destroyOnHidden
closable={true} // 隐藏默认关闭按钮
open={open}
onClose={() => setOpen(false)}
width={width}
footer={
<Space style={{ width: "100%", justifyContent: "end" }}>
<Button onClick={() => setOpen(false)}></Button>
<Button loading={loading} type="primary" onClick={handleSubmit}>
</Button>
</Space>
}
>
{/* {customHeader} */}
<BetaSchemaForm
initialValues={formData}
layoutType="Form"
formRef={formRef}
columns={columns}
layout="horizontal"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
submitter={false}
/>
</Drawer>
);
});
export default ConfigurableDrawerForm;

View File

@@ -1,5 +1,12 @@
// components/EnhancedProTable/EnhancedProTable.tsx // components/EnhancedProTable/EnhancedProTable.tsx
import React, { useRef, useState, useCallback, useMemo } from "react"; import React, {
useRef,
useState,
useCallback,
useMemo,
act,
forwardRef,
} from "react";
import { import {
ProTable, ProTable,
ProColumns, ProColumns,
@@ -19,17 +26,19 @@ import {
handleTableDropdownSelect, handleTableDropdownSelect,
formatPaginationTotal, formatPaginationTotal,
} from "@/utils/antd/tableHelpers"; } from "@/utils/antd/tableHelpers";
import { PlusOutlined } from "@ant-design/icons";
function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>( function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
props: EnhancedProTableProps<T, U> props: EnhancedProTableProps<T, U>,
ref: React.Ref<ActionType | undefined> | undefined
) { ) {
const { const {
columns: originalColumns, columns,
request, request,
actions = [], actions = [],
toolbarActions = [],
permissions = [], permissions = [],
checkPermission = () => true, checkPermission = () => true,
toolbarActions,
showIndex = true, showIndex = true,
showSelection = true, showSelection = true,
showActions = true, showActions = true,
@@ -44,160 +53,8 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
...restProps ...restProps
} = props; } = props;
const actionRef = useRef<ActionType>(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRows, setSelectedRows] = useState<T[]>([]); 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(() => { const rowSelection = useMemo(() => {
if (!showSelection) return undefined; if (!showSelection) return undefined;
@@ -233,45 +90,60 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
[showSelection] [showSelection]
); );
// 表格提醒操作渲染 const toolBarRender = useCallback(
const tableAlertOptionRender = useCallback(() => { (
if (!showSelection || selectedRowKeys.length === 0) return false; action: ActionType | undefined,
rows: {
const batchActions = filteredToolbarActions.filter( selectedRowKeys?: (string | number)[] | undefined;
(action) => action.needSelection selectedRows?: T[] | undefined;
); }
) => {
if (batchActions.length === 0) return false; const toolbarElements =
toolbarActions?.map((action) => {
return ( return (
<Space size={16}> <Button
{batchActions.map((action) => ( key={action.key}
<a type={action.type}
key={action.key} danger={action.danger}
onClick={() => disabled={action.disabled}
action.onClick(selectedRowKeys as number[], selectedRows) icon={<PlusOutlined />}
} onClick={() => action.onClick(selectedRowKeys, selectedRows)}
> >
{action.label} {action.label}
</a> </Button>
))} );
</Space> }) || [];
); // return [
}, [showSelection, selectedRowKeys, selectedRows, filteredToolbarActions]); // <Button
// key="button"
// icon={<PlusOutlined />}
// onClick={}
// type="primary"
// >
// 新建
// </Button>,
// ];
return toolbarElements;
},
[toolbarActions]
);
return ( return (
<ProTable<T, U> <ProTable<T, U>
{...restProps} {...restProps}
columns={enhancedColumns} columns={columns}
actionRef={actionRef} actionRef={ref}
request={request} request={request}
rowKey="id" rowKey="id"
rowSelection={rowSelection} rowSelection={rowSelection}
toolBarRender={toolBarRender} toolBarRender={toolBarRender}
manualRequest={false}
showSorterTooltip
tableAlertRender={tableAlertRender} tableAlertRender={tableAlertRender}
tableAlertOptionRender={tableAlertOptionRender} // tableAlertOptionRender={tableAlertOptionRender}
scroll={{ x: "max-content" }}
search={{ search={{
labelWidth: "auto", labelWidth: "auto",
defaultCollapsed: false,
...restProps.search, ...restProps.search,
}} }}
options={{ options={{
@@ -291,4 +163,9 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
); );
} }
export default EnhancedProTable; export default forwardRef(EnhancedProTable) as <
T extends BaseRecord,
U extends ParamsType = any
>(
props: EnhancedProTableProps<T, U> & { ref?: React.Ref<ActionType> }
) => React.ReactElement;

View File

@@ -47,7 +47,7 @@ export interface ToolbarAction {
type?: ButtonProps["type"]; type?: ButtonProps["type"];
danger?: boolean; danger?: boolean;
disabled?: boolean; disabled?: boolean;
onClick: (selectedKeys: React.Key[], selectedRows: any[]) => void; onClick: (selectedKeys?: React.Key[], selectedRows?: any[]) => void;
permission?: string; permission?: string;
needSelection?: boolean; needSelection?: boolean;
} }
@@ -58,6 +58,7 @@ export interface EnhancedProTableProps<T extends BaseRecord, U = any>
request?: ( request?: (
params: U & { current?: number; pageSize?: number } params: U & { current?: number; pageSize?: number }
) => Promise<ApiResponse<T>>; ) => Promise<ApiResponse<T>>;
loading?: boolean; // 添加 loading 属性
actions?: TableAction<T>[]; actions?: TableAction<T>[];
toolbarActions?: ToolbarAction[]; toolbarActions?: ToolbarAction[];
permissions?: string[]; permissions?: string[];

1
src/constants/index.ts Normal file
View File

@@ -0,0 +1 @@
export const formStatusType = { create: "创建", update: "编辑" };

View File

@@ -1,116 +0,0 @@
// 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 };

131
src/hooks/antd/useModal.ts Normal file
View File

@@ -0,0 +1,131 @@
import { App } from "antd";
import { useCallback } from "react";
export interface ConfirmOptions {
title: string;
content?: string;
okText?: string;
cancelText?: string;
onOk?: () => Promise<void> | void;
onCancel?: () => void;
type?: "confirm" | "info" | "success" | "error" | "warning";
}
export const useModal = () => {
const { modal, message } = App.useApp();
// 通用确认对话框
const showConfirm = useCallback(
(options: ConfirmOptions) => {
const {
title,
content,
okText = "确定",
cancelText = "取消",
onOk,
onCancel,
type = "confirm",
} = options;
const modalMethod = modal[type];
return modalMethod({
title,
content,
okText,
cancelText,
onOk,
onCancel,
});
},
[modal]
);
// 删除确认对话框
const showDeleteConfirm = useCallback(
(
onConfirm: () => Promise<void> | void,
title: string = "删除确认",
content: string = "确定要删除吗?此操作不可恢复。"
) => {
return modal.confirm({
title,
content,
okText: "删除",
cancelText: "取消",
okType: "danger",
onOk: async () => {
try {
await onConfirm();
message.success("删除成功");
} catch (error) {
message.error("删除失败");
throw error;
}
},
});
},
[modal, message]
);
// 编辑确认对话框
const showEditConfirm = useCallback(
(
onConfirm: () => Promise<void> | void,
title: string = "编辑确认",
content: string = "确定要保存修改吗?"
) => {
return modal.confirm({
title,
content,
okText: "保存",
cancelText: "取消",
onOk: async () => {
try {
await onConfirm();
message.success("保存成功");
} catch (error) {
message.error("保存失败");
throw error;
}
},
});
},
[modal, message]
);
// 状态切换确认对话框
const showStatusConfirm = useCallback(
(
onConfirm: () => Promise<void> | void,
action: string,
itemName: string = "该项"
) => {
return modal.confirm({
title: `${action}确认`,
content: `确定要${action}${itemName}吗?`,
okText: action,
cancelText: "取消",
onOk: async () => {
try {
await onConfirm();
message.success(`${action}成功`);
} catch (error) {
message.error(`${action}失败`);
throw error;
}
},
});
},
[modal, message]
);
return {
modal,
message,
showConfirm,
showDeleteConfirm,
showEditConfirm,
showStatusConfirm,
};
};

View File

@@ -0,0 +1,51 @@
// hooks/useRequest.ts
import { useState, useCallback } from "react";
import { message } from "antd";
export interface UseRequestOptions<T> {
onSuccess?: (data: T) => void;
onError?: (error: any) => void;
showErrorMessage?: boolean;
}
export function useRequest<T = any, P = any>(
requestFn: (params: P) => Promise<T>,
options: UseRequestOptions<T> = {}
) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<any>(null);
const { onSuccess, onError, showErrorMessage = true } = options;
const run = useCallback(
async (params: P) => {
setLoading(true);
setError(null);
try {
const result = await requestFn(params);
setData(result);
onSuccess?.(result);
return result;
} catch (err) {
setError(err);
onError?.(err);
if (showErrorMessage) {
message.error("请求失败");
}
throw err;
} finally {
setLoading(false);
}
},
[requestFn, onSuccess, onError, showErrorMessage]
);
return {
loading,
data,
error,
run,
};
}

View File

@@ -1,71 +1,71 @@
export default { export default {
'pages.layouts.userLayout.title': "pages.layouts.userLayout.title":
'Ant Design 是西湖区最具影响力的 Web 设计规范', "Ant Design 是西湖区最具影响力的 Web 设计规范",
'pages.login.accountLogin.tab': '账户密码登录', "pages.login.accountLogin.tab": "账户密码登录",
'pages.login.accountLogin.errorMessage': "pages.login.accountLogin.errorMessage":
'错误的用户名和密码(admin/ant.design)', "错误的用户名和密码(admin/ant.design)",
'pages.login.failure': '登录失败,请重试!', "pages.login.failure": "登录失败,请重试!",
'pages.login.success': '登录成功!', "pages.login.success": "登录成功!",
'pages.login.username.placeholder': '用户名: admin or user', "pages.login.username.placeholder": "用户名: admin or user",
'pages.login.username.required': '用户名是必填项!', "pages.login.username.required": "用户名是必填项!",
'pages.login.password.placeholder': '密码: ant.design', "pages.login.password.placeholder": "密码: ant.design",
'pages.login.password.required': '密码是必填项!', "pages.login.password.required": "密码是必填项!",
'pages.login.phoneLogin.tab': '手机号登录', "pages.login.phoneLogin.tab": "手机号登录",
'pages.login.phoneLogin.errorMessage': '验证码错误', "pages.login.phoneLogin.errorMessage": "验证码错误",
'pages.login.phoneNumber.placeholder': '请输入手机号!', "pages.login.phoneNumber.placeholder": "请输入手机号!",
'pages.login.phoneNumber.required': '手机号是必填项!', "pages.login.phoneNumber.required": "手机号是必填项!",
'pages.login.phoneNumber.invalid': '不合法的手机号!', "pages.login.phoneNumber.invalid": "不合法的手机号!",
'pages.login.captcha.placeholder': '请输入验证码!', "pages.login.captcha.placeholder": "请输入验证码!",
'pages.login.captcha.required': '验证码是必填项!', "pages.login.captcha.required": "验证码是必填项!",
'pages.login.phoneLogin.getVerificationCode': '获取验证码', "pages.login.phoneLogin.getVerificationCode": "获取验证码",
'pages.getCaptchaSecondText': '秒后重新获取', "pages.getCaptchaSecondText": "秒后重新获取",
'pages.login.rememberMe': '自动登录', "pages.login.rememberMe": "自动登录",
'pages.login.forgotPassword': '忘记密码 ?', "pages.login.forgotPassword": "忘记密码 ?",
'pages.login.submit': '登录', "pages.login.submit": "登录",
'pages.login.loginWith': '其他登录方式 :', "pages.login.loginWith": "其他登录方式 :",
'pages.login.registerAccount': '注册账户', "pages.login.registerAccount": "注册账户",
'pages.welcome.link': '欢迎使用', "pages.welcome.link": "欢迎使用",
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。', "pages.welcome.alertMessage": "更快更强的重型组件,已经发布。",
'pages.404.subTitle': '抱歉,您访问的页面不存在。', "pages.404.subTitle": "抱歉,您访问的页面不存在。",
'pages.404.buttonText': '返回首页', "pages.404.buttonText": "返回首页",
'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看', "pages.admin.subPage.title": " 这个页面只有 admin 权限才能查看",
'pages.admin.subPage.alertMessage': "pages.admin.subPage.alertMessage":
'umi ui 现已发布,欢迎使用 npm run ui 启动体验。', "umi ui 现已发布,欢迎使用 npm run ui 启动体验。",
'pages.searchTable.createForm.newRule': '新建规则', "pages.searchTable.createForm.newRule": "新建规则",
'pages.searchTable.updateForm.ruleConfig': '规则配置', "pages.searchTable.updateForm.ruleConfig": "规则配置",
'pages.searchTable.updateForm.basicConfig': '基本信息', "pages.searchTable.updateForm.basicConfig": "基本信息",
'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称', "pages.searchTable.updateForm.ruleName.nameLabel": "规则名称",
'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!', "pages.searchTable.updateForm.ruleName.nameRules": "请输入规则名称!",
'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述', "pages.searchTable.updateForm.ruleDesc.descLabel": "规则描述",
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符', "pages.searchTable.updateForm.ruleDesc.descPlaceholder": "请输入至少五个字符",
'pages.searchTable.updateForm.ruleDesc.descRules': "pages.searchTable.updateForm.ruleDesc.descRules":
'请输入至少五个字符的规则描述!', "请输入至少五个字符的规则描述!",
'pages.searchTable.updateForm.ruleProps.title': '配置规则属性', "pages.searchTable.updateForm.ruleProps.title": "配置规则属性",
'pages.searchTable.updateForm.object': '监控对象', "pages.searchTable.updateForm.object": "监控对象",
'pages.searchTable.updateForm.ruleProps.templateLabel': '规则模板', "pages.searchTable.updateForm.ruleProps.templateLabel": "规则模板",
'pages.searchTable.updateForm.ruleProps.typeLabel': '规则类型', "pages.searchTable.updateForm.ruleProps.typeLabel": "规则类型",
'pages.searchTable.updateForm.schedulingPeriod.title': '设定调度周期', "pages.searchTable.updateForm.schedulingPeriod.title": "设定调度周期",
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '开始时间', "pages.searchTable.updateForm.schedulingPeriod.timeLabel": "开始时间",
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!', "pages.searchTable.updateForm.schedulingPeriod.timeRules": "请选择开始时间!",
'pages.searchTable.titleDesc': '描述', "pages.searchTable.titleDesc": "描述",
'pages.searchTable.ruleName': '规则名称为必填项', "pages.searchTable.ruleName": "规则名称为必填项",
'pages.searchTable.titleCallNo': '服务调用次数', "pages.searchTable.titleCallNo": "服务调用次数",
'pages.searchTable.titleStatus': '状态', "pages.searchTable.titleStatus": "状态",
'pages.searchTable.nameStatus.default': '关闭', "pages.searchTable.nameStatus.default": "关闭",
'pages.searchTable.nameStatus.running': '运行中', "pages.searchTable.nameStatus.running": "运行中",
'pages.searchTable.nameStatus.online': '已上线', "pages.searchTable.nameStatus.online": "已上线",
'pages.searchTable.nameStatus.abnormal': '异常', "pages.searchTable.nameStatus.abnormal": "异常",
'pages.searchTable.titleUpdatedAt': '上次调度时间', "pages.searchTable.titleUpdatedAt": "上次调度时间",
'pages.searchTable.exception': '请输入异常原因!', "pages.searchTable.exception": "请输入异常原因!",
'pages.searchTable.titleOption': '操作', "pages.searchTable.titleOption": "操作",
'pages.searchTable.config': '配置', "pages.searchTable.config": "配置",
'pages.searchTable.subscribeAlert': '订阅警报', "pages.searchTable.subscribeAlert": "订阅警报",
'pages.searchTable.title': '查询表格', "pages.searchTable.title": "查询表格",
'pages.searchTable.new': '新建', "pages.searchTable.new": "新增",
'pages.searchTable.chosen': '已选择', "pages.searchTable.chosen": "已选择",
'pages.searchTable.item': '项', "pages.searchTable.item": "项",
'pages.searchTable.totalServiceCalls': '服务调用次数总计', "pages.searchTable.totalServiceCalls": "服务调用次数总计",
'pages.searchTable.tenThousand': '万', "pages.searchTable.tenThousand": "万",
'pages.searchTable.batchDeletion': '批量删除', "pages.searchTable.batchDeletion": "批量删除",
'pages.searchTable.batchApproval': '批量审批', "pages.searchTable.batchApproval": "批量审批",
}; };

View File

@@ -1,71 +1,71 @@
export default { export default {
'pages.layouts.userLayout.title': "pages.layouts.userLayout.title":
'Ant Design 是西湖區最具影響力的 Web 設計規範', "Ant Design 是西湖區最具影響力的 Web 設計規範",
'pages.login.accountLogin.tab': '賬戶密碼登錄', "pages.login.accountLogin.tab": "賬戶密碼登錄",
'pages.login.accountLogin.errorMessage': "pages.login.accountLogin.errorMessage":
'錯誤的用戶名和密碼(admin/ant.design)', "錯誤的用戶名和密碼(admin/ant.design)",
'pages.login.failure': '登錄失敗,請重試!', "pages.login.failure": "登錄失敗,請重試!",
'pages.login.success': '登錄成功!', "pages.login.success": "登錄成功!",
'pages.login.username.placeholder': '用戶名: admin or user', "pages.login.username.placeholder": "用戶名: admin or user",
'pages.login.username.required': '用戶名是必填項!', "pages.login.username.required": "用戶名是必填項!",
'pages.login.password.placeholder': '密碼: ant.design', "pages.login.password.placeholder": "密碼: ant.design",
'pages.login.password.required': '密碼是必填項!', "pages.login.password.required": "密碼是必填項!",
'pages.login.phoneLogin.tab': '手機號登錄', "pages.login.phoneLogin.tab": "手機號登錄",
'pages.login.phoneLogin.errorMessage': '驗證碼錯誤', "pages.login.phoneLogin.errorMessage": "驗證碼錯誤",
'pages.login.phoneNumber.placeholder': '請輸入手機號!', "pages.login.phoneNumber.placeholder": "請輸入手機號!",
'pages.login.phoneNumber.required': '手機號是必填項!', "pages.login.phoneNumber.required": "手機號是必填項!",
'pages.login.phoneNumber.invalid': '不合法的手機號!', "pages.login.phoneNumber.invalid": "不合法的手機號!",
'pages.login.captcha.placeholder': '請輸入驗證碼!', "pages.login.captcha.placeholder": "請輸入驗證碼!",
'pages.login.captcha.required': '驗證碼是必填項!', "pages.login.captcha.required": "驗證碼是必填項!",
'pages.login.phoneLogin.getVerificationCode': '獲取驗證碼', "pages.login.phoneLogin.getVerificationCode": "獲取驗證碼",
'pages.getCaptchaSecondText': '秒後重新獲取', "pages.getCaptchaSecondText": "秒後重新獲取",
'pages.login.rememberMe': '自動登錄', "pages.login.rememberMe": "自動登錄",
'pages.login.forgotPassword': '忘記密碼 ?', "pages.login.forgotPassword": "忘記密碼 ?",
'pages.login.submit': '登錄', "pages.login.submit": "登錄",
'pages.login.loginWith': '其他登錄方式 :', "pages.login.loginWith": "其他登錄方式 :",
'pages.login.registerAccount': '註冊賬戶', "pages.login.registerAccount": "註冊賬戶",
'pages.welcome.link': '歡迎使用', "pages.welcome.link": "歡迎使用",
'pages.welcome.alertMessage': '更快更強的重型組件,已經發布。', "pages.welcome.alertMessage": "更快更強的重型組件,已經發布。",
'pages.404.subTitle': '抱歉,您訪問的頁面不存在。', "pages.404.subTitle": "抱歉,您訪問的頁面不存在。",
'pages.404.buttonText': '返回首頁', "pages.404.buttonText": "返回首頁",
'pages.admin.subPage.title': '這個頁面只有 admin 權限才能查看', "pages.admin.subPage.title": "這個頁面只有 admin 權限才能查看",
'pages.admin.subPage.alertMessage': "pages.admin.subPage.alertMessage":
'umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。', "umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。",
'pages.searchTable.createForm.newRule': '新建規則', "pages.searchTable.createForm.newRule": "新建規則",
'pages.searchTable.updateForm.ruleConfig': '規則配置', "pages.searchTable.updateForm.ruleConfig": "規則配置",
'pages.searchTable.updateForm.basicConfig': '基本信息', "pages.searchTable.updateForm.basicConfig": "基本信息",
'pages.searchTable.updateForm.ruleName.nameLabel': '規則名稱', "pages.searchTable.updateForm.ruleName.nameLabel": "規則名稱",
'pages.searchTable.updateForm.ruleName.nameRules': '請輸入規則名稱!', "pages.searchTable.updateForm.ruleName.nameRules": "請輸入規則名稱!",
'pages.searchTable.updateForm.ruleDesc.descLabel': '規則描述', "pages.searchTable.updateForm.ruleDesc.descLabel": "規則描述",
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '請輸入至少五個字符', "pages.searchTable.updateForm.ruleDesc.descPlaceholder": "請輸入至少五個字符",
'pages.searchTable.updateForm.ruleDesc.descRules': "pages.searchTable.updateForm.ruleDesc.descRules":
'請輸入至少五個字符的規則描述!', "請輸入至少五個字符的規則描述!",
'pages.searchTable.updateForm.ruleProps.title': '配置規則屬性', "pages.searchTable.updateForm.ruleProps.title": "配置規則屬性",
'pages.searchTable.updateForm.object': '監控對象', "pages.searchTable.updateForm.object": "監控對象",
'pages.searchTable.updateForm.ruleProps.templateLabel': '規則模板', "pages.searchTable.updateForm.ruleProps.templateLabel": "規則模板",
'pages.searchTable.updateForm.ruleProps.typeLabel': '規則類型', "pages.searchTable.updateForm.ruleProps.typeLabel": "規則類型",
'pages.searchTable.updateForm.schedulingPeriod.title': '設定調度週期', "pages.searchTable.updateForm.schedulingPeriod.title": "設定調度週期",
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '開始時間', "pages.searchTable.updateForm.schedulingPeriod.timeLabel": "開始時間",
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '請選擇開始時間!', "pages.searchTable.updateForm.schedulingPeriod.timeRules": "請選擇開始時間!",
'pages.searchTable.titleDesc': '描述', "pages.searchTable.titleDesc": "描述",
'pages.searchTable.ruleName': '規則名稱爲必填項', "pages.searchTable.ruleName": "規則名稱爲必填項",
'pages.searchTable.titleCallNo': '服務調用次數', "pages.searchTable.titleCallNo": "服務調用次數",
'pages.searchTable.titleStatus': '狀態', "pages.searchTable.titleStatus": "狀態",
'pages.searchTable.nameStatus.default': '關閉', "pages.searchTable.nameStatus.default": "關閉",
'pages.searchTable.nameStatus.running': '運行中', "pages.searchTable.nameStatus.running": "運行中",
'pages.searchTable.nameStatus.online': '已上線', "pages.searchTable.nameStatus.online": "已上線",
'pages.searchTable.nameStatus.abnormal': '異常', "pages.searchTable.nameStatus.abnormal": "異常",
'pages.searchTable.titleUpdatedAt': '上次調度時間', "pages.searchTable.titleUpdatedAt": "上次調度時間",
'pages.searchTable.exception': '請輸入異常原因!', "pages.searchTable.exception": "請輸入異常原因!",
'pages.searchTable.titleOption': '操作', "pages.searchTable.titleOption": "操作",
'pages.searchTable.config': '配置', "pages.searchTable.config": "配置",
'pages.searchTable.subscribeAlert': '訂閱警報', "pages.searchTable.subscribeAlert": "訂閱警報",
'pages.searchTable.title': '查詢表格', "pages.searchTable.title": "查詢表格",
'pages.searchTable.new': '新建', "pages.searchTable.new": "新增",
'pages.searchTable.chosen': '已選擇', "pages.searchTable.chosen": "已選擇",
'pages.searchTable.item': '項', "pages.searchTable.item": "項",
'pages.searchTable.totalServiceCalls': '服務調用次數總計', "pages.searchTable.totalServiceCalls": "服務調用次數總計",
'pages.searchTable.tenThousand': '萬', "pages.searchTable.tenThousand": "萬",
'pages.searchTable.batchDeletion': '批量刪除', "pages.searchTable.batchDeletion": "批量刪除",
'pages.searchTable.batchApproval': '批量審批', "pages.searchTable.batchApproval": "批量審批",
}; };

View File

@@ -0,0 +1,5 @@
const SyStemDept = () => {
return <>SyStemDept</>;
};
export default SyStemDept;

View File

@@ -0,0 +1,5 @@
const SyStemDict = () => {
return <>SyStemDict</>;
};
export default SyStemDict;

View File

@@ -0,0 +1,5 @@
const SyStemLogLogin = () => {
return <>SyStemLogLogin</>;
};
export default SyStemLogLogin;

View File

@@ -0,0 +1,5 @@
const SyStemLogOperate = () => {
return <>SyStemLogOperate</>;
};
export default SyStemLogOperate;

View File

@@ -0,0 +1,5 @@
const SyStemMenu = () => {
return <>SyStemMenu</>;
};
export default SyStemMenu;

View File

@@ -0,0 +1,5 @@
const SyStemPost = () => {
return <>SyStemPost</>;
};
export default SyStemPost;

View File

@@ -0,0 +1,5 @@
const SyStemRole = () => {
return <>SyStemRole</>;
};
export default SyStemRole;

View File

@@ -0,0 +1,223 @@
import { type TenantVO, deleteTenant } from "@/services/system/tenant/list";
import { ProColumns } from "@ant-design/pro-components";
import { DatePicker, Modal, Popconfirm } from "antd";
import { FormInstance } from "antd/lib";
import dayjs from "dayjs";
export const baseTenantColumns: ProColumns<TenantVO>[] = [
{
title: "租户编号",
dataIndex: "id",
tip: "租户编号",
width: 100,
hideInSearch: true, // 在搜索表单中隐藏
},
{
title: "租户名",
dataIndex: "name",
tip: "租户名", // 提示信息
},
{
title: "租户套餐",
dataIndex: "packageId",
valueType: "select",
request: async () => {
return [
{
label: "默认套餐",
value: 1,
},
];
},
// valueEnum: {
// all: { text: "全部", status: "Default" },
// open: { text: "未解决", status: "Error" },
// closed: { text: "已解决", status: "Success" },
// },
},
{
title: "联系人",
dataIndex: "contactName",
},
{
title: "联系手机",
dataIndex: "contactMobile",
},
{
title: "账号额度",
dataIndex: "accountCount",
hideInSearch: true, // 在搜索表单中隐藏
},
{
title: "过期时间",
dataIndex: "expireTime",
valueType: "dateTime",
hideInSearch: true, // 在搜索表单中隐藏
},
{ title: "绑定域名", dataIndex: "website", width: 100 },
{
title: "租户状态",
dataIndex: "status",
valueType: "select",
valueEnum: {
all: { text: "全部", status: "Default" },
open: { text: "未解决", status: "Error" },
closed: { text: "已解决", status: "Success" },
},
},
{
title: "创建时间",
dataIndex: "createTime",
valueType: "dateRange",
search: {
transform: (value) => {
return [`${value[0]} 00:00:00`, `${value[1]} 00:00:00`];
},
},
render: (_, record: TenantVO) =>
dayjs(record.createTime).format("YYYY-MM-DD HH:mm:ss"),
},
];
export const formColumns: any = [
{
title: "租户名",
dataIndex: "name",
tip: "租户名", // 提示信息
formItemProps: {
rules: [
{
required: true,
message: "请输入用户名",
},
// {
// min: 2,
// max: 20,
// message: "用户名长度为2-20个字符",
// },
],
},
},
{
title: "租户套餐",
dataIndex: "packageId",
valueType: "select",
formItemProps: {
rules: [
{
required: true,
message: "请选择租户套餐",
},
],
},
fieldProps: {
placeholder: "请选择套餐类型",
options: [
{
label: "普通套餐",
value: 111,
},
],
},
},
{
title: "联系人",
dataIndex: "contactName",
},
{
title: "联系手机",
dataIndex: "contactMobile",
formItemProps: {
rules: [
{
required: true,
message: "请输入联系手机",
},
],
},
},
{
title: "用户名称",
dataIndex: "username",
hideInForm: true,
formItemProps: {
rules: [
{
required: true,
message: "请输入用户名称",
},
{
pattern: /^[a-zA-Z0-9]+$/,
message: "用户账号由 0-9、a-z、A-Z 组成",
},
// 用户账号由 数字、字母组成
],
},
},
{
title: "用户密码",
dataIndex: "password",
valueType: "password",
hideInForm: true,
fieldProps: {
placeholder: "请输入用户密码",
autoComplete: "new-password",
},
formItemProps: {
rules: [
{
required: true,
message: "请输入用户密码",
},
{
min: 4,
max: 16,
message: "密码长度为4-16个字符",
},
],
},
},
{
title: "账号额度",
dataIndex: "accountCount",
valueType: "digit",
},
{
title: "过期时间",
dataIndex: "expireTime",
valueType: "date",
fieldProps: {
placeholder: "请选择过期时间",
format: "YYYY-MM-DD",
},
},
{ title: "绑定域名", dataIndex: "website" },
{
title: "租户状态",
dataIndex: "status",
valueType: "radio",
formItemProps: {
rules: [
{
required: true,
message: "请选择租户状态",
},
],
},
fieldProps: {
placeholder: "请选择套餐类型",
options: [
{
label: "启用",
value: 1,
},
{
label: "禁用",
value: 0,
},
],
},
},
];

View File

@@ -1,68 +1,49 @@
import EnhancedProTable from "@/components/EnhancedProTable"; import EnhancedProTable from "@/components/EnhancedProTable";
import { import {
getTenantPage, getTenantPage,
deleteTenant, createTenant,
type TenantPageReqVO, type TenantPageReqVO,
type TenantVO, type TenantVO,
deleteTenant,
updateTenant,
} from "@/services/system/tenant/list"; } from "@/services/system/tenant/list";
import React, { createContext, useCallback } from "react";
import ConfigurableDrawerForm, {
ConfigurableDrawerFormRef,
} from "@/components/DrawerForm";
import { useRef, useState } from "react";
import { formColumns, baseTenantColumns } from "./config";
import { PlusOutlined } from "@ant-design/icons"; import { PlusOutlined } from "@ant-design/icons";
import { import { ref } from "process";
ProCoreActionType, import { ActionType, ProColumns } from "@ant-design/pro-components";
ProTable, import { ToolbarAction } from "@/components/EnhancedProTable/types";
TableDropdown, import { Modal, Popconfirm } from "antd";
type ProColumns, import { formStatusType } from "@/constants";
} from "@ant-design/pro-components"; import dayjs from "dayjs";
import { Button, Space } from "antd"; export const waitTimePromise = async (time: number = 90) => {
return new Promise((resolve) => {
import { setTimeout(() => {
createOrderTableConfig, resolve(true);
createCommonActions, }, time);
createCommonToolbarActions, });
} from "@/utils/antd/tableConfigFactory"; };
import { useEnhancedTable } from "@/hooks/antd/useEnhancedTable";
const TenantList = () => { const TenantList = () => {
const columns: ProColumns<TenantVO>[] = [ const configurableDrawerRef = useRef<ConfigurableDrawerFormRef>(null);
{ const tableRef = useRef<ActionType>(null);
title: "租户编号", const [type, setType] = useState<"create" | "update">("create");
dataIndex: "id", const [id, setId] = useState<number>(0);
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 ( const onFetch = async (
params: TenantPageReqVO & { params: TenantPageReqVO & {
pageSize?: number; pageSize?: number;
current?: number; current?: number;
} }
) => { ) => {
await waitTimePromise();
const data = await getTenantPage({ const data = await getTenantPage({
...params,
pageNo: params.current, pageNo: params.current,
pageSize: params.pageSize, pageSize: params.pageSize,
}); });
console.log(data);
return { return {
data: data.list, data: data.list,
success: true, success: true,
@@ -70,39 +51,89 @@ const TenantList = () => {
}; };
}; };
const { actionRef, selectedRowKeys, selectedRows, actions } = const handleSubmit = useCallback(
useEnhancedTable<TenantVO>({ async (values: TenantVO) => {
onEdit: (record) => { if (type === "create") {
console.log("编辑订单:", record); await createTenant(values);
}, } else {
onDelete: async (record) => { await updateTenant({
await deleteTenant(record.id); ...values,
return true; id,
}, expireTime: dayjs(values.expireTime).valueOf(),
}); });
const tableActions = createCommonActions<TenantVO>({ }
onView: actions.view, tableRef.current?.reload();
onEdit: actions.edit, return true;
onDelete: actions.delete, },
}); [type]
);
const toolbarActions = createCommonToolbarActions({ const handleAdd = () => {
onExport: actions.export, setType("create");
}); configurableDrawerRef.current?.open({});
};
const handleEdit = (record: TenantVO) => {
setType("update");
setId(record.id);
configurableDrawerRef.current?.open(record);
};
const toolbarActions: ToolbarAction[] = [
{
key: "add",
label: "新建",
type: "primary",
icon: <PlusOutlined />,
onClick: handleAdd,
},
];
const actionColumns: ProColumns<TenantVO> = {
title: "操作",
dataIndex: "option",
valueType: "option",
fixed: "right",
width: 120,
render: (text: React.ReactNode, record: TenantVO, _: any, action: any) => [
<a key="editable" onClick={() => handleEdit(record)}>
</a>,
<Popconfirm
title="是否删除?"
key="delete"
onConfirm={async () => {
await deleteTenant(record.id);
action?.reload();
}}
okText="是"
cancelText="否"
>
<a></a>
</Popconfirm>,
],
};
const columns = [...baseTenantColumns, actionColumns];
return ( return (
<div> <>
<EnhancedProTable<TenantVO> <EnhancedProTable<TenantVO>
ref={tableRef}
columns={columns} columns={columns}
request={onFetch} request={onFetch}
actions={tableActions}
toolbarActions={toolbarActions} toolbarActions={toolbarActions}
headerTitle="订单管理" headerTitle="租户列表"
showIndex={false} showIndex={false}
// showSelection showSelection={false}
showActions showActions
maxActionCount={3} maxActionCount={3}
/> />
</div>
<ConfigurableDrawerForm
ref={configurableDrawerRef}
title={formStatusType[type]}
columns={formColumns}
onSubmit={handleSubmit}
/>
</>
); );
}; };

View File

@@ -0,0 +1,5 @@
const SyStemUser = () => {
return <>SyStemUser</>;
};
export default SyStemUser;

View File

@@ -137,6 +137,7 @@ export const errorConfig: RequestConfig = {
setToken(refreshTokenRes); setToken(refreshTokenRes);
console.log(getAccessToken()); console.log(getAccessToken());
config.headers!.Authorization = "Bearer " + getAccessToken(); config.headers!.Authorization = "Bearer " + getAccessToken();
console.log(requestList);
requestList.forEach((cb: any) => { requestList.forEach((cb: any) => {
cb(); cb();
}); });
@@ -154,6 +155,7 @@ export const errorConfig: RequestConfig = {
} }
return; return;
} else { } else {
console.log("刷新令牌失败");
//添加到队列,等待刷新获取到新的令牌 //添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => { return new Promise((resolve) => {
requestList.push(() => { requestList.push(() => {

View File

@@ -10,7 +10,7 @@ export interface TenantVO {
packageId: number; packageId: number;
username: string; username: string;
password: string; password: string;
expireTime: Date; expireTime: Date | number;
accountCount: number; accountCount: number;
createTime: Date; createTime: Date;
} }

View File

@@ -0,0 +1,43 @@
// 常用的校验规则配置
// rules: [
// { required: true, message: '请输入用户名' },
// { min: 3, max: 20, message: '用户名长度为3-20个字符' },
// { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线' },
// ],
const commonRules = {
// 必填
required: { required: true, message: "此项为必填项" },
// 邮箱
email: { type: "email", message: "请输入正确的邮箱格式" },
// 手机号
phone: { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号格式" },
// 身份证
idCard: {
pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
message: "请输入正确的身份证号",
},
// 密码强度
strongPassword: {
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/,
message: "密码必须包含大小写字母、数字至少8位",
},
// 数字范围
numberRange: (min: number, max: number) => ({
type: "number",
min,
max,
message: `请输入${min}-${max}之间的数字`,
}),
// 字符长度
lengthRange: (min: number, max: number) => ({
min,
max,
message: `长度为${min}-${max}个字符`,
}),
};

View File

@@ -2,7 +2,8 @@
import React from "react"; import React from "react";
import { ProColumns } from "@ant-design/pro-components"; import { ProColumns } from "@ant-design/pro-components";
import { Tag } from "antd"; import { Tag } from "antd";
import { ICONS, IconType } from "@/constants/icons"; import { history, useIntl } from "@umijs/max";
import { ICONS, IconType } from "@/constants/antd/icons";
import { import {
TableAction, TableAction,
ToolbarAction, ToolbarAction,
@@ -129,13 +130,18 @@ export const createCommonToolbarActions = (handlers: {
onImport?: () => void; onImport?: () => void;
}): ToolbarAction[] => { }): ToolbarAction[] => {
const actions: ToolbarAction[] = []; const actions: ToolbarAction[] = [];
if (handlers.onAdd) { if (handlers.onAdd) {
actions.push( actions.push(
createToolbarAction("add", "新建", "add", handlers.onAdd, { createToolbarAction(
type: "primary", "add",
permission: "add", useIntl().formatMessage({ id: "pages.searchTable.new" }),
}) "add",
handlers.onAdd,
{
type: "primary",
permission: "add",
}
)
); );
} }