diff --git a/config/config.ts b/config/config.ts index 7562f8b..84d9977 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,20 +1,20 @@ // https://umijs.org/config/ -import { join } from "node:path"; -import { defineConfig } from "@umijs/max"; -import defaultSettings from "./defaultSettings"; -import proxy from "./proxy"; +import { join } from 'node:path'; +import { defineConfig } from '@umijs/max'; +import defaultSettings from './defaultSettings'; +import proxy from './proxy'; -import routes from "./routes"; +import routes from './routes'; -const { UMI_ENV = "dev" } = process.env; +const { UMI_ENV = 'dev' } = process.env; /** * @name 使用公共路径 * @description 部署时的路径,如果部署在非根目录下,需要配置这个变量 * @doc https://umijs.org/docs/api/config#publicpath */ -const PUBLIC_PATH: string = "/"; +const PUBLIC_PATH: string = '/'; export default defineConfig({ /** @@ -83,7 +83,7 @@ export default defineConfig({ * @name layout 插件 * @doc https://umijs.org/docs/max/layout-menu */ - title: "Ant Design Pro", + title: 'Ant Design Pro', layout: { locale: true, ...defaultSettings, @@ -94,8 +94,8 @@ export default defineConfig({ * @doc https://umijs.org/docs/max/moment2dayjs */ moment2dayjs: { - preset: "antd", - plugins: ["duration"], + preset: 'antd', + plugins: ['duration'], }, /** * @name 国际化插件 @@ -103,7 +103,7 @@ export default defineConfig({ */ locale: { // default zh-CN - default: "zh-CN", + default: 'zh-CN', antd: true, // default true, when it is true, will use `navigator.language` overwrite default baseNavigator: true, @@ -119,7 +119,7 @@ export default defineConfig({ theme: { cssVar: true, token: { - fontFamily: "AlibabaSans, sans-serif", + fontFamily: 'AlibabaSans, sans-serif', }, }, }, @@ -142,30 +142,33 @@ export default defineConfig({ */ headScripts: [ // 解决首次加载时白屏的问题 - { src: join(PUBLIC_PATH, "scripts/loading.js"), async: true }, + { src: join(PUBLIC_PATH, 'scripts/loading.js'), async: true }, ], //================ pro 插件配置 ================= - presets: ["umi-presets-pro"], + presets: ['umi-presets-pro'], /** * @name openAPI 插件的配置 * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码 * @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", - // }, - // {schemaPath: "./docs/apifox-api.json", - // requestLibPath: "import { request } from '@umijs/max'", - // schemaPath: join(__dirname, "oneapi.json"), - // projectName: "login", - // }, - ], + // openAPI: [ + // // { + // // 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"), + // // projectName: "login", + // // }, + // ], + codeSplitting: { + jsStrategy: 'granularChunks', + }, mock: { - include: ["mock/**/*", "src/pages/**/_mock.ts"], + include: ['mock/**/*', 'src/pages/**/_mock.ts'], }, /** * @name 是否开启 mako @@ -177,6 +180,6 @@ export default defineConfig({ requestRecord: {}, exportStatic: {}, define: { - "process.env.CI": process.env.CI, + 'process.env.CI': process.env.CI, }, }); diff --git a/package.json b/package.json index a863fba..70e8a83 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "scripts": { "analyze": "cross-env ANALYZE=1 max build", "build": "max build", - "deploy": "npm run build && npm run gh-pages", - "dev": "npm run start:dev", + "deploy": "pnpm run build && npm run gh-pages", + "dev": "pnpm run start:dev", "gh-pages": "gh-pages -d dist", "i18n-remove": "pro i18n-remove --locale=zh-CN --write", "postinstall": "max setup", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1b4835..c3b842d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14075,14 +14075,14 @@ snapshots: '@loadable/component@5.15.2(react@18.3.1)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.28.4 hoist-non-react-statics: 3.3.2 react: 18.3.1 react-is: 16.13.1 '@loadable/component@5.15.2(react@19.1.1)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.28.4 hoist-non-react-statics: 3.3.2 react: 19.1.1 react-is: 16.13.1 @@ -15402,7 +15402,7 @@ snapshots: '@umijs/history@5.3.1': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.28.4 query-string: 6.14.1 '@umijs/lint@4.4.12(eslint@8.35.0)(jest@30.1.3(@types/node@24.3.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)))(stylelint@14.8.2)(typescript@5.9.2)': @@ -16597,7 +16597,7 @@ snapshots: babel-plugin-macros@2.6.1: dependencies: - '@babel/runtime': 7.4.5 + '@babel/runtime': 7.28.4 cosmiconfig: 5.2.1 resolve: 1.22.10 @@ -23257,7 +23257,7 @@ snapshots: react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.28.4 invariant: 2.2.4 prop-types: 15.8.1 react: 18.3.1 @@ -23267,7 +23267,7 @@ snapshots: react-helmet-async@1.3.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.28.4 invariant: 2.2.4 prop-types: 15.8.1 react: 19.1.1 diff --git a/src/components/EnhancedProTable/index.tsx b/src/components/EnhancedProTable/index.tsx index 81d7787..a601df2 100644 --- a/src/components/EnhancedProTable/index.tsx +++ b/src/components/EnhancedProTable/index.tsx @@ -1,36 +1,37 @@ // components/EnhancedProTable/EnhancedProTable.tsx + +import { PlusOutlined } from '@ant-design/icons'; +import { + type ActionType, + type ParamsType, + ProColumns, + ProTable, + TableDropdown, +} from '@ant-design/pro-components'; +import { Button, Space } from 'antd'; import React, { - useRef, - useState, - useCallback, - useMemo, act, forwardRef, -} 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"; + useCallback, + useMemo, + useRef, + useState, +} from 'react'; import { buildTableDropdownMenuItems, - handleTableDropdownSelect, formatPaginationTotal, -} from "@/utils/antd/tableHelpers"; -import { PlusOutlined } from "@ant-design/icons"; + handleTableDropdownSelect, +} from '@/utils/antd/tableHelpers'; +import { + type BaseRecord, + type EnhancedProTableProps, + TableAction, + ToolbarAction, +} from './types'; function EnhancedProTable( props: EnhancedProTableProps, - ref: React.Ref | undefined + ref: React.Ref | undefined, ) { const { columns, @@ -87,7 +88,7 @@ function EnhancedProTable( ); }, - [showSelection] + [showSelection], ); const toolBarRender = useCallback( @@ -96,7 +97,7 @@ function EnhancedProTable( rows: { selectedRowKeys?: (string | number)[] | undefined; selectedRows?: T[] | undefined; - } + }, ) => { const toolbarElements = toolbarActions?.map((action) => { @@ -125,7 +126,7 @@ function EnhancedProTable( // ]; return toolbarElements; }, - [toolbarActions] + [toolbarActions], ); return ( @@ -140,9 +141,9 @@ function EnhancedProTable( showSorterTooltip tableAlertRender={tableAlertRender} // tableAlertOptionRender={tableAlertOptionRender} - scroll={{ x: "max-content" }} + scroll={{ x: 'max-content' }} search={{ - labelWidth: "auto", + labelWidth: 'auto', defaultCollapsed: false, ...restProps.search, }} @@ -156,6 +157,7 @@ function EnhancedProTable( pagination={{ showSizeChanger: true, showQuickJumper: true, + pageSize: 10, showTotal: formatPaginationTotal, ...restProps.pagination, }} @@ -165,7 +167,7 @@ function EnhancedProTable( export default forwardRef(EnhancedProTable) as < T extends BaseRecord, - U extends ParamsType = any + U extends ParamsType = any, >( - props: EnhancedProTableProps & { ref?: React.Ref } + props: EnhancedProTableProps & { ref?: React.Ref }, ) => React.ReactElement; diff --git a/src/components/ModalDescriptions/index.tsx b/src/components/ModalDescriptions/index.tsx new file mode 100644 index 0000000..33c2c52 --- /dev/null +++ b/src/components/ModalDescriptions/index.tsx @@ -0,0 +1,72 @@ +import { + ProDescriptions, + type ProDescriptionsItemProps, +} from '@ant-design/pro-components'; +import { type DescriptionsProps, Modal } from 'antd'; +import React, { + forwardRef, + useCallback, + useImperativeHandle, + useState, +} from 'react'; +export interface DescriptionsFormRef { + open: (data?: Record) => void; + close: () => void; +} + +interface Props { + columns: ProDescriptionsItemProps, 'text'>[]; + title: string; +} + +const ModalDescriptions = forwardRef((props: Props, ref) => { + const { columns, title } = props; + const [visible, setVisible] = useState(false); + const [data, setData] = useState([]); + useImperativeHandle(ref, () => ({ + open: (data: DescriptionsProps['items']) => { + console.log(data); + if (data) { + setData(data); + } + setVisible(true); + }, + close: () => setVisible(false), + })); + + const changeVisible = useCallback( + (flag: boolean) => { + setVisible(flag); + }, + [visible], + ); + + return ( + changeVisible(false)} + footer={null} + > + + {/* */} + + ); +}); + +export default React.memo(ModalDescriptions); diff --git a/src/pages/system/log/operate/config.tsx b/src/pages/system/log/operate/config.tsx new file mode 100644 index 0000000..88ccb42 --- /dev/null +++ b/src/pages/system/log/operate/config.tsx @@ -0,0 +1,134 @@ +import { + type ProColumns, + type ProDescriptionsItemProps, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { DescriptionsProps } from 'antd'; +import dayjs from 'dayjs'; +import type { OperateLogVO } from '@/services/system/log/operate'; +export const baseTenantColumns: ProColumns[] = [ + { + title: '日志编号', + dataIndex: 'id', + tip: '日志编号', + width: 100, + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '操作人', + dataIndex: 'userName', + tip: '操作人', // 提示信息 + }, + { + title: '操作模块', + dataIndex: 'type', + }, + { + title: '操作名', + dataIndex: 'subType', + }, + { + title: '操作内容', + dataIndex: 'action', + }, + + { + title: '操作时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + [`createTime[0]`]: dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + [`createTime[1]`]: dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: OperateLogVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, + { + title: '业务编号', + dataIndex: 'bizId', + }, + { + title: '操作 IP', + dataIndex: 'userIp', + hideInSearch: true, + }, +]; + +export const descriptionsColumns = (): ProDescriptionsItemProps< + Record, + 'text' +>[] => [ + { + title: '日志主键', + key: 'id', + dataIndex: 'id', + }, + { + title: '链路追踪', + key: 'traceId', + dataIndex: 'traceId', + }, + { + title: '操作人编号', + key: 'userId', + dataIndex: 'userId', + }, + { + title: '操作人名字', + key: 'userName', + dataIndex: 'userName', + }, + { + title: '操作人 IP', + key: 'userIp', + dataIndex: 'userIp', + }, + { + title: '操作人 UA', + key: 'userAgent', + dataIndex: 'userAgent', + }, + { + title: '操作模块', + key: 'type', + dataIndex: 'type', + }, + { + title: '操作名', + key: 'subType', + dataIndex: 'subType', + }, + { + title: '操作内容', + key: 'action', + dataIndex: 'action', + }, + { + title: '操作拓展参数', + key: 'extra', + dataIndex: 'extra', + }, + { + title: '请求 URL', + key: 'requestUrl', + dataIndex: 'requestUrl', + }, + { + title: '操作时间', + key: 'createTime', + dataIndex: 'createTime', + }, + { + title: '业务编号', + key: 'bizId', + dataIndex: 'bizId', + }, +]; diff --git a/src/pages/system/log/operate/index.tsx b/src/pages/system/log/operate/index.tsx index 45fe9f8..a92ee0c 100644 --- a/src/pages/system/log/operate/index.tsx +++ b/src/pages/system/log/operate/index.tsx @@ -1,5 +1,70 @@ +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import React, { useRef } from 'react'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import ModalDescriptions, { + type DescriptionsFormRef, +} from '@/components/ModalDescriptions'; +import { + getOperateLogPage, + type OperateLogVO, +} from '@/services/system/log/operate'; +import { baseTenantColumns, descriptionsColumns } from './config'; + const SyStemLogOperate = () => { - return <>SyStemLogOperate; + const tableRef = useRef(null); + const descriptionsRef = useRef(null); + const onFetch = async ( + params: OperateLogVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getOperateLogPage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleDetail = (record: OperateLogVO) => { + descriptionsRef.current?.open(record); + }; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 80, + render: (text: React.ReactNode, record: OperateLogVO) => [ + handleDetail(record)}> + 详情 + , + ], + }; + const columns = [...baseTenantColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + headerTitle="操作日志" + showIndex={false} + showSelection={false} + /> + + + ); }; -export default SyStemLogOperate; +export default React.memo(SyStemLogOperate); diff --git a/src/pages/system/tenant/list/config.tsx b/src/pages/system/tenant/list/config.tsx index 276810c..e61c262 100644 --- a/src/pages/system/tenant/list/config.tsx +++ b/src/pages/system/tenant/list/config.tsx @@ -1,33 +1,37 @@ -import { type TenantVO, deleteTenant } from "@/services/system/tenant/list"; -import { ProColumns, ProFormColumnsType } from "@ant-design/pro-components"; -import { DatePicker, Modal, Popconfirm } from "antd"; -import { FormInstance } from "antd/lib"; -import dayjs from "dayjs"; +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { DatePicker, Modal, Popconfirm } from 'antd'; +import { FormInstance } from 'antd/lib'; +import dayjs from 'dayjs'; +import { deleteTenant, type TenantVO } from '@/services/system/tenant/list'; +import { getTenantPackageList } from '@/services/system/tenant/package'; export const baseTenantColumns: ProColumns[] = [ { - title: "租户编号", - dataIndex: "id", - tip: "租户编号", + title: '租户编号', + dataIndex: 'id', + tip: '租户编号', width: 100, hideInSearch: true, // 在搜索表单中隐藏 }, { - title: "租户名", - dataIndex: "name", - tip: "租户名", // 提示信息 + title: '租户名', + dataIndex: 'name', + tip: '租户名', // 提示信息 }, { - title: "租户套餐", - dataIndex: "packageId", - valueType: "select", + title: '租户套餐', + dataIndex: 'packageId', + valueType: 'select', + hideInSearch: true, // 在搜索表单中隐藏 request: async () => { - return [ - { - label: "默认套餐", - value: 1, - }, - ]; + const packageList: { id: number; name: string }[] = + await getTenantPackageList(); + console.log(packageList); + packageList.map((item) => ({ label: item.name, value: item.id })); + return packageList.map((item) => ({ label: item.name, value: item.id })); }, // valueEnum: { // all: { text: "全部", status: "Default" }, @@ -36,60 +40,67 @@ export const baseTenantColumns: ProColumns[] = [ // }, }, { - title: "联系人", - dataIndex: "contactName", + title: '联系人', + dataIndex: 'contactName', }, { - title: "联系手机", - dataIndex: "contactMobile", + title: '联系手机', + dataIndex: 'contactMobile', }, { - title: "账号额度", - dataIndex: "accountCount", + title: '账号额度', + dataIndex: 'accountCount', hideInSearch: true, // 在搜索表单中隐藏 }, { - title: "过期时间", - dataIndex: "expireTime", - valueType: "dateTime", + title: '过期时间', + dataIndex: 'expireTime', + valueType: 'dateTime', hideInSearch: true, // 在搜索表单中隐藏 }, - { title: "绑定域名", dataIndex: "website", width: 100 }, + { title: '绑定域名', dataIndex: 'website', width: 100, hideInSearch: true }, { - title: "租户状态", - dataIndex: "status", - valueType: "select", - valueEnum: { - all: { text: "全部", status: "Default" }, - open: { text: "未解决", status: "Error" }, - closed: { text: "已解决", status: "Success" }, + title: '租户状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + options: [ + { + label: '开启', + value: 0, + }, + { + label: '关闭', + value: 1, + }, + ], }, }, { - title: "创建时间", - dataIndex: "createTime", - valueType: "dateRange", + 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"), + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), }, ]; export const formColumns = (type: string): ProFormColumnsType[] => [ { - title: "租户名", - dataIndex: "name", - tip: "租户名", // 提示信息 + title: '租户名', + dataIndex: 'name', + tip: '租户名', // 提示信息 formItemProps: { rules: [ { required: true, - message: "请输入用户名", + message: '请输入用户名', }, // { // min: 2, @@ -100,121 +111,121 @@ export const formColumns = (type: string): ProFormColumnsType[] => [ }, }, { - title: "租户套餐", - dataIndex: "packageId", - valueType: "select", + title: '租户套餐', + dataIndex: 'packageId', + valueType: 'select', formItemProps: { rules: [ { required: true, - message: "请选择租户套餐", + message: '请选择租户套餐', }, ], }, fieldProps: { - placeholder: "请选择套餐类型", + placeholder: '请选择套餐类型', options: [ { - label: "普通套餐", + label: '普通套餐', value: 111, }, ], }, }, { - title: "联系人", - dataIndex: "contactName", + title: '联系人', + dataIndex: 'contactName', }, { - title: "联系手机", - dataIndex: "contactMobile", + title: '联系手机', + dataIndex: 'contactMobile', formItemProps: { rules: [ { required: true, - message: "请输入联系手机", + message: '请输入联系手机', }, ], }, }, { - title: "用户名称", - dataIndex: "username", - hideInForm: type === "update", + title: '用户名称', + dataIndex: 'username', + hideInForm: type === 'update', formItemProps: { rules: [ { required: true, - message: "请输入用户名称", + message: '请输入用户名称', }, { pattern: /^[a-zA-Z0-9]+$/, - message: "用户账号由 0-9、a-z、A-Z 组成", + message: '用户账号由 0-9、a-z、A-Z 组成', }, // 用户账号由 数字、字母组成 ], }, }, { - title: "用户密码", - dataIndex: "password", - valueType: "password", - hideInForm: type === "update", + title: '用户密码', + dataIndex: 'password', + valueType: 'password', + hideInForm: type === 'update', fieldProps: { - placeholder: "请输入用户密码", - autoComplete: "new-password", + placeholder: '请输入用户密码', + autoComplete: 'new-password', }, formItemProps: { rules: [ { required: true, - message: "请输入用户密码", + message: '请输入用户密码', }, { min: 4, max: 16, - message: "密码长度为4-16个字符", + message: '密码长度为4-16个字符', }, ], }, }, { - title: "账号额度", - dataIndex: "accountCount", - valueType: "digit", + title: '账号额度', + dataIndex: 'accountCount', + valueType: 'digit', }, { - title: "过期时间", - dataIndex: "expireTime", - valueType: "date", + title: '过期时间', + dataIndex: 'expireTime', + valueType: 'date', fieldProps: { - placeholder: "请选择过期时间", - format: "YYYY-MM-DD", + placeholder: '请选择过期时间', + format: 'YYYY-MM-DD', }, }, - { title: "绑定域名", dataIndex: "website" }, + { title: '绑定域名', dataIndex: 'website' }, { - title: "租户状态", - dataIndex: "status", - valueType: "radio", + title: '租户状态', + dataIndex: 'status', + valueType: 'radio', formItemProps: { rules: [ { required: true, - message: "请选择租户状态", + message: '请选择租户状态', }, ], }, fieldProps: { - placeholder: "请选择套餐类型", + placeholder: '请选择套餐类型', options: [ { - label: "启用", + label: '启用', value: 1, }, { - label: "禁用", + label: '禁用', value: 0, }, ], diff --git a/src/services/system/log/operate.ts b/src/services/system/log/operate.ts new file mode 100644 index 0000000..8c269f1 --- /dev/null +++ b/src/services/system/log/operate.ts @@ -0,0 +1,37 @@ +import { request } from "@umijs/max"; + +export type OperateLogVO = { + id: number; + traceId: string; + userType: number; + userId: number; + userName: string; + type: string; + subType: string; + bizId: number; + action: string; + extra: string; + requestMethod: string; + requestUrl: string; + userIp: string; + userAgent: string; + creator: string; + creatorName: string; + createTime: Date; +}; + +// 查询操作日志列表 +// export const getOperateLogPage = (params: PageParam) => { +// return request.get({ url: '/system/operate-log/page', params }) +// } + +export async function getOperateLogPage(params: PageParam) { + return request("/system/operate-log/page", { + method: "GET", + params, + }); +} +// // 导出操作日志 +// export const exportOperateLog = (params: any) => { +// return request.download({ url: "/system/operate-log/export", params }); +// };