diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0da5816
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,54 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+
+######################################################################
+# Files used by docker containers
+docker/ruoyi/**/*.jar
+docker/mysql/data
+docker/mysql/db/*.sql
+docker/redis/data
+docker/nacos/logs
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bd95df1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 若依
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 6254c10..f1a9c13 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,138 @@
-# ci4sManagement
+## 平台简介
+项目链接:https://gitee.com/whiteshader/ruoyi-react
+
+相关项目:https://gitee.com/whiteshader/ruoyi-cloud-vben
+
+若依(Ruoyi-React)是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
+
+* 前端采用React 18、Ant Design Pro 6、TypeScript 5。
+* 后端采用Spring Cloud、Spring Security、Redis & Jwt。
+* 权限认证使用Jwt,支持多终端认证系统。
+* 支持加载动态权限菜单,多方式轻松权限控制。
+* 高效率开发,使用代码生成器可以一键生成前后端代码。
+
+
+## 内置功能
+
+1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3. 岗位管理:配置系统用户所属担任职务。
+4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7. 参数管理:对系统动态配置常用参数。
+8. 通知公告:系统通知公告信息发布维护。
+9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 在线构建器:拖动表单元素生成相应的HTML代码。
+17. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123
+- ry/123456
+
+演示地址:暂时没有
+
+https://gitee.com/whiteshader/ruoyi-cloud-vben/blob/master/ruoyi-react-demo-2023-04-27.gif
+
+## 前端开发注意事项
+
+Node:建议v16或以上
+
+安装依赖请支行:pnpm i
+
+正常启动请运行: pnpm run dev
+
+Mock测试模式请运行: npm run start
+
+发布打包请运行: npm run build
+
+## 相关技术文档
+
+### 后端说明文档
+http://doc.ruoyi.vip/ruoyi-cloud/
+
+### TypeScript
+https://www.tslang.cn/docs/home.html
+
+### React Js
+https://react.docschina.org/docs/getting-started.html
+
+### Ant Design
+https://ant.design/components/overview-cn/
+
+### Ant Design Pro
+https://pro.ant.design/zh-CN/docs/overview
+
+### Ant Design Chart
+https://charts.ant.design/zh
+
+### Umi Js
+https://umijs.org/docs/introduce/introduce
+
+## 部署
+http://doc.ruoyi.vip/ruoyi-vue/document/hjbs.html#nginx%E9%85%8D%E7%BD%AE
+
+## 演示图
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 若依(Ruoyi-React)前后端分离交流群
+
+QQ群: [](https://jq.qq.com/?_wv=1027&k=u58VEEQK) 点击按钮入群。
+
+
+## 感谢捐赠的伙伴,谢谢你们的支持
\ No newline at end of file
diff --git a/bin/clean.bat b/bin/clean.bat
new file mode 100644
index 0000000..578e77d
--- /dev/null
+++ b/bin/clean.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 清理工程target生成路径。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean
+
+pause
\ No newline at end of file
diff --git a/bin/package.bat b/bin/package.bat
new file mode 100644
index 0000000..c693ec0
--- /dev/null
+++ b/bin/package.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [Ϣ] Weḅwar/jarļ
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+call mvn clean package -Dmaven.test.skip=true
+
+pause
\ No newline at end of file
diff --git a/bin/run-auth.bat b/bin/run-auth.bat
new file mode 100644
index 0000000..954500f
--- /dev/null
+++ b/bin/run-auth.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [信息] 使用Jar命令运行Auth工程。
+echo.
+
+cd %~dp0
+cd ../ruoyi-auth/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-auth.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-gateway.bat b/bin/run-gateway.bat
new file mode 100644
index 0000000..e43d60b
--- /dev/null
+++ b/bin/run-gateway.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarGateway̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-gateway/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-gateway.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-modules-file.bat b/bin/run-modules-file.bat
new file mode 100644
index 0000000..6d89c90
--- /dev/null
+++ b/bin/run-modules-file.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarModules-File̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-modules/ruoyi-file/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-modules-file.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-modules-gen.bat b/bin/run-modules-gen.bat
new file mode 100644
index 0000000..0a4c390
--- /dev/null
+++ b/bin/run-modules-gen.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarModules-Gen̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-modules/ruoyi-gen/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-modules-gen.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-modules-job.bat b/bin/run-modules-job.bat
new file mode 100644
index 0000000..443e59d
--- /dev/null
+++ b/bin/run-modules-job.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarModules-Job̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-modules/ruoyi-job/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-modules-job.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-modules-system.bat b/bin/run-modules-system.bat
new file mode 100644
index 0000000..26db55e
--- /dev/null
+++ b/bin/run-modules-system.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarModules-System̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-modules/ruoyi-system/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-modules-system.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/bin/run-monitor.bat b/bin/run-monitor.bat
new file mode 100644
index 0000000..3e8e322
--- /dev/null
+++ b/bin/run-monitor.bat
@@ -0,0 +1,14 @@
+@echo off
+echo.
+echo [Ϣ] ʹJarMonitor̡
+echo.
+
+cd %~dp0
+cd ../ruoyi-visual/ruoyi-monitor/target
+
+set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
+
+java -Dfile.encoding=utf-8 %JAVA_OPTS% -jar ruoyi-visual-monitor.jar
+
+cd bin
+pause
\ No newline at end of file
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000..b196171
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,11 @@
+## 使用 docker 进行快速部署
+
+1. 在项目路径下执行 `mvn clean package` 完成所有后端项目的编译构建,生成 jar 产物。
+
+2. 在 `./docker` 路径下执行 `copy.sh`,将 docker 依赖的文件复制到 `./docker` 对应路径下。
+
+3. 在 `./docker` 路径下执行 `deploy.sh`,完成 docker 镜像的构建和部署。
+
+## 注意
+
+为了使 docker 容器中的服务能够正常的访问其他 docker 容器中的服务,`docker-compose.yml` 中在一些环境变量中配置了 `172.17.0.1` 这个 ip 地址。这个 ip 地址是 docker0 虚拟网卡下宿主机的地址。这个地址在 linux 中有效,而在 win/mac 中,[docker-desktop] (https://www.docker.com/products/docker-desktop/)是通过虚拟机运行的 docker,需要使用 `host.docker.internal` 来代替。
\ No newline at end of file
diff --git a/docker/copy.sh b/docker/copy.sh
new file mode 100644
index 0000000..dcea467
--- /dev/null
+++ b/docker/copy.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# 复制项目的文件到对应docker路径,便于一键生成镜像。
+usage() {
+ echo "Usage: sh copy.sh"
+ exit 1
+}
+
+
+# copy sql
+echo "begin copy sql "
+mkdir -p ./mysql/db
+cp ../sql/ry_20230706.sql ./mysql/db
+cp ../sql/ry_config_20220929.sql ./mysql/db
+
+# copy html
+echo "begin copy html "
+cp -r ../react-ui/dist/** ./nginx/html/dist
+
+
+# copy jar
+echo "begin copy ruoyi-gateway "
+cp ../ruoyi-gateway/target/ruoyi-gateway.jar ./ruoyi/gateway/jar
+
+echo "begin copy ruoyi-auth "
+cp ../ruoyi-auth/target/ruoyi-auth.jar ./ruoyi/auth/jar
+
+echo "begin copy ruoyi-visual "
+cp ../ruoyi-visual/ruoyi-monitor/target/ruoyi-visual-monitor.jar ./ruoyi/visual/monitor/jar
+
+echo "begin copy ruoyi-modules-system "
+cp ../ruoyi-modules/ruoyi-system/target/ruoyi-modules-system.jar ./ruoyi/modules/system/jar
+
+echo "begin copy ruoyi-modules-file "
+cp ../ruoyi-modules/ruoyi-file/target/ruoyi-modules-file.jar ./ruoyi/modules/file/jar
+
+echo "begin copy ruoyi-modules-job "
+cp ../ruoyi-modules/ruoyi-job/target/ruoyi-modules-job.jar ./ruoyi/modules/job/jar
+
+echo "begin copy ruoyi-modules-gen "
+cp ../ruoyi-modules/ruoyi-gen/target/ruoyi-modules-gen.jar ./ruoyi/modules/gen/jar
+
diff --git a/docker/deploy.sh b/docker/deploy.sh
new file mode 100644
index 0000000..0248909
--- /dev/null
+++ b/docker/deploy.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+# 使用说明,用来提示输入参数
+usage() {
+ echo "Usage: sh 执行脚本.sh [port|base|modules|stop|rm]"
+ exit 1
+}
+
+# 开启所需端口
+port(){
+ firewall-cmd --add-port=80/tcp --permanent
+ firewall-cmd --add-port=8080/tcp --permanent
+ firewall-cmd --add-port=8848/tcp --permanent
+ firewall-cmd --add-port=9848/tcp --permanent
+ firewall-cmd --add-port=9849/tcp --permanent
+ firewall-cmd --add-port=6379/tcp --permanent
+ firewall-cmd --add-port=3306/tcp --permanent
+ firewall-cmd --add-port=9100/tcp --permanent
+ firewall-cmd --add-port=9200/tcp --permanent
+ firewall-cmd --add-port=9201/tcp --permanent
+ firewall-cmd --add-port=9202/tcp --permanent
+ firewall-cmd --add-port=9203/tcp --permanent
+ firewall-cmd --add-port=9300/tcp --permanent
+ service firewalld restart
+}
+
+# 启动基础环境(必须)
+base(){
+ docker-compose up -d ruoyi-mysql ruoyi-redis ruoyi-nacos
+}
+
+# 启动程序模块(必须)
+modules(){
+ docker-compose up -d ruoyi-nginx ruoyi-gateway ruoyi-auth ruoyi-modules-system
+}
+
+# 关闭所有环境/模块
+stop(){
+ docker-compose stop
+}
+
+# 删除所有环境/模块
+rm(){
+ docker-compose rm
+}
+
+# 根据输入参数,选择执行对应方法,不输入则执行使用说明
+case "$1" in
+"port")
+ port
+;;
+"base")
+ base
+;;
+"modules")
+ modules
+;;
+"stop")
+ stop
+;;
+"rm")
+ rm
+;;
+*)
+ usage
+;;
+esac
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..13c78a9
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,169 @@
+version : '3.8'
+services:
+
+ ruoyi-mysql:
+ container_name: ruoyi-mysql
+ image: mysql:5.7
+ build:
+ context: ./mysql
+ ports:
+ - "3306:3306"
+ volumes:
+ - ./mysql/conf:/etc/mysql/conf.d
+ - ./mysql/logs:/logs
+ - ./mysql/data:/var/lib/mysql
+ - ./mysql/db:/docker-entrypoint-initdb.d
+ command: [
+ 'mysqld',
+ '--innodb-buffer-pool-size=80M',
+ '--character-set-server=utf8mb4',
+ '--collation-server=utf8mb4_unicode_ci',
+ '--default-time-zone=+8:00',
+ '--lower-case-table-names=1'
+ ]
+ environment:
+ MYSQL_DATABASE: 'ry-cloud'
+ MYSQL_ROOT_PASSWORD: password
+
+ ruoyi-redis:
+ container_name: ruoyi-redis
+ image: redis
+ build:
+ context: ./redis
+ ports:
+ - "6379:6379"
+ volumes:
+ - ./redis/conf/redis.conf:/home/ruoyi/redis/redis.conf
+ - ./redis/data:/data
+ command: redis-server /home/ruoyi/redis/redis.conf
+
+ ruoyi-nacos:
+ container_name: ruoyi-nacos
+ image: nacos/nacos-server
+ build:
+ context: ./nacos
+ environment:
+ - MODE=standalone
+ volumes:
+ - ./nacos/logs/:/home/nacos/logs
+ - ./nacos/conf/application.properties:/home/nacos/conf/application.properties
+ ports:
+ - "8848:8848"
+ - "9848:9848"
+ - "9849:9849"
+ depends_on:
+ - ruoyi-mysql
+
+ ruoyi-nginx:
+ container_name: ruoyi-nginx
+ image: nginx
+ build:
+ context: ./nginx
+ ports:
+ - "80:80"
+ volumes:
+ - ./nginx/html/dist:/home/ruoyi/projects/ruoyi-ui
+ - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
+ - ./nginx/logs:/var/log/nginx
+ - ./nginx/conf.d:/etc/nginx/conf.d
+ links:
+ - ruoyi-gateway
+
+ ruoyi-gateway:
+ container_name: ruoyi-gateway
+ build:
+ context: ./ruoyi/gateway
+ dockerfile: dockerfile
+ ports:
+ - "8080:8080"
+ - "8718:8718"
+ depends_on:
+ - ruoyi-redis
+ links:
+ - ruoyi-redis
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+ - REDIS_ADDR=172.17.0.1
+
+ ruoyi-auth:
+ container_name: ruoyi-auth
+ build:
+ context: ./ruoyi/auth
+ dockerfile: dockerfile
+ ports:
+ - "9200:9200"
+ depends_on:
+ - ruoyi-redis
+ links:
+ - ruoyi-redis
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+ - REDIS_ADDR=172.17.0.1
+
+ ruoyi-modules-system:
+ container_name: ruoyi-modules-system
+ build:
+ context: ./ruoyi/modules/system
+ dockerfile: dockerfile
+ ports:
+ - "9201:9201"
+ depends_on:
+ - ruoyi-redis
+ - ruoyi-mysql
+ links:
+ - ruoyi-redis
+ - ruoyi-mysql
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+ - REDIS_ADDR=172.17.0.1
+ - MYSQL_ADDR=172.17.0.1
+
+ ruoyi-modules-gen:
+ container_name: ruoyi-modules-gen
+ build:
+ context: ./ruoyi/modules/gen
+ dockerfile: dockerfile
+ ports:
+ - "9202:9202"
+ depends_on:
+ - ruoyi-mysql
+ links:
+ - ruoyi-mysql
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+
+ ruoyi-modules-job:
+ container_name: ruoyi-modules-job
+ build:
+ context: ./ruoyi/modules/job
+ dockerfile: dockerfile
+ ports:
+ - "9203:9203"
+ depends_on:
+ - ruoyi-mysql
+ links:
+ - ruoyi-mysql
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+
+ ruoyi-modules-file:
+ container_name: ruoyi-modules-file
+ build:
+ context: ./ruoyi/modules/file
+ dockerfile: dockerfile
+ ports:
+ - "9300:9300"
+ volumes:
+ - ./ruoyi/uploadPath:/home/ruoyi/uploadPath
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
+
+ ruoyi-visual-monitor:
+ container_name: ruoyi-visual-monitor
+ build:
+ context: ./ruoyi/visual/monitor
+ dockerfile: dockerfile
+ ports:
+ - "9100:9100"
+ environment:
+ - NACOS_SERVER_ADDR=172.17.0.1:8848
diff --git a/docker/mysql/dockerfile b/docker/mysql/dockerfile
new file mode 100644
index 0000000..cc006f4
--- /dev/null
+++ b/docker/mysql/dockerfile
@@ -0,0 +1,7 @@
+# 基础镜像
+FROM mysql:5.7
+# author
+MAINTAINER ruoyi
+
+# 执行sql脚本
+ADD ./db/*.sql /docker-entrypoint-initdb.d/
diff --git a/docker/nacos/conf/application.properties b/docker/nacos/conf/application.properties
new file mode 100644
index 0000000..7f2a61f
--- /dev/null
+++ b/docker/nacos/conf/application.properties
@@ -0,0 +1,32 @@
+spring.datasource.platform=mysql
+db.num=1
+db.url.0=jdbc:mysql://ruoyi-mysql:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
+db.user=root
+db.password=password
+
+nacos.naming.empty-service.auto-clean=true
+nacos.naming.empty-service.clean.initial-delay-ms=50000
+nacos.naming.empty-service.clean.period-time-ms=30000
+
+management.endpoints.web.exposure.include=*
+
+management.metrics.export.elastic.enabled=false
+management.metrics.export.influx.enabled=false
+
+server.tomcat.accesslog.enabled=true
+server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i
+
+server.tomcat.basedir=/home/ruoyi/nacos/tomcat/logs
+
+nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
+
+nacos.core.auth.system.type=nacos
+nacos.core.auth.enabled=false
+nacos.core.auth.default.token.expire.seconds=18000
+nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
+nacos.core.auth.caching.enabled=true
+nacos.core.auth.enable.userAgentAuthWhite=false
+nacos.core.auth.server.identity.key=serverIdentity
+nacos.core.auth.server.identity.value=security
+
+nacos.istio.mcp.server.enabled=false
diff --git a/docker/nacos/dockerfile b/docker/nacos/dockerfile
new file mode 100644
index 0000000..b74762c
--- /dev/null
+++ b/docker/nacos/dockerfile
@@ -0,0 +1,7 @@
+# 基础镜像
+FROM nacos/nacos-server
+# author
+MAINTAINER ruoyi
+
+# 复制conf文件到路径
+COPY ./conf/application.properties /home/nacos/conf/application.properties
diff --git a/docker/nginx/conf/nginx.conf b/docker/nginx/conf/nginx.conf
new file mode 100644
index 0000000..110cc20
--- /dev/null
+++ b/docker/nginx/conf/nginx.conf
@@ -0,0 +1,41 @@
+worker_processes 1;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+ sendfile on;
+ keepalive_timeout 65;
+
+ server {
+ listen 80;
+ server_name localhost;
+
+ location / {
+ root /home/ruoyi/projects/ruoyi-ui;
+ try_files $uri $uri/ /index.html;
+ index index.html index.htm;
+ }
+
+ location /api/{
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header REMOTE-HOST $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_pass http://ruoyi-gateway:8080/;
+ }
+
+ # 避免actuator暴露
+ if ($request_uri ~ "/actuator") {
+ return 403;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root html;
+ }
+ }
+}
\ No newline at end of file
diff --git a/docker/nginx/dockerfile b/docker/nginx/dockerfile
new file mode 100644
index 0000000..53b136d
--- /dev/null
+++ b/docker/nginx/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM nginx
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi/projects/ruoyi-ui
+# 创建目录
+RUN mkdir -p /home/ruoyi/projects/ruoyi-ui
+# 指定路径
+WORKDIR /home/ruoyi/projects/ruoyi-ui
+# 复制conf文件到路径
+COPY ./conf/nginx.conf /etc/nginx/nginx.conf
+# 复制html文件到路径
+COPY ./html/dist /home/ruoyi/projects/ruoyi-ui
diff --git a/docker/redis/conf/redis.conf b/docker/redis/conf/redis.conf
new file mode 100644
index 0000000..d762d65
--- /dev/null
+++ b/docker/redis/conf/redis.conf
@@ -0,0 +1 @@
+# requirepass 123456
\ No newline at end of file
diff --git a/docker/redis/dockerfile b/docker/redis/dockerfile
new file mode 100644
index 0000000..05c2bc4
--- /dev/null
+++ b/docker/redis/dockerfile
@@ -0,0 +1,13 @@
+# 基础镜像
+FROM redis
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi/redis
+# 创建目录
+RUN mkdir -p /home/ruoyi/redis
+# 指定路径
+WORKDIR /home/ruoyi/redis
+# 复制conf文件到路径
+COPY ./conf/redis.conf /home/ruoyi/redis/redis.conf
diff --git a/docker/ruoyi/auth/dockerfile b/docker/ruoyi/auth/dockerfile
new file mode 100644
index 0000000..f3c87c5
--- /dev/null
+++ b/docker/ruoyi/auth/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-auth.jar /home/ruoyi/ruoyi-auth.jar
+# 启动认证服务
+ENTRYPOINT ["java","-jar","ruoyi-auth.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/auth/jar/readme.txt b/docker/ruoyi/auth/jar/readme.txt
new file mode 100644
index 0000000..eb21d61
--- /dev/null
+++ b/docker/ruoyi/auth/jar/readme.txt
@@ -0,0 +1 @@
+存放认证中心打包好的jar文件,用于docker启动应用。
\ No newline at end of file
diff --git a/docker/ruoyi/gateway/dockerfile b/docker/ruoyi/gateway/dockerfile
new file mode 100644
index 0000000..0e8f612
--- /dev/null
+++ b/docker/ruoyi/gateway/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-gateway.jar /home/ruoyi/ruoyi-gateway.jar
+# 启动网关服务
+ENTRYPOINT ["java","-jar","ruoyi-gateway.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/gateway/jar/readme.txt b/docker/ruoyi/gateway/jar/readme.txt
new file mode 100644
index 0000000..5dfbec7
--- /dev/null
+++ b/docker/ruoyi/gateway/jar/readme.txt
@@ -0,0 +1 @@
+ģõjarļdockerӦá
\ No newline at end of file
diff --git a/docker/ruoyi/modules/file/dockerfile b/docker/ruoyi/modules/file/dockerfile
new file mode 100644
index 0000000..3e23c93
--- /dev/null
+++ b/docker/ruoyi/modules/file/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-modules-file.jar /home/ruoyi/ruoyi-modules-file.jar
+# 启动文件服务
+ENTRYPOINT ["java","-jar","ruoyi-modules-file.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/modules/file/jar/readme.txt b/docker/ruoyi/modules/file/jar/readme.txt
new file mode 100644
index 0000000..bf2b2a7
--- /dev/null
+++ b/docker/ruoyi/modules/file/jar/readme.txt
@@ -0,0 +1 @@
+ļõjarļdockerӦá
\ No newline at end of file
diff --git a/docker/ruoyi/modules/gen/dockerfile b/docker/ruoyi/modules/gen/dockerfile
new file mode 100644
index 0000000..4da8f30
--- /dev/null
+++ b/docker/ruoyi/modules/gen/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-modules-gen.jar /home/ruoyi/ruoyi-modules-gen.jar
+# 启动代码生成服务
+ENTRYPOINT ["java","-jar","ruoyi-modules-gen.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/modules/gen/jar/readme.txt b/docker/ruoyi/modules/gen/jar/readme.txt
new file mode 100644
index 0000000..2f25c0a
--- /dev/null
+++ b/docker/ruoyi/modules/gen/jar/readme.txt
@@ -0,0 +1 @@
+ŴɴõjarļdockerӦá
\ No newline at end of file
diff --git a/docker/ruoyi/modules/job/dockerfile b/docker/ruoyi/modules/job/dockerfile
new file mode 100644
index 0000000..3c4f7c7
--- /dev/null
+++ b/docker/ruoyi/modules/job/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-modules-job.jar /home/ruoyi/ruoyi-modules-job.jar
+# 启动定时任务服务
+ENTRYPOINT ["java","-jar","ruoyi-modules-job.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/modules/job/jar/readme.txt b/docker/ruoyi/modules/job/jar/readme.txt
new file mode 100644
index 0000000..58aea0b
--- /dev/null
+++ b/docker/ruoyi/modules/job/jar/readme.txt
@@ -0,0 +1 @@
+ŶʱõjarļdockerӦá
\ No newline at end of file
diff --git a/docker/ruoyi/modules/system/dockerfile b/docker/ruoyi/modules/system/dockerfile
new file mode 100644
index 0000000..fd825ec
--- /dev/null
+++ b/docker/ruoyi/modules/system/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-modules-system.jar /home/ruoyi/ruoyi-modules-system.jar
+# 启动系统服务
+ENTRYPOINT ["java","-jar","ruoyi-modules-system.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/modules/system/jar/readme.txt b/docker/ruoyi/modules/system/jar/readme.txt
new file mode 100644
index 0000000..cfc2a92
--- /dev/null
+++ b/docker/ruoyi/modules/system/jar/readme.txt
@@ -0,0 +1 @@
+ϵͳģõjarļdockerӦá
\ No newline at end of file
diff --git a/docker/ruoyi/visual/monitor/dockerfile b/docker/ruoyi/visual/monitor/dockerfile
new file mode 100644
index 0000000..1f17182
--- /dev/null
+++ b/docker/ruoyi/visual/monitor/dockerfile
@@ -0,0 +1,15 @@
+# 基础镜像
+FROM openjdk:8-jre
+# author
+MAINTAINER ruoyi
+
+# 挂载目录
+VOLUME /home/ruoyi
+# 创建目录
+RUN mkdir -p /home/ruoyi
+# 指定路径
+WORKDIR /home/ruoyi
+# 复制jar文件到路径
+COPY ./jar/ruoyi-visual-monitor.jar /home/ruoyi/ruoyi-visual-monitor.jar
+# 启动系统服务
+ENTRYPOINT ["java","-jar","ruoyi-visual-monitor.jar"]
\ No newline at end of file
diff --git a/docker/ruoyi/visual/monitor/jar/readme.txt b/docker/ruoyi/visual/monitor/jar/readme.txt
new file mode 100644
index 0000000..62b2841
--- /dev/null
+++ b/docker/ruoyi/visual/monitor/jar/readme.txt
@@ -0,0 +1 @@
+żĴõjarļdockerӦá
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..043343b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,285 @@
+
+
+ 4.0.0
+
+ com.ruoyi
+ ruoyi
+ 3.6.3
+
+ ruoyi
+ http://www.ruoyi.vip
+ 若依微服务系统
+
+
+ 3.6.3
+ UTF-8
+ UTF-8
+ 1.8
+ 2.7.13
+ 2021.0.8
+ 2021.0.5.0
+ 2.7.10
+ 3.0.0
+ 1.6.2
+ 1.27.2
+ 2.3.3
+ 1.4.7
+ 1.2.16
+ 3.5.2
+ 2.11.0
+ 2.3
+ 2.0.34
+ 0.9.1
+ 8.2.2
+ 4.1.2
+ 2.14.3
+
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-alibaba-dependencies
+ ${spring-cloud-alibaba.version}
+ pom
+ import
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+ com.github.tobato
+ fastdfs-client
+ ${tobato.version}
+
+
+
+
+ io.swagger
+ swagger-models
+ ${swagger.core.version}
+
+
+ io.swagger
+ swagger-annotations
+ ${swagger.core.version}
+
+
+
+
+ pro.fessional
+ kaptcha
+ ${kaptcha.version}
+
+
+
+
+ com.github.pagehelper
+ pagehelper-spring-boot-starter
+ ${pagehelper.boot.version}
+
+
+
+
+ commons-io
+ commons-io
+ ${commons.io.version}
+
+
+
+
+ org.apache.poi
+ poi-ooxml
+ ${poi.version}
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ ${velocity.version}
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ ${fastjson.version}
+
+
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+
+
+ com.alibaba
+ transmittable-thread-local
+ ${transmittable-thread-local.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-core
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-swagger
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-security
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-datascope
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-datasource
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-seata
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-log
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-common-redis
+ ${ruoyi.version}
+
+
+
+
+ com.ruoyi
+ ruoyi-api-system
+ ${ruoyi.version}
+
+
+
+
+
+
+ ruoyi-auth
+ ruoyi-gateway
+ ruoyi-visual
+ ruoyi-modules
+ ruoyi-api
+ ruoyi-common
+
+ pom
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+ ${project.build.sourceEncoding}
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public
+
+ true
+
+
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public
+
+ true
+
+
+ false
+
+
+
+
+
\ No newline at end of file
diff --git a/react-ui/.editorconfig b/react-ui/.editorconfig
new file mode 100644
index 0000000..7e3649a
--- /dev/null
+++ b/react-ui/.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/react-ui/.eslintignore b/react-ui/.eslintignore
new file mode 100644
index 0000000..8336e93
--- /dev/null
+++ b/react-ui/.eslintignore
@@ -0,0 +1,8 @@
+/lambda/
+/scripts
+/config
+.history
+public
+dist
+.umi
+mock
\ No newline at end of file
diff --git a/react-ui/.eslintrc.js b/react-ui/.eslintrc.js
new file mode 100644
index 0000000..3ac39ef
--- /dev/null
+++ b/react-ui/.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/react-ui/.github/FUNDING.yml b/react-ui/.github/FUNDING.yml
new file mode 100644
index 0000000..26b00e2
--- /dev/null
+++ b/react-ui/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: ant-design
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/react-ui/.github/ISSUE_TEMPLATE/bug_report.md b/react-ui/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..5cb2692
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,48 @@
+---
+name: '报告 Bug | Report bug 🐛'
+about: 报告 Ant Design Pro 的 bug
+title: '🐛 [BUG]'
+labels: '🐛 bug'
+assignees: ''
+---
+
+### 🐛 bug 描述
+
+
+
+### 📷 复现步骤 | Recurrence steps
+
+
+
+### 🏞 期望结果 | Expected results
+
+
+
+### 💻 复现代码 | Recurrence code
+
+
+
+### © 版本信息
+
+- Ant Design Pro 版本: [e.g. 4.0.0]
+- umi 版本
+- 浏览器环境
+- 开发环境 [e.g. mac OS]
+
+### 🚑 其他信息
+
+
diff --git a/react-ui/.github/ISSUE_TEMPLATE/feature_request.md b/react-ui/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..b2b866a
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,28 @@
+---
+name: '功能需求 | Feature Requirements ✨'
+about: 对 Ant Design Pro 的需求或建议
+title: '👑 [需求 | Feature]'
+labels: '👑 Feature Request'
+assignees: ''
+---
+
+### 🥰 需求描述 | Requirements description
+
+
+
+### 🧐 解决方案 | Solution
+
+
+
+### 🚑 其他信息 | Other information
+
+
diff --git a/react-ui/.github/ISSUE_TEMPLATE/question.md b/react-ui/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..f0fbe7d
--- /dev/null
+++ b/react-ui/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,34 @@
+---
+name: '疑问或需要帮助 | Questions or need help ❓'
+about: 对 Ant Design Pro 使用的疑问或需要帮助
+title: '🧐[问题 | question]'
+labels: '🧐 question'
+assignees: ''
+---
+
+### 🧐 问题描述 | Problem description
+
+
+
+### 💻 示例代码 | Sample code
+
+
+
+### 🚑 其他信息 | Other information
+
+
+
+OS:
+
+Node:
+
+浏览器 | browser:
diff --git a/react-ui/.github/workflows/ci.yml b/react-ui/.github/workflows/ci.yml
new file mode 100644
index 0000000..d6f5693
--- /dev/null
+++ b/react-ui/.github/workflows/ci.yml
@@ -0,0 +1,30 @@
+name: Node CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ node_version: [16.x, 14.x]
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js ${{ matrix.node_version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node_version }}
+ - run: echo ${{github.ref}}
+ - run: npm install
+ - run: yarn run lint
+ - run: yarn run tsc
+ - run: yarn run build
+ env:
+ CI: true
+ PROGRESS: none
+ NODE_ENV: test
+ NODE_OPTIONS: --max_old_space_size=4096
diff --git a/react-ui/.github/workflows/codeql.yml b/react-ui/.github/workflows/codeql.yml
new file mode 100644
index 0000000..705b30e
--- /dev/null
+++ b/react-ui/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ schedule:
+ - cron: "48 12 * * 2"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ javascript ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-and-quality
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/react-ui/.github/workflows/coverage.yml b/react-ui/.github/workflows/coverage.yml
new file mode 100644
index 0000000..d145753
--- /dev/null
+++ b/react-ui/.github/workflows/coverage.yml
@@ -0,0 +1,27 @@
+name: coverage CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js 16.x
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16.x
+ - run: echo ${{github.ref}}
+ - run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
+ - run: pnpm config set store-dir ~/.pnpm-store
+ - run: pnpm install --strict-peer-dependencies=false
+ - run: yarn run test:coverage
+ env:
+ CI: true
+ PROGRESS: none
+ NODE_ENV: test
+ NODE_OPTIONS: --max_old_space_size=4096
+ - run: bash <(curl -s https://codecov.io/bash)
diff --git a/react-ui/.github/workflows/emoji-helper.yml b/react-ui/.github/workflows/emoji-helper.yml
new file mode 100644
index 0000000..8965a1a
--- /dev/null
+++ b/react-ui/.github/workflows/emoji-helper.yml
@@ -0,0 +1,14 @@
+name: Emoji Helper
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ emoji:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-cool/emoji-helper@v1.0.0
+ with:
+ type: 'release'
+ emoji: '+1, laugh, heart, hooray, rocket, eyes'
diff --git a/react-ui/.github/workflows/issue-labeled.yml b/react-ui/.github/workflows/issue-labeled.yml
new file mode 100644
index 0000000..a3e8b5f
--- /dev/null
+++ b/react-ui/.github/workflows/issue-labeled.yml
@@ -0,0 +1,37 @@
+name: Issue labeled
+
+on:
+ issues:
+ types: [labeled]
+
+jobs:
+ reply-helper:
+ runs-on: ubuntu-latest
+ steps:
+ - name: help wanted
+ if: github.event.label.name == '❤️ help wanted' || github.event.label.name == '🤝Welcome PR'
+ uses: actions-cool/issues-helper@v1.11
+ with:
+ actions: 'create-comment'
+ token: ${{ secrets.GITHUB_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to [send us a Pull Request](https://help.github.com/en/articles/creating-a-pull-request) for it. Please provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
+
+ 你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库 [创建一个 Pull Request](https://help.github.com/en/articles/creating-a-pull-request) 来解决这个问题。请务必提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review,提前感谢和期待您的贡献。
+
+ 
+
+ - name: Need Reproduce
+ if: github.event.label.name == '🤔 Need Reproduce'
+ uses: actions-cool/issues-helper@v1.11
+ with:
+ actions: 'create-comment'
+ token: ${{ secrets.GITHUB_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this link https://codesandbox.io/ or a minimal GitHub repository.
+
+ 你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://codesandbox.io/) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。
+
+ 
diff --git a/react-ui/.github/workflows/issue-open-check.yml b/react-ui/.github/workflows/issue-open-check.yml
new file mode 100644
index 0000000..3644213
--- /dev/null
+++ b/react-ui/.github/workflows/issue-open-check.yml
@@ -0,0 +1,34 @@
+name: Issue Open Check
+
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ check-issue:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-cool/issues-helper@v2.2.0
+ id: check
+ with:
+ actions: 'check-issue'
+ issue-number: ${{ github.event.issue.number }}
+ title-excludes: '🐛 [BUG], 👑 [需求 | Feature], 🧐[问题 | question]'
+
+ - if: steps.check.outputs.check-result == 'false' && github.event.issue.state == 'open'
+ uses: actions-cool/issues-helper@v2.2.0
+ with:
+ actions: 'create-comment, close-issue'
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ 当前 Issue 未检测到标题,请规范填写,谢谢!
+
+ The title of the current issue is not detected, please fill in according to the specifications, thank you!
+
+ - if: steps.check.outputs.check-result == 'true'
+ uses: actions-cool/issues-similarity-analysis@v1
+ with:
+ filter-threshold: 0.8
+ title-excludes: '🐛[BUG], 👑 [需求 | Feature], 🧐[问题 | question]'
+ comment-title: '### 以下的 Issues 可能会帮助到你 / The following issues may help you'
+ show-footer: false
diff --git a/react-ui/.github/workflows/pnpm.yml b/react-ui/.github/workflows/pnpm.yml
new file mode 100644
index 0000000..63bb2e4
--- /dev/null
+++ b/react-ui/.github/workflows/pnpm.yml
@@ -0,0 +1,33 @@
+name: Node pnpm CI
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ node_version: [16.x]
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js ${{ matrix.node_version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node_version }}
+ - run: echo ${{github.ref}}
+ - run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
+ - run: pnpm config set store-dir ~/.pnpm-store
+ - run: pnpm install --strict-peer-dependencies=false
+ - run: pnpm run lint
+ - run: pnpm run tsc
+ - run: pnpm run build
+ - run: pnpm run test
+ env:
+ CI: true
+ PROGRESS: none
+ NODE_ENV: test
+ NODE_OPTIONS: --max_old_space_size=4096
diff --git a/react-ui/.github/workflows/preview-build.yml b/react-ui/.github/workflows/preview-build.yml
new file mode 100644
index 0000000..d8667a5
--- /dev/null
+++ b/react-ui/.github/workflows/preview-build.yml
@@ -0,0 +1,41 @@
+name: Preview Build
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: read
+
+jobs:
+ build-preview:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: build
+ run: |
+ yarn
+ yarn add umi-plugin-pro --save
+ yarn build
+
+ - name: upload dist artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: dist
+ path: dist/
+ retention-days: 5
+
+ - name: Save PR number
+ if: ${{ always() }}
+ run: echo ${{ github.event.number }} > ./pr-id.txt
+
+ - name: Upload PR number
+ if: ${{ always() }}
+ uses: actions/upload-artifact@v2
+ with:
+ name: pr
+ path: ./pr-id.txt
diff --git a/react-ui/.github/workflows/preview-deploy.yml b/react-ui/.github/workflows/preview-deploy.yml
new file mode 100644
index 0000000..e9a699f
--- /dev/null
+++ b/react-ui/.github/workflows/preview-deploy.yml
@@ -0,0 +1,100 @@
+name: Preview Deploy
+
+on:
+ workflow_run:
+ workflows: ['Preview Build']
+ types:
+ - completed
+
+permissions:
+ contents: read
+
+jobs:
+ success:
+ permissions:
+ actions: read # for dawidd6/action-download-artifact to query and download artifacts
+ issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+ pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+ runs-on: ubuntu-latest
+ if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
+ steps:
+ - name: download pr artifact
+ uses: dawidd6/action-download-artifact@v2
+ with:
+ workflow: ${{ github.event.workflow_run.workflow_id }}
+ name: pr
+
+ - name: save PR id
+ id: pr
+ run: echo "::set-output name=id::$(
+
+
+ body-include: ''
+ number: ${{ steps.pr.outputs.id }}
+
+ - name: The job failed
+ if: ${{ failure() }}
+ uses: actions-cool/maintain-one-comment@v1.2.1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ body: |
+ 😭 Deploy PR Preview failed.
+
+
+
+
+ body-include: ''
+ number: ${{ steps.pr.outputs.id }}
+
+ failed:
+ permissions:
+ actions: read # for dawidd6/action-download-artifact to query and download artifacts
+ issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+ pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+ runs-on: ubuntu-latest
+ if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure'
+ steps:
+ - name: download pr artifact
+ uses: dawidd6/action-download-artifact@v2
+ with:
+ workflow: ${{ github.event.workflow_run.workflow_id }}
+ name: pr
+
+ - name: save PR id
+ id: pr
+ run: echo "::set-output name=id::$(
+
+
+ body-include: ''
+ number: ${{ steps.pr.outputs.id }}
diff --git a/react-ui/.github/workflows/preview-start.yml b/react-ui/.github/workflows/preview-start.yml
new file mode 100644
index 0000000..39f561d
--- /dev/null
+++ b/react-ui/.github/workflows/preview-start.yml
@@ -0,0 +1,24 @@
+name: Preview Start
+
+on: pull_request_target
+
+permissions:
+ contents: read
+
+jobs:
+ preview:
+ permissions:
+ issues: write # for actions-cool/maintain-one-comment to modify or create issue comments
+ pull-requests: write # for actions-cool/maintain-one-comment to modify or create PR comments
+ runs-on: ubuntu-latest
+ steps:
+ - name: create
+ uses: actions-cool/maintain-one-comment@v1.2.1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ body: |
+ ⚡️ Deploying PR Preview...
+
+
+
+ body-include: ''
diff --git a/react-ui/.gitignore b/react-ui/.gitignore
new file mode 100644
index 0000000..82cadd2
--- /dev/null
+++ b/react-ui/.gitignore
@@ -0,0 +1,43 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# 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
+
+build
+
+pnpm-lock.yaml
\ No newline at end of file
diff --git a/react-ui/.husky/commit-msg b/react-ui/.husky/commit-msg
new file mode 100644
index 0000000..b04aa8f
--- /dev/null
+++ b/react-ui/.husky/commit-msg
@@ -0,0 +1,7 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# Export Git hook params
+export GIT_PARAMS=$*
+
+# npx --no-install fabric verify-commit
diff --git a/react-ui/.husky/pre-commit b/react-ui/.husky/pre-commit
new file mode 100644
index 0000000..6149072
--- /dev/null
+++ b/react-ui/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+# npx --no-install lint-staged
diff --git a/react-ui/.prettierignore b/react-ui/.prettierignore
new file mode 100644
index 0000000..7999ccd
--- /dev/null
+++ b/react-ui/.prettierignore
@@ -0,0 +1,22 @@
+**/*.svg
+.umi
+.umi-production
+/dist
+.dockerignore
+.DS_Store
+.eslintignore
+*.png
+*.toml
+docker
+.editorconfig
+Dockerfile*
+.gitignore
+.prettierignore
+LICENSE
+.eslintcache
+*.lock
+yarn-error.log
+.history
+CNAME
+/build
+/public
diff --git a/react-ui/.prettierrc.js b/react-ui/.prettierrc.js
new file mode 100644
index 0000000..3447a1a
--- /dev/null
+++ b/react-ui/.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/react-ui/CODE_OF_CONDUCT.md b/react-ui/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2b4571c
--- /dev/null
+++ b/react-ui/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/react-ui/LICENSE b/react-ui/LICENSE
new file mode 100644
index 0000000..b65958e
--- /dev/null
+++ b/react-ui/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019-present Alipay.inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/react-ui/README.md b/react-ui/README.md
new file mode 100644
index 0000000..2cf4d67
--- /dev/null
+++ b/react-ui/README.md
@@ -0,0 +1,133 @@
+Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)
+
+Ant Design Pro
+
+
+
+An out-of-box UI solution for enterprise applications as a React boilerplate.
+
+[](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)  
+
+[](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://umijs.org/) 
+
+
+
+
+
+- Preview: http://preview.pro.ant.design
+- Home Page: http://pro.ant.design
+- Documentation: http://pro.ant.design/docs/getting-started
+- ChangeLog: http://pro.ant.design/docs/changelog
+- FAQ: http://pro.ant.design/docs/faq
+- Mirror Site in China: http://ant-design-pro.gitee.io
+
+## 5.0 is out! 🎉🎉🎉
+
+[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)
+
+## Translation Recruitment :loudspeaker:
+
+We need your help: https://github.com/ant-design/ant-design-pro/issues/120
+
+## Features
+
+- :bulb: **TypeScript**: A language for application-scale JavaScript
+- :scroll: **Blocks**: Build page with block template
+- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
+- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
+- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
+- :iphone: **Responsive**: Designed for variable screen sizes
+- :art: **Theming**: Customizable theme with simple config
+- :globe_with_meridians: **International**: Built-in i18n solution
+- :gear: **Best Practices**: Solid workflow to make your code healthy
+- :1234: **Mock development**: Easy to use mock development solution
+- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests
+
+## Templates
+
+```
+- Dashboard
+ - Analytic
+ - Monitor
+ - Workspace
+- Form
+ - Basic Form
+ - Step Form
+ - Advanced From
+- List
+ - Standard Table
+ - Standard List
+ - Card List
+ - Search List (Project/Applications/Article)
+- Profile
+ - Simple Profile
+ - Advanced Profile
+- Account
+ - Account Center
+ - Account Settings
+- Result
+ - Success
+ - Failed
+- Exception
+ - 403
+ - 404
+ - 500
+- User
+ - Login
+ - Register
+ - Register Result
+```
+
+## Usage
+
+### Use bash
+
+We provide pro-cli to quickly initialize scaffolding.
+
+```bash
+# use npm
+npm i @ant-design/pro-cli -g
+pro create myapp
+```
+
+select umi version
+
+```shell
+🐂 Use umi@4 or umi@3 ? (Use arrow keys)
+❯ umi@4
+ umi@3
+```
+
+> If the umi@4 version is selected, full blocks are not yet supported.
+
+If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.
+
+```shell
+? 🚀 Full or a simple scaffold? (Use arrow keys)
+❯ simple
+ complete
+```
+
+Install dependencies:
+
+```shell
+$ cd myapp && tyarn
+// or
+$ cd myapp && npm install
+```
+
+## Browsers support
+
+Modern browsers.
+
+| [ ](http://godban.github.io/browsers-support-badges/)Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari | [ ](http://godban.github.io/browsers-support-badges/)Opera |
+| --- | --- | --- | --- | --- |
+| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
+
+## Contributing
+
+Any type of contribution is welcome, here are some examples of how you may contribute to this project:
+
+- Use Ant Design Pro in your daily work.
+- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
+- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
diff --git a/react-ui/config/config.ts b/react-ui/config/config.ts
new file mode 100644
index 0000000..cebef5c
--- /dev/null
+++ b/react-ui/config/config.ts
@@ -0,0 +1,157 @@
+// 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,
+ },
+ // keepalive: [/./],
+ // tabsLayout: {},
+ /**
+ * @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 中额外的 script
+ * @description 配置 中额外的 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',
+ },
+ requestRecord: {},
+});
diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts
new file mode 100644
index 0000000..e72ccdb
--- /dev/null
+++ b/react-ui/config/defaultSettings.ts
@@ -0,0 +1,29 @@
+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: false,
+ splitMenus: 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/react-ui/config/oneapi.json b/react-ui/config/oneapi.json
new file mode 100644
index 0000000..c77d988
--- /dev/null
+++ b/react-ui/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/react-ui/config/proxy.ts b/react-ui/config/proxy.ts
new file mode 100644
index 0000000..c290c6a
--- /dev/null
+++ b/react-ui/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/react-ui/config/routes.ts b/react-ui/config/routes.ts
new file mode 100644
index 0000000..ffad02f
--- /dev/null
+++ b/react-ui/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, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 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/react-ui/jest.config.ts b/react-ui/jest.config.ts
new file mode 100644
index 0000000..1de2a1a
--- /dev/null
+++ b/react-ui/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/react-ui/jsconfig.json b/react-ui/jsconfig.json
new file mode 100644
index 0000000..197bee5
--- /dev/null
+++ b/react-ui/jsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/react-ui/mock/listTableList.ts b/react-ui/mock/listTableList.ts
new file mode 100644
index 0000000..35ec3ce
--- /dev/null
+++ b/react-ui/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).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).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/react-ui/mock/notices.ts b/react-ui/mock/notices.ts
new file mode 100644
index 0000000..616c921
--- /dev/null
+++ b/react-ui/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/react-ui/mock/requestRecord.mock.js b/react-ui/mock/requestRecord.mock.js
new file mode 100644
index 0000000..6c59e19
--- /dev/null
+++ b/react-ui/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/react-ui/mock/route.ts b/react-ui/mock/route.ts
new file mode 100644
index 0000000..418d10f
--- /dev/null
+++ b/react-ui/mock/route.ts
@@ -0,0 +1,5 @@
+export default {
+ '/api/auth_routes': {
+ '/form/advanced-form': { authority: ['admin', 'user'] },
+ },
+};
diff --git a/react-ui/mock/user.ts b/react-ui/mock/user.ts
new file mode 100644
index 0000000..75edd34
--- /dev/null
+++ b/react-ui/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/react-ui/package.json b/react-ui/package.json
new file mode 100644
index 0000000..cd04521
--- /dev/null
+++ b/react-ui/package.json
@@ -0,0 +1,137 @@
+{
+ "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.0.0",
+ "@ant-design/pro-components": "^2.4.4",
+ "@ant-design/use-emotion-css": "1.0.4",
+ "@umijs/route-utils": "^4.0.1",
+ "antd": "^5.4.4",
+ "classnames": "^2.3.2",
+ "fabric": "^5.3.0",
+ "highlight.js": "^11.7.0",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.4",
+ "omit.js": "^2.0.2",
+ "query-string": "^8.1.0",
+ "rc-menu": "^9.8.4",
+ "rc-util": "^5.30.0",
+ "react": "^18.2.0",
+ "react-cropper": "^2.3.3",
+ "react-dev-inspector": "^1.8.1",
+ "react-dom": "^18.2.0",
+ "react-helmet-async": "^1.3.0",
+ "react-highlight": "^0.15.0"
+ },
+ "devDependencies": {
+ "@ant-design/pro-cli": "^3.1.0",
+ "@testing-library/react": "^14.0.0",
+ "@types/antd": "^1.0.0",
+ "@types/express": "^4.17.14",
+ "@types/jest": "^29.5.1",
+ "@types/lodash": "^4.14.194",
+ "@types/react": "^18.0.38",
+ "@types/react-dom": "^18.0.11",
+ "@types/react-helmet": "^6.1.5",
+ "@types/react-highlight": "^0.12.5",
+ "@umijs/lint": "^4.0.66",
+ "@umijs/max": "^4.0.66",
+ "cross-env": "^7.0.3",
+ "eslint": "^8.39.0",
+ "express": "^4.18.2",
+ "gh-pages": "^5.0.0",
+ "husky": "^8.0.3",
+ "jest": "^29.5.0",
+ "jest-environment-jsdom": "^29.5.0",
+ "lint-staged": "^13.2.0",
+ "mockjs": "^1.1.0",
+ "prettier": "^2.8.1",
+ "swagger-ui-dist": "^4.18.2",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.0.4",
+ "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/react-ui/public/CNAME b/react-ui/public/CNAME
new file mode 100644
index 0000000..30c2d4d
--- /dev/null
+++ b/react-ui/public/CNAME
@@ -0,0 +1 @@
+preview.pro.ant.design
\ No newline at end of file
diff --git a/react-ui/public/favicon.ico b/react-ui/public/favicon.ico
new file mode 100644
index 0000000..e2e9325
Binary files /dev/null and b/react-ui/public/favicon.ico differ
diff --git a/react-ui/public/icons/icon-128x128.png b/react-ui/public/icons/icon-128x128.png
new file mode 100644
index 0000000..48d0e23
Binary files /dev/null and b/react-ui/public/icons/icon-128x128.png differ
diff --git a/react-ui/public/icons/icon-192x192.png b/react-ui/public/icons/icon-192x192.png
new file mode 100644
index 0000000..938e9b5
Binary files /dev/null and b/react-ui/public/icons/icon-192x192.png differ
diff --git a/react-ui/public/icons/icon-512x512.png b/react-ui/public/icons/icon-512x512.png
new file mode 100644
index 0000000..21fc108
Binary files /dev/null and b/react-ui/public/icons/icon-512x512.png differ
diff --git a/react-ui/public/logo.svg b/react-ui/public/logo.svg
new file mode 100644
index 0000000..239bf69
--- /dev/null
+++ b/react-ui/public/logo.svg
@@ -0,0 +1 @@
+Group 28 Copy 5 Created with Sketch.
\ No newline at end of file
diff --git a/react-ui/public/pro_icon.svg b/react-ui/public/pro_icon.svg
new file mode 100644
index 0000000..e075b78
--- /dev/null
+++ b/react-ui/public/pro_icon.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/react-ui/public/scripts/loading.js b/react-ui/public/scripts/loading.js
new file mode 100644
index 0000000..c1ced54
--- /dev/null
+++ b/react-ui/public/scripts/loading.js
@@ -0,0 +1,202 @@
+/**
+ * loading 占位
+ * 解决首次加载时白屏的问题
+ */
+ (function () {
+ const _root = document.querySelector('#root');
+ if (_root && _root.innerHTML === '') {
+ _root.innerHTML = `
+
+
+
+
+
+ 正在加载资源
+
+
+ 初次加载资源可能需要较多时间 请耐心等待
+
+
+ `;
+ }
+})();
diff --git a/react-ui/src/access.ts b/react-ui/src/access.ts
new file mode 100644
index 0000000..8c11f31
--- /dev/null
+++ b/react-ui/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/react-ui/src/app.tsx b/react-ui/src/app.tsx
new file mode 100644
index 0000000..a5cf8e7
--- /dev/null
+++ b/react-ui/src/app.tsx
@@ -0,0 +1,234 @@
+import Footer from '@/components/Footer';
+import RightContent from '@/components/RightContent';
+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;
+ currentUser?: API.CurrentUser;
+ loading?: boolean;
+ fetchUserInfo?: () => Promise;
+}> {
+ 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,
+ };
+ }
+ return {
+ fetchUserInfo,
+ settings: defaultSettings as Partial,
+ };
+}
+
+// ProLayout 支持的api https://procomponents.ant.design/components/layout
+export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
+ return {
+ rightContentRender: () => ,
+ waterMarkProps: {
+ // content: initialState?.currentUser?.nickName,
+ },
+ menu: {
+ locale: false,
+ // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
+ params: {
+ userId: initialState?.currentUser?.userId,
+ },
+ request: async () => {
+ if (!initialState?.currentUser?.userId) {
+ return [];
+ }
+ // console.log('get menus')
+ // initialState.currentUser 中包含了所有用户信息
+ // console.log('get routers')
+ // setInitialState((preInitialState) => ({
+ // ...preInitialState,
+ // menus,
+ // }));
+ return getRemoteMenu();
+ },
+ },
+ footerRender: () => ,
+ 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
+ ? [
+
+
+ OpenAPI 文档
+ ,
+ ]
+ : [],
+ menuHeaderRender: undefined,
+ // 自定义 403 页面
+ // unAccessible: unAccessible
,
+ // 增加一个 loading 的状态
+ childrenRender: (children) => {
+ // if (initialState?.loading) return ;
+ return (
+ <>
+ {children}
+ {
+ 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/react-ui/src/components/DictTag/index.tsx b/react-ui/src/components/DictTag/index.tsx
new file mode 100644
index 0000000..2b2a791
--- /dev/null
+++ b/react-ui/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;
+
+export type DictTagProps = {
+ key?: string;
+ value?: string | number;
+ enums?: DictValueEnumObj;
+ options?: DictOptionType[];
+};
+
+const DictTag: React.FC = (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 (
+ {getTagText()}
+ )
+}
+
+
+export default DictTag;
\ No newline at end of file
diff --git a/react-ui/src/components/Footer/index.tsx b/react-ui/src/components/Footer/index.tsx
new file mode 100644
index 0000000..03ac146
--- /dev/null
+++ b/react-ui/src/components/Footer/index.tsx
@@ -0,0 +1,45 @@
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-components';
+import { useIntl } from '@umijs/max';
+import React from 'react';
+
+const Footer: React.FC = () => {
+ const intl = useIntl();
+ const defaultMessage = intl.formatMessage({
+ id: 'app.copyright.produced',
+ defaultMessage: '蚂蚁集团体验技术部出品',
+ });
+
+ const currentYear = new Date().getFullYear();
+
+ return (
+ ,
+ 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/react-ui/src/components/HeaderDropdown/index.tsx b/react-ui/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..8474be5
--- /dev/null
+++ b/react-ui/src/components/HeaderDropdown/index.tsx
@@ -0,0 +1,29 @@
+import { Dropdown } from 'antd';
+import type { DropDownProps } from 'antd/es/dropdown';
+import React from 'react';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import classNames from 'classnames';
+
+export type HeaderDropdownProps = {
+ overlayClassName?: string;
+ placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
+} & Omit;
+
+const HeaderDropdown: React.FC = ({ overlayClassName: cls, ...restProps }) => {
+ const className = useEmotionCss(({ token }) => {
+ return {
+ [`@media screen and (max-width: ${token.screenXS})`]: {
+ width: '100%',
+ },
+ };
+ });
+ return (
+ target.parentElement || document.body}
+ {...restProps}
+ />
+ );
+};
+
+export default HeaderDropdown;
diff --git a/react-ui/src/components/IconSelector/Category.tsx b/react-ui/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..dd0e93f
--- /dev/null
+++ b/react-ui/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 = props => {
+
+ const { icons, title, newIcons, theme } = props;
+ const intl = useIntl();
+ const [justCopied, setJustCopied] = React.useState(null);
+ const copyId = React.useRef(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(copyId.current);
+ }
+ },
+ [],
+ );
+
+ return (
+
+
{intl.formatMessage({
+ id: `app.docs.components.icon.category.${title}`,
+ defaultMessage: '信息',
+ })}
+
+ {icons.map(name => (
+
+ ))}
+
+
+ );
+};
+
+export default Category;
diff --git a/react-ui/src/components/IconSelector/CopyableIcon.tsx b/react-ui/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..371cba0
--- /dev/null
+++ b/react-ui/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,47 @@
+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 = ({
+ name,
+ justCopied,
+ onSelect,
+ theme,
+}) => {
+ const className = classNames({
+ copied: justCopied === name,
+ [theme]: !!theme,
+ });
+ return (
+ {
+ if (onSelect) {
+ onSelect(theme, name);
+ }
+ }}>
+
+ {React.createElement(allIcons[name], { className: styles.anticon })}
+
+ {/*
+ {name}
+ */}
+
+ );
+};
+
+export default CopyableIcon;
diff --git a/react-ui/src/components/IconSelector/IconPicSearcher.tsx b/react-ui/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/react-ui/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({
+ 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 (
+
+
+
+
+
+ {state.modelLoaded || (
+
+
+
+ )}
+ {state.modelLoaded && (
+ uploadFile(o.file as File)}
+ fileList={state.fileList}
+ showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
+ >
+
+
+
+
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-text'})}
+
+
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-hint'})}
+
+
+ )}
+
+
+ {state.icons.length > 0 && (
+
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.result-tip'})}
+
+ )}
+
+ {state.icons.length > 0 && (
+
+
+
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-icon'})}
+
+ {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-score'})}
+
+
+ )}
+
+ {state.icons.map(icon => {
+ const { type } = icon;
+ const iconName = `${type
+ .split('-')
+ .map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
+ .join('')}Outlined`;
+ return (
+
+
+
+ {React.createElement(allIcons[iconName])}
+
+
+
+
+
+
+ );
+ })}
+
+
+ {state.error && (
+
+ )}
+
+
+
+
+ );
+};
+
+export default PicSearcher;
diff --git a/react-ui/src/components/IconSelector/fields.ts b/react-ui/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/react-ui/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/react-ui/src/components/IconSelector/index.tsx b/react-ui/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/react-ui/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 = (props) => {
+ // const intl = useIntl();
+ // const { messages } = intl;
+ const { onSelect } = props;
+ const [displayState, setDisplayState] = React.useState({
+ 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(() => {
+ 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 }) => (
+ {
+ if (onSelect) {
+ onSelect(name, allIcons[name]);
+ }
+ }}
+ />
+ ));
+ return categoriesResult.length === 0 ? : categoriesResult;
+ }, [displayState.searchKey, displayState.theme]);
+ return (
+ <>
+
+ ,
+ value: ThemeType.Outlined
+ },
+ {
+ label: ,
+ value: ThemeType.Filled
+ },
+ {
+ label: ,
+ value: ThemeType.TwoTone
+ },
+ ]}
+ >
+ {/*
+ {messages['app.docs.components.icon.outlined']}
+
+
+ {messages['app.docs.components.icon.filled']}
+
+
+ {messages['app.docs.components.icon.two-tone']}
+ */}
+
+ handleSearchIcon(e.currentTarget.value)}
+ size="large"
+ autoFocus
+ suffix={ }
+ />
+
+ {renderCategories}
+ >
+ );
+};
+
+export default IconSelector
diff --git a/react-ui/src/components/IconSelector/style.less b/react-ui/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/react-ui/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/react-ui/src/components/IconSelector/themeIcons.tsx b/react-ui/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/react-ui/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 (
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+};
diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..9447493
--- /dev/null
+++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,176 @@
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { history, useModel } from '@umijs/max';
+import { Avatar, Spin } from 'antd';
+import { setAlpha } from '@ant-design/pro-components';
+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 { PageEnum } from '@/enums/pagesEnums';
+import { clearSessionToken } from '@/access';
+import { logout } from '@/services/system/auth';
+
+export type GlobalHeaderRightProps = {
+ menu?: boolean;
+};
+
+const Name = () => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+
+ const nameClassName = useEmotionCss(({ token }) => {
+ return {
+ width: '70px',
+ height: '48px',
+ overflow: 'hidden',
+ lineHeight: '48px',
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis',
+ [`@media only screen and (max-width: ${token.screenMD}px)`]: {
+ display: 'none',
+ },
+ };
+ });
+
+ return {currentUser?.nickName} ;
+};
+
+const AvatarLogo = () => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+
+ const avatarClassName = useEmotionCss(({ token }) => {
+ return {
+ marginRight: '8px',
+ color: token.colorPrimary,
+ verticalAlign: 'top',
+ background: setAlpha(token.colorBgContainer, 0.85),
+ [`@media only screen and (max-width: ${token.screenMD}px)`]: {
+ margin: 0,
+ },
+ };
+ });
+ return ;
+};
+
+const AvatarDropdown: React.FC = ({ menu }) => {
+ /**
+ * 退出登录,并且将当前的 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 !== PageEnum.LOGIN && !redirect) {
+ history.replace({
+ pathname: PageEnum.LOGIN,
+ search: stringify({
+ redirect: pathname + search,
+ }),
+ });
+ }
+ };
+ const actionClassName = useEmotionCss(({ token }) => {
+ return {
+ display: 'flex',
+ height: '48px',
+ marginLeft: 'auto',
+ overflow: 'hidden',
+ alignItems: 'center',
+ padding: '0 8px',
+ cursor: 'pointer',
+ borderRadius: token.borderRadius,
+ '&:hover': {
+ backgroundColor: token.colorBgTextHover,
+ },
+ };
+ });
+ 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 = (
+
+
+
+ );
+
+ if (!initialState) {
+ return loading;
+ }
+
+ const { currentUser } = initialState;
+
+ if (!currentUser || !currentUser.nickName) {
+ return loading;
+ }
+
+ const menuItems = [
+ ...(menu
+ ? [
+ {
+ key: 'center',
+ icon: ,
+ label: '个人中心',
+ },
+ {
+ key: 'settings',
+ icon: ,
+ label: '个人设置',
+ },
+ {
+ type: 'divider' as const,
+ },
+ ]
+ : []),
+ {
+ key: 'logout',
+ icon: ,
+ label: '退出登录',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default AvatarDropdown;
diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..52a49bb
--- /dev/null
+++ b/react-ui/src/components/RightContent/index.tsx
@@ -0,0 +1,57 @@
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { SelectLang, useModel } from '@umijs/max';
+import React from 'react';
+import Avatar from './AvatarDropdown';
+
+export type SiderTheme = 'light' | 'dark';
+
+const GlobalHeaderRight: React.FC = () => {
+ const className = useEmotionCss(() => {
+ return {
+ display: 'flex',
+ height: '48px',
+ marginLeft: 'auto',
+ overflow: 'hidden',
+ gap: 8,
+ };
+ });
+
+ const actionClassName = useEmotionCss(({ token }) => {
+ return {
+ display: 'flex',
+ float: 'right',
+ height: '48px',
+ marginLeft: 'auto',
+ overflow: 'hidden',
+ cursor: 'pointer',
+ padding: '0 12px',
+ borderRadius: token.borderRadius,
+ '&:hover': {
+ backgroundColor: token.colorBgTextHover,
+ },
+ };
+ });
+
+ const { initialState } = useModel('@@initialState');
+
+ if (!initialState || !initialState.settings) {
+ return null;
+ }
+
+ return (
+
+
{
+ window.open('https://pro.ant.design/docs/getting-started');
+ }}
+ >
+
+
+
+
+
+ );
+};
+export default GlobalHeaderRight;
diff --git a/react-ui/src/enums/httpEnum.ts b/react-ui/src/enums/httpEnum.ts
new file mode 100644
index 0000000..8b7ff1f
--- /dev/null
+++ b/react-ui/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/react-ui/src/enums/pagesEnums.ts b/react-ui/src/enums/pagesEnums.ts
new file mode 100644
index 0000000..4bdb5fd
--- /dev/null
+++ b/react-ui/src/enums/pagesEnums.ts
@@ -0,0 +1,4 @@
+
+export enum PageEnum {
+ LOGIN = '/user/login'
+}
\ No newline at end of file
diff --git a/react-ui/src/global.less b/react-ui/src/global.less
new file mode 100644
index 0000000..a9a0c51
--- /dev/null
+++ b/react-ui/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/react-ui/src/global.tsx b/react-ui/src/global.tsx
new file mode 100644
index 0000000..afa1fab
--- /dev/null
+++ b/react-ui/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 = (
+ {
+ notification.destroy(key);
+ reloadSW();
+ }}
+ >
+ {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+
+ );
+ 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/react-ui/src/hooks/net/dict.ts b/react-ui/src/hooks/net/dict.ts
new file mode 100644
index 0000000..9c2c41c
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US.ts b/react-ui/src/locales/en-US.ts
new file mode 100644
index 0000000..58407d2
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/app.ts b/react-ui/src/locales/en-US/app.ts
new file mode 100644
index 0000000..074dcb8
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/component.ts b/react-ui/src/locales/en-US/component.ts
new file mode 100644
index 0000000..3ba7eed
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/globalHeader.ts b/react-ui/src/locales/en-US/globalHeader.ts
new file mode 100644
index 0000000..60b6d4e
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/menu.ts b/react-ui/src/locales/en-US/menu.ts
new file mode 100644
index 0000000..eae3e53
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/pages.ts b/react-ui/src/locales/en-US/pages.ts
new file mode 100644
index 0000000..58ff2c8
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/pwa.ts b/react-ui/src/locales/en-US/pwa.ts
new file mode 100644
index 0000000..ed8d199
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/settingDrawer.ts b/react-ui/src/locales/en-US/settingDrawer.ts
new file mode 100644
index 0000000..a644905
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/en-US/settings.ts b/react-ui/src/locales/en-US/settings.ts
new file mode 100644
index 0000000..822dd00
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN.ts b/react-ui/src/locales/zh-CN.ts
new file mode 100644
index 0000000..06e2eac
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/app.ts b/react-ui/src/locales/zh-CN/app.ts
new file mode 100644
index 0000000..5be68e2
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/component.ts b/react-ui/src/locales/zh-CN/component.ts
new file mode 100644
index 0000000..1f1fead
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展开',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-CN/globalHeader.ts b/react-ui/src/locales/zh-CN/globalHeader.ts
new file mode 100644
index 0000000..9fd66a5
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/menu.ts b/react-ui/src/locales/zh-CN/menu.ts
new file mode 100644
index 0000000..fecb70a
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/job-log.ts b/react-ui/src/locales/zh-CN/monitor/job-log.ts
new file mode 100644
index 0000000..75cf8d0
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/job.ts b/react-ui/src/locales/zh-CN/monitor/job.ts
new file mode 100644
index 0000000..7e0d5b9
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/logininfor.ts b/react-ui/src/locales/zh-CN/monitor/logininfor.ts
new file mode 100644
index 0000000..959a6d2
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/onlineUser.ts b/react-ui/src/locales/zh-CN/monitor/onlineUser.ts
new file mode 100644
index 0000000..c693cde
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/operlog.ts b/react-ui/src/locales/zh-CN/monitor/operlog.ts
new file mode 100644
index 0000000..c45824c
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/monitor/server.ts b/react-ui/src/locales/zh-CN/monitor/server.ts
new file mode 100644
index 0000000..f887a22
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/pages.ts b/react-ui/src/locales/zh-CN/pages.ts
new file mode 100644
index 0000000..fc7abfb
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/pwa.ts b/react-ui/src/locales/zh-CN/pwa.ts
new file mode 100644
index 0000000..e950484
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/settingDrawer.ts b/react-ui/src/locales/zh-CN/settingDrawer.ts
new file mode 100644
index 0000000..3f44958
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/settings.ts b/react-ui/src/locales/zh-CN/settings.ts
new file mode 100644
index 0000000..df8af43
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/config.ts b/react-ui/src/locales/zh-CN/system/config.ts
new file mode 100644
index 0000000..5e1e764
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/dept.ts b/react-ui/src/locales/zh-CN/system/dept.ts
new file mode 100644
index 0000000..7774f2c
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/dict-data.ts b/react-ui/src/locales/zh-CN/system/dict-data.ts
new file mode 100644
index 0000000..db2c742
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/dict.ts b/react-ui/src/locales/zh-CN/system/dict.ts
new file mode 100644
index 0000000..cf00f66
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/menu.ts b/react-ui/src/locales/zh-CN/system/menu.ts
new file mode 100644
index 0000000..8a01e58
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/notice.ts b/react-ui/src/locales/zh-CN/system/notice.ts
new file mode 100644
index 0000000..ad55d51
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/post.ts b/react-ui/src/locales/zh-CN/system/post.ts
new file mode 100644
index 0000000..40e589b
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/role.ts b/react-ui/src/locales/zh-CN/system/role.ts
new file mode 100644
index 0000000..e88f979
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-CN/system/user.ts b/react-ui/src/locales/zh-CN/system/user.ts
new file mode 100644
index 0000000..7d676d0
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW.ts b/react-ui/src/locales/zh-TW.ts
new file mode 100644
index 0000000..6ad5f93
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/component.ts b/react-ui/src/locales/zh-TW/component.ts
new file mode 100644
index 0000000..ba48e29
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展開',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-TW/globalHeader.ts b/react-ui/src/locales/zh-TW/globalHeader.ts
new file mode 100644
index 0000000..ed58451
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/menu.ts b/react-ui/src/locales/zh-TW/menu.ts
new file mode 100644
index 0000000..0ef54c9
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/pages.ts b/react-ui/src/locales/zh-TW/pages.ts
new file mode 100644
index 0000000..f9e265b
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/pwa.ts b/react-ui/src/locales/zh-TW/pwa.ts
new file mode 100644
index 0000000..108a6e4
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/settingDrawer.ts b/react-ui/src/locales/zh-TW/settingDrawer.ts
new file mode 100644
index 0000000..454da28
--- /dev/null
+++ b/react-ui/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/react-ui/src/locales/zh-TW/settings.ts b/react-ui/src/locales/zh-TW/settings.ts
new file mode 100644
index 0000000..dd45151
--- /dev/null
+++ b/react-ui/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/react-ui/src/manifest.json b/react-ui/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/react-ui/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/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx
new file mode 100644
index 0000000..0263687
--- /dev/null
+++ b/react-ui/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 = () => (
+ history.push('/')}>
+ Back Home
+
+ }
+ />
+);
+
+export default NoFoundPage;
diff --git a/react-ui/src/pages/Monitor/Job/detail.tsx b/react-ui/src/pages/Monitor/Job/detail.tsx
new file mode 100644
index 0000000..e27bfb7
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type OperlogFormProps = {
+ onCancel: (flag?: boolean, formVals?: OperlogFormValueType) => void;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+};
+
+const OperlogForm: React.FC = (props) => {
+ const { values, statusOptions } = props;
+
+ useEffect(() => {}, [props]);
+
+ const intl = useIntl();
+
+ const misfirePolicy: any = {
+ '0': '默认策略',
+ '1': '立即执行',
+ '2': '执行一次',
+ '3': '放弃执行',
+ };
+
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+
+ 关闭
+ ,
+ ]}
+ >
+
+ }
+ >
+ {values.jobId}
+
+ }
+ >
+ {values.jobName}
+
+ }
+ >
+ {values.jobGroup}
+
+ }
+ >
+ {values.concurrent === '1' ? '禁止' : '允许'}
+
+
+ }
+ >
+ {misfirePolicy[values.misfirePolicy ? values.misfirePolicy : '0']}
+
+ }
+ >
+ {values.createTime?.toString()}
+
+ }
+ >
+ {getValueEnumLabel(statusOptions, values.status, '未知')}
+
+
+ }
+ >
+ {values.nextValidTime}
+
+
+ }
+ >
+ {values.cronExpression}
+
+
+ }
+ >
+ {values.invokeTarget}
+
+
+
+ );
+};
+
+export default OperlogForm;
diff --git a/react-ui/src/pages/Monitor/Job/edit.tsx b/react-ui/src/pages/Monitor/Job/edit.tsx
new file mode 100644
index 0000000..e4bfc74
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type JobFormProps = {
+ onCancel: (flag?: boolean, formVals?: JobFormData) => void;
+ onSubmit: (values: JobFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ jobGroupOptions: DictOptionType[];
+ statusOptions: DictValueEnumObj;
+};
+
+const JobForm: React.FC = (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) => {
+ props.onSubmit(values as JobFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ "生成表达式"}
+ onGetCaptcha={() => {
+ // form.setFieldValue('cronExpression', '0/20 * * * * ?');
+ return new Promise((resolve, reject) => {
+ reject();
+ });
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps={{
+ optionType: "button",
+ buttonStyle: "solid"
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps={{
+ optionType: "button",
+ buttonStyle: "solid"
+ }}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default JobForm;
diff --git a/react-ui/src/pages/Monitor/Job/index.tsx b/react-ui/src/pages/Monitor/Job/index.tsx
new file mode 100644
index 0000000..5d88059
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/index.tsx
@@ -0,0 +1,458 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [detailModalVisible, setDetailModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [jobGroupOptions, setJobGroupOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'jobId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'jobName',
+ valueType: 'text',
+ render: (dom, record) => {
+ return (
+ {
+ setDetailModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ {dom}
+
+ );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'jobGroup',
+ valueType: 'text',
+ valueEnum: jobGroupOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'invokeTarget',
+ valueType: 'textarea',
+ },
+ {
+ title: ,
+ dataIndex: 'cronExpression',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+
+ hidden={!access.hasPerms('monitor:job:edit')}
+ onClick={() => {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+
+ 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();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ {
+ 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}`);
+ }
+ }
+ }}
+ >
+ e.preventDefault()}>
+
+
+ 更多
+
+
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="jobId"
+ key="jobList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+ {
+ setDetailModalVisible(false);
+ setCurrentRow(undefined);
+ }}
+ open={detailModalVisible}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ />
+
+ );
+};
+
+export default JobTableList;
diff --git a/react-ui/src/pages/Monitor/JobLog/detail.tsx b/react-ui/src/pages/Monitor/JobLog/detail.tsx
new file mode 100644
index 0000000..560889c
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type JobLogFormProps = {
+ onCancel: (flag?: boolean, formVals?: JobLogFormValueType) => void;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+ jobGroupOptions: DictValueEnumObj;
+};
+
+const JobLogDetailForm: React.FC = (props) => {
+
+ const { values, statusOptions, jobGroupOptions } = props;
+
+ useEffect(() => {
+ }, []);
+
+ const intl = useIntl();
+ const handleOk = () => {
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+
+
+ }
+ >
+ {values.jobLogId}
+
+ }
+ >
+ {values.createTime?.toString()}
+
+ }
+ >
+ {values.jobName}
+
+ }
+ >
+ {getValueEnumLabel(jobGroupOptions, values.jobGroup, '无')}
+
+ }
+ >
+ {values.invokeTarget}
+
+ }
+ >
+ {values.jobMessage}
+
+ }
+ >
+ {values.exceptionInfo}
+
+ }
+ >
+ {getValueEnumLabel(statusOptions, values.status, '未知')}
+
+
+
+ );
+};
+
+export default JobLogDetailForm;
diff --git a/react-ui/src/pages/Monitor/JobLog/index.tsx b/react-ui/src/pages/Monitor/JobLog/index.tsx
new file mode 100644
index 0000000..3bca8a4
--- /dev/null
+++ b/react-ui/src/pages/Monitor/JobLog/index.tsx
@@ -0,0 +1,341 @@
+
+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();
+
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [jobGroupOptions, setJobGroupOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ const [queryParams, setQueryParams] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'jobLogId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'jobName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'jobGroup',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'invokeTarget',
+ valueType: 'textarea',
+ },
+ {
+ title: ,
+ dataIndex: 'jobMessage',
+ valueType: 'textarea',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalOpen(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="jobLogId"
+ key="job-logList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalOpen(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ setModalOpen(false);
+ setCurrentRow(undefined);
+ }}
+ open={modalOpen}
+ values={currentRow || {}}
+ statusOptions={statusOptions}
+ jobGroupOptions={jobGroupOptions}
+ />
+
+ );
+};
+
+export default JobLogTableList;
diff --git a/react-ui/src/pages/Monitor/Online/index.tsx b/react-ui/src/pages/Monitor/Online/index.tsx
new file mode 100644
index 0000000..2f81e3b
--- /dev/null
+++ b/react-ui/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();
+ const actionRef = useRef();
+ const access = useAccess();
+ const intl = useIntl();
+
+ useEffect(() => {}, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'tokenId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'deptName',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'ipaddr',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'loginLocation',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'browser',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'os',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'loginTime',
+ valueType: 'dateRange',
+ render: (_, record) => {record.loginTime} ,
+ hideInSearch: true,
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '60px',
+ valueType: 'option',
+ render: (_, record) => [
+
+ 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();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 强退
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+ 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}
+ />
+
+ );
+};
+
+export default OnlineUserTableList;
diff --git a/react-ui/src/pages/System/Config/edit.tsx b/react-ui/src/pages/System/Config/edit.tsx
new file mode 100644
index 0000000..6cb3942
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type ConfigFormProps = {
+ onCancel: (flag?: boolean, formVals?: ConfigFormData) => void;
+ onSubmit: (values: ConfigFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ configTypeOptions: DictValueEnumObj;
+};
+
+const ConfigForm: React.FC = (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) => {
+ props.onSubmit(values as ConfigFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default ConfigForm;
diff --git a/react-ui/src/pages/System/Config/index.tsx b/react-ui/src/pages/System/Config/index.tsx
new file mode 100644
index 0000000..a7c0d97
--- /dev/null
+++ b/react-ui/src/pages/System/Config/index.tsx
@@ -0,0 +1,396 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [configTypeOptions, setConfigTypeOptions] = useState([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_yes_no').then((data) => {
+ setConfigTypeOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'configId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'configName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'configKey',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'configValue',
+ valueType: 'textarea',
+ },
+ {
+ title: ,
+ dataIndex: 'configType',
+ valueType: 'select',
+ valueEnum: configTypeOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="configId"
+ key="configList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ {
+ handleRefreshCache();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default ConfigTableList;
diff --git a/react-ui/src/pages/System/Dept/edit.tsx b/react-ui/src/pages/System/Dept/edit.tsx
new file mode 100644
index 0000000..cb64813
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type DeptFormProps = {
+ onCancel: (flag?: boolean, formVals?: DeptFormData) => void;
+ onSubmit: (values: DeptFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ deptTree: DataNode[];
+ statusOptions: DictValueEnumObj;
+};
+
+const DeptForm: React.FC = (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) => {
+ props.onSubmit(values as DeptFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ {
+ return deptTree;
+ }}
+ placeholder="请选择上级部门"
+ rules={[
+ {
+ required: true,
+ message: (
+
+ ),
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default DeptForm;
diff --git a/react-ui/src/pages/System/Dept/index.tsx b/react-ui/src/pages/System/Dept/index.tsx
new file mode 100644
index 0000000..f8aba51
--- /dev/null
+++ b/react-ui/src/pages/System/Dept/index.tsx
@@ -0,0 +1,374 @@
+
+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, exportDept, 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 handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportDept();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const DeptTableList: React.FC = () => {
+ const formTableRef = useRef();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [deptTree, setDeptTree] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'deptName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'orderNum',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ 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);
+ }
+ });
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="deptId"
+ key="deptList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ getDeptList().then((res) => {
+ if (res.code === 200) {
+ setDeptTree(buildTreeData(res.data, 'deptId', 'deptName', '', '', ''));
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ } else {
+ message.warning(res.msg);
+ }
+ });
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default DeptTableList;
diff --git a/react-ui/src/pages/System/Dict/edit.tsx b/react-ui/src/pages/System/Dict/edit.tsx
new file mode 100644
index 0000000..882c1cb
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type DictTypeFormProps = {
+ onCancel: (flag?: boolean, formVals?: DictTypeFormData) => void;
+ onSubmit: (values: DictTypeFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+};
+
+const DictTypeForm: React.FC = (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) => {
+ props.onSubmit(values as DictTypeFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default DictTypeForm;
diff --git a/react-ui/src/pages/System/Dict/index.tsx b/react-ui/src/pages/System/Dict/index.tsx
new file mode 100644
index 0000000..bbe146a
--- /dev/null
+++ b/react-ui/src/pages/System/Dict/index.tsx
@@ -0,0 +1,392 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'dictId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'dictName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'dictType',
+ valueType: 'text',
+ render: (dom, record) => {
+ return (
+ {
+ history.push(`/system/dict-data/index/${record.dictId}`);
+ }}
+ >
+ {dom}
+
+ );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="dictId"
+ key="dictTypeList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default DictTableList;
diff --git a/react-ui/src/pages/System/DictData/edit.tsx b/react-ui/src/pages/System/DictData/edit.tsx
new file mode 100644
index 0000000..188e01b
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type DataFormProps = {
+ onCancel: (flag?: boolean, formVals?: DataFormData) => void;
+ onSubmit: (values: DataFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+};
+
+const DictDataForm: React.FC = (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) => {
+ props.onSubmit(values as DataFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default DictDataForm;
diff --git a/react-ui/src/pages/System/DictData/index.tsx b/react-ui/src/pages/System/DictData/index.tsx
new file mode 100644
index 0000000..c360ecc
--- /dev/null
+++ b/react-ui/src/pages/System/DictData/index.tsx
@@ -0,0 +1,437 @@
+
+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();
+
+ const [dictId, setDictId] = useState('');
+ const [dictType, setDictType] = useState('');
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [dictTypeOptions, setDictTypeOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'dictCode',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'dictLabel',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'dictType',
+ valueType: 'select',
+ hideInTable: true,
+ valueEnum: dictTypeOptions,
+ search: {
+ transform: (value) => {
+ setDictType(value);
+ return value;
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'dictValue',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'dictSort',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'remark',
+ valueType: 'textarea',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="dictCode"
+ key="dataList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow({ dictType: dictType, isDefault: 'N', status: '0' } as API.System.DictData);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default DictDataTableList;
diff --git a/react-ui/src/pages/System/Logininfor/edit.tsx b/react-ui/src/pages/System/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type LogininforFormProps = {
+ onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+ onSubmit: (values: LogininforFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC = (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) => {
+ props.onSubmit(values as LogininforFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default LogininforForm;
diff --git a/react-ui/src/pages/System/Logininfor/index.tsx b/react-ui/src/pages/System/Logininfor/index.tsx
new file mode 100644
index 0000000..4eafaa4
--- /dev/null
+++ b/react-ui/src/pages/System/Logininfor/index.tsx
@@ -0,0 +1,320 @@
+
+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();
+
+ const actionRef = useRef();
+ const [selectedRows, setSelectedRows] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_common_status', true).then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'infoId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'ipaddr',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'loginLocation',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'browser',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'os',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'msg',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'loginTime',
+ valueType: 'dateTime',
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="infoId"
+ key="logininforList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认清空所有数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleClean();
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认解锁该用户的数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleUnlock(selectedRows[0].userName);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+
+ );
+};
+
+export default LogininforTableList;
diff --git a/react-ui/src/pages/System/Menu/edit.tsx b/react-ui/src/pages/System/Menu/edit.tsx
new file mode 100644
index 0000000..e094255
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type MenuFormProps = {
+ onCancel: (flag?: boolean, formVals?: MenuFormData) => void;
+ onSubmit: (values: MenuFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ visibleOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+ menuTree: DataNode[];
+};
+
+const MenuForm: React.FC = (props) => {
+
+ const [form] = Form.useForm();
+
+ const [menuTypeId, setMenuTypeId] = useState('M');
+ const [menuIconName, setMenuIconName] = useState();
+ const [iconSelectorOpen, setIconSelectorOpen] = useState(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) => {
+ props.onSubmit(values as MenuFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ {
+ return menuTree;
+ }}
+ placeholder="请输入父菜单编号"
+ rules={[
+ {
+ required: true,
+ message: ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 0
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps={{
+ defaultValue: 'M',
+ onChange: (e) => {
+ setMenuTypeId(e.target.value);
+ },
+ }}
+ />
+ {
+ setIconSelectorOpen(true);
+ },
+ }}
+ placeholder="请输入菜单图标"
+ rules={[
+ {
+ required: false,
+ message: ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 1
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '1'
+ }}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 0
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '0'
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: '0'
+ }}
+ />
+
+ {
+ setIconSelectorOpen(false);
+ }}
+ footer={null}
+ >
+ {
+ form.setFieldsValue({ icon: name });
+ setMenuIconName(name);
+ setIconSelectorOpen(false);
+ }}
+ />
+
+
+ );
+};
+
+export default MenuForm;
diff --git a/react-ui/src/pages/System/Menu/index.tsx b/react-ui/src/pages/System/Menu/index.tsx
new file mode 100644
index 0000000..93144fb
--- /dev/null
+++ b/react-ui/src/pages/System/Menu/index.tsx
@@ -0,0 +1,367 @@
+
+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, exportMenu } 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 handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportMenu();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const MenuTableList: React.FC = () => {
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [menuTree, setMenuTree] = useState([]);
+ const [visibleOptions, setVisibleOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'menuName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'icon',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'orderNum',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'component',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'perms',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ rowKey="menuId"
+ key="menuList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default MenuTableList;
diff --git a/react-ui/src/pages/System/Notice/edit.tsx b/react-ui/src/pages/System/Notice/edit.tsx
new file mode 100644
index 0000000..0b111ce
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type NoticeFormProps = {
+ onCancel: (flag?: boolean, formVals?: NoticeFormData) => void;
+ onSubmit: (values: NoticeFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ noticeTypeOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+};
+
+const NoticeForm: React.FC = (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) => {
+ props.onSubmit(values as NoticeFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default NoticeForm;
diff --git a/react-ui/src/pages/System/Notice/index.tsx b/react-ui/src/pages/System/Notice/index.tsx
new file mode 100644
index 0000000..690407c
--- /dev/null
+++ b/react-ui/src/pages/System/Notice/index.tsx
@@ -0,0 +1,393 @@
+
+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, exportNotice } 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 handleExport = async () => {
+ const hide = message.loading('正在导出');
+ try {
+ await exportNotice();
+ hide();
+ message.success('导出成功');
+ return true;
+ } catch (error) {
+ hide();
+ message.error('导出失败,请重试');
+ return false;
+ }
+};
+
+
+const NoticeTableList: React.FC = () => {
+ const formTableRef = useRef();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [noticeTypeOptions, setNoticeTypeOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'noticeId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'noticeTitle',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'noticeType',
+ valueType: 'select',
+ valueEnum: noticeTypeOptions,
+ },
+ {
+ title: ,
+ dataIndex: 'noticeContent',
+ valueType: 'text',
+ hideInTable: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'remark',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="noticeId"
+ key="noticeList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default NoticeTableList;
diff --git a/react-ui/src/pages/System/Operlog/detail.tsx b/react-ui/src/pages/System/Operlog/detail.tsx
new file mode 100644
index 0000000..f5b4f65
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type OperlogFormProps = {
+ onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+ onSubmit: (values: OperlogFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ businessTypeOptions: DictValueEnumObj;
+ operatorTypeOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC = (props) => {
+
+ const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+ const intl = useIntl();
+ const handleOk = () => {
+ console.log("handle ok");
+ };
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ return (
+
+
+ }
+ >
+ {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+
+ }
+ >
+ {values.requestMethod}
+
+ }
+ >
+ {`${values.operName}/${values.operIp}`}
+
+ }
+ >
+ {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+
+ }
+ >
+ {values.method}
+
+ }
+ >
+ {values.operUrl}
+
+ }
+ >
+ {values.operParam}
+
+ }
+ >
+ {values.jsonResult}
+
+ }
+ >
+ {values.errorMsg}
+
+ }
+ >
+ {getValueEnumLabel(statusOptions, values.status)}
+
+ }
+ >
+ {values.operTime?.toString()}
+
+
+
+ );
+};
+
+export default OperlogDetailForm;
diff --git a/react-ui/src/pages/System/Operlog/index.tsx b/react-ui/src/pages/System/Operlog/index.tsx
new file mode 100644
index 0000000..d1d26e0
--- /dev/null
+++ b/react-ui/src/pages/System/Operlog/index.tsx
@@ -0,0 +1,363 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [businessTypeOptions, setBusinessTypeOptions] = useState([]);
+ const [operatorTypeOptions, setOperatorTypeOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'operId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'title',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'businessType',
+ valueType: 'select',
+ valueEnum: businessTypeOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'requestMethod',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'operatorType',
+ valueType: 'select',
+ valueEnum: operatorTypeOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'operName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'operIp',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'operLocation',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'operTime',
+ valueType: 'dateTime',
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '120px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 详细
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="operId"
+ key="operlogList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default OperlogTableList;
diff --git a/react-ui/src/pages/System/Post/edit.tsx b/react-ui/src/pages/System/Post/edit.tsx
new file mode 100644
index 0000000..555fcde
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type PostFormProps = {
+ onCancel: (flag?: boolean, formVals?: PostFormData) => void;
+ onSubmit: (values: PostFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ statusOptions: DictValueEnumObj;
+};
+
+const PostForm: React.FC = (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) => {
+ props.onSubmit(values as PostFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default PostForm;
diff --git a/react-ui/src/pages/System/Post/index.tsx b/react-ui/src/pages/System/Post/index.tsx
new file mode 100644
index 0000000..1055a5e
--- /dev/null
+++ b/react-ui/src/pages/System/Post/index.tsx
@@ -0,0 +1,364 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ const access = useAccess();
+
+ /** 国际化配置 */
+ const intl = useIntl();
+
+ useEffect(() => {
+ getDictValueEnum('sys_normal_disable').then((data) => {
+ setStatusOptions(data);
+ });
+ }, []);
+
+ const columns: ProColumns[] = [
+ {
+ title: ,
+ dataIndex: 'postId',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'postCode',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'postName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'postSort',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+ {
+ setModalVisible(true);
+ setCurrentRow(record);
+ }}
+ >
+ 编辑
+ ,
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemoveOne(record);
+ if (success) {
+ if (actionRef.current) {
+ actionRef.current.reload();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="postId"
+ key="postList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setCurrentRow(undefined);
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() {},
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+
+ );
+};
+
+export default PostTableList;
diff --git a/react-ui/src/pages/System/Role/authUser.tsx b/react-ui/src/pages/System/Role/authUser.tsx
new file mode 100644
index 0000000..c2eedb4
--- /dev/null
+++ b/react-ui/src/pages/System/Role/authUser.tsx
@@ -0,0 +1,273 @@
+
+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(false);
+
+ const actionRef = useRef();
+ const [selectedRows, setSelectedRows] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'deptId',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'nickName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'phonenumber',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '60px',
+ valueType: 'option',
+ render: (_, record) => [
+ }
+ 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();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 取消授权
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ rowKey="userId"
+ key="userList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ setModalVisible(true);
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await cancelAuthUserAll(roleId, selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ history.back();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {
+ 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;
+ })
+ }
+ />
+
+ );
+};
+
+export default AuthUserTableList;
diff --git a/react-ui/src/pages/System/Role/components/DataScope.tsx b/react-ui/src/pages/System/Role/components/DataScope.tsx
new file mode 100644
index 0000000..a3dcce0
--- /dev/null
+++ b/react-ui/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;
+
+export type DataScopeFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise;
+ open: boolean;
+ values: Partial;
+ deptTree: DataNode[];
+ deptCheckedKeys: string[];
+};
+
+const DataScopeForm: React.FC = (props) => {
+ const [form] = Form.useForm();
+
+ const { deptTree, deptCheckedKeys } = props;
+ const [dataScopeType, setDataScopeType] = useState('1');
+ const [deptIds, setDeptIds] = useState([]);
+ const [deptTreeExpandKey, setDeptTreeExpandKey] = useState([]);
+ const [checkStrictly, setCheckStrictly] = useState(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) => {
+ 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 (
+
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ {
+ setDataScopeType(value);
+ },
+ }}
+ />
+
+
+
+
+
+
+ {
+ 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));
+ }}
+ />
+
+
+
+
+
+ );
+};
+
+export default DataScopeForm;
diff --git a/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx b/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
new file mode 100644
index 0000000..fdcfb85
--- /dev/null
+++ b/react-ui/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) => Promise>>;
+};
+
+const UserSelectorModal: React.FC = (props) => {
+
+ const actionRef = useRef();
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'userId',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'userName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'nickName',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'phonenumber',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ hideInSearch: true,
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return ( );
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ hideInSearch: true,
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ }
+ ];
+
+ return (
+
+
+ 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);
+ },
+ }}
+ />
+
+ );
+};
+
+export default UserSelectorModal;
diff --git a/react-ui/src/pages/System/Role/edit.tsx b/react-ui/src/pages/System/Role/edit.tsx
new file mode 100644
index 0000000..580493a
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type RoleFormProps = {
+ onCancel: (flag?: boolean, formVals?: RoleFormData) => void;
+ onSubmit: (values: RoleFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ menuTree: DataNode[];
+ menuCheckedKeys: string[];
+ statusOptions: DictValueEnumObj;
+};
+
+const RoleForm: React.FC = (props) => {
+ const [form] = Form.useForm();
+ const { menuTree, menuCheckedKeys } = props;
+ const [menuIds, setMenuIds] = useState([]);
+ 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) => {
+ props.onSubmit({ ...values, menuIds } as RoleFormData);
+ };
+
+ return (
+
+
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: 1
+ }}
+ />
+ ,
+ },
+ ]}
+ fieldProps = {{
+ defaultValue: "0"
+ }}
+ />
+
+ {
+ return setMenuIds(checkedKeys.checked);
+ }}
+ />
+
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default RoleForm;
diff --git a/react-ui/src/pages/System/Role/index.tsx b/react-ui/src/pages/System/Role/index.tsx
new file mode 100644
index 0000000..9cb00e7
--- /dev/null
+++ b/react-ui/src/pages/System/Role/index.tsx
@@ -0,0 +1,511 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [dataScopeModalOpen, setDataScopeModalOpen] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [menuTree, setMenuTree] = useState();
+ const [menuIds, setMenuIds] = useState([]);
+ const [statusOptions, setStatusOptions] = useState([]);
+
+ 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[] = [
+ {
+ title: ,
+ dataIndex: 'roleId',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'roleName',
+ valueType: 'text',
+ },
+ {
+ title: ,
+ dataIndex: 'roleKey',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'roleSort',
+ valueType: 'text',
+ hideInSearch: true,
+ },
+ {
+ title: ,
+ dataIndex: 'status',
+ valueType: 'select',
+ valueEnum: statusOptions,
+ render: (_, record) => {
+ return (
+ showChangeStatusConfirm(record)}
+ />)
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'createTime',
+ valueType: 'dateRange',
+ render: (_, record) => {
+ return ({record.createTime.toString()} );
+ },
+ search: {
+ transform: (value) => {
+ return {
+ 'params[beginTime]': value[0],
+ 'params[endTime]': value[1],
+ };
+ },
+ },
+ },
+ {
+ title: ,
+ dataIndex: 'option',
+ width: '220px',
+ valueType: 'option',
+ render: (_, record) => [
+
+ 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);
+ }
+ });
+ }}
+ >
+ 编辑
+ ,
+
+ 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();
+ }
+ }
+ },
+ });
+ }}
+ >
+ 删除
+ ,
+ {
+ 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}`);
+ }
+ }
+ }}
+ >
+ e.preventDefault()}>
+
+
+ 更多
+
+
+ ,
+ ],
+ },
+ ];
+
+ return (
+
+ {contextHolder}
+
+
+ headerTitle={intl.formatMessage({
+ id: 'pages.searchTable.title',
+ defaultMessage: '信息',
+ })}
+ actionRef={actionRef}
+ formRef={formTableRef}
+ rowKey="roleId"
+ key="roleList"
+ search={{
+ labelWidth: 120,
+ }}
+ toolBarRender={() => [
+ {
+ 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);
+ }
+ });
+ }}
+ >
+
+ ,
+ {
+ Modal.confirm({
+ title: '是否确认删除所选数据项?',
+ icon: ,
+ content: '请谨慎操作',
+ async onOk() {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ onCancel() { },
+ });
+ }}
+ >
+
+
+ ,
+ {
+ handleExport();
+ }}
+ >
+
+
+ ,
+ ]}
+ 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);
+ },
+ }}
+ />
+
+ {selectedRows?.length > 0 && (
+
+
+ {selectedRows.length}
+
+
+ }
+ >
+ {
+ Modal.confirm({
+ title: '删除',
+ content: '确定删除该项吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: async () => {
+ const success = await handleRemove(selectedRows);
+ if (success) {
+ setSelectedRows([]);
+ actionRef.current?.reloadAndRest?.();
+ }
+ },
+ });
+ }}
+ >
+
+
+
+ )}
+ {
+ 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}
+ />
+ {
+ 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 || []}
+ />
+
+ );
+};
+
+export default RoleTableList;
diff --git a/react-ui/src/pages/System/User/components/AuthRole.tsx b/react-ui/src/pages/System/User/components/AuthRole.tsx
new file mode 100644
index 0000000..120d966
--- /dev/null
+++ b/react-ui/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;
+
+export type AuthRoleFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise;
+ open: boolean;
+ roleIds: number[];
+ roles: string[];
+};
+
+const AuthRoleForm: React.FC = (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) => {
+ props.onSubmit(values as FormValueType);
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default AuthRoleForm;
diff --git a/react-ui/src/pages/System/User/components/DeptTree.tsx b/react-ui/src/pages/System/User/components/DeptTree.tsx
new file mode 100644
index 0000000..d1a085c
--- /dev/null
+++ b/react-ui/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;
+};
+
+const DeptTree: React.FC = (props) => {
+ const [treeData, setTreeData] = useState([]);
+ const [expandedKeys, setExpandedKeys] = useState([]);
+ const [autoExpandParent, setAutoExpandParent] = useState(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 (
+
+ );
+};
+
+export default DeptTree;
diff --git a/react-ui/src/pages/System/User/components/ResetPwd.tsx b/react-ui/src/pages/System/User/components/ResetPwd.tsx
new file mode 100644
index 0000000..5523cd6
--- /dev/null
+++ b/react-ui/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;
+
+export type UpdateFormProps = {
+ onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+ onSubmit: (values: FormValueType) => Promise;
+ open: boolean;
+ values: Partial;
+};
+
+const UpdateForm: React.FC = (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) => {
+ props.onSubmit({ ...values, userId } as FormValueType);
+ };
+
+ const checkPassword = (rule: any, value: string) => {
+ if (value === loginPassword) {
+ // 校验条件自定义
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次密码输入不一致'));
+ };
+
+ return (
+
+
+ 请输入用户{props.values.userName}的新密码!
+
+
+
+
+ );
+};
+
+export default UpdateForm;
diff --git a/react-ui/src/pages/System/User/edit.tsx b/react-ui/src/pages/System/User/edit.tsx
new file mode 100644
index 0000000..31d442b
--- /dev/null
+++ b/react-ui/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 & Partial;
+
+export type UserFormProps = {
+ onCancel: (flag?: boolean, formVals?: UserFormData) => void;
+ onSubmit: (values: UserFormData) => Promise;
+ open: boolean;
+ values: Partial;
+ sexOptions: DictValueEnumObj;
+ statusOptions: DictValueEnumObj;
+ postIds: number[];
+ posts: string[];
+ roleIds: number[];
+ roles: string[];
+ depts: DataNode[];
+};
+
+const UserForm: React.FC = (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) => {
+ props.onSubmit(values as UserFormData);
+ };
+
+ return (
+
+
+
+ ),
+ },
+ ]}
+ />
+ {
+ return depts;
+ }}
+ placeholder="请输入用户部门"
+ colProps={{ md: 12, xl: 12 }}
+ rules={[
+ {
+ required: true,
+ message: (
+
+ ),
+ },
+ ]}
+ />
+
+ ),
+ },
+ ]}
+ />
+
+ ),
+ },
+ ]}
+ />
+
+ ,
+ },
+ ]}
+ />
+
+ ),
+ },
+ ]}
+ />
+
+ ),
+ },
+ ]}
+ />
+
+
+ ,
+ },
+ ]}
+ />
+
+
+ );
+};
+
+export default UserForm;
diff --git a/react-ui/src/pages/System/User/index.tsx b/react-ui/src/pages/System/User/index.tsx
new file mode 100644
index 0000000..6b4f388
--- /dev/null
+++ b/react-ui/src/pages/System/User/index.tsx
@@ -0,0 +1,559 @@
+
+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();
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [resetPwdModalVisible, setResetPwdModalVisible] = useState(false);
+ const [authRoleModalVisible, setAuthRoleModalVisible] = useState(false);
+
+ const actionRef = useRef();
+ const [currentRow, setCurrentRow] = useState();
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const [selectDept, setSelectDept] = useState({ id: 0 });
+ const [sexOptions, setSexOptions] = useState([]);
+ const [statusOptions, setStatusOptions] = useState