1.0
|
@ -0,0 +1,45 @@
|
|||
######################################################################
|
||||
# Build Tools
|
||||
|
||||
.gradle
|
||||
/build/
|
||||
**/node_modules
|
||||
!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
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/*
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
|
||||
######################################################################
|
||||
# Others
|
||||
*.log
|
||||
*.xml.versionsBackup
|
||||
*.swp
|
||||
|
||||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 RuoYi
|
||||
|
||||
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.
|
|
@ -0,0 +1,85 @@
|
|||
## 平台简介
|
||||
|
||||
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
|
||||
* 前端采用Vue、Element UI。
|
||||
* 后端采用Spring Boot、Spring Security、Redis & Jwt。
|
||||
* 权限认证使用Jwt,支持多终端认证系统。
|
||||
* 支持加载动态权限菜单,多方式轻松权限控制。
|
||||
* 高效率开发,使用代码生成器可以一键生成前后端代码。
|
||||
* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
|
||||
* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
|
||||
* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。
|
||||
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)
|
||||
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)
|
||||
|
||||
## 内置功能
|
||||
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 通知公告:系统通知公告信息发布维护。
|
||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
10. 登录日志:系统登录日志记录查询包含登录异常。
|
||||
11. 在线用户:当前系统中活跃用户状态监控。
|
||||
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||
13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
|
||||
14. 系统接口:根据业务代码自动生成相关的api接口文档。
|
||||
15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
|
||||
16. 缓存监控:对系统的缓存信息查询,命令统计等。
|
||||
17. 在线构建器:拖动表单元素生成相应的HTML代码。
|
||||
18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
|
||||
|
||||
## 在线体验
|
||||
|
||||
- admin/admin123
|
||||
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
|
||||
|
||||
演示地址:http://vue.ruoyi.vip
|
||||
文档地址:http://doc.ruoyi.vip
|
||||
|
||||
## 演示图
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 若依前后端分离交流群
|
||||
|
||||
QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) 点击按钮入群。
|
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
echo.
|
||||
echo [信息] 清理工程target生成路径。
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
cd ..
|
||||
call mvn clean
|
||||
|
||||
pause
|
|
@ -0,0 +1,12 @@
|
|||
@echo off
|
||||
echo.
|
||||
echo [信息] 打包Web工程,生成war/jar包文件。
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
cd ..
|
||||
call mvn clean package -Dmaven.test.skip=true
|
||||
|
||||
pause
|
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
echo.
|
||||
echo [信息] 使用Jar命令运行Web工程。
|
||||
echo.
|
||||
|
||||
cd %~dp0
|
||||
cd ../ruoyi-admin/target
|
||||
|
||||
set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
java -jar %JAVA_OPTS% ruoyi-admin.jar
|
||||
|
||||
cd bin
|
||||
pause
|
|
@ -0,0 +1,266 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<version>3.7.0</version>
|
||||
|
||||
<name>ruoyi</name>
|
||||
<url>http://www.ruoyi.vip</url>
|
||||
<description>若依管理系统</description>
|
||||
|
||||
<properties>
|
||||
<ruoyi.version>3.7.0</ruoyi.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<druid.version>1.2.6</druid.version>
|
||||
<bitwalker.version>1.21</bitwalker.version>
|
||||
<swagger.version>3.0.0</swagger.version>
|
||||
<kaptcha.version>2.3.2</kaptcha.version>
|
||||
<mybatis-spring-boot.version>2.1.4</mybatis-spring-boot.version>
|
||||
<pagehelper.boot.version>1.3.1</pagehelper.boot.version>
|
||||
<fastjson.version>1.2.76</fastjson.version>
|
||||
<oshi.version>5.8.0</oshi.version>
|
||||
<jna.version>5.8.0</jna.version>
|
||||
<commons.io.version>2.11.0</commons.io.version>
|
||||
<commons.fileupload.version>1.4</commons.fileupload.version>
|
||||
<commons.collections.version>3.2.2</commons.collections.version>
|
||||
<poi.version>4.1.2</poi.version>
|
||||
<velocity.version>1.7</velocity.version>
|
||||
<jwt.version>0.9.1</jwt.version>
|
||||
</properties>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>2.2.13.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里数据库连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 解析客户端操作系统、浏览器等 -->
|
||||
<dependency>
|
||||
<groupId>eu.bitwalker</groupId>
|
||||
<artifactId>UserAgentUtils</artifactId>
|
||||
<version>${bitwalker.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringBoot集成mybatis框架 -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>${mybatis-spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- pagehelper 分页插件 -->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>${pagehelper.boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 获取系统信息 -->
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>${oshi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger3依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- io常用工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons.io.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件上传工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>${commons.fileupload.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- excel工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- velocity代码生成使用模板 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity</artifactId>
|
||||
<version>${velocity.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- collections工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>${commons.collections.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里JSON解析器 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Token生成与解析-->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<dependency>
|
||||
<groupId>com.github.penggle</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>${kaptcha.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 定时任务-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-quartz</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-framework</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-admin</module>
|
||||
<module>ruoyi-framework</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-quartz</module>
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-common</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
/lambda/
|
||||
/scripts
|
||||
/config
|
||||
.history
|
||||
public
|
||||
dist
|
||||
.umi
|
||||
mock
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
extends: [require.resolve('@umijs/fabric/dist/eslint')],
|
||||
globals: {
|
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
|
||||
page: true,
|
||||
REACT_APP_ENV: true,
|
||||
},
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
# 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
|
||||
/.vscode
|
||||
|
||||
# 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
|
||||
|
||||
# screenshot
|
||||
screenshot
|
||||
.firebase
|
||||
.eslintcache
|
||||
|
||||
build
|
|
@ -0,0 +1,23 @@
|
|||
**/*.svg
|
||||
package.json
|
||||
.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
|
|
@ -0,0 +1,5 @@
|
|||
const fabric = require('@umijs/fabric');
|
||||
|
||||
module.exports = {
|
||||
...fabric.prettier,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const fabric = require('@umijs/fabric');
|
||||
|
||||
module.exports = {
|
||||
...fabric.stylelint,
|
||||
};
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,57 @@
|
|||
# Ant Design Pro
|
||||
|
||||
This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
|
||||
|
||||
## Environment Prepare
|
||||
|
||||
Install `node_modules`:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
## Provided Scripts
|
||||
|
||||
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.
|
||||
|
||||
Scripts provided in `package.json`. It's safe to modify or add additional script:
|
||||
|
||||
### Start project
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### Build project
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Check code style
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
You can also use script to auto fix some lint error:
|
||||
|
||||
```bash
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### Test code
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## More
|
||||
|
||||
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).
|
|
@ -0,0 +1,15 @@
|
|||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
// https://github.com/zthxxx/react-dev-inspector
|
||||
'react-dev-inspector/plugins/umi/react-inspector',
|
||||
],
|
||||
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
|
||||
inspectorConfig: {
|
||||
exclude: [],
|
||||
babelPlugins: [],
|
||||
babelOptions: {},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,534 @@
|
|||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi';
|
||||
import defaultSettings from './defaultSettings';
|
||||
import proxy from './proxy';
|
||||
const { REACT_APP_ENV } = process.env;
|
||||
export default defineConfig({
|
||||
hash: true,
|
||||
antd: {},
|
||||
dva: {
|
||||
hmr: true,
|
||||
},
|
||||
history: {
|
||||
type: 'browser',
|
||||
},
|
||||
locale: {
|
||||
// default zh-CN
|
||||
default: 'zh-CN',
|
||||
antd: true,
|
||||
// default true, when it is true, will use `navigator.language` overwrite default
|
||||
baseNavigator: true,
|
||||
},
|
||||
dynamicImport: {
|
||||
loading: '@/components/PageLoading/index',
|
||||
},
|
||||
targets: {
|
||||
ie: 11,
|
||||
},
|
||||
// umi routes: https://umijs.org/docs/routing
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BlankLayout',
|
||||
routes: [
|
||||
{
|
||||
path: '/user',
|
||||
component: '../layouts/UserLayout',
|
||||
routes: [
|
||||
{
|
||||
path: '/user/login',
|
||||
name: 'login',
|
||||
component: './User/login',
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
redirect: '/user/login',
|
||||
},
|
||||
{
|
||||
name: 'register-result',
|
||||
icon: 'smile',
|
||||
path: '/user/register-result',
|
||||
component: './user/register-result',
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
icon: 'smile',
|
||||
path: '/user/register',
|
||||
component: './user/register',
|
||||
},
|
||||
{
|
||||
component: '404',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/SecurityBasicLayout',
|
||||
Routes: ['src/pages/Authorized'],
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard/analysis',
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
icon: 'dashboard',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard/analysis',
|
||||
},
|
||||
{
|
||||
name: 'analysis',
|
||||
icon: 'smile',
|
||||
path: '/dashboard/analysis',
|
||||
component: './dashboard/analysis',
|
||||
},
|
||||
{
|
||||
name: 'monitor',
|
||||
icon: 'smile',
|
||||
path: '/dashboard/monitor',
|
||||
component: './dashboard/monitor',
|
||||
},
|
||||
{
|
||||
name: 'workplace',
|
||||
icon: 'smile',
|
||||
path: '/dashboard/workplace',
|
||||
component: './dashboard/workplace',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
icon: 'BugOutlined',
|
||||
path: '/test',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/test/mqtt',
|
||||
},
|
||||
{
|
||||
name: 'mqtt',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/test/mqtt',
|
||||
component: './mqtt',
|
||||
},
|
||||
{
|
||||
name: 'userInfo',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/test/user',
|
||||
component: 'system/user/index',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'system',
|
||||
icon: 'BugOutlined',
|
||||
path: '/system',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/system/user',
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/user',
|
||||
component: 'system/user/index',
|
||||
},
|
||||
{
|
||||
name: 'menu',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/menu',
|
||||
component: 'system/menu/index',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/role',
|
||||
component: 'system/role/index',
|
||||
},
|
||||
{
|
||||
name: 'dept',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/dept',
|
||||
component: 'system/dept/index',
|
||||
},
|
||||
{
|
||||
name: 'post',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/post',
|
||||
component: 'system/post/index',
|
||||
},
|
||||
{
|
||||
name: 'dict',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/dict',
|
||||
component: 'system/dict/index',
|
||||
},
|
||||
{
|
||||
name: 'dictData',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/dict-data/index/:id?',
|
||||
component: 'system/dictData/index',
|
||||
},
|
||||
{
|
||||
name: 'config',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/config',
|
||||
component: 'system/config/index',
|
||||
},
|
||||
{
|
||||
name: 'notice',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/notice',
|
||||
component: 'system/notice/index',
|
||||
},
|
||||
{
|
||||
name: 'log',
|
||||
icon: 'BugOutlined',
|
||||
path: '/system/log/',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/system/log/operlog',
|
||||
},
|
||||
{
|
||||
name: 'operlog',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/log/operlog',
|
||||
component: 'monitor/operlog',
|
||||
},
|
||||
{
|
||||
name: 'loginInfo',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/system/log/logininfor',
|
||||
component: 'monitor/logininfor',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'monitor',
|
||||
icon: 'BugOutlined',
|
||||
path: '/monitor',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/monitor/online',
|
||||
},
|
||||
{
|
||||
name: 'onlineUser',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/online',
|
||||
component: 'monitor/online',
|
||||
},
|
||||
{
|
||||
name: 'job',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/job',
|
||||
component: 'monitor/job',
|
||||
},
|
||||
{
|
||||
name: 'joblog',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/job-log/index/:jobId?',
|
||||
component: 'monitor/joblog',
|
||||
},
|
||||
{
|
||||
name: 'druid',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/druid',
|
||||
component: 'monitor/druid',
|
||||
},
|
||||
{
|
||||
name: 'serverInfo',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/server',
|
||||
component: 'monitor/server',
|
||||
},
|
||||
{
|
||||
name: 'cacheInfo',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/monitor/cache',
|
||||
component: 'monitor/cache',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tool',
|
||||
icon: 'BugOutlined',
|
||||
path: '/tool',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/tool/gen',
|
||||
},
|
||||
{
|
||||
name: 'gen',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/tool/gen',
|
||||
component: 'tool/gen/index',
|
||||
},
|
||||
{
|
||||
name: 'genEdit',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/tool/gen/edit',
|
||||
component: 'tool/gen/edit',
|
||||
},
|
||||
{
|
||||
name: 'genImport',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/tool/gen/import',
|
||||
component: 'tool/gen/import',
|
||||
},
|
||||
{
|
||||
name: 'buildForm',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/tool/build',
|
||||
component: 'tool/build',
|
||||
},
|
||||
{
|
||||
name: 'swagger',
|
||||
icon: 'PartitionOutlined',
|
||||
path: '/tool/swagger',
|
||||
component: 'tool/swagger',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
icon: 'form',
|
||||
name: 'form',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/form/basic-form',
|
||||
},
|
||||
{
|
||||
name: 'basic-form',
|
||||
icon: 'smile',
|
||||
path: '/form/basic-form',
|
||||
component: './form/basic-form',
|
||||
},
|
||||
{
|
||||
name: 'step-form',
|
||||
icon: 'smile',
|
||||
path: '/form/step-form',
|
||||
component: './form/step-form',
|
||||
},
|
||||
{
|
||||
name: 'advanced-form',
|
||||
icon: 'smile',
|
||||
path: '/form/advanced-form',
|
||||
component: './form/advanced-form',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/list',
|
||||
icon: 'table',
|
||||
name: 'list',
|
||||
routes: [
|
||||
{
|
||||
path: '/list/search',
|
||||
name: 'search-list',
|
||||
component: './list/search',
|
||||
routes: [
|
||||
{
|
||||
path: '/list/search',
|
||||
redirect: '/list/search/articles',
|
||||
},
|
||||
{
|
||||
name: 'articles',
|
||||
icon: 'smile',
|
||||
path: '/list/search/articles',
|
||||
component: './list/search/articles',
|
||||
},
|
||||
{
|
||||
name: 'projects',
|
||||
icon: 'smile',
|
||||
path: '/list/search/projects',
|
||||
component: './list/search/projects',
|
||||
},
|
||||
{
|
||||
name: 'applications',
|
||||
icon: 'smile',
|
||||
path: '/list/search/applications',
|
||||
component: './list/search/applications',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/list/table-list',
|
||||
},
|
||||
{
|
||||
name: 'table-list',
|
||||
icon: 'smile',
|
||||
path: '/list/table-list',
|
||||
component: './list/table-list',
|
||||
},
|
||||
{
|
||||
name: 'basic-list',
|
||||
icon: 'smile',
|
||||
path: '/list/basic-list',
|
||||
component: './list/basic-list',
|
||||
},
|
||||
{
|
||||
name: 'card-list',
|
||||
icon: 'smile',
|
||||
path: '/list/card-list',
|
||||
component: './list/card-list',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
icon: 'profile',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/profile/basic',
|
||||
},
|
||||
{
|
||||
name: 'basic',
|
||||
icon: 'smile',
|
||||
path: '/profile/basic',
|
||||
component: './profile/basic',
|
||||
},
|
||||
{
|
||||
name: 'advanced',
|
||||
icon: 'smile',
|
||||
path: '/profile/advanced',
|
||||
component: './profile/advanced',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'result',
|
||||
icon: 'CheckCircleOutlined',
|
||||
path: '/result',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/result/success',
|
||||
},
|
||||
{
|
||||
name: 'success',
|
||||
icon: 'smile',
|
||||
path: '/result/success',
|
||||
component: './result/success',
|
||||
},
|
||||
{
|
||||
name: 'fail',
|
||||
icon: 'smile',
|
||||
path: '/result/fail',
|
||||
component: './result/fail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'exception',
|
||||
icon: 'warning',
|
||||
path: '/exception',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/exception/403',
|
||||
},
|
||||
{
|
||||
name: '403',
|
||||
icon: 'smile',
|
||||
path: '/exception/403',
|
||||
component: './exception/403',
|
||||
},
|
||||
{
|
||||
name: '404',
|
||||
icon: 'smile',
|
||||
path: '/exception/404',
|
||||
component: './exception/404',
|
||||
},
|
||||
{
|
||||
name: '500',
|
||||
icon: 'smile',
|
||||
path: '/exception/500',
|
||||
component: './exception/500',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'account',
|
||||
icon: 'user',
|
||||
path: '/account',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/account/center',
|
||||
},
|
||||
{
|
||||
name: 'center',
|
||||
icon: 'smile',
|
||||
path: '/account/center',
|
||||
component: './account/center',
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
icon: 'smile',
|
||||
path: '/account/settings',
|
||||
component: './account/settings',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'editor',
|
||||
icon: 'highlight',
|
||||
path: '/editor',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/editor/flow',
|
||||
},
|
||||
{
|
||||
name: 'flow',
|
||||
icon: 'smile',
|
||||
path: '/editor/flow',
|
||||
component: './editor/flow',
|
||||
},
|
||||
{
|
||||
name: 'mind',
|
||||
icon: 'smile',
|
||||
path: '/editor/mind',
|
||||
component: './editor/mind',
|
||||
},
|
||||
{
|
||||
name: 'koni',
|
||||
icon: 'smile',
|
||||
path: '/editor/koni',
|
||||
component: './editor/koni',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: '404',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
|
||||
theme: {
|
||||
'primary-color': defaultSettings.primaryColor,
|
||||
},
|
||||
title: false,
|
||||
ignoreMomentLocale: true,
|
||||
proxy: proxy[REACT_APP_ENV || 'dev'],
|
||||
manifest: {
|
||||
basePath: '/',
|
||||
},
|
||||
esbuild: {},
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
|
||||
type DefaultSettings = Partial<ProSettings> & {
|
||||
pwa: boolean;
|
||||
};
|
||||
|
||||
const proSettings: DefaultSettings = {
|
||||
navTheme: 'dark',
|
||||
// 拂晓蓝
|
||||
// primaryColor: '#1890ff',
|
||||
primaryColor: '#13C2C2',
|
||||
layout: 'side',
|
||||
contentWidth: 'Fluid',
|
||||
fixedHeader: false,
|
||||
fixSiderbar: true,
|
||||
colorWeak: false,
|
||||
title: 'Ant Design Pro',
|
||||
pwa: false,
|
||||
iconfontUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
|
||||
};
|
||||
|
||||
export type { DefaultSettings };
|
||||
|
||||
export default proSettings;
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
|
||||
* 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
|
||||
*/
|
||||
export default {
|
||||
dev: {
|
||||
'/api/': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/api': '' },
|
||||
},
|
||||
'/profile/avatar/' : {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
test: {
|
||||
'/api/': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
'/api/': {
|
||||
target: 'your pre url',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
export default [
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BlankLayout',
|
||||
routes: [
|
||||
{
|
||||
path: '/user',
|
||||
component: '../layouts/UserLayout',
|
||||
routes: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/user/login',
|
||||
component: './User/login',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/SecurityLayout',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BasicLayout',
|
||||
authority: ['admin', 'user'],
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/welcome',
|
||||
},
|
||||
{
|
||||
path: '/welcome',
|
||||
name: 'welcome',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
icon: 'crown',
|
||||
component: './Admin',
|
||||
authority: ['admin'],
|
||||
routes: [
|
||||
{
|
||||
path: '/admin/sub-page',
|
||||
name: 'sub-page',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
authority: ['admin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'list.table-list',
|
||||
icon: 'table',
|
||||
path: '/list',
|
||||
component: './TableList',
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
testURL: 'http://localhost:8000',
|
||||
testEnvironment: './tests/PuppeteerEnvironment',
|
||||
verbose: false,
|
||||
globals: {
|
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
|
||||
localStorage: null,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Request, Response } from 'express';
|
||||
import { parse } from 'url';
|
||||
import { TableListItem, TableListParams } from '@/pages/TableList/data';
|
||||
|
||||
// mock tableListDataSource
|
||||
const genList = (current: number, pageSize: number) => {
|
||||
const tableListDataSource: TableListItem[] = [];
|
||||
|
||||
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: new Date(),
|
||||
createdAt: new Date(),
|
||||
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 TableListParams;
|
||||
|
||||
let dataSource = [...tableListDataSource].slice(
|
||||
((current as number) - 1) * (pageSize as number),
|
||||
(current as number) * (pageSize as number),
|
||||
);
|
||||
const sorter = JSON.parse(params.sorter as any);
|
||||
if (sorter) {
|
||||
dataSource = dataSource.sort((prev, next) => {
|
||||
let sortNumber = 0;
|
||||
Object.keys(sorter).forEach((key) => {
|
||||
if (sorter[key] === 'descend') {
|
||||
if (prev[key] - next[key] > 0) {
|
||||
sortNumber += -1;
|
||||
} else {
|
||||
sortNumber += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (prev[key] - next[key] > 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).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.currentPage}`, 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 = {
|
||||
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: new Date(),
|
||||
createdAt: new Date(),
|
||||
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,
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
import { Request, Response } from 'express';
|
||||
|
||||
const getNotices = (req: Request, res: Response) => {
|
||||
res.json([
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
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,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
'/api/auth_routes': {
|
||||
'/form/advanced-form': { authority: ['admin', 'user'] },
|
||||
},
|
||||
};
|
|
@ -0,0 +1,165 @@
|
|||
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');
|
||||
}
|
||||
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default {
|
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/getInfo': {
|
||||
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',
|
||||
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 === 'admin123' && userName === 'admin') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (password === 'ant' && userName === 'user') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'user',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === 'mobile') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({
|
||||
status: 'error',
|
||||
type,
|
||||
currentAuthority: 'guest',
|
||||
});
|
||||
},
|
||||
'POST /api/register': (req: Request, res: Response) => {
|
||||
res.send({ status: 'ok', currentAuthority: 'user' });
|
||||
},
|
||||
'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: 'Unauthorized',
|
||||
message: 'Unauthorized',
|
||||
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,
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "4.5.0",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
"analyze": "cross-env ANALYZE=1 umi build",
|
||||
"build": "umi build",
|
||||
"deploy": "npm run site && npm run gh-pages",
|
||||
"dev": "npm run start:dev",
|
||||
"fetch:blocks": "pro fetch-blocks && npm run prettier",
|
||||
"gh-pages": "gh-pages -d dist",
|
||||
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
|
||||
"postinstall": "umi g tmp",
|
||||
"lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier",
|
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
|
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
|
||||
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
|
||||
"lint:prettier": "prettier --check \"src/**/*\" --end-of-line auto",
|
||||
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
|
||||
"precommit": "lint-staged",
|
||||
"prettier": "prettier -c --write \"src/**/*\"",
|
||||
"start": "cross-env UMI_ENV=dev umi dev",
|
||||
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev",
|
||||
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev",
|
||||
"start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev",
|
||||
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev",
|
||||
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev",
|
||||
"pretest": "node ./tests/beforeTest",
|
||||
"test": "umi test",
|
||||
"test:all": "node ./tests/run-tests.js",
|
||||
"test:component": "umi test ./src/components",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.less": "stylelint --syntax less",
|
||||
"**/*.{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": "^4.6.0",
|
||||
"@ant-design/pro-descriptions": "^1.9.6",
|
||||
"@ant-design/pro-form": "^1.35.0",
|
||||
"@ant-design/pro-layout": "^6.24.0",
|
||||
"@ant-design/pro-table": "^2.49.0",
|
||||
"@antv/data-set": "^0.11.0",
|
||||
"@antv/l7": "^2.5.8",
|
||||
"@antv/l7-maps": "^2.5.8",
|
||||
"@antv/l7-react": "^2.3.3",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.isequal": "^4.5.5",
|
||||
"@umijs/route-utils": "^1.0.33",
|
||||
"antd": "^4.16.0",
|
||||
"bizcharts": "^3.5.3-beta.0",
|
||||
"bizcharts-plugin-slider": "^2.1.1-beta.1",
|
||||
"classnames": "^2.2.6",
|
||||
"dva": "^2.4.0",
|
||||
"gg-editor": "^2.0.2",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash-decorators": "^6.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"moment": "^2.25.3",
|
||||
"mqtt": "^4.2.8",
|
||||
"numeral": "^2.0.6",
|
||||
"nzh": "^1.0.3",
|
||||
"omit.js": "^2.0.2",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^17.0.0",
|
||||
"react-dev-inspector": "^1.6.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-fittext": "^1.0.0",
|
||||
"react-helmet-async": "^1.0.4",
|
||||
"react-router": "^5.2.0",
|
||||
"umi": "^3.5.16",
|
||||
"umi-request": "^1.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/pro-cli": "^2.1.5",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/history": "^4.7.2",
|
||||
"@types/jest": "^26.0.0",
|
||||
"@types/lodash": "^4.14.144",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@umijs/fabric": "^2.5.1",
|
||||
"@umijs/plugin-blocks": "^2.0.5",
|
||||
"@umijs/plugin-esbuild": "^1.0.1",
|
||||
"@umijs/preset-ant-design-pro": "^1.2.0",
|
||||
"@umijs/preset-react": "^1.4.8",
|
||||
"@umijs/yorkie": "^2.0.3",
|
||||
"carlo": "^0.9.46",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"cross-port-killer": "^1.1.1",
|
||||
"detect-installer": "^1.0.1",
|
||||
"enzyme": "^3.11.0",
|
||||
"eslint": "^7.1.0",
|
||||
"express": "^4.17.1",
|
||||
"gh-pages": "^3.0.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"lint-staged": "^10.0.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"prettier": "^2.0.1",
|
||||
"puppeteer-core": "^7.0.1",
|
||||
"stylelint": "^13.0.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"checkFiles": [
|
||||
"src/**/*.js*",
|
||||
"src/**/*.ts*",
|
||||
"src/**/*.less",
|
||||
"config/**/*.js*",
|
||||
"scripts/**/*.js"
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
preview.pro.ant.design
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
|
@ -0,0 +1,5 @@
|
|||
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 677 B |
|
@ -0,0 +1,22 @@
|
|||
// import React from 'react';
|
||||
// import RunTimeLayoutConfig from '@ant-design/pro-layout';
|
||||
// export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
||||
// return {
|
||||
// menu: {
|
||||
// // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
|
||||
// params: {
|
||||
// userId: initialState?.currentUser?.userid,
|
||||
// },
|
||||
// request: async (params, defaultMenuData) => {
|
||||
// // initialState.currentUser 中包含了所有用户信息
|
||||
// const menuData = await fetchMenuData();
|
||||
// return menuData;
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// };
|
||||
|
||||
export async function getInitialState() {
|
||||
console.log('get init state')
|
||||
return {};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import { Result } from 'antd';
|
||||
import check from './CheckPermissions';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
import type AuthorizedRoute from './AuthorizedRoute';
|
||||
import type Secured from './Secured';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import { FormattedMessage } from 'umi';
|
||||
|
||||
type AuthorizedProps = {
|
||||
authority: IAuthorityType;
|
||||
noMatch?: React.ReactNode;
|
||||
currentUser?: CurrentUser;
|
||||
};
|
||||
|
||||
type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
|
||||
Secured: typeof Secured;
|
||||
check: typeof check;
|
||||
AuthorizedRoute: typeof AuthorizedRoute;
|
||||
};
|
||||
|
||||
const Authorized: React.FunctionComponent<AuthorizedProps> = ({
|
||||
children,
|
||||
authority,
|
||||
currentUser,
|
||||
noMatch = (
|
||||
<Result
|
||||
status="403"
|
||||
title="403"
|
||||
subTitle={
|
||||
<FormattedMessage
|
||||
id="pages.authorized.forbiden"
|
||||
defaultMessage="Sorry, you are not authorized to access this page."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
),
|
||||
}) => {
|
||||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children;
|
||||
const dom = check(currentUser, authority, childrenRender, noMatch);
|
||||
return <>{dom}</>;
|
||||
};
|
||||
|
||||
export default Authorized as IAuthorizedType;
|
|
@ -0,0 +1,33 @@
|
|||
import { Redirect, Route } from 'umi';
|
||||
|
||||
import React from 'react';
|
||||
import Authorized from './Authorized';
|
||||
import type { IAuthorityType } from './CheckPermissions';
|
||||
|
||||
type AuthorizedRouteProps = {
|
||||
currentAuthority: string;
|
||||
component: React.ComponentClass<any, any>;
|
||||
render: (props: any) => React.ReactNode;
|
||||
redirectPath: string;
|
||||
authority: IAuthorityType;
|
||||
};
|
||||
|
||||
const AuthorizedRoute: React.SFC<AuthorizedRouteProps> = ({
|
||||
component: Component,
|
||||
render,
|
||||
authority,
|
||||
redirectPath,
|
||||
...rest
|
||||
}) => (
|
||||
<Authorized
|
||||
authority={authority}
|
||||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||
>
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props: any) => (Component ? <Component {...props} /> : render(props))}
|
||||
/>
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export default AuthorizedRoute;
|
|
@ -0,0 +1,98 @@
|
|||
import React from 'react';
|
||||
// import { CURRENT } from './renderAuthorize';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PromiseRender from './PromiseRender';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
|
||||
export type IAuthorityType =
|
||||
| undefined
|
||||
| string
|
||||
| string[]
|
||||
| Promise<boolean>
|
||||
| ((currentAuthority: string | string[]) => IAuthorityType);
|
||||
|
||||
/**
|
||||
* 通用权限检查方法 Common check permissions method
|
||||
*
|
||||
* @param { 权限判定 | Role judgment } authority
|
||||
* @param { 你的权限 | Your permission description } currentAuthority
|
||||
* @param { 通过的组件 | Passing components } target
|
||||
* @param { 未通过的组件 | no pass components } Exception
|
||||
*/
|
||||
const checkPermissions = <T, K>(
|
||||
authority: IAuthorityType,
|
||||
currentAuthority: string | string[],
|
||||
target: T,
|
||||
Exception: K,
|
||||
): T | K | React.ReactNode => {
|
||||
// 没有判定权限.默认查看所有
|
||||
// Retirement authority, return target;
|
||||
if (!authority) {
|
||||
return target;
|
||||
}
|
||||
// 数组处理
|
||||
if (Array.isArray(authority)) {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (currentAuthority.some((item) => authority.includes(item))) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority.includes(currentAuthority)) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// string 处理
|
||||
if (typeof authority === 'string') {
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
if (
|
||||
currentAuthority.some((item) => {
|
||||
if (authority === item) {
|
||||
return true;
|
||||
}
|
||||
if (item === '*:*:*') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return target;
|
||||
}
|
||||
} else if (authority === currentAuthority) {
|
||||
return target;
|
||||
}
|
||||
if (authority === '!*') {
|
||||
return Exception;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
// Promise 处理
|
||||
if (authority instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={authority} />;
|
||||
}
|
||||
// Function 处理
|
||||
if (typeof authority === 'function') {
|
||||
const bool = authority(currentAuthority);
|
||||
// 函数执行后返回值是 Promise
|
||||
if (bool instanceof Promise) {
|
||||
return <PromiseRender<T, K> ok={target} error={Exception} promise={bool} />;
|
||||
}
|
||||
if (bool) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
throw new Error('unsupported parameters');
|
||||
};
|
||||
|
||||
export { checkPermissions };
|
||||
|
||||
function check<T, K>(
|
||||
user: CurrentUser | undefined,
|
||||
authority: IAuthorityType,
|
||||
target: T,
|
||||
Exception: K,
|
||||
): T | K | React.ReactNode {
|
||||
return checkPermissions<T, K>(authority, user?.permissions || [], target, Exception);
|
||||
}
|
||||
|
||||
export default check;
|
|
@ -0,0 +1,96 @@
|
|||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { isComponentClass } from './Secured';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
||||
type PromiseRenderProps<T, K> = {
|
||||
ok: T;
|
||||
error: K;
|
||||
promise: Promise<boolean>;
|
||||
};
|
||||
|
||||
type PromiseRenderState = {
|
||||
component: React.ComponentClass | React.FunctionComponent;
|
||||
};
|
||||
|
||||
export default class PromiseRender<T, K> extends React.Component<
|
||||
PromiseRenderProps<T, K>,
|
||||
PromiseRenderState
|
||||
> {
|
||||
state: PromiseRenderState = {
|
||||
component: () => null,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.setRenderComponent(this.props);
|
||||
}
|
||||
|
||||
shouldComponentUpdate = (
|
||||
nextProps: PromiseRenderProps<T, K>,
|
||||
nextState: PromiseRenderState,
|
||||
): boolean => {
|
||||
const { component } = this.state;
|
||||
if (!isEqual(nextProps, this.props)) {
|
||||
this.setRenderComponent(nextProps);
|
||||
}
|
||||
if (nextState.component !== component) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// set render Component : ok or error
|
||||
setRenderComponent(props: PromiseRenderProps<T, K>): void {
|
||||
const ok = this.checkIsInstantiation(props.ok);
|
||||
const error = this.checkIsInstantiation(props.error);
|
||||
props.promise
|
||||
.then(() => {
|
||||
this.setState({
|
||||
component: ok,
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
component: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
checkIsInstantiation = (
|
||||
target: React.ReactNode | React.ComponentClass,
|
||||
): React.FunctionComponent => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target as React.ReactNode & null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { component: Component } = this.state;
|
||||
const { ok, error, promise, ...rest } = this.props;
|
||||
|
||||
return Component ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import React from 'react';
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
|
||||
/** 默认不能访问任何页面 default is "NULL" */
|
||||
const Exception403 = () => 403;
|
||||
|
||||
export const isComponentClass = (component: React.ComponentClass | React.ReactNode): boolean => {
|
||||
if (!component) return false;
|
||||
const proto = Object.getPrototypeOf(component);
|
||||
if (proto === React.Component || proto === Function.prototype) return true;
|
||||
return isComponentClass(proto);
|
||||
};
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
const checkIsInstantiation = (target: React.ComponentClass | React.ReactNode) => {
|
||||
if (isComponentClass(target)) {
|
||||
const Target = target as React.ComponentClass;
|
||||
return (props: any) => <Target {...props} />;
|
||||
}
|
||||
if (React.isValidElement(target)) {
|
||||
return (props: any) => React.cloneElement(target, props);
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用于判断是否拥有权限访问此 view 权限 authority 支持传入 string, () => boolean | Promise e.g. 'user' 只有 user 用户能访问
|
||||
* e.g. 'user,admin' user 和 admin 都能访问 e.g. ()=>boolean 返回true能访问,返回false不能访问 e.g. Promise then 能访问
|
||||
* catch不能访问 e.g. authority support incoming string, () => boolean | Promise e.g. 'user' only user
|
||||
* user can access e.g. 'user, admin' user and admin can access e.g. () => boolean true to be able
|
||||
* to visit, return false can not be accessed e.g. Promise then can not access the visit to catch
|
||||
*
|
||||
* @param {string | function | Promise} authority
|
||||
* @param {ReactNode} error 非必需参数
|
||||
*/
|
||||
const authorize = (authority: string, error?: React.ReactNode) => {
|
||||
/**
|
||||
* Conversion into a class 防止传入字符串时找不到staticContext造成报错 String parameters can cause staticContext
|
||||
* not found error
|
||||
*/
|
||||
let classError: boolean | React.FunctionComponent = false;
|
||||
if (error) {
|
||||
classError = (() => error) as React.FunctionComponent;
|
||||
}
|
||||
if (!authority) {
|
||||
throw new Error('authority is required');
|
||||
}
|
||||
return function decideAuthority(target: React.ComponentClass | React.ReactNode) {
|
||||
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||
return checkIsInstantiation(component);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorize;
|
|
@ -0,0 +1,11 @@
|
|||
import Authorized from './Authorized';
|
||||
import Secured from './Secured';
|
||||
import check from './CheckPermissions';
|
||||
import renderAuthorize from './renderAuthorize';
|
||||
|
||||
Authorized.Secured = Secured;
|
||||
Authorized.check = check;
|
||||
|
||||
const RenderAuthorize = renderAuthorize(Authorized);
|
||||
|
||||
export default RenderAuthorize;
|
|
@ -0,0 +1,31 @@
|
|||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let CURRENT: string | string[] = 'NULL';
|
||||
|
||||
type CurrentAuthorityType = string | string[] | (() => typeof CURRENT);
|
||||
/**
|
||||
* Use authority or getAuthority
|
||||
*
|
||||
* @param {string|()=>String} currentAuthority
|
||||
*/
|
||||
const renderAuthorize = <T>(Authorized: T): ((currentAuthority: CurrentAuthorityType) => T) => (
|
||||
currentAuthority: CurrentAuthorityType,
|
||||
): T => {
|
||||
if (currentAuthority) {
|
||||
if (typeof currentAuthority === 'function') {
|
||||
CURRENT = currentAuthority();
|
||||
}
|
||||
if (
|
||||
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||
Array.isArray(currentAuthority)
|
||||
) {
|
||||
CURRENT = currentAuthority as string[];
|
||||
}
|
||||
} else {
|
||||
CURRENT = 'NULL';
|
||||
}
|
||||
return Authorized;
|
||||
};
|
||||
|
||||
export { CURRENT };
|
||||
export default <T>(Authorized: T) => renderAuthorize<T>(Authorized);
|
|
@ -0,0 +1,92 @@
|
|||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Menu, Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { history, connect } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
currentUser?: CurrentUser;
|
||||
menu?: boolean;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {
|
||||
onMenuClick = (event: {
|
||||
key: React.Key;
|
||||
keyPath: React.Key[];
|
||||
item: React.ReactInstance;
|
||||
domEvent: React.MouseEvent<HTMLElement>;
|
||||
}) => {
|
||||
const { key } = event;
|
||||
|
||||
if (key === 'logout') {
|
||||
const { dispatch } = this.props;
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'login/logout',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(`/account/${key}`);
|
||||
};
|
||||
|
||||
render(): React.ReactNode {
|
||||
const {
|
||||
currentUser = {
|
||||
avatar: '',
|
||||
userName: '',
|
||||
},
|
||||
menu,
|
||||
} = this.props;
|
||||
const menuHeaderDropdown = (
|
||||
<Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
|
||||
{menu && (
|
||||
<Menu.Item key="center">
|
||||
<UserOutlined />
|
||||
个人中心
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && (
|
||||
<Menu.Item key="settings">
|
||||
<SettingOutlined />
|
||||
个人设置
|
||||
</Menu.Item>
|
||||
)}
|
||||
{menu && <Menu.Divider />}
|
||||
|
||||
<Menu.Item key="logout">
|
||||
<LogoutOutlined />
|
||||
退出登录
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
return currentUser && currentUser.userName ? (
|
||||
<HeaderDropdown overlay={menuHeaderDropdown}>
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Avatar size="default" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
|
||||
<span className={`${styles.name} anticon`}>{currentUser.userName}</span>
|
||||
</span>
|
||||
</HeaderDropdown>
|
||||
) : (
|
||||
<span className={`${styles.action} ${styles.account}`}>
|
||||
<Spin
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
}))(AvatarDropdown);
|
|
@ -0,0 +1,168 @@
|
|||
import React, { Component } from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect } from 'umi';
|
||||
import { Tag, message } from 'antd';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import moment from 'moment';
|
||||
import type { NoticeItem } from '@/models/global';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import NoticeIcon from '../NoticeIcon';
|
||||
import styles from './index.less';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
notices?: NoticeItem[];
|
||||
currentUser?: CurrentUser;
|
||||
fetchingNotices?: boolean;
|
||||
onNoticeVisibleChange?: (visible: boolean) => void;
|
||||
onNoticeClear?: (tabName?: string) => void;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
|
||||
componentDidMount() {
|
||||
// const { dispatch } = this.props;
|
||||
|
||||
// if (dispatch) {
|
||||
// dispatch({
|
||||
// type: 'global/fetchNotices',
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
changeReadState = (clickedItem: NoticeItem): void => {
|
||||
const { id } = clickedItem;
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeNoticeReadState',
|
||||
payload: id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleNoticeClear = (title: string, key: string) => {
|
||||
const { dispatch } = this.props;
|
||||
message.success(`${'清空了'} ${title}`);
|
||||
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/clearNotices',
|
||||
payload: key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getNoticeData = (): Record<string, NoticeItem[]> => {
|
||||
const { notices = [] } = this.props;
|
||||
|
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const newNotices = notices.map((notice) => {
|
||||
const newNotice = { ...notice };
|
||||
|
||||
if (newNotice.datetime) {
|
||||
newNotice.datetime = moment(notice.datetime as string).fromNow();
|
||||
}
|
||||
|
||||
if (newNotice.id) {
|
||||
newNotice.key = newNotice.id;
|
||||
}
|
||||
|
||||
if (newNotice.extra && newNotice.status) {
|
||||
const color = {
|
||||
todo: '',
|
||||
processing: 'blue',
|
||||
urgent: 'red',
|
||||
doing: 'gold',
|
||||
}[newNotice.status];
|
||||
newNotice.extra = (
|
||||
<Tag
|
||||
color={color}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
{newNotice.extra}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return newNotice;
|
||||
});
|
||||
return groupBy(newNotices, 'type');
|
||||
};
|
||||
|
||||
getUnreadData = (noticeData: Record<string, NoticeItem[]>) => {
|
||||
const unreadMsg: Record<string, number> = {};
|
||||
Object.keys(noticeData).forEach((key) => {
|
||||
const value = noticeData[key];
|
||||
|
||||
if (!unreadMsg[key]) {
|
||||
unreadMsg[key] = 0;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
unreadMsg[key] = value.filter((item) => !item.read).length;
|
||||
}
|
||||
});
|
||||
return unreadMsg;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
|
||||
const noticeData = this.getNoticeData();
|
||||
const unreadMsg = this.getUnreadData(noticeData);
|
||||
return (
|
||||
<NoticeIcon
|
||||
className={styles.action}
|
||||
count={currentUser && currentUser.unreadCount}
|
||||
onItemClick={(item) => {
|
||||
this.changeReadState(item as NoticeItem);
|
||||
}}
|
||||
loading={fetchingNotices}
|
||||
clearText="清空"
|
||||
viewMoreText="查看更多"
|
||||
onClear={this.handleNoticeClear}
|
||||
onPopupVisibleChange={onNoticeVisibleChange}
|
||||
onViewMore={() => message.info('Click on view more')}
|
||||
clearClose
|
||||
>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="notification"
|
||||
count={unreadMsg.notification}
|
||||
list={noticeData.notification}
|
||||
title="通知"
|
||||
emptyText="你已查看所有通知"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="message"
|
||||
count={unreadMsg.message}
|
||||
list={noticeData.message}
|
||||
title="消息"
|
||||
emptyText="您已读完所有消息"
|
||||
showViewMore
|
||||
/>
|
||||
<NoticeIcon.Tab
|
||||
tabKey="event"
|
||||
title="待办"
|
||||
emptyText="你已完成所有待办"
|
||||
count={unreadMsg.event}
|
||||
list={noticeData.event}
|
||||
showViewMore
|
||||
/>
|
||||
</NoticeIcon>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, global, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
collapsed: global.collapsed,
|
||||
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
|
||||
fetchingNotices: loading.effects['global/fetchNotices'],
|
||||
notices: global.notices,
|
||||
}))(GlobalHeaderRight);
|
|
@ -0,0 +1,86 @@
|
|||
import { Tooltip, Tag } from 'antd';
|
||||
import type { Settings as ProSettings } from '@ant-design/pro-layout';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { connect, SelectLang } from 'umi';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import Avatar from './AvatarDropdown';
|
||||
import HeaderSearch from '../HeaderSearch';
|
||||
import styles from './index.less';
|
||||
import NoticeIconView from './NoticeIconView';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
theme?: ProSettings['navTheme'] | 'realDark';
|
||||
} & Partial<ConnectProps> &
|
||||
Partial<ProSettings>;
|
||||
const ENVTagColor = {
|
||||
dev: 'orange',
|
||||
test: 'green',
|
||||
pre: '#87d068',
|
||||
};
|
||||
|
||||
const GlobalHeaderRight: React.SFC<GlobalHeaderRightProps> = (props) => {
|
||||
const { theme, layout } = props;
|
||||
let className = styles.right;
|
||||
|
||||
if (theme === 'dark' && layout === 'top') {
|
||||
className = `${styles.right} ${styles.dark}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<HeaderSearch
|
||||
className={`${styles.action} ${styles.search}`}
|
||||
placeholder="站内搜索"
|
||||
defaultValue="umi ui"
|
||||
options={[
|
||||
{
|
||||
label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>,
|
||||
value: 'umi ui',
|
||||
},
|
||||
{
|
||||
label: <a href="next.ant.design">Ant Design</a>,
|
||||
value: 'Ant Design',
|
||||
},
|
||||
{
|
||||
label: <a href="https://protable.ant.design/">Pro Table</a>,
|
||||
value: 'Pro Table',
|
||||
},
|
||||
{
|
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
|
||||
value: 'Pro Layout',
|
||||
},
|
||||
]} // onSearch={value => {
|
||||
// //console.log('input', value);
|
||||
// }}
|
||||
/>
|
||||
<Tooltip title="使用文档">
|
||||
<a
|
||||
style={{
|
||||
color: 'inherit',
|
||||
}}
|
||||
target="_blank"
|
||||
href="https://pro.ant.design/docs/getting-started"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.action}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<NoticeIconView />
|
||||
<Avatar menu />
|
||||
{REACT_APP_ENV && (
|
||||
<span>
|
||||
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
|
||||
</span>
|
||||
)}
|
||||
<SelectLang className={styles.action} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ settings }: ConnectState) => ({
|
||||
theme: settings.navTheme,
|
||||
layout: settings.layout,
|
||||
}))(GlobalHeaderRight);
|
|
@ -0,0 +1,82 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
|
||||
|
||||
.menu {
|
||||
:global(.anticon) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
:global(.ant-dropdown-menu-item) {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
float: right;
|
||||
height: 48px;
|
||||
margin-left: auto;
|
||||
overflow: hidden;
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
> span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&:hover {
|
||||
background: @pro-header-hover-bg;
|
||||
}
|
||||
&:global(.opened) {
|
||||
background: @pro-header-hover-bg;
|
||||
}
|
||||
}
|
||||
.search {
|
||||
padding: 0 12px;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.account {
|
||||
.avatar {
|
||||
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
|
||||
margin-right: 8px;
|
||||
color: @primary-color;
|
||||
vertical-align: top;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.action {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
&:hover,
|
||||
&:global(.opened) {
|
||||
background: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-pro-global-header) {
|
||||
.dark {
|
||||
.action {
|
||||
color: @text-color;
|
||||
> span {
|
||||
color: @text-color;
|
||||
}
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
> span {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.container > * {
|
||||
background-color: @popover-bg;
|
||||
border-radius: 4px;
|
||||
box-shadow: @shadow-1-down;
|
||||
}
|
||||
|
||||
@media screen and (max-width: @screen-xs) {
|
||||
.container {
|
||||
width: 100% !important;
|
||||
}
|
||||
.container > * {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import type { DropDownProps } from 'antd/es/dropdown';
|
||||
import { Dropdown } from 'antd';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
export type HeaderDropdownProps = {
|
||||
overlayClassName?: string;
|
||||
overlay: React.ReactNode | (() => React.ReactNode) | any;
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
|
||||
} & Omit<DropDownProps, 'overlay'>;
|
||||
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => (
|
||||
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
|
||||
);
|
||||
|
||||
export default HeaderDropdown;
|
|
@ -0,0 +1,30 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.headerSearch {
|
||||
.input {
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
transition: width 0.3s, margin-left 0.3s;
|
||||
:global(.ant-select-selection) {
|
||||
background: transparent;
|
||||
}
|
||||
input {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
border: 0;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
}
|
||||
&.show {
|
||||
width: 210px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { AutoComplete, Input } from 'antd';
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState';
|
||||
import type { AutoCompleteProps } from 'antd/es/auto-complete';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
export type HeaderSearchProps = {
|
||||
onSearch?: (value?: string) => void;
|
||||
onChange?: (value?: string) => void;
|
||||
onVisibleChange?: (b: boolean) => void;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
options: AutoCompleteProps['options'];
|
||||
defaultOpen?: boolean;
|
||||
open?: boolean;
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
defaultValue,
|
||||
onVisibleChange,
|
||||
placeholder,
|
||||
open,
|
||||
defaultOpen,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const inputRef = useRef<Input | null>(null);
|
||||
|
||||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, {
|
||||
value: props.value,
|
||||
onChange: props.onChange,
|
||||
});
|
||||
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultOpen ?? false, {
|
||||
value: props.open,
|
||||
onChange: onVisibleChange,
|
||||
});
|
||||
|
||||
const inputClass = classNames(styles.input, {
|
||||
[styles.show]: searchMode,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(className, styles.headerSearch)}
|
||||
onClick={() => {
|
||||
setSearchMode(true);
|
||||
if (searchMode && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}}
|
||||
onTransitionEnd={({ propertyName }) => {
|
||||
if (propertyName === 'width' && !searchMode) {
|
||||
if (onVisibleChange) {
|
||||
onVisibleChange(searchMode);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SearchOutlined
|
||||
key="Icon"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
<AutoComplete
|
||||
key="AutoComplete"
|
||||
className={inputClass}
|
||||
value={value}
|
||||
style={{
|
||||
height: 28,
|
||||
marginTop: -6,
|
||||
}}
|
||||
options={restProps.options}
|
||||
onChange={setValue}
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
defaultValue={defaultValue}
|
||||
aria-label={placeholder}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (restProps.onSearch) {
|
||||
restProps.onSearch(value);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
setSearchMode(false);
|
||||
}}
|
||||
/>
|
||||
</AutoComplete>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderSearch;
|
|
@ -0,0 +1,77 @@
|
|||
import * as React from 'react';
|
||||
import { message } from 'antd';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import CopyableIcon from './CopyableIcon';
|
||||
import type { ThemeType } from './index';
|
||||
import type { CategoriesKeys } from './fields';
|
||||
|
||||
interface CategoryProps {
|
||||
title: CategoriesKeys;
|
||||
icons: string[];
|
||||
theme: ThemeType;
|
||||
newIcons: string[];
|
||||
intl: any;
|
||||
onSelect: (name: string) => any;
|
||||
}
|
||||
|
||||
interface CategoryState {
|
||||
justCopied: string | null;
|
||||
}
|
||||
|
||||
class Category extends React.Component<CategoryProps, CategoryState> {
|
||||
copyId?: number;
|
||||
|
||||
state = {
|
||||
justCopied: null,
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearTimeout(this.copyId);
|
||||
}
|
||||
|
||||
onSelect = (name: string) => {
|
||||
const { onSelect } = this.props;
|
||||
if (onSelect) {
|
||||
onSelect(name);
|
||||
}
|
||||
message.success(
|
||||
<span>
|
||||
<code className="copied-code">{name}</code> selected 🎉
|
||||
</span>,
|
||||
);
|
||||
this.setState({ justCopied: name }, () => {
|
||||
this.copyId = window.setTimeout(() => {
|
||||
this.setState({ justCopied: null });
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
icons,
|
||||
title,
|
||||
newIcons,
|
||||
theme,
|
||||
intl: { messages },
|
||||
} = this.props;
|
||||
const items = icons.map((name) => (
|
||||
<CopyableIcon
|
||||
key={name}
|
||||
name={name}
|
||||
theme={theme}
|
||||
isNew={newIcons.indexOf(name) >= 0}
|
||||
justCopied={this.state.justCopied}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{messages[`app.docs.components.icon.category.${title}`]}</h3>
|
||||
<ul className="anticons-list">{items}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(Category);
|
|
@ -0,0 +1,45 @@
|
|||
import * as React from 'react';
|
||||
import { Badge } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import * as AntdIcons from '@ant-design/icons';
|
||||
import type { ThemeType } from './index';
|
||||
|
||||
const allIcons: Record<string, any> = AntdIcons;
|
||||
|
||||
export interface CopyableIconProps {
|
||||
name: string;
|
||||
isNew: boolean;
|
||||
theme: ThemeType;
|
||||
justCopied: string | null;
|
||||
onSelect: (name: string) => any;
|
||||
}
|
||||
|
||||
const CopyableIcon: React.FC<CopyableIconProps> = ({
|
||||
name,
|
||||
isNew,
|
||||
justCopied,
|
||||
theme,
|
||||
onSelect,
|
||||
}) => {
|
||||
const className = classNames({
|
||||
copied: justCopied === name,
|
||||
[theme]: !!theme,
|
||||
});
|
||||
return (
|
||||
<li
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (onSelect) {
|
||||
onSelect(name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{React.createElement(allIcons[name])}
|
||||
<span className="anticon-class">
|
||||
<Badge dot={isNew}>{name}</Badge>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyableIcon;
|
|
@ -0,0 +1,248 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Upload, Tooltip, Popover, Modal, Progress, message, Spin, Result } from 'antd';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import * as AntdIcons from '@ant-design/icons';
|
||||
|
||||
const allIcons: {
|
||||
[key: string]: any;
|
||||
} = AntdIcons;
|
||||
|
||||
const { Dragger } = Upload;
|
||||
interface AntdIconClassifier {
|
||||
load: Function;
|
||||
predict: Function;
|
||||
}
|
||||
declare global {
|
||||
interface Window {
|
||||
antdIconClassifier: AntdIconClassifier;
|
||||
}
|
||||
}
|
||||
|
||||
interface PicSearcherProps {
|
||||
intl: any;
|
||||
}
|
||||
|
||||
interface PicSearcherState {
|
||||
loading: boolean;
|
||||
modalVisible: boolean;
|
||||
popoverVisible: boolean;
|
||||
icons: string[];
|
||||
fileList: any[];
|
||||
error: boolean;
|
||||
modelLoaded: boolean;
|
||||
}
|
||||
|
||||
interface iconObject {
|
||||
type: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
class PicSearcher extends Component<PicSearcherProps, PicSearcherState> {
|
||||
state = {
|
||||
loading: false,
|
||||
modalVisible: false,
|
||||
popoverVisible: false,
|
||||
icons: [],
|
||||
fileList: [],
|
||||
error: false,
|
||||
modelLoaded: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.loadModel();
|
||||
this.setState({ popoverVisible: !localStorage.getItem('disableIconTip') });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('paste', this.onPaste);
|
||||
}
|
||||
|
||||
loadModel = () => {
|
||||
const script = document.createElement('script');
|
||||
script.onload = async () => {
|
||||
await window.antdIconClassifier.load();
|
||||
this.setState({ modelLoaded: true });
|
||||
document.addEventListener('paste', this.onPaste);
|
||||
};
|
||||
script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
|
||||
onPaste = (event: ClipboardEvent) => {
|
||||
const items = event.clipboardData && event.clipboardData.items;
|
||||
let file = null;
|
||||
if (items && items.length) {
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
if (items[i].type.indexOf('image') !== -1) {
|
||||
file = items[i].getAsFile();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (file) this.uploadFile(file);
|
||||
};
|
||||
|
||||
uploadFile = (file: File) => {
|
||||
this.setState(() => ({ loading: true }));
|
||||
const reader: FileReader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.toImage(reader.result).then(this.predict);
|
||||
this.setState(() => ({
|
||||
fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
|
||||
}));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
toImage = (url: any) =>
|
||||
new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.setAttribute('crossOrigin', 'anonymous');
|
||||
img.src = url;
|
||||
img.onload = function onload() {
|
||||
resolve(img);
|
||||
};
|
||||
});
|
||||
|
||||
predict = (imgEl: any) => {
|
||||
try {
|
||||
let icons = window.antdIconClassifier.predict(imgEl);
|
||||
// if (gtag && icons.length >= 1) {
|
||||
// gtag('event', 'icon', {
|
||||
// event_category: 'search-by-image',
|
||||
// event_label: icons[0].className,
|
||||
// });
|
||||
// }
|
||||
icons = icons.map((i: any) => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
|
||||
this.setState(() => ({ icons, loading: false, error: false }));
|
||||
} catch (err) {
|
||||
this.setState(() => ({ loading: false, error: true }));
|
||||
}
|
||||
};
|
||||
|
||||
toggleModal = () => {
|
||||
this.setState((prev) => ({
|
||||
modalVisible: !prev.modalVisible,
|
||||
popoverVisible: false,
|
||||
fileList: [],
|
||||
icons: [],
|
||||
}));
|
||||
if (!localStorage.getItem('disableIconTip')) {
|
||||
localStorage.setItem('disableIconTip', 'true');
|
||||
}
|
||||
};
|
||||
|
||||
onCopied = (text: string) => {
|
||||
message.success(
|
||||
<span>
|
||||
<code className="copied-code">{text}</code> copied 🎉
|
||||
</span>,
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl: { messages },
|
||||
} = this.props;
|
||||
const { modalVisible, popoverVisible, icons, fileList, loading, modelLoaded, error } =
|
||||
this.state;
|
||||
return (
|
||||
<div className="icon-pic-searcher">
|
||||
<Popover
|
||||
content={messages[`app.docs.components.icon.pic-searcher.intro`]}
|
||||
visible={popoverVisible}
|
||||
>
|
||||
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={this.toggleModal} />
|
||||
</Popover>
|
||||
<Modal
|
||||
title={messages[`app.docs.components.icon.pic-searcher.title`]}
|
||||
visible={modalVisible}
|
||||
onCancel={this.toggleModal}
|
||||
footer={null}
|
||||
>
|
||||
{modelLoaded || (
|
||||
<Spin
|
||||
spinning={!modelLoaded}
|
||||
tip={messages['app.docs.components.icon.pic-searcher.modelloading']}
|
||||
>
|
||||
<div style={{ height: 100 }} />
|
||||
</Spin>
|
||||
)}
|
||||
{modelLoaded && (
|
||||
<Dragger
|
||||
accept="image/jpeg, image/png"
|
||||
listType="picture"
|
||||
customRequest={(o: any) => this.uploadFile(o.file)}
|
||||
fileList={fileList}
|
||||
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<AntdIcons.InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
{messages['app.docs.components.icon.pic-searcher.upload-text']}
|
||||
</p>
|
||||
<p className="ant-upload-hint">
|
||||
{messages['app.docs.components.icon.pic-searcher.upload-hint']}
|
||||
</p>
|
||||
</Dragger>
|
||||
)}
|
||||
<Spin spinning={loading} tip={messages['app.docs.components.icon.pic-searcher.matching']}>
|
||||
<div className="icon-pic-search-result">
|
||||
{icons.length > 0 && (
|
||||
<div className="result-tip">
|
||||
{messages['app.docs.components.icon.pic-searcher.result-tip']}
|
||||
</div>
|
||||
)}
|
||||
<table>
|
||||
{icons.length > 0 && (
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-icon">
|
||||
{messages['app.docs.components.icon.pic-searcher.th-icon']}
|
||||
</th>
|
||||
<th>{messages['app.docs.components.icon.pic-searcher.th-score']}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
)}
|
||||
<tbody>
|
||||
{icons.map((icon: iconObject) => {
|
||||
const { type } = icon;
|
||||
const iconName = `${type
|
||||
.split('-')
|
||||
.map((str) => `${str[0].toUpperCase()}${str.slice(1)}`)
|
||||
.join('')}Outlined`;
|
||||
|
||||
return (
|
||||
<tr key={iconName}>
|
||||
<td className="col-icon">
|
||||
{/* <CopyToClipboard text={`<${iconName} />`} onCopy={this.onCopied}> */}
|
||||
<Tooltip title={icon.type} placement="right">
|
||||
{React.createElement(allIcons[iconName])}
|
||||
</Tooltip>
|
||||
{/* </CopyToClipboard> */}
|
||||
</td>
|
||||
<td>
|
||||
<Progress percent={Math.ceil(icon.score * 100)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{error && (
|
||||
<Result
|
||||
status="500"
|
||||
title="503"
|
||||
subTitle={messages['app.docs.components.icon.pic-searcher.server-error']}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(PicSearcher);
|
|
@ -0,0 +1,221 @@
|
|||
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',
|
||||
];
|
||||
|
||||
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;
|
|
@ -0,0 +1,163 @@
|
|||
import * as React from 'react';
|
||||
import Icon, * as AntdIcons from '@ant-design/icons';
|
||||
import { Radio, Input, Empty, Tabs, Row, Col } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/es/radio/interface';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import debounce from 'lodash/debounce';
|
||||
import Category from './Category';
|
||||
import type { Categories, CategoriesKeys } from './fields';
|
||||
import { categories } from './fields';
|
||||
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export enum ThemeType {
|
||||
Filled = 'Filled',
|
||||
Outlined = 'Outlined',
|
||||
TwoTone = 'TwoTone',
|
||||
}
|
||||
|
||||
const allIcons: Record<string, any> = AntdIcons;
|
||||
|
||||
interface IconSelectorProps {
|
||||
intl: any;
|
||||
onSelect: any;
|
||||
}
|
||||
|
||||
interface IconSelectorState {
|
||||
theme: ThemeType;
|
||||
searchKey: string;
|
||||
}
|
||||
|
||||
class IconSelector extends React.PureComponent<IconSelectorProps, IconSelectorState> {
|
||||
static categories: Categories = categories;
|
||||
|
||||
static newIconNames: string[] = [];
|
||||
|
||||
state: IconSelectorState = {
|
||||
theme: ThemeType.Outlined,
|
||||
searchKey: '',
|
||||
};
|
||||
|
||||
constructor(props: IconSelectorProps) {
|
||||
super(props);
|
||||
this.handleSearchIcon = debounce(this.handleSearchIcon, 300);
|
||||
}
|
||||
|
||||
handleChangeTheme = (e: RadioChangeEvent) => {
|
||||
this.setState({
|
||||
theme: e.target.value as ThemeType,
|
||||
});
|
||||
};
|
||||
|
||||
handleSearchIcon = (searchKey: string) => {
|
||||
this.setState((prevState) => ({
|
||||
...prevState,
|
||||
searchKey,
|
||||
}));
|
||||
};
|
||||
|
||||
getTitle(cate: string) {
|
||||
const titles = {
|
||||
direction: '方向性图标',
|
||||
suggestion: '提示建议性图标',
|
||||
editor: '编辑类图标',
|
||||
data: '数据类图标',
|
||||
logo: '品牌和标识',
|
||||
other: '网站通用图标',
|
||||
};
|
||||
return titles[cate];
|
||||
}
|
||||
|
||||
renderTabs() {
|
||||
const { searchKey = '', theme } = this.state;
|
||||
const { onSelect } = this.props;
|
||||
|
||||
const categoriesResult = Object.keys(categories)
|
||||
.map((key: any) => {
|
||||
let iconList = categories[key];
|
||||
if (searchKey) {
|
||||
const matchKey = searchKey
|
||||
.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: ThemeType) => iconName + theme)
|
||||
.filter((iconName: string | number) => allIcons[iconName]),
|
||||
};
|
||||
})
|
||||
.filter(({ icons }) => !!icons.length)
|
||||
.map(({ category, icons }) => (
|
||||
<TabPane tab={this.getTitle(category)} key={category}>
|
||||
<Category
|
||||
key={category}
|
||||
title={category as CategoriesKeys}
|
||||
theme={theme}
|
||||
icons={icons}
|
||||
newIcons={IconSelector.newIconNames}
|
||||
onSelect={(name) => {
|
||||
if (onSelect) {
|
||||
onSelect(name, allIcons[name]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</TabPane>
|
||||
));
|
||||
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl: { messages },
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={4}>
|
||||
<Radio.Group
|
||||
value={this.state.theme}
|
||||
onChange={this.handleChangeTheme}
|
||||
size="large"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
<Radio.Button value={ThemeType.Outlined}>
|
||||
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
|
||||
</Radio.Button>
|
||||
<Radio.Button value={ThemeType.Filled}>
|
||||
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
|
||||
</Radio.Button>
|
||||
<Radio.Button value={ThemeType.TwoTone}>
|
||||
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Input.Search
|
||||
style={{ margin: '0 10px', flex: 1 }}
|
||||
allowClear
|
||||
onChange={(e) => this.handleSearchIcon(e.currentTarget.value)}
|
||||
size="large"
|
||||
autoFocus
|
||||
// suffix={<IconPicSearcher />}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Tabs defaultActiveKey="1">{this.renderTabs()}</Tabs>
|
||||
</Row>
|
||||
{/* {this.renderCategories()} */}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(IconSelector);
|
|
@ -0,0 +1,81 @@
|
|||
ul.anticons-list {
|
||||
margin: 6px 0;
|
||||
overflow: hidden;
|
||||
direction: ltr;
|
||||
list-style: none;
|
||||
li {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 16.66%;
|
||||
height: 60px;
|
||||
margin: 30px 0;
|
||||
padding: 4px 0 0;
|
||||
overflow: hidden;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
background-color: inherit;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
|
||||
.rtl & {
|
||||
margin: 2px 0;
|
||||
padding: 3px 0 0;
|
||||
}
|
||||
.anticon {
|
||||
margin: 2px 0 3px;
|
||||
font-size: 26px;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.anticon-class {
|
||||
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);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
line-height: 110px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
content: 'Copied!';
|
||||
}
|
||||
|
||||
&.copied::after {
|
||||
top: -10px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react';
|
||||
|
||||
export const FilledIcon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (
|
||||
props,
|
||||
) => {
|
||||
const path =
|
||||
'M864 64H160C107 64 64 107 64 160v' +
|
||||
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
|
||||
'0c0-53-43-96-96-96z';
|
||||
return (
|
||||
<svg {...(props as any)} viewBox="0 0 1024 1024">
|
||||
<path d={path} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const OutlinedIcon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (
|
||||
props,
|
||||
) => {
|
||||
const path =
|
||||
'M864 64H160C107 64 64 107 64 160v7' +
|
||||
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
|
||||
'0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
|
||||
'12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
|
||||
' 12 12v680c0 6.6-5.4 12-12 12z';
|
||||
return (
|
||||
<svg {...(props as any)} viewBox="0 0 1024 1024">
|
||||
<path d={path} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const TwoToneIcon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (
|
||||
props,
|
||||
) => {
|
||||
const path =
|
||||
'M16 512c0 273.932 222.066 496 496 49' +
|
||||
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
|
||||
'066 16 512z m496 368V144c203.41 0 368 164.622 3' +
|
||||
'68 368 0 203.41-164.622 368-368 368z';
|
||||
return (
|
||||
<svg {...(props as any)} viewBox="0 0 1024 1024">
|
||||
<path d={path} />
|
||||
</svg>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,103 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.list {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.item {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.meta {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-top: 4px;
|
||||
background: @component-background;
|
||||
}
|
||||
.iconElement {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
&.read {
|
||||
opacity: 0.4;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&:hover {
|
||||
background: @primary-1;
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.description {
|
||||
font-size: 12px;
|
||||
line-height: @line-height-base;
|
||||
}
|
||||
.datetime {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: @line-height-base;
|
||||
}
|
||||
.extra {
|
||||
float: right;
|
||||
margin-top: -1.5px;
|
||||
margin-right: 0;
|
||||
color: @text-color-secondary;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.loadMore {
|
||||
padding: 8px 0;
|
||||
color: @primary-6;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&.loadedAll {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
cursor: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notFound {
|
||||
padding: 73px 0 88px;
|
||||
color: @text-color-secondary;
|
||||
text-align: center;
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 76px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
height: 46px;
|
||||
color: @text-color;
|
||||
line-height: 46px;
|
||||
text-align: center;
|
||||
border-top: 1px solid @border-color-split;
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||
transition: all 0.3s;
|
||||
div {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
user-select: none;
|
||||
|
||||
&:only-child {
|
||||
width: 100%;
|
||||
}
|
||||
&:not(:only-child):last-child {
|
||||
border-left: 1px solid @border-color-split;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { Avatar, List } from 'antd';
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { NoticeIconData } from './index';
|
||||
import styles from './NoticeList.less';
|
||||
|
||||
export type NoticeIconTabProps = {
|
||||
count?: number;
|
||||
name?: string;
|
||||
showClear?: boolean;
|
||||
showViewMore?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
title: string;
|
||||
tabKey: string;
|
||||
data?: NoticeIconData[];
|
||||
onClick?: (item: NoticeIconData) => void;
|
||||
onClear?: () => void;
|
||||
emptyText?: string;
|
||||
clearText?: string;
|
||||
viewMoreText?: string;
|
||||
list: NoticeIconData[];
|
||||
onViewMore?: (e: any) => void;
|
||||
};
|
||||
const NoticeList: React.SFC<NoticeIconTabProps> = ({
|
||||
data = [],
|
||||
onClick,
|
||||
onClear,
|
||||
title,
|
||||
onViewMore,
|
||||
emptyText,
|
||||
showClear = true,
|
||||
clearText,
|
||||
viewMoreText,
|
||||
showViewMore = false,
|
||||
}) => {
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<div className={styles.notFound}>
|
||||
<img
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
|
||||
alt="not found"
|
||||
/>
|
||||
<div>{emptyText}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<List<NoticeIconData>
|
||||
className={styles.list}
|
||||
dataSource={data}
|
||||
renderItem={(item, i) => {
|
||||
const itemCls = classNames(styles.item, {
|
||||
[styles.read]: item.read,
|
||||
});
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const leftIcon = item.avatar ? (
|
||||
typeof item.avatar === 'string' ? (
|
||||
<Avatar className={styles.avatar} src={item.avatar} />
|
||||
) : (
|
||||
<span className={styles.iconElement}>{item.avatar}</span>
|
||||
)
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
className={itemCls}
|
||||
key={item.key || i}
|
||||
onClick={() => {
|
||||
onClick?.(item);
|
||||
}}
|
||||
>
|
||||
<List.Item.Meta
|
||||
className={styles.meta}
|
||||
avatar={leftIcon}
|
||||
title={
|
||||
<div className={styles.title}>
|
||||
{item.title}
|
||||
<div className={styles.extra}>{item.extra}</div>
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div>
|
||||
<div className={styles.description}>{item.description}</div>
|
||||
<div className={styles.datetime}>{item.datetime}</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className={styles.bottomBar}>
|
||||
{showClear ? (
|
||||
<div onClick={onClear}>
|
||||
{clearText} {title}
|
||||
</div>
|
||||
) : null}
|
||||
{showViewMore ? (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
if (onViewMore) {
|
||||
onViewMore(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{viewMoreText}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoticeList;
|
|
@ -0,0 +1,35 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.popover {
|
||||
position: relative;
|
||||
width: 336px;
|
||||
}
|
||||
|
||||
.noticeButton {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.icon {
|
||||
padding: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
:global {
|
||||
.ant-tabs-nav-list {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-scroll {
|
||||
text-align: center;
|
||||
}
|
||||
.ant-tabs-bar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
import { BellOutlined } from '@ant-design/icons';
|
||||
import { Badge, Spin, Tabs } from 'antd';
|
||||
import useMergedState from 'rc-util/es/hooks/useMergedState';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { NoticeIconTabProps } from './NoticeList';
|
||||
import NoticeList from './NoticeList';
|
||||
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export type NoticeIconData = {
|
||||
avatar?: string | React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
datetime?: React.ReactNode;
|
||||
extra?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
key?: string | number;
|
||||
read?: boolean;
|
||||
};
|
||||
|
||||
export type NoticeIconProps = {
|
||||
count?: number;
|
||||
bell?: React.ReactNode;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
onClear?: (tabName: string, tabKey: string) => void;
|
||||
onItemClick?: (item: NoticeIconData, tabProps: NoticeIconTabProps) => void;
|
||||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
|
||||
onTabChange?: (tabTile: string) => void;
|
||||
style?: React.CSSProperties;
|
||||
onPopupVisibleChange?: (visible: boolean) => void;
|
||||
popupVisible?: boolean;
|
||||
clearText?: string;
|
||||
viewMoreText?: string;
|
||||
clearClose?: boolean;
|
||||
emptyImage?: string;
|
||||
children: React.ReactElement<NoticeIconTabProps>[];
|
||||
};
|
||||
|
||||
const NoticeIcon: React.FC<NoticeIconProps> & {
|
||||
Tab: typeof NoticeList;
|
||||
} = (props) => {
|
||||
const getNotificationBox = (): React.ReactNode => {
|
||||
const {
|
||||
children,
|
||||
loading,
|
||||
onClear,
|
||||
onTabChange,
|
||||
onItemClick,
|
||||
onViewMore,
|
||||
clearText,
|
||||
viewMoreText,
|
||||
} = props;
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
const panes: React.ReactNode[] = [];
|
||||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
|
||||
const len = list && list.length ? list.length : 0;
|
||||
const msgCount = count || count === 0 ? count : len;
|
||||
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
|
||||
panes.push(
|
||||
<TabPane tab={tabTitle} key={tabKey}>
|
||||
<NoticeList
|
||||
{...child.props}
|
||||
clearText={clearText}
|
||||
viewMoreText={viewMoreText}
|
||||
data={list}
|
||||
onClear={(): void => {
|
||||
onClear?.(title, tabKey);
|
||||
}}
|
||||
onClick={(item): void => {
|
||||
onItemClick?.(item, child.props);
|
||||
}}
|
||||
onViewMore={(event): void => {
|
||||
onViewMore?.(child.props, event);
|
||||
}}
|
||||
showClear={showClear}
|
||||
showViewMore={showViewMore}
|
||||
title={title}
|
||||
/>
|
||||
</TabPane>,
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Spin spinning={loading} delay={300}>
|
||||
<Tabs className={styles.tabs} onChange={onTabChange}>
|
||||
{panes}
|
||||
</Tabs>
|
||||
</Spin>
|
||||
);
|
||||
};
|
||||
|
||||
const { className, count, bell } = props;
|
||||
|
||||
const [visible, setVisible] = useMergedState<boolean>(false, {
|
||||
value: props.popupVisible,
|
||||
onChange: props.onPopupVisibleChange,
|
||||
});
|
||||
const noticeButtonClass = classNames(className, styles.noticeButton);
|
||||
const notificationBox = getNotificationBox();
|
||||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
|
||||
const trigger = (
|
||||
<span className={classNames(noticeButtonClass, { opened: visible })}>
|
||||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
|
||||
{NoticeBellIcon}
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
if (!notificationBox) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderDropdown
|
||||
placement="bottomRight"
|
||||
overlay={notificationBox}
|
||||
overlayClassName={styles.popover}
|
||||
trigger={['click']}
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
>
|
||||
{trigger}
|
||||
</HeaderDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
NoticeIcon.defaultProps = {
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
|
||||
};
|
||||
|
||||
NoticeIcon.Tab = NoticeList;
|
||||
|
||||
export default NoticeIcon;
|
|
@ -0,0 +1,5 @@
|
|||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
|
||||
// loading components from code split
|
||||
// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
|
||||
export default PageLoading;
|
|
@ -0,0 +1,115 @@
|
|||
import React from 'react';
|
||||
import type { TreeSelectProps } from 'antd';
|
||||
import ProField from '@ant-design/pro-field';
|
||||
import type { ProSchema } from '@ant-design/pro-utils';
|
||||
import { runFunction } from '@ant-design/pro-utils';
|
||||
import type { ProFormItemProps } from '@ant-design/pro-form/lib/interface'
|
||||
import type { ExtendsProps } from '@ant-design/pro-form/lib/BaseForm/createField';
|
||||
import createField from '@ant-design/pro-form/lib/BaseForm/createField';
|
||||
|
||||
export type ProFormTreeSelectProps<T = any> = ProFormItemProps<
|
||||
TreeSelectProps<T> & {
|
||||
/**
|
||||
* 是否在输入框聚焦时触发搜索
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
searchOnFocus?: boolean;
|
||||
/**
|
||||
* 选择完一个之后是否清空搜索项重新搜索
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
resetAfterSelect?: boolean;
|
||||
/** 自定义选项渲染 */
|
||||
optionItemRender?: (item: T) => React.ReactNode;
|
||||
}
|
||||
> & {
|
||||
valueEnum?: ProSchema['valueEnum'];
|
||||
params?: ProSchema['params'];
|
||||
request?: ProSchema['request'];
|
||||
options?: TreeSelectProps<any>['options'] | string[];
|
||||
multiple?: TreeSelectProps<any>['multiple'] | false;
|
||||
showSearch?: TreeSelectProps<any>['showSearch'];
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择框
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
const ProFormTreeSelectComponents = React.forwardRef<any, ProFormTreeSelectProps<any>>(
|
||||
(
|
||||
{ fieldProps, children, params, proFieldProps, multiple, valueEnum, request, showSearch, options },
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<ProField
|
||||
mode="edit"
|
||||
valueEnum={runFunction(valueEnum)}
|
||||
request={request}
|
||||
params={params}
|
||||
valueType="select"
|
||||
fieldProps={{
|
||||
options,
|
||||
multiple,
|
||||
showSearch,
|
||||
...fieldProps,
|
||||
}}
|
||||
ref={ref}
|
||||
{...proFieldProps}
|
||||
>
|
||||
{children}
|
||||
</ProField>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const SearchTreeSelect = React.forwardRef<any, ProFormTreeSelectProps<any>>(
|
||||
({ fieldProps, children, params, proFieldProps, multiple, valueEnum, request, options }, ref) => {
|
||||
const props: Omit<TreeSelectProps<any>, 'options'> & {
|
||||
options?: ProFormTreeSelectProps['options'];
|
||||
} = {
|
||||
options,
|
||||
multiple: false,
|
||||
labelInValue: true,
|
||||
showSearch: true,
|
||||
showArrow: false,
|
||||
autoClearSearchValue: true,
|
||||
...fieldProps,
|
||||
};
|
||||
return (
|
||||
<ProField
|
||||
mode="edit"
|
||||
valueEnum={runFunction(valueEnum)}
|
||||
request={request}
|
||||
params={params}
|
||||
valueType="select"
|
||||
fieldProps={props}
|
||||
ref={ref}
|
||||
{...proFieldProps}
|
||||
>
|
||||
{children}
|
||||
</ProField>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const ProFormSelect = createField<ProFormTreeSelectProps>(ProFormTreeSelectComponents, {
|
||||
customLightMode: true,
|
||||
}) as <T>(props: ProFormTreeSelectProps<T> & ExtendsProps) => React.ReactElement;
|
||||
|
||||
const ProFormSearchTreeSelect = createField<ProFormTreeSelectProps>(SearchTreeSelect, {
|
||||
customLightMode: true,
|
||||
}) as <T>(props: ProFormTreeSelectProps<T> & ExtendsProps) => React.ReactElement;
|
||||
|
||||
const WrappedProFormTreeSelect = ProFormSelect as (<T = any>(
|
||||
props: ProFormTreeSelectProps<T>,
|
||||
) => React.ReactElement) & {
|
||||
SearchTreeSelect: typeof ProFormSearchTreeSelect;
|
||||
};
|
||||
|
||||
WrappedProFormTreeSelect.SearchTreeSelect = ProFormSearchTreeSelect;
|
||||
|
||||
export default WrappedProFormTreeSelect;
|
|
@ -0,0 +1 @@
|
|||
export default undefined;
|
|
@ -0,0 +1,57 @@
|
|||
const { uniq } = require('lodash');
|
||||
const RouterConfig = require('../../config/config').default.routes;
|
||||
|
||||
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
|
||||
|
||||
function formatter(routes, parentPath = '') {
|
||||
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
|
||||
let result = [];
|
||||
routes.forEach((item) => {
|
||||
if (item.path) {
|
||||
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
|
||||
}
|
||||
if (item.routes) {
|
||||
result = result.concat(
|
||||
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
|
||||
);
|
||||
}
|
||||
});
|
||||
return uniq(result.filter((item) => !!item));
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.goto(`${BASE_URL}`);
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('antd-pro-authority', '["admin"]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ant Design Pro E2E test', () => {
|
||||
const testPage = (path) => async () => {
|
||||
await page.goto(`${BASE_URL}${path}`);
|
||||
await page.waitForSelector('footer', {
|
||||
timeout: 2000,
|
||||
});
|
||||
const haveFooter = await page.evaluate(
|
||||
() => document.getElementsByTagName('footer').length > 0,
|
||||
);
|
||||
expect(haveFooter).toBeTruthy();
|
||||
};
|
||||
|
||||
const routers = formatter(RouterConfig);
|
||||
routers.forEach((route) => {
|
||||
it(`test pages ${route}`, testPage(route));
|
||||
});
|
||||
|
||||
it('topmenu should have footer', async () => {
|
||||
const params = '?navTheme=light&layout=topmenu';
|
||||
await page.goto(`${BASE_URL}${params}`);
|
||||
await page.waitForSelector('footer', {
|
||||
timeout: 2000,
|
||||
});
|
||||
const haveFooter = await page.evaluate(
|
||||
() => document.getElementsByTagName('footer').length > 0,
|
||||
);
|
||||
expect(haveFooter).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
@import './components/IconSelector/style.less';
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.colorWeak {
|
||||
filter: invert(80%);
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
&-thead > tr,
|
||||
&-tbody > tr {
|
||||
> th,
|
||||
> td {
|
||||
white-space: pre;
|
||||
> span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容IE11
|
||||
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
|
||||
body .ant-design-pro > .ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import { Button, message, notification } from 'antd';
|
||||
|
||||
import React from 'react';
|
||||
import { useIntl } from 'umi';
|
||||
import defaultSettings from '../config/defaultSettings';
|
||||
|
||||
const { pwa } = defaultSettings;
|
||||
const isHttps = document.location.protocol === 'https:';
|
||||
|
||||
// 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]);
|
||||
});
|
||||
// Refresh current page to use the updated HTML and other assets after SW has skiped waiting
|
||||
window.location.reload(true);
|
||||
return true;
|
||||
};
|
||||
const key = `open${Date.now()}`;
|
||||
const btn = (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
notification.close(key);
|
||||
reloadSW();
|
||||
}}
|
||||
>
|
||||
{useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
|
||||
</Button>
|
||||
);
|
||||
notification.open({
|
||||
message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
|
||||
description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
|
||||
btn,
|
||||
key,
|
||||
onClose: async () => null,
|
||||
});
|
||||
});
|
||||
} else if ('serviceWorker' in navigator && isHttps) {
|
||||
// unregister service worker
|
||||
const { serviceWorker } = navigator;
|
||||
if (serviceWorker.getRegistrations) {
|
||||
serviceWorker.getRegistrations().then((sws) => {
|
||||
sws.forEach((sw) => {
|
||||
sw.unregister();
|
||||
});
|
||||
});
|
||||
}
|
||||
serviceWorker.getRegistration().then((sw) => {
|
||||
if (sw) sw.unregister();
|
||||
});
|
||||
|
||||
// remove all caches
|
||||
if (window.caches) {
|
||||
caches.keys().then((keys) => {
|
||||
keys.forEach((key) => {
|
||||
caches.delete(key);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
|
||||
*
|
||||
* @see You can view component api by: https://github.com/ant-design/ant-design-pro-layout
|
||||
*/
|
||||
import type {
|
||||
MenuDataItem,
|
||||
BasicLayoutProps as ProLayoutProps,
|
||||
Settings,
|
||||
} from '@ant-design/pro-layout';
|
||||
import ProLayout, { DefaultFooter, SettingDrawer } from '@ant-design/pro-layout';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { Link, useIntl, connect, history } from 'umi';
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { Result, Button } from 'antd';
|
||||
import Authorized from '@/utils/Authorized';
|
||||
import RightContent from '@/components/GlobalHeader/RightContent';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import { getMatchMenu } from '@umijs/route-utils';
|
||||
import logo from '../assets/logo.svg';
|
||||
|
||||
const noMatch = (
|
||||
<Result
|
||||
status={403}
|
||||
title="403"
|
||||
subTitle="Sorry, you are not authorized to access this page."
|
||||
extra={
|
||||
<Button type="primary">
|
||||
<Link to="/user/login">Go Login</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
export type BasicLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
route: ProLayoutProps['route'] & {
|
||||
authority: string[];
|
||||
};
|
||||
currentUser: CurrentUser;
|
||||
settings: Settings;
|
||||
dispatch: Dispatch;
|
||||
} & ProLayoutProps;
|
||||
export type BasicLayoutContext = { [K in 'location']: BasicLayoutProps[K] } & {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
};
|
||||
|
||||
const defaultFooterDom = (
|
||||
<DefaultFooter
|
||||
copyright={`${new Date().getFullYear()} 蚂蚁集团体验技术部出品`}
|
||||
links={[
|
||||
{
|
||||
key: 'Ant Design Pro',
|
||||
title: 'Ant Design Pro',
|
||||
href: 'https://pro.ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'ruoyi-react',
|
||||
title: <GithubOutlined />,
|
||||
href: 'https://gitee.com/whiteshader/ruoyi-react',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'Ant Design',
|
||||
title: 'Ant Design',
|
||||
href: 'https://ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
|
||||
const {
|
||||
dispatch,
|
||||
children,
|
||||
settings,
|
||||
currentUser,
|
||||
location = {
|
||||
pathname: '/',
|
||||
},
|
||||
} = props;
|
||||
const menuDataRef = useRef<MenuDataItem[]>([]);
|
||||
useEffect(() => {
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'user/initSession',
|
||||
});
|
||||
}
|
||||
}, [dispatch]);
|
||||
/** Init variables */
|
||||
|
||||
/** Use Authorized check all menu item */
|
||||
|
||||
const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
|
||||
menuList.map((item) => {
|
||||
const localItem = {
|
||||
...item,
|
||||
children: item.children ? menuDataRender(item.children) : undefined,
|
||||
};
|
||||
return Authorized.check(currentUser, item.authority, localItem, null) as MenuDataItem;
|
||||
});
|
||||
|
||||
const handleMenuCollapse = (payload: boolean): void => {
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeLayoutCollapsed',
|
||||
payload,
|
||||
});
|
||||
}
|
||||
}; // get children authority
|
||||
|
||||
const authorized = useMemo(
|
||||
() =>
|
||||
getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
|
||||
authority: undefined,
|
||||
},
|
||||
[location.pathname],
|
||||
);
|
||||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<>
|
||||
<ProLayout
|
||||
logo={logo}
|
||||
formatMessage={formatMessage}
|
||||
{...props}
|
||||
{...settings}
|
||||
onCollapse={handleMenuCollapse}
|
||||
onMenuHeaderClick={() => history.push('/')}
|
||||
menuItemRender={(menuItemProps, defaultDom) => {
|
||||
if (
|
||||
menuItemProps.isUrl ||
|
||||
!menuItemProps.path ||
|
||||
location.pathname === menuItemProps.path
|
||||
) {
|
||||
return defaultDom;
|
||||
}
|
||||
|
||||
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
|
||||
}}
|
||||
breadcrumbRender={(routers = []) => [
|
||||
{
|
||||
path: '/',
|
||||
breadcrumbName: formatMessage({
|
||||
id: 'menu.home',
|
||||
}),
|
||||
},
|
||||
...routers,
|
||||
]}
|
||||
itemRender={(route, params, routes, paths) => {
|
||||
const first = routes.indexOf(route) === 0;
|
||||
return first ? (
|
||||
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
|
||||
) : (
|
||||
<span>{route.breadcrumbName}</span>
|
||||
);
|
||||
}}
|
||||
footerRender={() => {
|
||||
if (settings.footerRender || settings.footerRender === undefined) {
|
||||
return defaultFooterDom;
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
menuDataRender={menuDataRender}
|
||||
rightContentRender={() => <RightContent />}
|
||||
postMenuData={(menuData) => {
|
||||
menuDataRef.current = menuData || [];
|
||||
return menuData || [];
|
||||
}}
|
||||
>
|
||||
<Authorized authority={authorized!.authority} noMatch={noMatch}>
|
||||
{children}
|
||||
</Authorized>
|
||||
</ProLayout>
|
||||
<SettingDrawer
|
||||
settings={settings}
|
||||
onSettingChange={(config) =>
|
||||
dispatch({
|
||||
type: 'settings/changeSetting',
|
||||
payload: config,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ global, user, settings }: ConnectState) => ({
|
||||
collapsed: global.collapsed,
|
||||
currentUser: user.currentUser,
|
||||
settings,
|
||||
}))(BasicLayout);
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Inspector } from 'react-dev-inspector';
|
||||
|
||||
const InspectorWrapper = process.env.NODE_ENV === 'development' ? Inspector : React.Fragment;
|
||||
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
return <InspectorWrapper>{children}</InspectorWrapper>;
|
||||
};
|
||||
|
||||
export default Layout;
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
|
||||
*
|
||||
* @see You can view component api by: https://github.com/ant-design/ant-design-pro-layout
|
||||
*/
|
||||
import type {
|
||||
MenuDataItem,
|
||||
BasicLayoutProps as ProLayoutProps,
|
||||
Settings,
|
||||
} from '@ant-design/pro-layout';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import ProLayout, { DefaultFooter, SettingDrawer } from '@ant-design/pro-layout';
|
||||
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { Dispatch } from 'umi';
|
||||
import { FormattedMessage } from 'umi';
|
||||
import { Redirect } from 'umi';
|
||||
import { Link, useIntl, connect, history } from 'umi';
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { Result, Button } from 'antd';
|
||||
import Authorized from '@/utils/Authorized';
|
||||
import RightContent from '@/components/GlobalHeader/RightContent';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
// import { getMatchMenu } from '@umijs/route-utils';
|
||||
import { getAccessToken, clearToken } from '@/utils/authority';
|
||||
import { getMatchMenuItem, getRoutersData } from '@/services/routers';
|
||||
import logo from '../assets/logo.svg';
|
||||
import { createIcon } from '@/utils/IconUtil';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author whiteshader@163.com
|
||||
*
|
||||
* */
|
||||
|
||||
const noMatch = (
|
||||
<Result
|
||||
status={403}
|
||||
title="403"
|
||||
subTitle={
|
||||
<FormattedMessage
|
||||
id="pages.authorized.forbiden"
|
||||
defaultMessage="Sorry, you are not authorized to access this page."
|
||||
/>
|
||||
}
|
||||
extra={
|
||||
<Button type="primary" onClick={() => history.goBack()}>
|
||||
<FormattedMessage id="pages.goback" defaultMessage="Go Back" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export type SecurityBasicLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
route: ProLayoutProps['route'] & {
|
||||
authority: string[];
|
||||
};
|
||||
currentUser: CurrentUser;
|
||||
settings: Settings;
|
||||
dispatch: Dispatch;
|
||||
} & ProLayoutProps;
|
||||
export type BasicLayoutContext = { [K in 'location']: SecurityBasicLayoutProps[K] } & {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
};
|
||||
|
||||
const defaultFooterDom = (
|
||||
<DefaultFooter
|
||||
copyright={`${new Date().getFullYear()} 蚂蚁集团体验技术部出品`}
|
||||
links={[
|
||||
{
|
||||
key: 'Ant Design Pro',
|
||||
title: 'Ant Design Pro',
|
||||
href: 'https://pro.ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'ruoyi-react',
|
||||
title: <GithubOutlined />,
|
||||
href: 'https://gitee.com/whiteshader/ruoyi-react',
|
||||
blankTarget: true,
|
||||
},
|
||||
{
|
||||
key: 'Ant Design',
|
||||
title: 'Ant Design',
|
||||
href: 'https://ant.design',
|
||||
blankTarget: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const SecurityBasicLayout: React.FC<SecurityBasicLayoutProps> = (props) => {
|
||||
const {
|
||||
dispatch,
|
||||
children,
|
||||
settings,
|
||||
loading,
|
||||
location = {
|
||||
pathname: '/',
|
||||
},
|
||||
currentUser,
|
||||
} = props;
|
||||
|
||||
const [isReady, setIsReady] = useState<boolean>(false);
|
||||
|
||||
const [remoteMenuData, setRemoteMenuData] = useState<MenuDataItem[]>([]);
|
||||
const menuDataRef = useRef<MenuDataItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dispatch) {
|
||||
setIsReady(true);
|
||||
dispatch({
|
||||
type: 'user/initSession',
|
||||
});
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
const handleMenuCollapse = (payload: boolean): void => {
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'global/changeLayoutCollapsed',
|
||||
payload,
|
||||
});
|
||||
}
|
||||
}; // get children authority
|
||||
|
||||
/** Use Authorized check all menu item */
|
||||
const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
|
||||
menuList.map((item) => {
|
||||
const localItem = {
|
||||
...item,
|
||||
icon: createIcon(item.icon),
|
||||
children: item.children ? menuDataRender(item.children) : undefined,
|
||||
};
|
||||
// console.log(localItem)
|
||||
return Authorized.check(currentUser, item.authority, localItem, null) as MenuDataItem;
|
||||
});
|
||||
|
||||
const accessToken = getAccessToken();
|
||||
const isLogin = !!accessToken;
|
||||
const queryString = new URLSearchParams({ redirect: window.location.href }).toString();
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const authorized = useMemo(() => {
|
||||
if (location.pathname === '/') {
|
||||
return { authority: undefined };
|
||||
}
|
||||
const item = getMatchMenuItem(location.pathname || '/', remoteMenuData).pop() || {
|
||||
authority: '!*',
|
||||
};
|
||||
return item;
|
||||
}, [location, remoteMenuData]);
|
||||
|
||||
if ((!isLogin && loading) || !isReady) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!isLogin && window.location.pathname !== '/user/login') {
|
||||
clearToken();
|
||||
return <Redirect to={`/user/login?${queryString}`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProLayout
|
||||
logo={logo}
|
||||
formatMessage={formatMessage}
|
||||
{...props}
|
||||
{...settings}
|
||||
onCollapse={handleMenuCollapse}
|
||||
onMenuHeaderClick={() => history.push('/')}
|
||||
menuItemRender={(menuItemProps, defaultDom) => {
|
||||
if (
|
||||
menuItemProps.isUrl ||
|
||||
!menuItemProps.path ||
|
||||
location.pathname === menuItemProps.path
|
||||
) {
|
||||
return defaultDom;
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
{createIcon(menuItemProps.icon)} <Link to={menuItemProps.path}>{defaultDom}</Link>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
breadcrumbRender={(routers = []) => [
|
||||
{
|
||||
path: '/',
|
||||
breadcrumbName: formatMessage({
|
||||
id: 'menu.home',
|
||||
}),
|
||||
},
|
||||
...routers,
|
||||
]}
|
||||
menu={{
|
||||
locale: false,
|
||||
request: async (): Promise<MenuDataItem[]> => {
|
||||
const res = await getRoutersData();
|
||||
setRemoteMenuData(res);
|
||||
// console.log(params, defaultMenuData);
|
||||
return res;
|
||||
},
|
||||
}}
|
||||
// itemRender={(route, params, routes, paths) => {
|
||||
// const first = routes.indexOf(route) === 0;
|
||||
// return first ? (
|
||||
// <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
|
||||
// ) : (
|
||||
// <span>{route.breadcrumbName}</span>
|
||||
// );
|
||||
// }}
|
||||
footerRender={() => {
|
||||
if (settings.footerRender || settings.footerRender === undefined) {
|
||||
return defaultFooterDom;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
menuDataRender={menuDataRender}
|
||||
rightContentRender={() => <RightContent />}
|
||||
postMenuData={(menuData) => {
|
||||
menuDataRef.current = menuData || [];
|
||||
return menuData || [];
|
||||
}}
|
||||
>
|
||||
<Authorized authority={authorized.authority} noMatch={noMatch} currentUser={currentUser}>
|
||||
{children}
|
||||
</Authorized>
|
||||
</ProLayout>
|
||||
<SettingDrawer
|
||||
settings={settings}
|
||||
onSettingChange={(config) =>
|
||||
dispatch({
|
||||
type: 'settings/changeSetting',
|
||||
payload: config,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ global, user, loading, settings }: ConnectState) => ({
|
||||
collapsed: global.collapsed,
|
||||
currentUser: user.currentUser,
|
||||
settings,
|
||||
loading: loading.models.user,
|
||||
}))(SecurityBasicLayout);
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Redirect, connect } from 'umi';
|
||||
import { stringify } from 'querystring';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import type { CurrentUser } from '@/models/user';
|
||||
import { getAccessToken, clearToken } from '@/utils/authority'
|
||||
|
||||
type SecurityLayoutProps = {
|
||||
loading?: boolean;
|
||||
currentUser?: CurrentUser;
|
||||
} & ConnectProps;
|
||||
|
||||
type SecurityLayoutState = {
|
||||
isReady: boolean;
|
||||
};
|
||||
|
||||
class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {
|
||||
state: SecurityLayoutState = {
|
||||
isReady: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
isReady: true,
|
||||
});
|
||||
const { dispatch } = this.props;
|
||||
if (dispatch) {
|
||||
dispatch({
|
||||
type: 'user/initSession',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isReady } = this.state;
|
||||
const { children, loading } = this.props;
|
||||
// You can replace it to your authentication rule (such as check token exists)
|
||||
// 你可以把它替换成你自己的登录认证规则 (比如判断 token 是否存在)
|
||||
const accessToken = getAccessToken();
|
||||
// const isLogin = (currentUser && currentUser.userId && accessToken)?true:false;
|
||||
const isLogin = !!accessToken;
|
||||
const queryString = stringify({
|
||||
redirect: window.location.href,
|
||||
});
|
||||
if ((!isLogin && loading) || !isReady) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
if (!isLogin && window.location.pathname !== '/user/login') {
|
||||
clearToken();
|
||||
return <Redirect to={`/user/login?${queryString}`} />;
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ user, loading }: ConnectState) => ({
|
||||
currentUser: user.currentUser,
|
||||
loading: loading.models.user,
|
||||
}))(SecurityLayout);
|
|
@ -0,0 +1,71 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background: @layout-body-background;
|
||||
}
|
||||
|
||||
.lang {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
:global(.ant-dropdown-trigger) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: @heading-color;
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import type { MenuDataItem } from '@ant-design/pro-layout';
|
||||
import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
import type { ConnectProps } from 'umi';
|
||||
import { Link, SelectLang, useIntl, connect, FormattedMessage } from 'umi';
|
||||
import React from 'react';
|
||||
import type { ConnectState } from '@/models/connect';
|
||||
import logo from '../assets/logo.svg';
|
||||
import styles from './UserLayout.less';
|
||||
|
||||
export type UserLayoutProps = {
|
||||
breadcrumbNameMap: Record<string, MenuDataItem>;
|
||||
} & Partial<ConnectProps>;
|
||||
|
||||
const UserLayout: React.FC<UserLayoutProps> = (props) => {
|
||||
const {
|
||||
route = {
|
||||
routes: [],
|
||||
},
|
||||
} = props;
|
||||
const { routes = [] } = route;
|
||||
const {
|
||||
children,
|
||||
location = {
|
||||
pathname: '',
|
||||
},
|
||||
} = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const { breadcrumb } = getMenuData(routes);
|
||||
const title = getPageTitle({
|
||||
pathname: location.pathname,
|
||||
formatMessage,
|
||||
breadcrumb,
|
||||
...props,
|
||||
});
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={title} />
|
||||
</Helmet>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.lang}>
|
||||
<SelectLang />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<Link to="/">
|
||||
<img alt="logo" className={styles.logo} src={logo} />
|
||||
<span className={styles.title}>Ant Design</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.desc}>
|
||||
<FormattedMessage
|
||||
id="pages.layouts.userLayout.title"
|
||||
defaultMessage="Ant Design 是西湖区最具影响力的 Web 设计规范"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<DefaultFooter />
|
||||
</div>
|
||||
</HelmetProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ settings }: ConnectState) => ({ ...settings }))(UserLayout);
|
|
@ -0,0 +1,24 @@
|
|||
import component from './en-US/component';
|
||||
import globalHeader from './en-US/globalHeader';
|
||||
import menu from './en-US/menu';
|
||||
import pwa from './en-US/pwa';
|
||||
import settingDrawer from './en-US/settingDrawer';
|
||||
import settings from './en-US/settings';
|
||||
import pages from './en-US/pages';
|
||||
|
||||
export default {
|
||||
'navBar.lang': 'Languages',
|
||||
'layout.user.link.help': 'Help',
|
||||
'layout.user.link.privacy': 'Privacy',
|
||||
'layout.user.link.terms': 'Terms',
|
||||
'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',
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
'component.tagSelect.expand': 'Expand',
|
||||
'component.tagSelect.collapse': 'Collapse',
|
||||
'component.tagSelect.all': 'All',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
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',
|
||||
'menu.test': 'Test',
|
||||
'menu.test.mqtt': 'Mqtt',
|
||||
'menu.System': 'System',
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
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/ant.design)',
|
||||
'pages.login.username.placeholder': 'Username: admin or user',
|
||||
'pages.login.username.required': 'Please input your username!',
|
||||
'pages.login.password.placeholder': 'Password: ant.design',
|
||||
'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': 'Submit',
|
||||
'pages.login.loginWith': 'Login with :',
|
||||
'pages.login.registerAccount': 'Register Account',
|
||||
'pages.authorized.forbiden': 'Sorry, you are not authorized to access this page.',
|
||||
'pages.welcome.advancedComponent': 'Advanced Component',
|
||||
'pages.welcome.link': 'Welcome',
|
||||
'pages.welcome.advancedLayout': 'Advanced Layout',
|
||||
'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.chosen': 'chosen',
|
||||
'pages.searchTable.item': 'item',
|
||||
'pages.searchTable.totalServiceCalls': 'Total Number of Service Calls',
|
||||
'pages.searchTable.tenThousand': '0000',
|
||||
'pages.searchTable.batchDeletion': 'bacth deletion',
|
||||
'pages.searchTable.batchApproval': 'batch approval',
|
||||
'pages.goback': 'Go Back',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
import component from './id-ID/component';
|
||||
import globalHeader from './id-ID/globalHeader';
|
||||
import menu from './id-ID/menu';
|
||||
import pwa from './id-ID/pwa';
|
||||
import settingDrawer from './id-ID/settingDrawer';
|
||||
import settings from './id-ID/settings';
|
||||
import pages from './id-ID/pages';
|
||||
|
||||
export default {
|
||||
'navbar.lang': 'Bahasa',
|
||||
'layout.user.link.help': 'Bantuan',
|
||||
'layout.user.link.privacy': 'Privasi',
|
||||
'layout.user.link.terms': 'Ketentuan',
|
||||
'app.preview.down.block': 'Unduh halaman ini dalam projek lokal anda',
|
||||
'app.welcome.link.fetch-blocks': 'Dapatkan semua blok',
|
||||
'app.welcome.link.block-list':
|
||||
'Buat standar dengan cepat, halaman-halaman berdasarkan pengembangan `block`',
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
'component.tagSelect.expand': 'Perluas',
|
||||
'component.tagSelect.collapse': 'Lipat',
|
||||
'component.tagSelect.all': 'Semua',
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
export default {
|
||||
'component.globalHeader.search': 'Pencarian',
|
||||
'component.globalHeader.search.example1': 'Contoh 1 Pencarian',
|
||||
'component.globalHeader.search.example2': 'Contoh 2 Pencarian',
|
||||
'component.globalHeader.search.example3': 'Contoh 3 Pencarian',
|
||||
'component.globalHeader.help': 'Bantuan',
|
||||
'component.globalHeader.notification': 'Notifikasi',
|
||||
'component.globalHeader.notification.empty': 'Anda telah membaca semua notifikasi',
|
||||
'component.globalHeader.message': 'Pesan',
|
||||
'component.globalHeader.message.empty': 'Anda telah membaca semua pesan.',
|
||||
'component.globalHeader.event': 'Acara',
|
||||
'component.globalHeader.event.empty': 'Anda telah melihat semua acara.',
|
||||
'component.noticeIcon.clear': 'Kosongkan',
|
||||
'component.noticeIcon.cleared': 'Berhasil dikosongkan',
|
||||
'component.noticeIcon.empty': 'Tidak ada pemberitahuan',
|
||||
'component.noticeIcon.view-more': 'Melihat lebih',
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
export default {
|
||||
'menu.welcome': 'Selamat Datang',
|
||||
'menu.more-blocks': 'Blocks Lainnya',
|
||||
'menu.home': 'Halaman Awal',
|
||||
'menu.admin': 'Admin',
|
||||
'menu.admin.sub-page': 'Sub-Halaman',
|
||||
'menu.login': 'Masuk',
|
||||
'menu.register': 'Pendaftaran',
|
||||
'menu.register.result': 'Hasil Pendaftaran',
|
||||
'menu.dashboard': 'Dasbor',
|
||||
'menu.dashboard.analysis': 'Analisis',
|
||||
'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': 'Form Dasar',
|
||||
'menu.form.step-form': 'Form Bertahap',
|
||||
'menu.form.step-form.info': 'Form Bertahap(menulis informasi yang dibagikan)',
|
||||
'menu.form.step-form.confirm': 'Form Bertahap(konfirmasi informasi yang dibagikan)',
|
||||
'menu.form.step-form.result': 'Form Bertahap(selesai)',
|
||||
'menu.form.advanced-form': 'Form Lanjutan',
|
||||
'menu.list': 'Daftar',
|
||||
'menu.list.table-list': 'Tabel Pencarian',
|
||||
'menu.list.basic-list': 'Daftar Dasar',
|
||||
'menu.list.card-list': 'Daftar Kartu',
|
||||
'menu.list.search-list': 'Daftar Pencarian',
|
||||
'menu.list.search-list.articles': 'Daftar Pencarian(artikel)',
|
||||
'menu.list.search-list.projects': 'Daftar Pencarian(projek)',
|
||||
'menu.list.search-list.applications': 'Daftar Pencarian(aplikasi)',
|
||||
'menu.profile': 'Profil',
|
||||
'menu.profile.basic': 'Profil Dasar',
|
||||
'menu.profile.advanced': 'Profile Lanjutan',
|
||||
'menu.result': 'Hasil',
|
||||
'menu.result.success': 'Sukses',
|
||||
'menu.result.fail': 'Gagal',
|
||||
'menu.exception': 'Pengecualian',
|
||||
'menu.exception.not-permission': '403',
|
||||
'menu.exception.not-find': '404',
|
||||
'menu.exception.server-error': '500',
|
||||
'menu.exception.trigger': 'Jalankan',
|
||||
'menu.account': 'Akun',
|
||||
'menu.account.center': 'Detail Akun',
|
||||
'menu.account.settings': 'Pengaturan Akun',
|
||||
'menu.account.trigger': 'Mengaktivasi Error',
|
||||
'menu.account.logout': 'Keluar',
|
||||
'menu.editor': 'Penyusun Grafis',
|
||||
'menu.editor.flow': 'Penyusun Alur',
|
||||
'menu.editor.mind': 'Penyusun Mind',
|
||||
'menu.editor.koni': 'Penyusun Koni',
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
export default {
|
||||
'pages.layouts.userLayout.title':
|
||||
'Ant Design adalah spesifikasi desain Web yang paling berpengaruh di Kabupaten Xihu',
|
||||
'pages.login.accountLogin.tab': 'Login dengan akun',
|
||||
'pages.login.accountLogin.errorMessage': 'Nama pengguna dan kata sandi salah(admin/ant.design)',
|
||||
'pages.login.username.placeholder': 'nama pengguna: admin atau user',
|
||||
'pages.login.username.required': 'Nama pengguna harus diisi!',
|
||||
'pages.login.password.placeholder': 'kata sandi: ant.design',
|
||||
'pages.login.password.required': 'Kata sandi harus diisi!',
|
||||
'pages.login.phoneLogin.tab': 'Login dengan ponsel',
|
||||
'pages.login.phoneLogin.errorMessage': 'Kesalahan kode verifikasi',
|
||||
'pages.login.phoneNumber.placeholder': 'masukkan nomor telepon',
|
||||
'pages.login.phoneNumber.required': 'Nomor ponsel harus diisi!',
|
||||
'pages.login.phoneNumber.invalid': 'Nomor ponsel tidak valid!',
|
||||
'pages.login.captcha.placeholder': 'kode verifikasi',
|
||||
'pages.login.captcha.required': 'Kode verifikasi diperlukan!',
|
||||
'pages.login.phoneLogin.getVerificationCode': 'Dapatkan kode',
|
||||
'pages.getCaptchaSecondText': 'detik tersisa',
|
||||
'pages.login.rememberMe': 'Ingat saya',
|
||||
'pages.login.forgotPassword': 'Lupa Kata Sandi?',
|
||||
'pages.login.submit': 'Masuk',
|
||||
'pages.login.loginWith': 'Masuk dengan :',
|
||||
'pages.login.registerAccount': 'Daftar Akun',
|
||||
'pages.welcome.advancedComponent': 'Formulir Lanjutan',
|
||||
'pages.welcome.link': 'Selamat datang',
|
||||
'pages.welcome.advancedLayout': 'Tata letak Lanjutan',
|
||||
'pages.welcome.alertMessage':
|
||||
'Komponen heavy-duty yang lebih cepat dan lebih kuat telah dirilis.',
|
||||
'pages.admin.subPage.title': 'Halaman ini hanya dapat dilihat oleh admin',
|
||||
'pages.admin.subPage.alertMessage':
|
||||
'umi ui telah dirilis, silahkan gunakan npm run ui untuk memulai pengalaman.',
|
||||
'pages.searchTable.createForm.newRule': 'Aturan baru',
|
||||
'pages.searchTable.updateForm.ruleConfig': 'Konfigurasi aturan',
|
||||
'pages.searchTable.updateForm.basicConfig': 'Informasi dasar',
|
||||
'pages.searchTable.updateForm.ruleName.nameLabel': 'Nama aturan',
|
||||
'pages.searchTable.updateForm.ruleName.nameRules': 'Harap masukkan nama aturan!',
|
||||
'pages.searchTable.updateForm.ruleDesc.descLabel': 'Deskripsi aturan',
|
||||
'pages.searchTable.updateForm.ruleDesc.descPlaceholder':
|
||||
'Harap masukkan setidaknya lima karakter',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules':
|
||||
'Harap masukkan deskripsi aturan setidaknya lima karakter!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': 'Properti aturan',
|
||||
'pages.searchTable.updateForm.object': 'Objek pemantauan',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': 'Template aturan',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': 'Jenis aturan',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': 'Periode penjadwalan',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Waktu mulai',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'Pilih waktu mulai!',
|
||||
'pages.searchTable.titleDesc': 'deskripsi',
|
||||
'pages.searchTable.ruleName': 'Nama aturan wajib diisi',
|
||||
'pages.searchTable.titleCallNo': 'Jumlah panggilan',
|
||||
'pages.searchTable.titleStatus': 'Status',
|
||||
'pages.searchTable.nameStatus.default': 'default',
|
||||
'pages.searchTable.nameStatus.running': 'menyala',
|
||||
'pages.searchTable.nameStatus.online': 'online',
|
||||
'pages.searchTable.nameStatus.abnormal': 'abnormal',
|
||||
'pages.searchTable.titleUpdatedAt': 'Waktu terjadwal',
|
||||
'pages.searchTable.exception': 'Harap masukkan alasan pengecualian!',
|
||||
'pages.searchTable.titleOption': 'Pengoperasian',
|
||||
'pages.searchTable.config': 'Konfigurasi',
|
||||
'pages.searchTable.subscribeAlert': 'Berlangganan notifikasi',
|
||||
'pages.searchTable.title': 'Formulir pertanyaan',
|
||||
'pages.searchTable.new': 'Baru',
|
||||
'pages.searchTable.chosen': 'Terpilih',
|
||||
'pages.searchTable.item': 'item',
|
||||
'pages.searchTable.totalServiceCalls': 'Jumlah total panggilan layanan',
|
||||
'pages.searchTable.tenThousand': '0000',
|
||||
'pages.searchTable.batchDeletion': 'Penghapusan batch',
|
||||
'pages.searchTable.batchApproval': 'Persetujuan batch',
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'app.pwa.offline': 'Koneksi anda terputus',
|
||||
'app.pwa.serviceworker.updated': 'Konten baru sudah tersedia',
|
||||
'app.pwa.serviceworker.updated.hint':
|
||||
'Silahkan klik tombol "Refresh" untuk memuat ulang halaman ini',
|
||||
'app.pwa.serviceworker.updated.ok': 'Memuat ulang',
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
'app.setting.pagestyle': 'Pengaturan style Halaman',
|
||||
'app.setting.pagestyle.dark': 'Style Gelap',
|
||||
'app.setting.pagestyle.light': 'Style Cerah',
|
||||
'app.setting.content-width': 'Lebar Konten',
|
||||
'app.setting.content-width.fixed': 'Tetap',
|
||||
'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 (bawaan)',
|
||||
'app.setting.themecolor.geekblue': 'Geek Glue',
|
||||
'app.setting.themecolor.purple': 'Golden Purple',
|
||||
'app.setting.navigationmode': 'Mode Navigasi',
|
||||
'app.setting.sidemenu': 'Susunan Menu Samping',
|
||||
'app.setting.topmenu': 'Susunan Menu Atas',
|
||||
'app.setting.fixedheader': 'Header Tetap',
|
||||
'app.setting.fixedsidebar': 'Sidebar Tetap',
|
||||
'app.setting.fixedsidebar.hint': 'Berjalan pada Susunan Menu Samping',
|
||||
'app.setting.hideheader': 'Sembunyikan Header ketika gulir ke bawah',
|
||||
'app.setting.hideheader.hint': 'Bekerja ketika Header tersembunyi dimunculkan',
|
||||
'app.setting.othersettings': 'Pengaturan Lainnya',
|
||||
'app.setting.weakmode': 'Mode Lemah',
|
||||
'app.setting.copy': 'Salin Pengaturan',
|
||||
'app.setting.copyinfo':
|
||||
'Berhasil disalin,tolong ubah defaultSettings pada src/models/setting.js',
|
||||
'app.setting.production.hint':
|
||||
'Panel pengaturan hanya muncul pada lingkungan pengembangan, silahkan modifikasi secara menual',
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
export default {
|
||||
'app.settings.menuMap.basic': 'Pengaturan Dasar',
|
||||
'app.settings.menuMap.security': 'Pengaturan Keamanan',
|
||||
'app.settings.menuMap.binding': 'Pengikatan Akun',
|
||||
'app.settings.menuMap.notification': 'Notifikasi Pesan Baru',
|
||||
'app.settings.basic.avatar': 'Avatar',
|
||||
'app.settings.basic.change-avatar': 'Ubah avatar',
|
||||
'app.settings.basic.email': 'Email',
|
||||
'app.settings.basic.email-message': 'Tolong masukkan email!',
|
||||
'app.settings.basic.nickname': 'Nickname',
|
||||
'app.settings.basic.nickname-message': 'Tolong masukkan Nickname!',
|
||||
'app.settings.basic.profile': 'Profil Personal',
|
||||
'app.settings.basic.profile-message': 'Tolong masukkan profil personal!',
|
||||
'app.settings.basic.profile-placeholder': 'Perkenalan Singkat tentang Diri Anda',
|
||||
'app.settings.basic.country': 'Negara/Wilayah',
|
||||
'app.settings.basic.country-message': 'Tolong masukkan negara anda!',
|
||||
'app.settings.basic.geographic': 'Provinsi atau kota',
|
||||
'app.settings.basic.geographic-message': 'Tolong masukkan info geografis anda!',
|
||||
'app.settings.basic.address': 'Alamat Jalan',
|
||||
'app.settings.basic.address-message': 'Tolong masukkan Alamat Jalan anda!',
|
||||
'app.settings.basic.phone': 'Nomor Ponsel',
|
||||
'app.settings.basic.phone-message': 'Tolong masukkan Nomor Ponsel anda!',
|
||||
'app.settings.basic.update': 'Perbarui Informasi',
|
||||
'app.settings.security.strong': 'Kuat',
|
||||
'app.settings.security.medium': 'Sedang',
|
||||
'app.settings.security.weak': 'Lemah',
|
||||
'app.settings.security.password': 'Kata Sandi Akun',
|
||||
'app.settings.security.password-description': 'Kekuatan Kata Sandi saat ini',
|
||||
'app.settings.security.phone': 'Keamanan Ponsel',
|
||||
'app.settings.security.phone-description': 'Mengikat Ponsel',
|
||||
'app.settings.security.question': 'Pertanyaan Keamanan',
|
||||
'app.settings.security.question-description':
|
||||
'Pertanyaan Keamanan belum diatur, dan kebijakan keamanan dapat melindungi akun secara efektif',
|
||||
'app.settings.security.email': 'Email Cadangan',
|
||||
'app.settings.security.email-description': 'Mengikat Email',
|
||||
'app.settings.security.mfa': 'Perangka MFA',
|
||||
'app.settings.security.mfa-description':
|
||||
'Tidak mengikat Perangkat MFA, setelah diikat, dapat dikonfirmasi dua kali',
|
||||
'app.settings.security.modify': 'Modifikasi',
|
||||
'app.settings.security.set': 'Setel',
|
||||
'app.settings.security.bind': 'Ikat',
|
||||
'app.settings.binding.taobao': 'Mengikat Taobao',
|
||||
'app.settings.binding.taobao-description': 'Tidak mengikat akun Taobao saat ini',
|
||||
'app.settings.binding.alipay': 'Mengikat Alipay',
|
||||
'app.settings.binding.alipay-description': 'Tidak mengikat akun Alipay saat ini',
|
||||
'app.settings.binding.dingding': 'Mengikat DingTalk',
|
||||
'app.settings.binding.dingding-description': 'Tidak mengikat akun DingTalk',
|
||||
'app.settings.binding.bind': 'Ikat',
|
||||
'app.settings.notification.password': 'Kata Sandi Akun',
|
||||
'app.settings.notification.password-description':
|
||||
'Pesan dari pengguna lain akan diberitahu dalam bentuk surat',
|
||||
'app.settings.notification.messages': 'Pesan Sistem',
|
||||
'app.settings.notification.messages-description':
|
||||
'Pesan sistem akan diberitahu dalam bentuk surat',
|
||||
'app.settings.notification.todo': 'Notifikasi daftar To-do',
|
||||
'app.settings.notification.todo-description':
|
||||
'Daftar to-do akan diberitahukan dalam bentuk surat dari stasiun',
|
||||
'app.settings.open': 'Buka',
|
||||
'app.settings.close': 'Tutup',
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
import globalHeader from './ja-JP/globalHeader';
|
||||
import menu from './ja-JP/menu';
|
||||
import settingDrawer from './ja-JP/settingDrawer';
|
||||
import settings from './ja-JP/settings';
|
||||
import pwa from './ja-JP/pwa';
|
||||
import component from './ja-JP/component';
|
||||
import pages from './ja-JP/pages';
|
||||
|
||||
export default {
|
||||
'navBar.lang': '言語',
|
||||
'layout.user.link.help': 'ヘルプ',
|
||||
'layout.user.link.privacy': 'プライバシー',
|
||||
'layout.user.link.terms': '利用規約',
|
||||
'app.preview.down.block': 'このページをローカルプロジェクトにダウンロードしてください',
|
||||
'app.welcome.link.fetch-blocks': '',
|
||||
'app.welcome.link.block-list': '',
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
'component.tagSelect.expand': '展開',
|
||||
'component.tagSelect.collapse': '折りたたむ',
|
||||
'component.tagSelect.all': 'すべて',
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
export default {
|
||||
'component.globalHeader.search': '検索',
|
||||
'component.globalHeader.search.example1': '検索例1',
|
||||
'component.globalHeader.search.example2': '検索例2',
|
||||
'component.globalHeader.search.example3': '検索例3',
|
||||
'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': 'もっと見る',
|
||||
};
|
|
@ -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': 'ダッシュボード',
|
||||
'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': 'コニエディター',
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
export default {
|
||||
'pages.layouts.userLayout.title': 'Ant Designは、西湖区で最も影響力のあるWebデザイン仕様です。',
|
||||
'pages.login.accountLogin.tab': 'アカウントログイン',
|
||||
'pages.login.accountLogin.errorMessage':
|
||||
'ユーザー名/パスワードが正しくありません(admin/ant.design)',
|
||||
'pages.login.username.placeholder': 'ユーザー名:adminまたはuser',
|
||||
'pages.login.username.required': 'ユーザー名を入力してください!',
|
||||
'pages.login.password.placeholder': 'パスワード:ant.design',
|
||||
'pages.login.password.required': 'パスワードを入力してください!',
|
||||
'pages.login.phoneLogin.tab': '電話ログイン',
|
||||
'pages.login.phoneLogin.errorMessage': '検証コードエラー',
|
||||
'pages.login.phoneNumber.placeholder': '電話番号',
|
||||
'pages.login.phoneNumber.required': '電話番号を入力してください!',
|
||||
'pages.login.phoneNumber.invalid': '電話番号が無効です!',
|
||||
'pages.login.captcha.placeholder': '確認コード',
|
||||
'pages.login.captcha.required': '確認コードを入力してください!',
|
||||
'pages.login.phoneLogin.getVerificationCode': '確認コードを取得',
|
||||
'pages.getCaptchaSecondText': '秒',
|
||||
'pages.login.rememberMe': 'Remember me',
|
||||
'pages.login.forgotPassword': 'パスワードをお忘れですか?',
|
||||
'pages.login.submit': 'ログイン',
|
||||
'pages.login.loginWith': 'その他のログイン方法:',
|
||||
'pages.login.registerAccount': 'アカウント登録',
|
||||
'pages.welcome.advancedComponent': '高度なコンポーネント',
|
||||
'pages.welcome.link': 'ようこそ',
|
||||
'pages.welcome.advancedLayout': '高度なレイアウト',
|
||||
'pages.welcome.alertMessage': 'より高速で強力な頑丈なコンポーネントがリリースされました。',
|
||||
'pages.admin.subPage.title': 'このページは管理者のみが表示できます',
|
||||
'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': '5文字以上入力してください',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules': '5文字以上のルールの説明を入力してください!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': 'プロパティの構成',
|
||||
'pages.searchTable.updateForm.object': '監視対象',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': 'ルールテンプレート',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': 'ルールタイプ',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': 'スケジュール期間の設定',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '開始時間',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '開始時間を選択してください!',
|
||||
'pages.searchTable.titleDesc': '説明',
|
||||
'pages.searchTable.ruleName': 'ルール名が必要です',
|
||||
'pages.searchTable.titleCallNo': 'サービスコール数',
|
||||
'pages.searchTable.titleStatus': 'ステータス',
|
||||
'pages.searchTable.nameStatus.default': 'デフォルト',
|
||||
'pages.searchTable.nameStatus.running': '起動中',
|
||||
'pages.searchTable.nameStatus.online': 'オンライン',
|
||||
'pages.searchTable.nameStatus.abnormal': '異常',
|
||||
'pages.searchTable.titleUpdatedAt': '最終スケジュール',
|
||||
'pages.searchTable.exception': '例外の理由を入力してください!',
|
||||
'pages.searchTable.titleOption': 'オプション',
|
||||
'pages.searchTable.config': '構成',
|
||||
'pages.searchTable.subscribeAlert': 'アラートを購読する',
|
||||
'pages.searchTable.title': 'お問い合わせフォーム',
|
||||
'pages.searchTable.new': '新しい',
|
||||
'pages.searchTable.chosen': '選んだ項目',
|
||||
'pages.searchTable.item': '項目',
|
||||
'pages.searchTable.totalServiceCalls': 'サービスコールの総数',
|
||||
'pages.searchTable.tenThousand': '万',
|
||||
'pages.searchTable.batchDeletion': 'バッチ削除',
|
||||
'pages.searchTable.batchApproval': 'バッチ承認',
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
'app.pwa.offline': 'あなたは今オフラインです',
|
||||
'app.pwa.serviceworker.updated': '新しいコンテンツが利用可能です',
|
||||
'app.pwa.serviceworker.updated.hint':
|
||||
'現在のページをリロードするには、「更新」ボタンを押してください',
|
||||
'app.pwa.serviceworker.updated.ok': 'リフレッシュ',
|
||||
};
|
|
@ -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': '固定ヘッダー',
|
||||
'app.setting.fixedsidebar': '固定サイドバー',
|
||||
'app.setting.fixedsidebar.hint': 'サイドメニューのレイアウトで動作します',
|
||||
'app.setting.hideheader': 'スクロール時の非表示ヘッダー',
|
||||
'app.setting.hideheader.hint': '非表示ヘッダーが有効になっている場合に機能します',
|
||||
'app.setting.othersettings': 'その他の設定',
|
||||
'app.setting.weakmode': 'ウィークモード',
|
||||
'app.setting.copy': 'コピー設定',
|
||||
'app.setting.copyinfo':
|
||||
'コピーが成功しました。src/models/setting.jsのdefaultSettingsを置き換えてください',
|
||||
'app.setting.production.hint': '設定パネルは開発環境でのみ表示されます。手動で変更してください',
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
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': '多要素認証デバイス',
|
||||
'app.settings.security.mfa-description':
|
||||
'バインドされていない多要素認証デバイスは、バインド後、2回確認できます',
|
||||
'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': 'To Do(用事) 通知',
|
||||
'app.settings.notification.todo-description': 'To Doタスクは、内部レターの形式で通知されます',
|
||||
'app.settings.open': '開く',
|
||||
'app.settings.close': '閉じる',
|
||||
};
|