'init_again'
Change-Id: Ib7ecdb9f5baeab1e4681152a57b936edf7475b35
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7e3649a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..9a071ec
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,8 @@
+/lambda/
+/scripts
+/config
+.history
+public
+dist
+.umi
+mock
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..3ac39ef
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ extends: [require.resolve('@umijs/lint/dist/config/eslint')],
+ globals: {
+ page: true,
+ REACT_APP_ENV: true,
+ },
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e59017b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+
+# dependencies
+**/node_modules
+# roadhog-api-doc ignore
+/src/utils/request-temp.js
+_roadhog-api-doc
+
+# production
+/dist
+
+# misc
+.DS_Store
+npm-debug.log*
+yarn-error.log
+
+/coverage
+.idea
+yarn.lock
+package-lock.json
+*bak
+.vscode
+
+
+# visual studio code
+.history
+*.log
+functions/*
+.temp/**
+
+# umi
+.umi
+.umi-production
+.umi-test
+
+# screenshot
+screenshot
+.firebase
+.eslintcache
+
+pnpm-lock.yaml
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..b771879
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,20 @@
+**/*.svg
+.umi
+.umi-production
+/dist
+.dockerignore
+.DS_Store
+.eslintignore
+*.png
+*.toml
+docker
+.editorconfig
+Dockerfile*
+.gitignore
+.prettierignore
+LICENSE
+.eslintcache
+*.lock
+yarn-error.log
+.history
+/public
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..3447a1a
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,21 @@
+module.exports = {
+ singleQuote: true,
+ trailingComma: 'all',
+ printWidth: 100,
+ proseWrap: 'never',
+ endOfLine: 'lf',
+ overrides: [
+ {
+ files: '.prettierrc',
+ options: {
+ parser: 'json',
+ },
+ },
+ {
+ files: 'document.ejs',
+ options: {
+ parser: 'html',
+ },
+ },
+ ],
+};
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2b4571c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+- The use of sexualized language or imagery and unwelcome sexual attention or advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at afc163@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..25fa795
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+雷霆星际
+ThunderHub 基于BT下载协议的私有电子资源分享平台
+hh
diff --git a/config/config.ts b/config/config.ts
new file mode 100644
index 0000000..f0004a8
--- /dev/null
+++ b/config/config.ts
@@ -0,0 +1,156 @@
+// https://umijs.org/config/
+import { defineConfig } from '@umijs/max';
+import { join } from 'path';
+import defaultSettings from './defaultSettings';
+import proxy from './proxy';
+import routes from './routes';
+
+const { REACT_APP_ENV = 'dev' } = process.env;
+
+export default defineConfig({
+ /**
+ * @name 开启 hash 模式
+ * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
+ * @doc https://umijs.org/docs/api/config#hash
+ */
+ hash: true,
+
+ /**
+ * @name 兼容性设置
+ * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
+ * @doc https://umijs.org/docs/api/config#targets
+ */
+ // targets: {
+ // ie: 11,
+ // },
+ /**
+ * @name 路由的配置,不在路由中引入的文件不会编译
+ * @description 只支持 path,component,routes,redirect,wrappers,title 的配置
+ * @doc https://umijs.org/docs/guides/routes
+ */
+ // umi routes: https://umijs.org/docs/routing
+ routes,
+ /**
+ * @name 主题的配置
+ * @description 虽然叫主题,但是其实只是 less 的变量设置
+ * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
+ * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
+ */
+ theme: {
+ // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
+ // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
+ 'root-entry-name': 'variable',
+ },
+ /**
+ * @name moment 的国际化配置
+ * @description 如果对国际化没有要求,打开之后能减少js的包大小
+ * @doc https://umijs.org/docs/api/config#ignoremomentlocale
+ */
+ ignoreMomentLocale: true,
+ /**
+ * @name 代理配置
+ * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
+ * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
+ * @doc 代理介绍 https://umijs.org/docs/guides/proxy
+ * @doc 代理配置 https://umijs.org/docs/api/config#proxy
+ */
+ proxy: proxy[REACT_APP_ENV as keyof typeof proxy],
+ /**
+ * @name 快速热更新配置
+ * @description 一个不错的热更新组件,更新时可以保留 state
+ */
+ fastRefresh: true,
+ //============== 以下都是max的插件配置 ===============
+ /**
+ * @name 数据流插件
+ * @@doc https://umijs.org/docs/max/data-flow
+ */
+ model: {},
+ /**
+ * 一个全局的初始数据流,可以用它在插件之间共享数据
+ * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
+ * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
+ */
+ initialState: {},
+ /**
+ * @name layout 插件
+ * @doc https://umijs.org/docs/max/layout-menu
+ */
+ title: 'Ant Design Pro',
+ layout: {
+ locale: true,
+ ...defaultSettings,
+ },
+ /**
+ * @name moment2dayjs 插件
+ * @description 将项目中的 moment 替换为 dayjs
+ * @doc https://umijs.org/docs/max/moment2dayjs
+ */
+ moment2dayjs: {
+ preset: 'antd',
+ plugins: ['duration'],
+ },
+ /**
+ * @name 国际化插件
+ * @doc https://umijs.org/docs/max/i18n
+ */
+ locale: {
+ // default zh-CN
+ default: 'zh-CN',
+ antd: true,
+ // default true, when it is true, will use `navigator.language` overwrite default
+ baseNavigator: true,
+ },
+ /**
+ * @name antd 插件
+ * @description 内置了 babel import 插件
+ * @doc https://umijs.org/docs/max/antd#antd
+ */
+ antd: {},
+ /**
+ * @name 网络请求配置
+ * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+ * @doc https://umijs.org/docs/max/request
+ */
+ request: {},
+ /**
+ * @name 权限插件
+ * @description 基于 initialState 的权限插件,必须先打开 initialState
+ * @doc https://umijs.org/docs/max/access
+ */
+ access: {},
+ /**
+ * @name <head> 中额外的 script
+ * @description 配置 <head> 中额外的 script
+ */
+ headScripts: [
+ // 解决首次加载时白屏的问题
+ { src: '/scripts/loading.js', async: true },
+ ],
+ //================ 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: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
+ schemaPath: join(__dirname, 'oneapi.json'),
+ mock: false,
+ },
+ {
+ requestLibPath: "import { request } from '@umijs/max'",
+ schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
+ projectName: 'swagger',
+ },
+ ],
+ mfsu: {
+ strategy: 'normal',
+ },
+ esbuildMinifyIIFE: true,
+ requestRecord: {},
+});
diff --git a/config/defaultSettings.ts b/config/defaultSettings.ts
new file mode 100644
index 0000000..2796a7e
--- /dev/null
+++ b/config/defaultSettings.ts
@@ -0,0 +1,28 @@
+import { ProLayoutProps } from '@ant-design/pro-components';
+
+/**
+ * @name
+ */
+const Settings: ProLayoutProps & {
+ pwa?: boolean;
+ logo?: string;
+} = {
+ navTheme: 'light',
+ // 拂晓蓝
+ colorPrimary: '#1890ff',
+ layout: 'mix',
+ contentWidth: 'Fluid',
+ fixedHeader: false,
+ fixSiderbar: true,
+ colorWeak: false,
+ title: 'Ant Design Pro',
+ pwa: true,
+ logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
+ iconfontUrl: '',
+ token: {
+ // 参见ts声明,demo 见文档,通过token 修改样式
+ //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
+ },
+};
+
+export default Settings;
diff --git a/config/oneapi.json b/config/oneapi.json
new file mode 100644
index 0000000..c77d988
--- /dev/null
+++ b/config/oneapi.json
@@ -0,0 +1,593 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "Ant Design Pro",
+ "version": "1.0.0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8000/"
+ },
+ {
+ "url": "https://localhost:8000/"
+ }
+ ],
+ "paths": {
+ "/api/currentUser": {
+ "get": {
+ "tags": ["api"],
+ "description": "获取当前的用户",
+ "operationId": "currentUser",
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CurrentUser"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-swagger-router-controller": "api"
+ },
+ "/api/login/captcha": {
+ "post": {
+ "description": "发送验证码",
+ "operationId": "getFakeCaptcha",
+ "tags": ["login"],
+ "parameters": [
+ {
+ "name": "phone",
+ "in": "query",
+ "description": "手机号",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FakeCaptcha"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/login/outLogin": {
+ "post": {
+ "description": "登录接口",
+ "operationId": "outLogin",
+ "tags": ["login"],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-swagger-router-controller": "api"
+ },
+ "/api/login/account": {
+ "post": {
+ "tags": ["login"],
+ "description": "登录接口",
+ "operationId": "login",
+ "requestBody": {
+ "description": "登录系统",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LoginParams"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LoginResult"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ },
+ "x-codegen-request-body-name": "body"
+ },
+ "x-swagger-router-controller": "api"
+ },
+ "/api/notices": {
+ "summary": "getNotices",
+ "description": "NoticeIconItem",
+ "get": {
+ "tags": ["api"],
+ "operationId": "getNotices",
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/NoticeIconList"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/rule": {
+ "get": {
+ "tags": ["rule"],
+ "description": "获取规则列表",
+ "operationId": "rule",
+ "parameters": [
+ {
+ "name": "current",
+ "in": "query",
+ "description": "当前的页码",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "pageSize",
+ "in": "query",
+ "description": "页面的容量",
+ "schema": {
+ "type": "number"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RuleList"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": ["rule"],
+ "description": "新建规则",
+ "operationId": "addRule",
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RuleListItem"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": ["rule"],
+ "description": "新建规则",
+ "operationId": "updateRule",
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RuleListItem"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": ["rule"],
+ "description": "删除规则",
+ "operationId": "removeRule",
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-swagger-router-controller": "api"
+ },
+ "/swagger": {
+ "x-swagger-pipe": "swagger_raw"
+ }
+ },
+ "components": {
+ "schemas": {
+ "CurrentUser": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "avatar": {
+ "type": "string"
+ },
+ "userid": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "signature": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "group": {
+ "type": "string"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "notifyCount": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "unreadCount": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "country": {
+ "type": "string"
+ },
+ "access": {
+ "type": "string"
+ },
+ "geographic": {
+ "type": "object",
+ "properties": {
+ "province": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ }
+ }
+ },
+ "city": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "address": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ }
+ }
+ },
+ "LoginResult": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "currentAuthority": {
+ "type": "string"
+ }
+ }
+ },
+ "PageParams": {
+ "type": "object",
+ "properties": {
+ "current": {
+ "type": "number"
+ },
+ "pageSize": {
+ "type": "number"
+ }
+ }
+ },
+ "RuleListItem": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "href": {
+ "type": "string"
+ },
+ "avatar": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "owner": {
+ "type": "string"
+ },
+ "desc": {
+ "type": "string"
+ },
+ "callNo": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "status": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "datetime"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "datetime"
+ },
+ "progress": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ },
+ "RuleList": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RuleListItem"
+ }
+ },
+ "total": {
+ "type": "integer",
+ "description": "列表的内容总数",
+ "format": "int32"
+ },
+ "success": {
+ "type": "boolean"
+ }
+ }
+ },
+ "FakeCaptcha": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "status": {
+ "type": "string"
+ }
+ }
+ },
+ "LoginParams": {
+ "type": "object",
+ "properties": {
+ "username": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "autoLogin": {
+ "type": "boolean"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "ErrorResponse": {
+ "required": ["errorCode"],
+ "type": "object",
+ "properties": {
+ "errorCode": {
+ "type": "string",
+ "description": "业务约定的错误码"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "业务上的错误信息"
+ },
+ "success": {
+ "type": "boolean",
+ "description": "业务上的请求是否成功"
+ }
+ }
+ },
+ "NoticeIconList": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NoticeIconItem"
+ }
+ },
+ "total": {
+ "type": "integer",
+ "description": "列表的内容总数",
+ "format": "int32"
+ },
+ "success": {
+ "type": "boolean"
+ }
+ }
+ },
+ "NoticeIconItemType": {
+ "title": "NoticeIconItemType",
+ "description": "已读未读列表的枚举",
+ "type": "string",
+ "properties": {},
+ "enum": ["notification", "message", "event"]
+ },
+ "NoticeIconItem": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "extra": {
+ "type": "string",
+ "format": "any"
+ },
+ "key": { "type": "string" },
+ "read": {
+ "type": "boolean"
+ },
+ "avatar": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ },
+ "datetime": {
+ "type": "string",
+ "format": "date"
+ },
+ "description": {
+ "type": "string"
+ },
+ "type": {
+ "extensions": {
+ "x-is-enum": true
+ },
+ "$ref": "#/components/schemas/NoticeIconItemType"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/proxy.ts b/config/proxy.ts
new file mode 100644
index 0000000..c290c6a
--- /dev/null
+++ b/config/proxy.ts
@@ -0,0 +1,49 @@
+/**
+ * @name 代理的配置
+ * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
+ * -------------------------------
+ * The agent cannot take effect in the production environment
+ * so there is no configuration of the production environment
+ * For details, please see
+ * https://pro.ant.design/docs/deploy
+ *
+ * @doc https://umijs.org/docs/guides/proxy
+ */
+export default {
+ // 如果需要自定义本地开发服务器 请取消注释按需调整
+ dev: {
+ // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+ '/api/': {
+ // 要代理的地址
+ target: 'http://localhost:8080',
+ // 配置了这个可以从 http 代理到 https
+ // 依赖 origin 的功能可能需要这个,比如 cookie
+ changeOrigin: true,
+ pathRewrite: { '^/api': '' },
+ },
+ '/profile/avatar/': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ }
+ },
+
+ /**
+ * @name 详细的代理配置
+ * @doc https://github.com/chimurai/http-proxy-middleware
+ */
+ test: {
+ // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
+ '/api/': {
+ target: 'https://proapi.azurewebsites.net',
+ changeOrigin: true,
+ pathRewrite: { '^': '' },
+ },
+ },
+ pre: {
+ '/api/': {
+ target: 'your pre url',
+ changeOrigin: true,
+ pathRewrite: { '^': '' },
+ },
+ },
+};
diff --git a/config/routes.ts b/config/routes.ts
new file mode 100644
index 0000000..ffad02f
--- /dev/null
+++ b/config/routes.ts
@@ -0,0 +1,92 @@
+/**
+ * @name umi 的路由配置
+ * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置
+ * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
+ * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。
+ * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。
+ * @param redirect 配置路由跳转
+ * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验
+ * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题
+ * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User
+ * @doc https://umijs.org/docs/guides/routes
+ */
+export default [
+ {
+ path: '/',
+ redirect: '/account/center',
+ },
+ {
+ path: '*',
+ layout: false,
+ component: './404',
+ },
+ {
+ path: '/user',
+ layout: false,
+ routes: [
+ {
+ name: 'login',
+ path: '/user/login',
+ component: './User/Login',
+ },
+ ],
+ },
+ {
+ path: '/account',
+ routes: [
+ {
+ name: 'acenter',
+ path: '/account/center',
+ component: './User/Center',
+ },
+ {
+ name: 'asettings',
+ path: '/account/settings',
+ component: './User/Settings',
+ },
+ ],
+ },
+ {
+ name: 'system',
+ path: '/system',
+ routes: [
+ {
+ name: '字典数据',
+ path: '/system/dict-data/index/:id',
+ component: './System/DictData',
+ },
+ {
+ name: '分配用户',
+ path: '/system/role-auth/user/:id',
+ component: './System/Role/authUser',
+ },
+ ]
+ },
+ {
+ name: 'monitor',
+ path: '/monitor',
+ routes: [
+ {
+ name: '任务日志',
+ path: '/monitor/job-log/index/:id',
+ component: './Monitor/JobLog',
+ },
+ ]
+ },
+ {
+ name: 'tool',
+ path: '/tool',
+ routes: [
+ {
+ name: '导入表',
+ path: '/tool/gen/import',
+ component: './Tool/Gen/import',
+ },
+ {
+ name: '编辑表',
+ path: '/tool/gen/edit',
+ component: './Tool/Gen/edit',
+ },
+ ]
+ },
+];
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 0000000..1de2a1a
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,23 @@
+import { configUmiAlias, createConfig } from '@umijs/max/test';
+
+export default async () => {
+ const config = await configUmiAlias({
+ ...createConfig({
+ target: 'browser',
+ }),
+ });
+
+ console.log();
+ return {
+ ...config,
+ testEnvironmentOptions: {
+ ...(config?.testEnvironmentOptions || {}),
+ url: 'http://localhost:8000',
+ },
+ setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'],
+ globals: {
+ ...config.globals,
+ localStorage: null,
+ },
+ };
+};
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..197bee5
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/mock/listTableList.ts b/mock/listTableList.ts
new file mode 100644
index 0000000..35ec3ce
--- /dev/null
+++ b/mock/listTableList.ts
@@ -0,0 +1,176 @@
+import { Request, Response } from 'express';
+import moment from 'moment';
+import { parse } from 'url';
+
+// mock tableListDataSource
+const genList = (current: number, pageSize: number) => {
+ const tableListDataSource: API.RuleListItem[] = [];
+
+ for (let i = 0; i < pageSize; i += 1) {
+ const index = (current - 1) * 10 + i;
+ tableListDataSource.push({
+ key: index,
+ disabled: i % 6 === 0,
+ href: 'https://ant.design',
+ avatar: [
+ 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ ][i % 2],
+ name: `TradeCode ${index}`,
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: Math.floor(Math.random() * 1000),
+ status: Math.floor(Math.random() * 10) % 4,
+ updatedAt: moment().format('YYYY-MM-DD'),
+ createdAt: moment().format('YYYY-MM-DD'),
+ progress: Math.ceil(Math.random() * 100),
+ });
+ }
+ tableListDataSource.reverse();
+ return tableListDataSource;
+};
+
+let tableListDataSource = genList(1, 100);
+
+function getRule(req: Request, res: Response, u: string) {
+ let realUrl = u;
+ if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
+ realUrl = req.url;
+ }
+ const { current = 1, pageSize = 10 } = req.query;
+ const params = parse(realUrl, true).query as unknown as API.PageParams &
+ API.RuleListItem & {
+ sorter: any;
+ filter: any;
+ };
+
+ let dataSource = [...tableListDataSource].slice(
+ ((current as number) - 1) * (pageSize as number),
+ (current as number) * (pageSize as number),
+ );
+ if (params.sorter) {
+ const sorter = JSON.parse(params.sorter);
+ dataSource = dataSource.sort((prev, next) => {
+ let sortNumber = 0;
+ (Object.keys(sorter) as Array<keyof API.RuleListItem>).forEach((key) => {
+ let nextSort = next?.[key] as number;
+ let preSort = prev?.[key] as number;
+ if (sorter[key] === 'descend') {
+ if (preSort - nextSort > 0) {
+ sortNumber += -1;
+ } else {
+ sortNumber += 1;
+ }
+ return;
+ }
+ if (preSort - nextSort > 0) {
+ sortNumber += 1;
+ } else {
+ sortNumber += -1;
+ }
+ });
+ return sortNumber;
+ });
+ }
+ if (params.filter) {
+ const filter = JSON.parse(params.filter as any) as {
+ [key: string]: string[];
+ };
+ if (Object.keys(filter).length > 0) {
+ dataSource = dataSource.filter((item) => {
+ return (Object.keys(filter) as Array<keyof API.RuleListItem>).some((key) => {
+ if (!filter[key]) {
+ return true;
+ }
+ if (filter[key].includes(`${item[key]}`)) {
+ return true;
+ }
+ return false;
+ });
+ });
+ }
+ }
+
+ if (params.name) {
+ dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
+ }
+ const result = {
+ data: dataSource,
+ total: tableListDataSource.length,
+ success: true,
+ pageSize,
+ current: parseInt(`${params.current}`, 10) || 1,
+ };
+
+ return res.json(result);
+}
+
+function postRule(req: Request, res: Response, u: string, b: Request) {
+ let realUrl = u;
+ if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
+ realUrl = req.url;
+ }
+
+ const body = (b && b.body) || req.body;
+ const { method, name, desc, key } = body;
+
+ switch (method) {
+ /* eslint no-case-declarations:0 */
+ case 'delete':
+ tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
+ break;
+ case 'post':
+ (() => {
+ const i = Math.ceil(Math.random() * 10000);
+ const newRule: API.RuleListItem = {
+ key: tableListDataSource.length,
+ href: 'https://ant.design',
+ avatar: [
+ 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ ][i % 2],
+ name,
+ owner: '曲丽丽',
+ desc,
+ callNo: Math.floor(Math.random() * 1000),
+ status: Math.floor(Math.random() * 10) % 2,
+ updatedAt: moment().format('YYYY-MM-DD'),
+ createdAt: moment().format('YYYY-MM-DD'),
+ progress: Math.ceil(Math.random() * 100),
+ };
+ tableListDataSource.unshift(newRule);
+ return res.json(newRule);
+ })();
+ return;
+
+ case 'update':
+ (() => {
+ let newRule = {};
+ tableListDataSource = tableListDataSource.map((item) => {
+ if (item.key === key) {
+ newRule = { ...item, desc, name };
+ return { ...item, desc, name };
+ }
+ return item;
+ });
+ return res.json(newRule);
+ })();
+ return;
+ default:
+ break;
+ }
+
+ const result = {
+ list: tableListDataSource,
+ pagination: {
+ total: tableListDataSource.length,
+ },
+ };
+
+ res.json(result);
+}
+
+export default {
+ 'GET /api/rule': getRule,
+ 'POST /api/rule': postRule,
+};
diff --git a/mock/notices.ts b/mock/notices.ts
new file mode 100644
index 0000000..616c921
--- /dev/null
+++ b/mock/notices.ts
@@ -0,0 +1,115 @@
+import { Request, Response } from 'express';
+
+const getNotices = (req: Request, res: Response) => {
+ res.json({
+ data: [
+ {
+ id: '000000001',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr',
+ title: '你收到了 14 份新周报',
+ datetime: '2017-08-09',
+ type: 'notification',
+ },
+ {
+ id: '000000002',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr',
+ title: '你推荐的 曲妮妮 已通过第三轮面试',
+ datetime: '2017-08-08',
+ type: 'notification',
+ },
+ {
+ id: '000000003',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr',
+ title: '这种模板可以区分多种通知类型',
+ datetime: '2017-08-07',
+ read: true,
+ type: 'notification',
+ },
+ {
+ id: '000000004',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr',
+ title: '左侧图标用于区分不同的类型',
+ datetime: '2017-08-07',
+ type: 'notification',
+ },
+ {
+ id: '000000005',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr',
+ title: '内容不要超过两行字,超出时自动截断',
+ datetime: '2017-08-07',
+ type: 'notification',
+ },
+ {
+ id: '000000006',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr',
+ title: '曲丽丽 评论了你',
+ description: '描述信息描述信息描述信息',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000007',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr',
+ title: '朱偏右 回复了你',
+ description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000008',
+ avatar:
+ 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr',
+ title: '标题',
+ description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000009',
+ title: '任务名称',
+ description: '任务需要在 2017-01-12 20:00 前启动',
+ extra: '未开始',
+ status: 'todo',
+ type: 'event',
+ },
+ {
+ id: '000000010',
+ title: '第三方紧急代码变更',
+ description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+ extra: '马上到期',
+ status: 'urgent',
+ type: 'event',
+ },
+ {
+ id: '000000011',
+ title: '信息安全考试',
+ description: '指派竹尔于 2017-01-09 前完成更新并发布',
+ extra: '已耗时 8 天',
+ status: 'doing',
+ type: 'event',
+ },
+ {
+ id: '000000012',
+ title: 'ABCD 版本发布',
+ description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+ extra: '进行中',
+ status: 'processing',
+ type: 'event',
+ },
+ ],
+ });
+};
+
+export default {
+ 'GET /api/notices': getNotices,
+};
diff --git a/mock/requestRecord.mock.js b/mock/requestRecord.mock.js
new file mode 100644
index 0000000..6c59e19
--- /dev/null
+++ b/mock/requestRecord.mock.js
@@ -0,0 +1,324 @@
+module.exports = {
+ 'GET /api/currentUser': {
+ data: {
+ name: 'Serati Ma',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+ userid: '00000001',
+ email: 'antdesign@alipay.com',
+ signature: '海纳百川,有容乃大',
+ title: '交互专家',
+ group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+ tags: [
+ { key: '0', label: '很有想法的' },
+ { key: '1', label: '专注设计' },
+ { key: '2', label: '辣~' },
+ { key: '3', label: '大长腿' },
+ { key: '4', label: '川妹子' },
+ { key: '5', label: '海纳百川' },
+ ],
+ notifyCount: 12,
+ unreadCount: 11,
+ country: 'China',
+ geographic: {
+ province: { label: '浙江省', key: '330000' },
+ city: { label: '杭州市', key: '330100' },
+ },
+ address: '西湖区工专路 77 号',
+ phone: '0752-268888888',
+ },
+ },
+ 'GET /api/rule': {
+ data: [
+ {
+ key: 99,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 99',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 503,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 81,
+ },
+ {
+ key: 98,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 98',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 164,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 97,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 97',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 174,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 81,
+ },
+ {
+ key: 96,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 96',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 914,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 7,
+ },
+ {
+ key: 95,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 95',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 698,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 82,
+ },
+ {
+ key: 94,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 94',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 488,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 14,
+ },
+ {
+ key: 93,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 93',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 580,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 77,
+ },
+ {
+ key: 92,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 92',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 244,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 58,
+ },
+ {
+ key: 91,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 91',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 959,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 66,
+ },
+ {
+ key: 90,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 90',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 958,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 72,
+ },
+ {
+ key: 89,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 89',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 301,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 2,
+ },
+ {
+ key: 88,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 88',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 277,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 87,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 87',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 810,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 82,
+ },
+ {
+ key: 86,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 86',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 780,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 22,
+ },
+ {
+ key: 85,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 85',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 705,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 84,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 84',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 203,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 79,
+ },
+ {
+ key: 83,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 83',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 491,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 59,
+ },
+ {
+ key: 82,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 82',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 73,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 100,
+ },
+ {
+ key: 81,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 81',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 406,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 61,
+ },
+ {
+ key: 80,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 80',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 112,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 20,
+ },
+ ],
+ total: 100,
+ success: true,
+ pageSize: 20,
+ current: 1,
+ },
+ 'POST /api/login/outLogin': { data: {}, success: true },
+ 'POST /api/login/account': {
+ status: 'ok',
+ type: 'account',
+ currentAuthority: 'admin',
+ },
+};
diff --git a/mock/route.ts b/mock/route.ts
new file mode 100644
index 0000000..418d10f
--- /dev/null
+++ b/mock/route.ts
@@ -0,0 +1,5 @@
+export default {
+ '/api/auth_routes': {
+ '/form/advanced-form': { authority: ['admin', 'user'] },
+ },
+};
diff --git a/mock/user.ts b/mock/user.ts
new file mode 100644
index 0000000..75edd34
--- /dev/null
+++ b/mock/user.ts
@@ -0,0 +1,203 @@
+import { Request, Response } from 'express';
+
+const waitTime = (time: number = 100) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(true);
+ }, time);
+ });
+};
+
+async function getFakeCaptcha(req: Request, res: Response) {
+ await waitTime(2000);
+ return res.json('captcha-xxx');
+}
+
+const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
+
+/**
+ * 当前用户的权限,如果为空代表没登录
+ * current user access, if is '', user need login
+ * 如果是 pro 的预览,默认是有权限的
+ */
+let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : '';
+
+const getAccess = () => {
+ return access;
+};
+
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
+export default {
+ // 支持值为 Object 和 Array
+ 'GET /api/currentUser': (req: Request, res: Response) => {
+ if (!getAccess()) {
+ res.status(401).send({
+ data: {
+ isLogin: false,
+ },
+ errorCode: '401',
+ errorMessage: '请先登录!',
+ success: true,
+ });
+ return;
+ }
+ res.send({
+ success: true,
+ data: {
+ name: 'Serati Ma',
+ avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
+ userid: '00000001',
+ email: 'antdesign@alipay.com',
+ signature: '海纳百川,有容乃大',
+ title: '交互专家',
+ group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+ tags: [
+ {
+ key: '0',
+ label: '很有想法的',
+ },
+ {
+ key: '1',
+ label: '专注设计',
+ },
+ {
+ key: '2',
+ label: '辣~',
+ },
+ {
+ key: '3',
+ label: '大长腿',
+ },
+ {
+ key: '4',
+ label: '川妹子',
+ },
+ {
+ key: '5',
+ label: '海纳百川',
+ },
+ ],
+ notifyCount: 12,
+ unreadCount: 11,
+ country: 'China',
+ access: getAccess(),
+ geographic: {
+ province: {
+ label: '浙江省',
+ key: '330000',
+ },
+ city: {
+ label: '杭州市',
+ key: '330100',
+ },
+ },
+ address: '西湖区工专路 77 号',
+ phone: '0752-268888888',
+ },
+ });
+ },
+ // GET POST 可省略
+ 'GET /api/users': [
+ {
+ key: '1',
+ name: 'John Brown',
+ age: 32,
+ address: 'New York No. 1 Lake Park',
+ },
+ {
+ key: '2',
+ name: 'Jim Green',
+ age: 42,
+ address: 'London No. 1 Lake Park',
+ },
+ {
+ key: '3',
+ name: 'Joe Black',
+ age: 32,
+ address: 'Sidney No. 1 Lake Park',
+ },
+ ],
+ 'POST /api/login/account': async (req: Request, res: Response) => {
+ const { password, username, type } = req.body;
+ await waitTime(2000);
+ if (password === 'ant.design' && username === 'admin') {
+ res.send({
+ status: 'ok',
+ type,
+ currentAuthority: 'admin',
+ });
+ access = 'admin';
+ return;
+ }
+ if (password === 'ant.design' && username === 'user') {
+ res.send({
+ status: 'ok',
+ type,
+ currentAuthority: 'user',
+ });
+ access = 'user';
+ return;
+ }
+ if (type === 'mobile') {
+ res.send({
+ status: 'ok',
+ type,
+ currentAuthority: 'admin',
+ });
+ access = 'admin';
+ return;
+ }
+
+ res.send({
+ status: 'error',
+ type,
+ currentAuthority: 'guest',
+ });
+ access = 'guest';
+ },
+ 'POST /api/login/outLogin': (req: Request, res: Response) => {
+ access = '';
+ res.send({ data: {}, success: true });
+ },
+ 'POST /api/register': (req: Request, res: Response) => {
+ res.send({ status: 'ok', currentAuthority: 'user', success: true });
+ },
+ 'GET /api/500': (req: Request, res: Response) => {
+ res.status(500).send({
+ timestamp: 1513932555104,
+ status: 500,
+ error: 'error',
+ message: 'error',
+ path: '/base/category/list',
+ });
+ },
+ 'GET /api/404': (req: Request, res: Response) => {
+ res.status(404).send({
+ timestamp: 1513932643431,
+ status: 404,
+ error: 'Not Found',
+ message: 'No message available',
+ path: '/base/category/list/2121212',
+ });
+ },
+ 'GET /api/403': (req: Request, res: Response) => {
+ res.status(403).send({
+ timestamp: 1513932555104,
+ status: 403,
+ error: 'Forbidden',
+ message: 'Forbidden',
+ path: '/base/category/list',
+ });
+ },
+ 'GET /api/401': (req: Request, res: Response) => {
+ res.status(401).send({
+ timestamp: 1513932555104,
+ status: 401,
+ error: 'Unauthorized',
+ message: 'Unauthorized',
+ path: '/base/category/list',
+ });
+ },
+
+ 'GET /api/login/captcha': getFakeCaptcha,
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ce917d9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,143 @@
+{
+ "name": "ant-design-pro",
+ "version": "6.0.0",
+ "private": true,
+ "description": "An out-of-box UI solution for enterprise applications",
+ "scripts": {
+ "dev": "npm run start:dev",
+ "build": "max build",
+ "deploy": "npm run build && npm run gh-pages",
+ "preview": "npm run build && max preview --port 8000",
+ "serve": "umi-serve",
+ "start": "cross-env UMI_ENV=dev max dev",
+ "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
+ "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
+ "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
+ "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
+ "test": "jest",
+ "test:coverage": "npm run jest -- --coverage",
+ "test:update": "npm run jest -- -u",
+ "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
+ "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
+ "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
+ "docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build",
+ "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
+ "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
+ "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
+ "analyze": "cross-env ANALYZE=1 max build",
+ "gh-pages": "gh-pages -d dist",
+ "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
+ "postinstall": "max setup",
+ "jest": "jest",
+ "lint": "npm run lint:js && npm run lint:prettier && npm run tsc",
+ "lint-staged": "lint-staged",
+ "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
+ "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ",
+ "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
+ "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto",
+ "openapi": "max openapi",
+ "prepare": "cd .. && husky install",
+ "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
+ "tsc": "tsc --noEmit",
+ "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login"
+ },
+ "lint-staged": {
+ "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
+ "**/*.{js,jsx,tsx,ts,less,md,json}": [
+ "prettier --write"
+ ]
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 10"
+ ],
+ "dependencies": {
+ "@ant-design/icons": "^5.5.0",
+ "@ant-design/plots": "^2.3.2",
+ "@ant-design/pro-components": "^2.7.19",
+ "@ant-design/use-emotion-css": "1.0.4",
+ "@testing-library/dom": "^10.4.0",
+ "@umijs/route-utils": "^4.0.1",
+ "ant-design-pro": "file:",
+ "antd": "^5.21.1",
+ "antd-style": "^3.6.2",
+ "classnames": "^2.5.1",
+ "dayjs": "^1.11.13",
+ "fabric": "^6.4.0",
+ "highlight.js": "^11.10.0",
+ "lodash": "^4.17.21",
+ "moment": "^2.30.1",
+ "omit.js": "^2.0.2",
+ "query-string": "^9.1.0",
+ "rc-menu": "^9.15.0",
+ "rc-util": "^5.43.0",
+ "react": "^18.3.0",
+ "react-cropper": "^2.3.3",
+ "react-dev-inspector": "^2.0.1",
+ "react-dom": "^18.3.0",
+ "react-helmet-async": "^2.0.0",
+ "react-highlight": "^0.15.0"
+ },
+ "devDependencies": {
+ "@ant-design/pro-cli": "^3.3.0",
+ "@testing-library/react": "^16.0.1",
+ "@types/classnames": "^2.3.1",
+ "@types/express": "^4.17.21",
+ "@types/history": "^4.7.11",
+ "@types/jest": "^29.5.12",
+ "@types/lodash": "^4.17.4",
+ "@types/node": "^22.13.5",
+ "@types/react": "^18.3.0",
+ "@types/react-dom": "^18.3.0",
+ "@types/react-helmet": "^6.1.11",
+ "@umijs/fabric": "^2.14.1",
+ "@umijs/lint": "^4.2.9",
+ "@umijs/max": "^4.2.9",
+ "cross-env": "^7.0.3",
+ "express": "^4.21.0",
+ "gh-pages": "^6.1.0",
+ "husky": "^9.1.3",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "lint-staged": "^15.2.0",
+ "mockjs": "^1.1.0",
+ "prettier": "^3.3.0",
+ "swagger-ui-dist": "^5.17.14",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.6.2",
+ "umi-presets-pro": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "create-umi": {
+ "ignoreScript": [
+ "docker*",
+ "functions*",
+ "site",
+ "generateMock"
+ ],
+ "ignoreDependencies": [
+ "netlify*",
+ "serverless"
+ ],
+ "ignore": [
+ ".dockerignore",
+ ".git",
+ ".github",
+ ".gitpod.yml",
+ "CODE_OF_CONDUCT.md",
+ "Dockerfile",
+ "Dockerfile.*",
+ "lambda",
+ "LICENSE",
+ "netlify.toml",
+ "README.*.md",
+ "azure-pipelines.yml",
+ "docker",
+ "CNAME",
+ "create-umi"
+ ]
+ }
+}
diff --git a/public/CNAME b/public/CNAME
new file mode 100644
index 0000000..30c2d4d
--- /dev/null
+++ b/public/CNAME
@@ -0,0 +1 @@
+preview.pro.ant.design
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e2e9325
--- /dev/null
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png
new file mode 100644
index 0000000..48d0e23
--- /dev/null
+++ b/public/icons/icon-128x128.png
Binary files differ
diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png
new file mode 100644
index 0000000..938e9b5
--- /dev/null
+++ b/public/icons/icon-192x192.png
Binary files differ
diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png
new file mode 100644
index 0000000..21fc108
--- /dev/null
+++ b/public/icons/icon-512x512.png
Binary files differ
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..239bf69
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
\ No newline at end of file
diff --git a/public/pro_icon.svg b/public/pro_icon.svg
new file mode 100644
index 0000000..e075b78
--- /dev/null
+++ b/public/pro_icon.svg
@@ -0,0 +1,5 @@
+<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/public/scripts/loading.js b/public/scripts/loading.js
new file mode 100644
index 0000000..c1ced54
--- /dev/null
+++ b/public/scripts/loading.js
@@ -0,0 +1,202 @@
+/**
+ * loading 占位
+ * 解决首次加载时白屏的问题
+ */
+ (function () {
+ const _root = document.querySelector('#root');
+ if (_root && _root.innerHTML === '') {
+ _root.innerHTML = `
+ <style>
+ html,
+ body,
+ #root {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ }
+ #root {
+ background-repeat: no-repeat;
+ background-size: 100% auto;
+ }
+
+ .loading-title {
+ font-size: 1.1rem;
+ }
+
+ .loading-sub-title {
+ margin-top: 20px;
+ font-size: 1rem;
+ color: #888;
+ }
+
+ .page-loading-warp {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 26px;
+ }
+ .ant-spin {
+ position: absolute;
+ display: none;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ color: rgba(0, 0, 0, 0.65);
+ color: #1890ff;
+ font-size: 14px;
+ font-variant: tabular-nums;
+ line-height: 1.5;
+ text-align: center;
+ list-style: none;
+ opacity: 0;
+ -webkit-transition: -webkit-transform 0.3s
+ cubic-bezier(0.78, 0.14, 0.15, 0.86);
+ transition: -webkit-transform 0.3s
+ cubic-bezier(0.78, 0.14, 0.15, 0.86);
+ transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+ transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
+ -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+ -webkit-font-feature-settings: "tnum";
+ font-feature-settings: "tnum";
+ }
+
+ .ant-spin-spinning {
+ position: static;
+ display: inline-block;
+ opacity: 1;
+ }
+
+ .ant-spin-dot {
+ position: relative;
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ font-size: 20px;
+ }
+
+ .ant-spin-dot-item {
+ position: absolute;
+ display: block;
+ width: 9px;
+ height: 9px;
+ background-color: #1890ff;
+ border-radius: 100%;
+ -webkit-transform: scale(0.75);
+ -ms-transform: scale(0.75);
+ transform: scale(0.75);
+ -webkit-transform-origin: 50% 50%;
+ -ms-transform-origin: 50% 50%;
+ transform-origin: 50% 50%;
+ opacity: 0.3;
+ -webkit-animation: antspinmove 1s infinite linear alternate;
+ animation: antSpinMove 1s infinite linear alternate;
+ }
+
+ .ant-spin-dot-item:nth-child(1) {
+ top: 0;
+ left: 0;
+ }
+
+ .ant-spin-dot-item:nth-child(2) {
+ top: 0;
+ right: 0;
+ -webkit-animation-delay: 0.4s;
+ animation-delay: 0.4s;
+ }
+
+ .ant-spin-dot-item:nth-child(3) {
+ right: 0;
+ bottom: 0;
+ -webkit-animation-delay: 0.8s;
+ animation-delay: 0.8s;
+ }
+
+ .ant-spin-dot-item:nth-child(4) {
+ bottom: 0;
+ left: 0;
+ -webkit-animation-delay: 1.2s;
+ animation-delay: 1.2s;
+ }
+
+ .ant-spin-dot-spin {
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ -webkit-animation: antrotate 1.2s infinite linear;
+ animation: antRotate 1.2s infinite linear;
+ }
+
+ .ant-spin-lg .ant-spin-dot {
+ width: 32px;
+ height: 32px;
+ font-size: 32px;
+ }
+
+ .ant-spin-lg .ant-spin-dot i {
+ width: 14px;
+ height: 14px;
+ }
+
+ @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ .ant-spin-blur {
+ background: #fff;
+ opacity: 0.5;
+ }
+ }
+
+ @-webkit-keyframes antSpinMove {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes antSpinMove {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @-webkit-keyframes antRotate {
+ to {
+ -webkit-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+ }
+
+ @keyframes antRotate {
+ to {
+ -webkit-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+ }
+ </style>
+
+ <div style="
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ min-height: 362px;
+ ">
+ <div class="page-loading-warp">
+ <div class="ant-spin ant-spin-lg ant-spin-spinning">
+ <span class="ant-spin-dot ant-spin-dot-spin">
+ <i class="ant-spin-dot-item"></i>
+ <i class="ant-spin-dot-item"></i>
+ <i class="ant-spin-dot-item"></i>
+ <i class="ant-spin-dot-item"></i>
+ </span>
+ </div>
+ </div>
+ <div class="loading-title">
+ 正在加载资源
+ </div>
+ <div class="loading-sub-title">
+ 初次加载资源可能需要较多时间 请耐心等待
+ </div>
+ </div>
+ `;
+ }
+})();
diff --git a/src/access.ts b/src/access.ts
new file mode 100644
index 0000000..8c11f31
--- /dev/null
+++ b/src/access.ts
@@ -0,0 +1,55 @@
+import { checkRole, matchPermission } from './utils/permission';
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-access
+ * */
+export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
+ const { currentUser } = initialState ?? {};
+ const hasPerms = (perm: string) => {
+ return matchPermission(initialState?.currentUser?.permissions, perm);
+ };
+ const roleFiler = (route: { authority: string[] }) => {
+ return checkRole(initialState?.currentUser?.roles, route.authority);
+ };
+ return {
+ canAdmin: currentUser && currentUser.access === 'admin',
+ hasPerms,
+ roleFiler,
+ };
+}
+
+export function setSessionToken(
+ access_token: string | undefined,
+ refresh_token: string | undefined,
+ expireTime: number,
+): void {
+ if (access_token) {
+ localStorage.setItem('access_token', access_token);
+ } else {
+ localStorage.removeItem('access_token');
+ }
+ if (refresh_token) {
+ localStorage.setItem('refresh_token', refresh_token);
+ } else {
+ localStorage.removeItem('refresh_token');
+ }
+ localStorage.setItem('expireTime', `${expireTime}`);
+}
+
+export function getAccessToken() {
+ return localStorage.getItem('access_token');
+}
+
+export function getRefreshToken() {
+ return localStorage.getItem('refresh_token');
+}
+
+export function getTokenExpireTime() {
+ return localStorage.getItem('expireTime');
+}
+
+export function clearSessionToken() {
+ sessionStorage.removeItem('user');
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('refresh_token');
+ localStorage.removeItem('expireTime');
+}
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..38f76b4
--- /dev/null
+++ b/src/app.tsx
@@ -0,0 +1,233 @@
+import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components';
+import { LinkOutlined } from '@ant-design/icons';
+import type { Settings as LayoutSettings } from '@ant-design/pro-components';
+import { SettingDrawer } from '@ant-design/pro-components';
+import type { RunTimeLayoutConfig } from '@umijs/max';
+import { history, Link } from '@umijs/max';
+import defaultSettings from '../config/defaultSettings';
+import { errorConfig } from './requestErrorConfig';
+import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
+import { getRemoteMenu, getRoutersInfo, getUserInfo, patchRouteWithRemoteMenus, setRemoteMenu } from './services/session';
+import { PageEnum } from './enums/pagesEnums';
+
+
+const isDev = process.env.NODE_ENV === 'development';
+
+
+
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-initial-state
+ * */
+export async function getInitialState(): Promise<{
+ settings?: Partial<LayoutSettings>;
+ currentUser?: API.CurrentUser;
+ loading?: boolean;
+ fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
+}> {
+ const fetchUserInfo = async () => {
+ try {
+ const response = await getUserInfo({
+ skipErrorHandler: true,
+ });
+ if (response.user.avatar === '') {
+ response.user.avatar =
+ 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
+ }
+ return {
+ ...response.user,
+ permissions: response.permissions,
+ roles: response.roles,
+ } as API.CurrentUser;
+ } catch (error) {
+ console.log(error);
+ history.push(PageEnum.LOGIN);
+ }
+ return undefined;
+ };
+ // 如果不是登录页面,执行
+ const { location } = history;
+ if (location.pathname !== PageEnum.LOGIN) {
+ const currentUser = await fetchUserInfo();
+ return {
+ fetchUserInfo,
+ currentUser,
+ settings: defaultSettings as Partial<LayoutSettings>,
+ };
+ }
+ return {
+ fetchUserInfo,
+ settings: defaultSettings as Partial<LayoutSettings>,
+ };
+}
+
+// ProLayout 支持的api https://procomponents.ant.design/components/layout
+export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
+ return {
+ actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
+ avatarProps: {
+ src: initialState?.currentUser?.avatar,
+ title: <AvatarName />,
+ render: (_, avatarChildren) => {
+ return <AvatarDropdown menu="True">{avatarChildren}</AvatarDropdown>;
+ },
+ },
+ waterMarkProps: {
+ // content: initialState?.currentUser?.nickName,
+ },
+ menu: {
+ locale: false,
+ // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
+ params: {
+ userId: initialState?.currentUser?.userId,
+ },
+ request: async () => {
+ if (!initialState?.currentUser?.userId) {
+ return [];
+ }
+ return getRemoteMenu();
+ },
+ },
+ footerRender: () => <Footer />,
+ onPageChange: () => {
+ const { location } = history;
+ // 如果没有登录,重定向到 login
+ if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
+ history.push(PageEnum.LOGIN);
+ }
+ },
+ layoutBgImgList: [
+ {
+ src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
+ left: 85,
+ bottom: 100,
+ height: '303px',
+ },
+ {
+ src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
+ bottom: -68,
+ right: -45,
+ height: '303px',
+ },
+ {
+ src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
+ bottom: 0,
+ left: 0,
+ width: '331px',
+ },
+ ],
+ links: isDev
+ ? [
+ <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
+ <LinkOutlined />
+ <span>OpenAPI 文档</span>
+ </Link>,
+ ]
+ : [],
+ menuHeaderRender: undefined,
+ // 自定义 403 页面
+ // unAccessible: <div>unAccessible</div>,
+ // 增加一个 loading 的状态
+ childrenRender: (children) => {
+ // if (initialState?.loading) return <PageLoading />;
+ return (
+ <>
+ {children}
+ <SettingDrawer
+ disableUrlParams
+ enableDarkTheme
+ settings={initialState?.settings}
+ onSettingChange={(settings) => {
+ setInitialState((preInitialState) => ({
+ ...preInitialState,
+ settings,
+ }));
+ }}
+ />
+ </>
+ );
+ },
+ ...initialState?.settings,
+ };
+};
+
+export async function onRouteChange({ clientRoutes, location }) {
+ const menus = getRemoteMenu();
+ // console.log('onRouteChange', clientRoutes, location, menus);
+ if(menus === null && location.pathname !== PageEnum.LOGIN) {
+ console.log('refresh')
+ history.go(0);
+ }
+}
+
+// export function patchRoutes({ routes, routeComponents }) {
+// console.log('patchRoutes', routes, routeComponents);
+// }
+
+
+export async function patchClientRoutes({ routes }) {
+ // console.log('patchClientRoutes', routes);
+ patchRouteWithRemoteMenus(routes);
+}
+
+export function render(oldRender: () => void) {
+ // console.log('render get routers', oldRender)
+ const token = getAccessToken();
+ if(!token || token?.length === 0) {
+ oldRender();
+ return;
+ }
+ getRoutersInfo().then(res => {
+ setRemoteMenu(res);
+ oldRender()
+ });
+}
+
+/**
+ * @name request 配置,可以配置错误处理
+ * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+const checkRegion = 5 * 60 * 1000;
+
+export const request = {
+ ...errorConfig,
+ requestInterceptors: [
+ (url: any, options: { headers: any }) => {
+ const headers = options.headers ? options.headers : [];
+ console.log('request ====>:', url);
+ const authHeader = headers['Authorization'];
+ const isToken = headers['isToken'];
+ if (!authHeader && isToken !== false) {
+ const expireTime = getTokenExpireTime();
+ if (expireTime) {
+ const left = Number(expireTime) - new Date().getTime();
+ const refreshToken = getRefreshToken();
+ if (left < checkRegion && refreshToken) {
+ if (left < 0) {
+ clearSessionToken();
+ }
+ } else {
+ const accessToken = getAccessToken();
+ if (accessToken) {
+ headers['Authorization'] = `Bearer ${accessToken}`;
+ }
+ }
+ } else {
+ clearSessionToken();
+ }
+ }
+ return { url, options };
+ },
+ ],
+ responseInterceptors: [
+ // (response) =>
+ // {
+ // // // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到
+ // // const { data = {} as any, config } = response;
+ // // // do something
+ // // console.log('data: ', data)
+ // // console.log('config: ', config)
+ // return response
+ // },
+ ],
+};
diff --git a/src/components/DictTag/index.tsx b/src/components/DictTag/index.tsx
new file mode 100644
index 0000000..2b2a791
--- /dev/null
+++ b/src/components/DictTag/index.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Tag } from 'antd';
+import { ProSchemaValueEnumType } from '@ant-design/pro-components';
+import { DefaultOptionType } from 'antd/es/select';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/10
+ *
+ * */
+
+export interface DictValueEnumType extends ProSchemaValueEnumType {
+ id?: string | number;
+ key?: string | number;
+ value: string | number;
+ label: string;
+ listClass?: string;
+}
+
+export interface DictOptionType extends DefaultOptionType {
+ id?: string | number;
+ key?: string | number;
+ text: string;
+ listClass?: string;
+}
+
+
+export type DictValueEnumObj = Record<string | number, DictValueEnumType>;
+
+export type DictTagProps = {
+ key?: string;
+ value?: string | number;
+ enums?: DictValueEnumObj;
+ options?: DictOptionType[];
+};
+
+const DictTag: React.FC<DictTagProps> = (props) => {
+ function getDictColor(type?: string) {
+ switch (type) {
+ case 'primary':
+ return 'blue';
+ case 'success':
+ return 'success';
+ case 'info':
+ return 'green';
+ case 'warning':
+ return 'warning';
+ case 'danger':
+ return 'error';
+ case 'default':
+ default:
+ return 'default';
+ }
+ }
+
+ function getDictLabelByValue(value: string | number | undefined): string {
+ if (value === undefined) {
+ return '';
+ }
+ if (props.enums) {
+ const item = props.enums[value];
+ return item.label;
+ }
+ if (props.options) {
+ if (!Array.isArray(props.options)) {
+ console.log('DictTag options is no array!')
+ return '';
+ }
+ for (const item of props.options) {
+ if (item.value === value) {
+ return item.text;
+ }
+ }
+ }
+ return String(props.value);
+ }
+
+ function getDictListClassByValue(value: string | number | undefined): string {
+ if (value === undefined) {
+ return 'default';
+ }
+ if (props.enums) {
+ const item = props.enums[value];
+ return item.listClass || 'default';
+ }
+ if (props.options) {
+ if (!Array.isArray(props.options)) {
+ console.log('DictTag options is no array!')
+ return 'default';
+ }
+ for (const item of props.options) {
+ if (item.value === value) {
+ return item.listClass || 'default';
+ }
+ }
+ }
+ return String(props.value);
+ }
+
+ const getTagColor = () => {
+ return getDictColor(getDictListClassByValue(props.value).toLowerCase());
+ };
+
+ const getTagText = (): string => {
+ return getDictLabelByValue(props.value);
+ };
+
+ return (
+ <Tag color={getTagColor()}>{getTagText()}</Tag>
+ )
+}
+
+
+export default DictTag;
\ No newline at end of file
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
new file mode 100644
index 0000000..f204ac2
--- /dev/null
+++ b/src/components/Footer/index.tsx
@@ -0,0 +1,35 @@
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-components';
+import React from 'react';
+
+const Footer: React.FC = () => {
+ return (
+ <DefaultFooter
+ style={{
+ background: 'none',
+ }}
+ links={[
+ {
+ key: 'Ant Design Pro',
+ title: 'Ant Design Pro',
+ href: 'https://pro.ant.design',
+ blankTarget: true,
+ },
+ {
+ key: 'github',
+ title: <GithubOutlined />,
+ href: 'https://github.com/ant-design/ant-design-pro',
+ blankTarget: true,
+ },
+ {
+ key: 'Ant Design',
+ title: 'Ant Design',
+ href: 'https://ant.design',
+ blankTarget: true,
+ },
+ ]}
+ />
+ );
+};
+
+export default Footer;
diff --git a/src/components/HeaderDropdown/index.tsx b/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..f89052d
--- /dev/null
+++ b/src/components/HeaderDropdown/index.tsx
@@ -0,0 +1,27 @@
+import { Dropdown } from 'antd';
+import type { DropDownProps } from 'antd/es/dropdown';
+import React from 'react';
+import { createStyles } from 'antd-style';
+import classNames from 'classnames';
+
+const useStyles = createStyles(({ token }) => {
+ return {
+ dropdown: {
+ [`@media screen and (max-width: ${token.screenXS}px)`]: {
+ width: '100%',
+ },
+ },
+ };
+});
+
+export type HeaderDropdownProps = {
+ overlayClassName?: string;
+ placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
+} & Omit<DropDownProps, 'overlay'>;
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
+ const { styles } = useStyles();
+ return <Dropdown overlayClassName={classNames(styles.dropdown, cls)} {...restProps} />;
+};
+
+export default HeaderDropdown;
diff --git a/src/components/IconSelector/Category.tsx b/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..2391687
--- /dev/null
+++ b/src/components/IconSelector/Category.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import CopyableIcon from './CopyableIcon';
+import type { ThemeType } from './index';
+import type { CategoriesKeys } from './fields';
+import { useIntl } from '@umijs/max';
+import styles from './style.less';
+
+interface CategoryProps {
+ title: CategoriesKeys;
+ icons: string[];
+ theme: ThemeType;
+ newIcons: string[];
+ onSelect: (type: string, name: string) => any;
+}
+
+const Category: React.FC<CategoryProps> = props => {
+
+ const { icons, title, newIcons, theme } = props;
+ const intl = useIntl();
+ const [justCopied, setJustCopied] = React.useState<string | null>(null);
+ const copyId = React.useRef<NodeJS.Timeout | null>(null);
+ const onSelect = React.useCallback((type: string, text: string) => {
+ const { onSelect } = props;
+ if (onSelect) {
+ onSelect(type, text);
+ }
+ setJustCopied(type);
+ copyId.current = setTimeout(() => {
+ setJustCopied(null);
+ }, 2000);
+ }, []);
+ React.useEffect(
+ () => () => {
+ if (copyId.current) {
+ clearTimeout(Number(copyId.current));
+ }
+ },
+ [],
+ );
+
+ return (
+ <div>
+ <h4>{intl.formatMessage({
+ id: `app.docs.components.icon.category.${title}`,
+ defaultMessage: '信息',
+ })}</h4>
+ <ul className={styles.anticonsList}>
+ {icons.map(name => (
+ <CopyableIcon
+ key={name}
+ name={name}
+ theme={theme}
+ isNew={newIcons.includes(name)}
+ justCopied={justCopied}
+ onSelect={onSelect}
+ />
+ ))}
+ </ul>
+ </div>
+ );
+};
+
+export default Category;
diff --git a/src/components/IconSelector/CopyableIcon.tsx b/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..a0d9656
--- /dev/null
+++ b/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { Tooltip } from 'antd';
+import classNames from 'classnames';
+import * as AntdIcons from '@ant-design/icons';
+import type { ThemeType } from './index';
+import styles from './style.less';
+
+const allIcons: {
+ [key: string]: any;
+} = AntdIcons;
+
+export interface CopyableIconProps {
+ name: string;
+ isNew: boolean;
+ theme: ThemeType;
+ justCopied: string | null;
+ onSelect: (type: string, text: string) => any;
+}
+
+const CopyableIcon: React.FC<CopyableIconProps> = ({
+ name,
+ justCopied,
+ onSelect,
+ theme,
+}) => {
+ const className = classNames({
+ copied: justCopied === name,
+ [theme]: !!theme,
+
+ });
+ return (
+ <li className={className}
+ onClick={() => {
+ if (onSelect) {
+ onSelect(theme, name);
+ }
+ }}>
+ <Tooltip title={name}>
+ {React.createElement(allIcons[name], { className: styles.anticon })}
+ </Tooltip>
+ {/* <span className={styles.anticonClass}>
+ <Badge dot={isNew}>{name}</Badge>
+ </span> */}
+ </li>
+ );
+};
+
+export default CopyableIcon;
diff --git a/src/components/IconSelector/IconPicSearcher.tsx b/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/src/components/IconSelector/IconPicSearcher.tsx
@@ -0,0 +1,233 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Upload, Tooltip, Popover, Modal, Progress, Spin, Result } from 'antd';
+import * as AntdIcons from '@ant-design/icons';
+import { useIntl } from '@umijs/max';
+import './style.less';
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+const { Dragger } = Upload;
+interface AntdIconClassifier {
+ load: () => void;
+ predict: (imgEl: HTMLImageElement) => void;
+}
+declare global {
+ interface Window {
+ antdIconClassifier: AntdIconClassifier;
+ }
+}
+
+interface PicSearcherState {
+ loading: boolean;
+ modalOpen: boolean;
+ popoverVisible: boolean;
+ icons: iconObject[];
+ fileList: any[];
+ error: boolean;
+ modelLoaded: boolean;
+}
+
+interface iconObject {
+ type: string;
+ score: number;
+}
+
+const PicSearcher: React.FC = () => {
+ const intl = useIntl();
+ const {formatMessage} = intl;
+ const [state, setState] = useState<PicSearcherState>({
+ loading: false,
+ modalOpen: false,
+ popoverVisible: false,
+ icons: [],
+ fileList: [],
+ error: false,
+ modelLoaded: false,
+ });
+ const predict = (imgEl: HTMLImageElement) => {
+ try {
+ let icons: any[] = window.antdIconClassifier.predict(imgEl);
+ if (gtag && icons.length) {
+ gtag('event', 'icon', {
+ event_category: 'search-by-image',
+ event_label: icons[0].className,
+ });
+ }
+ icons = icons.map(i => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
+ setState(prev => ({ ...prev, loading: false, error: false, icons }));
+ } catch {
+ setState(prev => ({ ...prev, loading: false, error: true }));
+ }
+ };
+ // eslint-disable-next-line class-methods-use-this
+ const toImage = (url: string) =>
+ new Promise(resolve => {
+ const img = new Image();
+ img.setAttribute('crossOrigin', 'anonymous');
+ img.src = url;
+ img.onload = () => {
+ resolve(img);
+ };
+ });
+
+ const uploadFile = useCallback((file: File) => {
+ setState(prev => ({ ...prev, loading: true }));
+ const reader = new FileReader();
+ reader.onload = () => {
+ toImage(reader.result as string).then(predict);
+ setState(prev => ({
+ ...prev,
+ fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
+ }));
+ };
+ reader.readAsDataURL(file);
+ }, []);
+
+ const onPaste = useCallback((event: ClipboardEvent) => {
+ const items = event.clipboardData && event.clipboardData.items;
+ let file = null;
+ if (items && items.length) {
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].type.includes('image')) {
+ file = items[i].getAsFile();
+ break;
+ }
+ }
+ }
+ if (file) {
+ uploadFile(file);
+ }
+ }, []);
+ const toggleModal = useCallback(() => {
+ setState(prev => ({
+ ...prev,
+ modalOpen: !prev.modalOpen,
+ popoverVisible: false,
+ fileList: [],
+ icons: [],
+ }));
+ if (!localStorage.getItem('disableIconTip')) {
+ localStorage.setItem('disableIconTip', 'true');
+ }
+ }, []);
+
+ useEffect(() => {
+ const script = document.createElement('script');
+ script.onload = async () => {
+ await window.antdIconClassifier.load();
+ setState(prev => ({ ...prev, modelLoaded: true }));
+ document.addEventListener('paste', onPaste);
+ };
+ script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
+ document.head.appendChild(script);
+ setState(prev => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
+ return () => {
+ document.removeEventListener('paste', onPaste);
+ };
+ }, []);
+
+ return (
+ <div className="iconPicSearcher">
+ <Popover
+ content={formatMessage({id: 'app.docs.components.icon.pic-searcher.intro'})}
+ open={state.popoverVisible}
+ >
+ <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
+ </Popover>
+ <Modal
+ title={intl.formatMessage({
+ id: 'app.docs.components.icon.pic-searcher.title',
+ defaultMessage: '信息',
+ })}
+ open={state.modalOpen}
+ onCancel={toggleModal}
+ footer={null}
+ >
+ {state.modelLoaded || (
+ <Spin
+ spinning={!state.modelLoaded}
+ tip={formatMessage({
+ id: 'app.docs.components.icon.pic-searcher.modelloading',
+
+ })}
+ >
+ <div style={{ height: 100 }} />
+ </Spin>
+ )}
+ {state.modelLoaded && (
+ <Dragger
+ accept="image/jpeg, image/png"
+ listType="picture"
+ customRequest={o => uploadFile(o.file as File)}
+ fileList={state.fileList}
+ showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
+ >
+ <p className="ant-upload-drag-icon">
+ <AntdIcons.InboxOutlined />
+ </p>
+ <p className="ant-upload-text">
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-text'})}
+ </p>
+ <p className="ant-upload-hint">
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-hint'})}
+ </p>
+ </Dragger>
+ )}
+ <Spin
+ spinning={state.loading}
+ tip={formatMessage({id: 'app.docs.components.icon.pic-searcher.matching'})}
+ >
+ <div className="icon-pic-search-result">
+ {state.icons.length > 0 && (
+ <div className="result-tip">
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.result-tip'})}
+ </div>
+ )}
+ <table>
+ {state.icons.length > 0 && (
+ <thead>
+ <tr>
+ <th className="col-icon">
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-icon'})}
+ </th>
+ <th>{formatMessage({id: 'app.docs.components.icon.pic-searcher.th-score'})}</th>
+ </tr>
+ </thead>
+ )}
+ <tbody>
+ {state.icons.map(icon => {
+ const { type } = icon;
+ const iconName = `${type
+ .split('-')
+ .map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
+ .join('')}Outlined`;
+ return (
+ <tr key={iconName}>
+ <td className="col-icon">
+ <Tooltip title={icon.type} placement="right">
+ {React.createElement(allIcons[iconName])}
+ </Tooltip>
+ </td>
+ <td>
+ <Progress percent={Math.ceil(icon.score * 100)} />
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ {state.error && (
+ <Result
+ status="500"
+ title="503"
+ subTitle={formatMessage({id: 'app.docs.components.icon.pic-searcher.server-error'})}
+ />
+ )}
+ </div>
+ </Spin>
+ </Modal>
+ </div>
+ );
+};
+
+export default PicSearcher;
diff --git a/src/components/IconSelector/fields.ts b/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/src/components/IconSelector/fields.ts
@@ -0,0 +1,223 @@
+import * as AntdIcons from '@ant-design/icons/lib/icons';
+
+const all = Object.keys(AntdIcons)
+ .map(n => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
+ .filter((n, i, arr) => arr.indexOf(n) === i);
+
+const direction = [
+ 'StepBackward',
+ 'StepForward',
+ 'FastBackward',
+ 'FastForward',
+ 'Shrink',
+ 'ArrowsAlt',
+ 'Down',
+ 'Up',
+ 'Left',
+ 'Right',
+ 'CaretUp',
+ 'CaretDown',
+ 'CaretLeft',
+ 'CaretRight',
+ 'UpCircle',
+ 'DownCircle',
+ 'LeftCircle',
+ 'RightCircle',
+ 'DoubleRight',
+ 'DoubleLeft',
+ 'VerticalLeft',
+ 'VerticalRight',
+ 'VerticalAlignTop',
+ 'VerticalAlignMiddle',
+ 'VerticalAlignBottom',
+ 'Forward',
+ 'Backward',
+ 'Rollback',
+ 'Enter',
+ 'Retweet',
+ 'Swap',
+ 'SwapLeft',
+ 'SwapRight',
+ 'ArrowUp',
+ 'ArrowDown',
+ 'ArrowLeft',
+ 'ArrowRight',
+ 'PlayCircle',
+ 'UpSquare',
+ 'DownSquare',
+ 'LeftSquare',
+ 'RightSquare',
+ 'Login',
+ 'Logout',
+ 'MenuFold',
+ 'MenuUnfold',
+ 'BorderBottom',
+ 'BorderHorizontal',
+ 'BorderInner',
+ 'BorderOuter',
+ 'BorderLeft',
+ 'BorderRight',
+ 'BorderTop',
+ 'BorderVerticle',
+ 'PicCenter',
+ 'PicLeft',
+ 'PicRight',
+ 'RadiusBottomleft',
+ 'RadiusBottomright',
+ 'RadiusUpleft',
+ 'RadiusUpright',
+ 'Fullscreen',
+ 'FullscreenExit',
+];
+
+const suggestion = [
+ 'Question',
+ 'QuestionCircle',
+ 'Plus',
+ 'PlusCircle',
+ 'Pause',
+ 'PauseCircle',
+ 'Minus',
+ 'MinusCircle',
+ 'PlusSquare',
+ 'MinusSquare',
+ 'Info',
+ 'InfoCircle',
+ 'Exclamation',
+ 'ExclamationCircle',
+ 'Close',
+ 'CloseCircle',
+ 'CloseSquare',
+ 'Check',
+ 'CheckCircle',
+ 'CheckSquare',
+ 'ClockCircle',
+ 'Warning',
+ 'IssuesClose',
+ 'Stop',
+];
+
+const editor = [
+ 'Edit',
+ 'Form',
+ 'Copy',
+ 'Scissor',
+ 'Delete',
+ 'Snippets',
+ 'Diff',
+ 'Highlight',
+ 'AlignCenter',
+ 'AlignLeft',
+ 'AlignRight',
+ 'BgColors',
+ 'Bold',
+ 'Italic',
+ 'Underline',
+ 'Strikethrough',
+ 'Redo',
+ 'Undo',
+ 'ZoomIn',
+ 'ZoomOut',
+ 'FontColors',
+ 'FontSize',
+ 'LineHeight',
+ 'Dash',
+ 'SmallDash',
+ 'SortAscending',
+ 'SortDescending',
+ 'Drag',
+ 'OrderedList',
+ 'UnorderedList',
+ 'RadiusSetting',
+ 'ColumnWidth',
+ 'ColumnHeight',
+];
+
+const data = [
+ 'AreaChart',
+ 'PieChart',
+ 'BarChart',
+ 'DotChart',
+ 'LineChart',
+ 'RadarChart',
+ 'HeatMap',
+ 'Fall',
+ 'Rise',
+ 'Stock',
+ 'BoxPlot',
+ 'Fund',
+ 'Sliders',
+];
+
+const logo = [
+ 'Android',
+ 'Apple',
+ 'Windows',
+ 'Ie',
+ 'Chrome',
+ 'Github',
+ 'Aliwangwang',
+ 'Dingding',
+ 'WeiboSquare',
+ 'WeiboCircle',
+ 'TaobaoCircle',
+ 'Html5',
+ 'Weibo',
+ 'Twitter',
+ 'Wechat',
+ 'Youtube',
+ 'AlipayCircle',
+ 'Taobao',
+ 'Skype',
+ 'Qq',
+ 'MediumWorkmark',
+ 'Gitlab',
+ 'Medium',
+ 'Linkedin',
+ 'GooglePlus',
+ 'Dropbox',
+ 'Facebook',
+ 'Codepen',
+ 'CodeSandbox',
+ 'CodeSandboxCircle',
+ 'Amazon',
+ 'Google',
+ 'CodepenCircle',
+ 'Alipay',
+ 'AntDesign',
+ 'AntCloud',
+ 'Aliyun',
+ 'Zhihu',
+ 'Slack',
+ 'SlackSquare',
+ 'Behance',
+ 'BehanceSquare',
+ 'Dribbble',
+ 'DribbbleSquare',
+ 'Instagram',
+ 'Yuque',
+ 'Alibaba',
+ 'Yahoo',
+ 'Reddit',
+ 'Sketch',
+ 'WhatsApp',
+ 'Dingtalk',
+];
+
+const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
+
+const other = all.filter(n => !datum.includes(n));
+
+export const categories = {
+ direction,
+ suggestion,
+ editor,
+ data,
+ logo,
+ other,
+};
+
+export default categories;
+
+export type Categories = typeof categories;
+export type CategoriesKeys = keyof Categories;
diff --git a/src/components/IconSelector/index.tsx b/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/src/components/IconSelector/index.tsx
@@ -0,0 +1,142 @@
+import * as React from 'react';
+import Icon, * as AntdIcons from '@ant-design/icons';
+import { Radio, Input, Empty } from 'antd';
+import type { RadioChangeEvent } from 'antd/es/radio/interface';
+import debounce from 'lodash/debounce';
+import Category from './Category';
+import IconPicSearcher from './IconPicSearcher';
+import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
+import type { CategoriesKeys } from './fields';
+import { categories } from './fields';
+// import { useIntl } from '@umijs/max';
+
+export enum ThemeType {
+ Filled = 'Filled',
+ Outlined = 'Outlined',
+ TwoTone = 'TwoTone',
+}
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+interface IconSelectorProps {
+ //intl: any;
+ onSelect: any;
+}
+
+interface IconSelectorState {
+ theme: ThemeType;
+ searchKey: string;
+}
+
+const IconSelector: React.FC<IconSelectorProps> = (props) => {
+ // const intl = useIntl();
+ // const { messages } = intl;
+ const { onSelect } = props;
+ const [displayState, setDisplayState] = React.useState<IconSelectorState>({
+ theme: ThemeType.Outlined,
+ searchKey: '',
+ });
+
+ const newIconNames: string[] = [];
+
+ const handleSearchIcon = React.useCallback(
+ debounce((searchKey: string) => {
+ setDisplayState(prevState => ({ ...prevState, searchKey }));
+ }),
+ [],
+ );
+
+ const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
+ setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
+ }, []);
+
+ const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
+ const { searchKey = '', theme } = displayState;
+
+ const categoriesResult = Object.keys(categories)
+ .map((key: CategoriesKeys) => {
+ let iconList = categories[key];
+ if (searchKey) {
+ const matchKey = searchKey
+ // eslint-disable-next-line prefer-regex-literals
+ .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
+ .replace(/(Filled|Outlined|TwoTone)$/, '')
+ .toLowerCase();
+ iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey));
+ }
+
+ // CopyrightCircle is same as Copyright, don't show it
+ iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle');
+
+ return {
+ category: key,
+ icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]),
+ };
+ })
+ .filter(({ icons }) => !!icons.length)
+ .map(({ category, icons }) => (
+ <Category
+ key={category}
+ title={category as CategoriesKeys}
+ theme={theme}
+ icons={icons}
+ newIcons={newIconNames}
+ onSelect={(type, name) => {
+ if (onSelect) {
+ onSelect(name, allIcons[name]);
+ }
+ }}
+ />
+ ));
+ return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
+ }, [displayState.searchKey, displayState.theme]);
+ return (
+ <>
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <Radio.Group
+ value={displayState.theme}
+ onChange={handleChangeTheme}
+ size="large"
+ optionType="button"
+ buttonStyle="solid"
+ options={[
+ {
+ label: <Icon component={OutlinedIcon} />,
+ value: ThemeType.Outlined
+ },
+ {
+ label: <Icon component={FilledIcon} />,
+ value: ThemeType.Filled
+ },
+ {
+ label: <Icon component={TwoToneIcon} />,
+ value: ThemeType.TwoTone
+ },
+ ]}
+ >
+ {/* <Radio.Button value={ThemeType.Outlined}>
+ <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
+ </Radio.Button>
+ <Radio.Button value={ThemeType.Filled}>
+ <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
+ </Radio.Button>
+ <Radio.Button value={ThemeType.TwoTone}>
+ <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
+ </Radio.Button> */}
+ </Radio.Group>
+ <Input.Search
+ // placeholder={messages['app.docs.components.icon.search.placeholder']}
+ style={{ margin: '0 10px', flex: 1 }}
+ allowClear
+ onChange={e => handleSearchIcon(e.currentTarget.value)}
+ size="large"
+ autoFocus
+ suffix={<IconPicSearcher />}
+ />
+ </div>
+ {renderCategories}
+ </>
+ );
+};
+
+export default IconSelector
diff --git a/src/components/IconSelector/style.less b/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/src/components/IconSelector/style.less
@@ -0,0 +1,137 @@
+.iconPicSearcher {
+ display: inline-block;
+ margin: 0 8px;
+
+ .icon-pic-btn {
+ color: @text-color-secondary;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ &:hover {
+ color: @input-icon-hover-color;
+ }
+ }
+}
+
+.icon-pic-preview {
+ width: 30px;
+ height: 30px;
+ margin-top: 10px;
+ padding: 8px;
+ text-align: center;
+ border: 1px solid @border-color-base;
+ border-radius: 4px;
+
+ > img {
+ max-width: 50px;
+ max-height: 50px;
+ }
+}
+
+.icon-pic-search-result {
+ min-height: 50px;
+ padding: 0 10px;
+
+ > .result-tip {
+ padding: 10px 0;
+ color: @text-color-secondary;
+ }
+
+ > table {
+ width: 100%;
+
+ .col-icon {
+ width: 80px;
+ padding: 10px 0;
+
+ > .anticon {
+ font-size: 30px;
+
+ :hover {
+ color: @link-hover-color;
+ }
+ }
+ }
+ }
+}
+
+ul.anticonsList {
+ margin: 2px 0;
+ overflow: hidden;
+ direction: ltr;
+ list-style: none;
+
+ li {
+ position: relative;
+ float: left;
+ width: 48px;
+ height: 48px;
+ margin: 3px 0;
+ padding: 2px 0 0;
+ overflow: hidden;
+ color: #555;
+ text-align: center;
+ list-style: none;
+ background-color: inherit;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+
+ .rtl & {
+ margin: 3px 0;
+ padding: 2px 0 0;
+ }
+
+ .anticon {
+ margin: 4px 0 2px;
+ font-size: 24px;
+ transition: transform 0.3s ease-in-out;
+ will-change: transform;
+ }
+
+ .anticonClass {
+ display: block;
+ font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+ white-space: nowrap;
+ text-align: center;
+ transform: scale(0.83);
+
+ .ant-badge {
+ transition: color 0.3s ease-in-out;
+ }
+ }
+
+ &:hover {
+ color: #fff;
+ background-color: @primary-color;
+
+ .anticon {
+ transform: scale(1.4);
+ }
+
+ .ant-badge {
+ color: #fff;
+ }
+ }
+
+ &.TwoTone:hover {
+ background-color: #8ecafe;
+ }
+
+ &.copied:hover {
+ color: rgba(255, 255, 255, 0.2);
+ }
+
+ &.copied::after {
+ top: -2px;
+ opacity: 1;
+ }
+ }
+}
+
+.copied-code {
+ padding: 2px 4px;
+ font-size: 12px;
+ background: #f5f5f5;
+ border-radius: 2px;
+}
diff --git a/src/components/IconSelector/themeIcons.tsx b/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/src/components/IconSelector/themeIcons.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+
+
+export const FilledIcon: React.FC = props => {
+ const path =
+ 'M864 64H160C107 64 64 107 64 160v' +
+ '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
+ '0c0-53-43-96-96-96z';
+ return (
+ <svg {...props} viewBox="0 0 1024 1024">
+ <path d={path} />
+ </svg>
+ );
+};
+
+export const OutlinedIcon: React.FC = props => {
+ const path =
+ 'M864 64H160C107 64 64 107 64 160v7' +
+ '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
+ '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
+ '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
+ ' 12 12v680c0 6.6-5.4 12-12 12z';
+ return (
+ <svg {...props} viewBox="0 0 1024 1024">
+ <path d={path} />
+ </svg>
+ );
+};
+
+export const TwoToneIcon: React.FC = props => {
+ const path =
+ 'M16 512c0 273.932 222.066 496 496 49' +
+ '6s496-222.068 496-496S785.932 16 512 16 16 238.' +
+ '066 16 512z m496 368V144c203.41 0 368 164.622 3' +
+ '68 368 0 203.41-164.622 368-368 368z';
+ return (
+ <svg {...props} viewBox="0 0 1024 1024">
+ <path d={path} />
+ </svg>
+ );
+};
diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..34ceb15
--- /dev/null
+++ b/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,142 @@
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { history, useModel } from '@umijs/max';
+import { Spin } from 'antd';
+import { createStyles } from 'antd-style';
+import { stringify } from 'querystring';
+import type { MenuInfo } from 'rc-menu/lib/interface';
+import React, { useCallback } from 'react';
+import { flushSync } from 'react-dom';
+import HeaderDropdown from '../HeaderDropdown';
+import { setRemoteMenu } from '@/services/session';
+import { clearSessionToken } from '@/access';
+import { logout } from '@/services/system/auth';
+
+export type GlobalHeaderRightProps = {
+ menu?: boolean;
+ children?: React.ReactNode;
+};
+
+export const AvatarName = () => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+ return <span className="anticon">{currentUser?.nickName}</span>;
+};
+
+const useStyles = createStyles(({ token }) => {
+ return {
+ action: {
+ display: 'flex',
+ height: '48px',
+ marginLeft: 'auto',
+ overflow: 'hidden',
+ alignItems: 'center',
+ padding: '0 8px',
+ cursor: 'pointer',
+ borderRadius: token.borderRadius,
+ '&:hover': {
+ backgroundColor: token.colorBgTextHover,
+ },
+ },
+ };
+});
+
+export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
+ /**
+ * 退出登录,并且将当前的 url 保存
+ */
+ const loginOut = async () => {
+ await logout();
+ clearSessionToken();
+ setRemoteMenu(null);
+ const { search, pathname } = window.location;
+ const urlParams = new URL(window.location.href).searchParams;
+ /** 此方法会跳转到 redirect 参数所在的位置 */
+ const redirect = urlParams.get('redirect');
+ // Note: There may be security issues, please note
+ if (window.location.pathname !== '/user/login' && !redirect) {
+ history.replace({
+ pathname: '/user/login',
+ search: stringify({
+ redirect: pathname + search,
+ }),
+ });
+ }
+ };
+ const { styles } = useStyles();
+
+ const { initialState, setInitialState } = useModel('@@initialState');
+
+ const onMenuClick = useCallback(
+ (event: MenuInfo) => {
+ const { key } = event;
+ if (key === 'logout') {
+ flushSync(() => {
+ setInitialState((s) => ({ ...s, currentUser: undefined }));
+ });
+ loginOut();
+ return;
+ }
+ history.push(`/account/${key}`);
+ },
+ [setInitialState],
+ );
+
+ const loading = (
+ <span className={styles.action}>
+ <Spin
+ size="small"
+ style={{
+ marginLeft: 8,
+ marginRight: 8,
+ }}
+ />
+ </span>
+ );
+
+ if (!initialState) {
+ return loading;
+ }
+
+ const { currentUser } = initialState;
+
+ if (!currentUser || !currentUser.nickName) {
+ return loading;
+ }
+
+ const menuItems = [
+ ...(menu
+ ? [
+ {
+ key: 'center',
+ icon: <UserOutlined />,
+ label: '个人中心',
+ },
+ {
+ key: 'settings',
+ icon: <SettingOutlined />,
+ label: '个人设置',
+ },
+ {
+ type: 'divider' as const,
+ },
+ ]
+ : []),
+ {
+ key: 'logout',
+ icon: <LogoutOutlined />,
+ label: '退出登录',
+ },
+ ];
+
+ return (
+ <HeaderDropdown
+ menu={{
+ selectedKeys: [],
+ onClick: onMenuClick,
+ items: menuItems,
+ }}
+ >
+ {children}
+ </HeaderDropdown>
+ );
+};
diff --git a/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..20a7831
--- /dev/null
+++ b/src/components/RightContent/index.tsx
@@ -0,0 +1,31 @@
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { SelectLang as UmiSelectLang } from '@umijs/max';
+import React from 'react';
+
+export type SiderTheme = 'light' | 'dark';
+
+export const SelectLang = () => {
+ return (
+ <UmiSelectLang
+ style={{
+ padding: 4,
+ }}
+ />
+ );
+};
+
+export const Question = () => {
+ return (
+ <div
+ style={{
+ display: 'flex',
+ height: 26,
+ }}
+ onClick={() => {
+ window.open('https://pro.ant.design/docs/getting-started');
+ }}
+ >
+ <QuestionCircleOutlined />
+ </div>
+ );
+};
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..ca88a6d
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,12 @@
+/**
+ * 这个文件作为组件的目录
+ * 目的是统一管理对外输出的组件,方便分类
+ */
+/**
+ * 布局组件
+ */
+import Footer from './Footer';
+import { Question, SelectLang } from './RightContent';
+import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
+
+export { Footer, Question, SelectLang, AvatarDropdown, AvatarName };
diff --git a/src/enums/httpEnum.ts b/src/enums/httpEnum.ts
new file mode 100644
index 0000000..8b7ff1f
--- /dev/null
+++ b/src/enums/httpEnum.ts
@@ -0,0 +1,31 @@
+/**
+ * @description: Request result set
+ */
+export enum HttpResult {
+ SUCCESS = 200,
+ ERROR = -1,
+ TIMEOUT = 401,
+ TYPE = 'success',
+}
+
+/**
+ * @description: request method
+ */
+export enum RequestMethod {
+ GET = 'GET',
+ POST = 'POST',
+ PUT = 'PUT',
+ DELETE = 'DELETE',
+}
+
+/**
+ * @description: contentType
+ */
+export enum ContentType {
+ // json
+ JSON = 'application/json;charset=UTF-8',
+ // form-data qs
+ FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+ // form-data upload
+ FORM_DATA = 'multipart/form-data;charset=UTF-8',
+}
diff --git a/src/enums/pagesEnums.ts b/src/enums/pagesEnums.ts
new file mode 100644
index 0000000..4bdb5fd
--- /dev/null
+++ b/src/enums/pagesEnums.ts
@@ -0,0 +1,4 @@
+
+export enum PageEnum {
+ LOGIN = '/user/login'
+}
\ No newline at end of file
diff --git a/src/global.less b/src/global.less
new file mode 100644
index 0000000..a9a0c51
--- /dev/null
+++ b/src/global.less
@@ -0,0 +1,53 @@
+html,
+body,
+#root {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
+ 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+ 'Noto Color Emoji';
+}
+
+.colorWeak {
+ filter: invert(80%);
+}
+
+.ant-layout {
+ min-height: 100vh;
+}
+.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
+ left: unset;
+}
+
+canvas {
+ display: block;
+}
+
+body {
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+ul,
+ol {
+ list-style: none;
+}
+
+@media (max-width: 768px) {
+ .ant-table {
+ width: 100%;
+ overflow-x: auto;
+ &-thead > tr,
+ &-tbody > tr {
+ > th,
+ > td {
+ white-space: pre;
+ > span {
+ display: block;
+ }
+ }
+ }
+ }
+}
diff --git a/src/global.tsx b/src/global.tsx
new file mode 100644
index 0000000..afa1fab
--- /dev/null
+++ b/src/global.tsx
@@ -0,0 +1,91 @@
+import { useIntl } from '@umijs/max';
+import { Button, message, notification } from 'antd';
+import defaultSettings from '../config/defaultSettings';
+
+const { pwa } = defaultSettings;
+const isHttps = document.location.protocol === 'https:';
+
+const clearCache = () => {
+ // remove all caches
+ if (window.caches) {
+ caches
+ .keys()
+ .then((keys) => {
+ keys.forEach((key) => {
+ caches.delete(key);
+ });
+ })
+ .catch((e) => console.log(e));
+ }
+};
+
+// if pwa is true
+if (pwa) {
+ // Notify user if offline now
+ window.addEventListener('sw.offline', () => {
+ message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
+ });
+
+ // Pop up a prompt on the page asking the user if they want to use the latest version
+ window.addEventListener('sw.updated', (event: Event) => {
+ const e = event as CustomEvent;
+ const reloadSW = async () => {
+ // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+ // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+ const worker = e.detail && e.detail.waiting;
+ if (!worker) {
+ return true;
+ }
+ // Send skip-waiting event to waiting SW with MessageChannel
+ await new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = (msgEvent) => {
+ if (msgEvent.data.error) {
+ reject(msgEvent.data.error);
+ } else {
+ resolve(msgEvent.data);
+ }
+ };
+ worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+ });
+
+ clearCache();
+ window.location.reload();
+ return true;
+ };
+ const key = `open${Date.now()}`;
+ const btn = (
+ <Button
+ type="primary"
+ onClick={() => {
+ notification.destroy(key);
+ reloadSW();
+ }}
+ >
+ {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+ </Button>
+ );
+ notification.open({
+ message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+ description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+ btn,
+ key,
+ onClose: async () => null,
+ });
+ });
+} else if ('serviceWorker' in navigator && isHttps) {
+ // unregister service worker
+ const { serviceWorker } = navigator;
+ if (serviceWorker.getRegistrations) {
+ serviceWorker.getRegistrations().then((sws) => {
+ sws.forEach((sw) => {
+ sw.unregister();
+ });
+ });
+ }
+ serviceWorker.getRegistration().then((sw) => {
+ if (sw) sw.unregister();
+ });
+
+ clearCache();
+}
diff --git a/src/hooks/net/dict.ts b/src/hooks/net/dict.ts
new file mode 100644
index 0000000..9c2c41c
--- /dev/null
+++ b/src/hooks/net/dict.ts
@@ -0,0 +1,7 @@
+import { getDictValueEnum } from "@/services/system/dict";
+
+export function useDictEnum(name: string)
+{
+ const data = getDictValueEnum(name);
+ return data;
+}
\ No newline at end of file
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
new file mode 100644
index 0000000..58407d2
--- /dev/null
+++ b/src/locales/en-US.ts
@@ -0,0 +1,27 @@
+import app from './en-US/app';
+import component from './en-US/component';
+import globalHeader from './en-US/globalHeader';
+import menu from './en-US/menu';
+import pages from './en-US/pages';
+import pwa from './en-US/pwa';
+import settingDrawer from './en-US/settingDrawer';
+import settings from './en-US/settings';
+
+export default {
+ 'navBar.lang': 'Languages',
+ 'layout.user.link.help': 'Help',
+ 'layout.user.link.privacy': 'Privacy',
+ 'layout.user.link.terms': 'Terms',
+ 'app.copyright.produced': 'Produced by Ant Financial Experience Department',
+ 'app.preview.down.block': 'Download this page to your local project',
+ 'app.welcome.link.fetch-blocks': 'Get all block',
+ 'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
+ ...app,
+ ...globalHeader,
+ ...menu,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+ ...component,
+ ...pages,
+};
diff --git a/src/locales/en-US/app.ts b/src/locales/en-US/app.ts
new file mode 100644
index 0000000..074dcb8
--- /dev/null
+++ b/src/locales/en-US/app.ts
@@ -0,0 +1,26 @@
+export default {
+ 'app.docs.components.icon.search.placeholder': 'Search icons here, click icon to copy code',
+ 'app.docs.components.icon.outlined': 'Outlined',
+ 'app.docs.components.icon.filled': 'Filled',
+ 'app.docs.components.icon.two-tone': 'Two Tone',
+ 'app.docs.components.icon.category.direction': 'Directional Icons',
+ 'app.docs.components.icon.category.suggestion': 'Suggested Icons',
+ 'app.docs.components.icon.category.editor': 'Editor Icons',
+ 'app.docs.components.icon.category.data': 'Data Icons',
+ 'app.docs.components.icon.category.other': 'Application Icons',
+ 'app.docs.components.icon.category.logo': 'Brand and Logos',
+ 'app.docs.components.icon.pic-searcher.intro':
+ 'AI Search by image is online, you are welcome to use it! 🎉',
+ 'app.docs.components.icon.pic-searcher.title': 'Search by image',
+ 'app.docs.components.icon.pic-searcher.upload-text':
+ 'Click, drag, or paste file to this area to upload',
+ 'app.docs.components.icon.pic-searcher.upload-hint':
+ 'We will find the best matching icon based on the image provided',
+ 'app.docs.components.icon.pic-searcher.server-error':
+ 'Predict service is temporarily unavailable',
+ 'app.docs.components.icon.pic-searcher.matching': 'Matching...',
+ 'app.docs.components.icon.pic-searcher.modelloading': 'Model is loading...',
+ 'app.docs.components.icon.pic-searcher.result-tip': 'Matched the following icons for you:',
+ 'app.docs.components.icon.pic-searcher.th-icon': 'Icon',
+ 'app.docs.components.icon.pic-searcher.th-score': 'Probability',
+};
diff --git a/src/locales/en-US/component.ts b/src/locales/en-US/component.ts
new file mode 100644
index 0000000..3ba7eed
--- /dev/null
+++ b/src/locales/en-US/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': 'Expand',
+ 'component.tagSelect.collapse': 'Collapse',
+ 'component.tagSelect.all': 'All',
+};
diff --git a/src/locales/en-US/globalHeader.ts b/src/locales/en-US/globalHeader.ts
new file mode 100644
index 0000000..60b6d4e
--- /dev/null
+++ b/src/locales/en-US/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+ 'component.globalHeader.search': 'Search',
+ 'component.globalHeader.search.example1': 'Search example 1',
+ 'component.globalHeader.search.example2': 'Search example 2',
+ 'component.globalHeader.search.example3': 'Search example 3',
+ 'component.globalHeader.help': 'Help',
+ 'component.globalHeader.notification': 'Notification',
+ 'component.globalHeader.notification.empty': 'You have viewed all notifications.',
+ 'component.globalHeader.message': 'Message',
+ 'component.globalHeader.message.empty': 'You have viewed all messsages.',
+ 'component.globalHeader.event': 'Event',
+ 'component.globalHeader.event.empty': 'You have viewed all events.',
+ 'component.noticeIcon.clear': 'Clear',
+ 'component.noticeIcon.cleared': 'Cleared',
+ 'component.noticeIcon.empty': 'No notifications',
+ 'component.noticeIcon.view-more': 'View more',
+};
diff --git a/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts
new file mode 100644
index 0000000..eae3e53
--- /dev/null
+++ b/src/locales/en-US/menu.ts
@@ -0,0 +1,52 @@
+export default {
+ 'menu.welcome': 'Welcome',
+ 'menu.more-blocks': 'More Blocks',
+ 'menu.home': 'Home',
+ 'menu.admin': 'Admin',
+ 'menu.admin.sub-page': 'Sub-Page',
+ 'menu.login': 'Login',
+ 'menu.register': 'Register',
+ 'menu.register-result': 'Register Result',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': 'Analysis',
+ 'menu.dashboard.monitor': 'Monitor',
+ 'menu.dashboard.workplace': 'Workplace',
+ 'menu.exception.403': '403',
+ 'menu.exception.404': '404',
+ 'menu.exception.500': '500',
+ 'menu.form': 'Form',
+ 'menu.form.basic-form': 'Basic Form',
+ 'menu.form.step-form': 'Step Form',
+ 'menu.form.step-form.info': 'Step Form(write transfer information)',
+ 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
+ 'menu.form.step-form.result': 'Step Form(finished)',
+ 'menu.form.advanced-form': 'Advanced Form',
+ 'menu.list': 'List',
+ 'menu.list.table-list': 'Search Table',
+ 'menu.list.basic-list': 'Basic List',
+ 'menu.list.card-list': 'Card List',
+ 'menu.list.search-list': 'Search List',
+ 'menu.list.search-list.articles': 'Search List(articles)',
+ 'menu.list.search-list.projects': 'Search List(projects)',
+ 'menu.list.search-list.applications': 'Search List(applications)',
+ 'menu.profile': 'Profile',
+ 'menu.profile.basic': 'Basic Profile',
+ 'menu.profile.advanced': 'Advanced Profile',
+ 'menu.result': 'Result',
+ 'menu.result.success': 'Success',
+ 'menu.result.fail': 'Fail',
+ 'menu.exception': 'Exception',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': 'Trigger',
+ 'menu.account': 'Account',
+ 'menu.account.center': 'Account Center',
+ 'menu.account.settings': 'Account Settings',
+ 'menu.account.trigger': 'Trigger Error',
+ 'menu.account.logout': 'Logout',
+ 'menu.editor': 'Graphic Editor',
+ 'menu.editor.flow': 'Flow Editor',
+ 'menu.editor.mind': 'Mind Editor',
+ 'menu.editor.koni': 'Koni Editor',
+};
diff --git a/src/locales/en-US/pages.ts b/src/locales/en-US/pages.ts
new file mode 100644
index 0000000..58ff2c8
--- /dev/null
+++ b/src/locales/en-US/pages.ts
@@ -0,0 +1,71 @@
+export default {
+ 'pages.layouts.userLayout.title':
+ 'Ant Design is the most influential web design specification in Xihu district',
+ 'pages.login.accountLogin.tab': 'Account Login',
+ 'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/admin123)',
+ 'pages.login.failure': 'Login failed, please try again!',
+ 'pages.login.success': 'Login successful!',
+ 'pages.login.username.placeholder': 'Username: admin',
+ 'pages.login.username.required': 'Please input your username!',
+ 'pages.login.password.placeholder': 'Password: admin123',
+ 'pages.login.password.required': 'Please input your password!',
+ 'pages.login.phoneLogin.tab': 'Phone Login',
+ 'pages.login.phoneLogin.errorMessage': 'Verification Code Error',
+ 'pages.login.phoneNumber.placeholder': 'Phone Number',
+ 'pages.login.phoneNumber.required': 'Please input your phone number!',
+ 'pages.login.phoneNumber.invalid': 'Phone number is invalid!',
+ 'pages.login.captcha.placeholder': 'Verification Code',
+ 'pages.login.captcha.required': 'Please input verification code!',
+ 'pages.login.phoneLogin.getVerificationCode': 'Get Code',
+ 'pages.getCaptchaSecondText': 'sec(s)',
+ 'pages.login.rememberMe': 'Remember me',
+ 'pages.login.forgotPassword': 'Forgot Password ?',
+ 'pages.login.submit': 'Login',
+ 'pages.login.loginWith': 'Login with :',
+ 'pages.login.registerAccount': 'Register Account',
+ 'pages.welcome.link': 'Welcome',
+ 'pages.welcome.alertMessage': 'Faster and stronger heavy-duty components have been released.',
+ 'pages.admin.subPage.title': 'This page can only be viewed by Admin',
+ 'pages.admin.subPage.alertMessage':
+ 'Umi ui is now released, welcome to use npm run ui to start the experience.',
+ 'pages.searchTable.createForm.newRule': 'New Rule',
+ 'pages.searchTable.updateForm.ruleConfig': 'Rule configuration',
+ 'pages.searchTable.updateForm.basicConfig': 'Basic Information',
+ 'pages.searchTable.updateForm.ruleName.nameLabel': 'Rule Name',
+ 'pages.searchTable.updateForm.ruleName.nameRules': 'Please enter the rule name!',
+ 'pages.searchTable.updateForm.ruleDesc.descLabel': 'Rule Description',
+ 'pages.searchTable.updateForm.ruleDesc.descPlaceholder': 'Please enter at least five characters',
+ 'pages.searchTable.updateForm.ruleDesc.descRules':
+ 'Please enter a rule description of at least five characters!',
+ 'pages.searchTable.updateForm.ruleProps.title': 'Configure Properties',
+ 'pages.searchTable.updateForm.object': 'Monitoring Object',
+ 'pages.searchTable.updateForm.ruleProps.templateLabel': 'Rule Template',
+ 'pages.searchTable.updateForm.ruleProps.typeLabel': 'Rule Type',
+ 'pages.searchTable.updateForm.schedulingPeriod.title': 'Set Scheduling Period',
+ 'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Starting Time',
+ 'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'Please choose a start time!',
+ 'pages.searchTable.titleDesc': 'Description',
+ 'pages.searchTable.ruleName': 'Rule name is required',
+ 'pages.searchTable.titleCallNo': 'Number of Service Calls',
+ 'pages.searchTable.titleStatus': 'Status',
+ 'pages.searchTable.nameStatus.default': 'default',
+ 'pages.searchTable.nameStatus.running': 'running',
+ 'pages.searchTable.nameStatus.online': 'online',
+ 'pages.searchTable.nameStatus.abnormal': 'abnormal',
+ 'pages.searchTable.titleUpdatedAt': 'Last Scheduled at',
+ 'pages.searchTable.exception': 'Please enter the reason for the exception!',
+ 'pages.searchTable.titleOption': 'Option',
+ 'pages.searchTable.config': 'Configuration',
+ 'pages.searchTable.subscribeAlert': 'Subscribe to alerts',
+ 'pages.searchTable.title': 'Enquiry Form',
+ 'pages.searchTable.new': 'New',
+ 'pages.searchTable.edit': 'Edit',
+ 'pages.searchTable.delete': 'Delete',
+ 'pages.searchTable.export': 'Export',
+ 'pages.searchTable.chosen': 'chosen',
+ 'pages.searchTable.item': 'item',
+ 'pages.searchTable.totalServiceCalls': 'Total Number of Service Calls',
+ 'pages.searchTable.tenThousand': '0000',
+ 'pages.searchTable.batchDeletion': 'batch deletion',
+ 'pages.searchTable.batchApproval': 'batch approval',
+};
diff --git a/src/locales/en-US/pwa.ts b/src/locales/en-US/pwa.ts
new file mode 100644
index 0000000..ed8d199
--- /dev/null
+++ b/src/locales/en-US/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': 'You are offline now',
+ 'app.pwa.serviceworker.updated': 'New content is available',
+ 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+ 'app.pwa.serviceworker.updated.ok': 'Refresh',
+};
diff --git a/src/locales/en-US/settingDrawer.ts b/src/locales/en-US/settingDrawer.ts
new file mode 100644
index 0000000..a644905
--- /dev/null
+++ b/src/locales/en-US/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': 'Page style setting',
+ 'app.setting.pagestyle.dark': 'Dark style',
+ 'app.setting.pagestyle.light': 'Light style',
+ 'app.setting.content-width': 'Content Width',
+ 'app.setting.content-width.fixed': 'Fixed',
+ 'app.setting.content-width.fluid': 'Fluid',
+ 'app.setting.themecolor': 'Theme Color',
+ 'app.setting.themecolor.dust': 'Dust Red',
+ 'app.setting.themecolor.volcano': 'Volcano',
+ 'app.setting.themecolor.sunset': 'Sunset Orange',
+ 'app.setting.themecolor.cyan': 'Cyan',
+ 'app.setting.themecolor.green': 'Polar Green',
+ 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
+ 'app.setting.themecolor.geekblue': 'Geek Glue',
+ 'app.setting.themecolor.purple': 'Golden Purple',
+ 'app.setting.navigationmode': 'Navigation Mode',
+ 'app.setting.sidemenu': 'Side Menu Layout',
+ 'app.setting.topmenu': 'Top Menu Layout',
+ 'app.setting.fixedheader': 'Fixed Header',
+ 'app.setting.fixedsidebar': 'Fixed Sidebar',
+ 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
+ 'app.setting.hideheader': 'Hidden Header when scrolling',
+ 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
+ 'app.setting.othersettings': 'Other Settings',
+ 'app.setting.weakmode': 'Weak Mode',
+ 'app.setting.copy': 'Copy Setting',
+ 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
+ 'app.setting.production.hint':
+ 'Setting panel shows in development environment only, please manually modify',
+};
diff --git a/src/locales/en-US/settings.ts b/src/locales/en-US/settings.ts
new file mode 100644
index 0000000..822dd00
--- /dev/null
+++ b/src/locales/en-US/settings.ts
@@ -0,0 +1,60 @@
+export default {
+ 'app.settings.menuMap.basic': 'Basic Settings',
+ 'app.settings.menuMap.security': 'Security Settings',
+ 'app.settings.menuMap.binding': 'Account Binding',
+ 'app.settings.menuMap.notification': 'New Message Notification',
+ 'app.settings.basic.avatar': 'Avatar',
+ 'app.settings.basic.change-avatar': 'Change avatar',
+ 'app.settings.basic.email': 'Email',
+ 'app.settings.basic.email-message': 'Please input your email!',
+ 'app.settings.basic.nickname': 'Nickname',
+ 'app.settings.basic.nickname-message': 'Please input your Nickname!',
+ 'app.settings.basic.profile': 'Personal profile',
+ 'app.settings.basic.profile-message': 'Please input your personal profile!',
+ 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
+ 'app.settings.basic.country': 'Country/Region',
+ 'app.settings.basic.country-message': 'Please input your country!',
+ 'app.settings.basic.geographic': 'Province or city',
+ 'app.settings.basic.geographic-message': 'Please input your geographic info!',
+ 'app.settings.basic.address': 'Street Address',
+ 'app.settings.basic.address-message': 'Please input your address!',
+ 'app.settings.basic.phone': 'Phone Number',
+ 'app.settings.basic.phone-message': 'Please input your phone!',
+ 'app.settings.basic.update': 'Update Information',
+ 'app.settings.security.strong': 'Strong',
+ 'app.settings.security.medium': 'Medium',
+ 'app.settings.security.weak': 'Weak',
+ 'app.settings.security.password': 'Account Password',
+ 'app.settings.security.password-description': 'Current password strength',
+ 'app.settings.security.phone': 'Security Phone',
+ 'app.settings.security.phone-description': 'Bound phone',
+ 'app.settings.security.question': 'Security Question',
+ 'app.settings.security.question-description':
+ 'The security question is not set, and the security policy can effectively protect the account security',
+ 'app.settings.security.email': 'Backup Email',
+ 'app.settings.security.email-description': 'Bound Email',
+ 'app.settings.security.mfa': 'MFA Device',
+ 'app.settings.security.mfa-description':
+ 'Unbound MFA device, after binding, can be confirmed twice',
+ 'app.settings.security.modify': 'Modify',
+ 'app.settings.security.set': 'Set',
+ 'app.settings.security.bind': 'Bind',
+ 'app.settings.binding.taobao': 'Binding Taobao',
+ 'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
+ 'app.settings.binding.alipay': 'Binding Alipay',
+ 'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
+ 'app.settings.binding.dingding': 'Binding DingTalk',
+ 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
+ 'app.settings.binding.bind': 'Bind',
+ 'app.settings.notification.password': 'Account Password',
+ 'app.settings.notification.password-description':
+ 'Messages from other users will be notified in the form of a station letter',
+ 'app.settings.notification.messages': 'System Messages',
+ 'app.settings.notification.messages-description':
+ 'System messages will be notified in the form of a station letter',
+ 'app.settings.notification.todo': 'To-do Notification',
+ 'app.settings.notification.todo-description':
+ 'The to-do list will be notified in the form of a letter from the station',
+ 'app.settings.open': 'Open',
+ 'app.settings.close': 'Close',
+};
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
new file mode 100644
index 0000000..06e2eac
--- /dev/null
+++ b/src/locales/zh-CN.ts
@@ -0,0 +1,57 @@
+import app from './zh-CN/app';
+import component from './zh-CN/component';
+import globalHeader from './zh-CN/globalHeader';
+import sysmenu from './zh-CN/menu';
+import pages from './zh-CN/pages';
+import pwa from './zh-CN/pwa';
+import settingDrawer from './zh-CN/settingDrawer';
+import settings from './zh-CN/settings';
+import user from './zh-CN/system/user';
+import menu from './zh-CN/system/menu';
+import dict from './zh-CN/system/dict';
+import dictData from './zh-CN/system/dict-data';
+import role from './zh-CN/system/role';
+import dept from './zh-CN/system/dept';
+import post from './zh-CN/system/post';
+import config from './zh-CN/system/config';
+import notice from './zh-CN/system/notice';
+import operlog from './zh-CN/monitor/operlog';
+import logininfor from './zh-CN/monitor/logininfor';
+import onlineUser from './zh-CN/monitor/onlineUser';
+import job from './zh-CN/monitor/job';
+import joblog from './zh-CN/monitor/job-log';
+import server from './zh-CN/monitor/server';
+
+export default {
+ 'navBar.lang': '语言',
+ 'layout.user.link.help': '帮助',
+ 'layout.user.link.privacy': '隐私',
+ 'layout.user.link.terms': '条款',
+ 'app.copyright.produced': '蚂蚁集团体验技术部出品',
+ 'app.preview.down.block': '下载此页面到本地项目',
+ 'app.welcome.link.fetch-blocks': '获取全部区块',
+ 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+ ...app,
+ ...pages,
+ ...globalHeader,
+ ...sysmenu,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+ ...component,
+ ...user,
+ ...menu,
+ ...dict,
+ ...dictData,
+ ...role,
+ ...dept,
+ ...post,
+ ...config,
+ ...notice,
+ ...operlog,
+ ...logininfor,
+ ...onlineUser,
+ ...job,
+ ...joblog,
+ ...server,
+};
diff --git a/src/locales/zh-CN/app.ts b/src/locales/zh-CN/app.ts
new file mode 100644
index 0000000..5be68e2
--- /dev/null
+++ b/src/locales/zh-CN/app.ts
@@ -0,0 +1,23 @@
+export default {
+ 'app.docs.components.icon.search.placeholder': '在此搜索图标,点击图标可复制代码',
+ 'app.docs.components.icon.outlined': '线框风格',
+ 'app.docs.components.icon.filled': '实底风格',
+ 'app.docs.components.icon.two-tone': '双色风格',
+ 'app.docs.components.icon.category.direction': '方向性图标',
+ 'app.docs.components.icon.category.suggestion': '提示建议性图标',
+ 'app.docs.components.icon.category.editor': '编辑类图标',
+ 'app.docs.components.icon.category.data': '数据类图标',
+ 'app.docs.components.icon.category.other': '网站通用图标',
+ 'app.docs.components.icon.category.logo': '品牌和标识',
+ 'app.docs.components.icon.pic-searcher.intro': 'AI 截图搜索上线了,快来体验吧!🎉',
+ 'app.docs.components.icon.pic-searcher.title': '上传图片搜索图标',
+ 'app.docs.components.icon.pic-searcher.upload-text': '点击/拖拽/粘贴上传图片',
+ 'app.docs.components.icon.pic-searcher.upload-hint':
+ '我们会通过上传的图片进行匹配,得到最相似的图标',
+ 'app.docs.components.icon.pic-searcher.server-error': '识别服务暂不可用',
+ 'app.docs.components.icon.pic-searcher.matching': '匹配中...',
+ 'app.docs.components.icon.pic-searcher.modelloading': '神经网络模型加载中...',
+ 'app.docs.components.icon.pic-searcher.result-tip': '为您匹配到以下图标:',
+ 'app.docs.components.icon.pic-searcher.th-icon': '图标',
+ 'app.docs.components.icon.pic-searcher.th-score': '匹配度',
+};
diff --git a/src/locales/zh-CN/component.ts b/src/locales/zh-CN/component.ts
new file mode 100644
index 0000000..1f1fead
--- /dev/null
+++ b/src/locales/zh-CN/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展开',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/src/locales/zh-CN/globalHeader.ts b/src/locales/zh-CN/globalHeader.ts
new file mode 100644
index 0000000..9fd66a5
--- /dev/null
+++ b/src/locales/zh-CN/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+ 'component.globalHeader.search': '站内搜索',
+ 'component.globalHeader.search.example1': '搜索提示一',
+ 'component.globalHeader.search.example2': '搜索提示二',
+ 'component.globalHeader.search.example3': '搜索提示三',
+ 'component.globalHeader.help': '使用文档',
+ 'component.globalHeader.notification': '通知',
+ 'component.globalHeader.notification.empty': '你已查看所有通知',
+ 'component.globalHeader.message': '消息',
+ 'component.globalHeader.message.empty': '您已读完所有消息',
+ 'component.globalHeader.event': '待办',
+ 'component.globalHeader.event.empty': '你已完成所有待办',
+ 'component.noticeIcon.clear': '清空',
+ 'component.noticeIcon.cleared': '清空了',
+ 'component.noticeIcon.empty': '暂无数据',
+ 'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts
new file mode 100644
index 0000000..fecb70a
--- /dev/null
+++ b/src/locales/zh-CN/menu.ts
@@ -0,0 +1,52 @@
+export default {
+ 'menu.welcome': '欢迎',
+ 'menu.more-blocks': '更多区块',
+ 'menu.home': '首页',
+ 'menu.admin': '管理页',
+ 'menu.admin.sub-page': '二级管理页',
+ 'menu.login': '登录',
+ 'menu.register': '注册',
+ 'menu.register-result': '注册结果',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': '分析页',
+ 'menu.dashboard.monitor': '监控页',
+ 'menu.dashboard.workplace': '工作台',
+ 'menu.exception.403': '403',
+ 'menu.exception.404': '404',
+ 'menu.exception.500': '500',
+ 'menu.form': '表单页',
+ 'menu.form.basic-form': '基础表单',
+ 'menu.form.step-form': '分步表单',
+ 'menu.form.step-form.info': '分步表单(填写转账信息)',
+ 'menu.form.step-form.confirm': '分步表单(确认转账信息)',
+ 'menu.form.step-form.result': '分步表单(完成)',
+ 'menu.form.advanced-form': '高级表单',
+ 'menu.list': '列表页',
+ 'menu.list.table-list': '查询表格',
+ 'menu.list.basic-list': '标准列表',
+ 'menu.list.card-list': '卡片列表',
+ 'menu.list.search-list': '搜索列表',
+ 'menu.list.search-list.articles': '搜索列表(文章)',
+ 'menu.list.search-list.projects': '搜索列表(项目)',
+ 'menu.list.search-list.applications': '搜索列表(应用)',
+ 'menu.profile': '详情页',
+ 'menu.profile.basic': '基础详情页',
+ 'menu.profile.advanced': '高级详情页',
+ 'menu.result': '结果页',
+ 'menu.result.success': '成功页',
+ 'menu.result.fail': '失败页',
+ 'menu.exception': '异常页',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': '触发错误',
+ 'menu.account': '个人页',
+ 'menu.account.center': '个人中心',
+ 'menu.account.settings': '个人设置',
+ 'menu.account.trigger': '触发报错',
+ 'menu.account.logout': '退出登录',
+ 'menu.editor': '图形编辑器',
+ 'menu.editor.flow': '流程编辑器',
+ 'menu.editor.mind': '脑图编辑器',
+ 'menu.editor.koni': '拓扑编辑器',
+};
diff --git a/src/locales/zh-CN/monitor/job-log.ts b/src/locales/zh-CN/monitor/job-log.ts
new file mode 100644
index 0000000..75cf8d0
--- /dev/null
+++ b/src/locales/zh-CN/monitor/job-log.ts
@@ -0,0 +1,18 @@
+/**
+ * 定时任务调度日志
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export default {
+ 'monitor.job.log.title': '定时任务调度日志',
+ 'monitor.job.log.job_log_id': '任务日志编号',
+ 'monitor.job.log.job_name': '任务名称',
+ 'monitor.job.log.job_group': '任务组名',
+ 'monitor.job.log.invoke_target': '调用方法',
+ 'monitor.job.log.job_message': '日志信息',
+ 'monitor.job.log.status': '执行状态',
+ 'monitor.job.log.exception_info': '异常信息',
+ 'monitor.job.log.create_time': '创建时间',
+};
diff --git a/src/locales/zh-CN/monitor/job.ts b/src/locales/zh-CN/monitor/job.ts
new file mode 100644
index 0000000..7e0d5b9
--- /dev/null
+++ b/src/locales/zh-CN/monitor/job.ts
@@ -0,0 +1,25 @@
+/**
+ * 定时任务调度
+ *
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+export default {
+ 'monitor.job.title': '定时任务调度',
+ 'monitor.job.job_id': '任务编号',
+ 'monitor.job.job_name': '任务名称',
+ 'monitor.job.job_group': '任务组名',
+ 'monitor.job.invoke_target': '调用方法',
+ 'monitor.job.cron_expression': 'cron执行表达式',
+ 'monitor.job.misfire_policy': '执行策略',
+ 'monitor.job.concurrent': '是否并发执行',
+ 'monitor.job.next_valid_time': '下次执行时间',
+ 'monitor.job.status': '状态',
+ 'monitor.job.create_by': '创建者',
+ 'monitor.job.create_time': '创建时间',
+ 'monitor.job.update_by': '更新者',
+ 'monitor.job.update_time': '更新时间',
+ 'monitor.job.remark': '备注信息',
+ 'monitor.job.detail': '任务详情',
+};
diff --git a/src/locales/zh-CN/monitor/logininfor.ts b/src/locales/zh-CN/monitor/logininfor.ts
new file mode 100644
index 0000000..959a6d2
--- /dev/null
+++ b/src/locales/zh-CN/monitor/logininfor.ts
@@ -0,0 +1,13 @@
+export default {
+ 'monitor.logininfor.title': '系统访问记录',
+ 'monitor.logininfor.info_id': '访问编号',
+ 'monitor.logininfor.user_name': '用户账号',
+ 'monitor.logininfor.ipaddr': '登录IP地址',
+ 'monitor.logininfor.login_location': '登录地点',
+ 'monitor.logininfor.browser': '浏览器类型',
+ 'monitor.logininfor.os': '操作系统',
+ 'monitor.logininfor.status': '登录状态',
+ 'monitor.logininfor.msg': '提示消息',
+ 'monitor.logininfor.login_time': '访问时间',
+ 'monitor.logininfor.unlock': '解锁',
+};
diff --git a/src/locales/zh-CN/monitor/onlineUser.ts b/src/locales/zh-CN/monitor/onlineUser.ts
new file mode 100644
index 0000000..c693cde
--- /dev/null
+++ b/src/locales/zh-CN/monitor/onlineUser.ts
@@ -0,0 +1,20 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+export default {
+ 'monitor.online.user.id': '编号',
+ 'monitor.online.user.token_id': '会话编号',
+ 'monitor.online.user.user_name': '会话编号',
+ 'monitor.online.user.ipaddr': '登录IP地址',
+ 'monitor.online.user.login_location': '登录地点',
+ 'monitor.online.user.browser': '浏览器类型',
+ 'monitor.online.user.os': '操作系统',
+ 'monitor.online.user.dept_name': '部门',
+ 'monitor.online.user.login_time': '访问时间',
+ 'monitor.online.user.force_logout': '强制退出',
+};
diff --git a/src/locales/zh-CN/monitor/operlog.ts b/src/locales/zh-CN/monitor/operlog.ts
new file mode 100644
index 0000000..c45824c
--- /dev/null
+++ b/src/locales/zh-CN/monitor/operlog.ts
@@ -0,0 +1,19 @@
+export default {
+ 'monitor.operlog.title': '操作日志记录',
+ 'monitor.operlog.oper_id': '日志主键',
+ 'monitor.operlog.business_type': '业务类型',
+ 'monitor.operlog.method': '方法名称',
+ 'monitor.operlog.request_method': '请求方式',
+ 'monitor.operlog.operator_type': '操作类别',
+ 'monitor.operlog.oper_name': '操作人员',
+ 'monitor.operlog.dept_name': '部门名称',
+ 'monitor.operlog.oper_url': '请求URL',
+ 'monitor.operlog.oper_ip': '主机地址',
+ 'monitor.operlog.oper_location': '操作地点',
+ 'monitor.operlog.oper_param': '请求参数',
+ 'monitor.operlog.json_result': '返回参数',
+ 'monitor.operlog.status': '操作状态',
+ 'monitor.operlog.error_msg': '错误消息',
+ 'monitor.operlog.oper_time': '操作时间',
+ 'monitor.operlog.module': '操作模块',
+};
diff --git a/src/locales/zh-CN/monitor/server.ts b/src/locales/zh-CN/monitor/server.ts
new file mode 100644
index 0000000..f887a22
--- /dev/null
+++ b/src/locales/zh-CN/monitor/server.ts
@@ -0,0 +1,27 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+export default {
+ 'monitor.server.cpu.cpuNum': '核心数',
+ 'monitor.server.cpu.total': '总使用率',
+ 'monitor.server.cpu.sys': '系统使用率',
+ 'monitor.server.cpu.used': '用户使用率',
+ 'monitor.server.cpu.wait': 'IO等待',
+ 'monitor.server.cpu.free': '当前空闲率',
+ 'monitor.server.mem.total': '总内存',
+ 'monitor.server.mem.used': '已用内存',
+ 'monitor.server.mem.free': '剩余内存',
+ 'monitor.server.mem.usage': '使用率',
+ 'monitor.server.disk.dirName': '盘符路径',
+ 'monitor.server.disk.sysTypeName': '文件系统',
+ 'monitor.server.disk.typeName': '盘符类型',
+ 'monitor.server.disk.total': '总大小',
+ 'monitor.server.disk.free': '可用大小',
+ 'monitor.server.disk.used': '已用大小',
+ 'monitor.server.disk.usage': '使用率',
+};
diff --git a/src/locales/zh-CN/pages.ts b/src/locales/zh-CN/pages.ts
new file mode 100644
index 0000000..fc7abfb
--- /dev/null
+++ b/src/locales/zh-CN/pages.ts
@@ -0,0 +1,71 @@
+export default {
+ 'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
+ 'pages.login.accountLogin.tab': '账户密码登录',
+ 'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/admin123)',
+ 'pages.login.failure': '登录失败,请重试!',
+ 'pages.login.success': '登录成功!',
+ 'pages.login.username.placeholder': '用户名: admin',
+ 'pages.login.username.required': '用户名是必填项!',
+ 'pages.login.password.placeholder': '密码: admin123',
+ '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.goback': '返回',
+ 'pages.welcome.link': '欢迎使用',
+ 'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
+ '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.updateForm.pleaseInput': '请输入',
+ '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.edit': '编辑',
+ 'pages.searchTable.delete': '删除',
+ 'pages.searchTable.cleanAll': '清空',
+ 'pages.searchTable.export': '导出',
+ 'pages.searchTable.chosen': '已选择',
+ 'pages.searchTable.item': '项',
+ 'pages.searchTable.totalServiceCalls': '服务调用次数总计',
+ 'pages.searchTable.tenThousand': '万',
+ 'pages.searchTable.batchDeletion': '批量删除',
+ 'pages.searchTable.batchApproval': '批量审批',
+};
diff --git a/src/locales/zh-CN/pwa.ts b/src/locales/zh-CN/pwa.ts
new file mode 100644
index 0000000..e950484
--- /dev/null
+++ b/src/locales/zh-CN/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': '当前处于离线状态',
+ 'app.pwa.serviceworker.updated': '有新内容',
+ 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+ 'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/src/locales/zh-CN/settingDrawer.ts b/src/locales/zh-CN/settingDrawer.ts
new file mode 100644
index 0000000..3f44958
--- /dev/null
+++ b/src/locales/zh-CN/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': '整体风格设置',
+ 'app.setting.pagestyle.dark': '暗色菜单风格',
+ 'app.setting.pagestyle.light': '亮色菜单风格',
+ 'app.setting.content-width': '内容区域宽度',
+ 'app.setting.content-width.fixed': '定宽',
+ 'app.setting.content-width.fluid': '流式',
+ 'app.setting.themecolor': '主题色',
+ 'app.setting.themecolor.dust': '薄暮',
+ 'app.setting.themecolor.volcano': '火山',
+ 'app.setting.themecolor.sunset': '日暮',
+ 'app.setting.themecolor.cyan': '明青',
+ 'app.setting.themecolor.green': '极光绿',
+ 'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
+ 'app.setting.themecolor.geekblue': '极客蓝',
+ 'app.setting.themecolor.purple': '酱紫',
+ 'app.setting.navigationmode': '导航模式',
+ 'app.setting.sidemenu': '侧边菜单布局',
+ 'app.setting.topmenu': '顶部菜单布局',
+ 'app.setting.fixedheader': '固定 Header',
+ 'app.setting.fixedsidebar': '固定侧边菜单',
+ 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
+ 'app.setting.hideheader': '下滑时隐藏 Header',
+ 'app.setting.hideheader.hint': '固定 Header 时可配置',
+ 'app.setting.othersettings': '其他设置',
+ 'app.setting.weakmode': '色弱模式',
+ 'app.setting.copy': '拷贝设置',
+ 'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置',
+ 'app.setting.production.hint':
+ '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
+};
diff --git a/src/locales/zh-CN/settings.ts b/src/locales/zh-CN/settings.ts
new file mode 100644
index 0000000..df8af43
--- /dev/null
+++ b/src/locales/zh-CN/settings.ts
@@ -0,0 +1,55 @@
+export default {
+ 'app.settings.menuMap.basic': '基本设置',
+ 'app.settings.menuMap.security': '安全设置',
+ 'app.settings.menuMap.binding': '账号绑定',
+ 'app.settings.menuMap.notification': '新消息通知',
+ 'app.settings.basic.avatar': '头像',
+ 'app.settings.basic.change-avatar': '更换头像',
+ 'app.settings.basic.email': '邮箱',
+ 'app.settings.basic.email-message': '请输入您的邮箱!',
+ 'app.settings.basic.nickname': '昵称',
+ 'app.settings.basic.nickname-message': '请输入您的昵称!',
+ 'app.settings.basic.profile': '个人简介',
+ 'app.settings.basic.profile-message': '请输入个人简介!',
+ 'app.settings.basic.profile-placeholder': '个人简介',
+ 'app.settings.basic.country': '国家/地区',
+ 'app.settings.basic.country-message': '请输入您的国家或地区!',
+ 'app.settings.basic.geographic': '所在省市',
+ 'app.settings.basic.geographic-message': '请输入您的所在省市!',
+ 'app.settings.basic.address': '街道地址',
+ 'app.settings.basic.address-message': '请输入您的街道地址!',
+ 'app.settings.basic.phone': '联系电话',
+ 'app.settings.basic.phone-message': '请输入您的联系电话!',
+ 'app.settings.basic.update': '更新基本信息',
+ 'app.settings.security.strong': '强',
+ 'app.settings.security.medium': '中',
+ 'app.settings.security.weak': '弱',
+ 'app.settings.security.password': '账户密码',
+ 'app.settings.security.password-description': '当前密码强度',
+ 'app.settings.security.phone': '密保手机',
+ 'app.settings.security.phone-description': '已绑定手机',
+ 'app.settings.security.question': '密保问题',
+ 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
+ 'app.settings.security.email': '备用邮箱',
+ 'app.settings.security.email-description': '已绑定邮箱',
+ 'app.settings.security.mfa': 'MFA 设备',
+ 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
+ 'app.settings.security.modify': '修改',
+ 'app.settings.security.set': '设置',
+ 'app.settings.security.bind': '绑定',
+ 'app.settings.binding.taobao': '绑定淘宝',
+ 'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
+ 'app.settings.binding.alipay': '绑定支付宝',
+ 'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
+ 'app.settings.binding.dingding': '绑定钉钉',
+ 'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
+ 'app.settings.binding.bind': '绑定',
+ 'app.settings.notification.password': '账户密码',
+ 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
+ 'app.settings.notification.messages': '系统消息',
+ 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
+ 'app.settings.notification.todo': '待办任务',
+ 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
+ 'app.settings.open': '开',
+ 'app.settings.close': '关',
+};
diff --git a/src/locales/zh-CN/system/config.ts b/src/locales/zh-CN/system/config.ts
new file mode 100644
index 0000000..5e1e764
--- /dev/null
+++ b/src/locales/zh-CN/system/config.ts
@@ -0,0 +1,14 @@
+export default {
+ 'system.config.title': '参数配置',
+ 'system.config.config_id': '参数主键',
+ 'system.config.config_name': '参数名称',
+ 'system.config.config_key': '参数键名',
+ 'system.config.config_value': '参数键值',
+ 'system.config.config_type': '系统内置',
+ 'system.config.create_by': '创建者',
+ 'system.config.create_time': '创建时间',
+ 'system.config.update_by': '更新者',
+ 'system.config.update_time': '更新时间',
+ 'system.config.remark': '备注',
+ 'system.config.refreshCache': '刷新缓存',
+};
diff --git a/src/locales/zh-CN/system/dept.ts b/src/locales/zh-CN/system/dept.ts
new file mode 100644
index 0000000..7774f2c
--- /dev/null
+++ b/src/locales/zh-CN/system/dept.ts
@@ -0,0 +1,18 @@
+export default {
+ 'system.dept.title': '部门',
+ 'system.dept.dept_id': '部门id',
+ 'system.dept.parent_id': '父部门id',
+ 'system.dept.parent_dept': '上级部门',
+ 'system.dept.ancestors': '祖级列表',
+ 'system.dept.dept_name': '部门名称',
+ 'system.dept.order_num': '显示顺序',
+ 'system.dept.leader': '负责人',
+ 'system.dept.phone': '联系电话',
+ 'system.dept.email': '邮箱',
+ 'system.dept.status': '部门状态',
+ 'system.dept.del_flag': '删除标志',
+ 'system.dept.create_by': '创建者',
+ 'system.dept.create_time': '创建时间',
+ 'system.dept.update_by': '更新者',
+ 'system.dept.update_time': '更新时间',
+};
diff --git a/src/locales/zh-CN/system/dict-data.ts b/src/locales/zh-CN/system/dict-data.ts
new file mode 100644
index 0000000..db2c742
--- /dev/null
+++ b/src/locales/zh-CN/system/dict-data.ts
@@ -0,0 +1,17 @@
+export default {
+ 'system.dict.data.title': '字典数据',
+ 'system.dict.data.dict_code': '字典编码',
+ 'system.dict.data.dict_sort': '字典排序',
+ 'system.dict.data.dict_label': '字典标签',
+ 'system.dict.data.dict_value': '字典键值',
+ 'system.dict.data.dict_type': '字典类型',
+ 'system.dict.data.css_class': '样式属性',
+ 'system.dict.data.list_class': '回显样式',
+ 'system.dict.data.is_default': '是否默认',
+ 'system.dict.data.status': '状态',
+ 'system.dict.data.create_by': '创建者',
+ 'system.dict.data.create_time': '创建时间',
+ 'system.dict.data.update_by': '更新者',
+ 'system.dict.data.update_time': '更新时间',
+ 'system.dict.data.remark': '备注',
+};
diff --git a/src/locales/zh-CN/system/dict.ts b/src/locales/zh-CN/system/dict.ts
new file mode 100644
index 0000000..cf00f66
--- /dev/null
+++ b/src/locales/zh-CN/system/dict.ts
@@ -0,0 +1,12 @@
+export default {
+ 'system.dict.title': '字典类型',
+ 'system.dict.dict_id': '字典主键',
+ 'system.dict.dict_name': '字典名称',
+ 'system.dict.dict_type': '字典类型',
+ 'system.dict.status': '状态',
+ 'system.dict.create_by': '创建者',
+ 'system.dict.create_time': '创建时间',
+ 'system.dict.update_by': '更新者',
+ 'system.dict.update_time': '更新时间',
+ 'system.dict.remark': '备注',
+};
diff --git a/src/locales/zh-CN/system/menu.ts b/src/locales/zh-CN/system/menu.ts
new file mode 100644
index 0000000..8a01e58
--- /dev/null
+++ b/src/locales/zh-CN/system/menu.ts
@@ -0,0 +1,22 @@
+export default {
+ 'system.menu.title': '菜单权限',
+ 'system.menu.menu_id': '菜单编号',
+ 'system.menu.menu_name': '菜单名称',
+ 'system.menu.parent_id': '上级菜单',
+ 'system.menu.order_num': '显示顺序',
+ 'system.menu.path': '路由地址',
+ 'system.menu.component': '组件路径',
+ 'system.menu.query': '路由参数',
+ 'system.menu.is_frame': '是否为外链',
+ 'system.menu.is_cache': '是否缓存',
+ 'system.menu.menu_type': '菜单类型',
+ 'system.menu.visible': '显示状态',
+ 'system.menu.status': '菜单状态',
+ 'system.menu.perms': '权限标识',
+ 'system.menu.icon': '菜单图标',
+ 'system.menu.create_by': '创建者',
+ 'system.menu.create_time': '创建时间',
+ 'system.menu.update_by': '更新者',
+ 'system.menu.update_time': '更新时间',
+ 'system.menu.remark': '备注',
+};
diff --git a/src/locales/zh-CN/system/notice.ts b/src/locales/zh-CN/system/notice.ts
new file mode 100644
index 0000000..ad55d51
--- /dev/null
+++ b/src/locales/zh-CN/system/notice.ts
@@ -0,0 +1,13 @@
+export default {
+ 'system.notice.title': '通知公告',
+ 'system.notice.notice_id': '公告编号',
+ 'system.notice.notice_title': '公告标题',
+ 'system.notice.notice_type': '公告类型',
+ 'system.notice.notice_content': '公告内容',
+ 'system.notice.status': '公告状态',
+ 'system.notice.create_by': '创建者',
+ 'system.notice.create_time': '创建时间',
+ 'system.notice.update_by': '更新者',
+ 'system.notice.update_time': '更新时间',
+ 'system.notice.remark': '备注',
+};
diff --git a/src/locales/zh-CN/system/post.ts b/src/locales/zh-CN/system/post.ts
new file mode 100644
index 0000000..40e589b
--- /dev/null
+++ b/src/locales/zh-CN/system/post.ts
@@ -0,0 +1,13 @@
+export default {
+ 'system.post.title': '岗位信息',
+ 'system.post.post_id': '岗位编号',
+ 'system.post.post_code': '岗位编码',
+ 'system.post.post_name': '岗位名称',
+ 'system.post.post_sort': '显示顺序',
+ 'system.post.status': '状态',
+ 'system.post.create_by': '创建者',
+ 'system.post.create_time': '创建时间',
+ 'system.post.update_by': '更新者',
+ 'system.post.update_time': '更新时间',
+ 'system.post.remark': '备注',
+};
diff --git a/src/locales/zh-CN/system/role.ts b/src/locales/zh-CN/system/role.ts
new file mode 100644
index 0000000..e88f979
--- /dev/null
+++ b/src/locales/zh-CN/system/role.ts
@@ -0,0 +1,21 @@
+export default {
+ 'system.role.title': '角色信息',
+ 'system.role.role_id': '角色编号',
+ 'system.role.role_name': '角色名称',
+ 'system.role.role_key': '权限字符',
+ 'system.role.role_sort': '显示顺序',
+ 'system.role.data_scope': '数据范围',
+ 'system.role.menu_check_strictly': '菜单树选择项是否关联显示',
+ 'system.role.dept_check_strictly': '部门树选择项是否关联显示',
+ 'system.role.status': '角色状态',
+ 'system.role.del_flag': '删除标志',
+ 'system.role.create_by': '创建者',
+ 'system.role.create_time': '创建时间',
+ 'system.role.update_by': '更新者',
+ 'system.role.update_time': '更新时间',
+ 'system.role.remark': '备注',
+ 'system.role.auth': '菜单权限',
+ 'system.role.auth.user': '选择用户',
+ 'system.role.auth.addUser': '添加用户',
+ 'system.role.auth.cancelAll': '批量取消授权',
+};
diff --git a/src/locales/zh-CN/system/user.ts b/src/locales/zh-CN/system/user.ts
new file mode 100644
index 0000000..7d676d0
--- /dev/null
+++ b/src/locales/zh-CN/system/user.ts
@@ -0,0 +1,31 @@
+export default {
+ 'system.user.title': '用户信息',
+ 'system.user.user_id': '用户编号',
+ 'system.user.dept_name': '部门',
+ 'system.user.user_name': '用户账号',
+ 'system.user.nick_name': '用户昵称',
+ 'system.user.user_type': '用户类型',
+ 'system.user.email': '用户邮箱',
+ 'system.user.phonenumber': '手机号码',
+ 'system.user.sex': '用户性别',
+ 'system.user.avatar': '头像地址',
+ 'system.user.password': '密码',
+ 'system.user.status': '帐号状态',
+ 'system.user.del_flag': '删除标志',
+ 'system.user.login_ip': '最后登录IP',
+ 'system.user.login_date': '最后登录时间',
+ 'system.user.create_by': '创建者',
+ 'system.user.create_time': '创建时间',
+ 'system.user.update_by': '更新者',
+ 'system.user.update_time': '更新时间',
+ 'system.user.remark': '备注',
+ 'system.user.post': '岗位',
+ 'system.user.role': '角色',
+ 'system.user.auth.role': '分配角色',
+ 'system.user.reset.password': '密码重置',
+ 'system.user.modify_info': '编辑用户信息',
+ 'system.user.old_password': '旧密码',
+ 'system.user.new_password': '新密码',
+ 'system.user.confirm_password': '确认密码',
+ 'system.user.modify_avatar': '修改头像',
+};
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
new file mode 100644
index 0000000..6ad5f93
--- /dev/null
+++ b/src/locales/zh-TW.ts
@@ -0,0 +1,20 @@
+import component from './zh-TW/component';
+import globalHeader from './zh-TW/globalHeader';
+import menu from './zh-TW/menu';
+import pwa from './zh-TW/pwa';
+import settingDrawer from './zh-TW/settingDrawer';
+import settings from './zh-TW/settings';
+
+export default {
+ 'navBar.lang': '語言',
+ 'layout.user.link.help': '幫助',
+ 'layout.user.link.privacy': '隱私',
+ 'layout.user.link.terms': '條款',
+ 'app.preview.down.block': '下載此頁面到本地項目',
+ ...globalHeader,
+ ...menu,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+ ...component,
+};
diff --git a/src/locales/zh-TW/component.ts b/src/locales/zh-TW/component.ts
new file mode 100644
index 0000000..ba48e29
--- /dev/null
+++ b/src/locales/zh-TW/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展開',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/src/locales/zh-TW/globalHeader.ts b/src/locales/zh-TW/globalHeader.ts
new file mode 100644
index 0000000..ed58451
--- /dev/null
+++ b/src/locales/zh-TW/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+ 'component.globalHeader.search': '站內搜索',
+ 'component.globalHeader.search.example1': '搜索提示壹',
+ 'component.globalHeader.search.example2': '搜索提示二',
+ 'component.globalHeader.search.example3': '搜索提示三',
+ 'component.globalHeader.help': '使用手冊',
+ 'component.globalHeader.notification': '通知',
+ 'component.globalHeader.notification.empty': '妳已查看所有通知',
+ 'component.globalHeader.message': '消息',
+ 'component.globalHeader.message.empty': '您已讀完所有消息',
+ 'component.globalHeader.event': '待辦',
+ 'component.globalHeader.event.empty': '妳已完成所有待辦',
+ 'component.noticeIcon.clear': '清空',
+ 'component.noticeIcon.cleared': '清空了',
+ 'component.noticeIcon.empty': '暫無資料',
+ 'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/src/locales/zh-TW/menu.ts b/src/locales/zh-TW/menu.ts
new file mode 100644
index 0000000..0ef54c9
--- /dev/null
+++ b/src/locales/zh-TW/menu.ts
@@ -0,0 +1,52 @@
+export default {
+ 'menu.welcome': '歡迎',
+ 'menu.more-blocks': '更多區塊',
+ 'menu.home': '首頁',
+ 'menu.admin': '权限',
+ 'menu.admin.sub-page': '二级管理页',
+ 'menu.login': '登錄',
+ 'menu.register': '註冊',
+ 'menu.register-result': '註冊結果',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': '分析頁',
+ 'menu.dashboard.monitor': '監控頁',
+ 'menu.dashboard.workplace': '工作臺',
+ 'menu.exception.403': '403',
+ 'menu.exception.404': '404',
+ 'menu.exception.500': '500',
+ 'menu.form': '表單頁',
+ 'menu.form.basic-form': '基礎表單',
+ 'menu.form.step-form': '分步表單',
+ 'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
+ 'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
+ 'menu.form.step-form.result': '分步表單(完成)',
+ 'menu.form.advanced-form': '高級表單',
+ 'menu.list': '列表頁',
+ 'menu.list.table-list': '查詢表格',
+ 'menu.list.basic-list': '標淮列表',
+ 'menu.list.card-list': '卡片列表',
+ 'menu.list.search-list': '搜索列表',
+ 'menu.list.search-list.articles': '搜索列表(文章)',
+ 'menu.list.search-list.projects': '搜索列表(項目)',
+ 'menu.list.search-list.applications': '搜索列表(應用)',
+ 'menu.profile': '詳情頁',
+ 'menu.profile.basic': '基礎詳情頁',
+ 'menu.profile.advanced': '高級詳情頁',
+ 'menu.result': '結果頁',
+ 'menu.result.success': '成功頁',
+ 'menu.result.fail': '失敗頁',
+ 'menu.exception': '异常页',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': '触发错误',
+ 'menu.account': '個人頁',
+ 'menu.account.center': '個人中心',
+ 'menu.account.settings': '個人設置',
+ 'menu.account.trigger': '觸發報錯',
+ 'menu.account.logout': '退出登錄',
+ 'menu.editor': '圖形編輯器',
+ 'menu.editor.flow': '流程編輯器',
+ 'menu.editor.mind': '腦圖編輯器',
+ 'menu.editor.koni': '拓撲編輯器',
+};
diff --git a/src/locales/zh-TW/pages.ts b/src/locales/zh-TW/pages.ts
new file mode 100644
index 0000000..f9e265b
--- /dev/null
+++ b/src/locales/zh-TW/pages.ts
@@ -0,0 +1,68 @@
+export default {
+ 'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範',
+ 'pages.login.accountLogin.tab': '賬戶密碼登錄',
+ 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/admin123)',
+ 'pages.login.failure': '登錄失敗,請重試!',
+ 'pages.login.success': '登錄成功!',
+ 'pages.login.username.placeholder': '用戶名: admin',
+ 'pages.login.username.required': '用戶名是必填項!',
+ 'pages.login.password.placeholder': '密碼: admin123',
+ '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.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.edit': '編輯',
+ 'pages.searchTable.delete': '刪除',
+ 'pages.searchTable.export': '導出',
+ 'pages.searchTable.chosen': '已選擇',
+ 'pages.searchTable.item': '項',
+ 'pages.searchTable.totalServiceCalls': '服務調用次數總計',
+ 'pages.searchTable.tenThousand': '萬',
+ 'pages.searchTable.batchDeletion': '批量刪除',
+ 'pages.searchTable.batchApproval': '批量審批',
+};
diff --git a/src/locales/zh-TW/pwa.ts b/src/locales/zh-TW/pwa.ts
new file mode 100644
index 0000000..108a6e4
--- /dev/null
+++ b/src/locales/zh-TW/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': '當前處於離線狀態',
+ 'app.pwa.serviceworker.updated': '有新內容',
+ 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
+ 'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/src/locales/zh-TW/settingDrawer.ts b/src/locales/zh-TW/settingDrawer.ts
new file mode 100644
index 0000000..454da28
--- /dev/null
+++ b/src/locales/zh-TW/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': '整體風格設置',
+ 'app.setting.pagestyle.dark': '暗色菜單風格',
+ 'app.setting.pagestyle.light': '亮色菜單風格',
+ 'app.setting.content-width': '內容區域寬度',
+ 'app.setting.content-width.fixed': '定寬',
+ 'app.setting.content-width.fluid': '流式',
+ 'app.setting.themecolor': '主題色',
+ 'app.setting.themecolor.dust': '薄暮',
+ 'app.setting.themecolor.volcano': '火山',
+ 'app.setting.themecolor.sunset': '日暮',
+ 'app.setting.themecolor.cyan': '明青',
+ 'app.setting.themecolor.green': '極光綠',
+ 'app.setting.themecolor.daybreak': '拂曉藍(默認)',
+ 'app.setting.themecolor.geekblue': '極客藍',
+ 'app.setting.themecolor.purple': '醬紫',
+ 'app.setting.navigationmode': '導航模式',
+ 'app.setting.sidemenu': '側邊菜單布局',
+ 'app.setting.topmenu': '頂部菜單布局',
+ 'app.setting.fixedheader': '固定 Header',
+ 'app.setting.fixedsidebar': '固定側邊菜單',
+ 'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
+ 'app.setting.hideheader': '下滑時隱藏 Header',
+ 'app.setting.hideheader.hint': '固定 Header 時可配置',
+ 'app.setting.othersettings': '其他設置',
+ 'app.setting.weakmode': '色弱模式',
+ 'app.setting.copy': '拷貝設置',
+ 'app.setting.copyinfo': '拷貝成功,請到 config/defaultSettings.js 中替換默認配置',
+ 'app.setting.production.hint':
+ '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
+};
diff --git a/src/locales/zh-TW/settings.ts b/src/locales/zh-TW/settings.ts
new file mode 100644
index 0000000..dd45151
--- /dev/null
+++ b/src/locales/zh-TW/settings.ts
@@ -0,0 +1,55 @@
+export default {
+ 'app.settings.menuMap.basic': '基本設置',
+ 'app.settings.menuMap.security': '安全設置',
+ 'app.settings.menuMap.binding': '賬號綁定',
+ 'app.settings.menuMap.notification': '新消息通知',
+ 'app.settings.basic.avatar': '頭像',
+ 'app.settings.basic.change-avatar': '更換頭像',
+ 'app.settings.basic.email': '郵箱',
+ 'app.settings.basic.email-message': '請輸入您的郵箱!',
+ 'app.settings.basic.nickname': '昵稱',
+ 'app.settings.basic.nickname-message': '請輸入您的昵稱!',
+ 'app.settings.basic.profile': '個人簡介',
+ 'app.settings.basic.profile-message': '請輸入個人簡介!',
+ 'app.settings.basic.profile-placeholder': '個人簡介',
+ 'app.settings.basic.country': '國家/地區',
+ 'app.settings.basic.country-message': '請輸入您的國家或地區!',
+ 'app.settings.basic.geographic': '所在省市',
+ 'app.settings.basic.geographic-message': '請輸入您的所在省市!',
+ 'app.settings.basic.address': '街道地址',
+ 'app.settings.basic.address-message': '請輸入您的街道地址!',
+ 'app.settings.basic.phone': '聯系電話',
+ 'app.settings.basic.phone-message': '請輸入您的聯系電話!',
+ 'app.settings.basic.update': '更新基本信息',
+ 'app.settings.security.strong': '強',
+ 'app.settings.security.medium': '中',
+ 'app.settings.security.weak': '弱',
+ 'app.settings.security.password': '賬戶密碼',
+ 'app.settings.security.password-description': '當前密碼強度',
+ 'app.settings.security.phone': '密保手機',
+ 'app.settings.security.phone-description': '已綁定手機',
+ 'app.settings.security.question': '密保問題',
+ 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
+ 'app.settings.security.email': '備用郵箱',
+ 'app.settings.security.email-description': '已綁定郵箱',
+ 'app.settings.security.mfa': 'MFA 設備',
+ 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
+ 'app.settings.security.modify': '修改',
+ 'app.settings.security.set': '設置',
+ 'app.settings.security.bind': '綁定',
+ 'app.settings.binding.taobao': '綁定淘寶',
+ 'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
+ 'app.settings.binding.alipay': '綁定支付寶',
+ 'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
+ 'app.settings.binding.dingding': '綁定釘釘',
+ 'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
+ 'app.settings.binding.bind': '綁定',
+ 'app.settings.notification.password': '賬戶密碼',
+ 'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
+ 'app.settings.notification.messages': '系統消息',
+ 'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
+ 'app.settings.notification.todo': '待辦任務',
+ 'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
+ 'app.settings.open': '開',
+ 'app.settings.close': '關',
+};
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Ant Design Pro",
+ "short_name": "Ant Design Pro",
+ "display": "standalone",
+ "start_url": "./?utm_source=homescreen",
+ "theme_color": "#002140",
+ "background_color": "#001529",
+ "icons": [
+ {
+ "src": "icons/icon-192x192.png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "icons/icon-128x128.png",
+ "sizes": "128x128"
+ },
+ {
+ "src": "icons/icon-512x512.png",
+ "sizes": "512x512"
+ }
+ ]
+}
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
new file mode 100644
index 0000000..0263687
--- /dev/null
+++ b/src/pages/404.tsx
@@ -0,0 +1,18 @@
+import { history } from '@umijs/max';
+import { Button, Result } from 'antd';
+import React from 'react';
+
+const NoFoundPage: React.FC = () => (
+ <Result
+ status="404"
+ title="404"
+ subTitle="Sorry, the page you visited does not exist."
+ extra={
+ <Button type="primary" onClick={() => history.push('/')}>
+ Back Home
+ </Button>
+ }
+ />
+);
+
+export default NoFoundPage;
diff --git a/src/pages/Monitor/Cache/List.tsx b/src/pages/Monitor/Cache/List.tsx
new file mode 100644
index 0000000..af6147a
--- /dev/null
+++ b/src/pages/Monitor/Cache/List.tsx
@@ -0,0 +1,240 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { clearCacheAll, clearCacheKey, clearCacheName, getCacheValue, listCacheKey, listCacheName } from '@/services/monitor/cachelist';
+import { Button, Card, Col, Form, FormInstance, Input, message, Row, Table } from 'antd';
+import styles from './index.less';
+import { FormattedMessage } from '@umijs/max';
+import { ReloadOutlined } from '@ant-design/icons';
+import { ProForm } from '@ant-design/pro-components';
+
+const { TextArea } = Input;
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2022/06/27
+ *
+ * */
+
+
+
+const CacheList: React.FC = () => {
+ const [cacheNames, setCacheNames] = useState<any>([]);
+ const [currentCacheName, setCurrentCacheName] = useState<any>([]);
+ const [cacheKeys, setCacheKeys] = useState<any>([]);
+ const [form] = Form.useForm();
+
+ const getCacheNames = () => {
+ listCacheName().then((res) => {
+ if (res.code === 200) {
+ setCacheNames(res.data);
+ }
+ });
+ }
+
+ useEffect(() => {
+ getCacheNames();
+ }, []);
+
+ const getCacheKeys = (cacheName: string) => {
+ listCacheKey(cacheName).then(res => {
+ if (res.code === 200) {
+ let index = 0;
+ const keysData = res.data.map((item: any) => {
+ return {
+ index: index++,
+ cacheKey: item
+ }
+ })
+ setCacheKeys(keysData);
+ }
+ });
+ };
+
+ const onClearAll = async () => {
+ clearCacheAll().then(res => {
+ if(res.code === 200) {
+ message.success("清理全部缓存成功");
+ }
+ });
+ };
+
+ const onClearAllFailed = (errorInfo: any) => {
+ message.error('Failed:', errorInfo);
+ };
+
+ const refreshCacheNames = () => {
+ getCacheNames();
+ message.success("刷新缓存列表成功");
+ };
+
+ const refreshCacheKeys = () => {
+ getCacheKeys(currentCacheName);
+ message.success("刷新键名列表成功");
+ };
+
+ const columns = [
+ {
+ title: '缓存名称',
+ dataIndex: 'cacheName',
+ key: 'cacheName',
+ render: (_: any, record: any) => {
+ return record.cacheName.replace(":", "");
+ }
+ },
+ {
+ title: '备注',
+ dataIndex: 'remark',
+ key: 'remark',
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '40px',
+ valueType: 'option',
+ render: (_: any, record: API.Monitor.CacheContent) => [
+ <Button
+ type="link"
+ size="small"
+ key="remove"
+ danger
+ onClick={() => {
+ clearCacheName(record.cacheName).then(res => {
+ if(res.code === 200) {
+ message.success("清理缓存名称[" + record.cacheName + "]成功");
+ getCacheKeys(currentCacheName);
+ }
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ ]
+ }
+ ];
+
+ const cacheKeysColumns = [
+ {
+ title: '序号',
+ dataIndex: 'index',
+ key: 'index'
+ },
+ {
+ title: '缓存键名',
+ dataIndex: 'cacheKey',
+ key: 'cacheKey',
+ render: (_: any, record: any) => {
+ return record.cacheKey.replace(currentCacheName, "");
+ }
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '40px',
+ valueType: 'option',
+ render: (_: any, record: API.Monitor.CacheContent) => [
+ <Button
+ type="link"
+ size="small"
+ key="remove"
+ danger
+ onClick={() => {
+ console.log(record)
+ clearCacheKey(record.cacheKey).then(res => {
+ if(res.code === 200) {
+ message.success("清理缓存键名[" + record.cacheKey + "]成功");
+ getCacheKeys(currentCacheName);
+ }
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ ]
+ }
+ ];
+
+ return (
+ <div>
+ <Row gutter={[24, 24]}>
+ <Col span={8}>
+ <Card title="缓存列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheNames()}} type="link" />} className={styles.card}>
+ <Table
+ rowKey="cacheName"
+ dataSource={cacheNames}
+ columns={columns}
+ onRow={(record: API.Monitor.CacheContent) => {
+ return {
+ onClick: () => {
+ setCurrentCacheName(record.cacheName);
+ getCacheKeys(record.cacheName);
+ },
+ };
+ }}
+ />
+ </Card>
+ </Col>
+ <Col span={8}>
+ <Card title="键名列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheKeys()}} type="link" />} className={styles.card}>
+ <Table
+ rowKey="index"
+ dataSource={cacheKeys}
+ columns={cacheKeysColumns}
+ onRow={(record: any) => {
+ return {
+ onClick: () => {
+ getCacheValue(currentCacheName, record.cacheKey).then(res => {
+ if (res.code === 200) {
+ form.resetFields();
+ form.setFieldsValue({
+ cacheName: res.data.cacheName,
+ cacheKey: res.data.cacheKey,
+ cacheValue: res.data.cacheValue,
+ remark: res.data.remark,
+ });
+ }
+ });
+ },
+ };
+ }}
+ />
+ </Card>
+ </Col>
+ <Col span={8}>
+ <Card title="缓存内容" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ onClearAll()}} type="link" >清理全部</Button>} className={styles.card}>
+ <ProForm
+ name="basic"
+ form={form}
+ labelCol={{ span: 8 }}
+ wrapperCol={{ span: 16 }}
+ onFinish={onClearAll}
+ onFinishFailed={onClearAllFailed}
+ autoComplete="off"
+ >
+ <Form.Item
+ label="缓存名称"
+ name="cacheName"
+ >
+ <Input />
+ </Form.Item>
+ <Form.Item
+ label="缓存键名"
+ name="cacheKey"
+ >
+ <Input />
+ </Form.Item>
+ <Form.Item
+ label="缓存内容"
+ name="cacheValue"
+ >
+ <TextArea autoSize={{ minRows: 2 }} />
+ </Form.Item>
+ </ProForm>
+ </Card>
+ </Col>
+ </Row>
+ </div>
+ );
+};
+
+export default CacheList;
diff --git a/src/pages/Monitor/Cache/List/index.less b/src/pages/Monitor/Cache/List/index.less
new file mode 100644
index 0000000..0c5aace
--- /dev/null
+++ b/src/pages/Monitor/Cache/List/index.less
@@ -0,0 +1,10 @@
+
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
diff --git a/src/pages/Monitor/Cache/index.less b/src/pages/Monitor/Cache/index.less
new file mode 100644
index 0000000..7112c50
--- /dev/null
+++ b/src/pages/Monitor/Cache/index.less
@@ -0,0 +1,33 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+
+.card {
+ margin-bottom: 12px;
+}
+
+
+.miniChart {
+ position: relative;
+ width: 100%;
+ .chartContent {
+ position: absolute;
+ bottom: -28px;
+ width: 100%;
+ > div {
+ margin: 0 -5px;
+ overflow: hidden;
+ }
+ }
+ .chartLoading {
+ position: absolute;
+ top: 16px;
+ left: 50%;
+ margin-left: -7px;
+ }
+}
diff --git a/src/pages/Monitor/Cache/index.tsx b/src/pages/Monitor/Cache/index.tsx
new file mode 100644
index 0000000..4c247ee
--- /dev/null
+++ b/src/pages/Monitor/Cache/index.tsx
@@ -0,0 +1,201 @@
+import React, { useEffect, useState } from 'react';
+import { Card, Col, Row, Table } from 'antd';
+import { DataItem } from '@antv/g2plot/esm/interface/config';
+import { Gauge, Pie } from '@ant-design/plots';
+import styles from './index.less';
+import { getCacheInfo } from '@/services/monitor/cache';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+
+const columns = [
+ {
+ title: 'col1',
+ dataIndex: 'col1',
+ key: 'col1',
+ },
+ {
+ title: 'col2',
+ dataIndex: 'col2',
+ key: 'col2',
+ },
+ {
+ title: 'col3',
+ dataIndex: 'col3',
+ key: 'col3',
+ },
+ {
+ title: 'col4',
+ dataIndex: 'col4',
+ key: 'col4',
+ },
+ {
+ title: 'col5',
+ dataIndex: 'col5',
+ key: 'col5',
+ },
+ {
+ title: 'col6',
+ dataIndex: 'col6',
+ key: 'col6',
+ },
+ {
+ title: 'col7',
+ dataIndex: 'col7',
+ key: 'col7',
+ },
+ {
+ title: 'col8',
+ dataIndex: 'col8',
+ key: 'col8',
+ },
+];
+
+const usageFormatter = (val: string): string => {
+ switch (val) {
+ case '10':
+ return '100%';
+ case '8':
+ return '80%';
+ case '6':
+ return '60%';
+ case '4':
+ return '40%';
+ case '2':
+ return '20%';
+ case '0':
+ return '0%';
+ default:
+ return '';
+ }
+};
+
+const CacheInfo: React.FC = () => {
+ const [baseInfoData, setBaseInfoData] = useState<any>([]);
+ const [memUsage, setMemUsage] = useState<Number>(0);
+ const [memUsageTitle, setMemUsageTitle] = useState<any>([]);
+ const [cmdInfoData, setCmdInfoData] = useState<DataItem[]>([]);
+ useEffect(() => {
+ getCacheInfo().then((res) => {
+ if (res.code === 200) {
+ const baseinfo = [];
+ baseinfo.push({
+ col1: 'Redis版本',
+ col2: res.data.info.redis_version,
+ col3: '运行模式',
+ col4: res.data.info.redis_mode === 'standalone' ? '单机' : '集群',
+ col5: '端口',
+ col6: res.data.info.tcp_port,
+ col7: '客户端数',
+ col8: res.data.info.connected_clients,
+ });
+ baseinfo.push({
+ col1: '运行时间(天)',
+ col2: res.data.info.uptime_in_days,
+ col3: '使用内存',
+ col4: res.data.info.used_memory_human,
+ col5: '使用CPU',
+ col6: `${res.data.info.used_cpu_user_children}%`,
+ col7: '内存配置',
+ col8: res.data.info.maxmemory_human,
+ });
+ baseinfo.push({
+ col1: 'AOF是否开启',
+ col2: res.data.info.aof_enabled === '0' ? '否' : '是',
+ col3: 'RDB是否成功',
+ col4: res.data.info.rdb_last_bgsave_status,
+ col5: 'Key数量',
+ col6: res.data.dbSize,
+ col7: '网络入口/出口',
+ col8: `${res.data.info.instantaneous_input_kbps}/${res.data.info.instantaneous_output_kbps}kps`,
+ });
+ setBaseInfoData(baseinfo);
+
+ const data: VisitDataType[] = res.data.commandStats.map((item) => {
+ return {
+ x: item.name,
+ y: Number(item.value),
+ };
+ });
+
+ setCmdInfoData(data);
+ setMemUsageTitle(res.data.info.used_memory_human);
+ setMemUsage(res.data.info.used_memory / res.data.info.total_system_memory);
+ }
+ });
+ }, []);
+
+ return (
+ <div>
+ <Row gutter={[24, 24]}>
+ <Col span={24}>
+ <Card title="基本信息" className={styles.card}>
+ <Table
+ rowKey="col1"
+ pagination={false}
+ showHeader={false}
+ dataSource={baseInfoData}
+ columns={columns}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={[24, 24]}>
+ <Col span={12}>
+ <Card title="命令统计" className={styles.card}>
+ <Pie
+ height={320}
+ radius={0.8}
+ innerRadius={0.5}
+ angleField="y"
+ colorField="x"
+ data={cmdInfoData as any}
+ legend={false}
+ label={{
+ position: 'spider',
+ text: (item: { x: number; y: number }) => {
+ return `${item.x}: ${item.y}`;
+ },
+ }}
+ />
+ </Card>
+ </Col>
+ <Col span={12}>
+ <Card title="内存信息" className={styles.card}>
+ <Gauge
+ title={memUsageTitle}
+ height={320}
+ percent={memUsage}
+ formatter={usageFormatter}
+ padding={-16}
+ style={{
+ textContent: () => { return memUsage.toFixed(2).toString() + '%'},
+ }}
+ data={
+ {
+ target: memUsage,
+ total: 100,
+ name: 'score',
+ thresholds: [20, 40, 60, 80, 100],
+ } as any
+ }
+ meta={{
+ color: {
+ range: ['#C3F71F', '#B5E61D', '#FFC90E', '#FF7F27', '#FF2518'],
+ },
+ }}
+ />
+ </Card>
+ </Col>
+ </Row>
+ </div>
+ );
+};
+
+export default CacheInfo;
diff --git a/src/pages/Monitor/Druid/index.tsx b/src/pages/Monitor/Druid/index.tsx
new file mode 100644
index 0000000..55692d2
--- /dev/null
+++ b/src/pages/Monitor/Druid/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+const DruidInfo: React.FC = () => {
+ useEffect(() => {
+ const frame = document.getElementById('bdIframe');
+ if (frame) {
+ const deviceWidth = document.documentElement.clientWidth;
+ const deviceHeight = document.documentElement.clientHeight;
+ frame.style.width = `${Number(deviceWidth) - 220}px`;
+ frame.style.height = `${Number(deviceHeight) - 120}px`;
+ }
+ });
+
+ return (
+ <iframe
+ style={{ width: '100%', border: '0px', height: '100%' }}
+ src={`/api/druid/login.html`}
+ id="bdIframe"
+ />
+ // </WrapContent>
+ );
+};
+
+export default DruidInfo;
diff --git a/src/pages/Monitor/Job/detail.tsx b/src/pages/Monitor/Job/detail.tsx
new file mode 100644
index 0000000..e27bfb7
--- /dev/null
+++ b/src/pages/Monitor/Job/detail.tsx
@@ -0,0 +1,131 @@
+import React, { useEffect } from 'react';
+import { Modal, Descriptions, Button } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { getValueEnumLabel } from '@/utils/options';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+export type OperlogFormValueType = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type OperlogFormProps = {
+ onCancel: (flag?: boolean, formVals?: OperlogFormValueType) => void;
+ open: boolean;
+ values: Partial<API.Monitor.Job>;
+ statusOptions: DictValueEnumObj;
+};
+
+const OperlogForm: React.FC<OperlogFormProps> = (props) => {
+ const { values, statusOptions } = props;
+
+ useEffect(() => {}, [props]);
+
+ const intl = useIntl();
+
+ const misfirePolicy: any = {
+ '0': '默认策略',
+ '1': '立即执行',
+ '2': '执行一次',
+ '3': '放弃执行',
+ };
+
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+ <Modal
+ width={800}
+ title={intl.formatMessage({
+ id: 'monitor.job.detail',
+ defaultMessage: '操作日志详细信息',
+ })}
+ open={props.open}
+ destroyOnClose
+ onCancel={handleCancel}
+ footer={[
+ <Button key="back" onClick={handleCancel}>
+ 关闭
+ </Button>,
+ ]}
+ >
+ <Descriptions column={24}>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+ >
+ {values.jobId}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+ >
+ {values.jobName}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+ >
+ {values.jobGroup}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.concurrent" defaultMessage="是否并发执行" />}
+ >
+ {values.concurrent === '1' ? '禁止' : '允许'}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={
+ <FormattedMessage id="monitor.job.misfire_policy" defaultMessage="计划执行错误策略" />
+ }
+ >
+ {misfirePolicy[values.misfirePolicy ? values.misfirePolicy : '0']}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.create_time" defaultMessage="创建时间" />}
+ >
+ {values.createTime?.toString()}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.status" defaultMessage="状态" />}
+ >
+ {getValueEnumLabel(statusOptions, values.status, '未知')}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={
+ <FormattedMessage id="monitor.job.next_valid_time" defaultMessage="下次执行时间" />
+ }
+ >
+ {values.nextValidTime}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={
+ <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />
+ }
+ >
+ {values.cronExpression}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={
+ <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />
+ }
+ >
+ {values.invokeTarget}
+ </Descriptions.Item>
+ </Descriptions>
+ </Modal>
+ );
+};
+
+export default OperlogForm;
diff --git a/src/pages/Monitor/Job/edit.tsx b/src/pages/Monitor/Job/edit.tsx
new file mode 100644
index 0000000..e4bfc74
--- /dev/null
+++ b/src/pages/Monitor/Job/edit.tsx
@@ -0,0 +1,232 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormTextArea,
+ ProFormRadio,
+ ProFormSelect,
+ ProFormCaptcha,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictOptionType, DictValueEnumObj } from '@/components/DictTag';
+
+/**
+ * 定时任务调度 Edit Form
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export type JobFormData = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type JobFormProps = {
+ onCancel: (flag?: boolean, formVals?: JobFormData) => void;
+ onSubmit: (values: JobFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.Monitor.Job>;
+ jobGroupOptions: DictOptionType[];
+ statusOptions: DictValueEnumObj;
+};
+
+const JobForm: React.FC<JobFormProps> = (props) => {
+ const [form] = Form.useForm();
+ const { jobGroupOptions, statusOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ jobId: props.values.jobId,
+ jobName: props.values.jobName,
+ jobGroup: props.values.jobGroup,
+ invokeTarget: props.values.invokeTarget,
+ cronExpression: props.values.cronExpression,
+ misfirePolicy: props.values.misfirePolicy,
+ concurrent: props.values.concurrent,
+ status: props.values.status,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ form.resetFields();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as JobFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'monitor.job.title',
+ defaultMessage: '编辑定时任务调度',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="jobId"
+ label={intl.formatMessage({
+ id: 'monitor.job.job_id',
+ defaultMessage: '任务编号',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入任务编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入任务编号!" defaultMessage="请输入任务编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="jobName"
+ label={intl.formatMessage({
+ id: 'monitor.job.job_name',
+ defaultMessage: '任务名称',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入任务名称"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入任务名称!" defaultMessage="请输入任务名称!" />,
+ },
+ ]}
+ />
+ <ProFormSelect
+ name="jobGroup"
+ options={jobGroupOptions}
+ label={intl.formatMessage({
+ id: 'monitor.job.job_group',
+ defaultMessage: '任务组名',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入任务组名"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入任务组名!" defaultMessage="请输入任务组名!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="invokeTarget"
+ label={intl.formatMessage({
+ id: 'monitor.job.invoke_target',
+ defaultMessage: '调用目标字符串',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入调用目标字符串"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入调用目标字符串!" defaultMessage="请输入调用目标字符串!" />,
+ },
+ ]}
+ />
+ <ProFormCaptcha
+ name="cronExpression"
+ label={intl.formatMessage({
+ id: 'monitor.job.cron_expression',
+ defaultMessage: 'cron执行表达式',
+ })}
+ captchaTextRender={() => "生成表达式"}
+ onGetCaptcha={() => {
+ // form.setFieldValue('cronExpression', '0/20 * * * * ?');
+ return new Promise((resolve, reject) => {
+ reject();
+ });
+ }}
+ />
+ <ProFormRadio.Group
+ name="misfirePolicy"
+ label={intl.formatMessage({
+ id: 'monitor.job.misfire_policy',
+ defaultMessage: '计划执行错误策略',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入计划执行错误策略"
+ valueEnum={{
+ 0: '立即执行',
+ 1: '执行一次',
+ 3: '放弃执行'
+ }}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入计划执行错误策略!" defaultMessage="请输入计划执行错误策略!" />,
+ },
+ ]}
+ fieldProps={{
+ optionType: "button",
+ buttonStyle: "solid"
+ }}
+ />
+ <ProFormRadio.Group
+ name="concurrent"
+ label={intl.formatMessage({
+ id: 'monitor.job.concurrent',
+ defaultMessage: '是否并发执行',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入是否并发执行"
+ valueEnum={{
+ 0: '允许',
+ 1: '禁止',
+ }}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入是否并发执行!" defaultMessage="请输入是否并发执行!" />,
+ },
+ ]}
+ fieldProps={{
+ optionType: "button",
+ buttonStyle: "solid"
+ }}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'monitor.job.status',
+ defaultMessage: '状态',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default JobForm;
diff --git a/src/pages/Monitor/Job/index.tsx b/src/pages/Monitor/Job/index.tsx
new file mode 100644
index 0000000..43030d9
--- /dev/null
+++ b/src/pages/Monitor/Job/index.tsx
@@ -0,0 +1,460 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { Dropdown, FormInstance, Space } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getJobList, removeJob, addJob, updateJob, exportJob, runJob } from '@/services/monitor/job';
+import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
+import UpdateForm from './edit';
+import DetailForm from './detail';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Job) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addJob({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Job) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateJob(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Job[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeJob(selectedRows.map((row) => row.jobId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.Job) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.jobId];
+ const resp = await removeJob(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportJob();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const JobTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+ const [detailModalVisible, setDetailModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.Monitor.Job>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.Job[]>([]);
+
+ const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictSelectOption('sys_job_group').then((data) => {
+ setJobGroupOptions(data);
+ });
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.Monitor.Job>[] = [
+ {
+ title: <FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />,
+ dataIndex: 'jobId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />,
+ dataIndex: 'jobName',
+ valueType: 'text',
+ render: (dom, record) => {
+ return (
+ <a
+ onClick={() => {
+ setDetailModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ {dom}
+ </a>
+ );
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />,
+ dataIndex: 'jobGroup',
+ valueType: 'text',
+ valueEnum: jobGroupOptions,
+ render: (_, record) => {
+ return (<DictTag options={jobGroupOptions} value={record.jobGroup} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />,
+ dataIndex: 'invokeTarget',
+ valueType: 'textarea',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />,
+ dataIndex: 'cronExpression',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.status" defaultMessage="状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ icon = <EditOutlined />
+ hidden={!access.hasPerms('monitor:job:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ icon = <DeleteOutlined />
+ hidden={!access.hasPerms('monitor:job:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ <Dropdown
+ key="more"
+ menu={{
+ items: [
+ {
+ label: '执行一次',
+ key: 'runOnce',
+ },
+ {
+ label: '详细',
+ key: 'detail',
+ },
+ {
+ label: '历史',
+ key: 'log',
+ },
+ ],
+ onClick: ({ key }) => {
+ if (key === 'runOnce') {
+ Modal.confirm({
+ title: '警告',
+ content: '确认要立即执行一次?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await runJob(record.jobId, record.jobGroup);
+ if (success) {
+ message.success('执行成功');
+ }
+ },
+ });
+ }
+ else if (key === 'detail') {
+ setDetailModalVisible(true);
+ setCurrentRow(record);
+ }
+ else if( key === 'log') {
+ history.push(`/monitor/job-log/index/${record.jobId}`);
+ }
+ }
+ }}
+ >
+ <a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
+ <Space>
+ <DownOutlined />
+ 更多
+ </Space>
+ </a>
+ </Dropdown>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.Job>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="jobId"
+ key="jobList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('monitor:job:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('monitor:job:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getJobList({ ...params } as API.Monitor.JobListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('monitor:job:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.jobId) {
+ success = await handleUpdate({ ...values } as API.Monitor.Job);
+ } else {
+ success = await handleAdd({ ...values } as API.Monitor.Job);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ jobGroupOptions={jobGroupOptions||{}}
+ statusOptions={statusOptions}
+ />
+ <DetailForm
+ onCancel={() => {
+ setDetailModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={detailModalVisible}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default JobTableList;
diff --git a/src/pages/Monitor/JobLog/detail.tsx b/src/pages/Monitor/JobLog/detail.tsx
new file mode 100644
index 0000000..560889c
--- /dev/null
+++ b/src/pages/Monitor/JobLog/detail.tsx
@@ -0,0 +1,105 @@
+import { getValueEnumLabel } from '@/utils/options';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Descriptions, Modal } from 'antd';
+import React, { useEffect } from 'react';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+export type JobLogFormValueType = Record<string, unknown> & Partial<API.Monitor.JobLog>;
+
+export type JobLogFormProps = {
+ onCancel: (flag?: boolean, formVals?: JobLogFormValueType) => void;
+ open: boolean;
+ values: Partial<API.Monitor.JobLog>;
+ statusOptions: DictValueEnumObj;
+ jobGroupOptions: DictValueEnumObj;
+};
+
+const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => {
+
+ const { values, statusOptions, jobGroupOptions } = props;
+
+ useEffect(() => {
+ }, []);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'monitor.job.log.title',
+ defaultMessage: '定时任务调度日志',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <Descriptions column={24}>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+ >
+ {values.jobLogId}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.create_time" defaultMessage="执行时间" />}
+ >
+ {values.createTime?.toString()}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+ >
+ {values.jobName}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+ >
+ {getValueEnumLabel(jobGroupOptions, values.jobGroup, '无')}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标" />}
+ >
+ {values.invokeTarget}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />}
+ >
+ {values.jobMessage}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.job.log.exception_info" defaultMessage="异常信息" />}
+ >
+ {values.exceptionInfo}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.job.status" defaultMessage="执行状态" />}
+ >
+ {getValueEnumLabel(statusOptions, values.status, '未知')}
+ </Descriptions.Item>
+ </Descriptions>
+ </Modal>
+ );
+};
+
+export default JobLogDetailForm;
diff --git a/src/pages/Monitor/JobLog/index.tsx b/src/pages/Monitor/JobLog/index.tsx
new file mode 100644
index 0000000..46e3313
--- /dev/null
+++ b/src/pages/Monitor/JobLog/index.tsx
@@ -0,0 +1,343 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, useParams, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getJobLogList, removeJobLog, exportJobLog } from '@/services/monitor/jobLog';
+import DetailForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import { getJob } from '@/services/monitor/job';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度日志 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.JobLog[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeJobLog(selectedRows.map((row) => row.jobLogId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.JobLog) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.jobLogId];
+ const resp = await removeJobLog(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 清空日志数据
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportJobLog();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const JobLogTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalOpen, setModalOpen] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.Monitor.JobLog>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.JobLog[]>([]);
+
+ const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const [queryParams, setQueryParams] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ const params = useParams();
+ if (params.id === undefined) {
+ history.push('/monitor/job');
+ }
+ const jobId = params.id || 0;
+ useEffect(() => {
+ if (jobId !== undefined && jobId !== 0) {
+ getJob(Number(jobId)).then(response => {
+ setQueryParams({
+ jobName: response.data.jobName,
+ jobGroup: response.data.jobGroup
+ });
+ });
+ }
+ getDictValueEnum('sys_job_status').then((data) => {
+ setStatusOptions(data);
+ });
+ getDictValueEnum('sys_job_group').then((data) => {
+ setJobGroupOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.Monitor.JobLog>[] = [
+ {
+ title: <FormattedMessage id="monitor.job.log.job_log_id" defaultMessage="任务日志编号" />,
+ dataIndex: 'jobLogId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.job_name" defaultMessage="任务名称" />,
+ dataIndex: 'jobName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.job_group" defaultMessage="任务组名" />,
+ dataIndex: 'jobGroup',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.invoke_target" defaultMessage="调用目标字符串" />,
+ dataIndex: 'invokeTarget',
+ valueType: 'textarea',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />,
+ dataIndex: 'jobMessage',
+ valueType: 'textarea',
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.status" defaultMessage="执行状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.job.log.create_time" defaultMessage="异常信息" />,
+ dataIndex: 'createTime',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('monitor:job-log:edit')}
+ onClick={() => {
+ setModalOpen(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('monitor:job-log:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.JobLog>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="jobLogId"
+ key="job-logList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('monitor:job-log:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalOpen(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job-log:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('monitor:job-log:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ params={queryParams}
+ request={(params) =>
+ getJobLogList({ ...params } as API.Monitor.JobLogListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('monitor:job-log:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <DetailForm
+ onCancel={() => {
+ setModalOpen(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalOpen}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ jobGroupOptions={jobGroupOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default JobLogTableList;
diff --git a/src/pages/Monitor/Logininfor/edit.tsx b/src/pages/Monitor/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/src/pages/Monitor/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTimePicker,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+ onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+ onSubmit: (values: LogininforFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.Monitor.Logininfor>;
+ statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions, } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ infoId: props.values.infoId,
+ userName: props.values.userName,
+ ipaddr: props.values.ipaddr,
+ loginLocation: props.values.loginLocation,
+ browser: props.values.browser,
+ os: props.values.os,
+ status: props.values.status,
+ msg: props.values.msg,
+ loginTime: props.values.loginTime,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ form.resetFields();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as LogininforFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.logininfor.title',
+ defaultMessage: '编辑系统访问记录',
+ })}
+ open={props.open}
+ destroyOnClose
+ forceRender
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="infoId"
+ label={intl.formatMessage({
+ id: 'system.logininfor.info_id',
+ defaultMessage: '访问编号',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入访问编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="userName"
+ label={intl.formatMessage({
+ id: 'system.logininfor.user_name',
+ defaultMessage: '用户账号',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入用户账号"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="ipaddr"
+ label={intl.formatMessage({
+ id: 'system.logininfor.ipaddr',
+ defaultMessage: '登录IP地址',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录IP地址"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="loginLocation"
+ label={intl.formatMessage({
+ id: 'system.logininfor.login_location',
+ defaultMessage: '登录地点',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录地点"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="browser"
+ label={intl.formatMessage({
+ id: 'system.logininfor.browser',
+ defaultMessage: '浏览器类型',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入浏览器类型"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="os"
+ label={intl.formatMessage({
+ id: 'system.logininfor.os',
+ defaultMessage: '操作系统',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入操作系统"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.logininfor.status',
+ defaultMessage: '登录状态',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="msg"
+ label={intl.formatMessage({
+ id: 'system.logininfor.msg',
+ defaultMessage: '提示消息',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入提示消息"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,
+ },
+ ]}
+ />
+ <ProFormTimePicker
+ name="loginTime"
+ label={intl.formatMessage({
+ id: 'system.logininfor.login_time',
+ defaultMessage: '访问时间',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入访问时间"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default LogininforForm;
diff --git a/src/pages/Monitor/Logininfor/index.tsx b/src/pages/Monitor/Logininfor/index.tsx
new file mode 100644
index 0000000..5a39281
--- /dev/null
+++ b/src/pages/Monitor/Logininfor/index.tsx
@@ -0,0 +1,335 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleClean = async () => {
+ const hide = message.loading('请稍候');
+ try {
+ const resp = await cleanLogininfor();
+ hide();
+ if (resp.code === 200) {
+ message.success('清空成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('请求失败,请重试');
+ return false;
+ }
+};
+
+const handleUnlock = async (userName: string) => {
+ const hide = message.loading('正在解锁');
+ try {
+ const resp = await unlockLogininfor(userName);
+ hide();
+ if (resp.code === 200) {
+ message.success('解锁成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('解锁失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportLogininfor();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const LogininforTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const actionRef = useRef<ActionType>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+
+ const access = useAccess();
+
+ const statusOptions = {
+ 0: {
+ label: '成功',
+ key: '0',
+ value: '0',
+ text: '成功',
+ status: 'success',
+ listClass: 'success'
+ },
+ 1: {
+ label: '失败',
+ key: '1',
+ value: '1',
+ text: '失败',
+ status: 'error',
+ listClass: 'danger'
+ },
+ };
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+
+ }, []);
+
+ const columns: ProColumns<API.Monitor.Logininfor>[] = [
+ {
+ title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+ dataIndex: 'infoId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+ dataIndex: 'ipaddr',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+ dataIndex: 'loginLocation',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+ dataIndex: 'browser',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+ dataIndex: 'os',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+ dataIndex: 'msg',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+ dataIndex: 'loginTime',
+ valueType: 'dateTime',
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.Logininfor>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="infoId"
+ key="logininforList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="clean"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认清空所有数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleClean();
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+ </Button>,
+ <Button
+ type="primary"
+ key="unlock"
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认解锁该用户的数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleUnlock(selectedRows[0].userName);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <UnlockOutlined />
+ <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('monitor:logininfor:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ </PageContainer>
+ );
+};
+
+export default LogininforTableList;
diff --git a/src/pages/Monitor/Online/index.tsx b/src/pages/Monitor/Online/index.tsx
new file mode 100644
index 0000000..2f81e3b
--- /dev/null
+++ b/src/pages/Monitor/Online/index.tsx
@@ -0,0 +1,162 @@
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import React, { useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { getOnlineUserList, forceLogout } from '@/services/monitor/online';
+import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
+import { DeleteOutlined } from '@ant-design/icons';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+
+const handleForceLogout = async (selectedRow: API.Monitor.OnlineUserType) => {
+ const hide = message.loading('正在强制下线');
+ try {
+ await forceLogout(selectedRow.tokenId);
+ hide();
+ message.success('强制下线成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('强制下线失败,请重试');
+ return false;
+ }
+};
+
+const OnlineUserTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+ const actionRef = useRef<ActionType>();
+ const access = useAccess();
+ const intl = useIntl();
+
+ useEffect(() => {}, []);
+
+ const columns: ProColumns<API.Monitor.OnlineUserType>[] = [
+ {
+ title: <FormattedMessage id="monitor.online.user.token_id" defaultMessage="会话编号" />,
+ dataIndex: 'tokenId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.dept_name" defaultMessage="部门名称" />,
+ dataIndex: 'deptName',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.ipaddr" defaultMessage="登录IP地址" />,
+ dataIndex: 'ipaddr',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.login_location" defaultMessage="登录地点" />,
+ dataIndex: 'loginLocation',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.browser" defaultMessage="浏览器类型" />,
+ dataIndex: 'browser',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.os" defaultMessage="操作系统" />,
+ dataIndex: 'os',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.online.user.login_time" defaultMessage="登录时间" />,
+ dataIndex: 'loginTime',
+ valueType: 'dateRange',
+ render: (_, record) => <span>{record.loginTime}</span>,
+ hideInSearch: true,
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '60px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ icon=<DeleteOutlined />
+ hidden={!access.hasPerms('monitor:online:forceLogout')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '强踢',
+ content: '确定强制将该用户踢下线吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleForceLogout(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 强退
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.OnlineUserType>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="tokenId"
+ key="logininforList"
+ search={{
+ labelWidth: 120,
+ }}
+ request={(params) =>
+ getOnlineUserList({ ...params } as API.Monitor.OnlineUserListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ />
+ </div>
+ );
+};
+
+export default OnlineUserTableList;
diff --git a/src/pages/Monitor/Operlog/detail.tsx b/src/pages/Monitor/Operlog/detail.tsx
new file mode 100644
index 0000000..f247522
--- /dev/null
+++ b/src/pages/Monitor/Operlog/detail.tsx
@@ -0,0 +1,113 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+ onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+ onSubmit: (values: OperlogFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.Monitor.Operlog>;
+ businessTypeOptions: DictValueEnumObj;
+ operatorTypeOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+ const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+ const intl = useIntl();
+ const handleOk = () => {};
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'monitor.operlog.title',
+ defaultMessage: '编辑操作日志记录',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <Descriptions column={24}>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+ >
+ {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+ >
+ {values.requestMethod}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+ >
+ {`${values.operName}/${values.operIp}`}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+ >
+ {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+ >
+ {values.method}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+ >
+ {values.operUrl}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+ >
+ {values.operParam}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+ >
+ {values.jsonResult}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+ >
+ {values.errorMsg}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+ >
+ {getValueEnumLabel(statusOptions, values.status)}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+ >
+ {values.operTime?.toString()}
+ </Descriptions.Item>
+ </Descriptions>
+ </Modal>
+ );
+};
+
+export default OperlogDetailForm;
diff --git a/src/pages/Monitor/Operlog/index.tsx b/src/pages/Monitor/Operlog/index.tsx
new file mode 100644
index 0000000..9440bc8
--- /dev/null
+++ b/src/pages/Monitor/Operlog/index.tsx
@@ -0,0 +1,365 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addOperlog({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateOperlog(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportOperlog();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const OperlogTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+ const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+ const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_oper_type', true).then((data) => {
+ setBusinessTypeOptions(data);
+ });
+ getDictValueEnum('sys_oper_type', true).then((data) => {
+ setOperatorTypeOptions(data);
+ });
+ getDictValueEnum('sys_common_status', true).then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.Monitor.Operlog>[] = [
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+ dataIndex: 'operId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+ dataIndex: 'title',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+ dataIndex: 'businessType',
+ valueType: 'select',
+ valueEnum: businessTypeOptions,
+ render: (_, record) => {
+ return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+ dataIndex: 'requestMethod',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+ dataIndex: 'operatorType',
+ valueType: 'select',
+ valueEnum: operatorTypeOptions,
+ render: (_, record) => {
+ return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+ dataIndex: 'operName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+ dataIndex: 'operIp',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+ dataIndex: 'operLocation',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+ dataIndex: 'operTime',
+ valueType: 'dateTime',
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:operlog:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 详细
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.Operlog>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="operId"
+ key="operlogList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:operlog:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:operlog:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:operlog:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.operId) {
+ success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+ } else {
+ success = await handleAdd({ ...values } as API.Monitor.Operlog);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ businessTypeOptions={businessTypeOptions}
+ operatorTypeOptions={operatorTypeOptions}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default OperlogTableList;
diff --git a/src/pages/Monitor/Server/index.tsx b/src/pages/Monitor/Server/index.tsx
new file mode 100644
index 0000000..9ea3969
--- /dev/null
+++ b/src/pages/Monitor/Server/index.tsx
@@ -0,0 +1,267 @@
+import React, { useEffect, useState } from 'react';
+import { getServerInfo } from '@/services/monitor/server';
+import { Card, Col, Row, Table } from 'antd';
+import { FormattedMessage } from '@umijs/max';
+import styles from './style.less';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+
+const columns = [
+ {
+ title: '属性',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '值',
+ dataIndex: 'value',
+ key: 'value',
+ },
+];
+
+const memColumns = [
+ {
+ title: '属性',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '内存',
+ dataIndex: 'mem',
+ key: 'mem',
+ },
+ {
+ title: 'JVM',
+ dataIndex: 'jvm',
+ key: 'jvm',
+ },
+];
+
+const hostColumns = [
+ {
+ title: 'col1',
+ dataIndex: 'col1',
+ key: 'col1',
+ },
+ {
+ title: 'col2',
+ dataIndex: 'col2',
+ key: 'col2',
+ },
+ {
+ title: 'col3',
+ dataIndex: 'col3',
+ key: 'col3',
+ },
+ {
+ title: 'col4',
+ dataIndex: 'col4',
+ key: 'col4',
+ },
+];
+
+const diskColumns = [
+ {
+ title: <FormattedMessage id="monitor.server.disk.dirName" defaultMessage="盘符路径" />,
+ dataIndex: 'dirName',
+ key: 'dirName',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.sysTypeName" defaultMessage="文件系统" />,
+ dataIndex: 'sysTypeName',
+ key: 'sysTypeName',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.typeName" defaultMessage="盘符类型" />,
+ dataIndex: 'typeName',
+ key: 'typeName',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.total" defaultMessage="总大小" />,
+ dataIndex: 'total',
+ key: 'total',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.free" defaultMessage="可用大小" />,
+ dataIndex: 'free',
+ key: 'free',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.used" defaultMessage="已用大小" />,
+ dataIndex: 'used',
+ key: 'used',
+ },
+ {
+ title: <FormattedMessage id="monitor.server.disk.usage" defaultMessage="已用百分比" />,
+ dataIndex: 'usage',
+ key: 'usage',
+ },
+];
+
+const ServerInfo: React.FC = () => {
+ const [cpuData, setCpuData] = useState<API.Monitor.CpuRowType[]>([]);
+ const [memData, setMemData] = useState<API.Monitor.MemRowType[]>([]);
+ const [hostData, setHostData] = useState<any>([]);
+ const [jvmData, setJvmData] = useState<any>([]);
+ const [diskData, setDiskData] = useState<any>([]);
+
+ useEffect(() => {
+ getServerInfo().then((res: API.Monitor.ServerInfoResponseType) => {
+ if (res.code === 200) {
+ // const cpuinfo: CpuRowType[] = [];
+ // Object.keys(res.data.cpu).forEach((item: any) => {
+ // cpuinfo.push({
+ // name: item,
+ // value: res.data.cpu[item],
+ // });
+ // });
+ // setCpuData(cpuinfo);
+
+ const cpuinfo: API.Monitor.CpuRowType[] = [];
+ cpuinfo.push({ name: '核心数', value: res.data.cpu.cpuNum });
+ cpuinfo.push({ name: '用户使用率', value: `${res.data.cpu.used}%` });
+ cpuinfo.push({ name: '系统使用率', value: `${res.data.cpu.sys}%` });
+ cpuinfo.push({ name: '当前空闲率', value: `${res.data.cpu.free}%` });
+
+ setCpuData(cpuinfo);
+
+ const memDatas: API.Monitor.MemRowType[] = [];
+ memDatas.push({
+ name: '总内存',
+ mem: `${res.data.mem.total}G`,
+ jvm: `${res.data.jvm.total}M`,
+ });
+ memDatas.push({
+ name: '已用内存',
+ mem: `${res.data.mem.used}G`,
+ jvm: `${res.data.jvm.used}M`,
+ });
+ memDatas.push({
+ name: '剩余内存',
+ mem: `${res.data.mem.free}G`,
+ jvm: `${res.data.jvm.free}M`,
+ });
+ memDatas.push({
+ name: '使用率',
+ mem: `${res.data.mem.usage}%`,
+ jvm: `${res.data.jvm.usage}%`,
+ });
+ setMemData(memDatas);
+
+ const hostinfo = [];
+ hostinfo.push({
+ col1: '服务器名称',
+ col2: res.data.sys.computerName,
+ col3: '操作系统',
+ col4: res.data.sys.osName,
+ });
+ hostinfo.push({
+ col1: '服务器IP',
+ col2: res.data.sys.computerIp,
+ col3: '系统架构',
+ col4: res.data.sys.osArch,
+ });
+ setHostData(hostinfo);
+
+ const jvminfo = [];
+ jvminfo.push({
+ col1: 'Java名称',
+ col2: res.data.jvm.name,
+ col3: 'Java版本',
+ col4: res.data.jvm.version,
+ });
+ jvminfo.push({
+ col1: '启动时间',
+ col2: res.data.jvm.startTime,
+ col3: '运行时长',
+ col4: res.data.jvm.runTime,
+ });
+ jvminfo.push({
+ col1: '安装路径',
+ col2: res.data.jvm.home,
+ col3: '项目路径',
+ col4: res.data.sys.userDir,
+ });
+ setJvmData(jvminfo);
+
+ const diskinfo = res.data.sysFiles.map((item: API.Monitor.DiskInfoType) => {
+ return {
+ dirName: item.dirName,
+ sysTypeName: item.sysTypeName,
+ typeName: item.typeName,
+ total: item.total,
+ free: item.free,
+ used: item.used,
+ usage: `${item.usage}%`,
+ };
+ });
+ setDiskData(diskinfo);
+ }
+ });
+ }, []);
+
+ return (
+ <div>
+ <Row gutter={[24, 24]}>
+ <Col span={12}>
+ <Card title="CPU" className={styles.card}>
+ <Table rowKey="name" pagination={false} showHeader={false} dataSource={cpuData} columns={columns} />
+ </Card>
+ </Col>
+ <Col span={12}>
+ <Card title="内存" className={styles.card}>
+ <Table
+ rowKey="name"
+ pagination={false}
+ showHeader={false}
+ dataSource={memData}
+ columns={memColumns}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <Card title="服务器信息" className={styles.card}>
+ <Table
+ rowKey="col1"
+ pagination={false}
+ showHeader={false}
+ dataSource={hostData}
+ columns={hostColumns}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <Card title="Java虚拟机信息" className={styles.card}>
+ <Table
+ rowKey="col1"
+ pagination={false}
+ showHeader={false}
+ dataSource={jvmData}
+ columns={hostColumns}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <Card title="磁盘状态" className={styles.card}>
+ <Table rowKey="dirName" pagination={false} dataSource={diskData} columns={diskColumns} />
+ </Card>
+ </Col>
+ </Row>
+ </div>
+ );
+};
+
+export default ServerInfo;
diff --git a/src/pages/Monitor/Server/style.less b/src/pages/Monitor/Server/style.less
new file mode 100644
index 0000000..d244a53
--- /dev/null
+++ b/src/pages/Monitor/Server/style.less
@@ -0,0 +1,11 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+.card {
+ margin-bottom: 12px;
+}
diff --git a/src/pages/System/Config/edit.tsx b/src/pages/System/Config/edit.tsx
new file mode 100644
index 0000000..6cb3942
--- /dev/null
+++ b/src/pages/System/Config/edit.tsx
@@ -0,0 +1,172 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormTextArea,
+ ProFormRadio,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type ConfigFormData = Record<string, unknown> & Partial<API.System.Config>;
+
+export type ConfigFormProps = {
+ onCancel: (flag?: boolean, formVals?: ConfigFormData) => void;
+ onSubmit: (values: ConfigFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Config>;
+ configTypeOptions: DictValueEnumObj;
+};
+
+const ConfigForm: React.FC<ConfigFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { configTypeOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ configId: props.values.configId,
+ configName: props.values.configName,
+ configKey: props.values.configKey,
+ configValue: props.values.configValue,
+ configType: props.values.configType,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as ConfigFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.config.title',
+ defaultMessage: '编辑参数配置',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="configId"
+ label={intl.formatMessage({
+ id: 'system.config.config_id',
+ defaultMessage: '参数主键',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入参数主键"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入参数主键!" defaultMessage="请输入参数主键!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="configName"
+ label={intl.formatMessage({
+ id: 'system.config.config_name',
+ defaultMessage: '参数名称',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入参数名称"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入参数名称!" defaultMessage="请输入参数名称!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="configKey"
+ label={intl.formatMessage({
+ id: 'system.config.config_key',
+ defaultMessage: '参数键名',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入参数键名"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入参数键名!" defaultMessage="请输入参数键名!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="configValue"
+ label={intl.formatMessage({
+ id: 'system.config.config_value',
+ defaultMessage: '参数键值',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入参数键值"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入参数键值!" defaultMessage="请输入参数键值!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={configTypeOptions}
+ name="configType"
+ label={intl.formatMessage({
+ id: 'system.config.config_type',
+ defaultMessage: '系统内置',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入系统内置"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入系统内置!" defaultMessage="请输入系统内置!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.config.remark',
+ defaultMessage: '备注',
+ })}
+ colProps={{ md: 24 }}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default ConfigForm;
diff --git a/src/pages/System/Config/index.tsx b/src/pages/System/Config/index.tsx
new file mode 100644
index 0000000..1428891
--- /dev/null
+++ b/src/pages/System/Config/index.tsx
@@ -0,0 +1,397 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined, DownloadOutlined } from '@ant-design/icons';
+import { getConfigList, removeConfig, addConfig, updateConfig, exportConfig, refreshConfigCache } from '@/services/system/config';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Config) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addConfig({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Config) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateConfig(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Config[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeConfig(selectedRows.map((row) => row.configId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Config) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.configId];
+ const resp = await removeConfig(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportConfig();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+const handleRefreshCache = async () => {
+ const hide = message.loading('正在刷新');
+ try {
+ await refreshConfigCache();
+ hide();
+ message.success('刷新成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('刷新失败,请重试');
+ return false;
+ }
+};
+
+const ConfigTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Config>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Config[]>([]);
+
+ const [configTypeOptions, setConfigTypeOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_yes_no').then((data) => {
+ setConfigTypeOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.Config>[] = [
+ {
+ title: <FormattedMessage id="system.config.config_id" defaultMessage="参数主键" />,
+ dataIndex: 'configId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.config.config_name" defaultMessage="参数名称" />,
+ dataIndex: 'configName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.config.config_key" defaultMessage="参数键名" />,
+ dataIndex: 'configKey',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.config.config_value" defaultMessage="参数键值" />,
+ dataIndex: 'configValue',
+ valueType: 'textarea',
+ },
+ {
+ title: <FormattedMessage id="system.config.config_type" defaultMessage="系统内置" />,
+ dataIndex: 'configType',
+ valueType: 'select',
+ valueEnum: configTypeOptions,
+ render: (_, record) => {
+ return (<DictTag enums={configTypeOptions} value={record.configType} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="system.config.remark" defaultMessage="备注" />,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:config:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:config:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Config>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="configId"
+ key="configList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:config:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:config:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:config:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <DownloadOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ <Button
+ type="primary"
+ key="refresh"
+ danger
+ hidden={!access.hasPerms('system:config:remove')}
+ onClick={async () => {
+ handleRefreshCache();
+ }}
+ >
+ <ReloadOutlined />
+ <FormattedMessage id="system.config.refreshCache" defaultMessage="刷新缓存" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getConfigList({ ...params } as API.System.ConfigListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:config:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.configId) {
+ success = await handleUpdate({ ...values } as API.System.Config);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Config);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ configTypeOptions={configTypeOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default ConfigTableList;
diff --git a/src/pages/System/Dept/edit.tsx b/src/pages/System/Dept/edit.tsx
new file mode 100644
index 0000000..cb64813
--- /dev/null
+++ b/src/pages/System/Dept/edit.tsx
@@ -0,0 +1,212 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DeptFormData = Record<string, unknown> & Partial<API.System.Dept>;
+
+export type DeptFormProps = {
+ onCancel: (flag?: boolean, formVals?: DeptFormData) => void;
+ onSubmit: (values: DeptFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Dept>;
+ deptTree: DataNode[];
+ statusOptions: DictValueEnumObj;
+};
+
+const DeptForm: React.FC<DeptFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions, deptTree } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ deptId: props.values.deptId,
+ parentId: props.values.parentId,
+ ancestors: props.values.ancestors,
+ deptName: props.values.deptName,
+ orderNum: props.values.orderNum,
+ leader: props.values.leader,
+ phone: props.values.phone,
+ email: props.values.email,
+ status: props.values.status,
+ delFlag: props.values.delFlag,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as DeptFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.dept.title',
+ defaultMessage: '编辑部门',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="deptId"
+ label={intl.formatMessage({
+ id: 'system.dept.dept_id',
+ defaultMessage: '部门id',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入部门id"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入部门id!" defaultMessage="请输入部门id!" />,
+ },
+ ]}
+ />
+ <ProFormTreeSelect
+ name="parentId"
+ label={intl.formatMessage({
+ id: 'system.dept.parent_dept',
+ defaultMessage: '上级部门:',
+ })}
+ request={async () => {
+ return deptTree;
+ }}
+ placeholder="请选择上级部门"
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage id="请输入用户昵称!" defaultMessage="请选择上级部门!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormText
+ name="deptName"
+ label={intl.formatMessage({
+ id: 'system.dept.dept_name',
+ defaultMessage: '部门名称',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入部门名称"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入部门名称!" defaultMessage="请输入部门名称!" />,
+ },
+ ]}
+ />
+ <ProFormDigit
+ name="orderNum"
+ label={intl.formatMessage({
+ id: 'system.dept.order_num',
+ defaultMessage: '显示顺序',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入显示顺序"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="leader"
+ label={intl.formatMessage({
+ id: 'system.dept.leader',
+ defaultMessage: '负责人',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入负责人"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入负责人!" defaultMessage="请输入负责人!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="phone"
+ label={intl.formatMessage({
+ id: 'system.dept.phone',
+ defaultMessage: '联系电话',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入联系电话"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入联系电话!" defaultMessage="请输入联系电话!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="email"
+ label={intl.formatMessage({
+ id: 'system.dept.email',
+ defaultMessage: '邮箱',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入邮箱"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.dept.status',
+ defaultMessage: '部门状态',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入部门状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入部门状态!" defaultMessage="请输入部门状态!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default DeptForm;
diff --git a/src/pages/System/Dept/index.tsx b/src/pages/System/Dept/index.tsx
new file mode 100644
index 0000000..3a1c5de
--- /dev/null
+++ b/src/pages/System/Dept/index.tsx
@@ -0,0 +1,346 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDeptList, removeDept, addDept, updateDept, getDeptListExcludeChild } from '@/services/system/dept';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Dept) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addDept({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Dept) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateDept(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Dept[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeDept(selectedRows.map((row) => row.deptId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Dept) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.deptId];
+ const resp = await removeDept(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+
+const DeptTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Dept>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Dept[]>([]);
+
+ const [deptTree, setDeptTree] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.Dept>[] = [
+ {
+ title: <FormattedMessage id="system.dept.dept_name" defaultMessage="部门名称" />,
+ dataIndex: 'deptName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dept.order_num" defaultMessage="显示顺序" />,
+ dataIndex: 'orderNum',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dept.status" defaultMessage="部门状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:dept:edit')}
+ onClick={() => {
+ getDeptListExcludeChild(record.deptId).then((res) => {
+ if (res.code === 200) {
+ let depts = buildTreeData(res.data, 'deptId', 'deptName', '', '', '');
+ if(depts.length === 0) {
+ depts = [{ id: 0, title: '无上级', children: undefined, key: 0, value: 0 }];
+ }
+ setDeptTree(depts);
+ setModalVisible(true);
+ setCurrentRow(record);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:dept:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Dept>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="deptId"
+ key="deptList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:dept:add')}
+ onClick={async () => {
+ getDeptList().then((res) => {
+ if (res.code === 200) {
+ setDeptTree(buildTreeData(res.data, 'deptId', 'deptName', '', '', ''));
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:dept:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getDeptList({ ...params } as API.System.DeptListParams).then((res) => {
+ const result = {
+ data: buildTreeData(res.data, 'deptId', '', '', '', ''),
+ total: res.data.length,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:dept:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.deptId) {
+ success = await handleUpdate({ ...values } as API.System.Dept);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Dept);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ deptTree={deptTree}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default DeptTableList;
diff --git a/src/pages/System/Dict/edit.tsx b/src/pages/System/Dict/edit.tsx
new file mode 100644
index 0000000..882c1cb
--- /dev/null
+++ b/src/pages/System/Dict/edit.tsx
@@ -0,0 +1,152 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTextArea,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DictTypeFormData = Record<string, unknown> & Partial<API.System.DictType>;
+
+export type DictTypeFormProps = {
+ onCancel: (flag?: boolean, formVals?: DictTypeFormData) => void;
+ onSubmit: (values: DictTypeFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.DictType>;
+ statusOptions: DictValueEnumObj;
+};
+
+const DictTypeForm: React.FC<DictTypeFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ dictId: props.values.dictId,
+ dictName: props.values.dictName,
+ dictType: props.values.dictType,
+ status: props.values.status,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as DictTypeFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.dict.title',
+ defaultMessage: '编辑字典类型',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="dictId"
+ label={intl.formatMessage({
+ id: 'system.dict.dict_id',
+ defaultMessage: '字典主键',
+ })}
+ placeholder="请输入字典主键"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典主键!" defaultMessage="请输入字典主键!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="dictName"
+ label={intl.formatMessage({
+ id: 'system.dict.dict_name',
+ defaultMessage: '字典名称',
+ })}
+ placeholder="请输入字典名称"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典名称!" defaultMessage="请输入字典名称!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="dictType"
+ label={intl.formatMessage({
+ id: 'system.dict.dict_type',
+ defaultMessage: '字典类型',
+ })}
+ placeholder="请输入字典类型"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.dict.status',
+ defaultMessage: '状态',
+ })}
+ initialValue={'0'}
+ placeholder="请输入状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.dict.remark',
+ defaultMessage: '备注',
+ })}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default DictTypeForm;
diff --git a/src/pages/System/Dict/index.tsx b/src/pages/System/Dict/index.tsx
new file mode 100644
index 0000000..ccdbc2a
--- /dev/null
+++ b/src/pages/System/Dict/index.tsx
@@ -0,0 +1,394 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictTypeList, removeDictType, addDictType, updateDictType, exportDictType } from '@/services/system/dict';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictType) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addDictType({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictType) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateDictType(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictType[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeDictType(selectedRows.map((row) => row.dictId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictType) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.dictId];
+ const resp = await removeDictType(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportDictType();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const DictTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.DictType>();
+ const [selectedRows, setSelectedRows] = useState<API.System.DictType[]>([]);
+
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.DictType>[] = [
+ {
+ title: <FormattedMessage id="system.dict.dict_id" defaultMessage="字典编号" />,
+ dataIndex: 'dictId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.dict.dict_name" defaultMessage="字典名称" />,
+ dataIndex: 'dictName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dict.dict_type" defaultMessage="字典类型" />,
+ dataIndex: 'dictType',
+ valueType: 'text',
+ render: (dom, record) => {
+ return (
+ <a
+ onClick={() => {
+ history.push(`/system/dict-data/index/${record.dictId}`);
+ }}
+ >
+ {dom}
+ </a>
+ );
+ },
+ },
+ {
+ title: <FormattedMessage id="system.dict.status" defaultMessage="状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="system.dict.remark" defaultMessage="备注" />,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:dictType:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:dictType:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.DictType>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="dictId"
+ key="dictTypeList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:dictType:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:dictType:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:dictType:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getDictTypeList({ ...params } as API.System.DictTypeListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:dictType:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.dictId) {
+ success = await handleUpdate({ ...values } as API.System.DictType);
+ } else {
+ success = await handleAdd({ ...values } as API.System.DictType);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default DictTableList;
diff --git a/src/pages/System/DictData/edit.tsx b/src/pages/System/DictData/edit.tsx
new file mode 100644
index 0000000..188e01b
--- /dev/null
+++ b/src/pages/System/DictData/edit.tsx
@@ -0,0 +1,252 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormSelect,
+ ProFormRadio,
+ ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DataFormData = Record<string, unknown> & Partial<API.System.DictData>;
+
+export type DataFormProps = {
+ onCancel: (flag?: boolean, formVals?: DataFormData) => void;
+ onSubmit: (values: DataFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.DictData>;
+ statusOptions: DictValueEnumObj;
+};
+
+const DictDataForm: React.FC<DataFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ dictCode: props.values.dictCode,
+ dictSort: props.values.dictSort,
+ dictLabel: props.values.dictLabel,
+ dictValue: props.values.dictValue,
+ dictType: props.values.dictType,
+ cssClass: props.values.cssClass,
+ listClass: props.values.listClass,
+ isDefault: props.values.isDefault,
+ status: props.values.status,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as DataFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.dict.data.title',
+ defaultMessage: '编辑字典数据',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="dictCode"
+ label={intl.formatMessage({
+ id: 'system.dict.data.dict_code',
+ defaultMessage: '字典编码',
+ })}
+ colProps={{ md: 24, xl: 24 }}
+ placeholder="请输入字典编码"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典编码!" defaultMessage="请输入字典编码!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="dictType"
+ label={intl.formatMessage({
+ id: 'system.dict.data.dict_type',
+ defaultMessage: '字典类型',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入字典类型"
+ disabled
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="dictLabel"
+ label={intl.formatMessage({
+ id: 'system.dict.data.dict_label',
+ defaultMessage: '字典标签',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入字典标签"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典标签!" defaultMessage="请输入字典标签!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="dictValue"
+ label={intl.formatMessage({
+ id: 'system.dict.data.dict_value',
+ defaultMessage: '字典键值',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入字典键值"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典键值!" defaultMessage="请输入字典键值!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="cssClass"
+ label={intl.formatMessage({
+ id: 'system.dict.data.css_class',
+ defaultMessage: '样式属性',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入样式属性"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入样式属性!" defaultMessage="请输入样式属性!" />,
+ },
+ ]}
+ />
+ <ProFormSelect
+ name="listClass"
+ label={intl.formatMessage({
+ id: 'system.dict.data.list_class',
+ defaultMessage: '回显样式',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入回显样式"
+ valueEnum={{
+ 'default': '默认',
+ 'primary': '主要',
+ 'success': '成功',
+ 'info': '信息',
+ 'warning': '警告',
+ 'danger': '危险',
+ }}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入回显样式!" defaultMessage="请输入回显样式!" />,
+ },
+ ]}
+ />
+ <ProFormDigit
+ name="dictSort"
+ label={intl.formatMessage({
+ id: 'system.dict.data.dict_sort',
+ defaultMessage: '字典排序',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入字典排序"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入字典排序!" defaultMessage="请输入字典排序!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ name="isDefault"
+ label={intl.formatMessage({
+ id: 'system.dict.data.is_default',
+ defaultMessage: '是否默认',
+ })}
+ valueEnum={{
+ 'Y': '是',
+ 'N': '否',
+ }}
+ initialValue={'N'}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入是否默认"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入是否默认!" defaultMessage="请输入是否默认!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.dict.data.status',
+ defaultMessage: '状态',
+ })}
+ initialValue={'0'}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.dict.data.remark',
+ defaultMessage: '备注',
+ })}
+ colProps={{ md: 24, xl: 24 }}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default DictDataForm;
diff --git a/src/pages/System/DictData/index.tsx b/src/pages/System/DictData/index.tsx
new file mode 100644
index 0000000..2bf1069
--- /dev/null
+++ b/src/pages/System/DictData/index.tsx
@@ -0,0 +1,439 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictDataList, removeDictData, addDictData, updateDictData, exportDictData } from '@/services/system/dictdata';
+import UpdateForm from './edit';
+import { getDictValueEnum, getDictType, getDictTypeOptionSelect } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictData) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addDictData({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictData) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateDictData(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictData[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeDictData(selectedRows.map((row) => row.dictCode).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictData) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.dictCode];
+ const resp = await removeDictData(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportDictData();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+export type DictTypeArgs = {
+ id: string;
+};
+
+
+const DictDataTableList: React.FC = () => {
+
+ const formTableRef = useRef<FormInstance>();
+
+ const [dictId, setDictId] = useState<string>('');
+ const [dictType, setDictType] = useState<string>('');
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.DictData>();
+ const [selectedRows, setSelectedRows] = useState<API.System.DictData[]>([]);
+
+ const [dictTypeOptions, setDictTypeOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ const params = useParams();
+ if (params.id === undefined) {
+ history.push('/system/dict');
+ }
+ const id = params.id || '0';
+
+ useEffect(() => {
+ if (dictId !== id) {
+ setDictId(id);
+ getDictTypeOptionSelect().then((res) => {
+ if (res.code === 200) {
+ const opts: any = {};
+ res.data.forEach((item: any) => {
+ opts[item.dictType] = item.dictName;
+ });
+ setDictTypeOptions(opts);
+ }
+ });
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ getDictType(id).then((res) => {
+ if (res.code === 200) {
+ setDictType(res.data.dictType);
+ formTableRef.current?.setFieldsValue({
+ dictType: res.data.dictType,
+ });
+ actionRef.current?.reloadAndRest?.();
+ } else {
+ message.error(res.msg);
+ }
+ });
+ }
+ }, [dictId, dictType, params]);
+
+ const columns: ProColumns<API.System.DictData>[] = [
+ {
+ title: <FormattedMessage id="system.dict.data.dict_code" defaultMessage="字典编码" />,
+ dataIndex: 'dictCode',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.dict_label" defaultMessage="字典标签" />,
+ dataIndex: 'dictLabel',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.dict_type" defaultMessage="字典类型" />,
+ dataIndex: 'dictType',
+ valueType: 'select',
+ hideInTable: true,
+ valueEnum: dictTypeOptions,
+ search: {
+ transform: (value) => {
+ setDictType(value);
+ return value;
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.dict_value" defaultMessage="字典键值" />,
+ dataIndex: 'dictValue',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.dict_sort" defaultMessage="字典排序" />,
+ dataIndex: 'dictSort',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.status" defaultMessage="状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.remark" defaultMessage="备注" />,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.dict.data.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:data:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:data:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.DictData>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="dictCode"
+ key="dataList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:data:add')}
+ onClick={async () => {
+ setCurrentRow({ dictType: dictType, isDefault: 'N', status: '0' } as API.System.DictData);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:data:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:data:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getDictDataList({ ...params, dictType } as API.System.DictDataListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:data:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.dictCode) {
+ success = await handleUpdate({ ...values } as API.System.DictData);
+ } else {
+ success = await handleAdd({ ...values } as API.System.DictData);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default DictDataTableList;
diff --git a/src/pages/System/Logininfor/edit.tsx b/src/pages/System/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/src/pages/System/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTimePicker,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+ onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+ onSubmit: (values: LogininforFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.Monitor.Logininfor>;
+ statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions, } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ infoId: props.values.infoId,
+ userName: props.values.userName,
+ ipaddr: props.values.ipaddr,
+ loginLocation: props.values.loginLocation,
+ browser: props.values.browser,
+ os: props.values.os,
+ status: props.values.status,
+ msg: props.values.msg,
+ loginTime: props.values.loginTime,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ form.resetFields();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as LogininforFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.logininfor.title',
+ defaultMessage: '编辑系统访问记录',
+ })}
+ open={props.open}
+ destroyOnClose
+ forceRender
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="infoId"
+ label={intl.formatMessage({
+ id: 'system.logininfor.info_id',
+ defaultMessage: '访问编号',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入访问编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="userName"
+ label={intl.formatMessage({
+ id: 'system.logininfor.user_name',
+ defaultMessage: '用户账号',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入用户账号"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="ipaddr"
+ label={intl.formatMessage({
+ id: 'system.logininfor.ipaddr',
+ defaultMessage: '登录IP地址',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录IP地址"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="loginLocation"
+ label={intl.formatMessage({
+ id: 'system.logininfor.login_location',
+ defaultMessage: '登录地点',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录地点"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="browser"
+ label={intl.formatMessage({
+ id: 'system.logininfor.browser',
+ defaultMessage: '浏览器类型',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入浏览器类型"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="os"
+ label={intl.formatMessage({
+ id: 'system.logininfor.os',
+ defaultMessage: '操作系统',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入操作系统"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.logininfor.status',
+ defaultMessage: '登录状态',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入登录状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="msg"
+ label={intl.formatMessage({
+ id: 'system.logininfor.msg',
+ defaultMessage: '提示消息',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入提示消息"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,
+ },
+ ]}
+ />
+ <ProFormTimePicker
+ name="loginTime"
+ label={intl.formatMessage({
+ id: 'system.logininfor.login_time',
+ defaultMessage: '访问时间',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入访问时间"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default LogininforForm;
diff --git a/src/pages/System/Logininfor/index.tsx b/src/pages/System/Logininfor/index.tsx
new file mode 100644
index 0000000..ee5c1e5
--- /dev/null
+++ b/src/pages/System/Logininfor/index.tsx
@@ -0,0 +1,321 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+import { getDictValueEnum } from '@/services/system/dict';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleClean = async () => {
+ const hide = message.loading('请稍候');
+ try {
+ const resp = await cleanLogininfor();
+ hide();
+ if (resp.code === 200) {
+ message.success('清空成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('请求失败,请重试');
+ return false;
+ }
+};
+
+const handleUnlock = async (userName: string) => {
+ const hide = message.loading('正在解锁');
+ try {
+ const resp = await unlockLogininfor(userName);
+ hide();
+ if (resp.code === 200) {
+ message.success('解锁成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('解锁失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportLogininfor();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const LogininforTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const actionRef = useRef<ActionType>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_common_status', true).then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.Monitor.Logininfor>[] = [
+ {
+ title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+ dataIndex: 'infoId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+ dataIndex: 'ipaddr',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+ dataIndex: 'loginLocation',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+ dataIndex: 'browser',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+ dataIndex: 'os',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+ dataIndex: 'msg',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+ dataIndex: 'loginTime',
+ valueType: 'dateTime',
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.Logininfor>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="infoId"
+ key="logininforList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="clean"
+ danger
+ hidden={!access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认清空所有数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleClean();
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+ </Button>,
+ <Button
+ type="primary"
+ key="unlock"
+ hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认解锁该用户的数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleUnlock(selectedRows[0].userName);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <UnlockOutlined />
+ <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('monitor:logininfor:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('monitor:logininfor:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ </PageContainer>
+ );
+};
+
+export default LogininforTableList;
diff --git a/src/pages/System/Menu/edit.tsx b/src/pages/System/Menu/edit.tsx
new file mode 100644
index 0000000..e094255
--- /dev/null
+++ b/src/pages/System/Menu/edit.tsx
@@ -0,0 +1,386 @@
+import React, { useEffect, useState } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTreeSelect,
+ ProFormSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { createIcon } from '@/utils/IconUtil';
+import { DictValueEnumObj } from '@/components/DictTag';
+import IconSelector from '@/components/IconSelector';
+
+export type MenuFormData = Record<string, unknown> & Partial<API.System.Menu>;
+
+export type MenuFormProps = {
+ onCancel: (flag?: boolean, formVals?: MenuFormData) => void;
+ onSubmit: (values: MenuFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Menu>;
+ visibleOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+ menuTree: DataNode[];
+};
+
+const MenuForm: React.FC<MenuFormProps> = (props) => {
+
+ const [form] = Form.useForm();
+
+ const [menuTypeId, setMenuTypeId] = useState<any>('M');
+ const [menuIconName, setMenuIconName] = useState<any>();
+ const [iconSelectorOpen, setIconSelectorOpen] = useState<boolean>(false);
+
+ const { menuTree, visibleOptions, statusOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ setMenuIconName(props.values.icon);
+ form.setFieldsValue({
+ menuId: props.values.menuId,
+ menuName: props.values.menuName,
+ parentId: props.values.parentId,
+ orderNum: props.values.orderNum,
+ path: props.values.path,
+ component: props.values.component,
+ query: props.values.query,
+ isFrame: props.values.isFrame,
+ isCache: props.values.isCache,
+ menuType: props.values.menuType,
+ visible: props.values.visible,
+ status: props.values.status,
+ perms: props.values.perms,
+ icon: props.values.icon,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as MenuFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.menu.title',
+ defaultMessage: '编辑菜单权限',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="menuId"
+ label={intl.formatMessage({
+ id: 'system.menu.menu_id',
+ defaultMessage: '菜单编号',
+ })}
+ placeholder="请输入菜单编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入菜单编号!" defaultMessage="请输入菜单编号!" />,
+ },
+ ]}
+ />
+ <ProFormTreeSelect
+ name="parentId"
+ label={intl.formatMessage({
+ id: 'system.menu.parent_id',
+ defaultMessage: '上级菜单',
+ })}
+ params={{menuTree}}
+ request={async () => {
+ return menuTree;
+ }}
+ placeholder="请输入父菜单编号"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入父菜单编号!" defaultMessage="请输入父菜单编号!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 0
+ }}
+ />
+ <ProFormRadio.Group
+ name="menuType"
+ valueEnum={{
+ M: '目录',
+ C: '菜单',
+ F: '按钮',
+ }}
+ label={intl.formatMessage({
+ id: 'system.menu.menu_type',
+ defaultMessage: '菜单类型',
+ })}
+ placeholder="请输入菜单类型"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入菜单类型!" defaultMessage="请输入菜单类型!" />,
+ },
+ ]}
+ fieldProps={{
+ defaultValue: 'M',
+ onChange: (e) => {
+ setMenuTypeId(e.target.value);
+ },
+ }}
+ />
+ <ProFormSelect
+ name="icon"
+ label={intl.formatMessage({
+ id: 'system.menu.icon',
+ defaultMessage: '菜单图标',
+ })}
+ valueEnum={{}}
+ hidden={menuTypeId === 'F'}
+ addonBefore={createIcon(menuIconName)}
+ fieldProps={{
+ onClick: () => {
+ setIconSelectorOpen(true);
+ },
+ }}
+ placeholder="请输入菜单图标"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入菜单图标!" defaultMessage="请输入菜单图标!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="menuName"
+ label={intl.formatMessage({
+ id: 'system.menu.menu_name',
+ defaultMessage: '菜单名称',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入菜单名称"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入菜单名称!" defaultMessage="请输入菜单名称!" />,
+ },
+ ]}
+ />
+ <ProFormDigit
+ name="orderNum"
+ label={intl.formatMessage({
+ id: 'system.menu.order_num',
+ defaultMessage: '显示顺序',
+ })}
+ width="lg"
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入显示顺序"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 1
+ }}
+ />
+ <ProFormRadio.Group
+ name="isFrame"
+ valueEnum={{
+ 0: '是',
+ 1: '否',
+ }}
+ initialValue="1"
+ label={intl.formatMessage({
+ id: 'system.menu.is_frame',
+ defaultMessage: '是否为外链',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入是否为外链"
+ hidden={menuTypeId === 'F'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入是否为外链!" defaultMessage="请输入是否为外链!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '1'
+ }}
+ />
+ <ProFormText
+ name="path"
+ label={intl.formatMessage({
+ id: 'system.menu.path',
+ defaultMessage: '路由地址',
+ })}
+ width="lg"
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入路由地址"
+ hidden={menuTypeId === 'F'}
+ rules={[
+ {
+ required: menuTypeId !== 'F',
+ message: <FormattedMessage id="请输入路由地址!" defaultMessage="请输入路由地址!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="component"
+ label={intl.formatMessage({
+ id: 'system.menu.component',
+ defaultMessage: '组件路径',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入组件路径"
+ hidden={menuTypeId !== 'C'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入组件路径!" defaultMessage="请输入组件路径!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="query"
+ label={intl.formatMessage({
+ id: 'system.menu.query',
+ defaultMessage: '路由参数',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入路由参数"
+ hidden={menuTypeId !== 'C'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入路由参数!" defaultMessage="请输入路由参数!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="perms"
+ label={intl.formatMessage({
+ id: 'system.menu.perms',
+ defaultMessage: '权限标识',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入权限标识"
+ hidden={menuTypeId === 'M'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入权限标识!" defaultMessage="请输入权限标识!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ name="isCache"
+ valueEnum={{
+ 0: '缓存',
+ 1: '不缓存',
+ }}
+ label={intl.formatMessage({
+ id: 'system.menu.is_cache',
+ defaultMessage: '是否缓存',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入是否缓存"
+ hidden={menuTypeId !== 'C'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入是否缓存!" defaultMessage="请输入是否缓存!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 0
+ }}
+ />
+ <ProFormRadio.Group
+ name="visible"
+ valueEnum={visibleOptions}
+ label={intl.formatMessage({
+ id: 'system.menu.visible',
+ defaultMessage: '显示状态',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入显示状态"
+ hidden={menuTypeId === 'F'}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入显示状态!" defaultMessage="请输入显示状态!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '0'
+ }}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.menu.status',
+ defaultMessage: '菜单状态',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入菜单状态"
+ hidden={menuTypeId === 'F'}
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入菜单状态!" defaultMessage="请输入菜单状态!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '0'
+ }}
+ />
+ </ProForm>
+ <Modal
+ width={800}
+ open={iconSelectorOpen}
+ onCancel={() => {
+ setIconSelectorOpen(false);
+ }}
+ footer={null}
+ >
+ <IconSelector
+ onSelect={(name: string) => {
+ form.setFieldsValue({ icon: name });
+ setMenuIconName(name);
+ setIconSelectorOpen(false);
+ }}
+ />
+ </Modal>
+ </Modal>
+ );
+};
+
+export default MenuForm;
diff --git a/src/pages/System/Menu/index.tsx b/src/pages/System/Menu/index.tsx
new file mode 100644
index 0000000..95ca2de
--- /dev/null
+++ b/src/pages/System/Menu/index.tsx
@@ -0,0 +1,339 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getMenuList, removeMenu, addMenu, updateMenu } from '@/services/system/menu';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import { DataNode } from 'antd/es/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Menu) => {
+ const hide = message.loading('正在添加');
+ try {
+ await addMenu({ ...fields });
+ hide();
+ message.success('添加成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Menu) => {
+ const hide = message.loading('正在配置');
+ try {
+ await updateMenu(fields);
+ hide();
+ message.success('配置成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Menu[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ await removeMenu(selectedRows.map((row) => row.menuId).join(','));
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Menu) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.menuId];
+ await removeMenu(params.join(','));
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+
+const MenuTableList: React.FC = () => {
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Menu>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Menu[]>([]);
+
+ const [menuTree, setMenuTree] = useState<DataNode[]>([]);
+ const [visibleOptions, setVisibleOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_show_hide').then((data) => {
+ setVisibleOptions(data);
+ });
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.Menu>[] = [
+ {
+ title: <FormattedMessage id="system.menu.menu_name" defaultMessage="菜单名称" />,
+ dataIndex: 'menuName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.menu.icon" defaultMessage="菜单图标" />,
+ dataIndex: 'icon',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.menu.order_num" defaultMessage="显示顺序" />,
+ dataIndex: 'orderNum',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.menu.component" defaultMessage="组件路径" />,
+ dataIndex: 'component',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.menu.perms" defaultMessage="权限标识" />,
+ dataIndex: 'perms',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.menu.status" defaultMessage="菜单状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:menu:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:menu:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Menu>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ rowKey="menuId"
+ key="menuList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:menu:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:menu:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getMenuList({ ...params } as API.System.MenuListParams).then((res) => {
+ const rootMenu = { id: 0, label: '主类目', children: [] as DataNode[], value: 0 };
+ const memuData = buildTreeData(res.data, 'menuId', 'menuName', '', '', '');
+ rootMenu.children = memuData;
+ const treeData: any = [];
+ treeData.push(rootMenu);
+ setMenuTree(treeData);
+ return {
+ data: memuData,
+ total: res.data.length,
+ success: true,
+ };
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:menu:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.menuId) {
+ success = await handleUpdate({ ...values } as API.System.Menu);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Menu);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ visibleOptions={visibleOptions}
+ statusOptions={statusOptions}
+ menuTree={menuTree}
+ />
+ </PageContainer>
+ );
+};
+
+export default MenuTableList;
diff --git a/src/pages/System/Notice/edit.tsx b/src/pages/System/Notice/edit.tsx
new file mode 100644
index 0000000..0b111ce
--- /dev/null
+++ b/src/pages/System/Notice/edit.tsx
@@ -0,0 +1,174 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormSelect,
+ ProFormTextArea,
+ ProFormRadio,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type NoticeFormData = Record<string, unknown> & Partial<API.System.Notice>;
+
+export type NoticeFormProps = {
+ onCancel: (flag?: boolean, formVals?: NoticeFormData) => void;
+ onSubmit: (values: NoticeFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Notice>;
+ noticeTypeOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+};
+
+const NoticeForm: React.FC<NoticeFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { noticeTypeOptions,statusOptions, } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ noticeId: props.values.noticeId,
+ noticeTitle: props.values.noticeTitle,
+ noticeType: props.values.noticeType,
+ noticeContent: props.values.noticeContent,
+ status: props.values.status,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as NoticeFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.notice.title',
+ defaultMessage: '编辑通知公告',
+ })}
+ forceRender
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="noticeId"
+ label={intl.formatMessage({
+ id: 'system.notice.notice_id',
+ defaultMessage: '公告编号',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入公告编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入公告编号!" defaultMessage="请输入公告编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="noticeTitle"
+ label={intl.formatMessage({
+ id: 'system.notice.notice_title',
+ defaultMessage: '公告标题',
+ })}
+ placeholder="请输入公告标题"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入公告标题!" defaultMessage="请输入公告标题!" />,
+ },
+ ]}
+ />
+ <ProFormSelect
+ valueEnum={noticeTypeOptions}
+ name="noticeType"
+ label={intl.formatMessage({
+ id: 'system.notice.notice_type',
+ defaultMessage: '公告类型',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入公告类型"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入公告类型!" defaultMessage="请输入公告类型!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.notice.status',
+ defaultMessage: '公告状态',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入公告状态"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入公告状态!" defaultMessage="请输入公告状态!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="noticeContent"
+ label={intl.formatMessage({
+ id: 'system.notice.notice_content',
+ defaultMessage: '公告内容',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入公告内容"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入公告内容!" defaultMessage="请输入公告内容!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.notice.remark',
+ defaultMessage: '备注',
+ })}
+ colProps={{ md: 12, xl: 24 }}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default NoticeForm;
diff --git a/src/pages/System/Notice/index.tsx b/src/pages/System/Notice/index.tsx
new file mode 100644
index 0000000..ea5b063
--- /dev/null
+++ b/src/pages/System/Notice/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getNoticeList, removeNotice, addNotice, updateNotice } from '@/services/system/notice';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Notice) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addNotice({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Notice) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateNotice(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Notice[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeNotice(selectedRows.map((row) => row.noticeId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Notice) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.noticeId];
+ const resp = await removeNotice(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+
+
+const NoticeTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Notice>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Notice[]>([]);
+
+ const [noticeTypeOptions, setNoticeTypeOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_notice_type').then((data) => {
+ setNoticeTypeOptions(data);
+ });
+ getDictValueEnum('sys_notice_status').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.Notice>[] = [
+ {
+ title: <FormattedMessage id="system.notice.notice_id" defaultMessage="公告编号" />,
+ dataIndex: 'noticeId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.notice.notice_title" defaultMessage="公告标题" />,
+ dataIndex: 'noticeTitle',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.notice.notice_type" defaultMessage="公告类型" />,
+ dataIndex: 'noticeType',
+ valueType: 'select',
+ valueEnum: noticeTypeOptions,
+ },
+ {
+ title: <FormattedMessage id="system.notice.notice_content" defaultMessage="公告内容" />,
+ dataIndex: 'noticeContent',
+ valueType: 'text',
+ hideInTable: true,
+ },
+ {
+ title: <FormattedMessage id="system.notice.status" defaultMessage="公告状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="system.notice.remark" defaultMessage="备注" />,
+ dataIndex: 'remark',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.notice.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:notice:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:notice:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Notice>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="noticeId"
+ key="noticeList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:notice:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:notice:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getNoticeList({ ...params } as API.System.NoticeListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:notice:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.noticeId) {
+ success = await handleUpdate({ ...values } as API.System.Notice);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Notice);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ noticeTypeOptions={noticeTypeOptions}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default NoticeTableList;
diff --git a/src/pages/System/Operlog/detail.tsx b/src/pages/System/Operlog/detail.tsx
new file mode 100644
index 0000000..f5b4f65
--- /dev/null
+++ b/src/pages/System/Operlog/detail.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+ onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+ onSubmit: (values: OperlogFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.Monitor.Operlog>;
+ businessTypeOptions: DictValueEnumObj;
+ operatorTypeOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+ const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+ const intl = useIntl();
+ const handleOk = () => {
+ console.log("handle ok");
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'monitor.operlog.title',
+ defaultMessage: '编辑操作日志记录',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <Descriptions column={24}>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+ >
+ {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+ >
+ {values.requestMethod}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+ >
+ {`${values.operName}/${values.operIp}`}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+ >
+ {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+ >
+ {values.method}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+ >
+ {values.operUrl}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+ >
+ {values.operParam}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+ >
+ {values.jsonResult}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={24}
+ label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+ >
+ {values.errorMsg}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+ >
+ {getValueEnumLabel(statusOptions, values.status)}
+ </Descriptions.Item>
+ <Descriptions.Item
+ span={12}
+ label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+ >
+ {values.operTime?.toString()}
+ </Descriptions.Item>
+ </Descriptions>
+ </Modal>
+ );
+};
+
+export default OperlogDetailForm;
diff --git a/src/pages/System/Operlog/index.tsx b/src/pages/System/Operlog/index.tsx
new file mode 100644
index 0000000..be46709
--- /dev/null
+++ b/src/pages/System/Operlog/index.tsx
@@ -0,0 +1,411 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, cleanAllOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addOperlog({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateOperlog(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 清空所有记录
+ *
+ */
+const handleCleanAll = async () => {
+ const hide = message.loading('正在清空');
+ try {
+ const resp = await cleanAllOperlog();
+ hide();
+ if (resp.code === 200) {
+ message.success('清空成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('清空失败,请重试');
+ return false;
+ }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportOperlog();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const OperlogTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+ const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+ const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+ const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_oper_type', true).then((data) => {
+ setBusinessTypeOptions(data);
+ });
+ getDictValueEnum('sys_oper_type', true).then((data) => {
+ setOperatorTypeOptions(data);
+ });
+ getDictValueEnum('sys_common_status', true).then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.Monitor.Operlog>[] = [
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+ dataIndex: 'operId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+ dataIndex: 'title',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+ dataIndex: 'businessType',
+ valueType: 'select',
+ valueEnum: businessTypeOptions,
+ render: (_, record) => {
+ return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+ dataIndex: 'requestMethod',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+ dataIndex: 'operatorType',
+ valueType: 'select',
+ valueEnum: operatorTypeOptions,
+ render: (_, record) => {
+ return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+ dataIndex: 'operName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+ dataIndex: 'operIp',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+ dataIndex: 'operLocation',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+ dataIndex: 'operTime',
+ valueType: 'dateTime',
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:operlog:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 详细
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.Monitor.Operlog>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="operId"
+ key="operlogList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:operlog:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="clean"
+ danger
+ hidden={!access.hasPerms('system:operlog:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认清空所有数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleCleanAll();
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:operlog:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:operlog:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.operId) {
+ success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+ } else {
+ success = await handleAdd({ ...values } as API.Monitor.Operlog);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ businessTypeOptions={businessTypeOptions}
+ operatorTypeOptions={operatorTypeOptions}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default OperlogTableList;
diff --git a/src/pages/System/Post/edit.tsx b/src/pages/System/Post/edit.tsx
new file mode 100644
index 0000000..555fcde
--- /dev/null
+++ b/src/pages/System/Post/edit.tsx
@@ -0,0 +1,166 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTextArea,
+ } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type PostFormData = Record<string, unknown> & Partial<API.System.Post>;
+
+export type PostFormProps = {
+ onCancel: (flag?: boolean, formVals?: PostFormData) => void;
+ onSubmit: (values: PostFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Post>;
+ statusOptions: DictValueEnumObj;
+};
+
+const PostForm: React.FC<PostFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { statusOptions, } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ postId: props.values.postId,
+ postCode: props.values.postCode,
+ postName: props.values.postName,
+ postSort: props.values.postSort,
+ status: props.values.status,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as PostFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.post.title',
+ defaultMessage: '编辑岗位信息',
+ })}
+ open={props.open}
+ forceRender
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ submitter={false}
+ layout="horizontal"
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="postId"
+ label={intl.formatMessage({
+ id: 'system.post.post_id',
+ defaultMessage: '岗位编号',
+ })}
+ placeholder="请输入岗位编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入岗位编号!" defaultMessage="请输入岗位编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="postName"
+ label={intl.formatMessage({
+ id: 'system.post.post_name',
+ defaultMessage: '岗位名称',
+ })}
+ placeholder="请输入岗位名称"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入岗位名称!" defaultMessage="请输入岗位名称!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="postCode"
+ label={intl.formatMessage({
+ id: 'system.post.post_code',
+ defaultMessage: '岗位编码',
+ })}
+ placeholder="请输入岗位编码"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入岗位编码!" defaultMessage="请输入岗位编码!" />,
+ },
+ ]}
+ />
+ <ProFormDigit
+ name="postSort"
+ label={intl.formatMessage({
+ id: 'system.post.post_sort',
+ defaultMessage: '显示顺序',
+ })}
+ placeholder="请输入显示顺序"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.post.status',
+ defaultMessage: '状态',
+ })}
+ placeholder="请输入状态"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+ },
+ ]}
+ />
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.post.remark',
+ defaultMessage: '备注',
+ })}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default PostForm;
diff --git a/src/pages/System/Post/index.tsx b/src/pages/System/Post/index.tsx
new file mode 100644
index 0000000..ad98aae
--- /dev/null
+++ b/src/pages/System/Post/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getPostList, removePost, addPost, updatePost, exportPost } from '@/services/system/post';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Post) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addPost({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Post) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updatePost(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Post[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removePost(selectedRows.map((row) => row.postId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Post) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.postId];
+ const resp = await removePost(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportPost();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const PostTableList: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Post>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Post[]>([]);
+
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.Post>[] = [
+ {
+ title: <FormattedMessage id="system.post.post_id" defaultMessage="岗位编号" />,
+ dataIndex: 'postId',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.post.post_code" defaultMessage="岗位编码" />,
+ dataIndex: 'postCode',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.post.post_name" defaultMessage="岗位名称" />,
+ dataIndex: 'postName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.post.post_sort" defaultMessage="显示顺序" />,
+ dataIndex: 'postSort',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.post.status" defaultMessage="状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ hidden={!access.hasPerms('system:post:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ hidden={!access.hasPerms('system:post:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Post>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="postId"
+ key="postList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:post:add')}
+ onClick={async () => {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:post:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:post:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getPostList({ ...params } as API.System.PostListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:post:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.postId) {
+ success = await handleUpdate({ ...values } as API.System.Post);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Post);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ />
+ </PageContainer>
+ );
+};
+
+export default PostTableList;
diff --git a/src/pages/System/Role/authUser.tsx b/src/pages/System/Role/authUser.tsx
new file mode 100644
index 0000000..c553d99
--- /dev/null
+++ b/src/pages/System/Role/authUser.tsx
@@ -0,0 +1,274 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import { Button, Modal, message } from 'antd';
+import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, RollbackOutlined } from '@ant-design/icons';
+import { authUserSelectAll, authUserCancel, authUserCancelAll, allocatedUserList, unallocatedUserList } from '@/services/system/role';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import UserSelectorModal from './components/UserSelectorModal';
+import { HttpResult } from '@/enums/httpEnum';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const cancelAuthUserAll = async (roleId: string, selectedRows: API.System.User[]) => {
+ const hide = message.loading('正在取消授权');
+ if (!selectedRows) return true;
+ try {
+ const userIds = selectedRows.map((row) => row.userId).join(',');
+ const resp = await authUserCancelAll({roleId, userIds});
+ hide();
+ if (resp.code === 200) {
+ message.success('取消授权成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('取消授权失败,请重试');
+ return false;
+ }
+};
+
+const cancelAuthUser = async (roleId: string, userId: number) => {
+ const hide = message.loading('正在取消授权');
+ try {
+ const resp = await authUserCancel({ userId, roleId });
+ hide();
+ if (resp.code === 200) {
+ message.success('取消授权成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('取消授权失败,请重试');
+ return false;
+ }
+};
+
+
+const AuthUserTableList: React.FC = () => {
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ const params = useParams();
+ if (params.id === undefined) {
+ history.back();
+ }
+ const roleId = params.id || '0';
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns<API.System.User>[] = [
+ {
+ title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+ dataIndex: 'deptId',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+ dataIndex: 'nickName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+ dataIndex: 'phonenumber',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '60px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ danger
+ icon={<DeleteOutlined />}
+ key="remove"
+ hidden={!access.hasPerms('system:role:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确认要取消该用户' + record.userName + '"角色授权吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await cancelAuthUser(roleId, record.userId);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 取消授权
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.User>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ rowKey="userId"
+ key="userList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:role:add')}
+ onClick={async () => {
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="system.role.auth.addUser" defaultMessage="添加用户" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await cancelAuthUserAll(roleId, selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="system.role.auth.cancelAll" defaultMessage="批量取消授权" />
+ </Button>,
+ <Button
+ type="primary"
+ key="back"
+ onClick={async () => {
+ history.back();
+ }}
+ >
+ <RollbackOutlined />
+ <FormattedMessage id="pages.goback" defaultMessage="返回" />
+ </Button>,
+ ]}
+ request={(params) =>
+ allocatedUserList({ ...params, roleId } as API.System.RoleListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ <UserSelectorModal
+ open={modalVisible}
+ onSubmit={(values: React.Key[]) => {
+ const userIds = values.join(",");
+ if (userIds === "") {
+ message.warning("请选择要分配的用户");
+ return;
+ }
+ authUserSelectAll({ roleId: roleId, userIds: userIds }).then(resp => {
+ if (resp.code === HttpResult.SUCCESS) {
+ message.success('更新成功!');
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ } else {
+ message.warning(resp.msg);
+ }
+ })
+ setModalVisible(false);
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ }}
+ params={{roleId}}
+ request={(params) =>
+ unallocatedUserList({ ...params } as API.System.RoleListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.rows.length,
+ success: true,
+ };
+ return result;
+ })
+ }
+ />
+ </PageContainer>
+ );
+};
+
+export default AuthUserTableList;
diff --git a/src/pages/System/Role/components/DataScope.tsx b/src/pages/System/Role/components/DataScope.tsx
new file mode 100644
index 0000000..a3dcce0
--- /dev/null
+++ b/src/pages/System/Role/components/DataScope.tsx
@@ -0,0 +1,233 @@
+import React, { useEffect, useState } from 'react';
+import { Checkbox, Col, Form, Modal, Row, Tree } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Key, ProForm, ProFormDigit, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+import { DataNode } from 'antd/es/tree';
+import { CheckboxValueType } from 'antd/es/checkbox/Group';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type DataScopeFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Role>;
+ deptTree: DataNode[];
+ deptCheckedKeys: string[];
+};
+
+const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const { deptTree, deptCheckedKeys } = props;
+ const [dataScopeType, setDataScopeType] = useState<string | undefined>('1');
+ const [deptIds, setDeptIds] = useState<string[] | {checked: string[], halfChecked: string[]}>([]);
+ const [deptTreeExpandKey, setDeptTreeExpandKey] = useState<Key[]>([]);
+ const [checkStrictly, setCheckStrictly] = useState<boolean>(true);
+
+
+ useEffect(() => {
+ setDeptIds(deptCheckedKeys);
+ form.resetFields();
+ form.setFieldsValue({
+ roleId: props.values.roleId,
+ roleName: props.values.roleName,
+ roleKey: props.values.roleKey,
+ dataScope: props.values.dataScope,
+ });
+ setDataScopeType(props.values.dataScope);
+ }, [props.values]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit({ ...values, deptIds } as FormValueType);
+ };
+
+ const getAllDeptNode = (node: DataNode[]) => {
+ let keys: any[] = [];
+ node.forEach(value => {
+ keys.push(value.key);
+ if(value.children) {
+ keys = keys.concat(getAllDeptNode(value.children));
+ }
+ });
+ return keys;
+ }
+
+ const deptAllNodes = getAllDeptNode(deptTree);
+
+
+ const onDeptOptionChange = (checkedValues: CheckboxValueType[]) => {
+ if(checkedValues.includes('deptExpand')) {
+ setDeptTreeExpandKey(deptAllNodes);
+ } else {
+ setDeptTreeExpandKey([]);
+ }
+ if(checkedValues.includes('deptNodeAll')) {
+ setDeptIds(deptAllNodes);
+ } else {
+ setDeptIds([]);
+ }
+
+ if(checkedValues.includes('deptCheckStrictly')) {
+ setCheckStrictly(false);
+ } else {
+ setCheckStrictly(true);
+ }
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.user.auth.role',
+ defaultMessage: '分配角色',
+ })}
+ open={props.open}
+ destroyOnClose
+ forceRender
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ layout="horizontal"
+ onFinish={handleFinish}
+ initialValues={{
+ login_password: '',
+ confirm_password: '',
+ }}
+ >
+
+ <ProFormDigit
+ name="roleId"
+ label={intl.formatMessage({
+ id: 'system.role.role_id',
+ defaultMessage: '角色编号',
+ })}
+ colProps={{ md: 12, xl: 12 }}
+ placeholder="请输入角色编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="roleName"
+ label={intl.formatMessage({
+ id: 'system.role.role_name',
+ defaultMessage: '角色名称',
+ })}
+ disabled
+ placeholder="请输入角色名称"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="roleKey"
+ label={intl.formatMessage({
+ id: 'system.role.role_key',
+ defaultMessage: '权限字符串',
+ })}
+ disabled
+ placeholder="请输入角色权限字符串"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+ },
+ ]}
+ />
+ <ProFormSelect
+ name="dataScope"
+ label='权限范围'
+ initialValue={'1'}
+ placeholder="请输入用户性别"
+ valueEnum={{
+ "1": "全部数据权限",
+ "2": "自定数据权限",
+ "3": "本部门数据权限",
+ "4": "本部门及以下数据权限",
+ "5": "仅本人数据权限"
+ }}
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ fieldProps={{
+ onChange: (value) => {
+ setDataScopeType(value);
+ },
+ }}
+ />
+ <ProForm.Item
+ name="deptIds"
+ label={intl.formatMessage({
+ id: 'system.role.auth',
+ defaultMessage: '菜单权限',
+ })}
+ required={dataScopeType === '1'}
+ hidden={dataScopeType !== '1'}
+ >
+ <Row gutter={[16, 16]}>
+ <Col md={24}>
+ <Checkbox.Group
+ options={[
+ { label: '展开/折叠', value: 'deptExpand' },
+ { label: '全选/全不选', value: 'deptNodeAll' },
+ // { label: '父子联动', value: 'deptCheckStrictly' },
+ ]}
+ onChange={onDeptOptionChange} />
+ </Col>
+ <Col md={24}>
+ <Tree
+ checkable={true}
+ checkStrictly={checkStrictly}
+ expandedKeys={deptTreeExpandKey}
+ treeData={deptTree}
+ checkedKeys={deptIds}
+ defaultCheckedKeys={deptCheckedKeys}
+ onCheck={(checkedKeys: any, checkInfo: any) => {
+ console.log(checkedKeys, checkInfo);
+ if(checkStrictly) {
+ return setDeptIds(checkedKeys.checked);
+ } else {
+ return setDeptIds({checked: checkedKeys, halfChecked: checkInfo.halfCheckedKeys});
+ }
+ }}
+ onExpand={(expandedKeys: Key[]) => {
+ setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys));
+ }}
+ />
+ </Col>
+ </Row>
+ </ProForm.Item>
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default DataScopeForm;
diff --git a/src/pages/System/Role/components/UserSelectorModal.tsx b/src/pages/System/Role/components/UserSelectorModal.tsx
new file mode 100644
index 0000000..fdcfb85
--- /dev/null
+++ b/src/pages/System/Role/components/UserSelectorModal.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ActionType, ParamsType, ProColumns, ProTable, RequestData } from '@ant-design/pro-components';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/10
+ *
+ * */
+
+export type DataScopeFormProps = {
+ onCancel: () => void;
+ onSubmit: (values: React.Key[]) => void;
+ open: boolean;
+ params: ParamsType;
+ request?: (params: Record<string, any>) => Promise<Partial<RequestData<API.System.User>>>;
+};
+
+const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
+
+ const actionRef = useRef<ActionType>();
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, [props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ props.onSubmit(selectedRowKeys);
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ const columns: ProColumns<API.System.User>[] = [
+ {
+ title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+ dataIndex: 'userId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+ dataIndex: 'nickName',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+ dataIndex: 'phonenumber',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ hideInSearch: true,
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (<DictTag enums={statusOptions} value={record.status} />);
+ },
+ },
+ {
+ title: <FormattedMessage id="system.user.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ hideInSearch: true,
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ }
+ ];
+
+ return (
+ <Modal
+ width={800}
+ title={intl.formatMessage({
+ id: 'system.role.auth.user',
+ defaultMessage: '选择用户',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProTable<API.System.User>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ rowKey="userId"
+ key="userList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolbar={{}}
+ params={props.params}
+ request={props.request}
+ columns={columns}
+ rowSelection={{
+ onChange: (selectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(selectedRowKeys);
+ },
+ }}
+ />
+ </Modal>
+ );
+};
+
+export default UserSelectorModal;
diff --git a/src/pages/System/Role/edit.tsx b/src/pages/System/Role/edit.tsx
new file mode 100644
index 0000000..580493a
--- /dev/null
+++ b/src/pages/System/Role/edit.tsx
@@ -0,0 +1,199 @@
+import React, { useEffect, useState } from 'react';
+import {
+ ProForm,
+ ProFormDigit,
+ ProFormText,
+ ProFormRadio,
+ ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import Tree, { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type RoleFormData = Record<string, unknown> & Partial<API.System.Role>;
+
+export type RoleFormProps = {
+ onCancel: (flag?: boolean, formVals?: RoleFormData) => void;
+ onSubmit: (values: RoleFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.Role>;
+ menuTree: DataNode[];
+ menuCheckedKeys: string[];
+ statusOptions: DictValueEnumObj;
+};
+
+const RoleForm: React.FC<RoleFormProps> = (props) => {
+ const [form] = Form.useForm();
+ const { menuTree, menuCheckedKeys } = props;
+ const [menuIds, setMenuIds] = useState<string[]>([]);
+ const { statusOptions } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ roleId: props.values.roleId,
+ roleName: props.values.roleName,
+ roleKey: props.values.roleKey,
+ roleSort: props.values.roleSort,
+ dataScope: props.values.dataScope,
+ menuCheckStrictly: props.values.menuCheckStrictly,
+ deptCheckStrictly: props.values.deptCheckStrictly,
+ status: props.values.status,
+ delFlag: props.values.delFlag,
+ createBy: props.values.createBy,
+ createTime: props.values.createTime,
+ updateBy: props.values.updateBy,
+ updateTime: props.values.updateTime,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit({ ...values, menuIds } as RoleFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.role.title',
+ defaultMessage: '编辑角色信息',
+ })}
+ forceRender
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ layout="horizontal"
+ submitter={false}
+ onFinish={handleFinish}>
+ <ProFormDigit
+ name="roleId"
+ label={intl.formatMessage({
+ id: 'system.role.role_id',
+ defaultMessage: '角色编号',
+ })}
+ placeholder="请输入角色编号"
+ disabled
+ hidden={true}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="roleName"
+ label={intl.formatMessage({
+ id: 'system.role.role_name',
+ defaultMessage: '角色名称',
+ })}
+ placeholder="请输入角色名称"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+ },
+ ]}
+ />
+ <ProFormText
+ name="roleKey"
+ label={intl.formatMessage({
+ id: 'system.role.role_key',
+ defaultMessage: '权限字符串',
+ })}
+ placeholder="请输入角色权限字符串"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+ },
+ ]}
+ />
+ <ProFormDigit
+ name="roleSort"
+ label={intl.formatMessage({
+ id: 'system.role.role_sort',
+ defaultMessage: '显示顺序',
+ })}
+ placeholder="请输入显示顺序"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 1
+ }}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.role.status',
+ defaultMessage: '角色状态',
+ })}
+ placeholder="请输入角色状态"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入角色状态!" defaultMessage="请输入角色状态!" />,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: "0"
+ }}
+ />
+ <ProForm.Item
+ name="menuIds"
+ label={intl.formatMessage({
+ id: 'system.role.auth',
+ defaultMessage: '菜单权限',
+ })}
+ >
+ <Tree
+ checkable={true}
+ multiple={true}
+ checkStrictly={true}
+ defaultExpandAll={false}
+ treeData={menuTree}
+ defaultCheckedKeys={menuCheckedKeys}
+ onCheck={(checkedKeys: any) => {
+ return setMenuIds(checkedKeys.checked);
+ }}
+ />
+ </ProForm.Item>
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.role.remark',
+ defaultMessage: '备注',
+ })}
+ placeholder="请输入备注"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default RoleForm;
diff --git a/src/pages/System/Role/index.tsx b/src/pages/System/Role/index.tsx
new file mode 100644
index 0000000..ea15b2e
--- /dev/null
+++ b/src/pages/System/Role/index.tsx
@@ -0,0 +1,513 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { Button, message, Modal, Dropdown, FormInstance, Space, Switch } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons';
+import { getRoleList, removeRole, addRole, updateRole, exportRole, getRoleMenuList, changeRoleStatus, updateRoleDataScope, getDeptTreeSelect, getRole } from '@/services/system/role';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { formatTreeData } from '@/utils/tree';
+import { getMenuTree } from '@/services/system/menu';
+import DataScopeForm from './components/DataScope';
+
+const { confirm } = Modal;
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Role) => {
+ const hide = message.loading('正在添加');
+ try {
+ const resp = await addRole({ ...fields });
+ hide();
+ if (resp.code === 200) {
+ message.success('添加成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Role) => {
+ const hide = message.loading('正在更新');
+ try {
+ const resp = await updateRole(fields);
+ hide();
+ if (resp.code === 200) {
+ message.success('更新成功');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Role[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ const resp = await removeRole(selectedRows.map((row) => row.roleId).join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Role) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.roleId];
+ const resp = await removeRole(params.join(','));
+ hide();
+ if (resp.code === 200) {
+ message.success('删除成功,即将刷新');
+ } else {
+ message.error(resp.msg);
+ }
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportRole();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const RoleTableList: React.FC = () => {
+
+ const [messageApi, contextHolder] = message.useMessage();
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+ const [dataScopeModalOpen, setDataScopeModalOpen] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.Role>();
+ const [selectedRows, setSelectedRows] = useState<API.System.Role[]>([]);
+
+ const [menuTree, setMenuTree] = useState<DataNode[]>();
+ const [menuIds, setMenuIds] = useState<string[]>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const showChangeStatusConfirm = (record: API.System.Role) => {
+ let text = record.status === "1" ? "启用" : "停用";
+ const newStatus = record.status === '0' ? '1' : '0';
+ confirm({
+ title: `确认要${text}${record.roleName}角色吗?`,
+ onOk() {
+ changeRoleStatus(record.roleId, newStatus).then(resp => {
+ if (resp.code === 200) {
+ messageApi.open({
+ type: 'success',
+ content: '更新成功!',
+ });
+ actionRef.current?.reload();
+ } else {
+ messageApi.open({
+ type: 'error',
+ content: '更新失败!',
+ });
+ }
+ });
+ },
+ });
+ };
+
+ const columns: ProColumns<API.System.Role>[] = [
+ {
+ title: <FormattedMessage id="system.role.role_id" defaultMessage="角色编号" />,
+ dataIndex: 'roleId',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.role.role_name" defaultMessage="角色名称" />,
+ dataIndex: 'roleName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.role.role_key" defaultMessage="角色权限字符串" />,
+ dataIndex: 'roleKey',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.role.role_sort" defaultMessage="显示顺序" />,
+ dataIndex: 'roleSort',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: <FormattedMessage id="system.role.status" defaultMessage="角色状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (
+ <Switch
+ checked={record.status === '0'}
+ checkedChildren="正常"
+ unCheckedChildren="停用"
+ defaultChecked
+ onClick={() => showChangeStatusConfirm(record)}
+ />)
+ },
+ },
+ {
+ title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return (<span>{record.createTime.toString()} </span>);
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ icon=<EditOutlined />
+ hidden={!access.hasPerms('system:role:edit')}
+ onClick={() => {
+ getRoleMenuList(record.roleId).then((res) => {
+ if (res.code === 200) {
+ const treeData = formatTreeData(res.menus);
+ setMenuTree(treeData);
+ setMenuIds(res.checkedKeys.map(item => {
+ return `${item}`
+ }));
+ setModalVisible(true);
+ setCurrentRow(record);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="batchRemove"
+ icon=<DeleteOutlined />
+ hidden={!access.hasPerms('system:role:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ <Dropdown
+ key="more"
+ menu={{
+ items: [
+ {
+ label: '数据权限',
+ key: 'datascope',
+ disabled: !access.hasPerms('system:role:edit'),
+ },
+ {
+ label: '分配用户',
+ key: 'authUser',
+ disabled: !access.hasPerms('system:role:edit'),
+ },
+ ],
+ onClick: ({ key }: any) => {
+ if (key === 'datascope') {
+ getRole(record.roleId).then(resp => {
+ if(resp.code === 200) {
+ setCurrentRow(resp.data);
+ setDataScopeModalOpen(true);
+ }
+ })
+ getDeptTreeSelect(record.roleId).then(resp => {
+ if (resp.code === 200) {
+ setMenuTree(formatTreeData(resp.depts));
+ setMenuIds(resp.checkedKeys.map((item:number) => {
+ return `${item}`
+ }));
+ }
+ })
+ }
+ else if (key === 'authUser') {
+ history.push(`/system/role-auth/user/${record.roleId}`);
+ }
+ }
+ }}
+ >
+ <a onClick={(e) => e.preventDefault()}>
+ <Space>
+ <DownOutlined />
+ 更多
+ </Space>
+ </a>
+ </Dropdown>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ {contextHolder}
+ <div style={{ width: '100%', float: 'right' }}>
+ <ProTable<API.System.Role>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="roleId"
+ key="roleList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:role:add')}
+ onClick={async () => {
+ getMenuTree().then((res: any) => {
+ if (res.code === 200) {
+ const treeData = formatTreeData(res.data);
+ setMenuTree(treeData);
+ setMenuIds([]);
+ setModalVisible(true);
+ setCurrentRow(undefined);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:role:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getRoleList({ ...params } as API.System.RoleListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </div>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:role:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.roleId) {
+ success = await handleUpdate({ ...values } as API.System.Role);
+ } else {
+ success = await handleAdd({ ...values } as API.System.Role);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ menuTree={menuTree || []}
+ menuCheckedKeys={menuIds || []}
+ statusOptions={statusOptions}
+ />
+ <DataScopeForm
+ onSubmit={async (values: any) => {
+ const success = await updateRoleDataScope(values);
+ if (success) {
+ setDataScopeModalOpen(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ message.success('配置成功。');
+ }
+ }}
+ onCancel={() => {
+ setDataScopeModalOpen(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ }}
+ open={dataScopeModalOpen}
+ values={currentRow || {}}
+ deptTree={menuTree || []}
+ deptCheckedKeys={menuIds || []}
+ />
+ </PageContainer>
+ );
+};
+
+export default RoleTableList;
diff --git a/src/pages/System/User/components/AuthRole.tsx b/src/pages/System/User/components/AuthRole.tsx
new file mode 100644
index 0000000..120d966
--- /dev/null
+++ b/src/pages/System/User/components/AuthRole.tsx
@@ -0,0 +1,81 @@
+import React, { useEffect } from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormSelect } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type AuthRoleFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise<void>;
+ open: boolean;
+ roleIds: number[];
+ roles: string[];
+};
+
+const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
+ const [form] = Form.useForm();
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldValue( 'roleIds', props.roleIds);
+ });
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as FormValueType);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.user.auth.role',
+ defaultMessage: '分配角色',
+ })}
+ open={props.open}
+ destroyOnClose
+ forceRender
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ form={form}
+ grid={true}
+ layout="horizontal"
+ onFinish={handleFinish}
+ initialValues={{
+ login_password: '',
+ confirm_password: '',
+ }}
+ >
+ <ProFormSelect
+ name="roleIds"
+ mode="multiple"
+ label={intl.formatMessage({
+ id: 'system.user.role',
+ defaultMessage: '角色',
+ })}
+ options={props.roles}
+ placeholder="请选择角色"
+ rules={[{ required: true, message: '请选择角色!' }]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default AuthRoleForm;
diff --git a/src/pages/System/User/components/DeptTree.tsx b/src/pages/System/User/components/DeptTree.tsx
new file mode 100644
index 0000000..d1a085c
--- /dev/null
+++ b/src/pages/System/User/components/DeptTree.tsx
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react';
+import { Tree, message } from 'antd';
+import { getDeptTree } from '@/services/system/user';
+
+const { DirectoryTree } = Tree;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+
+export type TreeProps = {
+ onSelect: (values: any) => Promise<void>;
+};
+
+const DeptTree: React.FC<TreeProps> = (props) => {
+ const [treeData, setTreeData] = useState<any>([]);
+ const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
+ const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
+
+ const fetchDeptList = async () => {
+ const hide = message.loading('正在查询');
+ try {
+ await getDeptTree({}).then((res: any) => {
+ const exKeys = [];
+ exKeys.push('1');
+ setTreeData(res);
+ exKeys.push(res[0].children[0].id);
+ setExpandedKeys(exKeys);
+ props.onSelect(res[0].children[0]);
+ });
+ hide();
+ return true;
+ } catch (error) {
+ hide();
+ return false;
+ }
+ };
+
+ useEffect(() => {
+ fetchDeptList();
+ }, []);
+
+ const onSelect = (keys: React.Key[], info: any) => {
+ props.onSelect(info.node);
+ };
+
+ const onExpand = (expandedKeysValue: React.Key[]) => {
+ setExpandedKeys(expandedKeysValue);
+ setAutoExpandParent(false);
+ };
+
+ return (
+ <DirectoryTree
+ // multiple
+ defaultExpandAll
+ onExpand={onExpand}
+ expandedKeys={expandedKeys}
+ autoExpandParent={autoExpandParent}
+ onSelect={onSelect}
+ treeData={treeData}
+ />
+ );
+};
+
+export default DeptTree;
diff --git a/src/pages/System/User/components/ResetPwd.tsx b/src/pages/System/User/components/ResetPwd.tsx
new file mode 100644
index 0000000..5523cd6
--- /dev/null
+++ b/src/pages/System/User/components/ResetPwd.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+export type FormValueType = any & Partial<API.System.User>;
+
+export type UpdateFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.User>;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+ const [form] = Form.useForm();
+ const loginPassword = Form.useWatch('password', form);
+ const userId = props.values.userId;
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit({ ...values, userId } as FormValueType);
+ };
+
+ const checkPassword = (rule: any, value: string) => {
+ if (value === loginPassword) {
+ // 校验条件自定义
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次密码输入不一致'));
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.user.reset.password',
+ defaultMessage: '密码重置',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ grid={true}
+ form={form}
+ layout="horizontal"
+ onFinish={handleFinish}
+ initialValues={{
+ password: '',
+ confirm_password: '',
+ }}
+ >
+ <p>请输入用户{props.values.userName}的新密码!</p>
+ <ProFormText.Password
+ name="password"
+ label="登录密码"
+ rules={[
+ {
+ required: true,
+ message: '登录密码不可为空。',
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="confirm_password"
+ label="确认密码"
+ rules={[
+ {
+ required: true,
+ message: "确认密码",
+ },
+ { validator: checkPassword },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default UpdateForm;
diff --git a/src/pages/System/User/edit.tsx b/src/pages/System/User/edit.tsx
new file mode 100644
index 0000000..31d442b
--- /dev/null
+++ b/src/pages/System/User/edit.tsx
@@ -0,0 +1,279 @@
+import React, { useEffect } from 'react';
+import {
+ ProForm,
+ ProFormText,
+ ProFormSelect,
+ ProFormRadio,
+ ProFormTextArea,
+ ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+
+export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
+
+export type UserFormProps = {
+ onCancel: (flag?: boolean, formVals?: UserFormData) => void;
+ onSubmit: (values: UserFormData) => Promise<void>;
+ open: boolean;
+ values: Partial<API.System.User>;
+ sexOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+ postIds: number[];
+ posts: string[];
+ roleIds: number[];
+ roles: string[];
+ depts: DataNode[];
+};
+
+const UserForm: React.FC<UserFormProps> = (props) => {
+ const [form] = Form.useForm();
+ const userId = Form.useWatch('userId', form);
+ const { sexOptions, statusOptions, } = props;
+ const { roles, posts, depts } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ userId: props.values.userId,
+ deptId: props.values.deptId,
+ postIds: props.postIds,
+ roleIds: props.roleIds,
+ userName: props.values.userName,
+ nickName: props.values.nickName,
+ email: props.values.email,
+ phonenumber: props.values.phonenumber,
+ sex: props.values.sex,
+ avatar: props.values.avatar,
+ status: props.values.status,
+ delFlag: props.values.delFlag,
+ loginIp: props.values.loginIp,
+ loginDate: props.values.loginDate,
+ remark: props.values.remark,
+ });
+ }, [form, props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ form.submit();
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+ const handleFinish = async (values: Record<string, any>) => {
+ props.onSubmit(values as UserFormData);
+ };
+
+ return (
+ <Modal
+ width={640}
+ title={intl.formatMessage({
+ id: 'system.user.title',
+ defaultMessage: '编辑用户信息',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <ProForm
+ grid={true}
+ form={form}
+ layout="horizontal"
+ submitter={false}
+ onFinish={handleFinish}>
+ <ProFormText
+ name="nickName"
+ label={intl.formatMessage({
+ id: 'system.user.nick_name',
+ defaultMessage: '用户昵称',
+ })}
+ placeholder="请输入用户昵称"
+ colProps={{ xs: 24, md: 12, xl: 12 }}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormTreeSelect
+ name="deptId"
+ label={intl.formatMessage({
+ id: 'system.user.dept_name',
+ defaultMessage: '部门',
+ })}
+ request={async () => {
+ return depts;
+ }}
+ placeholder="请输入用户部门"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormText
+ name="phonenumber"
+ label={intl.formatMessage({
+ id: 'system.user.phonenumber',
+ defaultMessage: '手机号码',
+ })}
+ placeholder="请输入手机号码"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: false,
+ message: (
+ <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormText
+ name="email"
+ label={intl.formatMessage({
+ id: 'system.user.email',
+ defaultMessage: '用户邮箱',
+ })}
+ placeholder="请输入用户邮箱"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: false,
+ message: (
+ <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormText
+ name="userName"
+ label={intl.formatMessage({
+ id: 'system.user.user_name',
+ defaultMessage: '用户账号',
+ })}
+ hidden={userId}
+ placeholder="请输入用户账号"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="password"
+ label={intl.formatMessage({
+ id: 'system.user.password',
+ defaultMessage: '密码',
+ })}
+ hidden={userId}
+ placeholder="请输入密码"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />,
+ },
+ ]}
+ />
+ <ProFormSelect
+ valueEnum={sexOptions}
+ name="sex"
+ label={intl.formatMessage({
+ id: 'system.user.sex',
+ defaultMessage: '用户性别',
+ })}
+ initialValue={'0'}
+ placeholder="请输入用户性别"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: false,
+ message: (
+ <FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormRadio.Group
+ valueEnum={statusOptions}
+ name="status"
+ label={intl.formatMessage({
+ id: 'system.user.status',
+ defaultMessage: '帐号状态',
+ })}
+ initialValue={'0'}
+ placeholder="请输入帐号状态"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: false,
+ message: (
+ <FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />
+ ),
+ },
+ ]}
+ />
+ <ProFormSelect
+ name="postIds"
+ mode="multiple"
+ label={intl.formatMessage({
+ id: 'system.user.post',
+ defaultMessage: '岗位',
+ })}
+ options={posts}
+ placeholder="请选择岗位"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[{ required: true, message: '请选择岗位!' }]}
+ />
+ <ProFormSelect
+ name="roleIds"
+ mode="multiple"
+ label={intl.formatMessage({
+ id: 'system.user.role',
+ defaultMessage: '角色',
+ })}
+ options={roles}
+ placeholder="请选择角色"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[{ required: true, message: '请选择角色!' }]}
+ />
+ <ProFormTextArea
+ name="remark"
+ label={intl.formatMessage({
+ id: 'system.user.remark',
+ defaultMessage: '备注',
+ })}
+ placeholder="请输入备注"
+ colProps={{ md: 24, xl: 24 }}
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+ },
+ ]}
+ />
+ </ProForm>
+ </Modal>
+ );
+};
+
+export default UserForm;
diff --git a/src/pages/System/User/index.tsx b/src/pages/System/User/index.tsx
new file mode 100644
index 0000000..6435787
--- /dev/null
+++ b/src/pages/System/User/index.tsx
@@ -0,0 +1,561 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getUserList, removeUser, addUser, updateUser, exportUser, getUser, changeUserStatus, updateAuthRole, resetUserPwd } from '@/services/system/user';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { DataNode } from 'antd/es/tree';
+import { getDeptTree } from '@/services/system/user';
+import DeptTree from './components/DeptTree';
+import ResetPwd from './components/ResetPwd';
+import { getPostList } from '@/services/system/post';
+import { getRoleList } from '@/services/system/role';
+import AuthRoleForm from './components/AuthRole';
+
+const { confirm } = Modal;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/06
+ *
+ * */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.User) => {
+ const hide = message.loading('正在添加');
+ try {
+ await addUser({ ...fields });
+ hide();
+ message.success('添加成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('添加失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.User) => {
+ const hide = message.loading('正在配置');
+ try {
+ await updateUser(fields);
+ hide();
+ message.success('配置成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.User[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ await removeUser(selectedRows.map((row) => row.userId).join(','));
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.User) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.userId];
+ await removeUser(params.join(','));
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportUser();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+const UserTableList: React.FC = () => {
+ const [messageApi, contextHolder] = message.useMessage();
+
+ const formTableRef = useRef<FormInstance>();
+
+ const [modalVisible, setModalVisible] = useState<boolean>(false);
+ const [resetPwdModalVisible, setResetPwdModalVisible] = useState<boolean>(false);
+ const [authRoleModalVisible, setAuthRoleModalVisible] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<API.System.User>();
+ const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+
+ const [selectDept, setSelectDept] = useState<any>({ id: 0 });
+ const [sexOptions, setSexOptions] = useState<any>([]);
+ const [statusOptions, setStatusOptions] = useState<any>([]);
+
+ const [postIds, setPostIds] = useState<number[]>();
+ const [postList, setPostList] = useState<any[]>();
+ const [roleIds, setRoleIds] = useState<number[]>();
+ const [roleList, setRoleList] = useState<any[]>();
+ const [deptTree, setDeptTree] = useState<DataNode[]>();
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_user_sex').then((data) => {
+ setSexOptions(data);
+ });
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const showChangeStatusConfirm = (record: API.System.User) => {
+ let text = record.status === "1" ? "启用" : "停用";
+ const newStatus = record.status === '0' ? '1' : '0';
+ confirm({
+ title: `确认要${text}${record.userName}用户吗?`,
+ onOk() {
+ changeUserStatus(record.userId, newStatus).then(resp => {
+ if (resp.code === 200) {
+ messageApi.open({
+ type: 'success',
+ content: '更新成功!',
+ });
+ actionRef.current?.reload();
+ } else {
+ messageApi.open({
+ type: 'error',
+ content: '更新失败!',
+ });
+ }
+ });
+ },
+ });
+ };
+
+ const fetchUserInfo = async (userId: number) => {
+ const res = await getUser(userId);
+ setPostIds(res.postIds);
+ setPostList(
+ res.posts.map((item: any) => {
+ return {
+ value: item.postId,
+ label: item.postName,
+ };
+ }),
+ );
+ setRoleIds(res.roleIds);
+ setRoleList(
+ res.roles.map((item: any) => {
+ return {
+ value: item.roleId,
+ label: item.roleName,
+ };
+ }),
+ );
+ };
+
+ const columns: ProColumns<API.System.User>[] = [
+ {
+ title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+ dataIndex: 'deptId',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+ dataIndex: 'nickName',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
+ dataIndex: ['dept', 'deptName'],
+ valueType: 'text',
+ hideInSearch: true
+ },
+ {
+ title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+ dataIndex: 'phonenumber',
+ valueType: 'text',
+ },
+ {
+ title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (
+ <Switch
+ checked={record.status === '0'}
+ checkedChildren="正常"
+ unCheckedChildren="停用"
+ defaultChecked
+ onClick={() => showChangeStatusConfirm(record)}
+ />)
+ },
+ },
+ {
+ title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="edit"
+ icon=<EditOutlined />
+ hidden={!access.hasPerms('system:user:edit')}
+ onClick={async () => {
+ fetchUserInfo(record.userId);
+ const treeData = await getDeptTree({});
+ setDeptTree(treeData);
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ icon=<DeleteOutlined />
+ key="batchRemove"
+ hidden={!access.hasPerms('system:user:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ <Dropdown
+ key="more"
+ menu={{
+ items: [
+ {
+ label: <FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />,
+ key: 'reset',
+ disabled: !access.hasPerms('system:user:edit'),
+ },
+ {
+ label: '分配角色',
+ key: 'authRole',
+ disabled: !access.hasPerms('system:user:edit'),
+ },
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reset') {
+ setResetPwdModalVisible(true);
+ setCurrentRow(record);
+ }
+ else if (key === 'authRole') {
+ fetchUserInfo(record.userId);
+ setAuthRoleModalVisible(true);
+ setCurrentRow(record);
+ }
+ }
+ }}
+ >
+ <a onClick={(e) => e.preventDefault()}>
+ <Space>
+ <DownOutlined />
+ 更多
+ </Space>
+ </a>
+ </Dropdown>,
+ ],
+ },
+ ];
+
+ return (
+ <PageContainer>
+ {contextHolder}
+ <Row gutter={[16, 24]}>
+ <Col lg={6} md={24}>
+ <Card>
+ <DeptTree
+ onSelect={async (value: any) => {
+ setSelectDept(value);
+ if (actionRef.current) {
+ formTableRef?.current?.submit();
+ }
+ }}
+ />
+ </Card>
+ </Col>
+ <Col lg={18} md={24}>
+ <ProTable<API.System.User>
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="userId"
+ key="userList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="add"
+ hidden={!access.hasPerms('system:user:add')}
+ onClick={async () => {
+ const treeData = await getDeptTree({});
+ setDeptTree(treeData);
+
+ const postResp = await getPostList()
+ if (postResp.code === 200) {
+ setPostList(
+ postResp.rows.map((item: any) => {
+ return {
+ value: item.postId,
+ label: item.postName,
+ };
+ }),
+ );
+ }
+
+ const roleResp = await getRoleList()
+ if (roleResp.code === 200) {
+ setRoleList(
+ roleResp.rows.map((item: any) => {
+ return {
+ value: item.roleId,
+ label: item.roleName,
+ };
+ }),
+ );
+ }
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+ </Button>,
+ <Button
+ type="primary"
+ key="remove"
+ danger
+ hidden={selectedRows?.length === 0 || !access.hasPerms('system:user:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: <ExclamationCircleOutlined />,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+ <DeleteOutlined />
+ <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+ </Button>,
+ <Button
+ type="primary"
+ key="export"
+ hidden={!access.hasPerms('system:user:export')}
+ onClick={async () => {
+ handleExport();
+ }}
+ >
+ <PlusOutlined />
+ <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then((res) => {
+ const result = {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ return result;
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ </Col>
+ </Row>
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="remove"
+ danger
+ hidden={!access.hasPerms('system:user:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <UpdateForm
+ onSubmit={async (values) => {
+ let success = false;
+ if (values.userId) {
+ success = await handleUpdate({ ...values } as API.System.User);
+ } else {
+ success = await handleAdd({ ...values } as API.System.User);
+ }
+ if (success) {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ }}
+ onCancel={() => {
+ setModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalVisible}
+ values={currentRow || {}}
+ sexOptions={sexOptions}
+ statusOptions={statusOptions}
+ posts={postList || []}
+ postIds={postIds || []}
+ roles={roleList || []}
+ roleIds={roleIds || []}
+ depts={deptTree || []}
+ />
+ <ResetPwd
+ onSubmit={async (values: any) => {
+ const success = await resetUserPwd(values.userId, values.password);
+ if (success) {
+ setResetPwdModalVisible(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ message.success('密码重置成功。');
+ }
+ }}
+ onCancel={() => {
+ setResetPwdModalVisible(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ }}
+ open={resetPwdModalVisible}
+ values={currentRow || {}}
+ />
+ <AuthRoleForm
+ onSubmit={async (values: any) => {
+ const success = await updateAuthRole(values);
+ if (success) {
+ setAuthRoleModalVisible(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ message.success('配置成功。');
+ }
+ }}
+ onCancel={() => {
+ setAuthRoleModalVisible(false);
+ setSelectedRows([]);
+ setCurrentRow(undefined);
+ }}
+ open={authRoleModalVisible}
+ roles={roleList || []}
+ roleIds={roleIds || []}
+ />
+ </PageContainer>
+ );
+};
+
+export default UserTableList;
diff --git a/src/pages/Tool/Build/index.tsx b/src/pages/Tool/Build/index.tsx
new file mode 100644
index 0000000..647d729
--- /dev/null
+++ b/src/pages/Tool/Build/index.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+
+const testIndex :React.FC = ()=>{
+ return (
+ <div>111</div>
+ );
+}
+export default testIndex;
diff --git a/src/pages/Tool/Gen/components/BaseInfo.tsx b/src/pages/Tool/Gen/components/BaseInfo.tsx
new file mode 100644
index 0000000..6a0466f
--- /dev/null
+++ b/src/pages/Tool/Gen/components/BaseInfo.tsx
@@ -0,0 +1,124 @@
+import { Button, Col, Form, message, Row } from 'antd';
+import React, { Fragment, useEffect } from 'react';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { ProForm, ProFormText, ProFormTextArea } from '@ant-design/pro-components';
+
+export type BaseInfoProps = {
+ values?: any;
+ onStepSubmit?: any;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+ const [form] = Form.useForm();
+ const { onStepSubmit } = props;
+
+ useEffect(() => {
+ form.resetFields();
+ form.setFieldsValue({
+ tableName: props.values.tableName,
+ });
+ });
+
+ const onValidateForm = async () => {
+ const values = await form.validateFields();
+ if (onStepSubmit) {
+ onStepSubmit('base', values);
+ }
+ };
+
+ return (
+ <Fragment>
+ <Row>
+ <Col span={24}>
+ <ProForm
+ form={form}
+ onFinish={async () => {
+ message.success('提交成功');
+ }}
+ initialValues={{
+ tableName: props.values?.tableName,
+ tableComment: props.values?.tableComment,
+ className: props.values?.className,
+ functionAuthor: props.values?.functionAuthor,
+ remark: props.values?.remark,
+ }}
+ submitter={{
+ resetButtonProps: {
+ style: { display: 'none' },
+ },
+ submitButtonProps: {
+ style: { display: 'none' },
+ },
+ }}
+ >
+ <Row>
+ <Col span={12} order={1}>
+ <ProFormText
+
+ name="tableName"
+ label="表名称"
+ rules={[
+ {
+ required: true,
+ message: '表名称不可为空。',
+ },
+ ]}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormText
+ name="tableComment"
+ label="表描述"
+ />
+ </Col>
+ </Row>
+ <Row>
+ <Col span={12} order={1}>
+ <ProFormText
+
+ name="className"
+ label="实体类名称"
+ rules={[
+ {
+ required: true,
+ message: '实体类名称不可为空。',
+ },
+ ]}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormText name="functionAuthor" label="作者" />
+ </Col>
+ </Row>
+ <Row>
+ <Col span={24}>
+ <ProFormTextArea name="remark" label="备注" />
+ </Col>
+ </Row>
+ </ProForm>
+ </Col>
+ </Row>
+ <Row justify="center">
+ <Col span={4}>
+ <Button
+ type="primary"
+ className={styles.step_buttons}
+ onClick={() => {
+ history.back();
+ }}
+ >
+ 返回
+ </Button>
+ </Col>
+ <Col span={4}>
+ <Button type="primary" onClick={onValidateForm}>
+ 下一步
+ </Button>
+ </Col>
+ </Row>
+ </Fragment>
+ );
+};
+
+export default BaseInfo;
diff --git a/src/pages/Tool/Gen/components/ColumnInfo.tsx b/src/pages/Tool/Gen/components/ColumnInfo.tsx
new file mode 100644
index 0000000..2db5de4
--- /dev/null
+++ b/src/pages/Tool/Gen/components/ColumnInfo.tsx
@@ -0,0 +1,304 @@
+import React, { Fragment, useEffect, useRef, useState } from 'react';
+import type { GenCodeType } from '../data';
+import { Button, Checkbox, Col, Row, Tag } from 'antd';
+import type { FormInstance } from 'antd';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { EditableProTable, ProColumns } from '@ant-design/pro-components';
+
+export type ColumnInfoProps = {
+ parentType?: string;
+ data?: any[];
+ dictData?: any[];
+ onStepSubmit?: any;
+};
+
+const booleanEnum = [
+ {
+ label: 'true',
+ value: '1',
+ },
+ {
+ label: 'false',
+ value: '0',
+ },
+];
+
+const ColumnInfo: React.FC<ColumnInfoProps> = (props) => {
+ const formRef = useRef<FormInstance>();
+
+ const [dataSource, setDataSource] = useState<any[]>();
+
+ const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
+
+ const { data, dictData, onStepSubmit } = props;
+
+ const columns: ProColumns<GenCodeType>[] = [
+ {
+ title: '编号',
+ dataIndex: 'columnId',
+ editable: false,
+ width: 80,
+ },
+ {
+ title: '字段名',
+ dataIndex: 'columnName',
+ editable: false,
+ },
+ {
+ title: '字段描述',
+ dataIndex: 'columnComment',
+ hideInForm: true,
+ hideInSearch: true,
+ width: 200,
+ },
+ {
+ title: '字段类型',
+ dataIndex: 'columnType',
+ editable: false,
+ },
+ {
+ title: 'Java类型',
+ dataIndex: 'javaType',
+ valueType: 'select',
+ valueEnum: {
+ Long: {
+ text: 'Long',
+ },
+ String: {
+ text: 'String',
+ },
+ Integer: {
+ text: 'Integer',
+ },
+ Double: {
+ text: 'Double',
+ },
+ BigDecimal: {
+ text: 'BigDecimal',
+ },
+ Date: {
+ text: 'Date',
+ },
+ },
+ },
+ {
+ title: 'Java属性',
+ dataIndex: 'javaField',
+ },
+ {
+ title: '插入',
+ dataIndex: 'isInsert',
+ valueType: 'select',
+ fieldProps: {
+ options: booleanEnum,
+ },
+ render: (_, record) => {
+ return <Checkbox checked={record.isInsert === '1'} />;
+ },
+ },
+ {
+ title: '编辑',
+ dataIndex: 'isEdit',
+ valueType: 'select',
+ fieldProps: {
+ options: booleanEnum,
+ },
+ render: (_, record) => {
+ return <Checkbox checked={record.isEdit === '1'} />;
+ },
+ },
+ {
+ title: '列表',
+ dataIndex: 'isList',
+ valueType: 'select',
+ fieldProps: {
+ options: booleanEnum,
+ },
+ render: (_, record) => {
+ return <Checkbox checked={record.isList === '1'} />;
+ },
+ },
+ {
+ title: '查询',
+ dataIndex: 'isQuery',
+ valueType: 'select',
+ fieldProps: {
+ options: booleanEnum,
+ },
+ render: (_, record) => {
+ return <Checkbox checked={record.isQuery === '1'} />;
+ },
+ },
+ {
+ title: '查询方式',
+ dataIndex: 'queryType',
+ valueType: 'select',
+ valueEnum: {
+ EQ: {
+ text: '=',
+ },
+ NE: {
+ text: '!=',
+ },
+ GT: {
+ text: '>',
+ },
+ GTE: {
+ text: '>=',
+ },
+ LT: {
+ text: '<',
+ },
+ LTE: {
+ text: '<=',
+ },
+ LIKE: {
+ text: 'LIKE',
+ },
+ BETWEEN: {
+ text: 'BETWEEN',
+ },
+ },
+ },
+ {
+ title: '必填',
+ dataIndex: 'isRequired',
+ valueType: 'select',
+ fieldProps: {
+ options: booleanEnum,
+ },
+ render: (_, record) => {
+ return <Checkbox checked={record.isRequired === '1'} />;
+ },
+ },
+ {
+ title: '显示类型',
+ dataIndex: 'htmlType',
+ hideInSearch: true,
+ valueType: 'select',
+ valueEnum: {
+ input: {
+ text: '文本框',
+ },
+ textarea: {
+ text: '文本域',
+ },
+ select: {
+ text: '下拉框',
+ },
+ radio: {
+ text: '单选框',
+ },
+ checkbox: {
+ text: '复选框',
+ },
+ datetime: {
+ text: '日期控件',
+ },
+ imageUpload: {
+ text: '图片上传',
+ },
+ fileUpload: {
+ text: '文件上传',
+ },
+ editor: {
+ text: '富文本控件',
+ },
+ },
+ },
+ {
+ title: '字典类型',
+ dataIndex: 'dictType',
+ hideInSearch: true,
+ valueType: 'select',
+ fieldProps: {
+ options: dictData,
+ },
+ render: (text) => {
+ return <Tag color="#108ee9">{text}</Tag>;
+ },
+ },
+ ];
+
+ useEffect(() => {
+ setDataSource(data);
+ if (data) {
+ setEditableRowKeys(data.map((item) => item.columnId));
+ }
+ }, [data]);
+
+ const onSubmit = (direction: string) => {
+ if (onStepSubmit) {
+ onStepSubmit('column', dataSource, direction);
+ }
+ };
+
+ const onDataChange = (value: readonly GenCodeType[]) => {
+ setDataSource({ ...value } as []);
+ };
+
+ return (
+ <Fragment>
+ <Row>
+ <Col span={24}>
+ <EditableProTable<GenCodeType>
+ formRef={formRef}
+ rowKey="columnId"
+ search={false}
+ columns={columns}
+ value={dataSource}
+ editable={{
+ type: 'multiple',
+ editableKeys,
+ onChange: setEditableRowKeys,
+ actionRender: (row, config, defaultDoms) => {
+ return [defaultDoms.delete];
+ },
+ onValuesChange: (record, recordList) => {
+ setDataSource(recordList);
+ },
+ }}
+ onChange={onDataChange}
+ recordCreatorProps={false}
+ />
+ </Col>
+ </Row>
+ <Row justify="center">
+ <Col span={4}>
+ <Button
+ type="primary"
+ onClick={() => {
+ history.back();
+ }}
+ >
+ 返回
+ </Button>
+ </Col>
+ <Col span={4}>
+ <Button
+ type="primary"
+ className={styles.step_buttons}
+ onClick={() => {
+ onSubmit('prev');
+ }}
+ >
+ 上一步
+ </Button>
+ </Col>
+ <Col span={4}>
+ <Button
+ type="primary"
+ onClick={() => {
+ onSubmit('next');
+ }}
+ >
+ 下一步
+ </Button>
+ </Col>
+ </Row>
+ </Fragment>
+ );
+};
+
+export default ColumnInfo;
diff --git a/src/pages/Tool/Gen/components/GenInfo.tsx b/src/pages/Tool/Gen/components/GenInfo.tsx
new file mode 100644
index 0000000..97901ec
--- /dev/null
+++ b/src/pages/Tool/Gen/components/GenInfo.tsx
@@ -0,0 +1,330 @@
+import { Button, Col, Divider, Form, Row, TreeSelect } from 'antd';
+import React, { Fragment, useEffect, useState } from 'react';
+import { history } from '@umijs/max';
+import type { TableInfo } from '../data';
+import styles from '../style.less';
+import { DataNode } from 'antd/es/tree';
+import { ProForm, ProFormRadio, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+
+export type GenInfoProps = {
+ values?: any;
+ menuData?: DataNode[];
+ tableInfo?: TableInfo[];
+ onStepSubmit?: any;
+};
+
+const GenInfo: React.FC<GenInfoProps> = (props) => {
+ const [form] = Form.useForm();
+
+ const [pathType, setPathType] = useState<string>('0');
+ const [tlpType, setTlpType] = useState<string>('curd');
+
+ const [subTablesColumnOptions, setSubTablesColumnOptions] = useState<any[]>();
+
+ const { menuData, tableInfo, onStepSubmit } = props;
+
+ const tablesOptions = tableInfo?.map((item: any) => {
+ return {
+ value: item.tableName,
+ label: `${item.tableName}:${item.tableComment}`,
+ };
+ });
+
+ if (tableInfo) {
+ for (let index = 0; index < tableInfo?.length; index += 1) {
+ const tbl = tableInfo[index];
+ if (tbl.tableName === props.values.subTableName) {
+ const opts = [];
+ tbl.columns.forEach((item) => {
+ opts.push({
+ value: item.columnName,
+ label: `${item.columnName}: ${item.columnComment}`,
+ });
+ });
+ break;
+ }
+ }
+ }
+
+ const treeColumns = props.values.columns.map((item: any) => {
+ return {
+ value: item.columnName,
+ label: `${item.columnName}: ${item.columnComment}`,
+ };
+ });
+
+ const onSubmit = async (direction: string) => {
+ const values = await form.validateFields();
+ onStepSubmit('gen', values, direction);
+ };
+
+ useEffect(() => {
+ setPathType(props.values.genType);
+ setTlpType(props.values.tplCategory);
+ }, [props.values.genType, props.values.tplCategory]);
+
+ return (
+ <Fragment>
+ <Row>
+ <Col span={24}>
+ <ProForm
+ form={form}
+ onFinish={async () => {
+ const values = await form.validateFields();
+ onStepSubmit('gen', values);
+ }}
+ initialValues={{
+ curd: props.values.curd,
+ tree: props.values.tree,
+ sub: props.values.sub,
+ tplCategory: props.values.tplCategory,
+ packageName: props.values.packageName,
+ moduleName: props.values.moduleName,
+ businessName: props.values.businessName,
+ functionName: props.values.functionName,
+ parentMenuId: props.values.parentMenuId,
+ genType: props.values.genType,
+ genPath: props.values.genPath,
+ treeCode: props.values.treeCode,
+ treeParentCode: props.values.treeParentCode,
+ treeName: props.values.treeName,
+ subTableName: props.values.subTableName,
+ subTableFkName: props.values.subTableFkName,
+ }}
+ submitter={{
+ resetButtonProps: {
+ style: { display: 'none' },
+ },
+ submitButtonProps: {
+ style: { display: 'none' },
+ },
+ }}
+ >
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormSelect
+ fieldProps={{
+ onChange: (val) => {
+ setTlpType(val);
+ },
+ }}
+ valueEnum={{
+ crud: '单表(增删改查)',
+ tree: '树表(增删改查)',
+ sub: '主子表(增删改查)',
+ }}
+ name="tplCategory"
+ label="生成模板"
+ rules={[
+ {
+ required: true,
+ message: '选择类型',
+ },
+ ]}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormText name="packageName" label="生成包路径" />
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormText name="moduleName" label="生成模块名" />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormText name="businessName" label="生成业务名" />
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormText name="functionName" label="生成功能名" />
+ </Col>
+ <Col span={12} order={2}>
+ <ProForm.Item
+ labelCol={{ span: 20 }}
+ name="parentMenuId"
+ label="父菜单"
+ >
+ <TreeSelect
+ style={{ width: '74%' }}
+ defaultValue={props.values.parentMenuId}
+ treeData={menuData}
+ placeholder="请选择父菜单"
+ />
+ </ProForm.Item>
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <ProFormRadio.Group
+ valueEnum={{
+ '0': 'zip压缩包',
+ '1': '自定义路径',
+ }}
+ name="genType"
+ label="生成代码方式"
+ rules={[
+ {
+ required: true,
+ message: '选择类型',
+ },
+ ]}
+ fieldProps={{
+ onChange: (e) => {
+ setPathType(e.target.value);
+ },
+ }}
+ />
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={24} order={1}>
+ <ProFormText
+ hidden={pathType === '0'}
+ width="md"
+ name="genPath"
+ label="自定义路径"
+ />
+ </Col>
+ </Row>
+ <div hidden={tlpType !== 'tree'}>
+ <Divider orientation="left">其他信息</Divider>
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormSelect
+ name="treeCode"
+ label="树编码字段"
+ options={treeColumns}
+ rules={[
+ {
+ required: tlpType === 'tree',
+ message: '树编码字段',
+ },
+ ]}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormSelect
+ name="treeParentCode"
+ label="树父编码字段"
+ options={treeColumns}
+ rules={[
+ {
+ required: tlpType === 'tree',
+ message: '树父编码字段',
+ },
+ ]}
+ />
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormSelect
+ name="treeName"
+ label="树名称字段"
+ options={treeColumns}
+ rules={[
+ {
+ required: tlpType === 'tree',
+ message: '树名称字段',
+ },
+ ]}
+ />
+ </Col>
+ </Row>
+ </div>
+ <div hidden={tlpType !== 'sub'}>
+ <Divider orientation="left">关联信息</Divider>
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <ProFormSelect
+ name="subTableName"
+ label="关联子表的表名"
+ options={tablesOptions}
+ rules={[
+ {
+ required: tlpType === 'sub',
+ message: '关联子表的表名',
+ },
+ ]}
+ fieldProps={{
+ onChange: (val) => {
+ form.setFieldsValue({
+ subTableFkName: '',
+ });
+ if (tableInfo) {
+ for (let index = 0; index < tableInfo?.length; index += 1) {
+ const tbl = tableInfo[index];
+ if (tbl.tableName === val) {
+ const opts: any[] = [];
+ tbl.columns.forEach((item) => {
+ opts.push({
+ value: item.columnName,
+ label: `${item.columnName}:${item.columnComment}`,
+ });
+ });
+ setSubTablesColumnOptions(opts);
+ break;
+ }
+ }
+ }
+ },
+ }}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <ProFormSelect
+ name="subTableFkName"
+ options={subTablesColumnOptions}
+ label="子表关联的外键名"
+ rules={[
+ {
+ required: tlpType === 'sub',
+ message: '子表关联的外键名',
+ },
+ ]}
+ />
+ </Col>
+ </Row>
+ </div>
+ </ProForm>
+ </Col>
+ </Row>
+ <Row justify="center">
+ <Col span={4}>
+ <Button
+ type="primary"
+ onClick={() => {
+ history.back();
+ }}
+ >
+ 返回
+ </Button>
+ </Col>
+ <Col span={4}>
+ <Button
+ type="primary"
+ className={styles.step_buttons}
+ onClick={() => {
+ onSubmit('prev');
+ }}
+ >
+ 上一步
+ </Button>
+ </Col>
+ <Col span={4}>
+ <Button
+ type="primary"
+ onClick={() => {
+ onSubmit('next');
+ }}
+ >
+ 提交
+ </Button>
+ </Col>
+ </Row>
+ </Fragment>
+ );
+};
+
+export default GenInfo;
diff --git a/src/pages/Tool/Gen/components/PreviewCode.tsx b/src/pages/Tool/Gen/components/PreviewCode.tsx
new file mode 100644
index 0000000..f3611ad
--- /dev/null
+++ b/src/pages/Tool/Gen/components/PreviewCode.tsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react';
+import { useIntl } from '@umijs/max';
+import { Modal, Tabs } from 'antd';
+import type { TabsProps } from 'antd';
+import Highlight from 'react-highlight';
+import 'highlight.js/styles/base16/material.css';
+
+interface PreviewTableProps {
+ open: boolean;
+ data?: any;
+ onHide: () => void;
+}
+
+const PreviewTableCode: React.FC<PreviewTableProps> = (props) => {
+ const intl = useIntl();
+ const panes: any = [];
+ const keys = Object.keys(props.data);
+ keys.forEach((key) => {
+ panes.push({
+ key: key + '1',
+ label: key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm')),
+ children: <Highlight className="java">{props.data[key]}</Highlight>,
+ } as TabsProps);
+ });
+
+ useEffect(() => {}, []);
+
+ return (
+ <Modal
+ width={900}
+ title={intl.formatMessage({
+ id: 'gen.preview',
+ defaultMessage: '预览',
+ })}
+ open={props.open}
+ destroyOnClose
+ footer={false}
+ onOk={() => {
+ props.onHide();
+ }}
+ onCancel={() => {
+ props.onHide();
+ }}
+ >
+ <Tabs defaultActiveKey="1" items={panes}></Tabs>
+ </Modal>
+ );
+};
+
+export default PreviewTableCode;
diff --git a/src/pages/Tool/Gen/data.d.ts b/src/pages/Tool/Gen/data.d.ts
new file mode 100644
index 0000000..a63ea9d
--- /dev/null
+++ b/src/pages/Tool/Gen/data.d.ts
@@ -0,0 +1,106 @@
+export type GenCodeType = {
+ columnId: string;
+ businessName: string;
+ className: string;
+ createBy: string;
+ createTime: string;
+ crud: string;
+ functionAuthor: string;
+ functionName: string;
+ genPath: string;
+ genType: string;
+ moduleName: string;
+ options: string;
+ packageName: string;
+ params: string;
+ parentMenuId: string;
+ parentMenuName: string;
+ remark: string;
+ status: string;
+ isList: string;
+ isEdit: string;
+ isQuery: string;
+ isInsert: string;
+ isRequired: string;
+ tableComment: string;
+ tableId: string;
+ tableName: string;
+ tplCategory: string;
+ tree: string;
+ treeCode: number;
+ treeName: string;
+ treeParentCode: number;
+ updateBy: string;
+ updateTime: string;
+ columns: any;
+};
+
+export type GenCodePagination = {
+ total: number;
+ pageSize: number;
+ current: number;
+};
+
+export type GenCodeListData = {
+ list: GenCodeType[];
+ pagination: Partial<GenCodePagination>;
+};
+
+export type GenCodeTableListParams = {
+ businessName?: string;
+ className?: string;
+ createBy?: string;
+ createTime?: string;
+ crud?: string;
+ functionAuthor?: string;
+ functionName?: string;
+ genPath?: string;
+ genType?: string;
+ moduleName?: string;
+ isList?: string;
+ isEdit?: string;
+ isQuery?: string;
+ isInsert?: string;
+ isRequired?: string;
+ options?: string;
+ params?: any;
+ packageName?: string;
+ parentMenuId?: string;
+ columns?: any;
+ parentMenuName?: string;
+ remark?: string;
+ tableComment?: string;
+ tableId?: string;
+ tableName?: string;
+ tplCategory?: string;
+ tree?: string;
+ treeCode?: string;
+ treeName?: string;
+ treeParentCode?: string;
+ updateBy?: string;
+ updateTime?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+};
+
+export type TableColumnInfo = {
+ columnName: string;
+ columnComment: string;
+};
+
+export type TableInfo = {
+ tableName: string;
+ tableComment: string;
+ columns: TableColumnInfo[];
+};
+
+export type ImportTableListItem = {
+ tableName: string;
+ tableComment: string;
+};
+
+export type ImportTableParams = {
+ tables: string;
+};
diff --git a/src/pages/Tool/Gen/edit.tsx b/src/pages/Tool/Gen/edit.tsx
new file mode 100644
index 0000000..99a0d1a
--- /dev/null
+++ b/src/pages/Tool/Gen/edit.tsx
@@ -0,0 +1,183 @@
+import React, { useEffect, useState } from 'react';
+import BaseInfo from './components/BaseInfo';
+import { Card, Layout, message, Steps } from 'antd';
+import ColumnInfo from './components/ColumnInfo';
+import GenInfo from './components/GenInfo';
+import { getGenCode, updateData } from './service';
+import { formatTreeData } from '@/utils/tree';
+import styles from './style.less';
+import type { GenCodeType } from './data';
+import { getMenuTree } from '@/services/system/menu';
+import { getDictTypeList } from '@/services/system/dict';
+import queryString from 'query-string';
+import { useLocation } from '@umijs/max';
+
+const { Content } = Layout;
+
+export type GenCodeArgs = {
+ id: string;
+};
+
+const TableList: React.FC = () => {
+ const location = useLocation();
+ const query = queryString.parse(location.search);
+ const { id } = query as GenCodeArgs;
+ const tableId = id;
+
+ const [currentStep, setCurrentStep] = useState<number>(0);
+ const [columnData, setColumnData] = useState<any>([]);
+ const [baseInfoData, setBaseInfoData] = useState<any>([]);
+ const [genInfoData, setGenInfoData] = useState<any>([]);
+ const [menuTree, setMenuTree] = useState<any>([]);
+ const [dictData, setDictData] = useState<any>([]);
+ const [tableInfo, setTableInfo] = useState<any>([]);
+ const [formData, setFormData] = useState<any>([]);
+ const [stepComponent, setStepComponent] = useState<any>([]);
+ const [stepKey, setStepKey] = useState<string>('');
+
+ const getCurrentStepAndComponent = (key?: string) => {
+ if (key === 'base') {
+ return (
+ <BaseInfo
+ values={baseInfoData}
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ onStepSubmit={onNextStep}
+ />
+ );
+ }
+ if (key === 'column') {
+ return (
+ <ColumnInfo
+ data={columnData}
+ dictData={dictData}
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ onStepSubmit={onNextStep}
+ />
+ );
+ }
+ if (key === 'gen') {
+ return (
+ <GenInfo
+ values={genInfoData}
+ menuData={menuTree}
+ tableInfo={tableInfo}
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ onStepSubmit={onNextStep}
+ />
+ );
+ }
+ return null;
+ };
+
+ const onNextStep = (step: string, values: any, direction: string) => {
+ let stepKey = 'base';
+ if (step === 'base') {
+ setStepKey('column');
+ setCurrentStep(1);
+ setFormData(values);
+ setStepComponent(getCurrentStepAndComponent(stepKey));
+ } else if (step === 'column') {
+ if (direction === 'prev') {
+ setStepKey('base');
+ setCurrentStep(0);
+ } else {
+ setStepKey('gen');
+ const tableData: GenCodeType = formData || ({} as GenCodeType);
+ tableData.columns = values;
+ setCurrentStep(2);
+ setFormData(tableData);
+ }
+ setStepComponent(getCurrentStepAndComponent(stepKey));
+ } else if (step === 'gen') {
+ if (direction === 'prev') {
+ setStepKey('column');
+ setCurrentStep(1);
+ setStepComponent(getCurrentStepAndComponent(stepKey));
+ } else {
+ const postData: GenCodeType = {
+ ...formData,
+ ...values,
+ params: values,
+ tableId: tableId,
+ };
+ setFormData(postData);
+ updateData({ ...postData } as GenCodeType).then((res) => {
+ if (res.code === 200) {
+ message.success('提交成功');
+ history.back();
+ } else {
+ message.success('提交失败');
+ }
+ });
+ }
+ }
+ };
+ useEffect(() => {
+ setStepComponent(getCurrentStepAndComponent(stepKey));
+ }, [stepKey]);
+
+ useEffect(() => {
+ getGenCode(tableId).then((res) => {
+ if (res.code === 200) {
+ setBaseInfoData(res.data.info);
+ setColumnData(res.data.rows);
+ setGenInfoData(res.data.info);
+ setTableInfo(res.data.tables);
+ setStepKey('base');
+ } else {
+ message.error(res.msg);
+ }
+ });
+ getMenuTree().then((res) => {
+ if (res.code === 200) {
+ const treeData = formatTreeData(res.data);
+ setMenuTree(treeData);
+ } else {
+ message.error(res.msg);
+ }
+ });
+
+ getDictTypeList().then((res: any) => {
+ if (res.code === 200) {
+ const dicts = res.rows.map((item: any) => {
+ return {
+ label: item.dictName,
+ value: item.dictType,
+ };
+ });
+ setDictData(dicts);
+ } else {
+ message.error(res.msg);
+ }
+ });
+ }, []);
+
+ // const onFinish = (values: any) => {
+ // console.log('Success:', values);
+ // };
+
+ // const onFinishFailed = (errorInfo: any) => {
+ // console.log('Failed:', errorInfo);
+ // };
+
+ return (
+ <Content>
+ <Card className={styles.tabsCard} bordered={false}>
+ <Steps current={currentStep} className={styles.steps} items={[
+ {
+ title: '基本信息',
+ },
+ {
+ title: '字段信息',
+ },
+ {
+ title: '生成信息',
+ },
+ ]} />
+ {stepComponent}
+ </Card>
+ </Content>
+ );
+};
+
+export default TableList;
diff --git a/src/pages/Tool/Gen/import.tsx b/src/pages/Tool/Gen/import.tsx
new file mode 100644
index 0000000..6e2c8df
--- /dev/null
+++ b/src/pages/Tool/Gen/import.tsx
@@ -0,0 +1,107 @@
+import { Button, Card, message, Layout } from 'antd';
+import React, { useState } from 'react';
+import { history, FormattedMessage } from '@umijs/max';
+import { importTables, queryTableList } from './service';
+import type { GenCodeType } from './data.d';
+import { ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, RollbackOutlined } from '@ant-design/icons';
+
+const { Content } = Layout;
+
+const handleImport = async (tables: string) => {
+ const hide = message.loading('正在配置');
+ try {
+ await importTables(tables);
+ hide();
+ message.success('配置成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('配置失败请重试!');
+ return false;
+ }
+};
+
+const ImportTableList: React.FC = () => {
+ const [selectTables, setSelectTables] = useState<string[]>([]);
+
+ const columns: ProColumns<GenCodeType>[] = [
+ {
+ title: '表名称',
+ dataIndex: 'tableName',
+ },
+ {
+ title: '表描述',
+ dataIndex: 'tableComment',
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createTime',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ ];
+
+ return (
+ <Content>
+ <Card bordered={false}>
+ <ProTable<GenCodeType>
+ headerTitle="代码生成信息"
+ rowKey="tableName"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="primary"
+ onClick={async () => {
+ if (selectTables.length < 1) {
+ message.error('请选择要导入的表!');
+ return;
+ }
+ const success = await handleImport(selectTables.join(','));
+ if (success) {
+ history.back();
+ }
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="gen.submit" defaultMessage="提交" />
+ </Button>,
+ <Button
+ type="primary"
+ key="goback"
+ onClick={() => {
+ history.back();
+ }}
+ >
+ <RollbackOutlined /> <FormattedMessage id="gen.goback" defaultMessage="返回" />
+ </Button>,
+ ]}
+ request={(params) =>
+ queryTableList({ ...params }).then((res) => {
+ return {
+ data: res.rows,
+ total: res.total,
+ success: true,
+ };
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ if (selectedRows && selectedRows.length > 0) {
+ const tables = selectedRows.map((row) => {
+ return row.tableName;
+ });
+ setSelectTables(tables);
+ }
+ },
+ }}
+ />
+ </Card>
+ </Content>
+ );
+};
+
+export default ImportTableList;
diff --git a/src/pages/Tool/Gen/index.tsx b/src/pages/Tool/Gen/index.tsx
new file mode 100644
index 0000000..9db00d7
--- /dev/null
+++ b/src/pages/Tool/Gen/index.tsx
@@ -0,0 +1,360 @@
+import { DownloadOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, message, Drawer, Modal, Card, Layout } from 'antd';
+import type { FormInstance } from 'antd';
+import React, { useState, useRef } from 'react';
+import { history, FormattedMessage, useAccess } from '@umijs/max';
+import PreviewForm from './components/PreviewCode';
+import type { GenCodeTableListParams, GenCodeType } from './data.d';
+import {
+ batchGenCode,
+ genCode,
+ previewCode,
+ getGenCodeList,
+ removeData,
+ syncDbInfo,
+} from './service';
+import {
+ ActionType,
+ FooterToolbar,
+ ProColumns,
+ ProDescriptions,
+ ProDescriptionsItemProps,
+ ProTable,
+} from '@ant-design/pro-components';
+
+const { Content } = Layout;
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: GenCodeType[]) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRows) return true;
+ try {
+ await removeData({
+ ids: selectedRows.map((row) => row.tableId),
+ });
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const handleRemoveOne = async (selectedRow: GenCodeType) => {
+ const hide = message.loading('正在删除');
+ if (!selectedRow) return true;
+ try {
+ const params = [selectedRow.tableId];
+ await removeData({
+ ids: params,
+ });
+ hide();
+ message.success('删除成功,即将刷新');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('删除失败,请重试');
+ return false;
+ }
+};
+
+const GenCodeView: React.FC = () => {
+ const formTableRef = useRef<FormInstance>();
+
+ const [showDetail, setShowDetail] = useState<boolean>(false);
+ const [showPreview, setShowPreview] = useState<boolean>(false);
+ const [preivewData, setPreivewData] = useState<boolean>(false);
+
+ const actionRef = useRef<ActionType>();
+ const [currentRow, setCurrentRow] = useState<GenCodeType>();
+ const [selectedRows, setSelectedRows] = useState<GenCodeType[]>([]);
+
+ const access = useAccess();
+
+ const columns: ProColumns<GenCodeType>[] = [
+ {
+ title: '编号',
+ dataIndex: 'tableId',
+ tip: '编号',
+ render: (dom, entity) => {
+ return (
+ <a
+ onClick={() => {
+ setCurrentRow(entity);
+ setShowDetail(true);
+ }}
+ >
+ {dom}
+ </a>
+ );
+ },
+ },
+ {
+ title: '表名',
+ dataIndex: 'tableName',
+ valueType: 'textarea',
+ },
+ {
+ title: '表描述',
+ dataIndex: 'tableComment',
+ hideInForm: true,
+ hideInSearch: true,
+ },
+ {
+ title: '实体',
+ dataIndex: 'className',
+ valueType: 'textarea',
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createTime',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: '更新时间',
+ dataIndex: 'updateTime',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: '操作',
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ <Button
+ type="link"
+ size="small"
+ key="preview"
+ hidden={!access.hasPerms('tool:gen:edit')}
+ onClick={() => {
+ previewCode(record.tableId).then((res) => {
+ if (res.code === 200) {
+ setPreivewData(res.data);
+ setShowPreview(true);
+ } else {
+ message.error('获取数据失败');
+ }
+ });
+ }}
+ >
+ 预览
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ key="config"
+ hidden={!access.hasPerms('tool:gen:edit')}
+ onClick={() => {
+ history.push(`/tool/gen/edit?id=${record.tableId}`);
+ }}
+ >
+ 编辑
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ danger
+ key="delete"
+ hidden={!access.hasPerms('tool:gen:del')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除任务',
+ content: '确定删除该任务吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ key="sync"
+ hidden={!access.hasPerms('tool:gen:edit')}
+ onClick={() => {
+ syncDbInfo(record.tableName).then((res) => {
+ if (res.code === 200) {
+ message.success('同步成功');
+ } else {
+ message.error('同步失败');
+ }
+ });
+ }}
+ >
+ 同步
+ </Button>,
+ <Button
+ type="link"
+ size="small"
+ key="gencode"
+ hidden={!access.hasPerms('tool:gen:edit')}
+ onClick={() => {
+ if (record.genType === '1') {
+ genCode(record.tableName).then((res) => {
+ if (res.code === 200) {
+ message.success(`成功生成到自定义路径:${record.genPath}`);
+ } else {
+ message.error(res.msg);
+ }
+ });
+ } else {
+ batchGenCode(record.tableName);
+ }
+ }}
+ >
+ 生成代码
+ </Button>,
+ ],
+ },
+ ];
+
+ return (
+ <Content>
+ <Card bordered={false}>
+ <ProTable<GenCodeType>
+ headerTitle="代码生成信息"
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="tableId"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ <Button
+ type="primary"
+ key="gen"
+ hidden={!access.hasPerms('tool:gen:edit')}
+ onClick={() => {
+ if (selectedRows.length === 0) {
+ message.error('请选择要生成的数据');
+ return;
+ }
+ const tableNames = selectedRows.map((row) => row.tableName);
+ if (selectedRows[0].genType === '1') {
+ genCode(tableNames.join(',')).then((res) => {
+ if (res.code === 200) {
+ message.success(`成功生成到自定义路径:${selectedRows[0].genPath}`);
+ } else {
+ message.error(res.msg);
+ }
+ });
+ } else {
+ batchGenCode(tableNames.join(','));
+ }
+ }}
+ >
+ <DownloadOutlined /> <FormattedMessage id="gen.gencode" defaultMessage="生成" />
+ </Button>,
+ <Button
+ type="primary"
+ key="import"
+ hidden={!access.hasPerms('tool:gen:add')}
+ onClick={() => {
+ history.push('/tool/gen/import');
+ }}
+ >
+ <PlusOutlined /> <FormattedMessage id="gen.import" defaultMessage="导入" />
+ </Button>,
+ ]}
+ request={(params) =>
+ getGenCodeList({ ...params } as GenCodeTableListParams).then((res) => {
+ return {
+ data: res.rows,
+ total: res.rows.length,
+ success: true,
+ };
+ })
+ }
+ columns={columns}
+ rowSelection={{
+ onChange: (_, selectedRows) => {
+ setSelectedRows(selectedRows);
+ },
+ }}
+ />
+ {selectedRows?.length > 0 && (
+ <FooterToolbar
+ extra={
+ <div>
+ <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />{' '}
+ <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>{' '}
+ <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+ </div>
+ }
+ >
+ <Button
+ key="delete"
+ hidden={!access.hasPerms('tool:gen:remove')}
+ onClick={async () => {
+ Modal.confirm({
+ title: '删除任务',
+ content: '确定删除该任务吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+ <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+ </Button>
+ </FooterToolbar>
+ )}
+ <PreviewForm
+ open={showPreview}
+ data={preivewData}
+ onHide={() => {
+ setShowPreview(false);
+ }}
+ />
+ <Drawer
+ width={600}
+ open={showDetail}
+ onClose={() => {
+ setCurrentRow(undefined);
+ setShowDetail(false);
+ }}
+ closable={false}
+ >
+ {currentRow?.tableName && (
+ <ProDescriptions<GenCodeType>
+ column={2}
+ title={currentRow?.tableName}
+ request={async () => ({
+ data: currentRow || {},
+ })}
+ params={{
+ id: currentRow?.tableName,
+ }}
+ columns={columns as ProDescriptionsItemProps<GenCodeType>[]}
+ />
+ )}
+ </Drawer>
+ </Card>
+ </Content>
+ );
+};
+
+export default GenCodeView;
diff --git a/src/pages/Tool/Gen/locales/zh-CN.ts b/src/pages/Tool/Gen/locales/zh-CN.ts
new file mode 100644
index 0000000..9c3a5d4
--- /dev/null
+++ b/src/pages/Tool/Gen/locales/zh-CN.ts
@@ -0,0 +1,8 @@
+export default {
+ 'gen.import': '导入',
+ 'gen.title': '表信息',
+ 'gen.goback': '返回',
+ 'gen.submit': '提交',
+ 'gen.gencode': '生成',
+ 'gen.preview': '预览',
+};
diff --git a/src/pages/Tool/Gen/service.ts b/src/pages/Tool/Gen/service.ts
new file mode 100644
index 0000000..0e472ee
--- /dev/null
+++ b/src/pages/Tool/Gen/service.ts
@@ -0,0 +1,106 @@
+import { request } from '@umijs/max';
+import { downLoadZip } from '@/utils/downloadfile';
+import type { GenCodeType, GenCodeTableListParams } from './data.d';
+
+// 查询分页列表
+export async function getGenCodeList(params?: GenCodeTableListParams) {
+ const queryString = new URLSearchParams(params).toString();
+ return request(`/api/code/gen/list?${queryString}`, {
+ data: params,
+ method: 'get',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 查询表信息
+export async function getGenCode(id?: string) {
+ return request(`/api/code/gen/${id}`, {
+ method: 'get',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 查询数据表信息
+export async function queryTableList(params?: any) {
+ const queryString = new URLSearchParams(params).toString();
+ return request(`/api/code/gen/db/list?${queryString}`, {
+ data: params,
+ method: 'get',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 导入数据表信息
+export async function importTables(tables?: string) {
+ return request(`/api/code/gen/importTable?tables=${tables}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 删除
+export async function removeData(params: { ids: string[] }) {
+ return request(`/api/code/gen/${params.ids}`, {
+ method: 'delete',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 添加数据
+export async function addData(params: GenCodeType) {
+ return request('/api/code/gen', {
+ method: 'POST',
+ data: {
+ ...params,
+ },
+ });
+}
+
+// 更新数据
+export async function updateData(params: GenCodeType) {
+ return request('/api/code/gen', {
+ method: 'PUT',
+ data: {
+ ...params,
+ },
+ });
+}
+
+// 更新状态
+export async function syncDbInfo(tableName: string) {
+ return request(`/api/code/gen/synchDb/${tableName}`, {
+ method: 'GET',
+ });
+}
+
+// 生成代码(自定义路径)
+export async function genCode(tableName: string) {
+ return request(`/api/code/gen/genCode/${tableName}`, {
+ method: 'GET',
+ });
+}
+
+// 生成代码(压缩包)
+export async function batchGenCode(tableName: string) {
+ return downLoadZip(`/api/code/gen/batchGenCode?tables=${tableName}`);
+}
+
+// 预览
+export async function previewCode(id: string) {
+ return request(`/api/code/gen/preview/${id}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
diff --git a/src/pages/Tool/Gen/style.less b/src/pages/Tool/Gen/style.less
new file mode 100644
index 0000000..6999d7a
--- /dev/null
+++ b/src/pages/Tool/Gen/style.less
@@ -0,0 +1,4 @@
+.steps:global(.ant-steps) {
+ max-width: 750px;
+ margin: 16px auto;
+}
diff --git a/src/pages/Tool/Swagger/index.tsx b/src/pages/Tool/Swagger/index.tsx
new file mode 100644
index 0000000..adcc968
--- /dev/null
+++ b/src/pages/Tool/Swagger/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+const CacheInfo: React.FC = () => {
+ useEffect(() => {
+ const frame = document.getElementById('bdIframe');
+ if (frame) {
+ const deviceWidth = document.documentElement.clientWidth;
+ const deviceHeight = document.documentElement.clientHeight;
+ frame.style.width = `${Number(deviceWidth) - 260}px`;
+ frame.style.height = `${Number(deviceHeight) - 120}px`;
+ }
+ });
+
+ return (
+ <div style={{}}>
+ <iframe
+ style={{ width: '100%', border: '0px', height: '100%' }}
+ src={`/api/swagger-ui/index.html`}
+ id="bdIframe"
+ />
+ </div>
+ );
+};
+
+export default CacheInfo;
diff --git a/src/pages/User/Center/Center.less b/src/pages/User/Center/Center.less
new file mode 100644
index 0000000..430d88e
--- /dev/null
+++ b/src/pages/User/Center/Center.less
@@ -0,0 +1,61 @@
+
+.avatarHolder {
+ margin-bottom: 16px;
+ text-align: center;
+ position: relative;
+ display: inline-block;
+ height: 120px;
+
+ & > img {
+ width: 120px;
+ height: 120px;
+ margin-bottom: 20px;
+ border-radius: 50%;
+ }
+ &:hover:after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ color: #eee;
+ font-size: 24px;
+ font-style: normal;
+ line-height: 110px;
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: 50%;
+ cursor: pointer;
+ content: '+';
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+}
+
+.teamTitle {
+ margin-bottom: 12px;
+ color: @heading-color;
+ font-weight: 500;
+}
+
+.team {
+ :global {
+ .ant-avatar {
+ margin-right: 12px;
+ }
+ }
+
+ a {
+ display: block;
+ margin-bottom: 24px;
+ overflow: hidden;
+ color: @text-color;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ transition: color 0.3s;
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
diff --git a/src/pages/User/Center/components/AvatarCropper/cropper.css b/src/pages/User/Center/components/AvatarCropper/cropper.css
new file mode 100644
index 0000000..7f2f350
--- /dev/null
+++ b/src/pages/User/Center/components/AvatarCropper/cropper.css
@@ -0,0 +1,309 @@
+/*!
+ * Cropper.js v1.5.13
+ * https://fengyuanchen.github.io/cropperjs
+ *
+ * Copyright 2015-present Chen Fengyuan
+ * Released under the MIT license
+ *
+ * Date: 2022-11-20T05:30:43.444Z
+ */
+
+.cropper-container {
+ direction: ltr;
+ font-size: 0;
+ line-height: 0;
+ position: relative;
+ -ms-touch-action: none;
+ touch-action: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.cropper-container img {
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ display: block;
+ height: 100%;
+ image-orientation: 0deg;
+ max-height: none !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ min-width: 0 !important;
+ width: 100%;
+ }
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+}
+
+.cropper-wrap-box,
+.cropper-canvas {
+ overflow: hidden;
+}
+
+.cropper-drag-box {
+ background-color: #fff;
+ opacity: 0;
+}
+
+.cropper-modal {
+ background-color: #000;
+ opacity: 0.5;
+}
+
+.cropper-view-box {
+ display: block;
+ height: 100%;
+ outline: 1px solid #39f;
+ outline-color: rgba(51, 153, 255, 75%);
+ overflow: hidden;
+ width: 100%;
+}
+
+.cropper-dashed {
+ border: 0 dashed #eee;
+ display: block;
+ opacity: 0.5;
+ position: absolute;
+}
+
+.cropper-dashed.dashed-h {
+ border-bottom-width: 1px;
+ border-top-width: 1px;
+ height: calc(100% / 3);
+ left: 0;
+ top: calc(100% / 3);
+ width: 100%;
+ }
+
+.cropper-dashed.dashed-v {
+ border-left-width: 1px;
+ border-right-width: 1px;
+ height: 100%;
+ left: calc(100% / 3);
+ top: 0;
+ width: calc(100% / 3);
+ }
+
+.cropper-center {
+ display: block;
+ height: 0;
+ left: 50%;
+ opacity: 0.75;
+ position: absolute;
+ top: 50%;
+ width: 0;
+}
+
+.cropper-center::before,
+ .cropper-center::after {
+ background-color: #eee;
+ content: " ";
+ display: block;
+ position: absolute;
+ }
+
+.cropper-center::before {
+ height: 1px;
+ left: -3px;
+ top: 0;
+ width: 7px;
+ }
+
+.cropper-center::after {
+ height: 7px;
+ left: 0;
+ top: -3px;
+ width: 1px;
+ }
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+ display: block;
+ height: 100%;
+ opacity: 0.1;
+ position: absolute;
+ width: 100%;
+}
+
+.cropper-face {
+ background-color: #fff;
+ left: 0;
+ top: 0;
+}
+
+.cropper-line {
+ background-color: #39f;
+}
+
+.cropper-line.line-e {
+ cursor: ew-resize;
+ right: -3px;
+ top: 0;
+ width: 5px;
+ }
+
+.cropper-line.line-n {
+ cursor: ns-resize;
+ height: 5px;
+ left: 0;
+ top: -3px;
+ }
+
+.cropper-line.line-w {
+ cursor: ew-resize;
+ left: -3px;
+ top: 0;
+ width: 5px;
+ }
+
+.cropper-line.line-s {
+ bottom: -3px;
+ cursor: ns-resize;
+ height: 5px;
+ left: 0;
+ }
+
+.cropper-point {
+ background-color: #39f;
+ height: 5px;
+ opacity: 0.75;
+ width: 5px;
+}
+
+.cropper-point.point-e {
+ cursor: ew-resize;
+ margin-top: -3px;
+ right: -3px;
+ top: 50%;
+ }
+
+.cropper-point.point-n {
+ cursor: ns-resize;
+ left: 50%;
+ margin-left: -3px;
+ top: -3px;
+ }
+
+.cropper-point.point-w {
+ cursor: ew-resize;
+ left: -3px;
+ margin-top: -3px;
+ top: 50%;
+ }
+
+.cropper-point.point-s {
+ bottom: -3px;
+ cursor: s-resize;
+ left: 50%;
+ margin-left: -3px;
+ }
+
+.cropper-point.point-ne {
+ cursor: nesw-resize;
+ right: -3px;
+ top: -3px;
+ }
+
+.cropper-point.point-nw {
+ cursor: nwse-resize;
+ left: -3px;
+ top: -3px;
+ }
+
+.cropper-point.point-sw {
+ bottom: -3px;
+ cursor: nesw-resize;
+ left: -3px;
+ }
+
+.cropper-point.point-se {
+ bottom: -3px;
+ cursor: nwse-resize;
+ height: 20px;
+ opacity: 1;
+ right: -3px;
+ width: 20px;
+ }
+
+@media (min-width: 768px) {
+
+.cropper-point.point-se {
+ height: 15px;
+ width: 15px;
+ }
+ }
+
+@media (min-width: 992px) {
+
+.cropper-point.point-se {
+ height: 10px;
+ width: 10px;
+ }
+ }
+
+@media (min-width: 1200px) {
+
+.cropper-point.point-se {
+ height: 5px;
+ opacity: 0.75;
+ width: 5px;
+ }
+ }
+
+.cropper-point.point-se::before {
+ background-color: #39f;
+ bottom: -50%;
+ content: " ";
+ display: block;
+ height: 200%;
+ opacity: 0;
+ position: absolute;
+ right: -50%;
+ width: 200%;
+ }
+
+.cropper-invisible {
+ opacity: 0;
+}
+
+.cropper-bg {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
+}
+
+.cropper-hide {
+ display: block;
+ height: 0;
+ position: absolute;
+ width: 0;
+}
+
+.cropper-hidden {
+ display: none !important;
+}
+
+.cropper-move {
+ cursor: move;
+}
+
+.cropper-crop {
+ cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+ cursor: not-allowed;
+}
diff --git a/src/pages/User/Center/components/AvatarCropper/images/bg.png b/src/pages/User/Center/components/AvatarCropper/images/bg.png
new file mode 100644
index 0000000..3c7056b
--- /dev/null
+++ b/src/pages/User/Center/components/AvatarCropper/images/bg.png
Binary files differ
diff --git a/src/pages/User/Center/components/AvatarCropper/index.less b/src/pages/User/Center/components/AvatarCropper/index.less
new file mode 100644
index 0000000..dc3fecf
--- /dev/null
+++ b/src/pages/User/Center/components/AvatarCropper/index.less
@@ -0,0 +1,10 @@
+.avatarPreview {
+ position: absolute;
+ top: 50%;
+ transform: translate(50%, -50%);
+ width: 200px;
+ height: 200px;
+ border-radius: 50%;
+ box-shadow: 0 0 4px #ccc;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/pages/User/Center/components/AvatarCropper/index.tsx b/src/pages/User/Center/components/AvatarCropper/index.tsx
new file mode 100644
index 0000000..83d0bcf
--- /dev/null
+++ b/src/pages/User/Center/components/AvatarCropper/index.tsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal, Row, Col, Button, Space, Upload, message } from 'antd';
+import { useIntl } from '@umijs/max';
+import { uploadAvatar } from '@/services/system/user';
+import { Cropper } from 'react-cropper';
+import './cropper.css';
+import styles from './index.less';
+import {
+ MinusOutlined,
+ PlusOutlined,
+ RedoOutlined,
+ UndoOutlined,
+ UploadOutlined,
+} from '@ant-design/icons';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2022/02/24
+ *
+ * */
+
+export type AvatarCropperProps = {
+ onFinished: (isSuccess: boolean) => void;
+ open: boolean;
+ data: any;
+};
+
+const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
+ const cropperRef = useRef<HTMLImageElement>(null);
+ const [avatarData, setAvatarData] = useState<any>();
+ const [previewData, setPreviewData] = useState();
+
+ useEffect(() => {
+ setAvatarData(props.data);
+ }, [props]);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ cropper.getCroppedCanvas().toBlob((blob: Blob) => {
+ const formData = new FormData();
+ formData.append('avatarfile', blob);
+ uploadAvatar(formData).then((res) => {
+ if (res.code === 200) {
+ message.success(res.msg);
+ props.onFinished(true);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }, 'image/png');
+ };
+ const handleCancel = () => {
+ props.onFinished(false);
+ };
+ const onCrop = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ setPreviewData(cropper.getCroppedCanvas().toDataURL());
+ };
+ const onRotateRight = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ cropper.rotate(90);
+ };
+ const onRotateLeft = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ cropper.rotate(-90);
+ };
+ const onZoomIn = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ cropper.zoom(0.1);
+ };
+ const onZoomOut = () => {
+ const imageElement: any = cropperRef?.current;
+ const cropper: any = imageElement?.cropper;
+ cropper.zoom(-0.1);
+ };
+ const beforeUpload = (file: any) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => {
+ setAvatarData(reader.result);
+ };
+ };
+ return (
+ <Modal
+ width={800}
+ title={intl.formatMessage({
+ id: 'system.user.modify_avatar',
+ defaultMessage: '修改头像',
+ })}
+ open={props.open}
+ destroyOnClose
+ onOk={handleOk}
+ onCancel={handleCancel}
+ >
+ <Row gutter={[16, 16]}>
+ <Col span={12} order={1}>
+ <Cropper
+ ref={cropperRef}
+ src={avatarData}
+ style={{ height: 350, width: '100%', marginBottom: '16px' }}
+ initialAspectRatio={1}
+ guides={false}
+ crop={onCrop}
+ zoomable={true}
+ zoomOnWheel={true}
+ rotatable={true}
+ />
+ </Col>
+ <Col span={12} order={2}>
+ <div className={styles.avatarPreview}>
+ <img src={previewData} style={{ height: '100%', width: '100%' }} />
+ </div>
+ </Col>
+ </Row>
+ <Row gutter={[16, 16]}>
+ <Col span={6}>
+ <Upload beforeUpload={beforeUpload} maxCount={1}>
+ <Button>
+ <UploadOutlined />
+ 上传
+ </Button>
+ </Upload>
+ </Col>
+ <Col>
+ <Space>
+ <Button icon={<RedoOutlined />} onClick={onRotateRight} />
+ <Button icon={<UndoOutlined />} onClick={onRotateLeft} />
+ <Button icon={<PlusOutlined />} onClick={onZoomIn} />
+ <Button icon={<MinusOutlined />} onClick={onZoomOut} />
+ </Space>
+ </Col>
+ </Row>
+ </Modal>
+ );
+};
+
+export default AvatarCropperForm;
diff --git a/src/pages/User/Center/components/BaseInfo/index.tsx b/src/pages/User/Center/components/BaseInfo/index.tsx
new file mode 100644
index 0000000..5cdca5f
--- /dev/null
+++ b/src/pages/User/Center/components/BaseInfo/index.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import { Form, message, Row } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
+import { updateUserProfile } from '@/services/system/user';
+
+
+export type BaseInfoProps = {
+ values: Partial<API.CurrentUser> | undefined;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+ const [form] = Form.useForm();
+ const intl = useIntl();
+
+ const handleFinish = async (values: Record<string, any>) => {
+ const data = { ...props.values, ...values } as API.CurrentUser;
+ const resp = await updateUserProfile(data);
+ if (resp.code === 200) {
+ message.success('修改成功');
+ } else {
+ message.warning(resp.msg);
+ }
+ };
+
+ return (
+ <>
+ <ProForm form={form} onFinish={handleFinish} initialValues={props.values}>
+ <Row>
+ <ProFormText
+ name="nickName"
+ label={intl.formatMessage({
+ id: 'system.user.nick_name',
+ defaultMessage: '用户昵称',
+ })}
+ width="xl"
+ placeholder="请输入用户昵称"
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+ ),
+ },
+ ]}
+ />
+ </Row>
+ <Row>
+ <ProFormText
+ name="phonenumber"
+ label={intl.formatMessage({
+ id: 'system.user.phonenumber',
+ defaultMessage: '手机号码',
+ })}
+ width="xl"
+ placeholder="请输入手机号码"
+ rules={[
+ {
+ required: false,
+ message: (
+ <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+ ),
+ },
+ ]}
+ />
+ </Row>
+ <Row>
+ <ProFormText
+ name="email"
+ label={intl.formatMessage({
+ id: 'system.user.email',
+ defaultMessage: '邮箱',
+ })}
+ width="xl"
+ placeholder="请输入邮箱"
+ rules={[
+ {
+ type: 'email',
+ message: '无效的邮箱地址!',
+ },
+ {
+ required: false,
+ message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+ },
+ ]}
+ />
+ </Row>
+ <Row>
+ <ProFormRadio.Group
+ options={[
+ {
+ label: '男',
+ value: '0',
+ },
+ {
+ label: '女',
+ value: '1',
+ },
+ ]}
+ name="sex"
+ label={intl.formatMessage({
+ id: 'system.user.sex',
+ defaultMessage: 'sex',
+ })}
+ width="xl"
+ rules={[
+ {
+ required: false,
+ message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />,
+ },
+ ]}
+ />
+ </Row>
+ </ProForm>
+ </>
+ );
+};
+
+export default BaseInfo;
diff --git a/src/pages/User/Center/components/ResetPassword/index.tsx b/src/pages/User/Center/components/ResetPassword/index.tsx
new file mode 100644
index 0000000..26825d4
--- /dev/null
+++ b/src/pages/User/Center/components/ResetPassword/index.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { Form, message } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { updateUserPwd } from '@/services/system/user';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+const ResetPassword: React.FC = () => {
+ const [form] = Form.useForm();
+ const intl = useIntl();
+
+ const handleFinish = async (values: Record<string, any>) => {
+ const resp = await updateUserPwd(values.oldPassword, values.newPassword);
+ if (resp.code === 200) {
+ message.success('密码重置成功。');
+ } else {
+ message.warning(resp.msg);
+ }
+ };
+
+ const checkPassword = (rule: any, value: string) => {
+ const login_password = form.getFieldValue('newPassword');
+ if (value === login_password) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次密码输入不一致'));
+ };
+
+ return (
+ <>
+ <ProForm form={form} onFinish={handleFinish}>
+ <ProFormText.Password
+ name="oldPassword"
+ label={intl.formatMessage({
+ id: 'system.user.old_password',
+ defaultMessage: '旧密码',
+ })}
+ width="xl"
+ placeholder="请输入旧密码"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />,
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="newPassword"
+ label={intl.formatMessage({
+ id: 'system.user.new_password',
+ defaultMessage: '新密码',
+ })}
+ width="xl"
+ placeholder="请输入新密码"
+ rules={[
+ {
+ required: true,
+ message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />,
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="confirmPassword"
+ label={intl.formatMessage({
+ id: 'system.user.confirm_password',
+ defaultMessage: '确认密码',
+ })}
+ width="xl"
+ placeholder="请输入确认密码"
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />
+ ),
+ },
+ { validator: checkPassword },
+ ]}
+ />
+ </ProForm>
+ </>
+ );
+};
+
+export default ResetPassword;
diff --git a/src/pages/User/Center/index.tsx b/src/pages/User/Center/index.tsx
new file mode 100644
index 0000000..2ce308c
--- /dev/null
+++ b/src/pages/User/Center/index.tsx
@@ -0,0 +1,200 @@
+import {
+ ClusterOutlined,
+ MailOutlined,
+ TeamOutlined,
+ UserOutlined,
+ MobileOutlined,
+ ManOutlined,
+} from '@ant-design/icons';
+import { Card, Col, Divider, List, Row } from 'antd';
+import React, { useState } from 'react';
+import styles from './Center.less';
+import BaseInfo from './components/BaseInfo';
+import ResetPassword from './components/ResetPassword';
+import AvatarCropper from './components/AvatarCropper';
+import { useRequest } from '@umijs/max';
+import { getUserInfo } from '@/services/session';
+import { PageLoading } from '@ant-design/pro-components';
+
+const operationTabList = [
+ {
+ key: 'base',
+ tab: (
+ <span>
+ 基本资料
+ </span>
+ ),
+ },
+ {
+ key: 'password',
+ tab: (
+ <span>
+ 重置密码
+ </span>
+ ),
+ },
+];
+
+export type tabKeyType = 'base' | 'password';
+
+const Center: React.FC = () => {
+
+ const [tabKey, setTabKey] = useState<tabKeyType>('base');
+
+ const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
+
+ // 获取用户信息
+ const { data: userInfo, loading } = useRequest(async () => {
+ return { data: await getUserInfo()};
+ });
+ if (loading) {
+ return <div>loading...</div>;
+ }
+
+ const currentUser = userInfo?.user;
+
+ // 渲染用户信息
+ const renderUserInfo = ({
+ userName,
+ phonenumber,
+ email,
+ sex,
+ dept,
+ }: Partial<API.CurrentUser>) => {
+ return (
+ <List>
+ <List.Item>
+ <div>
+ <UserOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ 用户名
+ </div>
+ <div>{userName}</div>
+ </List.Item>
+ <List.Item>
+ <div>
+ <ManOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ 性别
+ </div>
+ <div>{sex === '1' ? '女' : '男'}</div>
+ </List.Item>
+ <List.Item>
+ <div>
+ <MobileOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ 电话
+ </div>
+ <div>{phonenumber}</div>
+ </List.Item>
+ <List.Item>
+ <div>
+ <MailOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ 邮箱
+ </div>
+ <div>{email}</div>
+ </List.Item>
+ <List.Item>
+ <div>
+ <ClusterOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ 部门
+ </div>
+ <div>{dept?.deptName}</div>
+ </List.Item>
+ </List>
+ );
+ };
+
+ // 渲染tab切换
+ const renderChildrenByTabKey = (tabValue: tabKeyType) => {
+ if (tabValue === 'base') {
+ return <BaseInfo values={currentUser} />;
+ }
+ if (tabValue === 'password') {
+ return <ResetPassword />;
+ }
+ return null;
+ };
+
+ if (!currentUser) {
+ return <PageLoading />;
+ }
+
+ return (
+ <div>
+ <Row gutter={[16, 24]}>
+ <Col lg={8} md={24}>
+ <Card
+ title="个人信息"
+ bordered={false}
+ loading={loading}
+ >
+ {!loading && (
+ <div style={{ textAlign: "center"}}>
+ <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
+ <img alt="" src={currentUser.avatar} />
+ </div>
+ {renderUserInfo(currentUser)}
+ <Divider dashed />
+ <div className={styles.team}>
+ <div className={styles.teamTitle}>角色</div>
+ <Row gutter={36}>
+ {currentUser.roles &&
+ currentUser.roles.map((item: any) => (
+ <Col key={item.roleId} lg={24} xl={12}>
+ <TeamOutlined
+ style={{
+ marginRight: 8,
+ }}
+ />
+ {item.roleName}
+ </Col>
+ ))}
+ </Row>
+ </div>
+ </div>
+ )}
+ </Card>
+ </Col>
+ <Col lg={16} md={24}>
+ <Card
+ bordered={false}
+ tabList={operationTabList}
+ activeTabKey={tabKey}
+ onTabChange={(_tabKey: string) => {
+ setTabKey(_tabKey as tabKeyType);
+ }}
+ >
+ {renderChildrenByTabKey(tabKey)}
+ </Card>
+ </Col>
+ </Row>
+ <AvatarCropper
+ onFinished={() => {
+ setCropperModalOpen(false);
+ }}
+ open={cropperModalOpen}
+ data={currentUser.avatar}
+ />
+ </div>
+ );
+};
+
+export default Center;
diff --git a/src/pages/User/Login/index.tsx b/src/pages/User/Login/index.tsx
new file mode 100644
index 0000000..a18f785
--- /dev/null
+++ b/src/pages/User/Login/index.tsx
@@ -0,0 +1,436 @@
+import Footer from '@/components/Footer';
+import { getCaptchaImg, login } from '@/services/system/auth';
+import { getFakeCaptcha } from '@/services/ant-design-pro/login';
+import {
+ AlipayCircleOutlined,
+ LockOutlined,
+ MobileOutlined,
+ TaobaoCircleOutlined,
+ UserOutlined,
+ WeiboCircleOutlined,
+} from '@ant-design/icons';
+import {
+ LoginForm,
+ ProFormCaptcha,
+ ProFormCheckbox,
+ ProFormText,
+} from '@ant-design/pro-components';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
+import { Alert, Col, message, Row, Tabs, Image } from 'antd';
+import Settings from '../../../../config/defaultSettings';
+import React, { useEffect, useState } from 'react';
+import { flushSync } from 'react-dom';
+import { clearSessionToken, setSessionToken } from '@/access';
+
+const ActionIcons = () => {
+ const langClassName = useEmotionCss(({ token }) => {
+ return {
+ marginLeft: '8px',
+ color: 'rgba(0, 0, 0, 0.2)',
+ fontSize: '24px',
+ verticalAlign: 'middle',
+ cursor: 'pointer',
+ transition: 'color 0.3s',
+ '&:hover': {
+ color: token.colorPrimaryActive,
+ },
+ };
+ });
+
+ return (
+ <>
+ <AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
+ <TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
+ <WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
+ </>
+ );
+};
+
+const Lang = () => {
+ const langClassName = useEmotionCss(({ token }) => {
+ return {
+ width: 42,
+ height: 42,
+ lineHeight: '42px',
+ position: 'fixed',
+ right: 16,
+ borderRadius: token.borderRadius,
+ ':hover': {
+ backgroundColor: token.colorBgTextHover,
+ },
+ };
+ });
+
+ return (
+ <div className={langClassName} data-lang>
+ {SelectLang && <SelectLang />}
+ </div>
+ );
+};
+
+const LoginMessage: React.FC<{
+ content: string;
+}> = ({ content }) => {
+ return (
+ <Alert
+ style={{
+ marginBottom: 24,
+ }}
+ message={content}
+ type="error"
+ showIcon
+ />
+ );
+};
+
+const Login: React.FC = () => {
+ const [userLoginState, setUserLoginState] = useState<API.LoginResult>({code: 200});
+ const [type, setType] = useState<string>('account');
+ const { initialState, setInitialState } = useModel('@@initialState');
+ const [captchaCode, setCaptchaCode] = useState<string>('');
+ const [uuid, setUuid] = useState<string>('');
+
+ const containerClassName = useEmotionCss(() => {
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100vh',
+ overflow: 'auto',
+ backgroundImage:
+ "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
+ backgroundSize: '100% 100%',
+ };
+ });
+
+ const intl = useIntl();
+
+ const getCaptchaCode = async () => {
+ const response = await getCaptchaImg();
+ const imgdata = `data:image/png;base64,${response.img}`;
+ setCaptchaCode(imgdata);
+ setUuid(response.uuid);
+ };
+
+ const fetchUserInfo = async () => {
+ const userInfo = await initialState?.fetchUserInfo?.();
+ if (userInfo) {
+ flushSync(() => {
+ setInitialState((s) => ({
+ ...s,
+ currentUser: userInfo,
+ }));
+ });
+ }
+ };
+
+ const handleSubmit = async (values: API.LoginParams) => {
+ try {
+ // 登录
+ const response = await login({ ...values, uuid });
+ if (response.code === 200) {
+ const defaultLoginSuccessMessage = intl.formatMessage({
+ id: 'pages.login.success',
+ defaultMessage: '登录成功!',
+ });
+ const current = new Date();
+ const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
+ console.log('login response: ', response);
+ setSessionToken(response?.token, response?.token, expireTime);
+ message.success(defaultLoginSuccessMessage);
+ await fetchUserInfo();
+ console.log('login ok');
+ const urlParams = new URL(window.location.href).searchParams;
+ history.push(urlParams.get('redirect') || '/');
+ return;
+ } else {
+ console.log(response.msg);
+ clearSessionToken();
+ // 如果失败去设置用户错误信息
+ setUserLoginState({ ...response, type });
+ getCaptchaCode();
+ }
+ } catch (error) {
+ const defaultLoginFailureMessage = intl.formatMessage({
+ id: 'pages.login.failure',
+ defaultMessage: '登录失败,请重试!',
+ });
+ console.log(error);
+ message.error(defaultLoginFailureMessage);
+ }
+ };
+ const { code } = userLoginState;
+ const loginType = type;
+
+ useEffect(() => {
+ getCaptchaCode();
+ }, []);
+
+ return (
+ <div className={containerClassName}>
+ <Helmet>
+ <title>
+ {intl.formatMessage({
+ id: 'menu.login',
+ defaultMessage: '登录页',
+ })}
+ - {Settings.title}
+ </title>
+ </Helmet>
+ <Lang />
+ <div
+ style={{
+ flex: '1',
+ padding: '32px 0',
+ }}
+ >
+ <LoginForm
+ contentStyle={{
+ minWidth: 280,
+ maxWidth: '75vw',
+ }}
+ logo={<img alt="logo" src="/logo.svg" />}
+ title="Ant Design"
+ subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
+ initialValues={{
+ autoLogin: true,
+ }}
+ actions={[
+ <FormattedMessage
+ key="loginWith"
+ id="pages.login.loginWith"
+ defaultMessage="其他登录方式"
+ />,
+ <ActionIcons key="icons" />,
+ ]}
+ onFinish={async (values) => {
+ await handleSubmit(values as API.LoginParams);
+ }}
+ >
+ <Tabs
+ activeKey={type}
+ onChange={setType}
+ centered
+ items={[
+ {
+ key: 'account',
+ label: intl.formatMessage({
+ id: 'pages.login.accountLogin.tab',
+ defaultMessage: '账户密码登录',
+ }),
+ },
+ {
+ key: 'mobile',
+ label: intl.formatMessage({
+ id: 'pages.login.phoneLogin.tab',
+ defaultMessage: '手机号登录',
+ }),
+ },
+ ]}
+ />
+
+ {code !== 200 && loginType === 'account' && (
+ <LoginMessage
+ content={intl.formatMessage({
+ id: 'pages.login.accountLogin.errorMessage',
+ defaultMessage: '账户或密码错误(admin/admin123)',
+ })}
+ />
+ )}
+ {type === 'account' && (
+ <>
+ <ProFormText
+ name="username"
+ initialValue="admin"
+ fieldProps={{
+ size: 'large',
+ prefix: <UserOutlined />,
+ }}
+ placeholder={intl.formatMessage({
+ id: 'pages.login.username.placeholder',
+ defaultMessage: '用户名: admin',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.username.required"
+ defaultMessage="请输入用户名!"
+ />
+ ),
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="password"
+ initialValue="admin123"
+ fieldProps={{
+ size: 'large',
+ prefix: <LockOutlined />,
+ }}
+ placeholder={intl.formatMessage({
+ id: 'pages.login.password.placeholder',
+ defaultMessage: '密码: admin123',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.password.required"
+ defaultMessage="请输入密码!"
+ />
+ ),
+ },
+ ]}
+ />
+ <Row>
+ <Col flex={3}>
+ <ProFormText
+ style={{
+ float: 'right',
+ }}
+ name="code"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.captcha.placeholder',
+ defaultMessage: '请输入验证',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.searchTable.updateForm.ruleName.nameRules"
+ defaultMessage="请输入验证啊"
+ />
+ ),
+ },
+ ]}
+ />
+ </Col>
+ <Col flex={2}>
+ <Image
+ src={captchaCode}
+ alt="验证码"
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ cursor: 'pointer',
+ paddingLeft: '10px',
+ width: '100px',
+ }}
+ preview={false}
+ onClick={() => getCaptchaCode()}
+ />
+ </Col>
+ </Row>
+ </>
+ )}
+
+ {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
+ {type === 'mobile' && (
+ <>
+ <ProFormText
+ fieldProps={{
+ size: 'large',
+ prefix: <MobileOutlined />,
+ }}
+ name="mobile"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.phoneNumber.placeholder',
+ defaultMessage: '手机号',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.phoneNumber.required"
+ defaultMessage="请输入手机号!"
+ />
+ ),
+ },
+ {
+ pattern: /^1\d{10}$/,
+ message: (
+ <FormattedMessage
+ id="pages.login.phoneNumber.invalid"
+ defaultMessage="手机号格式错误!"
+ />
+ ),
+ },
+ ]}
+ />
+ <ProFormCaptcha
+ fieldProps={{
+ size: 'large',
+ prefix: <LockOutlined />,
+ }}
+ captchaProps={{
+ size: 'large',
+ }}
+ placeholder={intl.formatMessage({
+ id: 'pages.login.captcha.placeholder',
+ defaultMessage: '请输入验证码',
+ })}
+ captchaTextRender={(timing, count) => {
+ if (timing) {
+ return `${count} ${intl.formatMessage({
+ id: 'pages.getCaptchaSecondText',
+ defaultMessage: '获取验证码',
+ })}`;
+ }
+ return intl.formatMessage({
+ id: 'pages.login.phoneLogin.getVerificationCode',
+ defaultMessage: '获取验证码',
+ });
+ }}
+ name="captcha"
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.captcha.required"
+ defaultMessage="请输入验证码!"
+ />
+ ),
+ },
+ ]}
+ onGetCaptcha={async (phone) => {
+ const result = await getFakeCaptcha({
+ phone,
+ });
+ if (!result) {
+ return;
+ }
+ message.success('获取验证码成功!验证码为:1234');
+ }}
+ />
+ </>
+ )}
+ <div
+ style={{
+ marginBottom: 24,
+ }}
+ >
+ <ProFormCheckbox noStyle name="autoLogin">
+ <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+ </ProFormCheckbox>
+ <a
+ style={{
+ float: 'right',
+ }}
+ >
+ <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
+ </a>
+ </div>
+ </LoginForm>
+ </div>
+ <Footer />
+ </div>
+ );
+};
+
+export default Login;
diff --git a/src/pages/User/Settings/index.tsx b/src/pages/User/Settings/index.tsx
new file mode 100644
index 0000000..f29d0b9
--- /dev/null
+++ b/src/pages/User/Settings/index.tsx
@@ -0,0 +1,20 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { Card } from 'antd';
+import React from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+
+const Settings: React.FC = () => {
+ return (
+ <PageContainer>
+ <Card title="Developing" />
+ </PageContainer>
+ );
+};
+
+export default Settings;
diff --git a/src/pages/Welcome.tsx b/src/pages/Welcome.tsx
new file mode 100644
index 0000000..d0c49f7
--- /dev/null
+++ b/src/pages/Welcome.tsx
@@ -0,0 +1,164 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Card, theme } from 'antd';
+import React from 'react';
+
+/**
+ * 每个单独的卡片,为了复用样式抽成了组件
+ * @param param0
+ * @returns
+ */
+const InfoCard: React.FC<{
+ title: string;
+ index: number;
+ desc: string;
+ href: string;
+}> = ({ title, href, index, desc }) => {
+ const { useToken } = theme;
+
+ const { token } = useToken();
+
+ return (
+ <div
+ style={{
+ backgroundColor: token.colorBgContainer,
+ boxShadow: token.boxShadow,
+ borderRadius: '8px',
+ fontSize: '14px',
+ color: token.colorTextSecondary,
+ lineHeight: '22px',
+ padding: '16px 19px',
+ minWidth: '220px',
+ flex: 1,
+ }}
+ >
+ <div
+ style={{
+ display: 'flex',
+ gap: '4px',
+ alignItems: 'center',
+ }}
+ >
+ <div
+ style={{
+ width: 48,
+ height: 48,
+ lineHeight: '22px',
+ backgroundSize: '100%',
+ textAlign: 'center',
+ padding: '8px 16px 16px 12px',
+ color: '#FFF',
+ fontWeight: 'bold',
+ backgroundImage:
+ "url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
+ }}
+ >
+ {index}
+ </div>
+ <div
+ style={{
+ fontSize: '16px',
+ color: token.colorText,
+ paddingBottom: 8,
+ }}
+ >
+ {title}
+ </div>
+ </div>
+ <div
+ style={{
+ fontSize: '14px',
+ color: token.colorTextSecondary,
+ textAlign: 'justify',
+ lineHeight: '22px',
+ marginBottom: 8,
+ }}
+ >
+ {desc}
+ </div>
+ <a href={href} target="_blank" rel="noreferrer">
+ 了解更多 {'>'}
+ </a>
+ </div>
+ );
+};
+
+const Welcome: React.FC = () => {
+ const { token } = theme.useToken();
+ const { initialState } = useModel('@@initialState');
+ return (
+ <PageContainer>
+ <Card
+ style={{
+ borderRadius: 8,
+ }}
+ bodyStyle={{
+ backgroundImage:
+ initialState?.settings?.navTheme === 'realDark'
+ ? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
+ : 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
+ }}
+ >
+ <div
+ style={{
+ backgroundPosition: '100% -30%',
+ backgroundRepeat: 'no-repeat',
+ backgroundSize: '274px auto',
+ backgroundImage:
+ "url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
+ }}
+ >
+ <div
+ style={{
+ fontSize: '20px',
+ color: token.colorTextHeading,
+ }}
+ >
+ 欢迎使用 Ant Design Pro
+ </div>
+ <p
+ style={{
+ fontSize: '14px',
+ color: token.colorTextSecondary,
+ lineHeight: '22px',
+ marginTop: 16,
+ marginBottom: 32,
+ width: '65%',
+ }}
+ >
+ Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
+ 的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
+ </p>
+ <div
+ style={{
+ display: 'flex',
+ flexWrap: 'wrap',
+ gap: 16,
+ }}
+ >
+ <InfoCard
+ index={1}
+ href="https://umijs.org/docs/introduce/introduce"
+ title="了解 umi"
+ desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
+ />
+ <InfoCard
+ index={2}
+ title="了解 ant design"
+ href="https://ant.design"
+ desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
+ />
+ <InfoCard
+ index={3}
+ title="了解 Pro Components"
+ href="https://procomponents.ant.design"
+ desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
+ />
+ </div>
+ </div>
+ </Card>
+ </PageContainer>
+ );
+};
+
+export default Welcome;
diff --git a/src/requestErrorConfig.ts b/src/requestErrorConfig.ts
new file mode 100644
index 0000000..c0c4a6a
--- /dev/null
+++ b/src/requestErrorConfig.ts
@@ -0,0 +1,109 @@
+import type { RequestOptions } from '@@/plugin-request/request';
+import type { RequestConfig } from '@umijs/max';
+import { message, notification } from 'antd';
+
+// 错误处理方案: 错误类型
+enum ErrorShowType {
+ SILENT = 0,
+ WARN_MESSAGE = 1,
+ ERROR_MESSAGE = 2,
+ NOTIFICATION = 3,
+ REDIRECT = 9,
+}
+// 与后端约定的响应数据格式
+interface ResponseStructure {
+ success: boolean;
+ data: any;
+ errorCode?: number;
+ errorMessage?: string;
+ showType?: ErrorShowType;
+}
+
+/**
+ * @name 错误处理
+ * pro 自带的错误处理, 可以在这里做自己的改动
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+export const errorConfig: RequestConfig = {
+ // 错误处理: umi@3 的错误处理方案。
+ errorConfig: {
+ // 错误抛出
+ errorThrower: (res) => {
+ const { success, data, errorCode, errorMessage, showType } =
+ res as unknown as ResponseStructure;
+ if (!success) {
+ const error: any = new Error(errorMessage);
+ error.name = 'BizError';
+ error.info = { errorCode, errorMessage, showType, data };
+ throw error; // 抛出自制的错误
+ }
+ },
+ // 错误接收及处理
+ errorHandler: (error: any, opts: any) => {
+ if (opts?.skipErrorHandler) throw error;
+ // 我们的 errorThrower 抛出的错误。
+ if (error.name === 'BizError') {
+ const errorInfo: ResponseStructure | undefined = error.info;
+ if (errorInfo) {
+ const { errorMessage, errorCode } = errorInfo;
+ switch (errorInfo.showType) {
+ case ErrorShowType.SILENT:
+ // do nothing
+ break;
+ case ErrorShowType.WARN_MESSAGE:
+ message.warning(errorMessage);
+ break;
+ case ErrorShowType.ERROR_MESSAGE:
+ message.error(errorMessage);
+ break;
+ case ErrorShowType.NOTIFICATION:
+ notification.open({
+ description: errorMessage,
+ message: errorCode,
+ });
+ break;
+ case ErrorShowType.REDIRECT:
+ // TODO: redirect
+ break;
+ default:
+ message.error(errorMessage);
+ }
+ }
+ } else if (error.response) {
+ // Axios 的错误
+ // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+ message.error(`Response status:${error.response.status}`);
+ } else if (error.request) {
+ // 请求已经成功发起,但没有收到响应
+ // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
+ // 而在node.js中是 http.ClientRequest 的实例
+ message.error('None response! Please retry.');
+ } else {
+ // 发送请求时出了点问题
+ message.error('Request error, please retry.');
+ }
+ },
+ },
+
+ // 请求拦截器
+ requestInterceptors: [
+ (config: RequestOptions) => {
+ // 拦截请求配置,进行个性化处理。
+ const url = config?.url?.concat('?token = 123');
+ return { ...config, url };
+ },
+ ],
+
+ // 响应拦截器
+ responseInterceptors: [
+ (response) => {
+ // 拦截响应数据,进行个性化处理
+ const { data } = response as unknown as ResponseStructure;
+
+ if (data?.success === false) {
+ message.error('请求失败!');
+ }
+ return response;
+ },
+ ],
+};
diff --git a/src/service-worker.js b/src/service-worker.js
new file mode 100644
index 0000000..b86726c
--- /dev/null
+++ b/src/service-worker.js
@@ -0,0 +1,65 @@
+/* eslint-disable no-restricted-globals */
+/* eslint-disable no-underscore-dangle */
+/* globals workbox */
+workbox.core.setCacheNameDetails({
+ prefix: 'antd-pro',
+ suffix: 'v5',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/** Handle API requests */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/** Handle third party requests */
+workbox.routing.registerRoute(
+ /^https:\/\/gw\.alipayobjects\.com\//,
+ workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(
+ /^https:\/\/cdnjs\.cloudflare\.com\//,
+ workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/** Response to client after skipping waiting with MessageChannel */
+addEventListener('message', (event) => {
+ const replyPort = event.ports[0];
+ const message = event.data;
+ if (replyPort && message && message.type === 'skip-waiting') {
+ event.waitUntil(
+ self.skipWaiting().then(
+ () => {
+ replyPort.postMessage({
+ error: null,
+ });
+ },
+ (error) => {
+ replyPort.postMessage({
+ error,
+ });
+ },
+ ),
+ );
+ }
+});
diff --git a/src/services/ant-design-pro/api.ts b/src/services/ant-design-pro/api.ts
new file mode 100644
index 0000000..64a950a
--- /dev/null
+++ b/src/services/ant-design-pro/api.ts
@@ -0,0 +1,11 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 此处后端没有提供注释 GET /api/notices */
+export async function getNotices(options?: { [key: string]: any }) {
+ return request<API.NoticeIconList>('/api/notices', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
diff --git a/src/services/ant-design-pro/index.ts b/src/services/ant-design-pro/index.ts
new file mode 100644
index 0000000..9ae58be
--- /dev/null
+++ b/src/services/ant-design-pro/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as api from './api';
+import * as login from './login';
+import * as rule from './rule';
+export default {
+ api,
+ login,
+ rule,
+};
diff --git a/src/services/ant-design-pro/login.ts b/src/services/ant-design-pro/login.ts
new file mode 100644
index 0000000..3b00b43
--- /dev/null
+++ b/src/services/ant-design-pro/login.ts
@@ -0,0 +1,38 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
+ return request<API.LoginResult>('/api/login/account', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** 发送验证码 POST /api/login/captcha */
+export async function getFakeCaptcha(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.getFakeCaptchaParams,
+ options?: { [key: string]: any },
+) {
+ return request<API.FakeCaptcha>('/api/login/captcha', {
+ method: 'POST',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** 登录接口 POST /api/login/outLogin */
+export async function outLogin(options?: { [key: string]: any }) {
+ return request<Record<string, any>>('/api/login/outLogin', {
+ method: 'POST',
+ ...(options || {}),
+ });
+}
diff --git a/src/services/ant-design-pro/rule.ts b/src/services/ant-design-pro/rule.ts
new file mode 100644
index 0000000..4b8ebc5
--- /dev/null
+++ b/src/services/ant-design-pro/rule.ts
@@ -0,0 +1,42 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 获取规则列表 GET /api/rule */
+export async function rule(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.ruleParams,
+ options?: { [key: string]: any },
+) {
+ return request<API.RuleList>('/api/rule', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** 新建规则 PUT /api/rule */
+export async function updateRule(options?: { [key: string]: any }) {
+ return request<API.RuleListItem>('/api/rule', {
+ method: 'PUT',
+ ...(options || {}),
+ });
+}
+
+/** 新建规则 POST /api/rule */
+export async function addRule(options?: { [key: string]: any }) {
+ return request<API.RuleListItem>('/api/rule', {
+ method: 'POST',
+ ...(options || {}),
+ });
+}
+
+/** 删除规则 DELETE /api/rule */
+export async function removeRule(options?: { [key: string]: any }) {
+ return request<Record<string, any>>('/api/rule', {
+ method: 'DELETE',
+ ...(options || {}),
+ });
+}
diff --git a/src/services/ant-design-pro/typings.d.ts b/src/services/ant-design-pro/typings.d.ts
new file mode 100644
index 0000000..4ee1264
--- /dev/null
+++ b/src/services/ant-design-pro/typings.d.ts
@@ -0,0 +1,108 @@
+declare namespace API {
+ type CurrentUser = UserInfo & {
+ signature?: string;
+ title?: string;
+ group?: string;
+ tags?: { key?: string; label?: string }[];
+ notifyCount?: number;
+ unreadCount?: number;
+ country?: string;
+ access?: string;
+ geographic?: {
+ province?: { label?: string; key?: string };
+ city?: { label?: string; key?: string };
+ };
+ address?: string;
+ phone?: string;
+ };
+
+ type ErrorResponse = {
+ /** 业务约定的错误码 */
+ errorCode: string;
+ /** 业务上的错误信息 */
+ errorMessage?: string;
+ /** 业务上的请求是否成功 */
+ success?: boolean;
+ };
+
+ type FakeCaptcha = {
+ code?: number;
+ status?: string;
+ };
+
+ type getFakeCaptchaParams = {
+ /** 手机号 */
+ phone?: string;
+ };
+
+ type LoginParams = {
+ username?: string;
+ password?: string;
+ uuid?: string;
+ autoLogin?: boolean;
+ type?: string;
+ };
+
+ type LoginResult = {
+ code: number;
+ msg?: string;
+ type?: string;
+ token?: string;
+ };
+
+ type NoticeIconItem = {
+ id?: string;
+ extra?: string;
+ key?: string;
+ read?: boolean;
+ avatar?: string;
+ title?: string;
+ status?: string;
+ datetime?: string;
+ description?: string;
+ type?: NoticeIconItemType;
+ };
+
+ type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+ type NoticeIconList = {
+ data?: NoticeIconItem[];
+ /** 列表的内容总数 */
+ total?: number;
+ success?: boolean;
+ };
+
+ type PageParams = {
+ current?: number;
+ pageSize?: number;
+ };
+
+ type RuleList = {
+ data?: RuleListItem[];
+ /** 列表的内容总数 */
+ total?: number;
+ success?: boolean;
+ };
+
+ type RuleListItem = {
+ key?: number;
+ disabled?: boolean;
+ href?: string;
+ avatar?: string;
+ name?: string;
+ owner?: string;
+ desc?: string;
+ callNo?: number;
+ status?: number;
+ updatedAt?: string;
+ createdAt?: string;
+ progress?: number;
+ };
+
+ type ruleParams = {
+ /** 当前的页码 */
+ current?: number;
+ /** 页面的容量 */
+ pageSize?: number;
+ };
+}
diff --git a/src/services/monitor/cache.ts b/src/services/monitor/cache.ts
new file mode 100644
index 0000000..244368e
--- /dev/null
+++ b/src/services/monitor/cache.ts
@@ -0,0 +1,17 @@
+import { request } from '@umijs/max';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+
+// 获取服务器信息
+export async function getCacheInfo() {
+ return request<API.Monitor.CacheInfoResult>('/api/monitor/cache', {
+ method: 'GET',
+ });
+}
diff --git a/src/services/monitor/cachelist.ts b/src/services/monitor/cachelist.ts
new file mode 100644
index 0000000..d348d4b
--- /dev/null
+++ b/src/services/monitor/cachelist.ts
@@ -0,0 +1,51 @@
+import { request } from '@umijs/max';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2022/06/27
+ *
+ * */
+
+
+// 查询缓存名称列表
+export function listCacheName() {
+ return request<API.Monitor.CacheNamesResponse>('/api/monitor/cache/getNames', {
+ method: 'get'
+ })
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName: string) {
+ return request<API.Monitor.CacheKeysResponse>('/api/monitor/cache/getKeys/' + cacheName, {
+ method: 'get'
+ })
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName: string, cacheKey: string) {
+ return request<API.Monitor.CacheValueResponse>('/api/monitor/cache/getValue/' + cacheName + '/' + cacheKey, {
+ method: 'get'
+ })
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName: string) {
+ return request<API.Result>('/api/monitor/cache/clearCacheName/' + cacheName, {
+ method: 'delete'
+ })
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey: string) {
+ return request<API.Result>('/api/monitor/cache/clearCacheKey/' + cacheKey, {
+ method: 'delete'
+ })
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+ return request<API.Result>('/api/monitor/cache/clearCacheAll', {
+ method: 'delete'
+ })
+}
diff --git a/src/services/monitor/job.ts b/src/services/monitor/job.ts
new file mode 100644
index 0000000..192f819
--- /dev/null
+++ b/src/services/monitor/job.ts
@@ -0,0 +1,73 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度 API
+ *
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度列表
+export async function getJobList(params?: API.Monitor.JobListParams) {
+ return request<API.Monitor.JobPageResult>('/api/monitor/job/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询定时任务调度详细
+export function getJob(jobId: number) {
+ return request<API.Monitor.JobInfoResult>(`/api/monitor/job/${jobId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增定时任务调度
+export async function addJob(params: API.Monitor.Job) {
+ return request<API.Result>('/api/monitor/job', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改定时任务调度
+export async function updateJob(params: API.Monitor.Job) {
+ return request<API.Result>('/api/monitor/job', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除定时任务调度
+export async function removeJob(ids: string) {
+ return request<API.Result>(`/api/monitor/job/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出定时任务调度
+export function exportJob(params?: API.Monitor.JobListParams) {
+ return downLoadXlsx(`/api/monitor/job/export`, { params }, `job_${new Date().getTime()}.xlsx`);
+}
+
+// 定时任务立即执行一次
+export async function runJob(jobId: number, jobGroup: string) {
+ const job = {
+ jobId,
+ jobGroup,
+ };
+ return request('/api/monitor/job/run', {
+ method: 'PUT',
+ data: job,
+ });
+}
diff --git a/src/services/monitor/jobLog.ts b/src/services/monitor/jobLog.ts
new file mode 100644
index 0000000..9885aa7
--- /dev/null
+++ b/src/services/monitor/jobLog.ts
@@ -0,0 +1,40 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度日志 API
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度日志列表
+export async function getJobLogList(params?: API.Monitor.JobLogListParams) {
+ return request<API.Monitor.JobLogPageResult>('/api/schedule/job/log/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+
+// 删除定时任务调度日志
+export async function removeJobLog(jobLogId: string) {
+ return request<API.Result>(`/api/schedule/job/log/${jobLogId}`, {
+ method: 'DELETE'
+ });
+}
+
+// 清空调度日志
+export function cleanJobLog() {
+ return request('/api/schedule/job/log/clean', {
+ method: 'delete'
+ })
+}
+
+// 导出定时任务调度日志
+export function exportJobLog(params?: API.Monitor.JobLogListParams) {
+ return downLoadXlsx(`/api/monitor/jobLog/export`, { params }, `joblog_${new Date().getTime()}.xlsx`);
+}
diff --git a/src/services/monitor/logininfor.ts b/src/services/monitor/logininfor.ts
new file mode 100644
index 0000000..ea1864a
--- /dev/null
+++ b/src/services/monitor/logininfor.ts
@@ -0,0 +1,68 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询系统访问记录列表
+export async function getLogininforList(params?: API.Monitor.LogininforListParams) {
+ return request<API.Monitor.LogininforPageResult>('/api/monitor/logininfor/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询系统访问记录详细
+export function getLogininfor(infoId: number) {
+ return request<API.Monitor.LogininforInfoResult>(`/api/monitor/logininfor/${infoId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增系统访问记录
+export async function addLogininfor(params: API.Monitor.Logininfor) {
+ return request<API.Result>('/api/monitor/logininfor', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改系统访问记录
+export async function updateLogininfor(params: API.Monitor.Logininfor) {
+ return request<API.Result>('/api/monitor/logininfor', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除系统访问记录
+export async function removeLogininfor(ids: string) {
+ return request<API.Result>(`/api/monitor/logininfor/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出系统访问记录
+export function exportLogininfor(params?: API.Monitor.LogininforListParams) {
+ return downLoadXlsx(`/api/monitor/logininfor/export`, { params }, `logininfor_${new Date().getTime()}.xlsx`);
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName: string) {
+ return request<API.Result>('/api/monitor/logininfor/unlock/' + userName, {
+ method: 'get'
+ })
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+ return request<API.Result>('/api/monitor/logininfor/clean', {
+ method: 'delete'
+ })
+}
diff --git a/src/services/monitor/online.ts b/src/services/monitor/online.ts
new file mode 100644
index 0000000..ae2a6ac
--- /dev/null
+++ b/src/services/monitor/online.ts
@@ -0,0 +1,23 @@
+import { request } from '@umijs/max';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+// 查询在线用户列表
+export async function getOnlineUserList(params?: API.Monitor.OnlineUserListParams) {
+ return request<API.Monitor.OnlineUserPageResult>('/api/monitor/online/list', {
+ method: 'GET',
+ params,
+ });
+}
+
+// 强退用户
+export async function forceLogout(tokenId: string) {
+ return request(`/api/monitor/online/${tokenId}`, {
+ method: 'DELETE',
+ });
+}
diff --git a/src/services/monitor/operlog.ts b/src/services/monitor/operlog.ts
new file mode 100644
index 0000000..004863a
--- /dev/null
+++ b/src/services/monitor/operlog.ts
@@ -0,0 +1,60 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询操作日志记录列表
+export async function getOperlogList(params?: API.Monitor.OperlogListParams) {
+ return request<API.Monitor.OperlogPageResult>('/api/monitor/operlog/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询操作日志记录详细
+export function getOperlog(operId: number) {
+ return request<API.Monitor.OperlogInfoResult>(`/api/monitor/operlog/${operId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增操作日志记录
+export async function addOperlog(params: API.Monitor.Operlog) {
+ return request<API.Result>('/api/monitor/operlog', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改操作日志记录
+export async function updateOperlog(params: API.Monitor.Operlog) {
+ return request<API.Result>('/api/monitor/operlog', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除操作日志记录
+export async function removeOperlog(ids: string) {
+ return request<API.Result>(`/api/monitor/operlog/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+export async function cleanAllOperlog() {
+ return request<API.Result>(`/api/monitor/operlog/clean`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出操作日志记录
+export function exportOperlog(params?: API.Monitor.OperlogListParams) {
+ return downLoadXlsx(`/api/monitor/operlog/export`, { params }, `operlog_${new Date().getTime()}.xlsx`);
+}
diff --git a/src/services/monitor/server.ts b/src/services/monitor/server.ts
new file mode 100644
index 0000000..24a849f
--- /dev/null
+++ b/src/services/monitor/server.ts
@@ -0,0 +1,16 @@
+import { request } from '@umijs/max';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+
+// 获取服务器信息
+export async function getServerInfo() {
+ return request('/api/monitor/server', {
+ method: 'GET',
+ });
+}
diff --git a/src/services/session.ts b/src/services/session.ts
new file mode 100644
index 0000000..8b4e0d3
--- /dev/null
+++ b/src/services/session.ts
@@ -0,0 +1,159 @@
+import { createIcon } from '@/utils/IconUtil';
+import { MenuDataItem } from '@ant-design/pro-components';
+import { request } from '@umijs/max';
+import React, { lazy } from 'react';
+
+
+let remoteMenu: any = null;
+
+export function getRemoteMenu() {
+ return remoteMenu;
+}
+
+export function setRemoteMenu(data: any) {
+ remoteMenu = data;
+}
+
+
+function patchRouteItems(route: any, menu: any, parentPath: string) {
+ for (const menuItem of menu) {
+ if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') {
+ if (menuItem.routes) {
+ let hasItem = false;
+ let newItem = null;
+ for (const routeChild of route.routes) {
+ if (routeChild.path === menuItem.path) {
+ hasItem = true;
+ newItem = routeChild;
+ }
+ }
+ if (!hasItem) {
+ newItem = {
+ path: menuItem.path,
+ routes: [],
+ children: []
+ }
+ route.routes.push(newItem)
+ }
+ patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/');
+ }
+ } else {
+ const names: string[] = menuItem.component.split('/');
+ let path = '';
+ names.forEach(name => {
+ if (path.length > 0) {
+ path += '/';
+ }
+ if (name !== 'index') {
+ path += name.at(0)?.toUpperCase() + name.substr(1);
+ } else {
+ path += name;
+ }
+ })
+ if (!path.endsWith('.tsx')) {
+ path += '.tsx'
+ }
+ if (route.routes === undefined) {
+ route.routes = [];
+ }
+ if (route.children === undefined) {
+ route.children = [];
+ }
+ const newRoute = {
+ element: React.createElement(lazy(() => import('@/pages/' + path))),
+ path: parentPath + menuItem.path,
+ }
+ route.children.push(newRoute);
+ route.routes.push(newRoute);
+ }
+ }
+}
+
+export function patchRouteWithRemoteMenus(routes: any) {
+ if (remoteMenu === null) { return; }
+ let proLayout = null;
+ for (const routeItem of routes) {
+ if (routeItem.id === 'ant-design-pro-layout') {
+ proLayout = routeItem;
+ break;
+ }
+ }
+ patchRouteItems(proLayout, remoteMenu, '');
+}
+
+/** 获取当前的用户 GET /api/getUserInfo */
+export async function getUserInfo(options?: Record<string, any>) {
+ return request<API.UserInfoResult>('/api/getInfo', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
+// 刷新方法
+export async function refreshToken() {
+ return request('/api/auth/refresh', {
+ method: 'post'
+ })
+}
+
+export async function getRouters(): Promise<any> {
+ return request('/api/getRouters');
+}
+
+export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
+ return childrens.map((item: API.RoutersMenuItem) => {
+ return {
+ path: item.path,
+ icon: createIcon(item.meta.icon),
+ // icon: item.meta.icon,
+ name: item.meta.title,
+ routes: item.children ? convertCompatRouters(item.children) : undefined,
+ hideChildrenInMenu: item.hidden,
+ hideInMenu: item.hidden,
+ component: item.component,
+ authority: item.perms,
+ };
+ });
+}
+
+export async function getRoutersInfo(): Promise<MenuDataItem[]> {
+ return getRouters().then((res) => {
+ if (res.code === 200) {
+ return convertCompatRouters(res.data);
+ } else {
+ return [];
+ }
+ });
+}
+
+export function getMatchMenuItem(
+ path: string,
+ menuData: MenuDataItem[] | undefined,
+): MenuDataItem[] {
+ if (!menuData) return [];
+ let items: MenuDataItem[] = [];
+ menuData.forEach((item) => {
+ if (item.path) {
+ if (item.path === path) {
+ items.push(item);
+ return;
+ }
+ if (path.length >= item.path?.length) {
+ const exp = `${item.path}/*`;
+ if (path.match(exp)) {
+ if (item.routes) {
+ const subpath = path.substr(item.path.length + 1);
+ const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes);
+ items = items.concat(subItem);
+ } else {
+ const paths = path.split('/');
+ if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
+ items.push(item);
+ }
+ }
+ }
+ }
+ }
+ });
+ return items;
+}
diff --git a/src/services/swagger/index.ts b/src/services/swagger/index.ts
new file mode 100644
index 0000000..83cf97c
--- /dev/null
+++ b/src/services/swagger/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as pet from './pet';
+import * as store from './store';
+import * as user from './user';
+export default {
+ pet,
+ store,
+ user,
+};
diff --git a/src/services/swagger/pet.ts b/src/services/swagger/pet.ts
new file mode 100644
index 0000000..b887475
--- /dev/null
+++ b/src/services/swagger/pet.ts
@@ -0,0 +1,153 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Update an existing pet PUT /pet */
+export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
+ return request<any>('/pet', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Add a new pet to the store POST /pet */
+export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
+ return request<any>('/pet', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Find pet by ID Returns a single pet GET /pet/${param0} */
+export async function getPetById(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.getPetByIdParams,
+ options?: { [key: string]: any },
+) {
+ const { petId: param0, ...queryParams } = params;
+ return request<API.Pet>(`/pet/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Updates a pet in the store with form data POST /pet/${param0} */
+export async function updatePetWithForm(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.updatePetWithFormParams,
+ body: { name?: string; status?: string },
+ options?: { [key: string]: any },
+) {
+ const { petId: param0, ...queryParams } = params;
+ const formData = new FormData();
+
+ Object.keys(body).forEach((ele) => {
+ const item = (body as any)[ele];
+
+ if (item !== undefined && item !== null) {
+ formData.append(
+ ele,
+ typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+ );
+ }
+ });
+
+ return request<any>(`/pet/${param0}`, {
+ method: 'POST',
+ params: { ...queryParams },
+ data: formData,
+ ...(options || {}),
+ });
+}
+
+/** Deletes a pet DELETE /pet/${param0} */
+export async function deletePet(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.deletePetParams & {
+ // header
+ api_key?: string;
+ },
+ options?: { [key: string]: any },
+) {
+ const { petId: param0, ...queryParams } = params;
+ return request<any>(`/pet/${param0}`, {
+ method: 'DELETE',
+ headers: {},
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** uploads an image POST /pet/${param0}/uploadImage */
+export async function uploadFile(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.uploadFileParams,
+ body: { additionalMetadata?: string; file?: string },
+ file?: File,
+ options?: { [key: string]: any },
+) {
+ const { petId: param0, ...queryParams } = params;
+ const formData = new FormData();
+
+ if (file) {
+ formData.append('file', file);
+ }
+
+ Object.keys(body).forEach((ele) => {
+ const item = (body as any)[ele];
+
+ if (item !== undefined && item !== null) {
+ formData.append(
+ ele,
+ typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+ );
+ }
+ });
+
+ return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
+ method: 'POST',
+ params: { ...queryParams },
+ data: formData,
+ requestType: 'form',
+ ...(options || {}),
+ });
+}
+
+/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
+export async function findPetsByStatus(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.findPetsByStatusParams,
+ options?: { [key: string]: any },
+) {
+ return request<API.Pet[]>('/pet/findByStatus', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
+export async function findPetsByTags(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.findPetsByTagsParams,
+ options?: { [key: string]: any },
+) {
+ return request<API.Pet[]>('/pet/findByTags', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
diff --git a/src/services/swagger/store.ts b/src/services/swagger/store.ts
new file mode 100644
index 0000000..b9c689a
--- /dev/null
+++ b/src/services/swagger/store.ts
@@ -0,0 +1,48 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
+export async function getInventory(options?: { [key: string]: any }) {
+ return request<Record<string, any>>('/store/inventory', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
+/** Place an order for a pet POST /store/order */
+export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
+ return request<API.Order>('/store/order', {
+ method: 'POST',
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
+export async function getOrderById(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.getOrderByIdParams,
+ options?: { [key: string]: any },
+) {
+ const { orderId: param0, ...queryParams } = params;
+ return request<API.Order>(`/store/order/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
+export async function deleteOrder(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.deleteOrderParams,
+ options?: { [key: string]: any },
+) {
+ const { orderId: param0, ...queryParams } = params;
+ return request<any>(`/store/order/${param0}`, {
+ method: 'DELETE',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
diff --git a/src/services/swagger/typings.d.ts b/src/services/swagger/typings.d.ts
new file mode 100644
index 0000000..d06bcfc
--- /dev/null
+++ b/src/services/swagger/typings.d.ts
@@ -0,0 +1,112 @@
+declare namespace API {
+ type ApiResponse = {
+ code?: number;
+ type?: string;
+ message?: string;
+ };
+
+ type Category = {
+ id?: number;
+ name?: string;
+ };
+
+ type deleteOrderParams = {
+ /** ID of the order that needs to be deleted */
+ orderId: number;
+ };
+
+ type deletePetParams = {
+ api_key?: string;
+ /** Pet id to delete */
+ petId: number;
+ };
+
+ type deleteUserParams = {
+ /** The name that needs to be deleted */
+ username: string;
+ };
+
+ type findPetsByStatusParams = {
+ /** Status values that need to be considered for filter */
+ status: ('available' | 'pending' | 'sold')[];
+ };
+
+ type findPetsByTagsParams = {
+ /** Tags to filter by */
+ tags: string[];
+ };
+
+ type getOrderByIdParams = {
+ /** ID of pet that needs to be fetched */
+ orderId: number;
+ };
+
+ type getPetByIdParams = {
+ /** ID of pet to return */
+ petId: number;
+ };
+
+ type getUserByNameParams = {
+ /** The name that needs to be fetched. Use user1 for testing. */
+ username: string;
+ };
+
+ type loginUserParams = {
+ /** The user name for login */
+ username: string;
+ /** The password for login in clear text */
+ password: string;
+ };
+
+ type Order = {
+ id?: number;
+ petId?: number;
+ quantity?: number;
+ shipDate?: string;
+ /** Order Status */
+ status?: 'placed' | 'approved' | 'delivered';
+ complete?: boolean;
+ };
+
+ type Pet = {
+ id?: number;
+ category?: Category;
+ name: string;
+ photoUrls: string[];
+ tags?: Tag[];
+ /** pet status in the store */
+ status?: 'available' | 'pending' | 'sold';
+ };
+
+ type Tag = {
+ id?: number;
+ name?: string;
+ };
+
+ type updatePetWithFormParams = {
+ /** ID of pet that needs to be updated */
+ petId: number;
+ };
+
+ type updateUserParams = {
+ /** name that need to be updated */
+ username: string;
+ };
+
+ type uploadFileParams = {
+ /** ID of pet to update */
+ petId: number;
+ };
+
+ type User = {
+ id?: number;
+ username?: string;
+ firstName?: string;
+ lastName?: string;
+ email?: string;
+ password?: string;
+ phone?: string;
+ /** User Status */
+ userStatus?: number;
+ };
+}
diff --git a/src/services/swagger/user.ts b/src/services/swagger/user.ts
new file mode 100644
index 0000000..4dd6f42
--- /dev/null
+++ b/src/services/swagger/user.ts
@@ -0,0 +1,100 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Create user This can only be done by the logged in user. POST /user */
+export async function createUser(body: API.User, options?: { [key: string]: any }) {
+ return request<any>('/user', {
+ method: 'POST',
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Get user by user name GET /user/${param0} */
+export async function getUserByName(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.getUserByNameParams,
+ options?: { [key: string]: any },
+) {
+ const { username: param0, ...queryParams } = params;
+ return request<API.User>(`/user/${param0}`, {
+ method: 'GET',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
+export async function updateUser(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.updateUserParams,
+ body: API.User,
+ options?: { [key: string]: any },
+) {
+ const { username: param0, ...queryParams } = params;
+ return request<any>(`/user/${param0}`, {
+ method: 'PUT',
+ params: { ...queryParams },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
+export async function deleteUser(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.deleteUserParams,
+ options?: { [key: string]: any },
+) {
+ const { username: param0, ...queryParams } = params;
+ return request<any>(`/user/${param0}`, {
+ method: 'DELETE',
+ params: { ...queryParams },
+ ...(options || {}),
+ });
+}
+
+/** Creates list of users with given input array POST /user/createWithArray */
+export async function createUsersWithArrayInput(
+ body: API.User[],
+ options?: { [key: string]: any },
+) {
+ return request<any>('/user/createWithArray', {
+ method: 'POST',
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Creates list of users with given input array POST /user/createWithList */
+export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
+ return request<any>('/user/createWithList', {
+ method: 'POST',
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** Logs user into the system GET /user/login */
+export async function loginUser(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.loginUserParams,
+ options?: { [key: string]: any },
+) {
+ return request<string>('/user/login', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ ...(options || {}),
+ });
+}
+
+/** Logs out current logged in user session GET /user/logout */
+export async function logoutUser(options?: { [key: string]: any }) {
+ return request<any>('/user/logout', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
diff --git a/src/services/system/auth.ts b/src/services/system/auth.ts
new file mode 100644
index 0000000..b757618
--- /dev/null
+++ b/src/services/system/auth.ts
@@ -0,0 +1,39 @@
+import { request } from '@umijs/max';
+
+export async function getCaptchaImg(params?: Record<string, any>, options?: Record<string, any>) {
+ return request('/api/captchaImage', {
+ method: 'GET',
+ params: {
+ ...params,
+ },
+ headers: {
+ isToken: false,
+ },
+ ...(options || {}),
+ });
+}
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: Record<string, any>) {
+ return request<API.LoginResult>('/api/login', {
+ method: 'POST',
+ headers: {
+ isToken: false,
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** 退出登录接口 POST /api/login/outLogin */
+export async function logout() {
+ return request<Record<string, any>>('/api/logout', {
+ method: 'delete',
+ });
+}
+
+// 获取手机验证码
+export async function getMobileCaptcha(mobile: string) {
+ return request(`/api/login/captcha?mobile=${mobile}`);
+}
diff --git a/src/services/system/config.ts b/src/services/system/config.ts
new file mode 100644
index 0000000..9843daf
--- /dev/null
+++ b/src/services/system/config.ts
@@ -0,0 +1,62 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询参数配置列表
+export async function getConfigList(params?: API.System.ConfigListParams) {
+ return request<API.System.ConfigPageResult>('/api/system/config/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询参数配置详细
+export function getConfig(configId: number) {
+ return request<API.System.ConfigInfoResult>(`/api/system/config/${configId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增参数配置
+export async function addConfig(params: API.System.Config) {
+ return request<API.Result>('/api/system/config', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改参数配置
+export async function updateConfig(params: API.System.Config) {
+ return request<API.Result>('/api/system/config', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除参数配置
+export async function removeConfig(ids: string) {
+ return request<API.Result>(`/api/system/config/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出参数配置
+export function exportConfig(params?: API.System.ConfigListParams) {
+ return downLoadXlsx(`/api/system/config/export`, { params }, `config_${new Date().getTime()}.xlsx`);
+}
+
+
+// 刷新参数缓存
+export function refreshConfigCache() {
+ return request<API.Result>('/api/system/config/refreshCache', {
+ method: 'delete'
+ })
+}
diff --git a/src/services/system/dept.ts b/src/services/system/dept.ts
new file mode 100644
index 0000000..ad58807
--- /dev/null
+++ b/src/services/system/dept.ts
@@ -0,0 +1,56 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询部门列表
+export async function getDeptList(params?: API.System.DeptListParams) {
+ return request<API.System.DeptPageResult>('/api/system/dept/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询部门列表(排除节点)
+export function getDeptListExcludeChild(deptId: number) {
+ return request(`/api/system/dept/list/exclude/${deptId}`, {
+ method: 'get',
+ });
+}
+
+// 查询部门详细
+export function getDept(deptId: number) {
+ return request<API.System.DeptInfoResult>(`/api/system/dept/${deptId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增部门
+export async function addDept(params: API.System.Dept) {
+ return request<API.Result>('/api/system/dept', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改部门
+export async function updateDept(params: API.System.Dept) {
+ return request<API.Result>('/api/system/dept', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除部门
+export async function removeDept(ids: string) {
+ return request<API.Result>(`/api/system/dept/${ids}`, {
+ method: 'DELETE'
+ });
+}
diff --git a/src/services/system/dict.ts b/src/services/system/dict.ts
new file mode 100644
index 0000000..5671124
--- /dev/null
+++ b/src/services/system/dict.ts
@@ -0,0 +1,120 @@
+import { request } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { HttpResult } from '@/enums/httpEnum';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
+// 查询字典类型列表
+export async function getDictTypeList(params?: API.DictTypeListParams) {
+ return request(`/api/system/dict/type/list`, {
+ params: {
+ ...params,
+ },
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
+
+// 查询字典类型详细
+export function getDictType(dictId: string) {
+ return request(`/api/system/dict/type/${dictId}`, {
+ method: 'GET',
+ });
+}
+
+// 查询字典数据详细
+export async function getDictValueEnum(dictType: string, isDigital?: boolean): Promise<DictValueEnumObj> {
+ const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+ method: 'GET',
+ });
+ if(resp.code === HttpResult.SUCCESS) {
+ const opts: DictValueEnumObj = {};
+ resp.data.forEach((item: any) => {
+ opts[item.dictValue] = {
+ text: item.dictLabel,
+ label: item.dictLabel,
+ value: isDigital ? Number(item.dictValue) : item.dictValue,
+ key: item.dictCode,
+ listClass: item.listClass,
+ status: item.listClass };
+ });
+ return opts;
+ } else {
+ return {};
+ }
+}
+
+export async function getDictSelectOption(dictType: string, isDigital?: boolean) {
+ const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+ method: 'GET',
+ });
+ if (resp.code === 200) {
+ const options: DictValueEnumObj[] = resp.data.map((item) => {
+ return {
+ text: item.dictLabel,
+ label: item.dictLabel,
+ value: isDigital ? Number(item.dictValue) : item.dictValue,
+ key: item.dictCode,
+ listClass: item.listClass,
+ status: item.listClass
+ };
+ });
+ return options;
+ }
+ return [];
+};
+
+// 新增字典类型
+export async function addDictType(params: API.System.DictType) {
+ return request<API.Result>('/api/system/dict/type', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改字典类型
+export async function updateDictType(params: API.System.DictType) {
+ return request<API.Result>('/api/system/dict/type', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除字典类型
+export async function removeDictType(ids: string) {
+ return request<API.Result>(`/api/system/dict/type/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出字典类型
+export function exportDictType(params?: API.System.DictTypeListParams) {
+ return downLoadXlsx(`/api/system/dict/type/export`, { params }, `dict_type_${new Date().getTime()}.xlsx`);
+}
+
+// 获取字典选择框列表
+export async function getDictTypeOptionSelect(params?: API.DictTypeListParams) {
+ return request('/api/system/dict/type/optionselect', {
+ params: {
+ ...params,
+ },
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ });
+}
diff --git a/src/services/system/dictdata.ts b/src/services/system/dictdata.ts
new file mode 100644
index 0000000..f7856e8
--- /dev/null
+++ b/src/services/system/dictdata.ts
@@ -0,0 +1,65 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询字典数据列表
+export async function getDictDataList(
+ params?: API.System.DictDataListParams,
+ options?: { [key: string]: any },
+) {
+ return request<API.System.DictDataPageResult>('/api/system/dict/data/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params,
+ ...(options || {}),
+ });
+}
+
+// 查询字典数据详细
+export function getDictData(dictCode: number, options?: { [key: string]: any }) {
+ return request<API.System.DictDataInfoResult>(`/api/system/dict/data/${dictCode}`, {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
+// 新增字典数据
+export async function addDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/dict/data', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {}),
+ });
+}
+
+// 修改字典数据
+export async function updateDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/dict/data', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {}),
+ });
+}
+
+// 删除字典数据
+export async function removeDictData(ids: string, options?: { [key: string]: any }) {
+ return request<API.Result>(`/api/system/dict/data/${ids}`, {
+ method: 'DELETE',
+ ...(options || {}),
+ });
+}
+
+// 导出字典数据
+export function exportDictData(
+ params?: API.System.DictDataListParams,
+ options?: { [key: string]: any },
+) {
+ return downLoadXlsx(`/api/system/dict/data/export`, { params }, `dict_data_${new Date().getTime()}.xlsx`);
+}
diff --git a/src/services/system/index.ts b/src/services/system/index.ts
new file mode 100644
index 0000000..24ce62d
--- /dev/null
+++ b/src/services/system/index.ts
@@ -0,0 +1,14 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+import * as Auth from './auth';
+import * as User from './User';
+import * as Dict from './dict';
+import * as Menu from './menu';
+
+export default {
+ Auth,
+ User,
+ Dict,
+ Menu,
+};
diff --git a/src/services/system/menu.ts b/src/services/system/menu.ts
new file mode 100644
index 0000000..b2f411b
--- /dev/null
+++ b/src/services/system/menu.ts
@@ -0,0 +1,61 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询菜单权限列表
+export async function getMenuList(params?: API.System.MenuListParams, options?: { [key: string]: any }) {
+ return request<API.System.MenuPageResult>('/api/system/menu/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params,
+ ...(options || {}),
+ });
+}
+
+// 查询菜单权限详细
+export function getMenu(menuId: number, options?: { [key: string]: any }) {
+ return request<API.System.MenuInfoResult>(`/api/system/menu/${menuId}`, {
+ method: 'GET',
+ ...(options || {})
+ });
+}
+
+// 新增菜单权限
+export async function addMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/menu', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {})
+ });
+}
+
+// 修改菜单权限
+export async function updateMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/menu', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {})
+ });
+}
+
+// 删除菜单权限
+export async function removeMenu(ids: string, options?: { [key: string]: any }) {
+ return request<API.Result>(`/api/system/menu/${ids}`, {
+ method: 'DELETE',
+ ...(options || {})
+ });
+}
+
+// 查询菜单权限详细
+export function getMenuTree() {
+ return request('/api/system/menu/treeselect', {
+ method: 'GET',
+ });
+}
diff --git a/src/services/system/notice.ts b/src/services/system/notice.ts
new file mode 100644
index 0000000..2c86a08
--- /dev/null
+++ b/src/services/system/notice.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询通知公告列表
+export async function getNoticeList(params?: API.System.NoticeListParams) {
+ return request<API.System.NoticePageResult>('/api/system/notice/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询通知公告详细
+export function getNotice(noticeId: number) {
+ return request<API.System.NoticeInfoResult>(`/api/system/notice/${noticeId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增通知公告
+export async function addNotice(params: API.System.Notice) {
+ return request<API.Result>('/api/system/notice', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改通知公告
+export async function updateNotice(params: API.System.Notice) {
+ return request<API.Result>('/api/system/notice', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除通知公告
+export async function removeNotice(ids: string) {
+ return request<API.Result>(`/api/system/notice/${ids}`, {
+ method: 'DELETE'
+ });
+}
diff --git a/src/services/system/post.ts b/src/services/system/post.ts
new file mode 100644
index 0000000..3170b89
--- /dev/null
+++ b/src/services/system/post.ts
@@ -0,0 +1,54 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询岗位信息列表
+export async function getPostList(params?: API.System.PostListParams) {
+ return request<API.System.PostPageResult>('/api/system/post/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params
+ });
+}
+
+// 查询岗位信息详细
+export function getPost(postId: number) {
+ return request<API.System.PostInfoResult>(`/api/system/post/${postId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增岗位信息
+export async function addPost(params: API.System.Post) {
+ return request<API.Result>('/api/system/post', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改岗位信息
+export async function updatePost(params: API.System.Post) {
+ return request<API.Result>('/api/system/post', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除岗位信息
+export async function removePost(ids: string) {
+ return request<API.Result>(`/api/system/post/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出岗位信息
+export function exportPost(params?: API.System.PostListParams) {
+ return downLoadXlsx(`/api/system/post/export`, { params }, `post_${new Date().getTime()}.xlsx`);
+}
diff --git a/src/services/system/role.ts b/src/services/system/role.ts
new file mode 100644
index 0000000..3836243
--- /dev/null
+++ b/src/services/system/role.ts
@@ -0,0 +1,128 @@
+import { ContentType } from '@/enums/httpEnum';
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询角色信息列表
+export async function getRoleList(params?: API.System.RoleListParams) {
+ return request<API.System.RolePageResult>('/api/system/role/list', {
+ method: 'GET',
+ headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+ params
+ });
+}
+
+// 查询角色信息详细
+export function getRole(roleId: number) {
+ return request<API.System.RoleInfoResult>(`/api/system/role/${roleId}`, {
+ method: 'GET'
+ });
+}
+
+// 新增角色信息
+export async function addRole(params: API.System.Role) {
+ return request<API.Result>('/api/system/role', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 修改角色信息
+export async function updateRole(params: API.System.Role) {
+ return request<API.Result>('/api/system/role', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params
+ });
+}
+
+// 删除角色信息
+export async function removeRole(ids: string) {
+ return request<API.Result>(`/api/system/role/${ids}`, {
+ method: 'DELETE'
+ });
+}
+
+// 导出角色信息
+export function exportRole(params?: API.System.RoleListParams) {
+ return downLoadXlsx(`/api/system/role/export`, { params }, `role_${new Date().getTime()}.xlsx`);
+}
+
+// 获取角色菜单列表
+export function getRoleMenuList(id: number) {
+ return request<API.System.RoleMenuResult>(`/api/system/menu/roleMenuTreeselect/${id}`, {
+ method: 'get',
+ });
+}
+
+// 角色数据权限
+export function updateRoleDataScope(data: Record<string, any>) {
+ return request('/api/system/role/dataScope', {
+ method: 'put',
+ data
+ })
+}
+
+// 角色状态修改
+export function changeRoleStatus(roleId: number, status: string) {
+ const data = {
+ roleId,
+ status
+ }
+ return request<API.Result>('/api/system/role/changeStatus', {
+ method: 'put',
+ data: data
+ })
+}
+
+// 查询角色已授权用户列表
+export function allocatedUserList(params?: API.System.RoleListParams) {
+ return request('/api/system/role/authUser/allocatedList', {
+ method: 'get',
+ params
+ })
+}
+
+// 查询角色未授权用户列表
+export function unallocatedUserList(params?: API.System.RoleListParams) {
+ return request('/api/system/role/authUser/unallocatedList', {
+ method: 'get',
+ params
+ })
+}
+
+// 取消用户授权角色
+export function authUserCancel(data: any) {
+ return request<API.Result>('/api/system/role/authUser/cancel', {
+ method: 'put',
+ data: data
+ })
+}
+
+// 批量取消用户授权角色
+export function authUserCancelAll(data: any) {
+ return request<API.Result>('/api/system/role/authUser/cancelAll', {
+ method: 'put',
+ params: data
+ })
+}
+
+// 授权用户选择
+export function authUserSelectAll(data: Record<string, any>) {
+ return request<API.Result>('/api/system/role/authUser/selectAll', {
+ method: 'put',
+ params: data,
+ headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+ })
+}
+
+// 根据角色ID查询部门树结构
+export function getDeptTreeSelect(roleId: number) {
+ return request('/api/system/role/deptTree/' + roleId, {
+ method: 'get'
+ })
+}
diff --git a/src/services/system/user.ts b/src/services/system/user.ts
new file mode 100644
index 0000000..da997bf
--- /dev/null
+++ b/src/services/system/user.ts
@@ -0,0 +1,152 @@
+import { formatTreeData } from '@/utils/tree';
+import { request } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询用户信息列表
+export async function getUserList(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+ return request<API.System.UserPageResult>('/api/system/user/list', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ params,
+ ...(options || {})
+ });
+}
+
+// 查询用户信息详细
+export function getUser(userId: number, options?: { [key: string]: any }) {
+ return request<API.System.UserInfoResult>(`/api/system/user/${userId}`, {
+ method: 'GET',
+ ...(options || {})
+ });
+}
+
+// 新增用户信息
+export async function addUser(params: API.System.User, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/user', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {})
+ });
+}
+
+// 修改用户信息
+export async function updateUser(params: API.System.User, options?: { [key: string]: any }) {
+ return request<API.Result>('/api/system/user', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ },
+ data: params,
+ ...(options || {})
+ });
+}
+
+// 删除用户信息
+export async function removeUser(ids: string, options?: { [key: string]: any }) {
+ return request<API.Result>(`/api/system/user/${ids}`, {
+ method: 'DELETE',
+ ...(options || {})
+ });
+}
+
+// 导出用户信息
+export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+ return downLoadXlsx(`/api/system/user/export`, { params }, `user_${new Date().getTime()}.xlsx`);
+}
+
+// 用户状态修改
+export function changeUserStatus(userId: number, status: string) {
+ const data = {
+ userId,
+ status
+ }
+ return request<API.Result>('/api/system/user/changeStatus', {
+ method: 'put',
+ data: data
+ })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+ return request('/api/system/user/profile', {
+ method: 'get'
+ })
+}
+
+export function updateUserProfile(data: API.CurrentUser) {
+ return request<API.Result>('/api/system/user/profile', {
+ method: 'put',
+ data: data
+ })
+}
+
+// 用户密码重置
+export function resetUserPwd(userId: number, password: string) {
+ const data = {
+ userId,
+ password
+ }
+ return request<API.Result>('/api/system/user/resetPwd', {
+ method: 'put',
+ data: data
+ })
+}
+
+// 用户t个人密码重置
+export function updateUserPwd(oldPassword: string, newPassword: string) {
+ const data = {
+ oldPassword,
+ newPassword
+ }
+ return request<API.Result>('/api/system/user/profile/updatePwd', {
+ method: 'put',
+ params: data
+ })
+}
+
+// 用户头像上传
+export function uploadAvatar(data: any) {
+ return request('/api/system/user/profile/avatar', {
+ method: 'post',
+ data: data
+ })
+}
+
+
+// 查询授权角色
+export function getAuthRole(userId: number) {
+ return request('/system/user/authRole/' + userId, {
+ method: 'get'
+ })
+}
+
+// 保存授权角色
+export function updateAuthRole(data: Record<string, any>) {
+ return request('/system/user/authRole', {
+ method: 'put',
+ params: data
+ })
+}
+
+// 获取数据列表
+export function getDeptTree(params: any): Promise<DataNode[]> {
+ return new Promise((resolve) => {
+ request(`/api/system/user/deptTree`, {
+ method: 'get',
+ params,
+ }).then((res: any) => {
+ if (res && res.code === 200) {
+ const treeData = formatTreeData(res.data);
+ resolve(treeData);
+ } else {
+ resolve([]);
+ }
+ });
+ });
+}
diff --git a/src/services/typings.d.ts b/src/services/typings.d.ts
new file mode 100644
index 0000000..468320e
--- /dev/null
+++ b/src/services/typings.d.ts
@@ -0,0 +1,192 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+declare namespace API {
+
+ interface PageInfo {
+ current?: number;
+ pageSize?: number;
+ total?: number;
+ list?: Array<Record<string, any>>;
+ }
+
+ interface PageInfo_UserInfo_ {
+ current?: number;
+ pageSize?: number;
+ total?: number;
+ list?: Array<UserInfo>;
+ }
+
+ interface Result {
+ code: number;
+ msg: string;
+ data?: Record<string, any>;
+ }
+
+ interface Result_PageInfo_UserInfo__ {
+ code: number;
+ msg: string;
+ data?: PageInfo_UserInfo_;
+ }
+ interface UserInfoResult {
+ code?: number;
+ msg?: string;
+ user: UserInfo;
+ permissions: any;
+ roles: any;
+ }
+
+ interface Result_string_ {
+ success?: boolean;
+ errorMessage?: string;
+ data?: string;
+ }
+
+ type UserGenderEnum = 'MALE' | 'FEMALE';
+
+ interface UserInfo {
+ userId?: string;
+ userName?: string;
+ nickName?: string;
+ avatar?: string;
+ sex?: string;
+ email?: string;
+ gender?: UserGenderEnum;
+ unreadCount: number;
+ address?: string;
+ phonenumber?: string;
+ dept?: Dept;
+ roles?: Role[];
+ permissions: string[];
+ }
+
+ interface UserInfoVO {
+ name?: string;
+ /** nick */
+ nickName?: string;
+ /** email */
+ email?: string;
+ }
+
+ type definitions_0 = null;
+
+ type MenuItemMeta = {
+ title: string;
+ icon: string;
+ noCache: boolean;
+ link: string;
+ };
+
+ type RoutersMenuItem = {
+ alwaysShow?: boolean;
+ children?: RoutersMenuItem[];
+ component?: string;
+ hidden?: boolean;
+ meta: MenuItemMeta;
+ name: string;
+ path: string;
+ redirect?: string;
+ [key: string]: any;
+ };
+ interface GetRoutersResult {
+ code: number;
+ msg: string;
+ data: RoutersMenuItem[];
+ }
+
+ type NoticeIconList = {
+ data?: NoticeIconItem[];
+ /** 列表的内容总数 */
+ total?: number;
+ success?: boolean;
+ };
+
+ type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+ type NoticeIconItem = {
+ id?: string;
+ extra?: string;
+ key?: string;
+ read?: boolean;
+ avatar?: string;
+ title?: string;
+ status?: string;
+ datetime?: string;
+ description?: string;
+ type?: NoticeIconItemType;
+ };
+
+ export type MenuType = {
+ menuId: number;
+ menuName: string;
+ parentId: string;
+ orderNum: number;
+ path: string;
+ component: string;
+ isFrame: number;
+ isCache: number;
+ menuType: string;
+ visible: string;
+ status: string;
+ perms: string;
+ icon: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ };
+
+ export type MenuListParams = {
+ menuId?: string;
+ menuName?: string;
+ parentId?: string;
+ orderNum?: string;
+ path?: string;
+ component?: string;
+ isFrame?: string;
+ isCache?: string;
+ menuType?: string;
+ visible?: string;
+ status?: string;
+ perms?: string;
+ icon?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ };
+
+ export type DictTypeType = {
+ dictId: number;
+ dictName: string;
+ dictType: string;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ };
+
+ export type DictTypeListParams = {
+ dictId?: string;
+ dictName?: string;
+ dictType?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ };
+}
diff --git a/src/types/monitor/cache.d.ts b/src/types/monitor/cache.d.ts
new file mode 100644
index 0000000..bfbab1a
--- /dev/null
+++ b/src/types/monitor/cache.d.ts
@@ -0,0 +1,153 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+declare namespace API.Monitor {
+
+ export type CommandInfo = {
+ name: string;
+ value: string;
+ };
+
+ export type CacheRuntimeInfo = {
+ active_defrag_hits: string;
+ active_defrag_key_hits: string;
+ active_defrag_key_misses: string;
+ active_defrag_misses: string;
+ active_defrag_running: string;
+ allocator_active: string;
+ allocator_allocated: string;
+ allocator_frag_bytes: string;
+ allocator_frag_ratio: string;
+ allocator_resident: string;
+ allocator_rss_bytes: string;
+ allocator_rss_ratio: string;
+ aof_current_rewrite_time_sec: string;
+ aof_enabled: string;
+ aof_last_bgrewrite_status: string;
+ aof_last_cow_size: string;
+ aof_last_rewrite_time_sec: string;
+ aof_last_write_status: string;
+ aof_rewrite_in_progress: string;
+ aof_rewrite_scheduled: string;
+ arch_bits: string;
+ atomicvar_api: string;
+ blocked_clients: string;
+ client_recent_max_input_buffer: string;
+ client_recent_max_output_buffer: string;
+ cluster_enabled: string;
+ config_file: string;
+ configured_hz: string;
+ connected_clients: string;
+ connected_slaves: string;
+ db0: string;
+ db1: string;
+ evicted_keys: string;
+ executable: string;
+ expired_keys: string;
+ expired_stale_perc: string;
+ expired_time_cap_reached_count: string;
+ gcc_version: string;
+ hz: string;
+ instantaneous_input_kbps: number;
+ instantaneous_ops_per_sec: string;
+ instantaneous_output_kbps: number;
+ keyspace_hits: string;
+ keyspace_misses: string;
+ latest_fork_usec: string;
+ lazyfree_pending_objects: string;
+ loading: string;
+ lru_clock: string;
+ master_repl_offset: string;
+ master_replid: string;
+ master_replid2: string;
+ maxmemory: string;
+ maxmemory_human: string;
+ maxmemory_policy: string;
+ mem_allocator: string;
+ mem_aof_buffer: string;
+ mem_clients_normal: string;
+ mem_clients_slaves: string;
+ mem_fragmentation_bytes: string;
+ mem_fragmentation_ratio: string;
+ mem_not_counted_for_evict: string;
+ mem_replication_backlog: string;
+ migrate_cached_sockets: string;
+ multiplexing_api: string;
+ number_of_cached_scripts: string;
+ os: string;
+ process_id: string;
+ pubsub_channels: string;
+ pubsub_patterns: string;
+ rdb_bgsave_in_progress: string;
+ rdb_changes_since_last_save: string;
+ rdb_current_bgsave_time_sec: string;
+ rdb_last_bgsave_status: string;
+ rdb_last_bgsave_time_sec: string;
+ rdb_last_cow_size: string;
+ rdb_last_save_time: string;
+ redis_build_id: string;
+ redis_git_dirty: string;
+ redis_git_sha1: string;
+ redis_mode: string;
+ redis_version: string;
+ rejected_connections: string;
+ repl_backlog_active: string;
+ repl_backlog_first_byte_offset: string;
+ repl_backlog_histlen: string;
+ repl_backlog_size: string;
+ role: string;
+ rss_overhead_bytes: string;
+ rss_overhead_ratio: string;
+ run_id: string;
+ second_repl_offset: string;
+ slave_expires_tracked_keys: string;
+ sync_full: string;
+ sync_partial_err: string;
+ sync_partial_ok: string;
+ tcp_port: string;
+ total_commands_processed: string;
+ total_connections_received: string;
+ total_net_input_bytes: string;
+ total_net_output_bytes: string;
+ total_system_memory: number;
+ total_system_memory_human: string;
+ uptime_in_days: string;
+ uptime_in_seconds: string;
+ used_cpu_sys: string;
+ used_cpu_sys_children: string;
+ used_cpu_user: string;
+ used_cpu_user_children: number;
+ used_memory: number;
+ used_memory_dataset: number;
+ used_memory_dataset_perc: string;
+ used_memory_human: string;
+ used_memory_lua: string;
+ used_memory_lua_human: string;
+ used_memory_overhead: string;
+ used_memory_peak: string;
+ used_memory_peak_human: string;
+ used_memory_peak_perc: string;
+ used_memory_rss: string;
+ used_memory_rss_human: string;
+ used_memory_scripts: string;
+ used_memory_scripts_human: string;
+ used_memory_startup: string;
+ };
+
+ export type CacheInfo = {
+ commandStats: CommandInfo[];
+ dbSize: number;
+ info: CacheRuntimeInfo;
+ };
+
+ export type CacheInfoResult = {
+ data: CacheInfo;
+ code: number;
+ msg: string;
+ };
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/cacheList.d.ts b/src/types/monitor/cacheList.d.ts
new file mode 100644
index 0000000..e2de8cd
--- /dev/null
+++ b/src/types/monitor/cacheList.d.ts
@@ -0,0 +1,36 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2022/06/27
+ *
+ * */
+
+declare namespace API.Monitor {
+
+ export type CacheContent = {
+ cacheKey: string;
+ cacheName: string;
+ cacheValue: string;
+ remark: string;
+ };
+
+ export type CacheNamesResponse = {
+ data: CacheContent[];
+ code: number;
+ msg: string;
+ };
+
+ export type CacheKeysResponse = {
+ data: string[];
+ code: number;
+ msg: string;
+ };
+
+ export type CacheValueResponse = {
+ data: CacheContent;
+ code: number;
+ msg: string;
+ };
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/job.d.ts b/src/types/monitor/job.d.ts
new file mode 100644
index 0000000..5b1c965
--- /dev/null
+++ b/src/types/monitor/job.d.ts
@@ -0,0 +1,58 @@
+/**
+ * 定时任务调度 Model Declare
+ *
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+ export interface Job {
+ jobId: number;
+ jobName: string;
+ jobGroup: string;
+ invokeTarget: string;
+ cronExpression: string;
+ misfirePolicy: string;
+ concurrent: string;
+ nextValidTime: string;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface JobListParams {
+ jobId?: string;
+ jobName?: string;
+ jobGroup?: string;
+ invokeTarget?: string;
+ cronExpression?: string;
+ misfirePolicy?: string;
+ concurrent?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface JobInfoResult {
+ code: number;
+ msg: string;
+ data: Job;
+ }
+
+ export interface JobPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Job>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/jobLog.d.ts b/src/types/monitor/jobLog.d.ts
new file mode 100644
index 0000000..e62cb9f
--- /dev/null
+++ b/src/types/monitor/jobLog.d.ts
@@ -0,0 +1,47 @@
+/**
+ * 定时任务调度日志 Model Declare
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+ export interface JobLog {
+ jobLogId: number;
+ jobName: string;
+ jobGroup: string;
+ invokeTarget: string;
+ jobMessage: string;
+ status: string;
+ exceptionInfo: string;
+ createTime: Date;
+ }
+
+ export interface JobLogListParams {
+ jobLogId?: string;
+ jobName?: string;
+ jobGroup?: string;
+ invokeTarget?: string;
+ jobMessage?: string;
+ status?: string;
+ exceptionInfo?: string;
+ createTime?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface JobLogInfoResult {
+ code: number;
+ msg: string;
+ data: JobLog;
+ }
+
+ export interface JobLogPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<JobLog>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/logininfor.d.ts b/src/types/monitor/logininfor.d.ts
new file mode 100644
index 0000000..ef9208a
--- /dev/null
+++ b/src/types/monitor/logininfor.d.ts
@@ -0,0 +1,43 @@
+
+declare namespace API.Monitor {
+
+ export interface Logininfor {
+ infoId: number;
+ userName: string;
+ ipaddr: string;
+ loginLocation: string;
+ browser: string;
+ os: string;
+ status: string;
+ msg: string;
+ loginTime: Date;
+ }
+
+ export interface LogininforListParams {
+ infoId?: string;
+ userName?: string;
+ ipaddr?: string;
+ loginLocation?: string;
+ browser?: string;
+ os?: string;
+ status?: string;
+ msg?: string;
+ loginTime?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface LogininforInfoResult {
+ code: number;
+ msg: string;
+ data: Logininfor;
+ }
+
+ export interface LogininforPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Logininfor>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/online.d.ts b/src/types/monitor/online.d.ts
new file mode 100644
index 0000000..5b8e0dd
--- /dev/null
+++ b/src/types/monitor/online.d.ts
@@ -0,0 +1,55 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+declare namespace API.Monitor {
+
+ export type OnlineUserType = {
+ tokenId: string;
+ userName: string;
+ ipaddr: string;
+ loginLocation: string;
+ browser: string;
+ os: string;
+ deptName: string;
+ loginTime: string;
+ };
+
+ export type OnlineUserListPagination = {
+ total: number;
+ pageSize: number;
+ current: number;
+ };
+
+ export type OnlineUserListData = {
+ list: OnlineUserType[];
+ pagination: Partial<OnlineUserListPagination>;
+ };
+
+ export type OnlineUserListParams = {
+ tokenId?: string;
+ userName?: string;
+ ipaddr?: string;
+ loginLocation?: string;
+ browser?: string;
+ os?: string;
+ deptName?: string;
+ loginTime?: string;
+ pageSize?: string;
+ current?: string;
+ pageNum?: string;
+ filter?: string;
+ sorter?: string;
+ };
+
+ export interface OnlineUserPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<OnlineUser>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/operlog.d.ts b/src/types/monitor/operlog.d.ts
new file mode 100644
index 0000000..96ee27e
--- /dev/null
+++ b/src/types/monitor/operlog.d.ts
@@ -0,0 +1,57 @@
+
+declare namespace API.Monitor {
+
+ export interface Operlog {
+ operId: number;
+ title: string;
+ businessType: number;
+ method: string;
+ requestMethod: string;
+ operatorType: number;
+ operName: string;
+ deptName: string;
+ operUrl: string;
+ operIp: string;
+ operLocation: string;
+ operParam: string;
+ jsonResult: string;
+ status: number;
+ errorMsg: string;
+ operTime: Date;
+ }
+
+ export interface OperlogListParams {
+ operId?: string;
+ title?: string;
+ businessType?: string;
+ method?: string;
+ requestMethod?: string;
+ operatorType?: string;
+ operName?: string;
+ deptName?: string;
+ operUrl?: string;
+ operIp?: string;
+ operLocation?: string;
+ operParam?: string;
+ jsonResult?: string;
+ status?: string;
+ errorMsg?: string;
+ operTime?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface OperlogInfoResult {
+ code: number;
+ msg: string;
+ data: Operlog;
+ }
+
+ export interface OperlogPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Operlog>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/monitor/server.d.ts b/src/types/monitor/server.d.ts
new file mode 100644
index 0000000..8a32d16
--- /dev/null
+++ b/src/types/monitor/server.d.ts
@@ -0,0 +1,84 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2023/02/07
+ *
+ * */
+
+
+declare namespace API.Monitor {
+
+ export type CpuInfoType = {
+ cpuNum: string;
+ total: string;
+ sys: string;
+ used: string;
+ wait: string;
+ free: string;
+ };
+
+ export type MemInfoType = {
+ total: string;
+ used: string;
+ usage: string;
+ free: string;
+ };
+
+ export type SysInfoType = {
+ computerIp: string;
+ computerName: string;
+ osArch: string;
+ osName: string;
+ userDir: string;
+ };
+
+ export type JvmInfoType = {
+ free: string;
+ home: string;
+ max: string;
+ name: string;
+ runTime: string;
+ startTime: string;
+ total: string;
+ usage: string;
+ used: string;
+ version: string;
+ };
+
+ export type DiskInfoType = {
+ dirName: string;
+ free: string;
+ sysTypeName: string;
+ total: string;
+ typeName: string;
+ usage: string;
+ used: string;
+ };
+
+ export type ServerInfoType = {
+ cpu: CpuInfoType;
+ mem: MemInfoType;
+ jvm: JvmInfoType;
+ sys: SysInfoType;
+ sysFiles: DiskInfoType[];
+ };
+
+ export type ServerInfoResponseType = {
+ data: ServerInfoType;
+ code: number;
+ msg: string;
+ };
+
+ export type CpuRowType = {
+ name: string;
+ value: string;
+ };
+
+ export type MemRowType = {
+ name: string;
+ mem: string;
+ jvm: string;
+ };
+
+}
\ No newline at end of file
diff --git a/src/types/system/config.d.ts b/src/types/system/config.d.ts
new file mode 100644
index 0000000..7e7e43e
--- /dev/null
+++ b/src/types/system/config.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+ export interface Config {
+ configId: number;
+ configName: string;
+ configKey: string;
+ configValue: string;
+ configType: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface ConfigListParams {
+ configId?: string;
+ configName?: string;
+ configKey?: string;
+ configValue?: string;
+ configType?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface ConfigInfoResult {
+ code: number;
+ msg: string;
+ data: Config;
+ }
+
+ export interface ConfigPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Config>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/system/dept.d.ts b/src/types/system/dept.d.ts
new file mode 100644
index 0000000..14ad274
--- /dev/null
+++ b/src/types/system/dept.d.ts
@@ -0,0 +1,53 @@
+
+declare namespace API.System {
+
+ interface Dept {
+ deptId: number;
+ parentId: number;
+ ancestors: string;
+ deptName: string;
+ orderNum: number;
+ leader: string;
+ phone: string;
+ email: string;
+ status: string;
+ delFlag: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ }
+
+ export interface DeptListParams {
+ deptId?: string;
+ parentId?: string;
+ ancestors?: string;
+ deptName?: string;
+ orderNum?: string;
+ leader?: string;
+ phone?: string;
+ email?: string;
+ status?: string;
+ delFlag?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface DeptInfoResult {
+ code: number;
+ msg: string;
+ data: Dept;
+ }
+
+ export interface DeptPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ data: Array<Dept>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/system/dict-data.d.ts b/src/types/system/dict-data.d.ts
new file mode 100644
index 0000000..30373f5
--- /dev/null
+++ b/src/types/system/dict-data.d.ts
@@ -0,0 +1,53 @@
+declare namespace API.System {
+ interface DictData {
+ dictCode: number;
+ dictSort: number;
+ dictLabel: string;
+ dictValue: string;
+ dictType: string;
+ cssClass: string;
+ listClass: string;
+ isDefault: string;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface DictDataListParams {
+ dictCode?: string;
+ dictSort?: string;
+ dictLabel?: string;
+ dictValue?: string;
+ dictType?: string;
+ cssClass?: string;
+ listClass?: string;
+ isDefault?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ }
+
+ export interface DictDataInfoResult {
+ current: number;
+ pageSize: number;
+ total: number;
+ data: DictData;
+ }
+
+ export interface DictDataPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<DictData>;
+ }
+}
diff --git a/src/types/system/dict.d.ts b/src/types/system/dict.d.ts
new file mode 100644
index 0000000..eef354a
--- /dev/null
+++ b/src/types/system/dict.d.ts
@@ -0,0 +1,48 @@
+declare namespace API.System {
+ export interface DictType {
+ dictId: number;
+ dictName: string;
+ dictType: string;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface DictTypeListParams {
+ dictId?: string;
+ dictName?: string;
+ dictType?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ }
+
+ export interface DictTypeInfoResult {
+ code: number;
+ msg: string;
+ data: Dict;
+ }
+
+ export interface DictTypePageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Dict>;
+ }
+
+ export interface DictTypeResult {
+ code: number;
+ msg: string;
+ data: Array<Dict>;
+ }
+}
diff --git a/src/types/system/menu.d.ts b/src/types/system/menu.d.ts
new file mode 100644
index 0000000..15e68ce
--- /dev/null
+++ b/src/types/system/menu.d.ts
@@ -0,0 +1,66 @@
+
+declare namespace API.System {
+
+ interface Menu {
+ menuId: number;
+ menuName: string;
+ parentId: number;
+ orderNum: number;
+ path: string;
+ component: string;
+ query: string;
+ isFrame: number;
+ isCache: number;
+ menuType: string;
+ visible: string;
+ status: string;
+ perms: string;
+ icon: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface MenuListParams {
+ menuId?: string;
+ menuName?: string;
+ parentId?: string;
+ orderNum?: string;
+ path?: string;
+ component?: string;
+ query?: string;
+ isFrame?: string;
+ isCache?: string;
+ menuType?: string;
+ visible?: string;
+ status?: string;
+ perms?: string;
+ icon?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ }
+
+ export interface MenuInfoResult {
+ current: number;
+ pageSize: number;
+ total: number;
+ data: Menu;
+ }
+
+ export interface MenuPageResult {
+ current: number;
+ pageSize: number;
+ total: number;
+ data: Array<Menu>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/system/notice.d.ts b/src/types/system/notice.d.ts
new file mode 100644
index 0000000..89a77ee
--- /dev/null
+++ b/src/types/system/notice.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+ export interface Notice {
+ noticeId: number;
+ noticeTitle: string;
+ noticeType: string;
+ noticeContent: string;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface NoticeListParams {
+ noticeId?: string;
+ noticeTitle?: string;
+ noticeType?: string;
+ noticeContent?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface NoticeInfoResult {
+ code: number;
+ msg: string;
+ data: Notice;
+ }
+
+ export interface NoticePageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Notice>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/system/post.d.ts b/src/types/system/post.d.ts
new file mode 100644
index 0000000..772140c
--- /dev/null
+++ b/src/types/system/post.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+ interface Post {
+ postId: number;
+ postCode: string;
+ postName: string;
+ postSort: number;
+ status: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface PostListParams {
+ postId?: string;
+ postCode?: string;
+ postName?: string;
+ postSort?: string;
+ status?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface PostInfoResult {
+ code: number;
+ msg: string;
+ data: Post;
+ }
+
+ export interface PostPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Post>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/system/role.d.ts b/src/types/system/role.d.ts
new file mode 100644
index 0000000..1034e47
--- /dev/null
+++ b/src/types/system/role.d.ts
@@ -0,0 +1,65 @@
+
+declare namespace API.System {
+
+ interface Role {
+ roleId: number;
+ roleName: string;
+ roleKey: string;
+ roleSort: number;
+ dataScope: string;
+ menuCheckStrictly: number;
+ deptCheckStrictly: number;
+ status: string;
+ delFlag: string;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface RoleListParams {
+ roleId?: string;
+ roleName?: string;
+ roleKey?: string;
+ roleSort?: string;
+ dataScope?: string;
+ menuCheckStrictly?: string;
+ deptCheckStrictly?: string;
+ status?: string;
+ delFlag?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ current?: string;
+ }
+
+ export interface RoleInfoResult {
+ code: number;
+ msg: string;
+ data: Role;
+ }
+
+ export interface RolePageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<Role>;
+ }
+
+ export type RoleMenuNode = {
+ id: number|string;
+ label: string;
+ children?: Array<RoleMenuNode>;
+ }
+ export interface RoleMenuResult {
+ code: number;
+ msg: string;
+ checkedKeys: number[];
+ menus: Array<RoleMenuNode>;
+ }
+
+}
diff --git a/src/types/system/user.d.ts b/src/types/system/user.d.ts
new file mode 100644
index 0000000..5751956
--- /dev/null
+++ b/src/types/system/user.d.ts
@@ -0,0 +1,71 @@
+
+declare namespace API.System {
+
+ interface User {
+ userId: number;
+ deptId: number;
+ userName: string;
+ nickName: string;
+ userType: string;
+ email: string;
+ phonenumber: string;
+ sex: string;
+ avatar: string;
+ password: string;
+ status: string;
+ delFlag: string;
+ loginIp: string;
+ loginDate: Date;
+ createBy: string;
+ createTime: Date;
+ updateBy: string;
+ updateTime: Date;
+ remark: string;
+ }
+
+ export interface UserListParams {
+ userId?: string;
+ deptId?: string;
+ userName?: string;
+ nickName?: string;
+ userType?: string;
+ email?: string;
+ phonenumber?: string;
+ sex?: string;
+ avatar?: string;
+ password?: string;
+ status?: string;
+ delFlag?: string;
+ loginIp?: string;
+ loginDate?: string;
+ createBy?: string;
+ createTime?: string;
+ updateBy?: string;
+ updateTime?: string;
+ remark?: string;
+ pageSize?: string;
+ currentPage?: string;
+ filter?: string;
+ sorter?: string;
+ }
+
+ export interface UserInfoResult {
+ current: number;
+ pageSize: number;
+ total: number;
+ data: User;
+ posts: [];
+ postIds: any;
+ roleId: number;
+ roleIds: [];
+ roles: [];
+ }
+
+ export interface UserPageResult {
+ code: number;
+ msg: string;
+ total: number;
+ rows: Array<User>;
+ }
+
+}
\ No newline at end of file
diff --git a/src/types/typings.d.ts b/src/types/typings.d.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/types/typings.d.ts
diff --git a/src/typings.d.ts b/src/typings.d.ts
new file mode 100644
index 0000000..742f70c
--- /dev/null
+++ b/src/typings.d.ts
@@ -0,0 +1,20 @@
+declare module 'slash2';
+declare module '*.css';
+declare module '*.less';
+declare module '*.scss';
+declare module '*.sass';
+declare module '*.svg';
+declare module '*.png';
+declare module '*.jpg';
+declare module '*.jpeg';
+declare module '*.gif';
+declare module '*.bmp';
+declare module '*.tiff';
+declare module 'omit.js';
+declare module 'numeral';
+declare module '@antv/data-set';
+declare module 'mockjs';
+declare module 'react-fittext';
+declare module 'bizcharts-plugin-slider';
+
+declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
diff --git a/src/utils/IconUtil.ts b/src/utils/IconUtil.ts
new file mode 100644
index 0000000..31102ee
--- /dev/null
+++ b/src/utils/IconUtil.ts
@@ -0,0 +1,20 @@
+import * as AntdIcons from '@ant-design/icons';
+import React from 'react';
+
+const allIcons: Record<string, any> = AntdIcons;
+
+export function getIcon(name: string): React.ReactNode | string {
+ const icon = allIcons[name];
+ return icon || '';
+}
+
+export function createIcon(icon: string | any): React.ReactNode | string {
+ if (typeof icon === 'object') {
+ return icon;
+ }
+ const ele = allIcons[icon];
+ if (ele) {
+ return React.createElement(allIcons[icon]);
+ }
+ return '';
+}
diff --git a/src/utils/downloadfile.ts b/src/utils/downloadfile.ts
new file mode 100644
index 0000000..5d56cda
--- /dev/null
+++ b/src/utils/downloadfile.ts
@@ -0,0 +1,63 @@
+import { request } from '@umijs/max';
+
+const mimeMap = {
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ zip: 'application/zip',
+};
+
+/**
+ * 解析blob响应内容并下载
+ * @param {*} res blob响应内容
+ * @param {String} mimeType MIME类型
+ */
+export function resolveBlob(res: any, mimeType: string) {
+ const aLink = document.createElement('a');
+ const blob = new Blob([res.data], { type: mimeType });
+ // //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
+ const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
+ // console.log(res);
+ const contentDisposition = decodeURI(res.headers['content-disposition']);
+ const result = patt.exec(contentDisposition);
+ let fileName = result ? result[1] : 'file';
+ fileName = fileName.replace(/"/g, '');
+ aLink.style.display = 'none';
+ aLink.href = URL.createObjectURL(blob);
+ aLink.setAttribute('download', fileName); // 设置下载文件名称
+ document.body.appendChild(aLink);
+ aLink.click();
+ URL.revokeObjectURL(aLink.href); // 清除引用
+ document.body.removeChild(aLink);
+}
+
+export function downLoadZip(url: string) {
+ request(url, {
+ method: 'GET',
+ responseType: 'blob',
+ getResponse: true,
+ }).then((res) => {
+ resolveBlob(res, mimeMap.zip);
+ });
+}
+
+export async function downLoadXlsx(url: string, params: any, fileName: string) {
+ return request(url, {
+ ...params,
+ method: 'POST',
+ responseType: 'blob',
+ }).then((data) => {
+ const aLink = document.createElement('a');
+ const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+ aLink.style.display = 'none';
+ aLink.href = URL.createObjectURL(blob);
+ aLink.setAttribute('download', fileName); // 设置下载文件名称
+ document.body.appendChild(aLink);
+ aLink.click();
+ URL.revokeObjectURL(aLink.href); // 清除引用
+ document.body.removeChild(aLink);
+ });
+}
+
+
+export function download(fileName: string) {
+ window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`;
+}
diff --git a/src/utils/options.ts b/src/utils/options.ts
new file mode 100644
index 0000000..79ee803
--- /dev/null
+++ b/src/utils/options.ts
@@ -0,0 +1,12 @@
+import { DictValueEnumObj } from "@/components/DictTag";
+import { ProSchemaValueEnumObj, ProSchemaValueEnumType } from "@ant-design/pro-components";
+
+export function getValueEnumLabel(options: DictValueEnumObj | ProSchemaValueEnumObj, val: string | number | undefined, defaultValue?: string) {
+ if (val !== undefined) {
+ const data = options[val] as ProSchemaValueEnumType;
+ if(data) {
+ return data.text;
+ }
+ }
+ return defaultValue?defaultValue:val;
+}
diff --git a/src/utils/permission.ts b/src/utils/permission.ts
new file mode 100644
index 0000000..2b39e48
--- /dev/null
+++ b/src/utils/permission.ts
@@ -0,0 +1,67 @@
+// /**
+// * 字符权限校验
+// * @param {Array} value 校验值
+// * @returns {Boolean}
+
+// import Role = API.System.Role;
+
+export function matchPerms(permissions: string[], value: string[]) {
+ if (value && value instanceof Array && value.length > 0) {
+ const permissionDatas = value;
+ const all_permission = '*:*:*';
+ const hasPermission = permissions.some((permission) => {
+ return all_permission === permission || permissionDatas.includes(permission);
+ });
+ if (!hasPermission) {
+ return false;
+ }
+ return true;
+ }
+ console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+ return false;
+}
+
+export function matchPerm(permissions: string[], value: string) {
+ if (value && value.length > 0) {
+ const permissionDatas = value;
+ const all_permission = '*:*:*';
+ const hasPermission = permissions.some((permission) => {
+ return all_permission === permission || permissionDatas === permission;
+ });
+ if (!hasPermission) {
+ return false;
+ }
+ return true;
+ }
+ console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+ return false;
+}
+
+export function matchPermission(permissions: string[] | undefined, value: any): boolean {
+ if (permissions === undefined) return false;
+ const type = typeof value;
+ if (type === 'string') {
+ return matchPerm(permissions, value);
+ }
+ return matchPerms(permissions, value);
+}
+
+/**
+ * 角色权限校验
+ * @param roles
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkRole(roles: Role[] | undefined, value: string[]) {
+ if (roles && value && value.length > 0) {
+ for (let i = 0; i < roles?.length; i++) {
+ for (let j = 0; j < value?.length; j++) {
+ if (value[j] === roles[i].roleKey) {
+ return true;
+ }
+ }
+ }
+ }
+ console.error(`need roles! Like checkRole="['admin','editor']"`);
+ return false;
+}
diff --git a/src/utils/tree.ts b/src/utils/tree.ts
new file mode 100644
index 0000000..7f1a2e5
--- /dev/null
+++ b/src/utils/tree.ts
@@ -0,0 +1,95 @@
+import { DataNode } from 'antd/es/tree';
+import { parse } from 'querystring';
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param name
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param parentName
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function buildTreeData(
+ data: any[],
+ id: string,
+ name: string,
+ parentId: string,
+ parentName: string,
+ children: string,
+) {
+ const config = {
+ id: id || 'id',
+ name: name || 'name',
+ parentId: parentId || 'parentId',
+ parentName: parentName || 'parentName',
+ childrenList: children || 'children',
+ };
+
+ const childrenListMap: any[] = [];
+ const nodeIds: any[] = [];
+ const tree: any[] = [];
+ data.forEach((item) => {
+ const d = item;
+ const pId = d[config.parentId];
+ if (!childrenListMap[pId]) {
+ childrenListMap[pId] = [];
+ }
+ d.key = d[config.id];
+ d.title = d[config.name];
+ d.value = d[config.id];
+ d[config.childrenList] = null;
+ nodeIds[d[config.id]] = d;
+ childrenListMap[pId].push(d);
+ });
+
+ data.forEach((item: any) => {
+ const d = item;
+ const pId = d[config.parentId];
+ if (!nodeIds[pId]) {
+ d[config.parentName] = '';
+ tree.push(d);
+ }
+ });
+
+ function adaptToChildrenList(item: any) {
+ const o = item;
+ if (childrenListMap[o[config.id]]) {
+ if (!o[config.childrenList]) {
+ o[config.childrenList] = [];
+ }
+ o[config.childrenList] = childrenListMap[o[config.id]];
+ }
+ if (o[config.childrenList]) {
+ o[config.childrenList].forEach((child: any) => {
+ const c = child;
+ c[config.parentName] = o[config.name];
+ adaptToChildrenList(c);
+ });
+ }
+ }
+
+ tree.forEach((t: any) => {
+ adaptToChildrenList(t);
+ });
+
+ return tree;
+}
+
+export const getPageQuery = () => parse(window.location.href.split('?')[1]);
+
+export function formatTreeData(arrayList: any): DataNode[] {
+ const treeSelectData: DataNode[] = arrayList.map((item: any) => {
+ const node: DataNode = {
+ id: item.id,
+ title: item.label,
+ key: `${item.id}`,
+ value: item.id,
+ } as DataNode;
+ if (item.children) {
+ node.children = formatTreeData(item.children);
+ }
+ return node;
+ });
+ return treeSelectData;
+}
diff --git a/tests/setupTests.jsx b/tests/setupTests.jsx
new file mode 100644
index 0000000..952561d
--- /dev/null
+++ b/tests/setupTests.jsx
@@ -0,0 +1,64 @@
+const localStorageMock = {
+ getItem: jest.fn(),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+ clear: jest.fn(),
+};
+
+global.localStorage = localStorageMock;
+
+Object.defineProperty(URL, 'createObjectURL', {
+ writable: true,
+ value: jest.fn(),
+});
+
+class Worker {
+ constructor(stringUrl) {
+ this.url = stringUrl;
+ this.onmessage = () => {};
+ }
+
+ postMessage(msg) {
+ this.onmessage(msg);
+ }
+}
+window.Worker = Worker;
+
+/* eslint-disable global-require */
+if (typeof window !== 'undefined') {
+ // ref: https://github.com/ant-design/ant-design/issues/18774
+ if (!window.matchMedia) {
+ Object.defineProperty(global.window, 'matchMedia', {
+ writable: true,
+ configurable: true,
+ value: jest.fn(() => ({
+ matches: false,
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ })),
+ });
+ }
+ if (!window.matchMedia) {
+ Object.defineProperty(global.window, 'matchMedia', {
+ writable: true,
+ configurable: true,
+ value: jest.fn((query) => ({
+ matches: query.includes('max-width'),
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ })),
+ });
+ }
+}
+const errorLog = console.error;
+Object.defineProperty(global.window.console, 'error', {
+ writable: true,
+ configurable: true,
+ value: (...rest) => {
+ const logStr = rest.join('');
+ if (logStr.includes('Warning: An update to %s inside a test was not wrapped in act(...)')) {
+ return;
+ }
+ errorLog(...rest);
+ },
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c2ea9bc
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "types": ["node"],
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "jsx": "preserve",
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "baseUrl": "./",
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+ "strict": true,
+ "resolveJsonModule": true,
+ "allowSyntheticDefaultImports": true,
+ "paths": {
+ "@/*": ["./src/*"],
+ "@@/*": ["./src/.umi/*"],
+ "@@test/*": ["./src/.umi-test/*"]
+ }
+ },
+ "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"]
+}
diff --git a/types/cache/cache.json b/types/cache/cache.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/types/cache/cache.json
@@ -0,0 +1 @@
+{}
diff --git a/types/cache/login.cache.json b/types/cache/login.cache.json
new file mode 100644
index 0000000..81109b4
--- /dev/null
+++ b/types/cache/login.cache.json
@@ -0,0 +1,386 @@
+{
+ "GET /api/currentUser": {
+ "res": {
+ "data": {
+ "name": "Serati Ma",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
+ "userid": "00000001",
+ "email": "antdesign@alipay.com",
+ "signature": "海纳百川,有容乃大",
+ "title": "交互专家",
+ "group": "蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED",
+ "tags": [
+ {
+ "key": "0",
+ "label": "很有想法的"
+ },
+ {
+ "key": "1",
+ "label": "专注设计"
+ },
+ {
+ "key": "2",
+ "label": "辣~"
+ },
+ {
+ "key": "3",
+ "label": "大长腿"
+ },
+ {
+ "key": "4",
+ "label": "川妹子"
+ },
+ {
+ "key": "5",
+ "label": "海纳百川"
+ }
+ ],
+ "notifyCount": 12,
+ "unreadCount": 11,
+ "country": "China",
+ "geographic": {
+ "province": {
+ "label": "浙江省",
+ "key": "330000"
+ },
+ "city": {
+ "label": "杭州市",
+ "key": "330100"
+ }
+ },
+ "address": "西湖区工专路 77 号",
+ "phone": "0752-268888888"
+ }
+ },
+ "query": {
+ "token ": " 123"
+ },
+ "payload": {},
+ "types": "/** GET /api/currentUser */\nexport type GET_API_CURRENT_USER_QUERY = {\n /** example: 123 */\n token : string\n}\n \n\nexport type GET_API_CURRENT_USER_PAYLOAD = {\n \n}\n \n\nexport type GET_API_CURRENT_USER_RES = {\n /** example: {\"name\": \"Serati Ma\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png\", \"userid\": \"00000001\", \"email\": \"antdesign@alipay.com\", \"signature\": \"海纳百川,有容乃大\", \"title\": \"交互专家\", \"group\": \"蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED\", \"tags\": [{\"key\": \"0\", \"label\": \"很有想法的\"}, {\"key\": \"1\", \"label\": \"专注设计\"}, {\"key\": \"2\", \"label\": \"辣~\"}, {\"key\": \"3\", \"label\": \"大长腿\"}, {\"key\": \"4\", \"label\": \"川妹子\"}, {\"key\": \"5\", \"label\": \"海纳百川\"}], \"notifyCount\": 12, \"unreadCount\": 11, \"country\": \"China\", \"geographic\": {\"province\": {\"label\": \"浙江省\", \"key\": \"330000\"}, \"city\": {\"label\": \"杭州市\", \"key\": \"330100\"}}, \"address\": \"西湖区工专路 77 号\", \"phone\": \"0752-268888888\"} */\n data: {\n name: string,\navatar: string,\nuserid: string,\nemail: string,\nsignature: string,\ntitle: string,\ngroup: string,\ntags: {\n key: string,\nlabel: string\n }[],\nnotifyCount: number,\nunreadCount: number,\ncountry: string,\ngeographic: {\n province: {\n label: string,\nkey: string\n },\ncity: {\n label: string,\nkey: string\n }\n },\naddress: string,\nphone: string\n }\n}\n "
+ },
+ "GET /api/rule": {
+ "res": {
+ "data": [
+ {
+ "key": 99,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 99",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 503,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 81
+ },
+ {
+ "key": 98,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 98",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 164,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 12
+ },
+ {
+ "key": 97,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 97",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 174,
+ "status": "1",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 81
+ },
+ {
+ "key": 96,
+ "disabled": true,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 96",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 914,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 7
+ },
+ {
+ "key": 95,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 95",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 698,
+ "status": "2",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 82
+ },
+ {
+ "key": 94,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 94",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 488,
+ "status": "1",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 14
+ },
+ {
+ "key": 93,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 93",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 580,
+ "status": "2",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 77
+ },
+ {
+ "key": 92,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 92",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 244,
+ "status": "3",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 58
+ },
+ {
+ "key": 91,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 91",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 959,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 66
+ },
+ {
+ "key": 90,
+ "disabled": true,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 90",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 958,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 72
+ },
+ {
+ "key": 89,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 89",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 301,
+ "status": "2",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 2
+ },
+ {
+ "key": 88,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 88",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 277,
+ "status": "1",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 12
+ },
+ {
+ "key": 87,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 87",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 810,
+ "status": "1",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 82
+ },
+ {
+ "key": 86,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 86",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 780,
+ "status": "3",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 22
+ },
+ {
+ "key": 85,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 85",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 705,
+ "status": "3",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 12
+ },
+ {
+ "key": 84,
+ "disabled": true,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 84",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 203,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 79
+ },
+ {
+ "key": 83,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 83",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 491,
+ "status": "2",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 59
+ },
+ {
+ "key": 82,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 82",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 73,
+ "status": "0",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 100
+ },
+ {
+ "key": 81,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png",
+ "name": "TradeCode 81",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 406,
+ "status": "3",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 61
+ },
+ {
+ "key": 80,
+ "disabled": false,
+ "href": "https://ant.design",
+ "avatar": "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png",
+ "name": "TradeCode 80",
+ "owner": "曲丽丽",
+ "desc": "这是一段描述",
+ "callNo": 112,
+ "status": "2",
+ "updatedAt": "2022-12-06T05:00:57.040Z",
+ "createdAt": "2022-12-06T05:00:57.040Z",
+ "progress": 20
+ }
+ ],
+ "total": 100,
+ "success": true,
+ "pageSize": 20,
+ "current": 1
+ },
+ "query": {
+ "token ": " 123",
+ "current": "1",
+ "pageSize": "20"
+ },
+ "payload": {},
+ "types": "/** GET /api/rule */\nexport type GET_API_RULE_QUERY = {\n /** example: 123 */\n token : string;\n /** example: 1 */\n current: string;\n /** example: 20 */\n pageSize: string\n}\n \n\nexport type GET_API_RULE_PAYLOAD = {\n \n}\n \n\nexport type GET_API_RULE_RES = {\n /** example: [{\"key\": 99, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 99\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 503, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 81}, {\"key\": 98, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 98\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 164, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 97, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 97\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 174, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 81}, {\"key\": 96, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 96\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 914, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 7}, {\"key\": 95, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 95\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 698, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 82}, {\"key\": 94, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 94\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 488, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 14}, {\"key\": 93, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 93\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 580, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 77}, {\"key\": 92, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 92\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 244, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 58}, {\"key\": 91, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 91\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 959, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 66}, {\"key\": 90, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 90\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 958, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 72}, {\"key\": 89, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 89\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 301, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 2}, {\"key\": 88, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 88\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 277, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 87, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 87\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 810, \"status\": \"1\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 82}, {\"key\": 86, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 86\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 780, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 22}, {\"key\": 85, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 85\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 705, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 12}, {\"key\": 84, \"disabled\": true, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 84\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 203, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 79}, {\"key\": 83, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 83\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 491, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 59}, {\"key\": 82, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 82\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 73, \"status\": \"0\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 100}, {\"key\": 81, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png\", \"name\": \"TradeCode 81\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 406, \"status\": \"3\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 61}, {\"key\": 80, \"disabled\": false, \"href\": \"https: //ant.design\", \"avatar\": \"https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png\", \"name\": \"TradeCode 80\", \"owner\": \"曲丽丽\", \"desc\": \"这是一段描述\", \"callNo\": 112, \"status\": \"2\", \"updatedAt\": \"2022-12-06T05: 00: 57.040Z\", \"createdAt\": \"2022-12-06T05: 00: 57.040Z\", \"progress\": 20}] */\n data: {\n key: number,\ndisabled: boolean,\nhref: string,\navatar: string,\nname: string,\nowner: string,\ndesc: string,\ncallNo: number,\nstatus: string,\nupdatedAt: string,\ncreatedAt: string,\nprogress: number\n }[];\n /** example: 100 */\n total: number;\n /** example: true */\n success: boolean;\n /** example: 20 */\n pageSize: number;\n /** example: 1 */\n current: number\n}\n "
+ },
+ "POST /api/login/outLogin": {
+ "res": {
+ "data": {},
+ "success": true
+ },
+ "query": {
+ "token ": " 123"
+ },
+ "payload": {},
+ "types": "/** POST /api/login/outLogin */\nexport type POST_API_LOGIN_OUT_LOGIN_QUERY = {\n /** example: 123 */\n token : string\n}\n \n\nexport type POST_API_LOGIN_OUT_LOGIN_PAYLOAD = {\n \n}\n \n\nexport type POST_API_LOGIN_OUT_LOGIN_RES = {\n /** example: {} */\n data: {\n \n };\n /** example: true */\n success: boolean\n}\n "
+ },
+ "POST /api/login/account": {
+ "res": {
+ "status": "ok",
+ "type": "account",
+ "currentAuthority": "admin"
+ },
+ "query": {
+ "token ": " 123"
+ },
+ "payload": {
+ "username": "admin",
+ "password": "ant.design",
+ "autoLogin": true,
+ "type": "account"
+ },
+ "types": "/** POST /api/login/account */\nexport type POST_API_LOGIN_ACCOUNT_QUERY = {\n /** example: 123 */\n token : string\n}\n \n\nexport type POST_API_LOGIN_ACCOUNT_PAYLOAD = {\n /** example: admin */\n username: string;\n /** example: ant.design */\n password: string;\n /** example: true */\n autoLogin: boolean;\n /** example: account */\n type: string\n}\n \n\nexport type POST_API_LOGIN_ACCOUNT_RES = {\n /** example: ok */\n status: string;\n /** example: account */\n type: string;\n /** example: admin */\n currentAuthority: string\n}\n "
+ }
+}
diff --git a/types/cache/mock/login.mock.cache.js b/types/cache/mock/login.mock.cache.js
new file mode 100644
index 0000000..6c59e19
--- /dev/null
+++ b/types/cache/mock/login.mock.cache.js
@@ -0,0 +1,324 @@
+module.exports = {
+ 'GET /api/currentUser': {
+ data: {
+ name: 'Serati Ma',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+ userid: '00000001',
+ email: 'antdesign@alipay.com',
+ signature: '海纳百川,有容乃大',
+ title: '交互专家',
+ group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+ tags: [
+ { key: '0', label: '很有想法的' },
+ { key: '1', label: '专注设计' },
+ { key: '2', label: '辣~' },
+ { key: '3', label: '大长腿' },
+ { key: '4', label: '川妹子' },
+ { key: '5', label: '海纳百川' },
+ ],
+ notifyCount: 12,
+ unreadCount: 11,
+ country: 'China',
+ geographic: {
+ province: { label: '浙江省', key: '330000' },
+ city: { label: '杭州市', key: '330100' },
+ },
+ address: '西湖区工专路 77 号',
+ phone: '0752-268888888',
+ },
+ },
+ 'GET /api/rule': {
+ data: [
+ {
+ key: 99,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 99',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 503,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 81,
+ },
+ {
+ key: 98,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 98',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 164,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 97,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 97',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 174,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 81,
+ },
+ {
+ key: 96,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 96',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 914,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 7,
+ },
+ {
+ key: 95,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 95',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 698,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 82,
+ },
+ {
+ key: 94,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 94',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 488,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 14,
+ },
+ {
+ key: 93,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 93',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 580,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 77,
+ },
+ {
+ key: 92,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 92',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 244,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 58,
+ },
+ {
+ key: 91,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 91',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 959,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 66,
+ },
+ {
+ key: 90,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 90',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 958,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 72,
+ },
+ {
+ key: 89,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 89',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 301,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 2,
+ },
+ {
+ key: 88,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 88',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 277,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 87,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 87',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 810,
+ status: '1',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 82,
+ },
+ {
+ key: 86,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 86',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 780,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 22,
+ },
+ {
+ key: 85,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 85',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 705,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 12,
+ },
+ {
+ key: 84,
+ disabled: true,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 84',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 203,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 79,
+ },
+ {
+ key: 83,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 83',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 491,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 59,
+ },
+ {
+ key: 82,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 82',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 73,
+ status: '0',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 100,
+ },
+ {
+ key: 81,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
+ name: 'TradeCode 81',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 406,
+ status: '3',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 61,
+ },
+ {
+ key: 80,
+ disabled: false,
+ href: 'https://ant.design',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
+ name: 'TradeCode 80',
+ owner: '曲丽丽',
+ desc: '这是一段描述',
+ callNo: 112,
+ status: '2',
+ updatedAt: '2022-12-06T05:00:57.040Z',
+ createdAt: '2022-12-06T05:00:57.040Z',
+ progress: 20,
+ },
+ ],
+ total: 100,
+ success: true,
+ pageSize: 20,
+ current: 1,
+ },
+ 'POST /api/login/outLogin': { data: {}, success: true },
+ 'POST /api/login/account': {
+ status: 'ok',
+ type: 'account',
+ currentAuthority: 'admin',
+ },
+};
diff --git a/types/cache/mock/mock.cache.js b/types/cache/mock/mock.cache.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/types/cache/mock/mock.cache.js
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000..2c2805a
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,120 @@
+export namespace API {
+ /** GET /api/currentUser */
+ export type GET_API_CURRENT_USER_QUERY = {
+ /** example: 123 */
+ token: string;
+ };
+
+ export type GET_API_CURRENT_USER_PAYLOAD = Record<string, any>;
+
+ export type GET_API_CURRENT_USER_RES = {
+ /** example: {"name": "Serati Ma", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png", "userid": "00000001", "email": "antdesign@alipay.com", "signature": "海纳百川,有容乃大", "title": "交互专家", "group": "蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED", "tags": [{"key": "0", "label": "很有想法的"}, {"key": "1", "label": "专注设计"}, {"key": "2", "label": "辣~"}, {"key": "3", "label": "大长腿"}, {"key": "4", "label": "川妹子"}, {"key": "5", "label": "海纳百川"}], "notifyCount": 12, "unreadCount": 11, "country": "China", "geographic": {"province": {"label": "浙江省", "key": "330000"}, "city": {"label": "杭州市", "key": "330100"}}, "address": "西湖区工专路 77 号", "phone": "0752-268888888"} */
+ data: {
+ name: string;
+ avatar: string;
+ userid: string;
+ email: string;
+ signature: string;
+ title: string;
+ group: string;
+ tags: {
+ key: string;
+ label: string;
+ }[];
+ notifyCount: number;
+ unreadCount: number;
+ country: string;
+ geographic: {
+ province: {
+ label: string;
+ key: string;
+ };
+ city: {
+ label: string;
+ key: string;
+ };
+ };
+ address: string;
+ phone: string;
+ };
+ };
+
+ /** GET /api/rule */
+ export type GET_API_RULE_QUERY = {
+ /** example: 123 */
+ token: string;
+ /** example: 1 */
+ current: string;
+ /** example: 20 */
+ pageSize: string;
+ };
+
+ export type GET_API_RULE_PAYLOAD = Record<string, any>;
+
+ export type GET_API_RULE_RES = {
+ /** example: [{"key": 99, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 99", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 503, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 81}, {"key": 98, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 98", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 164, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 97, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 97", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 174, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 81}, {"key": 96, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 96", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 914, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 7}, {"key": 95, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 95", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 698, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 82}, {"key": 94, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 94", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 488, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 14}, {"key": 93, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 93", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 580, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 77}, {"key": 92, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 92", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 244, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 58}, {"key": 91, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 91", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 959, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 66}, {"key": 90, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 90", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 958, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 72}, {"key": 89, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 89", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 301, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 2}, {"key": 88, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 88", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 277, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 87, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 87", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 810, "status": "1", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 82}, {"key": 86, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 86", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 780, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 22}, {"key": 85, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 85", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 705, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 12}, {"key": 84, "disabled": true, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 84", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 203, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 79}, {"key": 83, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 83", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 491, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 59}, {"key": 82, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 82", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 73, "status": "0", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 100}, {"key": 81, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", "name": "TradeCode 81", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 406, "status": "3", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 61}, {"key": 80, "disabled": false, "href": "https: //ant.design", "avatar": "https: //gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", "name": "TradeCode 80", "owner": "曲丽丽", "desc": "这是一段描述", "callNo": 112, "status": "2", "updatedAt": "2022-12-06T05: 00: 57.040Z", "createdAt": "2022-12-06T05: 00: 57.040Z", "progress": 20}] */
+ data: {
+ key: number;
+ disabled: boolean;
+ href: string;
+ avatar: string;
+ name: string;
+ owner: string;
+ desc: string;
+ callNo: number;
+ status: string;
+ updatedAt: string;
+ createdAt: string;
+ progress: number;
+ }[];
+ /** example: 100 */
+ total: number;
+ /** example: true */
+ success: boolean;
+ /** example: 20 */
+ pageSize: number;
+ /** example: 1 */
+ current: number;
+ };
+
+ /** POST /api/login/outLogin */
+ export type POST_API_LOGIN_OUT_LOGIN_QUERY = {
+ /** example: 123 */
+ token: string;
+ };
+
+ export type POST_API_LOGIN_OUT_LOGIN_PAYLOAD = Record<string, any>;
+
+ export type POST_API_LOGIN_OUT_LOGIN_RES = {
+ /** example: {} */
+ data: Record<string, any>;
+ /** example: true */
+ success: boolean;
+ };
+
+ /** POST /api/login/account */
+ export type POST_API_LOGIN_ACCOUNT_QUERY = {
+ /** example: 123 */
+ token: string;
+ };
+
+ export type POST_API_LOGIN_ACCOUNT_PAYLOAD = {
+ /** example: admin */
+ username: string;
+ /** example: ant.design */
+ password: string;
+ /** example: true */
+ autoLogin: boolean;
+ /** example: account */
+ type: string;
+ };
+
+ export type POST_API_LOGIN_ACCOUNT_RES = {
+ /** example: ok */
+ status: string;
+ /** example: account */
+ type: string;
+ /** example: admin */
+ currentAuthority: string;
+ };
+}
diff --git a/types/route.d.ts b/types/route.d.ts
new file mode 100644
index 0000000..bf35123
--- /dev/null
+++ b/types/route.d.ts
@@ -0,0 +1,18 @@
+declare namespace API {
+ type RoutersMenuItem = {
+ alwaysShow?: boolean;
+ children?: RoutersMenuItem[];
+ component?: string;
+ hidden?: boolean;
+ meta: MenuItemMeta;
+ name: string;
+ path: string;
+ redirect?: string;
+ [key: string]: any;
+ };
+ interface GetRoutersResult {
+ code: number;
+ msg: string;
+ data: RoutersMenuItem[];
+ }
+}