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:
build:
if: false
runs-on: ${{ matrix.os }}
strategy:
matrix:

View File

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

View File

@@ -1,5 +1,11 @@
import React, { Children, Component, JSX, Suspense } from "react";
import { Spin } from "antd";
import React, {
Children,
Component,
createContext,
JSX,
Suspense,
} from "react";
import { Modal, Spin } from "antd";
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
import { SettingDrawer } from "@ant-design/pro-components";
import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max";
@@ -197,6 +203,34 @@ export const request: RequestConfig = {
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
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
import React, { useRef, useState, useCallback, useMemo } from "react";
import React, {
useRef,
useState,
useCallback,
useMemo,
act,
forwardRef,
} from "react";
import {
ProTable,
ProColumns,
@@ -19,17 +26,19 @@ import {
handleTableDropdownSelect,
formatPaginationTotal,
} from "@/utils/antd/tableHelpers";
import { PlusOutlined } from "@ant-design/icons";
function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
props: EnhancedProTableProps<T, U>
props: EnhancedProTableProps<T, U>,
ref: React.Ref<ActionType | undefined> | undefined
) {
const {
columns: originalColumns,
columns,
request,
actions = [],
toolbarActions = [],
permissions = [],
checkPermission = () => true,
toolbarActions,
showIndex = true,
showSelection = true,
showActions = true,
@@ -44,160 +53,8 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
...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;
@@ -233,45 +90,60 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
[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)
const toolBarRender = useCallback(
(
action: ActionType | undefined,
rows: {
selectedRowKeys?: (string | number)[] | undefined;
selectedRows?: T[] | undefined;
}
) => {
const toolbarElements =
toolbarActions?.map((action) => {
return (
<Button
key={action.key}
type={action.type}
danger={action.danger}
disabled={action.disabled}
icon={<PlusOutlined />}
onClick={() => action.onClick(selectedRowKeys, selectedRows)}
>
{action.label}
</a>
))}
</Space>
</Button>
);
}) || [];
// return [
// <Button
// key="button"
// icon={<PlusOutlined />}
// onClick={}
// type="primary"
// >
// 新建
// </Button>,
// ];
return toolbarElements;
},
[toolbarActions]
);
}, [showSelection, selectedRowKeys, selectedRows, filteredToolbarActions]);
return (
<ProTable<T, U>
{...restProps}
columns={enhancedColumns}
actionRef={actionRef}
columns={columns}
actionRef={ref}
request={request}
rowKey="id"
rowSelection={rowSelection}
toolBarRender={toolBarRender}
manualRequest={false}
showSorterTooltip
tableAlertRender={tableAlertRender}
tableAlertOptionRender={tableAlertOptionRender}
// tableAlertOptionRender={tableAlertOptionRender}
scroll={{ x: "max-content" }}
search={{
labelWidth: "auto",
defaultCollapsed: false,
...restProps.search,
}}
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"];
danger?: boolean;
disabled?: boolean;
onClick: (selectedKeys: React.Key[], selectedRows: any[]) => void;
onClick: (selectedKeys?: React.Key[], selectedRows?: any[]) => void;
permission?: string;
needSelection?: boolean;
}
@@ -58,6 +58,7 @@ export interface EnhancedProTableProps<T extends BaseRecord, U = any>
request?: (
params: U & { current?: number; pageSize?: number }
) => Promise<ApiResponse<T>>;
loading?: boolean; // 添加 loading 属性
actions?: TableAction<T>[];
toolbarActions?: ToolbarAction[];
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 {
'pages.layouts.userLayout.title':
'Ant Design 是西湖区最具影响力的 Web 设计规范',
'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.accountLogin.errorMessage':
'错误的用户名和密码(admin/ant.design)',
'pages.login.failure': '登录失败,请重试!',
'pages.login.success': '登录成功!',
'pages.login.username.placeholder': '用户名: admin or user',
'pages.login.username.required': '用户名是必填项!',
'pages.login.password.placeholder': '密码: ant.design',
'pages.login.password.required': '密码是必填项!',
'pages.login.phoneLogin.tab': '手机号登录',
'pages.login.phoneLogin.errorMessage': '验证码错误',
'pages.login.phoneNumber.placeholder': '请输入手机号!',
'pages.login.phoneNumber.required': '手机号是必填项!',
'pages.login.phoneNumber.invalid': '不合法的手机号!',
'pages.login.captcha.placeholder': '请输入验证码!',
'pages.login.captcha.required': '验证码是必填项!',
'pages.login.phoneLogin.getVerificationCode': '获取验证码',
'pages.getCaptchaSecondText': '秒后重新获取',
'pages.login.rememberMe': '自动登录',
'pages.login.forgotPassword': '忘记密码 ?',
'pages.login.submit': '登录',
'pages.login.loginWith': '其他登录方式 :',
'pages.login.registerAccount': '注册账户',
'pages.welcome.link': '欢迎使用',
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
'pages.404.subTitle': '抱歉,您访问的页面不存在。',
'pages.404.buttonText': '返回首页',
'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',
'pages.admin.subPage.alertMessage':
'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
'pages.searchTable.createForm.newRule': '新建规则',
'pages.searchTable.updateForm.ruleConfig': '规则配置',
'pages.searchTable.updateForm.basicConfig': '基本信息',
'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称',
'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!',
'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述',
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符',
'pages.searchTable.updateForm.ruleDesc.descRules':
'请输入至少五个字符的规则描述!',
'pages.searchTable.updateForm.ruleProps.title': '配置规则属性',
'pages.searchTable.updateForm.object': '监控对象',
'pages.searchTable.updateForm.ruleProps.templateLabel': '规则模板',
'pages.searchTable.updateForm.ruleProps.typeLabel': '规则类型',
'pages.searchTable.updateForm.schedulingPeriod.title': '设定调度周期',
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '开始时间',
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!',
'pages.searchTable.titleDesc': '描述',
'pages.searchTable.ruleName': '规则名称为必填项',
'pages.searchTable.titleCallNo': '服务调用次数',
'pages.searchTable.titleStatus': '状态',
'pages.searchTable.nameStatus.default': '关闭',
'pages.searchTable.nameStatus.running': '运行中',
'pages.searchTable.nameStatus.online': '已上线',
'pages.searchTable.nameStatus.abnormal': '异常',
'pages.searchTable.titleUpdatedAt': '上次调度时间',
'pages.searchTable.exception': '请输入异常原因!',
'pages.searchTable.titleOption': '操作',
'pages.searchTable.config': '配置',
'pages.searchTable.subscribeAlert': '订阅警报',
'pages.searchTable.title': '查询表格',
'pages.searchTable.new': '新建',
'pages.searchTable.chosen': '已选择',
'pages.searchTable.item': '项',
'pages.searchTable.totalServiceCalls': '服务调用次数总计',
'pages.searchTable.tenThousand': '万',
'pages.searchTable.batchDeletion': '批量删除',
'pages.searchTable.batchApproval': '批量审批',
"pages.layouts.userLayout.title":
"Ant Design 是西湖区最具影响力的 Web 设计规范",
"pages.login.accountLogin.tab": "账户密码登录",
"pages.login.accountLogin.errorMessage":
"错误的用户名和密码(admin/ant.design)",
"pages.login.failure": "登录失败,请重试!",
"pages.login.success": "登录成功!",
"pages.login.username.placeholder": "用户名: admin or user",
"pages.login.username.required": "用户名是必填项!",
"pages.login.password.placeholder": "密码: ant.design",
"pages.login.password.required": "密码是必填项!",
"pages.login.phoneLogin.tab": "手机号登录",
"pages.login.phoneLogin.errorMessage": "验证码错误",
"pages.login.phoneNumber.placeholder": "请输入手机号!",
"pages.login.phoneNumber.required": "手机号是必填项!",
"pages.login.phoneNumber.invalid": "不合法的手机号!",
"pages.login.captcha.placeholder": "请输入验证码!",
"pages.login.captcha.required": "验证码是必填项!",
"pages.login.phoneLogin.getVerificationCode": "获取验证码",
"pages.getCaptchaSecondText": "秒后重新获取",
"pages.login.rememberMe": "自动登录",
"pages.login.forgotPassword": "忘记密码 ?",
"pages.login.submit": "登录",
"pages.login.loginWith": "其他登录方式 :",
"pages.login.registerAccount": "注册账户",
"pages.welcome.link": "欢迎使用",
"pages.welcome.alertMessage": "更快更强的重型组件,已经发布。",
"pages.404.subTitle": "抱歉,您访问的页面不存在。",
"pages.404.buttonText": "返回首页",
"pages.admin.subPage.title": " 这个页面只有 admin 权限才能查看",
"pages.admin.subPage.alertMessage":
"umi ui 现已发布,欢迎使用 npm run ui 启动体验。",
"pages.searchTable.createForm.newRule": "新建规则",
"pages.searchTable.updateForm.ruleConfig": "规则配置",
"pages.searchTable.updateForm.basicConfig": "基本信息",
"pages.searchTable.updateForm.ruleName.nameLabel": "规则名称",
"pages.searchTable.updateForm.ruleName.nameRules": "请输入规则名称!",
"pages.searchTable.updateForm.ruleDesc.descLabel": "规则描述",
"pages.searchTable.updateForm.ruleDesc.descPlaceholder": "请输入至少五个字符",
"pages.searchTable.updateForm.ruleDesc.descRules":
"请输入至少五个字符的规则描述!",
"pages.searchTable.updateForm.ruleProps.title": "配置规则属性",
"pages.searchTable.updateForm.object": "监控对象",
"pages.searchTable.updateForm.ruleProps.templateLabel": "规则模板",
"pages.searchTable.updateForm.ruleProps.typeLabel": "规则类型",
"pages.searchTable.updateForm.schedulingPeriod.title": "设定调度周期",
"pages.searchTable.updateForm.schedulingPeriod.timeLabel": "开始时间",
"pages.searchTable.updateForm.schedulingPeriod.timeRules": "请选择开始时间!",
"pages.searchTable.titleDesc": "描述",
"pages.searchTable.ruleName": "规则名称为必填项",
"pages.searchTable.titleCallNo": "服务调用次数",
"pages.searchTable.titleStatus": "状态",
"pages.searchTable.nameStatus.default": "关闭",
"pages.searchTable.nameStatus.running": "运行中",
"pages.searchTable.nameStatus.online": "已上线",
"pages.searchTable.nameStatus.abnormal": "异常",
"pages.searchTable.titleUpdatedAt": "上次调度时间",
"pages.searchTable.exception": "请输入异常原因!",
"pages.searchTable.titleOption": "操作",
"pages.searchTable.config": "配置",
"pages.searchTable.subscribeAlert": "订阅警报",
"pages.searchTable.title": "查询表格",
"pages.searchTable.new": "新增",
"pages.searchTable.chosen": "已选择",
"pages.searchTable.item": "项",
"pages.searchTable.totalServiceCalls": "服务调用次数总计",
"pages.searchTable.tenThousand": "万",
"pages.searchTable.batchDeletion": "批量删除",
"pages.searchTable.batchApproval": "批量审批",
};

View File

@@ -1,71 +1,71 @@
export default {
'pages.layouts.userLayout.title':
'Ant Design 是西湖區最具影響力的 Web 設計規範',
'pages.login.accountLogin.tab': '賬戶密碼登錄',
'pages.login.accountLogin.errorMessage':
'錯誤的用戶名和密碼(admin/ant.design)',
'pages.login.failure': '登錄失敗,請重試!',
'pages.login.success': '登錄成功!',
'pages.login.username.placeholder': '用戶名: admin or user',
'pages.login.username.required': '用戶名是必填項!',
'pages.login.password.placeholder': '密碼: ant.design',
'pages.login.password.required': '密碼是必填項!',
'pages.login.phoneLogin.tab': '手機號登錄',
'pages.login.phoneLogin.errorMessage': '驗證碼錯誤',
'pages.login.phoneNumber.placeholder': '請輸入手機號!',
'pages.login.phoneNumber.required': '手機號是必填項!',
'pages.login.phoneNumber.invalid': '不合法的手機號!',
'pages.login.captcha.placeholder': '請輸入驗證碼!',
'pages.login.captcha.required': '驗證碼是必填項!',
'pages.login.phoneLogin.getVerificationCode': '獲取驗證碼',
'pages.getCaptchaSecondText': '秒後重新獲取',
'pages.login.rememberMe': '自動登錄',
'pages.login.forgotPassword': '忘記密碼 ?',
'pages.login.submit': '登錄',
'pages.login.loginWith': '其他登錄方式 :',
'pages.login.registerAccount': '註冊賬戶',
'pages.welcome.link': '歡迎使用',
'pages.welcome.alertMessage': '更快更強的重型組件,已經發布。',
'pages.404.subTitle': '抱歉,您訪問的頁面不存在。',
'pages.404.buttonText': '返回首頁',
'pages.admin.subPage.title': '這個頁面只有 admin 權限才能查看',
'pages.admin.subPage.alertMessage':
'umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。',
'pages.searchTable.createForm.newRule': '新建規則',
'pages.searchTable.updateForm.ruleConfig': '規則配置',
'pages.searchTable.updateForm.basicConfig': '基本信息',
'pages.searchTable.updateForm.ruleName.nameLabel': '規則名稱',
'pages.searchTable.updateForm.ruleName.nameRules': '請輸入規則名稱!',
'pages.searchTable.updateForm.ruleDesc.descLabel': '規則描述',
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '請輸入至少五個字符',
'pages.searchTable.updateForm.ruleDesc.descRules':
'請輸入至少五個字符的規則描述!',
'pages.searchTable.updateForm.ruleProps.title': '配置規則屬性',
'pages.searchTable.updateForm.object': '監控對象',
'pages.searchTable.updateForm.ruleProps.templateLabel': '規則模板',
'pages.searchTable.updateForm.ruleProps.typeLabel': '規則類型',
'pages.searchTable.updateForm.schedulingPeriod.title': '設定調度週期',
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '開始時間',
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '請選擇開始時間!',
'pages.searchTable.titleDesc': '描述',
'pages.searchTable.ruleName': '規則名稱爲必填項',
'pages.searchTable.titleCallNo': '服務調用次數',
'pages.searchTable.titleStatus': '狀態',
'pages.searchTable.nameStatus.default': '關閉',
'pages.searchTable.nameStatus.running': '運行中',
'pages.searchTable.nameStatus.online': '已上線',
'pages.searchTable.nameStatus.abnormal': '異常',
'pages.searchTable.titleUpdatedAt': '上次調度時間',
'pages.searchTable.exception': '請輸入異常原因!',
'pages.searchTable.titleOption': '操作',
'pages.searchTable.config': '配置',
'pages.searchTable.subscribeAlert': '訂閱警報',
'pages.searchTable.title': '查詢表格',
'pages.searchTable.new': '新建',
'pages.searchTable.chosen': '已選擇',
'pages.searchTable.item': '項',
'pages.searchTable.totalServiceCalls': '服務調用次數總計',
'pages.searchTable.tenThousand': '萬',
'pages.searchTable.batchDeletion': '批量刪除',
'pages.searchTable.batchApproval': '批量審批',
"pages.layouts.userLayout.title":
"Ant Design 是西湖區最具影響力的 Web 設計規範",
"pages.login.accountLogin.tab": "賬戶密碼登錄",
"pages.login.accountLogin.errorMessage":
"錯誤的用戶名和密碼(admin/ant.design)",
"pages.login.failure": "登錄失敗,請重試!",
"pages.login.success": "登錄成功!",
"pages.login.username.placeholder": "用戶名: admin or user",
"pages.login.username.required": "用戶名是必填項!",
"pages.login.password.placeholder": "密碼: ant.design",
"pages.login.password.required": "密碼是必填項!",
"pages.login.phoneLogin.tab": "手機號登錄",
"pages.login.phoneLogin.errorMessage": "驗證碼錯誤",
"pages.login.phoneNumber.placeholder": "請輸入手機號!",
"pages.login.phoneNumber.required": "手機號是必填項!",
"pages.login.phoneNumber.invalid": "不合法的手機號!",
"pages.login.captcha.placeholder": "請輸入驗證碼!",
"pages.login.captcha.required": "驗證碼是必填項!",
"pages.login.phoneLogin.getVerificationCode": "獲取驗證碼",
"pages.getCaptchaSecondText": "秒後重新獲取",
"pages.login.rememberMe": "自動登錄",
"pages.login.forgotPassword": "忘記密碼 ?",
"pages.login.submit": "登錄",
"pages.login.loginWith": "其他登錄方式 :",
"pages.login.registerAccount": "註冊賬戶",
"pages.welcome.link": "歡迎使用",
"pages.welcome.alertMessage": "更快更強的重型組件,已經發布。",
"pages.404.subTitle": "抱歉,您訪問的頁面不存在。",
"pages.404.buttonText": "返回首頁",
"pages.admin.subPage.title": "這個頁面只有 admin 權限才能查看",
"pages.admin.subPage.alertMessage":
"umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。",
"pages.searchTable.createForm.newRule": "新建規則",
"pages.searchTable.updateForm.ruleConfig": "規則配置",
"pages.searchTable.updateForm.basicConfig": "基本信息",
"pages.searchTable.updateForm.ruleName.nameLabel": "規則名稱",
"pages.searchTable.updateForm.ruleName.nameRules": "請輸入規則名稱!",
"pages.searchTable.updateForm.ruleDesc.descLabel": "規則描述",
"pages.searchTable.updateForm.ruleDesc.descPlaceholder": "請輸入至少五個字符",
"pages.searchTable.updateForm.ruleDesc.descRules":
"請輸入至少五個字符的規則描述!",
"pages.searchTable.updateForm.ruleProps.title": "配置規則屬性",
"pages.searchTable.updateForm.object": "監控對象",
"pages.searchTable.updateForm.ruleProps.templateLabel": "規則模板",
"pages.searchTable.updateForm.ruleProps.typeLabel": "規則類型",
"pages.searchTable.updateForm.schedulingPeriod.title": "設定調度週期",
"pages.searchTable.updateForm.schedulingPeriod.timeLabel": "開始時間",
"pages.searchTable.updateForm.schedulingPeriod.timeRules": "請選擇開始時間!",
"pages.searchTable.titleDesc": "描述",
"pages.searchTable.ruleName": "規則名稱爲必填項",
"pages.searchTable.titleCallNo": "服務調用次數",
"pages.searchTable.titleStatus": "狀態",
"pages.searchTable.nameStatus.default": "關閉",
"pages.searchTable.nameStatus.running": "運行中",
"pages.searchTable.nameStatus.online": "已上線",
"pages.searchTable.nameStatus.abnormal": "異常",
"pages.searchTable.titleUpdatedAt": "上次調度時間",
"pages.searchTable.exception": "請輸入異常原因!",
"pages.searchTable.titleOption": "操作",
"pages.searchTable.config": "配置",
"pages.searchTable.subscribeAlert": "訂閱警報",
"pages.searchTable.title": "查詢表格",
"pages.searchTable.new": "新增",
"pages.searchTable.chosen": "已選擇",
"pages.searchTable.item": "項",
"pages.searchTable.totalServiceCalls": "服務調用次數總計",
"pages.searchTable.tenThousand": "萬",
"pages.searchTable.batchDeletion": "批量刪除",
"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 {
getTenantPage,
deleteTenant,
createTenant,
type TenantPageReqVO,
type TenantVO,
deleteTenant,
updateTenant,
} 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 {
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";
import { ref } from "process";
import { ActionType, ProColumns } from "@ant-design/pro-components";
import { ToolbarAction } from "@/components/EnhancedProTable/types";
import { Modal, Popconfirm } from "antd";
import { formStatusType } from "@/constants";
import dayjs from "dayjs";
export const waitTimePromise = async (time: number = 90) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const TenantList = () => {
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 configurableDrawerRef = useRef<ConfigurableDrawerFormRef>(null);
const tableRef = useRef<ActionType>(null);
const [type, setType] = useState<"create" | "update">("create");
const [id, setId] = useState<number>(0);
const onFetch = async (
params: TenantPageReqVO & {
pageSize?: number;
current?: number;
}
) => {
await waitTimePromise();
const data = await getTenantPage({
...params,
pageNo: params.current,
pageSize: params.pageSize,
});
console.log(data);
return {
data: data.list,
success: true,
@@ -70,39 +51,89 @@ const TenantList = () => {
};
};
const { actionRef, selectedRowKeys, selectedRows, actions } =
useEnhancedTable<TenantVO>({
onEdit: (record) => {
console.log("编辑订单:", record);
},
onDelete: async (record) => {
await deleteTenant(record.id);
const handleSubmit = useCallback(
async (values: TenantVO) => {
if (type === "create") {
await createTenant(values);
} else {
await updateTenant({
...values,
id,
expireTime: dayjs(values.expireTime).valueOf(),
});
}
tableRef.current?.reload();
return true;
},
});
const tableActions = createCommonActions<TenantVO>({
onView: actions.view,
onEdit: actions.edit,
onDelete: actions.delete,
});
[type]
);
const toolbarActions = createCommonToolbarActions({
onExport: actions.export,
});
const handleAdd = () => {
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 (
<div>
<>
<EnhancedProTable<TenantVO>
ref={tableRef}
columns={columns}
request={onFetch}
actions={tableActions}
toolbarActions={toolbarActions}
headerTitle="订单管理"
headerTitle="租户列表"
showIndex={false}
// showSelection
showSelection={false}
showActions
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);
console.log(getAccessToken());
config.headers!.Authorization = "Bearer " + getAccessToken();
console.log(requestList);
requestList.forEach((cb: any) => {
cb();
});
@@ -154,6 +155,7 @@ export const errorConfig: RequestConfig = {
}
return;
} else {
console.log("刷新令牌失败");
//添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {

View File

@@ -10,7 +10,7 @@ export interface TenantVO {
packageId: number;
username: string;
password: string;
expireTime: Date;
expireTime: Date | number;
accountCount: number;
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 { ProColumns } from "@ant-design/pro-components";
import { Tag } from "antd";
import { ICONS, IconType } from "@/constants/icons";
import { history, useIntl } from "@umijs/max";
import { ICONS, IconType } from "@/constants/antd/icons";
import {
TableAction,
ToolbarAction,
@@ -129,13 +130,18 @@ export const createCommonToolbarActions = (handlers: {
onImport?: () => void;
}): ToolbarAction[] => {
const actions: ToolbarAction[] = [];
if (handlers.onAdd) {
actions.push(
createToolbarAction("add", "新建", "add", handlers.onAdd, {
createToolbarAction(
"add",
useIntl().formatMessage({ id: "pages.searchTable.new" }),
"add",
handlers.onAdd,
{
type: "primary",
permission: "add",
})
}
)
);
}