├── .env.develop
├── .env.mock
├── .env.pre
├── .env.pro
├── .env.test
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── README.md
├── craco.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.png
└── index.html
├── src
├── Mock
│ ├── accountManage.js
│ ├── index.js
│ ├── rolesManage.js
│ ├── user.js
│ └── utils.js
├── api
│ ├── AccountsManage
│ │ └── AccountsManage.ts
│ ├── EntryScreenApi
│ │ └── EntryScreenApi.ts
│ ├── request.ts
│ └── rolesManage
│ │ └── rolesManage.ts
├── assets
│ └── images
│ │ ├── entryScreenBG.svg
│ │ ├── index.ts
│ │ └── logo.png
├── components
│ ├── AdvancedModal
│ │ └── AdvancedModal.tsx
│ ├── AdvancedSearch
│ │ └── AdvancedSearch.tsx
│ ├── AppProviders
│ │ ├── AppProviders.tsx
│ │ └── InitProviders.tsx
│ ├── AuthRoute
│ │ └── AuthRoute.tsx
│ ├── BreadCrumbPro
│ │ └── BreadCrumbPro.tsx
│ ├── Copyright
│ │ ├── Copyright.module.less
│ │ └── Copyright.tsx
│ ├── CustomLogo
│ │ ├── CustomLogo.module.less
│ │ └── CustomLogo.tsx
│ ├── CustomMenu
│ │ └── CustomMenu.tsx
│ ├── LoadingComponent
│ │ └── LoadingComponent.tsx
│ ├── ToggleLang
│ │ ├── ToggleLang.tsx
│ │ └── params.ts
│ ├── aboutAdvanceTable
│ │ ├── AdvancedTable
│ │ │ ├── AdvancedTable.tsx
│ │ │ └── components
│ │ │ │ └── ColumnsConfig
│ │ │ │ └── ColumnsConfig.tsx
│ │ └── AdvancedTablePro
│ │ │ └── AdvancedTablePro.tsx
│ └── aboutAuthButton
│ │ ├── AuthButton
│ │ ├── AuthButton.module.less
│ │ └── AuthButton.tsx
│ │ └── AuthButtonGroup
│ │ └── index.tsx
├── config
│ └── config.ts
├── index.tsx
├── layouts
│ ├── LayoutContent
│ │ ├── LayoutContent.module.less
│ │ └── LayoutContent.tsx
│ ├── LayoutHeader
│ │ ├── HeaderRight
│ │ │ ├── HeaderRight.module.less
│ │ │ └── HeaderRight.tsx
│ │ ├── LayoutHeader.module.less
│ │ ├── LayoutHeader.tsx
│ │ └── components
│ │ │ ├── NotificationCenter
│ │ │ └── NotificationCenter.tsx
│ │ │ └── Personal
│ │ │ └── Personal.tsx
│ └── LayoutSider
│ │ └── LayoutSiderMenu.tsx
├── locales
│ ├── en_US
│ │ ├── columns.ts
│ │ ├── common.ts
│ │ ├── errorCode.ts
│ │ ├── header.ts
│ │ ├── login.ts
│ │ ├── menus.ts
│ │ ├── rightCode.ts
│ │ └── searchForm.ts
│ ├── index.ts
│ └── zh_CN
│ │ ├── columns.ts
│ │ ├── common.ts
│ │ ├── errorCode.ts
│ │ ├── header.ts
│ │ ├── login.ts
│ │ ├── menus.ts
│ │ ├── rightCode.ts
│ │ └── searchForm.ts
├── pages
│ ├── App.module.less
│ ├── App.tsx
│ ├── AuthManage
│ │ ├── AccountsManage
│ │ │ ├── AccountsManage.tsx
│ │ │ ├── components
│ │ │ │ ├── AccountModal
│ │ │ │ │ └── AccountModal.tsx
│ │ │ │ └── AccountTable
│ │ │ │ │ └── AccountTable.tsx
│ │ │ └── service
│ │ │ │ ├── columnsHook.ts
│ │ │ │ └── constantParams.ts
│ │ ├── MenusManage
│ │ │ └── MenusManage.tsx
│ │ └── RolesManage
│ │ │ ├── RolesManage.tsx
│ │ │ └── service
│ │ │ ├── aboutSearchForm.tsx
│ │ │ ├── aboutTable.tsx
│ │ │ └── constantParams.ts
│ ├── Dashbord
│ │ ├── Analysis
│ │ │ ├── Analysis.tsx
│ │ │ └── components
│ │ │ │ ├── ConsumptionRanking.tsx
│ │ │ │ ├── DistributionMap.tsx
│ │ │ │ └── TrendChart.tsx
│ │ └── Monitor
│ │ │ ├── Monitor.module.less
│ │ │ └── Monitor.tsx
│ ├── EntryScreen
│ │ ├── EntryScreen.module.less
│ │ ├── EntryScreen.tsx
│ │ ├── Login
│ │ │ └── Login.tsx
│ │ ├── Register
│ │ │ └── Register.tsx
│ │ ├── service
│ │ │ └── EntryScreenHoooks.ts
│ │ └── type.ts
│ ├── Home
│ │ └── Home.tsx
│ ├── NotFind
│ │ └── NotFind.tsx
│ └── System
│ │ └── SystemConfig
│ │ ├── SystemConfig.tsx
│ │ └── components
│ │ ├── SiderMenuConfig
│ │ └── SiderMenuConfig.tsx
│ │ └── TopMenuConfig
│ │ └── TopMenuConfig.tsx
├── publicHooks
│ ├── apiHooks
│ │ └── apiHooks.ts
│ ├── index.ts
│ ├── modalHooks
│ │ └── modalHooks.ts
│ └── tableHooks
│ │ └── tableHooks.ts
├── react-app-env.d.ts
├── routers
│ ├── AsyncComponent.ts
│ └── userDynamicRouters.tsx
├── store
│ ├── actionTypes
│ │ └── configActionType.ts
│ ├── reducers
│ │ ├── configReducer
│ │ │ └── configReducer.ts
│ │ └── reducers.ts
│ └── store.ts
├── styles
│ ├── globalAntd.module.less
│ └── reset.less
├── typings
│ ├── accountsManage.d.ts
│ ├── breadcrumbItem.d.ts
│ ├── config.d.ts
│ ├── rolesManage.d.ts
│ ├── router.d.ts
│ ├── store.d.ts
│ ├── systemConfig.d.ts
│ └── typings.d.ts
└── utils
│ ├── globalConstantParams.ts
│ └── public.ts
├── tsconfig.json
└── yarn.lock
/.env.develop:
--------------------------------------------------------------------------------
1 | # 开发环境
2 | REACT_APP_ENV = develop
3 | REACT_APP_BASEURL = http://localhost:8090
--------------------------------------------------------------------------------
/.env.mock:
--------------------------------------------------------------------------------
1 | # mock环境
2 | REACT_APP_ENV = mock
3 | REACT_APP_BASEURL = undefined
--------------------------------------------------------------------------------
/.env.pre:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeshuai0329/react-admin/9e20ccd507b36ad4c6fa744e7abd428925c7fcc8/.env.pre
--------------------------------------------------------------------------------
/.env.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeshuai0329/react-admin/9e20ccd507b36ad4c6fa744e7abd428925c7fcc8/.env.pro
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeshuai0329/react-admin/9e20ccd507b36ad4c6fa744e7abd428925c7fcc8/.env.test
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.d.ts
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true
5 | },
6 | extends: [
7 | 'plugin:react/recommended',
8 | 'standard'
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | parserOptions: {
12 | ecmaFeatures: {
13 | jsx: true
14 | },
15 | ecmaVersion: 12,
16 | sourceType: 'module'
17 | },
18 | plugins: [
19 | 'react',
20 | '@typescript-eslint'
21 | ],
22 | rules: {
23 | semi: ['error', 'never'],
24 | 'no-var': 0, // 禁用var,用let和const代替
25 | 'space-before-function-paren': [
26 | 'error',
27 | {
28 | anonymous: 'always',
29 | named: 'never',
30 | asyncArrow: 'always'
31 | }
32 | ],
33 | 'react/prop-types': 0, // 防止在react组件定义中缺少props验证
34 | 'no-new': 0,
35 | 'no-use-before-define': 'off',
36 | '@typescript-eslint/no-use-before-define': 'off',
37 | 'react/display-name': [0],
38 | 'max-len': ['error', { code: 140 }]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /build
3 | /.idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎉基于react17+hooks+ts中后台R-Boot🎉
2 | ## 一、序言
3 | `R-Boot` 是基于 `create-react-app` 封装的一款脚手架,用于快速搭建 react 中后台项目,内置了{redux}国际化解决方案,权限路由方案,按钮粒度权限解决方案,提炼了经典的中后台业务模型。
4 |
5 |
6 | ## 二、目录结构
7 | ```html
8 | ├── public
9 | │ └── favicon.png # Favicon
10 | │ └── index.html # 入口html文件
11 | ├── src
12 | │ ├── api # 后台接口服务
13 | │ ├── assets # 本地静态资源
14 | │ │ └── images # 全局图片资源
15 | │ ├── components # 业务通用组件
16 | │ │ └── AdvancedSearch # Form高级搜索
17 | │ │ └── AdvancedTable # Table高级表格
18 | │ │ └── AppProviders # App组件包裹组件
19 | │ │ └── AuthButton # 全局权限按钮
20 | │ │ └── AuthRoute # 全局权限路由,token过期,跳转登录页/过滤一些特定的路由
21 | │ │ └── BreadCrumbPro # 面包屑
22 | │ │ └── Copyright # footer
23 | │ │ └── CustomLogo # logo
24 | │ │ └── CustomMenu # 自定义菜单
25 | │ │ └── ToggleLang # 全局国际化语言组件
26 | │ ├── config # 全局配置文件
27 | │ │ └── config.ts # 全局配置文件
28 | │ ├── layouts # 通用布局
29 | │ ├── locales # 国际化资源
30 | │ ├── Mock #模拟后台请求的mock服务器
31 | │ ├── pages # 业务页面入口和常用模板
32 | │ │ └── EntryScreen # 登录注册界面
33 | │ ├── publicHooks # 公共的hooks
34 | │ │ └── apiHooks # apihooks
35 | │ │ └── modalHooks # 模态框hooks
36 | │ │ └── tableHooks # 表格hooks
37 | │ │ └── index # 公共hooks
38 | │ ├── routers # 全局路由组件配置
39 | │ │ └── AsyncComponent # 异步加载组件
40 | │ │ └── userDynamicRouters # 动态路由匹配文件
41 | │ ├── store # 全局状态共享store
42 | │ ├── styles # 全局样式
43 | │ ├── typings # 全局TS声明文件
44 | │ ├── utils # 工具库/通用库
45 | │ ├── react-app-env.d.ts # 全局忽略文件
46 | │ └── index.ts # 入口文件
47 | ├── .env.mock # mock环境变量配置文件
48 | ├── .env.develop # 开发环境变量配置文件
49 | ├── .env.test # 测试环境变量配置文件
50 | ├── .env.pre # 预发布环境变量配置文件
51 | ├── .env.pro # 生产环境变量配置文件
52 | ├── .eslintrc.js # eslint配置文件
53 | ├── .gitignore # git 上传忽略文件
54 | ├── config-overrides # webpack 扩展文件
55 | ├── package-lock.json # 项目配置文件
56 | ├── package.json # 项目配置文件
57 | ├── README.md # 项目介绍文件README
58 | └── tsconfig.json # ts配置文件
59 | ```
60 |
61 | ## 三、启动项目
62 | 1. 首先下载项目依赖
63 | ```
64 | $ npm i
65 | ```
66 | 2. 启动mock环境数据
67 | ```
68 | $ npm run start:mock
69 | &
70 | $ yarn start:mock
71 | ```
72 | 3. 具体脚本命令 ---> package.json
73 |
74 |
75 | ## 四、项目依赖
76 |
77 | - react 17 - 中后台项目选型使用 react 框架
78 | - react-hooks - 使用最新的官方推荐的最新的hooks写法
79 | - typescript 4 - 使用更强的typescript语言
80 | - react-router
81 | - react-router-dom
82 | - redux - 使用redux作为全局状态管理工具
83 | - react-redux - 使用react-redux插件更便捷使用redux
84 | - eslint - 使用eslint检测规则
85 | - less - 使用less作为css预处理器语言
86 | - axios - 使用axios网络库,请求数据
87 | - antd - 使用antd组件库快速开发
88 | - mock - 使用mock 模拟数据开发
89 |
90 | ...
91 | ## 五、功能
92 |
93 | - 菜单、路由权限
94 | - 从服务端获取路由信息
95 | - 路由信息包含auth字段;
96 | - auth===ture,则用户对该路由具有可视权限,渲染此菜单和路由;
97 | - auth===false,则用户对该路由不具有可视权限,并且不会渲染此菜单和该路由;
98 |
99 | - 按钮权限
100 | - 基于用户具有可视权限菜单, 设计AuthButton组件,对新建、编辑、删除、导出等按钮做颗粒度控制
101 | - 基于Antd Button设计AuthButton组件,实现颗粒度按钮权限控制。
102 | - 增加 auth 属性, 用户菜单权限数组包含 auth 属性对应的值,则显示此按钮
103 | - 增加 customtype 属性, 增加Antd Button 按钮的样式 sucess|warning|error|info
104 |
105 | - 单向的数据流
106 | - 使用redux做全局共享状态管理器
107 | - 使用react-redux便捷操作redux
108 |
109 | - 语言国际化
110 | - 依据需求设计语言国际化模块。
111 | - 设计ToggleLang组件,做全局国际化语言切换,利用localStorage对语言状态做持久化。
112 | - 利用require.context() 工程化自动化读取国际化语言模块文件,解放🙌🏻
113 |
114 | - 统一错误码拦截
115 | - 在axios拦截器中拦截错误码,提示错误信息,优化工作效率
116 |
117 | - 文件完整度校验
118 | - 采用MD5对上传文件计算MD5,与后台交互校验文件完整性
119 |
120 | ## 六、性能
121 | - 代码分割
122 |
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | const CracoLessPlugin = require('craco-less')
2 |
3 | module.exports = {
4 | plugins: [
5 | {
6 | plugin: CracoLessPlugin,
7 | options: {
8 | lessLoaderOptions: {
9 | lessOptions: {
10 | modifyVars: {
11 | // '@primary-color': '#1DA57A',
12 | '@border-radius-base': '4px'
13 | },
14 | javascriptEnabled: true
15 | }
16 | }
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "r-boot",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.3.0",
7 | "@testing-library/jest-dom": "^5.12.0",
8 | "@testing-library/react": "^11.2.6",
9 | "@testing-library/user-event": "^12.8.3",
10 | "@types/jest": "^26.0.22",
11 | "@types/node": "^12.20.10",
12 | "@types/react": "^17.0.3",
13 | "@types/react-dom": "^17.0.3",
14 | "antd": "^4.16.0",
15 | "axios": "^0.21.1",
16 | "babel-plugin-import": "^1.13.3",
17 | "classnames": "^2.3.1",
18 | "craco-less": "^1.20.0",
19 | "dotenv-cli": "^4.0.0",
20 | "echarts": "^5.2.1",
21 | "js-cookie": "^2.2.1",
22 | "less": "^4.1.1",
23 | "less-loader": "^5.0.0",
24 | "lodash": "^4.17.21",
25 | "rc-animate": "^3.1.1",
26 | "rc-queue-anim": "^1.8.5",
27 | "rc-texty": "^0.2.0",
28 | "react": "^17.0.2",
29 | "react-dom": "^17.0.2",
30 | "react-redux": "^7.2.4",
31 | "react-router": "^5.2.0",
32 | "react-router-dom": "^5.2.0",
33 | "react-scripts": "4.0.3",
34 | "redux": "^4.1.0",
35 | "redux-thunk": "^2.3.0",
36 | "spark-md5": "^3.0.2",
37 | "typescript": "^4.2.4",
38 | "web-vitals": "^1.1.1"
39 | },
40 | "scripts": {
41 | "start:mock": "dotenv -e .env.mock craco start",
42 | "start": "dotenv -e .env.develop craco start",
43 | "start:test": "dotenv -e .env.test craco start",
44 | "build:develop": "dotenv -e .env.develop craco build",
45 | "build:test": "dotenv -e .env.test craco build",
46 | "build:pre": "dotenv -e .env.pre craco build",
47 | "build:pro": "dotenv -e .env.pro craco build",
48 | "lint-fix": "eslint --fix --ext .js,.ts,.tsx src/",
49 | "test": "craco test"
50 | },
51 | "eslintConfig": {
52 | "extends": [
53 | "react-app",
54 | "react-app/jest"
55 | ]
56 | },
57 | "browserslist": {
58 | "production": [
59 | ">0.2%",
60 | "not dead",
61 | "not op_mini all"
62 | ],
63 | "development": [
64 | "last 1 chrome version",
65 | "last 1 firefox version",
66 | "last 1 safari version"
67 | ]
68 | },
69 | "devDependencies": {
70 | "@types/js-cookie": "^2.2.6",
71 | "@types/lodash": "^4.14.172",
72 | "@types/react-loadable": "^5.5.5",
73 | "@types/react-router": "^5.1.14",
74 | "@types/react-router-dom": "^5.1.7",
75 | "@types/spark-md5": "^3.0.2",
76 | "@typescript-eslint/eslint-plugin": "^4.22.1",
77 | "@typescript-eslint/parser": "^4.22.1",
78 | "eslint": "^7.26.0",
79 | "eslint-config-standard": "^16.0.2",
80 | "eslint-plugin-node": "^11.1.0",
81 | "eslint-plugin-promise": "^4.3.1",
82 | "eslint-plugin-react": "^7.23.2",
83 | "mockjs": "^1.1.0",
84 | "prettier": "^1.9.1",
85 | "prettier-eslint": "^10.1.0"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeshuai0329/react-admin/9e20ccd507b36ad4c6fa744e7abd428925c7fcc8/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
163 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
Loading...
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/src/Mock/accountManage.js:
--------------------------------------------------------------------------------
1 | var Mock = require('mockjs')
2 | let List = []
3 | const count = 666
4 | for (let i = 0; i < count; i++) {
5 | List.push(Mock.mock({
6 | 'accountsOrder|+1': /\d{5,10}/,
7 | name: Mock.mock('@cname'),
8 | loginAccount: /\d{9,11}@qq\.com/,
9 | accountPassword: Mock.mock('@word(8, 16)'),
10 | 'department|1': ['1', '2', '3', '4', '5'],
11 | 'accountStatus|1': [0, 1],
12 | phoneNumber: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
13 | createTime: Mock.Random.datetime(),
14 | updateTime: Mock.Random.datetime(),
15 | description: Mock.mock('@cparagraph(2)')
16 | }))
17 | }
18 | module.exports = [
19 | {
20 | url: '/v1/account/query',
21 | type: 'get',
22 | response: config => {
23 | const {
24 | accountsOrder,
25 | loginAccount,
26 | department,
27 | accountStatus,
28 | name,
29 | pageNo = 1,
30 | pageSize = 10
31 | } = config.query
32 | let mockList = List.filter(item => {
33 | if (accountsOrder === '' || accountsOrder === undefined || accountsOrder === null) {
34 | return true
35 | }
36 | return item.accountsOrder === accountsOrder
37 | })
38 | mockList = mockList.filter(item => {
39 | if (loginAccount === '' || loginAccount === undefined || loginAccount === null) {
40 | return true
41 | }
42 | return item.loginAccount === loginAccount
43 | })
44 | mockList = mockList.filter(item => {
45 | if (department === '' || department === undefined || department === null) {
46 | return true
47 | }
48 | return item.department === department
49 | })
50 | mockList = mockList.filter(item => {
51 | if (accountStatus === '' || accountStatus === undefined || accountStatus === null) {
52 | return true
53 | }
54 | return item.accountStatus === Number(accountStatus)
55 | })
56 | mockList = mockList.filter(item => {
57 | if (name === '' || name === undefined || name === null) {
58 | return true
59 | }
60 | return item.name === name
61 | })
62 |
63 | const pageList = mockList.filter((item, index) => index < pageSize * pageNo && index >= pageSize * (pageNo - 1))
64 |
65 | return {
66 | code: 200,
67 | total: mockList.length,
68 | data: pageList
69 | }
70 | }
71 | },
72 | {
73 | url: '/v1/account/del',
74 | type: 'delete',
75 | response: config => {
76 | const paramsData = config.body
77 | const mockList = List.filter(item => {
78 | return !paramsData.includes(item.accountsOrder)
79 | })
80 | List = mockList
81 | return {
82 | code: 200,
83 | data: []
84 | }
85 | }
86 | },
87 | {
88 | url: '/v1/account/changeAccountStatus',
89 | type: 'post',
90 | response: config => {
91 | const paramsData = config.body
92 | const mockList = List.filter(item => {
93 | if (item.accountsOrder === paramsData.accountsOrder) {
94 | item.accountStatus = paramsData.accountStatus
95 | }
96 | return item.accountsOrder === paramsData.accountsOrder
97 | })
98 | if (mockList.length === 0) {
99 | return {
100 | code: 1001,
101 | data: []
102 | }
103 | } else if (mockList.length === 1) {
104 | return {
105 | code: 200,
106 | data: mockList
107 | }
108 | } else {
109 | return {
110 | code: 1002,
111 | data: mockList
112 | }
113 | }
114 | }
115 | }
116 | ]
117 |
--------------------------------------------------------------------------------
/src/Mock/index.js:
--------------------------------------------------------------------------------
1 | var Mock = require('mockjs')
2 | const { param2Obj } = require('./utils')
3 |
4 | const user = require('./user')
5 | const accountManage = require('./accountManage')
6 | const rolesManage = require('./rolesManage')
7 |
8 | const mocks = [
9 | ...user,
10 | ...accountManage,
11 | ...rolesManage
12 | ]
13 |
14 | // for front mock
15 | // please use it cautiously, it will redefine XMLHttpRequest,
16 | // which will cause many of your third-party libraries to be invalidated(like progress event).
17 | function mockXHR() {
18 | // mock patch
19 | // https://github.com/nuysoft/Mock/issues/300
20 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
21 | Mock.XHR.prototype.send = function () {
22 | if (this.custom.xhr) {
23 | this.custom.xhr.withCredentials = this.withCredentials || false
24 |
25 | if (this.responseType) {
26 | this.custom.xhr.responseType = this.responseType
27 | }
28 | }
29 | this.proxy_send(...arguments)
30 | }
31 |
32 | function XHR2ExpressReqWrap(respond) {
33 | return function (options) {
34 | let result = null
35 | if (respond instanceof Function) {
36 | const { body, type, url } = options
37 | // https://expressjs.com/en/4x/api.html#req
38 | result = respond({
39 | method: type,
40 | body: JSON.parse(body),
41 | query: param2Obj(url)
42 | })
43 | } else {
44 | result = respond
45 | }
46 | return Mock.mock(result)
47 | }
48 | }
49 |
50 | for (const i of mocks) {
51 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
52 | }
53 | }
54 |
55 | module.exports = {
56 | mockXHR
57 | }
58 |
--------------------------------------------------------------------------------
/src/Mock/rolesManage.js:
--------------------------------------------------------------------------------
1 | var Mock = require('mockjs')
2 | let List = []
3 | const count = 30
4 | for (let i = 0; i < count; i++) {
5 | List.push(Mock.mock({
6 | 'rolesOrder|+1': /\d{5,10}/,
7 | rolesName: `超级管理员${i}`,
8 | authCharacter: `admin${i}`,
9 | 'rolesStatus|1': [0, 1],
10 | createBy: Mock.Random.datetime(),
11 | description: Mock.mock('@cparagraph(2)')
12 | }))
13 | }
14 |
15 | module.exports = [
16 | {
17 | url: '/v1/roles/query',
18 | type: 'get',
19 | response: config => {
20 | const {
21 | roleName,
22 | authCharacter,
23 | roleStatus,
24 | pageNo = 1,
25 | pageSize = 10
26 | } = config.query
27 | let mockList = List.filter(item => {
28 | if (roleName === '' || roleName === undefined || roleName === null) {
29 | return true
30 | }
31 | return item.roleName === roleName
32 | })
33 | mockList = mockList.filter(item => {
34 | if (authCharacter === '' || authCharacter === undefined || authCharacter === null) {
35 | return true
36 | }
37 | return item.authCharacter === authCharacter
38 | })
39 | mockList = mockList.filter(item => {
40 | if (roleStatus === '' || roleStatus === undefined || roleStatus === null) {
41 | return true
42 | }
43 | return item.roleStatus === roleStatus
44 | })
45 |
46 | const pageList = mockList.filter((item, index) => index < pageSize * pageNo && index >= pageSize * (pageNo - 1))
47 |
48 | return {
49 | code: 200,
50 | total: mockList.length,
51 | data: pageList
52 | }
53 | }
54 | },
55 | {
56 | url: '/v1/roles/del',
57 | type: 'delete',
58 | response: config => {
59 | const paramsData = config.body
60 | const mockList = List.filter(item => {
61 | return !paramsData.includes(item.accountsOrder)
62 | })
63 | List = mockList
64 | return {
65 | code: 200,
66 | data: []
67 | }
68 | }
69 | },
70 | {
71 | url: '/v1/roles/changerolesstatus',
72 | type: 'post',
73 | response: config => {
74 | const paramsData = config.body
75 | const mockList = List.filter(item => {
76 | if (item.accountsOrder === paramsData.accountsOrder) {
77 | item.accountStatus = paramsData.accountStatus
78 | }
79 | return item.accountsOrder === paramsData.accountsOrder
80 | })
81 | if (mockList.length === 0) {
82 | return {
83 | code: 1001,
84 | data: []
85 | }
86 | } else if (mockList.length === 1) {
87 | return {
88 | code: 200,
89 | data: mockList
90 | }
91 | } else {
92 | return {
93 | code: 1002,
94 | data: mockList
95 | }
96 | }
97 | }
98 | }
99 | ]
100 |
--------------------------------------------------------------------------------
/src/Mock/user.js:
--------------------------------------------------------------------------------
1 | var Mock = require('mockjs')
2 | const List = []
3 | const count = 888
4 | for (let i = 0; i < count; i++) {
5 | List.push(Mock.mock({
6 | key: '@increment()'
7 |
8 | }))
9 | }
10 |
11 | module.exports = [
12 | {
13 | url: 'v1/user/login',
14 | type: 'post',
15 | response: config => {
16 | if (config.body?.username === 'admin' && config.body?.password === 'admin') {
17 | return {
18 | code: 200,
19 | data: {
20 | id: '1',
21 | username: 'admin',
22 | password: 'admin',
23 | Avatar:
24 | 'https://avatars.githubusercontent.com/u/67174937?s=400&u=40aca279b633a3bdcff7dc99be16357128c9974f&v=4',
25 | token: 'ABCDEFG'
26 | }
27 | }
28 | } else {
29 | return {
30 | code: 100,
31 | message: '账号或者密码错误',
32 | data: {
33 |
34 | }
35 | }
36 | }
37 | }
38 | },
39 | {
40 | url: '/v1/user/authinfo',
41 | type: 'post',
42 | response: config => {
43 | if (config.body?.token === 'ABCDEFG') {
44 | return {
45 | code: 200,
46 | data: {
47 | authMenu: [
48 | {
49 | menuDefaultName: '首页',
50 | menuNameId: 'page.menu.home',
51 | menuId: 1,
52 | menuPId: '',
53 | menuType: 2,
54 | path: '/home',
55 | Ppath: '',
56 | icon: 'HomeOutlined',
57 | auth: true,
58 | component: 'home'
59 | },
60 | {
61 | menuDefaultName: 'DashBoard',
62 | menuNameId: 'page.menu.dashboard',
63 | menuId: 2,
64 | menuPId: '',
65 | menuType: 1,
66 | path: '/dashboard',
67 | Ppath: '',
68 | icon: 'DashboardOutlined',
69 | auth: true,
70 | component: '',
71 | children: [
72 | {
73 | menuDefaultName: '分析页',
74 | menuNameId: 'page.menu.dashboard.analysis',
75 | menuId: 3,
76 | menuPId: 2,
77 | menuType: 2,
78 | path: '/dashboard/analysis',
79 | Ppath: '/dashboard',
80 | icon: 'PieChartOutlined',
81 | auth: true,
82 | component: 'analysis'
83 | },
84 | {
85 | menuDefaultName: '监控页',
86 | menuNameId: 'page.menu.dashboard.monitor',
87 | menuId: 4,
88 | menuPId: 2,
89 | menuType: 2,
90 | path: '/dashboard/monitor',
91 | Ppath: '/dashboard',
92 | icon: 'DesktopOutlined',
93 | auth: true,
94 | component: 'monitor'
95 | }
96 | ]
97 | },
98 | {
99 | menuDefaultName: '权限管理',
100 | menuNameId: 'page.menu.auth',
101 | menuId: 5,
102 | menuPId: '',
103 | menuType: 1,
104 | path: '/auth',
105 | Ppath: '',
106 | icon: 'FolderOpenFilled',
107 | auth: true,
108 | component: '',
109 | children: [
110 | {
111 | menuDefaultName: '角色管理',
112 | menuNameId: 'page.menu.auth.rolesmanage',
113 | menuId: 6,
114 | menuPId: 5,
115 | menuType: 2,
116 | path: '/auth/rolesmanage',
117 | Ppath: '/auth',
118 | icon: 'UsergroupAddOutlined',
119 | auth: true,
120 | component: 'rolesmanage'
121 | },
122 | {
123 | menuDefaultName: '账号管理',
124 | menuNameId: 'page.menu.auth.accountsmanage',
125 | menuId: 7,
126 | menuPId: 5,
127 | menuType: 2,
128 | path: '/auth/accountsmanage',
129 | Ppath: '/auth',
130 | icon: 'AccountBookOutlined',
131 | auth: true,
132 | component: 'accountsmanage'
133 | },
134 | {
135 | menuDefaultName: '菜单管理',
136 | menuNameId: 'page.menu.auth.menusmanage',
137 | menuId: 8,
138 | menuPId: 5,
139 | menuType: 2,
140 | path: '/auth/menusmanage',
141 | Ppath: '/auth',
142 | icon: 'MenuOutlined',
143 | auth: true,
144 | component: 'menusmanage'
145 | }
146 | ]
147 | },
148 | {
149 | menuDefaultName: '系统管理',
150 | menuNameId: 'page.menu.system',
151 | menuId: 9,
152 | menuPId: '',
153 | menuType: 1,
154 | path: '/system',
155 | Ppath: '',
156 | icon: 'SettingFilled',
157 | auth: true,
158 | component: '',
159 | children: [
160 | {
161 | menuDefaultName: '系统配置',
162 | menuNameId: 'page.menu.system.systemconfig',
163 | menuId: 10,
164 | menuPId: 9,
165 | menuType: 2,
166 | path: '/system/systemconfig',
167 | Ppath: '/system',
168 | icon: 'LayoutFilled',
169 | auth: true,
170 | component: 'systemconfig'
171 | }
172 | ]
173 | }
174 | ],
175 | authButton: [
176 | { id: '11', authName: 'HOME_ADD' },
177 | { id: '12', authName: 'HOME_EDIT' },
178 | { id: '13', authName: 'HOME_DEL' },
179 | { id: '21', authName: 'ROLES_ADD' },
180 | { id: '22', authName: 'ROLES_EDIT' },
181 | { id: '23', authName: 'ROLES_DEL' },
182 | { id: '24', authName: 'ROLES_EXPORT' },
183 | { id: '31', authName: 'ACCOUNT_ADD' },
184 | { id: '32', authName: 'ACCOUNT_EDIT' },
185 | { id: '33', authName: 'ACCOUNT_DEL' },
186 | { id: '34', authName: 'ACCOUNT_EXPORT' }
187 | ]
188 | }
189 | }
190 | } else {
191 | return {
192 | code: 101,
193 | data: {}
194 | }
195 | }
196 | }
197 | }
198 | ]
199 |
--------------------------------------------------------------------------------
/src/Mock/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} url
3 | * @returns {Object}
4 | */
5 | function param2Obj(url) {
6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
7 | if (!search) {
8 | return {}
9 | }
10 | const obj = {}
11 | const searchArr = search.split('&')
12 | searchArr.forEach(v => {
13 | const index = v.indexOf('=')
14 | if (index !== -1) {
15 | const name = v.substring(0, index)
16 | const val = v.substring(index + 1, v.length)
17 | obj[name] = val
18 | }
19 | })
20 | return obj
21 | }
22 |
23 | /**
24 | * This is just a simple version of deep copy
25 | * Has a lot of edge cases bug
26 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep
27 | * @param {Object} source
28 | * @returns {Object}
29 | */
30 | function deepClone(source) {
31 | if (!source && typeof source !== 'object') {
32 | throw new Error('error arguments', 'deepClone')
33 | }
34 | const targetObj = source.constructor === Array ? [] : {}
35 | Object.keys(source).forEach(keys => {
36 | if (source[keys] && typeof source[keys] === 'object') {
37 | targetObj[keys] = deepClone(source[keys])
38 | } else {
39 | targetObj[keys] = source[keys]
40 | }
41 | })
42 | return targetObj
43 | }
44 |
45 | module.exports = {
46 | param2Obj,
47 | deepClone
48 | }
49 |
--------------------------------------------------------------------------------
/src/api/AccountsManage/AccountsManage.ts:
--------------------------------------------------------------------------------
1 | import request from '../request'
2 |
3 | const baseUrl = process.env.REACT_APP_BASEURL
4 |
5 | // 账号管理删除接口
6 | export const accountDeleteApi = (data:any) => {
7 | return request({
8 | url: `${baseUrl}/v1/account/del`,
9 | method: 'delete',
10 | data
11 | })
12 | }
13 |
14 | // 账号管理查询接口
15 | export const accountQueryApi = (params:any) => {
16 | return request({
17 | url: `${baseUrl}/v1/account/query`,
18 | method: 'get',
19 | params
20 | })
21 | }
22 |
23 | // 账号管理改变账号状态接口
24 | export const changeAccountStatusApi = (data:any) => {
25 | return request({
26 | url: `${baseUrl}/v1/account/changeAccountStatus`,
27 | method: 'post',
28 | data
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/api/EntryScreenApi/EntryScreenApi.ts:
--------------------------------------------------------------------------------
1 | import request from '../request'
2 |
3 | const baseUrl = process.env.REACT_APP_BASEURL
4 |
5 | // 登录接口
6 | export const loginApi = (data:{username: string, password: string}) => {
7 | return request({
8 | url: `${baseUrl}/v1/user/login`,
9 | method: 'post',
10 | data
11 | })
12 | }
13 |
14 | // 获取用户权限信息
15 | export const getAuthInfoApi = (data:{token: string}) => {
16 | return request({
17 | url: `${baseUrl}/v1/user/authinfo`,
18 | method: 'post',
19 | data
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/src/api/request.ts:
--------------------------------------------------------------------------------
1 |
2 | import axios, { AxiosInstance } from 'axios'
3 | import { message } from 'antd'
4 | import { init } from 'locales'
5 |
6 | const instance: AxiosInstance = axios.create({
7 | timeout: 30000, // 超时时间
8 | withCredentials: true
9 | })
10 |
11 | // 请求拦截器:在请求被发送出去之前,做一些验证工作
12 | instance.interceptors.request.use((config) => {
13 | return config
14 | }, (error) => {
15 | return Promise.reject(error)
16 | })
17 |
18 | // 响应拦截器:在响应到达之前,先进行数据过滤,错误处理
19 | // @ts-ignore
20 | instance.interceptors.response.use((response) => {
21 | // 统一拦截错误码
22 | if (response.status === 200 && response.data) {
23 | if (response.data.code !== 200) {
24 | message.error(init(response.data.code))
25 | return response
26 | }
27 | return response
28 | }
29 | return response
30 | }, (error) => {
31 | return Promise.reject(error)
32 | })
33 |
34 | export default instance
35 |
--------------------------------------------------------------------------------
/src/api/rolesManage/rolesManage.ts:
--------------------------------------------------------------------------------
1 | import request from '../request'
2 |
3 | const baseUrl = process.env.REACT_APP_BASEURL
4 |
5 | // 角色管理删除接口
6 | export const rolesDeleteApi = (data:any) => {
7 | return request({
8 | url: `${baseUrl}/v1/roles/del`,
9 | method: 'delete',
10 | data
11 | })
12 | }
13 |
14 | // 角色管理查询接口
15 | export const rolesQueryApi = (params:any) => {
16 | return request({
17 | url: `${baseUrl}/v1/roles/query`,
18 | method: 'get',
19 | params
20 | })
21 | }
22 |
23 | // 角色管理改变角色状态接口
24 | export const changeRolesStatusApi = (data:any) => {
25 | return request({
26 | url: `${baseUrl}/v1/roles/changerolesStatus`,
27 | method: 'post',
28 | data
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/assets/images/entryScreenBG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/images/index.ts:
--------------------------------------------------------------------------------
1 | import entryScreenBG from './entryScreenBG.svg'
2 | import logo from './logo.png'
3 |
4 | const baseUrl = ''
5 | export const EntryScreenBG = baseUrl + entryScreenBG
6 | export const Logo = baseUrl + logo
7 |
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeshuai0329/react-admin/9e20ccd507b36ad4c6fa744e7abd428925c7fcc8/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/components/AdvancedModal/AdvancedModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Modal, Form, ModalProps, FormProps, FormItemProps } from 'antd'
3 |
4 | interface IFormItemProps extends FormItemProps {
5 | render?: React.ReactElement
6 | }
7 |
8 | export interface IFormProps extends FormProps {
9 | modalFormList?: IFormItemProps[]
10 | }
11 |
12 | export interface IModalProps extends ModalProps {
13 | modalDetail?: { [key: string]: any }
14 | confirmed?: (value: any) => void
15 | }
16 |
17 | export interface IAdvancedModalProps {
18 | modalOptions: IModalProps
19 | formOptions: IFormProps
20 | }
21 |
22 | const AdvancedModal = (props: IAdvancedModalProps) => {
23 | const { modalOptions, formOptions } = props
24 | const { confirmed, ...surplusModalOptions } = modalOptions
25 | const { modalFormList, ...surplusFormOptions } = formOptions
26 |
27 | const [form] = Form.useForm()
28 |
29 | const onOk = () => {
30 | form
31 | .validateFields()
32 | .then(values => {
33 | confirmed && confirmed(values)
34 | })
35 | .catch(info => {
36 | console.log('info', info)
37 | })
38 | }
39 |
40 | return (
41 |
42 |
47 | {item.render}
48 |
49 | )
50 | })}
51 |
52 |
53 | )
54 | }
55 |
56 | export default AdvancedModal
57 |
--------------------------------------------------------------------------------
/src/components/AdvancedSearch/AdvancedSearch.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, memo, useImperativeHandle, useState } from 'react'
2 | import { Button, Col, Form, Input, Row, Space } from 'antd'
3 | import { SearchOutlined, ReloadOutlined, UpOutlined } from '@ant-design/icons'
4 | import { init } from 'locales'
5 |
6 | export interface SearchFormItem {
7 | name: string,
8 | label: string,
9 | placeholder?: string,
10 | rules?: object[],
11 | render?: any,
12 | initialValue?: any,
13 | shouldUpdate?: boolean
14 | }
15 |
16 | export interface AdvancedSearchProps {
17 | formList: SearchFormItem[],
18 | onSearch: (values: any) => void,
19 | }
20 |
21 | const AdvancedSearch = forwardRef(function fnRef(props: AdvancedSearchProps, ref) {
22 | const { formList, onSearch } = props
23 |
24 | const [isOpen, setIsOpen] = useState(false)
25 | const [count, setCount] = useState(3)
26 | const [form] = Form.useForm()
27 |
28 | const reset = () => {
29 | form.resetFields()
30 | onSearch({})
31 | }
32 |
33 | const onFinish = () => {
34 | form.validateFields().then(res => {
35 | onSearch(res)
36 | })
37 | }
38 |
39 | useImperativeHandle(ref, () => {
40 | return {
41 | reset
42 | }
43 | })
44 |
45 | const toggle = () => {
46 | if (!isOpen) {
47 | setCount(formList.length)
48 | } else {
49 | setCount(3)
50 | }
51 | setIsOpen(!isOpen)
52 | }
53 |
54 | return (
55 |
121 | )
122 | }
123 | )
124 |
125 | export default memo(AdvancedSearch)
126 |
--------------------------------------------------------------------------------
/src/components/AppProviders/AppProviders.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { ConfigProvider } from 'antd'
3 | import { BrowserRouter } from 'react-router-dom'
4 | import { Provider } from 'react-redux'
5 | import store from 'store/store'
6 | import { useAntdLocale, useSetDocumentTitle } from 'publicHooks'
7 | import { init } from 'locales'
8 | import config from 'config/config'
9 | import InitProviders from './InitProviders'
10 |
11 | // 数据mock
12 | if (process.env.REACT_APP_ENV === 'mock') {
13 | const { mockXHR } = require('../../Mock')
14 | mockXHR()
15 | }
16 |
17 | const AppProviders: React.FC = (props): ReactElement => {
18 | // 设置title
19 | useSetDocumentTitle(init('page.common.docTitle'))
20 | // 设置antd 语言
21 | const antdLocale = useAntdLocale(config.locale)
22 |
23 | return (
24 |
25 |
26 |
27 | {props.children}
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default AppProviders
35 |
--------------------------------------------------------------------------------
/src/components/AppProviders/InitProviders.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, Fragment } from 'react'
2 | import LoadingComponent from 'components/LoadingComponent/LoadingComponent'
3 |
4 | const InitProviders = (props: any) => {
5 | const [loading, setLoading] = useState(true)
6 | // 避免页面闪烁
7 | useEffect(() => {
8 | const timer = setTimeout(() => {
9 | setLoading(false)
10 | }, 300)
11 | return () => {
12 | clearTimeout(timer)
13 | }
14 | }, [])
15 | return (
16 |
17 | {loading
18 | ? (
19 |
28 |
29 |
30 | )
31 | : null}
32 |
39 | {props.children}
40 |
41 |
42 | )
43 | }
44 |
45 | export default InitProviders
46 |
--------------------------------------------------------------------------------
/src/components/AuthRoute/AuthRoute.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, ReactElement } from 'react'
2 | import { Route } from 'react-router-dom'
3 | import EntryScreen from 'pages/EntryScreen/EntryScreen'
4 | import cookie from 'js-cookie'
5 | import { useSetDocumentTitle } from 'publicHooks'
6 | import { init } from 'locales'
7 |
8 | type RouteProps = React.ComponentProps
9 |
10 | const AuthRoute: React.FC = (props): ReactElement => {
11 | // @ts-ignore
12 | useSetDocumentTitle(init(props!.menuNameId))
13 |
14 | return (
15 |
16 | {
17 | cookie.get('R-Boot-token')
18 | ?
19 | :
20 | }
21 |
22 | )
23 | }
24 |
25 | export default AuthRoute
26 |
--------------------------------------------------------------------------------
/src/components/BreadCrumbPro/BreadCrumbPro.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Breadcrumb, Space } from 'antd'
3 | import * as Icon from '@ant-design/icons'
4 | import { BreadcrumbItem } from 'typings/breadcrumbItem'
5 | import { NavLink, useLocation } from 'react-router-dom'
6 | import { init } from 'locales'
7 |
8 | type BreadcrumbProps = React.ComponentProps
9 |
10 | const BreadCrumbPro: React.FC = (props): ReactElement => {
11 | const localtion = useLocation()
12 | const [breadCrumbList, setBreadCrumbList] = React.useState([])
13 |
14 | React.useEffect(() => {
15 | const path = localtion.pathname
16 | const authMenu = JSON.parse(localStorage.getItem('authMenu') || '[]')
17 | const list = findAllFather(authMenu, path)?.reverse()
18 | setBreadCrumbList(list)
19 | }, [localtion])
20 |
21 | /**
22 | * @description: 根据传入的路由 过滤面包屑路径
23 | */
24 | const findAllFather = (authMenu: any[], path:string) => {
25 | for (const i in authMenu) {
26 | if (authMenu[i].path === path) {
27 | // 查询到就返回该数组对象
28 | return [authMenu[i]]
29 | }
30 | if (authMenu[i].children) {
31 | const node:any = findAllFather(authMenu[i].children, path)
32 | if (node !== undefined) {
33 | // 查询到把父节点连起来
34 | return node.concat(authMenu[i])
35 | }
36 | }
37 | }
38 | }
39 | return (
40 |
41 | {
42 | breadCrumbList && breadCrumbList.map((item: any, index: number) => {
43 | // @ts-ignore
44 | const iconfont = item.icon ? React.createElement(Icon[item.icon]) : ''
45 | return (
46 |
47 | {
48 | item.path && item.menuType !== 1
49 | ? (
50 |
51 |
52 | {iconfont}
53 | {init(item.menuNameId)}
54 |
55 |
56 | )
57 | : (
58 |
59 | {iconfont}
60 | {init(item.menuNameId)}
61 |
62 | )
63 | }
64 |
65 | )
66 | })
67 | }
68 |
69 | )
70 | }
71 |
72 | export default BreadCrumbPro
73 |
--------------------------------------------------------------------------------
/src/components/Copyright/Copyright.module.less:
--------------------------------------------------------------------------------
1 | .Copyright {
2 | height: 48px;
3 | cursor: pointer;
4 | & > div {
5 | width: 100%;
6 | height: 24px;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | .footer {
12 | color: rgba(0,0,0,.45);
13 | }
14 | .footer :hover {
15 | color: rgba(0,0,0,.65);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/components/Copyright/Copyright.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import style from './Copyright.module.less'
3 | import classNames from 'classnames/bind'
4 | import { GithubOutlined } from '@ant-design/icons'
5 | import { Space } from 'antd'
6 |
7 | const cx = classNames.bind(style)
8 |
9 | const Copyright: React.FC = (): ReactElement => {
10 | return (
11 |
12 |
13 |
14 | 开发文档
15 |
16 | R-Boot
17 |
18 |
19 |
20 | Copyright © 2021 - R-Boot 版权所有
21 |
22 |
23 | )
24 | }
25 |
26 | export default Copyright
27 |
--------------------------------------------------------------------------------
/src/components/CustomLogo/CustomLogo.module.less:
--------------------------------------------------------------------------------
1 | .menus{
2 | &-logo{
3 | height: 48px;
4 | box-sizing: border-box;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | &__imgtrue {
9 | width: 32px;
10 | height: 32px;
11 | }
12 | &__imgfalse {
13 | width: 32px;
14 | height: 32px;
15 | margin-right: 20px;
16 | }
17 | &__spandark {
18 | font-size: 24px;
19 | color: #FFF;
20 | font-weight: 600;
21 | }
22 | &__spanlight {
23 | font-size: 24px;
24 | color: #000;
25 | font-weight: 600;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/components/CustomLogo/CustomLogo.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Logo } from 'assets/images'
3 | import { useSelector } from 'react-redux'
4 | import Texty from 'rc-texty'
5 | import style from './CustomLogo.module.less'
6 | import classNames from 'classnames/bind'
7 | import { RootState } from 'typings/store'
8 |
9 | const cx = classNames.bind(style)
10 |
11 | const CustomLogo: React.FC = (): ReactElement => {
12 | const reduxConfig = useSelector((state: RootState) => state.config)
13 |
14 | return (
15 |
16 |

23 |
24 | {reduxConfig.siderMenuIsCollapsed
25 | ? null
26 | : (
27 |
28 | R-Boot
29 |
30 | )}
31 |
32 |
33 | )
34 | }
35 |
36 | export default CustomLogo
37 |
--------------------------------------------------------------------------------
/src/components/CustomMenu/CustomMenu.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Menu } from 'antd'
3 | import * as Icon from '@ant-design/icons'
4 | import { NavLink } from 'react-router-dom'
5 | import { useLocation } from 'react-router'
6 | import CustomLogo from 'components/CustomLogo/CustomLogo'
7 | import { init } from 'locales'
8 |
9 | const { SubMenu } = Menu
10 | type PropsMenu = React.ComponentProps
11 |
12 | interface IProps extends PropsMenu {
13 | menuList: any[]
14 | siderMenuIshHasLogo?: boolean
15 | MenuIshHasLogo: boolean
16 | }
17 |
18 | /**
19 | * @description: 基于antd封装递归菜单
20 | * @param {*} props
21 | * @return {*}
22 | */
23 | const CustomMenu: React.FC = props => {
24 | const { menuList, siderMenuIshHasLogo, MenuIshHasLogo, mode, ...remainProps } = props
25 | const localtion = useLocation()
26 | const [selectedKeys, setSelectedKeys] = React.useState([])
27 | const [openKeys, setOpenKeys] = React.useState([])
28 |
29 | React.useLayoutEffect(
30 | () => {
31 | setSelectedKeys([localtion.pathname])
32 | setOpenKeys(findAllParent(menuList, localtion.pathname))
33 | },
34 | [localtion]
35 | )
36 |
37 | /**
38 | * @description: 查找localtion.pathname的所有父级
39 | * @param {*}
40 | * @return {*}
41 | */
42 | const findAllParent = (menuList: any, path: string, allParentPaths: any = []) => {
43 | if (!menuList || !menuList.length) {
44 | return null
45 | }
46 | for (const node of menuList) {
47 | if (node.path === path) {
48 | return allParentPaths
49 | }
50 | const find: any = findAllParent(node.children, path, [...allParentPaths, node.path])
51 | if (find) return find
52 | }
53 | return null
54 | }
55 |
56 | /**
57 | * 菜单栏递归函数
58 | */
59 | const createMenu = (menuList: any[]) => {
60 | return menuList.map((menu: any) => {
61 | // @ts-ignorets
62 | const iconfont = menu.icon ? React.createElement(Icon[menu.icon]) : ''
63 | if (menu.children && menu.children.length) {
64 | return (
65 |
66 | {createMenu(menu.children)}
67 |
68 | )
69 | } else {
70 | return (
71 |
72 | {init(menu.menuNameId)}
73 |
74 | )
75 | }
76 | })
77 | }
78 | /**
79 | * @description: 设置选中高亮的key
80 | */
81 | const onSelect = ({ selectedKeys }: any) => {
82 | setSelectedKeys(selectedKeys)
83 | }
84 |
85 | /**
86 | * @description: 设置展开的key
87 | */
88 | const onOpenChange = (openKeys: any) => {
89 | setOpenKeys(openKeys)
90 | }
91 |
92 | return (
93 |
104 | )
105 | }
106 |
107 | export default CustomMenu
108 |
--------------------------------------------------------------------------------
/src/components/LoadingComponent/LoadingComponent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Spin } from 'antd'
3 |
4 | const LoadingComponent = () => {
5 | return (
6 |
18 | )
19 | }
20 |
21 | export default LoadingComponent
22 |
--------------------------------------------------------------------------------
/src/components/ToggleLang/ToggleLang.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { GlobalOutlined } from '@ant-design/icons'
3 | import { Dropdown, Menu, Space } from 'antd'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import { LangMap, ILangMap } from './params'
6 | import { SET_LANG } from 'store/actionTypes/configActionType'
7 | import { RootState } from 'typings/store'
8 | import { init } from 'locales'
9 |
10 | /**
11 | * @description: ToggleLang 组件
12 | * @description: redux 语言国际化中使用ToggleLang组件,切换语言
13 | */
14 | export const ToggleLang = (props: any) => {
15 | const reduxConfig = useSelector((state: RootState) => state.config)
16 | // 语言切换组件选中的key
17 | const [selectedKeysArray, setSelectedKeysArray] = useState([reduxConfig.locale])
18 | const dispatch = useDispatch()
19 |
20 | const reduxToggleLangMethod = (type: string) => {
21 | const currentLocale = localStorage.getItem('currentLocale')
22 | if (currentLocale === type) {
23 | return
24 | }
25 | dispatch({
26 | type: SET_LANG,
27 | payload: type
28 | })
29 | window.location.reload()
30 | }
31 |
32 | useEffect(() => {
33 | setSelectedKeysArray([reduxConfig.locale])
34 | }, [reduxConfig])
35 |
36 | const menu = (
37 |
59 | )
60 |
61 | return (
62 |
66 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/ToggleLang/params.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react'
2 |
3 | export interface IObj {
4 | [key:string]: string
5 | }
6 |
7 | export interface IProps {
8 | children?:ReactNode;
9 | id: string;
10 | defaultText: string
11 | reduxLang?: IObj
12 | }
13 |
14 | export interface ILangMap {
15 | icon: string,
16 | nameId: string,
17 | defaultName: string,
18 | value: string
19 | }
20 | export const LangMap: ILangMap[] = [
21 | { icon: '🇨🇳', nameId: 'page.header.simplifiedChinese', defaultName: '简体中文', value: 'zh_CN' },
22 | { icon: '🇬🇧', nameId: 'page.header.english', defaultName: 'English', value: 'en_US' }
23 | ]
24 |
--------------------------------------------------------------------------------
/src/components/aboutAdvanceTable/AdvancedTable/AdvancedTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Space, Table, TableProps as AntdTableProps } from 'antd'
3 | import ColumnsConfig from './components/ColumnsConfig/ColumnsConfig'
4 | import AuthButtonGroup, { AuthAction } from 'components/aboutAuthButton/AuthButtonGroup'
5 | import { init } from 'locales'
6 |
7 | export interface IPickColumn {
8 | label: string
9 | value: string
10 | }
11 |
12 | export interface IAdvancedTableProps extends AntdTableProps {
13 | alert?: React.ReactElement
14 | paging: { pageNo: number; pageSize: number | undefined }
15 | pageTotal?: number
16 | authActions?: AuthAction[] // 权限按钮
17 | canConfig?: boolean // 改变列选项的配置icon是否显示
18 | changePage?: (page: number, pageSize?: number | undefined) => void
19 | }
20 |
21 | const AdvancedTable = (props: IAdvancedTableProps): ReactElement => {
22 | const { paging, authActions, canConfig, columns, pageTotal, changePage, ...mainProps } = props
23 |
24 | const [pickColumns, setPickColumns] = React.useState(columns)
25 |
26 | const changePickColumns = (pickList: string[]) => {
27 | const result =
28 | columns &&
29 | columns.filter(item => {
30 | return pickList.includes(item.title as string)
31 | })
32 | setPickColumns(result)
33 | }
34 |
35 | return (
36 |
37 |
38 |
39 | {canConfig ? : null}
40 |
41 | {}
42 | `${init('page.common.total')} ${total} ${init('page.common.items')}`,
52 | position: ['bottomCenter'],
53 | onChange: changePage
54 | }}
55 | {...mainProps}
56 | />
57 |
58 | )
59 | }
60 |
61 | export default AdvancedTable
62 |
--------------------------------------------------------------------------------
/src/components/aboutAdvanceTable/AdvancedTable/components/ColumnsConfig/ColumnsConfig.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { Popover, Checkbox, Divider, Row, Col, Tooltip } from 'antd'
3 | import { SettingFilled } from '@ant-design/icons'
4 | import { CheckboxValueType } from 'antd/lib/checkbox/Group'
5 | import { init } from 'locales'
6 |
7 | const CheckboxGroup = Checkbox.Group
8 |
9 | interface IProps{
10 | columns?: any
11 | changePickColumns: (pickList: string[]) => void
12 | }
13 |
14 | const ColumnsConfig = (props: IProps) => {
15 | const { columns, changePickColumns } = props
16 |
17 | const [indeterminate, setIndeterminate] = React.useState(false)
18 | const [checkAll, setCheckAll] = React.useState(true)
19 |
20 | const options: string[] = useMemo(() => {
21 | return columns && columns.map((item: any) => {
22 | return item.title
23 | })
24 | }, [columns])
25 |
26 | const [checkedList, setCheckedList] = React.useState(options)
27 |
28 | React.useEffect(() => {
29 | changePickColumns((checkedList as string[]))
30 | }, [checkedList])
31 |
32 | const onChange = (list: CheckboxValueType[]) => {
33 | setCheckedList(list)
34 | setIndeterminate(!!list.length && list.length < options.length)
35 | setCheckAll(list.length === options.length)
36 | }
37 |
38 | const onCheckAllChange = (e: any) => {
39 | setCheckedList(e.target.checked ? options : [])
40 | setIndeterminate(false)
41 | setCheckAll(e.target.checked)
42 | }
43 |
44 | const content = () => {
45 | return (
46 |
47 |
48 | {init('page.common.selectAll')} / {init('page.common.unselectAll')}
49 |
50 |
51 |
52 |
53 | {
54 | options && options.map((item:string) => {
55 | return (
56 |
57 | {item}
58 |
59 | )
60 | })
61 | }
62 |
63 |
64 |
65 | )
66 | }
67 |
68 | return (
69 |
75 |
76 |
82 |
83 |
84 | )
85 | }
86 |
87 | export default ColumnsConfig
88 |
--------------------------------------------------------------------------------
/src/components/aboutAdvanceTable/AdvancedTablePro/AdvancedTablePro.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import AdvancedSearch, { AdvancedSearchProps } from 'components/AdvancedSearch/AdvancedSearch'
3 | import AdvancedTable, { IAdvancedTableProps } from 'components/aboutAdvanceTable/AdvancedTable/AdvancedTable'
4 | import AdvancedModal, { IAdvancedModalProps } from 'components/AdvancedModal/AdvancedModal'
5 |
6 | interface IAdvancedTableProProps {
7 | serchFormOptions: AdvancedSearchProps,
8 | advancedTableOptions: IAdvancedTableProps
9 | advancedModalOptions: IAdvancedModalProps
10 | }
11 |
12 | const AdvancedTablePro = (props: IAdvancedTableProProps) => {
13 | const { serchFormOptions, advancedTableOptions, advancedModalOptions } = props
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default AdvancedTablePro
24 |
--------------------------------------------------------------------------------
/src/components/aboutAuthButton/AuthButton/AuthButton.module.less:
--------------------------------------------------------------------------------
1 | .custom {
2 | // 主要按钮
3 | &-default {
4 | color: #1890ff;
5 | background: #e8f4ff;
6 | border-color: #a3d3ff;
7 | }
8 | &-default:hover {
9 | color: #fff;
10 | background-color: #1890ff;
11 | border-color: #1890ff;
12 | }
13 | &-default:focus {
14 | color: #fff;
15 | background-color: #1890ff;
16 | border-color: #1890ff;
17 | }
18 |
19 | // 成功按钮
20 | &-success {
21 | color: #13ce66;
22 | background: #e7faf0;
23 | border-color: #a1ebc2;
24 | }
25 | &-success:hover {
26 | color: #fff;
27 | background-color: #13ce66;
28 | border-color: #13ce66;
29 | }
30 | &-success:focus {
31 | color: #fff;
32 | background-color: #13ce66;
33 | border-color: #13ce66;
34 | }
35 |
36 | // 危险按钮
37 | &-danger {
38 | color: #ff4949;
39 | background: #ffeded;
40 | border-color: #ffb6b6;
41 | }
42 | &-danger:hover {
43 | color: #fff;
44 | background-color: #ff4949;
45 | border-color: #ff4949;
46 | }
47 | &-danger:focus {
48 | color: #fff;
49 | background-color: #ff4949;
50 | border-color: #ff4949;
51 | }
52 |
53 | // 警告按钮
54 | &-warning {
55 | color: #ffba00;
56 | background: #fff8e6;
57 | border-color: #ffe399;
58 | }
59 | &-warning:hover {
60 | color: #fff;
61 | background-color: #ffba00;
62 | border-color: #ffba00;
63 | }
64 | &-warning:focus {
65 | color: #fff;
66 | background-color: #ffba00;
67 | border-color: #ffba00;
68 | }
69 |
70 | // 信息按钮
71 | &-info {
72 | border-color: #ccc;
73 | color: #909399;
74 | }
75 | &-info:hover {
76 | border-color: #a6a9ad;
77 | background: #a6a9ad;
78 | color: #fff;
79 | }
80 | &-info:focus {
81 | border-color: #a6a9ad;
82 | background: #a6a9ad;
83 | color: #fff;
84 | }
85 |
86 |
87 | }
--------------------------------------------------------------------------------
/src/components/aboutAuthButton/AuthButton/AuthButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import style from './AuthButton.module.less'
3 | import classNames from 'classnames/bind'
4 | import { Button, ButtonProps } from 'antd'
5 |
6 | const cx = classNames.bind(style)
7 |
8 | export type AuthButtonType = 'HOME_ADD'
9 | | 'HOME_EDIT'
10 | | 'HOME_DEL'
11 | | 'ROLES_ADD'
12 | | 'ROLES_EDIT'
13 | | 'ROLES_DEL'
14 | | 'ROLES_EXPORT'
15 | | 'ACCOUNT_ADD'
16 | | 'ACCOUNT_EDIT'
17 | | 'ACCOUNT_DEL'
18 | | 'ACCOUNT_EXPORT'
19 |
20 | export type CustomType = 'default' | 'success' | 'danger' | 'warning' | 'info'
21 |
22 | export interface IButtonProps extends ButtonProps {
23 | auth: AuthButtonType,
24 | customtype?: CustomType
25 | }
26 |
27 | const AuthButton = (props: IButtonProps): ReactElement | null => {
28 | const { auth, customtype, ...remainProps } = props
29 | const userAuthButtonType: {id:string, authName: string}[] = JSON.parse(localStorage.getItem('authButton') || '[]')
30 |
31 | const bool = userAuthButtonType.filter(item => item.authName === auth)
32 | return (
33 | bool.length > 0
34 | ?
35 | : null
36 | )
37 | }
38 |
39 | export default AuthButton
40 |
--------------------------------------------------------------------------------
/src/components/aboutAuthButton/AuthButtonGroup/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Space } from 'antd'
3 | import AuthButton, { IButtonProps } from 'components/aboutAuthButton/AuthButton/AuthButton'
4 |
5 | export interface AuthAction extends IButtonProps {
6 | name: string,
7 | onClick: () => void
8 | }
9 |
10 | export interface IAuthButtonGroupProps {
11 | authActions?: AuthAction[]
12 | }
13 | const AuthButtonGroup = (props: IAuthButtonGroupProps) => {
14 | return (
15 |
16 | {
17 | props.authActions && props.authActions.map((item: any, index: number) => {
18 | return (
19 |
23 | {item.name}
24 |
25 | )
26 | })
27 | }
28 |
29 | )
30 | }
31 |
32 | export default AuthButtonGroup
33 |
--------------------------------------------------------------------------------
/src/config/config.ts:
--------------------------------------------------------------------------------
1 | import { TConfig } from 'typings/config'
2 |
3 | const config: TConfig = {
4 | // 默认国际化语言配置
5 | locale: 'zh_CN',
6 | // |||||||||||||||||||||||||||
7 | // |||||| 侧边栏菜单相关||||||||
8 | // |||||||||||||||||||||||||||
9 | // 是否有侧边菜单
10 | siderMenuIsHas: true,
11 | // 改变宽度的时候,是否自动显示/隐藏侧边菜单
12 | autoHoldSiderIsShow: false,
13 | // 侧边菜单的颜色
14 | siderMenuTheme: 'dark',
15 | // 侧边菜单的是否收起
16 | siderMenuIsCollapsed: false,
17 | // 侧边菜单是否有LOGO
18 | siderMenuIshHasLogo: true,
19 | // |||||||||||||||||||||||||||
20 | // ||||||| 顶部菜单相关 ||||||||
21 | // |||||||||||||||||||||||||||
22 | // 是否有顶部菜单
23 | topMenuIsHas: false,
24 | // 顶部菜单颜色
25 | topMenuTheme: 'light',
26 | // 顶部菜单是否有logo
27 | topMenuIsHasLogo: false,
28 | // |||||||||||||||||||||||||||
29 | // ||||||| 顶部面包屑相关 ||||||
30 | // |||||||||||||||||||||||||||
31 | // 是否有顶部面包屑
32 | breadCrumbIsHas: true,
33 | // 是否有备案号
34 | isHasCopyright: true
35 | }
36 |
37 | export default config
38 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import AppProviders from 'components/AppProviders/AppProviders'
4 | import 'antd/dist/antd.less'
5 | import './styles/reset.less'
6 | import './styles/globalAntd.module.less'
7 | import App from './pages/App'
8 | import EntryScreen from 'pages/EntryScreen/EntryScreen'
9 | import { Redirect, Route, Switch } from 'react-router-dom'
10 | import Cookies from 'js-cookie'
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 | Cookies.get('R-Boot-token') ? : }/>
17 |
18 | ,
19 | document.getElementById('root')
20 | )
21 |
--------------------------------------------------------------------------------
/src/layouts/LayoutContent/LayoutContent.module.less:
--------------------------------------------------------------------------------
1 | .LayoutContent {
2 | padding: 8px;
3 | display: flex;
4 | flex-direction: column;
5 | &-route {
6 | flex: 1;
7 | padding: 16px;
8 | }
9 |
10 | &-Copyright {
11 | height: 80px;
12 | flex-shrink: 0;
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | background: #fff;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/layouts/LayoutContent/LayoutContent.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, useEffect, useState } from 'react'
2 | import style from './LayoutContent.module.less'
3 | import classNames from 'classnames/bind'
4 | import { Switch, Redirect } from 'react-router-dom'
5 | import AuthRoute from 'components/AuthRoute/AuthRoute'
6 | import { userDynamicRouters } from 'routers/userDynamicRouters'
7 | import Copyright from 'components/Copyright/Copyright'
8 | import NotFind from 'pages/NotFind/NotFind'
9 | import LoadingComponent from 'components/LoadingComponent/LoadingComponent'
10 | import { IRouter } from 'typings/router'
11 | import { useSelector } from 'react-redux'
12 | import { RootState } from 'typings/store'
13 |
14 | const cx = classNames.bind(style)
15 |
16 | const LayoutContent: React.FC = (): ReactElement => {
17 | const [routerList, setRouterList] = useState([])
18 | const reduxConfig = useSelector((state: RootState) => state.config)
19 |
20 | useEffect(() => {
21 | const routers = JSON.parse(localStorage.getItem('authMenu') || '[]')
22 | setRouterList(userDynamicRouters(routers))
23 | }, [])
24 |
25 | return (
26 |
27 |
}>
28 |
29 | {routerList &&
30 | routerList.map((router: any) => {
31 | return
32 | })}
33 |
34 |
35 |
36 |
37 | {reduxConfig.isHasCopyright
38 | ? (
39 |
40 |
41 |
42 | )
43 | : null}
44 |
45 | )
46 | }
47 |
48 | export default LayoutContent
49 |
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/HeaderRight/HeaderRight.module.less:
--------------------------------------------------------------------------------
1 | .headerRight {
2 | display: flex;
3 | justify-content: flex-start;
4 | align-items: center;
5 | cursor: pointer;
6 | }
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/HeaderRight/HeaderRight.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import Personal from '../components/Personal/Personal'
3 | import style from 'layouts/LayoutHeader/HeaderRight/HeaderRight.module.less'
4 | import classNames from 'classnames/bind'
5 | import { ToggleLang } from 'components/ToggleLang/ToggleLang'
6 | import { Space } from 'antd'
7 |
8 | const cx = classNames.bind(style)
9 |
10 | const HeaderRight: React.FC = (): ReactElement => {
11 | return (
12 |
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default HeaderRight
20 |
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/LayoutHeader.module.less:
--------------------------------------------------------------------------------
1 | .layoutHeader {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | box-sizing: border-box;
8 | padding: 0 16px;
9 | &-left {
10 | display: flex;
11 | justify-content: flex-start;
12 | align-items: center;
13 | &__trigger {
14 | font-size: 20px;
15 | cursor: pointer;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/LayoutHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons'
4 | import style from 'layouts/LayoutHeader/LayoutHeader.module.less'
5 | import classNames from 'classnames/bind'
6 | import HeaderRight from './HeaderRight/HeaderRight'
7 | import BreadCrumbPro from 'components/BreadCrumbPro/BreadCrumbPro'
8 | import { SET_COLLAPSED } from 'store/actionTypes/configActionType'
9 | import { RootState } from 'typings/store'
10 | import { Space } from 'antd'
11 |
12 | const cx = classNames.bind(style)
13 |
14 | const LayoutHeader = (): ReactElement => {
15 | const reduxConfig = useSelector((state: RootState) => state.config)
16 | const dispatch = useDispatch()
17 |
18 | const reduxSetConfig = (type: string, payload: string | boolean) => {
19 | dispatch({ type, payload })
20 | }
21 |
22 | return (
23 |
24 |
25 | {/* 展开收缩侧边栏菜单按钮 */}
26 | {
27 | reduxConfig.siderMenuIsHas && reduxConfig.autoHoldSiderIsShow
28 | ? { reduxSetConfig(SET_COLLAPSED, !reduxConfig.siderMenuIsCollapsed) }}>
29 | {
30 | reduxConfig.siderMenuIsCollapsed
31 | ?
32 | :
33 | }
34 |
35 | : null
36 | }
37 |
38 | {/* 控制面包屑是否存在 */}
39 | {
40 | reduxConfig.breadCrumbIsHas
41 | ?
44 | : null
45 | }
46 |
47 |
48 |
49 | )
50 | }
51 |
52 | export default LayoutHeader
53 |
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/components/NotificationCenter/NotificationCenter.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 |
3 | const Example: React.FC = (): ReactElement => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default Example
12 |
--------------------------------------------------------------------------------
/src/layouts/LayoutHeader/components/Personal/Personal.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, useEffect, useState } from 'react'
2 | import { Avatar, Dropdown, Space, Menu } from 'antd'
3 | import { UserOutlined, SettingOutlined, LogoutOutlined } from '@ant-design/icons'
4 | import style from 'layouts/LayoutHeader/LayoutHeader.module.less'
5 | import classNames from 'classnames/bind'
6 | import { useLogout } from 'pages/EntryScreen/service/EntryScreenHoooks'
7 | import { init } from 'locales'
8 |
9 | const cx = classNames.bind(style)
10 |
11 | const Personal: React.FC = (): ReactElement => {
12 | const [userName, setUserName] = useState('R-Boot')
13 | const [avatar, setAvatar] = useState('')
14 |
15 | useEffect(() => {
16 | const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
17 | setUserName(userInfo.username || 'R-Boot')
18 | setAvatar(userInfo.Avatar)
19 | }, [])
20 |
21 | const { logout } = useLogout()
22 |
23 | const menu = (
24 |
32 | )
33 | return (
34 |
35 |
36 |
37 | {userName}
38 |
39 |
40 | )
41 | }
42 |
43 | export default Personal
44 |
--------------------------------------------------------------------------------
/src/layouts/LayoutSider/LayoutSiderMenu.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import CustomMenu from 'components/CustomMenu/CustomMenu'
3 | import { useSelector } from 'react-redux'
4 | import { RootState } from 'typings/store'
5 |
6 | const LayoutSiderMenu: React.FC = (): ReactElement => {
7 | const reduxConfig = useSelector((state: RootState) => state.config)
8 | const authMenu = JSON.parse(localStorage.getItem('authMenu') || '[]')
9 |
10 | return (
11 |
18 | )
19 | }
20 |
21 | export default LayoutSiderMenu
22 |
--------------------------------------------------------------------------------
/src/locales/en_US/columns.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | // 账号管理
3 | 'page.table.columns.orderId': 'Order id',
4 | 'page.table.columns.name': 'Name',
5 | 'page.table.columns.phoneNum': 'Cell-phone number',
6 | 'page.table.columns.belongToRoles': 'Roles',
7 | 'page.table.columns.department': 'Department',
8 | 'page.table.columns.accountStatus': 'Account status',
9 | 'page.table.columns.loginAccount': 'Login account',
10 | 'page.table.columns.password': 'Password',
11 | 'page.table.columns.createTime': 'Create time',
12 | 'page.table.columns.updateTime': 'Modify time',
13 | 'page.table.columns.operation': 'Operation',
14 | 'page.table.columns.accountsList': 'Accounts list',
15 | 'page.table.columns.confirmEnable': 'Confirm to enable',
16 | 'page.table.columns.confirmDisable': 'Confirm to disable',
17 | 'page.table.columns.account': 'account',
18 | 'page.table.columns.newCreateAccount': 'New create account',
19 | 'page.table.columns.editAccount': 'Edit account'
20 | }
21 |
--------------------------------------------------------------------------------
/src/locales/en_US/common.ts:
--------------------------------------------------------------------------------
1 | // 常用文案
2 | export default {
3 | 'page.common.ok': 'Ok',
4 | 'page.common.cancel': 'Cancel',
5 | 'page.common.docTitle': 'R-Boot Middle and back stage scaffold',
6 | 'page.common.newCreate': 'New Create',
7 | 'page.common.delete': 'Delete',
8 | 'page.common.edit': 'Edit',
9 | 'page.common.export': 'Export',
10 | 'page.common.enable': 'Enable',
11 | 'page.common.disable': 'Disable',
12 | 'page.common.query': 'Query',
13 | 'page.common.reset': 'Reset',
14 | 'page.common.putaway': 'Put away',
15 | 'page.common.open': 'Open',
16 | 'page.common.total': 'total',
17 | 'page.common.items': 'items',
18 | 'page.common.all': 'All',
19 | 'page.common.displayColumn': 'Display column',
20 | 'page.common.selectAll': 'Select all',
21 | 'page.common.reverseSelect': 'Reverse select',
22 | 'page.common.unselectAll': 'Unselect all'
23 | }
24 |
--------------------------------------------------------------------------------
/src/locales/en_US/errorCode.ts:
--------------------------------------------------------------------------------
1 | // 错误码
2 | export default {
3 | 200: 'Successfull',
4 | 100: 'The account or password is wrong, please contact the administrator!',
5 | 101: 'Failed to obtain permission information, please contact the administrator!',
6 | 102: 'The account and password are not registered, please contact the administrator!',
7 | // 账号管理错误码
8 | 1001: 'Account information does not exist',
9 | 1002: 'Unknown error in account information modification'
10 | }
11 |
--------------------------------------------------------------------------------
/src/locales/en_US/header.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.header.personCenter': 'Personal Center',
3 | 'page.header.personConfig': 'Personal settings',
4 | 'page.header.logOut': 'Log out',
5 | 'page.header.simplifiedChinese': 'Simplified Chinese',
6 | 'page.header.english': 'English'
7 | }
8 |
--------------------------------------------------------------------------------
/src/locales/en_US/login.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.login.login': 'Login',
3 | 'page.login.register': 'Register',
4 | 'page.login.h2': 'React middle and back office solution',
5 | 'page.login.getvcode': 'Get VCode',
6 | 'page.login.otherloginmethods': 'Other Login Methods :',
7 | 'page.login.usernameplaceholder': 'username: admin',
8 | 'page.login.passwordplaceholder': 'password: admin',
9 | 'page.login.phonenumplaceholder': 'Please input mobile phone number',
10 | 'page.login.vcodeplaceholder': 'Please enter the verification code'
11 | }
12 |
--------------------------------------------------------------------------------
/src/locales/en_US/menus.ts:
--------------------------------------------------------------------------------
1 | // 登录页
2 | export default {
3 | 'page.menu.home': 'Home',
4 | 'page.menu.dashboard': 'DashBoard',
5 | 'page.menu.dashboard.analysis': 'Analysis',
6 | 'page.menu.dashboard.monitor': 'Monitor',
7 | 'page.menu.auth': 'AuthManage',
8 | 'page.menu.auth.rolesmanage': 'RolesManage',
9 | 'page.menu.auth.accountsmanage': 'AccountsManage',
10 | 'page.menu.auth.menusmanage': 'MenuManage',
11 | 'page.menu.system': 'SystemManage',
12 | 'page.menu.system.systemconfig': 'SystemConfig'
13 | }
14 |
--------------------------------------------------------------------------------
/src/locales/en_US/rightCode.ts:
--------------------------------------------------------------------------------
1 | // 正确码
2 | export default {
3 | loginSuccess: '🎉 Login succeeded!'
4 | }
5 |
--------------------------------------------------------------------------------
/src/locales/en_US/searchForm.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.searchForm.orderIdPlaceholder': 'Please enter the number',
3 | 'page.searchForm.loginAccountPlaceholder': 'Please enter login account',
4 | 'page.searchForm.namePlaceholder': 'Please enter your name',
5 | 'page.searchForm.adminDepartment': 'Admin Depart',
6 | 'page.searchForm.financeDepartment': 'Finance Depart',
7 | 'page.searchForm.R&Ddepartment': 'R&D Depart',
8 | 'page.searchForm.salesDepartment': 'Sales Depart',
9 | 'page.searchForm.publicDepartment': 'Public Depart'
10 | }
11 |
--------------------------------------------------------------------------------
/src/locales/index.ts:
--------------------------------------------------------------------------------
1 | import config from 'config/config'
2 |
3 | // 查找本地语言配置
4 | const currentLocale = localStorage.getItem('currentLocale') || config.locale
5 |
6 | // @ts-ignore
7 | const ctx = require.context('./', true, /(? item.indexOf(currentLocale || 'zh_CN') !== -1)
10 | // 语言
11 | const lang = ctxFilterKeys.reduce((total: any, path: string) => {
12 | const requireContext = ctx(path).default
13 | return { ...total, ...requireContext }
14 | }, {})
15 | // 调用语言的方法
16 | export const init = (id: string) => {
17 | return lang[id] || id
18 | }
19 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/columns.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | // 账号管理
3 | 'page.table.columns.orderId': '编号',
4 | 'page.table.columns.name': '姓名',
5 | 'page.table.columns.phoneNum': '手机号',
6 | 'page.table.columns.belongToRoles': '所属角色',
7 | 'page.table.columns.department': '所属部门',
8 | 'page.table.columns.accountStatus': '账号状态',
9 | 'page.table.columns.loginAccount': '登录账号',
10 | 'page.table.columns.password': '账号密码',
11 | 'page.table.columns.createTime': '创建时间',
12 | 'page.table.columns.updateTime': '更新时间',
13 | 'page.table.columns.operation': '操作',
14 | 'page.table.columns.accountsList': '账号列表',
15 | 'page.table.columns.confirmEnable': '确定启用',
16 | 'page.table.columns.confirmDisable': '确定禁用',
17 | 'page.table.columns.account': '账号',
18 | 'page.table.columns.newCreateAccount': '新建账号',
19 | 'page.table.columns.editAccount': '编辑账号'
20 | }
21 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/common.ts:
--------------------------------------------------------------------------------
1 | // 常用文案
2 | export default {
3 | 'page.common.ok': '确定',
4 | 'page.common.cancel': '取消',
5 | 'page.common.docTitle': 'R-Boot中后台脚手架',
6 | 'page.common.newCreate': '新建',
7 | 'page.common.delete': '删除',
8 | 'page.common.edit': '编辑',
9 | 'page.common.export': '导出',
10 | 'page.common.enable': '已启用',
11 | 'page.common.disable': '已禁用',
12 | 'page.common.query': '查询',
13 | 'page.common.reset': '重置',
14 | 'page.common.putaway': '收起',
15 | 'page.common.open': '展开',
16 | 'page.common.total': '总共',
17 | 'page.common.items': '条',
18 | 'page.common.all': '全部',
19 | 'page.common.displayColumn': '展示列',
20 | 'page.common.selectAll': '全选',
21 | 'page.common.reverseSelect': '返选',
22 | 'page.common.unselectAll': '取消全选'
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/errorCode.ts:
--------------------------------------------------------------------------------
1 | // 错误码
2 | export default {
3 | 200: '成功',
4 | 100: '账号或者密码错误,请联系管理员!',
5 | 101: '权限信息获取失败,请联系管理员!',
6 | 102: '账号密码未注册,请联系管理员!',
7 | // 账号管理错误码
8 | 1001: '账号信息不存在',
9 | 1002: '账号信息修改未知错误'
10 | }
11 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/header.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.header.personCenter': '个人中心',
3 | 'page.header.personConfig': '个人设置',
4 | 'page.header.logOut': '退出登录',
5 | 'page.header.simplifiedChinese': '简体中文',
6 | 'page.header.english': '英语'
7 | }
8 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/login.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.login.login': '登录',
3 | 'page.login.register': '注册',
4 | 'page.login.h2': 'React中后台解决方案脚手架',
5 | 'page.login.getvcode': '获取验证码',
6 | 'page.login.otherloginmethods': '其他登录方式',
7 | 'page.login.usernameplaceholder': '用户名: admin',
8 | 'page.login.passwordplaceholder': '密 码: admin',
9 | 'page.login.phonenumplaceholder': '请输入手机号',
10 | 'page.login.vcodeplaceholder': '请输入验证码'
11 | }
12 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/menus.ts:
--------------------------------------------------------------------------------
1 | // 登录页
2 | export default {
3 | 'page.menu.home': '首页',
4 | 'page.menu.dashboard': 'DashBoard',
5 | 'page.menu.dashboard.analysis': '分析页',
6 | 'page.menu.dashboard.monitor': '监控页',
7 | 'page.menu.auth': '权限管理',
8 | 'page.menu.auth.rolesmanage': '角色管理',
9 | 'page.menu.auth.accountsmanage': '账号管理',
10 | 'page.menu.auth.menusmanage': '菜单管理',
11 | 'page.menu.system': '系统管理',
12 | 'page.menu.system.systemconfig': '系统配置'
13 | }
14 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/rightCode.ts:
--------------------------------------------------------------------------------
1 | // 正确码
2 | export default {
3 | loginSuccess: '🎉登录成功!'
4 | }
5 |
--------------------------------------------------------------------------------
/src/locales/zh_CN/searchForm.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'page.searchForm.orderIdPlaceholder': '请输入编号',
3 | 'page.searchForm.loginAccountPlaceholder': '请输入登录账号',
4 | 'page.searchForm.namePlaceholder': '请输入姓名',
5 | 'page.searchForm.adminDepartment': '行政部门',
6 | 'page.searchForm.financeDepartment': '财务部门',
7 | 'page.searchForm.R&Ddepartment': '研发部门',
8 | 'page.searchForm.salesDepartment': '销售部门',
9 | 'page.searchForm.publicDepartment': '公关部门'
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/App.module.less:
--------------------------------------------------------------------------------
1 | .layout {
2 | width: 100%;
3 | height: 100%;
4 | box-sizing: border-box;
5 | &-right {
6 | &__header {
7 | background-color: #fff;
8 | padding: 0;
9 | box-shadow: 4px 2px 8px #f0f1f2;
10 | z-index: 10;
11 | }
12 | &__content {
13 | box-sizing: border-box;
14 | overflow: auto;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/pages/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, useEffect, Fragment } from 'react'
2 | import { Grid, Layout } from 'antd'
3 | import LayoutHeader from 'layouts/LayoutHeader/LayoutHeader'
4 | import LayoutContent from 'layouts/LayoutContent/LayoutContent'
5 | import LayoutSiderMenu from 'layouts/LayoutSider/LayoutSiderMenu'
6 | import { useDispatch, useSelector } from 'react-redux'
7 | import style from './App.module.less'
8 | import classNames from 'classnames/bind'
9 | import { RootState } from 'typings/store'
10 | import {
11 | SET_COLLAPSED,
12 | SET_AUTO_SHOW_SIDER
13 | } from 'store/actionTypes/configActionType'
14 |
15 | const cx = classNames.bind(style)
16 | const { useBreakpoint } = Grid
17 | const { Header, Sider, Content } = Layout
18 |
19 | const App = (): ReactElement => {
20 | // 避免页面闪烁
21 | const reduxConfig = useSelector((state: RootState) => state.config)
22 | const dispatch = useDispatch()
23 | const screens = useBreakpoint()
24 |
25 | const reduxSetSiderMenuIsCollapsed = (
26 | type: string,
27 | payload: string | boolean
28 | ) => {
29 | dispatch({ type, payload })
30 | }
31 |
32 | const reduxSetAutoShowSider = (type: string, payload: string | boolean) => {
33 | dispatch({ type, payload })
34 | }
35 |
36 | useEffect(
37 | () => {
38 | reduxSetAutoShowSider(SET_AUTO_SHOW_SIDER, screens.sm as boolean)
39 | },
40 | [screens]
41 | )
42 |
43 | return (
44 |
45 | {/* Sider */}
46 | {reduxConfig.siderMenuIsHas
47 | ? (
48 |
49 | {reduxConfig.autoHoldSiderIsShow
50 | ? (
51 | {
58 | reduxSetSiderMenuIsCollapsed(
59 | SET_COLLAPSED,
60 | !reduxConfig.siderMenuIsCollapsed
61 | )
62 | }}
63 | >
64 |
65 |
66 | )
67 | : null}
68 |
69 | )
70 | : null}
71 | {/* 右侧 */}
72 |
73 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
85 | export default App
86 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/AccountsManage/AccountsManage.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, useMemo, useState, useEffect } from 'react'
2 | import { InputNumber, Select } from 'antd'
3 | import AdvancedSearch, { SearchFormItem } from 'components/AdvancedSearch/AdvancedSearch'
4 | import AccountTable from './components/AccountTable/AccountTable'
5 | import AccountModal from './components/AccountModal/AccountModal'
6 | import { AccountRecord } from 'typings/accountsManage'
7 | import { accountQueryApi } from 'api/AccountsManage/AccountsManage'
8 | import { usePaging } from '../../../publicHooks/tableHooks/tableHooks'
9 | import { FIRST_TYPE } from 'utils/globalConstantParams'
10 | import { titleMap, departmentMap, accountStatusMap } from 'pages/AuthManage/AccountsManage/service/constantParams'
11 | import { init } from 'locales'
12 |
13 | const AccountsManage: React.FC = (): ReactElement => {
14 | const [modalTitle, setModalTitle] = useState(titleMap[FIRST_TYPE]) // 新建或者编辑的模态框的标题
15 | const [modalVisible, setModalVisible] = useState(false) // 模态框的显示隐藏
16 | const [rowList, setRowList] = useState() // 表格编辑按钮被点击获取到的行数据
17 | const [tableLoading, setTableLoading] = useState(false) // 表格loading
18 | const { paging, changePage } = usePaging() // 分页的自定义hook
19 | const [pageTotal, setPageTotal] = useState(0) // 表格页数
20 | const [tableList, setTableList] = useState([]) // 表格数据
21 | const [searchData, setSearchData] = useState({
22 | accountsOrder: '',
23 | loginAccount: '',
24 | department: '',
25 | accountStatus: '',
26 | email: ''
27 | }) // 高级搜索查询参数
28 |
29 | /**
30 | * 查询表格数据的接口
31 | */
32 | const accountQueryMethod = async () => {
33 | setTableLoading(true)
34 | const params = { ...searchData, ...paging }
35 | console.log('params', params)
36 | const { data } = await accountQueryApi(params)
37 | console.log('data', data)
38 | if (data.code === 200) {
39 | setTableList(data.data)
40 | setPageTotal(data.total)
41 | }
42 | setTableLoading(false)
43 | }
44 |
45 | // 刷新页面查询表格数据,分页改变的时候,查询表格数据
46 | useEffect(
47 | () => {
48 | accountQueryMethod()
49 | },
50 | [paging]
51 | )
52 |
53 | /**
54 | * 新建和编辑打开模态框方法
55 | * @param {boolean} visible 控制模态框显示隐藏
56 | * @param title 模态框新建还是编辑
57 | * @param record 点击新建和编辑的时候可能需要传递的表格行数据
58 | */
59 | const toggleModalVisibleMethod = (visible: boolean, title?: string, record?: AccountRecord) => {
60 | setModalVisible(visible)
61 | setModalTitle(title as string)
62 | setRowList(record)
63 | }
64 |
65 | // 高级搜索 - 搜索条件
66 | const formList: SearchFormItem[] = useMemo(() => {
67 | return [
68 | {
69 | name: 'accountsOrder',
70 | label: init('page.table.columns.orderId'),
71 | initialValue: '',
72 | render: (
73 |
79 | )
80 | },
81 | {
82 | name: 'loginAccount',
83 | label: init('page.table.columns.loginAccount'),
84 | initialValue: '',
85 | placeholder: init('page.searchForm.loginAccountPlaceholder')
86 | },
87 | {
88 | name: 'name',
89 | label: init('page.table.columns.name'),
90 | initialValue: '',
91 | placeholder: init('page.searchForm.namePlaceholder')
92 | },
93 | {
94 | name: 'department',
95 | label: init('page.table.columns.department'),
96 | initialValue: '',
97 | render:
98 | },
99 | {
100 | name: 'accountStatus',
101 | label: init('page.table.columns.accountStatus'),
102 | initialValue: '',
103 | render:
104 | }
105 | ]
106 | }, [])
107 |
108 | /**
109 | * 高级搜索栏 - 搜索按钮事件
110 | * @param params 搜索查询参数
111 | */
112 | const onSearch = (params: any) => {
113 | setSearchData(params)
114 | changePage(1)
115 | }
116 |
117 | return (
118 |
119 |
120 |
121 |
130 |
131 |
{
136 | toggleModalVisibleMethod(false)
137 | }}
138 | />
139 |
140 | )
141 | }
142 |
143 | export default AccountsManage
144 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/AccountsManage/components/AccountModal/AccountModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Modal } from 'antd'
3 | import { IAccountModal } from 'typings/accountsManage'
4 |
5 | const AccountModal = (props: IAccountModal) => {
6 | return
7 | }
8 |
9 | export default AccountModal
10 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/AccountsManage/components/AccountTable/AccountTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, Fragment, useMemo } from 'react'
2 | import AdvancedTable from 'components/aboutAdvanceTable/AdvancedTable/AdvancedTable'
3 | import { AuthAction } from 'components/aboutAuthButton/AuthButtonGroup'
4 | import { Modal, Space, Switch, Tag } from 'antd'
5 | import AuthButton from 'components/aboutAuthButton/AuthButton/AuthButton'
6 | import { useAccountColumns } from 'pages/AuthManage/AccountsManage/service/columnsHook'
7 | import { AccountRecord, IAccountTable } from 'typings/accountsManage'
8 | import { useRowSelection, useExpandable } from 'publicHooks/tableHooks/tableHooks'
9 | import { ColumnsType } from 'antd/lib/table/interface'
10 | import { titleMap, accountStatusMap } from 'pages/AuthManage/AccountsManage/service/constantParams'
11 | import { FIRST_TYPE, SECOND_TYPE } from 'utils/globalConstantParams'
12 | import {
13 | PlusOutlined,
14 | DeleteOutlined,
15 | EditOutlined,
16 | VerticalAlignBottomOutlined,
17 | ExclamationCircleOutlined
18 | } from '@ant-design/icons'
19 | import { accountDeleteApi, changeAccountStatusApi } from 'api/AccountsManage/AccountsManage'
20 | import { init } from 'locales'
21 |
22 | const AccountTable: React.FC = (props): ReactElement => {
23 | const {
24 | tableList, // 账号表格数据
25 | toggleModalVisibleMethod, // 新建和编辑控制模态框关闭打开的方法
26 | pageTotal, // 总条数
27 | changePage, // 改变分页的方法
28 | paging,
29 | tableLoading,
30 | accountQueryMethod
31 | } = props
32 |
33 | // 表格选择配置选项
34 | const {
35 | checkedRowKeys,
36 | checkedRows,
37 | setCheckedRowKeys,
38 | setCheckedRows,
39 | rowSelection
40 | } = useRowSelection('accountsOrder')
41 | const parantAccountQueryMethod = () => {
42 | accountQueryMethod()
43 | setCheckedRowKeys([])
44 | setCheckedRows([])
45 | }
46 |
47 | // 表格展开配置选项
48 | const expandable = useExpandable(
49 | (record: AccountRecord) => {
50 | return {record.description}
51 | }
52 | )
53 |
54 | // 表格操作按钮配置项
55 | const authActions: AuthAction[] = useMemo(() => [
56 | {
57 | name: init('page.common.newCreate'),
58 | auth: 'ACCOUNT_ADD',
59 | customtype: 'default',
60 | icon: ,
61 | onClick: () => {
62 | toggleModalVisibleMethod(true, titleMap[FIRST_TYPE])
63 | }
64 | },
65 | {
66 | name: init('page.common.delete'),
67 | auth: 'ACCOUNT_DEL',
68 | customtype: 'danger',
69 | icon: ,
70 | disabled: checkedRowKeys.length === 0 && checkedRows.length === 0,
71 | onClick: () => {
72 | deleteTableRow(checkedRows)
73 | }
74 | },
75 | {
76 | name: init('page.common.edit'),
77 | auth: 'ACCOUNT_EDIT',
78 | customtype: 'warning',
79 | icon: ,
80 | disabled: checkedRowKeys.length !== 1 && checkedRows.length !== 1,
81 | onClick: () => {
82 | toggleModalVisibleMethod(true, titleMap[SECOND_TYPE], checkedRows[0])
83 | }
84 | },
85 | {
86 | name: init('page.common.export'),
87 | auth: 'ACCOUNT_EXPORT',
88 | customtype: 'info',
89 | icon: ,
90 | disabled: checkedRowKeys.length === 0 && checkedRows.length === 0,
91 | onClick: () => {
92 | console.log('obj', 444444444444444)
93 | }
94 | }
95 | ], [checkedRowKeys, checkedRows])
96 |
97 | // 账号状态渲染函数
98 | const accountsStatusRender = (value: number, record: AccountRecord) => {
99 | return (
100 | item.value === 1)?.label}
103 | unCheckedChildren={accountStatusMap.find(item => item.value === 0)?.label}
104 | onClick={(val) => { editAccountStatus(val, record) }}
105 | />
106 | )
107 | }
108 | // 操作渲染函数
109 | const operationRender = (value: number, record: AccountRecord) => {
110 | return (
111 |
112 | { toggleModalVisibleMethod(true, titleMap[2], record) }}
116 | >
117 | {init('page.common.edit')}
118 |
119 | { deleteTableRow([record]) }}
124 | >
125 | {init('page.common.delete')}
126 |
127 |
128 | )
129 | }
130 |
131 | // 可展示列
132 | const columns :ColumnsType = useAccountColumns({
133 | accountsStatusRender,
134 | operationRender
135 | })
136 |
137 | /**
138 | * 编辑账号状态的二次弹窗
139 | * @param {Boolean} val 账号启用还是禁用的值
140 | * @param {AccountRecord} record 账号管理列表的行数据
141 | */
142 | const editAccountStatus = (val: boolean, record: AccountRecord) => {
143 | Modal.confirm({
144 | icon: ,
145 | content: (
146 |
147 | {init(`page.table.columns.${val ? 'confirmEnable' : 'confirmDisable'}`)}
148 | {` ${record.loginAccount} `}
149 | {init('page.table.columns.account')}
150 | ?
151 |
152 | ),
153 | okText: init('page.common.ok'),
154 | cancelText: init('page.common.cancel'),
155 | onOk: () => {
156 | enAbleOrBindAccount(val, record)
157 | }
158 | })
159 | }
160 |
161 | /**
162 | * 账号启用禁用的方法
163 | * @param {Boolean} val 账号启用还是禁用的值
164 | * @param {AccountRecord} record 账号管理列表的行数据
165 | */
166 | const enAbleOrBindAccount = async (val: boolean, record: AccountRecord) => {
167 | const paramsData = { accountsOrder: record.accountsOrder, accountStatus: Number(val) }
168 | const { data } = await changeAccountStatusApi(paramsData)
169 | if (data.code === 200) {
170 | parantAccountQueryMethod()
171 | }
172 | }
173 |
174 | /**
175 | * 单个删除/批量删除
176 | * @param {Array} rowArr 要删除的行数据集合
177 | */
178 | const deleteTableRow = async (rowArr: AccountRecord[]) => {
179 | const paramsData = rowArr.map((item: AccountRecord) => item.accountsOrder)
180 | Modal.confirm({
181 | icon: null,
182 | content:
183 |
184 | 确定要删除
185 |
186 | {
187 | rowArr.map((item: AccountRecord) => {
188 | return (
189 | {item.name}
190 | )
191 | })
192 | }
193 |
194 | {rowArr.length}个账号 ?
195 | ,
196 | onOk: async () => {
197 | const { data } = await accountDeleteApi(paramsData)
198 | if (data.code === 200) {
199 | parantAccountQueryMethod()
200 | }
201 | }
202 | })
203 | }
204 |
205 | return (
206 | {init('page.table.columns.accountsList')}
}
210 | canConfig={true}
211 | authActions={authActions}
212 | columns={columns}
213 | paging={paging}
214 | dataSource={tableList}
215 | rowKey={(record) => {
216 | return `${record.accountsOrder}`
217 | }}
218 | rowSelection={rowSelection}
219 | expandable={expandable}
220 | pageTotal={pageTotal}
221 | changePage={changePage}
222 | />
223 | )
224 | }
225 |
226 | export default AccountTable
227 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/AccountsManage/service/columnsHook.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ColumnsType } from 'antd/lib/table'
3 | import { AccountRecord } from 'typings/accountsManage'
4 | import { useCommonColumns } from 'publicHooks/tableHooks/tableHooks'
5 | import { departmentMap } from 'pages/AuthManage/AccountsManage/service/constantParams'
6 | import { init } from 'locales'
7 |
8 | interface IOptions {
9 | accountsStatusRender:(value: number, record: AccountRecord) => React.ReactElement
10 | operationRender:(value: number, record: AccountRecord) => React.ReactElement
11 | }
12 |
13 | export const useAccountColumns = (options: IOptions) : ColumnsType => {
14 | const commonColumns = useCommonColumns()
15 | return [
16 | {
17 | title: init('page.table.columns.orderId'),
18 | dataIndex: 'accountsOrder',
19 | align: 'center',
20 | fixed: 'left'
21 | },
22 | {
23 | title: init('page.table.columns.name'),
24 | dataIndex: 'name',
25 | align: 'center'
26 | },
27 | {
28 | title: init('page.table.columns.phoneNum'),
29 | dataIndex: 'phoneNumber',
30 | align: 'center',
31 | render: function renderPhoneNumber(value:string) {
32 | return value.substring(0, 3) + '****' + value.substr(value.length - 4)
33 | }
34 | },
35 | {
36 | title: init('page.table.columns.belongToRoles'),
37 | dataIndex: 'belongToRoles',
38 | align: 'center',
39 | ellipsis: true,
40 | render: function (value:string) {
41 | return departmentMap.find(item => item.value === value)?.label
42 | }
43 | },
44 | {
45 | title: init('page.table.columns.department'),
46 | dataIndex: 'department',
47 | align: 'center',
48 | render: function (value:string) {
49 | return departmentMap.find(item => item.value === value)?.label
50 | }
51 | },
52 | {
53 | title: init('page.table.columns.accountStatus'),
54 | dataIndex: 'accountsStatus',
55 | align: 'center',
56 | render: options.accountsStatusRender
57 | },
58 | {
59 | title: init('page.table.columns.loginAccount'),
60 | dataIndex: 'loginAccount',
61 | align: 'center'
62 | },
63 | {
64 | title: init('page.table.columns.password'),
65 | dataIndex: 'accountPassword',
66 | align: 'center'
67 | },
68 | ...commonColumns,
69 | {
70 | title: init('page.table.columns.operation'),
71 | dataIndex: 'operation',
72 | align: 'center',
73 | fixed: 'right',
74 | render: options.operationRender
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/AccountsManage/service/constantParams.ts:
--------------------------------------------------------------------------------
1 | import { init } from 'locales'
2 |
3 | // 账号管理Table
4 | export const titleMap: {[key:string]: string} = {
5 | 1: init('page.table.columns.newCreateAccount'),
6 | 2: init('page.table.columns.editAccount')
7 | }
8 |
9 | export const departmentMap: {value: string, label: string}[] = [
10 | { value: '', label: init('page.common.all') },
11 | { value: '1', label: init('page.searchForm.adminDepartment') },
12 | { value: '2', label: init('page.searchForm.financeDepartment') },
13 | { value: '3', label: init('page.searchForm.R&Ddepartment') },
14 | { value: '4', label: init('page.searchForm.salesDepartment') },
15 | { value: '5', label: init('page.searchForm.publicDepartment') }
16 | ]
17 |
18 | export const accountStatusMap: {value: string | number, label: string}[] = [
19 | { value: '', label: init('page.common.all') },
20 | { value: 0, label: init('page.common.disable') },
21 | { value: 1, label: init('page.common.enable') }
22 | ]
23 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/MenusManage/MenusManage.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 |
3 | const MenusManage: React.FC = (): ReactElement => {
4 | return MenusManage
5 | }
6 |
7 | export default MenusManage
8 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/RolesManage/RolesManage.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Input } from 'antd'
3 | import { RolesRecord, titleMap } from 'typings/rolesManage.d'
4 | import AdvancedTablePro from 'components/aboutAdvanceTable/AdvancedTablePro/AdvancedTablePro'
5 | import { IAdvancedTableProps } from 'components/aboutAdvanceTable/AdvancedTable/AdvancedTable'
6 | import { usePaging, useRowSelection } from 'publicHooks/tableHooks/tableHooks'
7 | import { rolesQueryApi } from 'api/rolesManage/rolesManage'
8 | import { IAdvancedModalProps } from 'components/AdvancedModal/AdvancedModal'
9 | import { useAboutRolesTable } from './service/aboutTable'
10 | import { useSearchFormOptions } from './service/aboutSearchForm'
11 | import { useDialog } from 'publicHooks/modalHooks/modalHooks'
12 | import { useQueryDataList } from 'publicHooks/apiHooks/apiHooks'
13 |
14 | const RolesManage: React.FC = (): ReactElement => {
15 | const {
16 | paging, // 分页数据
17 | changePage // 改变分页的方法
18 | } = usePaging() // 分页的自定义hook
19 | const { modalVisible, modalType, modalDetail, openModal, closeModal } = useDialog()
20 | const { checkedRowKeys, checkedRows, rowSelection } = useRowSelection('rolesOrder')
21 |
22 | // ----------- 高级搜索相关
23 | const { serchFormOptions, searchData } = useSearchFormOptions({ changePage })
24 |
25 | // ----------- 查询数据Api
26 | const { queryLoading, tableList, pageTotal } = useQueryDataList(searchData, paging, rolesQueryApi)
27 |
28 | // ----------- 高级表格相关
29 | const { rolesColumns, authActions } = useAboutRolesTable({
30 | openModal,
31 | checkedRowKeys,
32 | checkedRows
33 | })
34 |
35 | const advanceTableOptions: IAdvancedTableProps = {
36 | authActions: authActions,
37 | dataSource: tableList,
38 | columns: rolesColumns,
39 | rowSelection: rowSelection,
40 | loading: queryLoading,
41 | paging: paging,
42 | changePage,
43 | pageTotal,
44 | canConfig: true,
45 | rowKey: record => {
46 | return record.rolesOrder
47 | }
48 | }
49 |
50 | // ----------- 高级模态框相关
51 | const advancedModalOptions: IAdvancedModalProps = {
52 | modalOptions: {
53 | visible: modalVisible,
54 | title: titleMap[modalType],
55 | modalDetail: modalDetail,
56 | onCancel: () => {
57 | closeModal()
58 | },
59 | confirmed: () => {
60 | closeModal()
61 | }
62 | },
63 | formOptions: {
64 | modalFormList: [
65 | {
66 | name: 'userName',
67 | label: '用户名',
68 | render:
69 | }
70 | ]
71 | }
72 | }
73 |
74 | return (
75 |
82 | )
83 | }
84 |
85 | export default RolesManage
86 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/RolesManage/service/aboutSearchForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Select } from 'antd'
3 | import { AdvancedSearchProps } from 'components/AdvancedSearch/AdvancedSearch'
4 | import { rolesStatusMap } from './constantParams'
5 |
6 | interface IOptions {
7 | changePage: (page: number, pageSize?: number | undefined) => void
8 | }
9 | export const useSearchFormOptions = ({ changePage }: IOptions) => {
10 | const [searchData, setSearchData] = useState({
11 | roleName: '',
12 | authCharacter: '',
13 | roleStatus: ''
14 | }) // 高级搜索查询参数
15 | const serchFormOptions: AdvancedSearchProps = {
16 | formList: [
17 | {
18 | name: 'roleName',
19 | label: '角色名称'
20 | },
21 | {
22 | name: 'authCharacter',
23 | label: '权限字符'
24 | },
25 | {
26 | name: 'roleStatus',
27 | label: '角色状态',
28 | shouldUpdate: true,
29 | render:
30 | }
31 | ],
32 | onSearch: res => {
33 | setSearchData(res)
34 | changePage(1)
35 | }
36 | }
37 |
38 | return {
39 | serchFormOptions,
40 | searchData
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/RolesManage/service/aboutTable.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { Modal, Switch, Space } from 'antd'
3 | import { ColumnsType } from 'antd/lib/table'
4 | import { RolesRecord } from 'typings/rolesManage'
5 | import AuthButton from 'components/aboutAuthButton/AuthButton/AuthButton'
6 | import { FIRST_TYPE, SECOND_TYPE } from 'utils/globalConstantParams'
7 | import { AuthAction } from 'components/aboutAuthButton/AuthButtonGroup'
8 | import {
9 | PlusOutlined,
10 | DeleteOutlined,
11 | EditOutlined,
12 | ExclamationCircleOutlined,
13 | VerticalAlignBottomOutlined
14 | } from '@ant-design/icons'
15 |
16 | interface IOptions {
17 | openModal: (
18 | title?: string | undefined,
19 | record?: RolesRecord | undefined
20 | ) => void // 新建编辑模态框
21 | checkedRowKeys: React.Key[] // 选中的key
22 | checkedRows: RolesRecord[] // 选中的行
23 | }
24 |
25 | export const useAboutRolesTable = ({
26 | openModal,
27 | checkedRowKeys,
28 | checkedRows
29 | }: IOptions) => {
30 | const editRoleStatus = (val: boolean, record: RolesRecord) => {
31 | Modal.confirm({
32 | icon: ,
33 | content: val
34 | ? `确定启用 ${record.rolesName} 角色 ?`
35 | : `确定禁用 ${record.rolesName} 角色 ?`,
36 | okText: '确认',
37 | cancelText: '取消',
38 | onOk: () => {
39 | console.log('obj', 11)
40 | }
41 | })
42 | }
43 |
44 | const rolesColumns: ColumnsType = [
45 | {
46 | title: '角色编号',
47 | dataIndex: 'rolesOrder',
48 | align: 'center',
49 | width: 200,
50 | fixed: 'left'
51 | },
52 | {
53 | title: '角色名称',
54 | dataIndex: 'rolesName',
55 | align: 'center',
56 | width: 200
57 | },
58 | {
59 | title: '权限字符',
60 | dataIndex: 'authCharacter',
61 | align: 'center',
62 | width: 200
63 | },
64 | {
65 | title: '角色状态',
66 | dataIndex: 'rolesStatus',
67 | align: 'center',
68 | width: 200,
69 | render: function rolesStatusRender(
70 | text: number,
71 | record: RolesRecord,
72 | index: number
73 | ) {
74 | return (
75 | {
80 | editRoleStatus(val, record)
81 | }}
82 | />
83 | )
84 | }
85 | },
86 | {
87 | title: '创建时间',
88 | dataIndex: 'createBy',
89 | align: 'center',
90 | width: 200
91 | },
92 | {
93 | title: '操作',
94 | dataIndex: 'operation',
95 | align: 'center',
96 | width: 200,
97 | fixed: 'right',
98 | render: function operationRender(
99 | value: any,
100 | record: RolesRecord,
101 | index: number
102 | ) {
103 | return (
104 |
105 | {
109 | openModal(SECOND_TYPE, record)
110 | }}
111 | >
112 | 编辑
113 |
114 |
115 | 删除
116 |
117 |
118 | )
119 | }
120 | }
121 | ]
122 |
123 | const authActions: AuthAction[] = useMemo(
124 | () => [
125 | {
126 | name: '新建',
127 | auth: 'ROLES_ADD',
128 | customtype: 'default',
129 | icon: ,
130 | onClick: () => {
131 | openModal(FIRST_TYPE)
132 | }
133 | },
134 | {
135 | name: '删除',
136 | auth: 'ROLES_DEL',
137 | customtype: 'danger',
138 | icon: ,
139 | disabled: checkedRowKeys.length === 0 && checkedRows.length === 0,
140 | onClick: () => {
141 | console.log('obj', 222222222222222)
142 | }
143 | },
144 | {
145 | name: '编辑',
146 | auth: 'ROLES_EDIT',
147 | customtype: 'warning',
148 | icon: ,
149 | disabled: checkedRowKeys.length !== 1 && checkedRows.length !== 1,
150 | onClick: () => {
151 | openModal(SECOND_TYPE, checkedRows[0])
152 | }
153 | },
154 | {
155 | name: '导出',
156 | auth: 'ROLES_EXPORT',
157 | customtype: 'info',
158 | icon: ,
159 | disabled: checkedRowKeys.length === 0 && checkedRows.length === 0,
160 | onClick: () => {
161 | console.log('obj', 444444444444444)
162 | }
163 | }
164 | ],
165 | [checkedRowKeys, checkedRows]
166 | )
167 |
168 | return {
169 | rolesColumns,
170 | authActions
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/pages/AuthManage/RolesManage/service/constantParams.ts:
--------------------------------------------------------------------------------
1 |
2 | export const rolesStatusMap: {value: string | number, label: string}[] = [
3 | { value: '', label: '全部' },
4 | { value: 0, label: '已禁用' },
5 | { value: 1, label: '已启用' }
6 | ]
7 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Analysis/Analysis.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Col, Row } from 'antd'
3 | import ConsumptionRanking from './components/ConsumptionRanking'
4 | import DistributionMap from './components/DistributionMap'
5 | import TrendChart from './components/TrendChart'
6 |
7 | const Analysis: React.FC = (): ReactElement => {
8 | return (
9 |
10 | {/* 趋势图 */}
11 |
12 |
13 |
14 |
15 | {/* 分布图 */}
16 |
17 |
18 |
19 |
20 | {/* 消费排行 */}
21 |
22 |
23 |
24 |
25 | {/* 地区 */}
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default Analysis
34 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Analysis/components/ConsumptionRanking.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Card, Space } from 'antd'
3 | import { OrderedListOutlined } from '@ant-design/icons'
4 |
5 | const ConsumptionRanking = () => {
6 | return (
7 | 消费排行}
9 | hoverable
10 | >
11 |
12 |
13 | )
14 | }
15 |
16 | export default ConsumptionRanking
17 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Analysis/components/DistributionMap.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Card, Space } from 'antd'
3 | import { PieChartOutlined } from '@ant-design/icons'
4 | import * as echarts from 'echarts'
5 |
6 | const DistributionMap = () => {
7 | const distributionMapRef = React.useRef()
8 | let myChart: any
9 |
10 | const addEventResize = () => {
11 | myChart.resize()
12 | }
13 |
14 | useEffect(() => {
15 | setTimeout(() => {
16 | initEchart()
17 | }, 300)
18 | return () => {
19 | window.removeEventListener('resize', addEventResize)
20 | }
21 | }, [])
22 |
23 | const initEchart = () => {
24 | myChart = echarts.init(distributionMapRef.current as HTMLElement)
25 | const option = {
26 | tooltip: {
27 | // 触发方式
28 | trigger: 'item'
29 | },
30 | series: [
31 | {
32 | type: 'pie',
33 | data: [
34 | {
35 | value: 1410,
36 | name: '直接访问'
37 | },
38 | {
39 | value: 1234,
40 | name: '搜索引擎'
41 | },
42 | {
43 | value: 1548,
44 | name: '联盟广告'
45 | },
46 | {
47 | value: 334,
48 | name: '邮件营销'
49 | },
50 | {
51 | value: 234,
52 | name: '视频广告'
53 | }
54 | ]
55 | }
56 | ]
57 | }
58 | // 绘制图表
59 | myChart.setOption(option)
60 |
61 | window.addEventListener('resize', addEventResize)
62 | }
63 | return (
64 |
67 | 分布
68 |
69 | }
70 | hoverable
71 | >
72 |
73 |
74 | )
75 | }
76 |
77 | export default DistributionMap
78 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Analysis/components/TrendChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Card, Space } from 'antd'
3 | import { LineChartOutlined } from '@ant-design/icons'
4 | import * as echarts from 'echarts'
5 | type EChartsOption = echarts.EChartsOption
6 |
7 | const TrendChart = () => {
8 | const trendChartRef = React.useRef()
9 | let myChart: any
10 |
11 | const addEventResize = () => {
12 | myChart.resize()
13 | }
14 |
15 | useEffect(() => {
16 | setTimeout(() => {
17 | initEchart()
18 | }, 300)
19 |
20 | return () => {
21 | window.removeEventListener('resize', addEventResize)
22 | }
23 | }, [])
24 |
25 | const initEchart = () => {
26 | myChart = echarts.init(trendChartRef.current as HTMLElement)
27 | const option: EChartsOption = {
28 | title: {
29 | text: 'R-Boot 中后台访问量'
30 | },
31 | tooltip: {
32 | // 触发方式
33 | trigger: 'axis'
34 | },
35 | legend: {
36 | data: ['访问量']
37 | },
38 | xAxis: {
39 | name: '月份',
40 | type: 'category',
41 | data: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
42 | },
43 | yAxis: {
44 | name: '次',
45 | type: 'value'
46 | },
47 | series: [
48 | {
49 | name: '访问量',
50 | data: [305, 230, 402, 789, 512, 777, 988, 578, 898, 1290, 1510, 1792],
51 | type: 'line'
52 | }
53 | ]
54 | }
55 | // 绘制图表
56 | option && myChart.setOption(option)
57 | window.addEventListener('resize', addEventResize)
58 | }
59 |
60 | return (
61 |
64 |
65 | 趋势
66 |
67 | }
68 | hoverable
69 | >
70 |
71 |
72 | )
73 | }
74 |
75 | export default TrendChart
76 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Monitor/Monitor.module.less:
--------------------------------------------------------------------------------
1 | .Monitor {
2 | height: 1000px;
3 | font-family: vab-count;
4 | // 第八题
5 | .wrapper {
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/Dashbord/Monitor/Monitor.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import style from './Monitor.module.less'
3 | import classNames from 'classnames/bind'
4 |
5 | const cx = classNames.bind(style)
6 |
7 | const Monitor: React.FC = (): ReactElement => {
8 | return 1000
9 | }
10 |
11 | export default Monitor
12 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/EntryScreen.module.less:
--------------------------------------------------------------------------------
1 | .EntryScreen {
2 | width: 100%;
3 | height: 100%;
4 | background: url(../../assets/images/entryScreenBG.svg);
5 | display: flex;
6 | justify-content: center;
7 | position: relative;
8 | &-content {
9 | margin-top: -48px;
10 | width: 328px;
11 | &__header {
12 | margin: 150px 0 40px;
13 | & > h1 {
14 | font-size: 28px;
15 | font-weight: 600;
16 | text-align: center;
17 | line-height: 64px;
18 | }
19 | & > p {
20 | text-align: center;
21 | color: rgba(0, 0, 0, 0.45);
22 | }
23 | }
24 | &__card {
25 | height: 272px;
26 | .EntryScreen-card__loginTitle,
27 | .EntryScreen-card__registerTitle {
28 | display: inline-block;
29 | width: 100px;
30 | height: 100%;
31 | text-align: center;
32 | }
33 | }
34 | &__otherLogin {
35 | font-size: 20px;
36 | & span:hover {
37 | cursor: pointer;
38 | color: #1890ff;
39 | }
40 | }
41 | }
42 | &-toggleLang {
43 | position: absolute;
44 | top: 20px;
45 | right: 40px;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/EntryScreen.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState } from 'react'
2 | import Login from 'pages/EntryScreen/Login/Login'
3 | import Register from 'pages/EntryScreen/Register/Register'
4 | import {
5 | AlipayOutlined,
6 | GithubOutlined,
7 | WechatOutlined,
8 | QqOutlined
9 | } from '@ant-design/icons'
10 | import style from './EntryScreen.module.less'
11 | import classNames from 'classnames/bind'
12 | import { ITab } from './type'
13 | import { Card, Space } from 'antd'
14 | import { ToggleLang } from 'components/ToggleLang/ToggleLang'
15 | import { init } from 'locales'
16 |
17 | const cx = classNames.bind(style)
18 |
19 | const EntryScreen: FC = () => {
20 | // Card 组件 当前选中的tab的key
21 | const [currentKey, setCurrentKey] = useState('login')
22 |
23 | // Card 组件 tabList - tab标题
24 | const tabList: ITab[] = [
25 | {
26 | key: 'login',
27 | tab: (
28 |
29 | {init('page.login.login')}
30 |
31 | )
32 | },
33 | {
34 | key: 'register',
35 | tab: (
36 |
37 | {init('page.login.register')}
38 |
39 | )
40 | }
41 | ]
42 |
43 | //
44 | const onTabChange = (key: string) => {
45 | setCurrentKey(key)
46 | }
47 |
48 | return (
49 |
50 |
51 |
52 |
R-Boot
53 |
{init('page.login.h2')}
54 |
55 |
69 | {currentKey === 'login' ? : }
70 |
71 |
72 |
73 |
74 | {init('page.login.otherloginmethods')}
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | )
96 | }
97 |
98 | export default EntryScreen
99 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/Login/Login.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Form, Input, Button } from 'antd'
3 | import { UserOutlined, LockOutlined } from '@ant-design/icons'
4 | import QueueAnim from 'rc-queue-anim'
5 | import { useLogin } from 'pages/EntryScreen/service/EntryScreenHoooks'
6 | import { init } from 'locales'
7 |
8 | const Login: React.FC = () => {
9 | /**
10 | * @description: 中后台登录服务
11 | * @param {*}
12 | * @return {*}
13 | */
14 | const { onFinish } = useLogin()
15 |
16 | return (
17 |
58 | )
59 | }
60 |
61 | export default Login
62 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/Register/Register.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Form, Input, Button, Space, Statistic } from 'antd'
3 | import { TabletOutlined, MailOutlined } from '@ant-design/icons'
4 | import QueueAnim from 'rc-queue-anim'
5 | import { init } from 'locales'
6 |
7 | const { Countdown } = Statistic
8 |
9 | const Register: React.FC = () => {
10 | const deadline = Date.now() + 1000 * 60
11 | // 获取验证码按钮点击以后置灰/显示倒计时状态
12 | const [pCodeButtonDisabled, setPCodeButtonDisabled] = React.useState(false)
13 | const onFinish = (values: any) => {
14 | console.log('values: ', values)
15 | }
16 |
17 | const getPCodeButton = (): void => {
18 | setPCodeButtonDisabled(true)
19 | }
20 | return (
21 |
86 | )
87 | }
88 |
89 | export default Register
90 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/service/EntryScreenHoooks.ts:
--------------------------------------------------------------------------------
1 | import { message } from 'antd'
2 | import { loginApi, getAuthInfoApi } from 'api/EntryScreenApi/EntryScreenApi'
3 | import Cookies from 'js-cookie'
4 | import { useHistory } from 'react-router'
5 | import { filterAuthRoutes } from 'routers/userDynamicRouters'
6 | import { init } from 'locales'
7 |
8 | export interface ILogin {
9 | username: string,
10 | password: string
11 | }
12 |
13 | /**
14 | * @description: 中后台登录服务
15 | * @param {*}
16 | * @return {*}
17 | */
18 | export const useLogin = () => {
19 | const history = useHistory()
20 | const onFinish = async (values: any) => {
21 | const bool = await loginFetch(values)
22 | if (bool) {
23 | await getAuthInfoAction()
24 | history.push('/home')
25 | }
26 | }
27 |
28 | /**
29 | * @description: 调用登录的接口
30 | */
31 | const loginFetch = async (values: ILogin) => {
32 | const { data } = await loginApi(values)
33 | // 存储过滤好的权限路由信息
34 | if (data.code === 200) {
35 | console.log('data', data)
36 | localStorage.setItem('userInfo', JSON.stringify(data.data))
37 | Cookies.set('R-Boot-token', data.data.token)
38 | message.success(init('loginSuccess'))
39 | return true
40 | }
41 | return false
42 | }
43 |
44 | /**
45 | * @description: 获取菜单路由权限信息/按钮权限信息
46 | */
47 | const getAuthInfoAction = async () => {
48 | const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
49 | if (userInfo.token) {
50 | const { data } = await getAuthInfoApi({ token: userInfo.token })
51 | if (data.code === 200 && data.data.authMenu) {
52 | localStorage.setItem('authMenu', JSON.stringify(filterAuthRoutes(data.data.authMenu)))
53 | localStorage.setItem('authButton', JSON.stringify(data.data.authButton))
54 | }
55 | }
56 | }
57 | return {
58 | onFinish
59 | }
60 | }
61 |
62 | /**
63 | * @description: 退出中后台系统配置
64 | * @param {*}
65 | * @return {*}
66 | */
67 | export const useLogout = () => {
68 | const logout = () => {
69 | localStorage.clear()
70 | Cookies.remove('R-Boot-token')
71 | window.location.reload()
72 | }
73 | return {
74 | logout
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/pages/EntryScreen/type.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export interface ITab {
4 | key: string,
5 | tab: React.ReactNode
6 | }
7 |
8 | export interface IContent {
9 | [key: string]: React.ReactNode
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Grid, Tag } from 'antd'
3 |
4 | const { useBreakpoint } = Grid
5 |
6 | function UseBreakpointDemo() {
7 | const screens = useBreakpoint()
8 | return (
9 | <>
10 | Current break point:{' '}
11 | {Object.entries(screens)
12 | .filter(screen => !!screen[1])
13 | .map(screen => (
14 |
15 | {screen[0]}
16 |
17 | ))}
18 | >
19 | )
20 | }
21 |
22 | export default UseBreakpointDemo
23 |
--------------------------------------------------------------------------------
/src/pages/NotFind/NotFind.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Result, Button } from 'antd'
3 | import { NavLink } from 'react-router-dom'
4 | const NotFind: React.FC = (): ReactElement => {
5 | return (
6 |
12 | Back Home
13 |
14 | }
15 | />
16 | )
17 | }
18 |
19 | export default NotFind
20 |
--------------------------------------------------------------------------------
/src/pages/System/SystemConfig/SystemConfig.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react'
2 | import { Row, Col, Card } from 'antd'
3 | import { useDispatch, useSelector } from 'react-redux'
4 | import SiderMenuConfig from './components/SiderMenuConfig/SiderMenuConfig'
5 | import TopMenuConfig from './components/TopMenuConfig/TopMenuConfig'
6 | import { RootState } from 'typings/store'
7 |
8 | const SystemConfig: React.FC = (): ReactElement => {
9 | const reduxConfig = useSelector((state: RootState) => state.config)
10 | const dispatch = useDispatch()
11 |
12 | const reduxSetConfig = (type: string, payload: string | boolean) => {
13 | dispatch({
14 | type,
15 | payload
16 | })
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default SystemConfig
42 |
--------------------------------------------------------------------------------
/src/pages/System/SystemConfig/components/SiderMenuConfig/SiderMenuConfig.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ISiderMenuConfig } from 'typings/systemConfig'
3 | import {
4 | SET_SIDER_MENU_IS_HAS,
5 | SET_SIDER_MENU_THEME,
6 | SET_SIDER_MENU_IS_HAS_LOGO
7 | } from 'store/actionTypes/configActionType'
8 | import { Row, Col, Switch, Select } from 'antd'
9 | import { TConfig } from 'typings/config'
10 |
11 | interface IProps {
12 | reduxConfig: TConfig
13 | reduxSetConfig: (type: string, payload: string | boolean) => void
14 | }
15 | const SiderMenuConfig: React.FC = props => {
16 | const { reduxConfig, reduxSetConfig } = props
17 |
18 | const SiderMenuConfigs: ISiderMenuConfig[] = [
19 | {
20 | label: '侧边菜单',
21 | value: 'siderMenuIsHas',
22 | type: SET_SIDER_MENU_IS_HAS
23 | },
24 | {
25 | label: '侧边菜单Logo',
26 | value: 'siderMenuIshHasLogo',
27 | type: SET_SIDER_MENU_IS_HAS_LOGO
28 | },
29 | {
30 | label: '侧边菜单颜色',
31 | value: 'siderMenuTheme',
32 | type: SET_SIDER_MENU_THEME,
33 | render: function siderMenuThemeRender() {
34 | return (
35 |
45 | )
46 | }
47 | }
48 | ]
49 |
50 | return (
51 |
52 | {SiderMenuConfigs &&
53 | SiderMenuConfigs.map((row: ISiderMenuConfig, index: number) => {
54 | return (
55 |
64 | {row.label}
65 | {row.render
66 | ? (
67 | row.render()
68 | )
69 | : (
70 | {
73 | reduxSetConfig(row.type, !reduxConfig[row.value])
74 | }}
75 | />
76 | )}
77 |
78 | )
79 | })}
80 |
81 | )
82 | }
83 |
84 | export default SiderMenuConfig
85 |
--------------------------------------------------------------------------------
/src/pages/System/SystemConfig/components/TopMenuConfig/TopMenuConfig.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ISiderMenuConfig } from 'typings/systemConfig'
3 | import {
4 | SET_TOP_MENU_IS_HAS,
5 | SET_TOP_MENU_IS_HAS_LOGO,
6 | SET_BREADCRUMB_IS_HAS
7 | } from 'store/actionTypes/configActionType'
8 | import { Row, Col, Switch } from 'antd'
9 | import { TConfig } from 'typings/config'
10 |
11 | interface IProps {
12 | reduxConfig: TConfig
13 | reduxSetConfig: (type: string, payload: string | boolean) => void
14 | }
15 | const TopMenuConfig: React.FC = props => {
16 | const { reduxConfig, reduxSetConfig } = props
17 |
18 | const SiderMenuConfigs: ISiderMenuConfig[] = [
19 | {
20 | label: '顶部菜单',
21 | value: 'topMenuIsHas',
22 | type: SET_TOP_MENU_IS_HAS
23 | },
24 | {
25 | label: '顶部菜单Logo',
26 | value: 'topMenuIsHasLogo',
27 | type: SET_TOP_MENU_IS_HAS_LOGO
28 | },
29 | {
30 | label: '顶部面包屑',
31 | value: 'breadCrumbIsHas',
32 | type: SET_BREADCRUMB_IS_HAS
33 | }
34 | // {
35 | // label: '顶部菜单颜色',
36 | // value: 'topMenuTheme',
37 | // type: SET_TOP_MENU_THEME,
38 | // render: function siderMenuThemeRender() {
39 | // return (
40 | //
48 | // )
49 | // }
50 | // }
51 | ]
52 |
53 | return (
54 |
55 | {SiderMenuConfigs &&
56 | SiderMenuConfigs.map((row: ISiderMenuConfig, index: number) => {
57 | return (
58 |
67 | {row.label}
68 | {row.render
69 | ? (
70 | row.render()
71 | )
72 | : (
73 | {
76 | reduxSetConfig(row.type, !reduxConfig[row.value])
77 | }}
78 | />
79 | )}
80 |
81 | )
82 | })}
83 |
84 | )
85 | }
86 |
87 | export default TopMenuConfig
88 |
--------------------------------------------------------------------------------
/src/publicHooks/apiHooks/apiHooks.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { AxiosPromise } from 'axios'
3 |
4 | /**
5 | *
6 | * @param params 查询参数和分页
7 | * @param action 查询方法
8 | * @returns
9 | */
10 | export const useQueryDataList = (params: any, paging: any, action: (params: any) => AxiosPromise) => {
11 | const [queryLoading, setQueryLoading] = useState(false)
12 | const [tableList, setTableList] = useState()
13 | const [pageTotal, setPageTotal] = useState()
14 |
15 | useEffect(() => {
16 | setQueryLoading(true)
17 | action({ ...params, ...paging })
18 | .then(res => {
19 | setTableList(res.data.data)
20 | setPageTotal(res.data.total)
21 | setQueryLoading(false)
22 | })
23 | }, [paging])
24 |
25 | return {
26 | queryLoading,
27 | tableList,
28 | pageTotal
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/publicHooks/index.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | /**
4 | * 设置网页标题
5 | * @param title 网页标题
6 | */
7 | export const useSetDocumentTitle = (title: string) => {
8 | React.useEffect(() => {
9 | document.title = title
10 | }, [])
11 | }
12 |
13 | /**
14 | * 设置 Antd 国际化方案切换的方法
15 | */
16 |
17 | export const useAntdLocale = (locale: string) => {
18 | const [antdLocale, setAntdLocale] = React.useState()
19 | // 查找本地语言配置
20 | const currentLocale = localStorage.getItem('currentLocale') || locale
21 |
22 | React.useEffect(() => {
23 | currentAntdLocale(currentLocale).then((res) => {
24 | setAntdLocale(res)
25 | })
26 | }, [])
27 |
28 | const currentAntdLocale = async (currentLocale: string) => {
29 | switch (currentLocale) {
30 | case 'zh_CN':
31 | return (await import('antd/lib/locale/zh_CN')).default
32 | case 'en_US':
33 | return (await import('antd/lib/locale/en_US')).default
34 | default:
35 | return (await import('antd/lib/locale/zh_CN')).default
36 | }
37 | }
38 |
39 | return antdLocale
40 | }
41 |
--------------------------------------------------------------------------------
/src/publicHooks/modalHooks/modalHooks.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FIRST_TYPE } from 'utils/globalConstantParams'
3 | interface IDialogProps {
4 | closed: (data: any) => void // 模态框关闭按钮点击模态框关闭以后的回调函数
5 | confirmed: (data: any) => void // 模态框确定按钮点击模态框关闭以后的回调函数
6 | }
7 | /**
8 | *
9 | * @param props
10 | */
11 | export const useDialog = (props?: IDialogProps) => {
12 | const closed = props?.closed
13 | const confirmed = props?.confirmed
14 |
15 | const [modalVisible, setModalVisible] = React.useState(false) // 模态框是否显示
16 | const [modalType, setModalType] = React.useState(FIRST_TYPE) // '1' 新建; '2' 编辑;
17 | const [modalDetail, setModalDetail] = React.useState()
18 |
19 | // 打开弹窗
20 | const openModal = (title?: string, record?: T) => {
21 | setModalVisible(true)
22 | if (title) {
23 | setModalType(title)
24 | }
25 | if (record) {
26 | setModalDetail(record)
27 | }
28 | }
29 |
30 | // 关闭弹窗
31 | const closeModal = () => {
32 | setModalVisible(false)
33 | }
34 |
35 | // 模态框关闭按钮点击模态框关闭以后的回调函数
36 | const onClosed = (data?: any) => {
37 | closeModal()
38 | closed && closed(data)
39 | }
40 |
41 | // 模态框确定按钮点击模态框关闭以后的回调函数
42 | const onConfirmed = (data?: any) => {
43 | closeModal()
44 | confirmed && confirmed(data)
45 | }
46 |
47 | return {
48 | modalVisible,
49 | modalType,
50 | modalDetail,
51 | setModalDetail,
52 | openModal,
53 | closeModal,
54 | onClosed,
55 | onConfirmed
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/publicHooks/tableHooks/tableHooks.ts:
--------------------------------------------------------------------------------
1 | import { useState, Key } from 'react'
2 | import { ColumnsType, ExpandableConfig, TableRowSelection } from 'antd/lib/table/interface'
3 | import moment from 'moment'
4 | import { scrollIntoView } from 'utils/public'
5 | import { init } from 'locales'
6 |
7 | /**
8 | * @description 表格选择行配置项
9 | */
10 | export const useRowSelection = (keyID: string) => {
11 | const [checkedRowKeys, setCheckedRowKeys] = useState([])
12 | const [checkedRows, setCheckedRows] = useState([])
13 |
14 | const rowSelection: TableRowSelection | undefined = {
15 | selectedRowKeys: checkedRowKeys,
16 | fixed: true,
17 | onSelect: (record, selected, selectedRow) => {
18 | if (selected) {
19 | // 添加
20 | // @ts-ignore
21 | setCheckedRowKeys([...checkedRowKeys, record[keyID]])
22 | setCheckedRows([...checkedRows, record])
23 | } else {
24 | // @ts-ignore
25 | const subCheckedKeys = checkedRowKeys.filter((item: Key) => item !== record[keyID])
26 | // @ts-ignore
27 | const subCheckedRows = checkedRows.filter((item: T) => item[keyID] !== record[keyID])
28 | setCheckedRowKeys(subCheckedKeys)
29 | setCheckedRows(subCheckedRows)
30 | }
31 | },
32 | onSelectAll: (selected, selectedRows, changeRows) => {
33 | if (selected) {
34 | const addCheckedKeys = changeRows.map((item: T) => {
35 | // @ts-ignore
36 | return item[keyID]
37 | })
38 | setCheckedRowKeys([...checkedRowKeys, ...addCheckedKeys])
39 | setCheckedRows([...checkedRows, ...changeRows])
40 | } else {
41 | const subCheckedKeys = checkedRowKeys.filter((ite: Key) => {
42 | return !changeRows.some((item: T) => {
43 | // @ts-ignore
44 | return item[keyID] === ite
45 | })
46 | })
47 | const subCheckedRows = checkedRows.filter((ite: any) => {
48 | return !changeRows.some((item: T) => {
49 | // @ts-ignore
50 | return item[keyID] === ite[keyID]
51 | })
52 | })
53 | setCheckedRowKeys(subCheckedKeys)
54 | setCheckedRows(subCheckedRows)
55 | }
56 | },
57 | columnWidth: 64,
58 | // selections: [
59 | // Table.SELECTION_ALL,
60 | // Table.SELECTION_INVERT,
61 | // Table.SELECTION_NONE
62 | // ],
63 | type: 'checkbox'
64 | }
65 |
66 | return {
67 | checkedRowKeys,
68 | checkedRows,
69 | setCheckedRowKeys,
70 | setCheckedRows,
71 | rowSelection
72 | }
73 | }
74 |
75 | /**
76 | * @description 表格展开配置项
77 | */
78 | export const useExpandable = (expandedRowRender?: any) => {
79 | const expandable: ExpandableConfig | undefined = {
80 | fixed: 'left',
81 | expandedRowRender: expandedRowRender
82 | }
83 | return expandable
84 | }
85 |
86 | /**
87 | * 公共表格列
88 | */
89 | export const useCommonColumns = () => {
90 | // 时间渲染函数
91 | const renderTime = (value: number) => {
92 | return moment(value).format('YYYY-MM-DD HH:mm:ss')
93 | }
94 |
95 | const commonColumns: ColumnsType = [
96 | {
97 | title: init('page.table.columns.createTime'),
98 | dataIndex: 'createTime',
99 | align: 'center',
100 | width: 200,
101 | render: renderTime
102 | },
103 | {
104 | title: init('page.table.columns.updateTime'),
105 | dataIndex: 'updateTime',
106 | align: 'center',
107 | width: 200,
108 | render: renderTime
109 | }
110 | ]
111 | return commonColumns
112 | }
113 |
114 | /**
115 | * 分页hooks
116 | */
117 | export const usePaging = () => {
118 | const [paging, setPaging] = useState<{ pageNo: number, pageSize: number}>({
119 | pageNo: 1,
120 | pageSize: 10
121 | })
122 |
123 | /**
124 | * @description: 改变分页的方法
125 | * */
126 | const changePage = (page: number, pageSize?: number | undefined) => {
127 | scrollIntoView('#scrollTop')
128 | setTimeout(() => {
129 | if (pageSize) {
130 | setPaging({ ...paging, pageNo: page, pageSize: (pageSize as number) })
131 | } else {
132 | setPaging({ ...paging, pageNo: page })
133 | }
134 | })
135 | }
136 |
137 | /**
138 | * 刷新页面请求的方法
139 | */
140 |
141 | const resetPage = () => {
142 | setPaging({ ...paging })
143 | }
144 | return {
145 | paging,
146 | changePage,
147 | resetPage
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | declare module '*.less'
3 | declare module 'rc-animate'
4 | declare module 'rc-texty'
--------------------------------------------------------------------------------
/src/routers/AsyncComponent.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // 首页
4 | export const Home = React.lazy(() => import(/* webpackChunkName: "Home" */'pages/Home/Home'))
5 |
6 | /**
7 | * Dashborad
8 | */
9 | // 分析页
10 | export const Analysis = React.lazy(() => import(/* webpackChunkName: "Analysis" */'pages/Dashbord/Analysis/Analysis'))
11 | // 监控页面
12 | export const Monitor = React.lazy(() => import(/* webpackChunkName: "Monitor" */'pages/Dashbord/Monitor/Monitor'))
13 |
14 | /**
15 | * 权限管理
16 | */
17 | // 角色管理
18 | export const Rolesmanage = React.lazy(
19 | () => import(/* webpackChunkName: "RolesManage" */'pages/AuthManage/RolesManage/RolesManage')
20 | )
21 | // 账户管理
22 | export const Accountsmanage = React.lazy(
23 | () => import(/* webpackChunkName: "AccountsManage" */'pages/AuthManage/AccountsManage/AccountsManage')
24 | )
25 | // 菜单管理
26 | export const Menusmanage = React.lazy(
27 | () => import(/* webpackChunkName: "MenusManage" */'pages/AuthManage/MenusManage/MenusManage')
28 | )
29 |
30 | /**
31 | * 系统管理
32 | */
33 | // 系统配置
34 | export const Systemconfig = React.lazy(
35 | () => import(/* webpackChunkName: "MenusManage" */'pages/System/SystemConfig/SystemConfig')
36 | )
37 |
--------------------------------------------------------------------------------
/src/routers/userDynamicRouters.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { IRouter } from 'typings/router'
3 | import {
4 | Home,
5 | Analysis,
6 | Monitor,
7 | Rolesmanage,
8 | Accountsmanage,
9 | Menusmanage,
10 | Systemconfig
11 | } from './AsyncComponent'
12 | /**
13 | * @description: 本地所有路由信息映射
14 | */
15 | const allAuthRouter:{[key:string]: any} = {
16 | // 首页
17 | home: Home,
18 | // 分析页
19 | analysis: Analysis,
20 | // 监控页面
21 | monitor: Monitor,
22 | // 角色管理
23 | rolesmanage: Rolesmanage,
24 | // 账户管理
25 | accountsmanage: Accountsmanage,
26 | // 菜单管理
27 | menusmanage: Menusmanage,
28 | // 系统配置
29 | systemconfig: Systemconfig
30 | }
31 |
32 | /**
33 | * 动态路由映射函数
34 | */
35 | export const userDynamicRouters = (rightRouters: IRouter[]) => {
36 | const userRouters: IRouter[] = []
37 | const recursionMap = (arr: any) => {
38 | arr.forEach((item: any) => {
39 | if (item.children && item.children.length) {
40 | recursionMap(item.children)
41 | } else {
42 | const component = item.component?.toLowerCase()
43 | if (allAuthRouter[component]) {
44 | userRouters.push({ ...item, component: allAuthRouter[component] })
45 | }
46 | }
47 | })
48 | }
49 | recursionMap(rightRouters)
50 | return userRouters
51 | }
52 |
53 | /**
54 | * @description: 权限路由,根据请求回来的路由,过滤有权限的路由
55 | * @return {*}
56 | */
57 | export const filterAuthRoutes = (authMenu: IRouter[]) => {
58 | const newArr: IRouter[] = authMenu.filter((menu: any) => menu.auth)
59 | newArr.forEach((item: any) => {
60 | if (item.children && item.children.length) {
61 | item.children = filterAuthRoutes(item.children)
62 | }
63 | })
64 | return newArr
65 | }
66 |
--------------------------------------------------------------------------------
/src/store/actionTypes/configActionType.ts:
--------------------------------------------------------------------------------
1 | export const SET_LANG = 'SET_LANG'
2 | export const SET_SIDER_MENU_IS_HAS = 'SET_SIDER_MENU_IS_HAS'
3 | export const SET_COLLAPSED = 'SET_COLLAPSED'
4 | export const SET_SIDER_MENU_THEME = 'SET_SIDER_MENU_THEME'
5 | export const SET_SIDER_MENU_IS_HAS_LOGO = 'SET_SIDER_MENU_IS_HAS_LOGO'
6 | export const SET_TOP_MENU_IS_HAS = 'SET_TOP_MENU_IS_HAS'
7 | export const SET_TOP_MENU_IS_HAS_LOGO = 'SET_TOP_MENU_IS_HAS_LOGO'
8 | export const SET_TOP_MENU_THEME = 'SET_TOP_MENU_THEME'
9 |
10 | export const SET_BREADCRUMB_IS_HAS = 'SET_BREADCRUMB_IS_HAS'
11 | export const SET_AUTO_SHOW_SIDER = 'SET_AUTO_SHOW_SIDER'
12 |
--------------------------------------------------------------------------------
/src/store/reducers/configReducer/configReducer.ts:
--------------------------------------------------------------------------------
1 | import { TConfig } from 'typings/config'
2 | import config from 'config/config'
3 | import {
4 | SET_LANG,
5 | SET_SIDER_MENU_IS_HAS,
6 | SET_COLLAPSED,
7 | SET_SIDER_MENU_THEME,
8 | SET_SIDER_MENU_IS_HAS_LOGO,
9 | SET_TOP_MENU_IS_HAS,
10 | SET_TOP_MENU_IS_HAS_LOGO,
11 | SET_TOP_MENU_THEME,
12 | SET_BREADCRUMB_IS_HAS,
13 | SET_AUTO_SHOW_SIDER
14 | } from 'store/actionTypes/configActionType'
15 |
16 | // redux默认的配置
17 | const defaultConfig = {
18 | // 默认国际化语言配置
19 | locale: 'zh_CN',
20 | // 是否有侧边菜单
21 | siderMenuIsHas: true,
22 | // 改变宽度的时候,是否自动隐藏侧边菜单
23 | autoHoldSiderIsShow: true,
24 | // 侧边菜单的颜色
25 | siderMenuTheme: 'dark',
26 | // 侧边菜单的是否收起
27 | siderMenuIsCollapsed: false,
28 | // 侧边菜单是否有LOGO
29 | siderMenuIshHasLogo: true,
30 | // 是否有顶部菜单
31 | topMenuIsHas: false,
32 | // 顶部菜单颜色
33 | topMenuTheme: 'light',
34 | // 顶部菜单是否有logo
35 | topMenuIsHasLogo: false,
36 | // 是否有顶部面包屑
37 | breadCrumbIsHas: true,
38 | // 是否默认有备案号
39 | isHasCopyright: true
40 | }
41 |
42 | const userCurrentConfig = JSON.parse(localStorage.getItem('userCurrentConfig') || '{}')
43 |
44 | const defaultState: TConfig = Object.assign(defaultConfig, config, userCurrentConfig)
45 |
46 | interface IAction {
47 | type: string;
48 | payload?: string | boolean;
49 | }
50 |
51 | export default (state = defaultState, { type, payload }: IAction) => {
52 | const newState = JSON.parse(JSON.stringify(state))
53 | // 全局config配置 - 当前语言
54 | if (type === SET_LANG) {
55 | newState.locale = payload
56 | localStorage.setItem('currentLocale', payload as string)
57 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
58 | return newState
59 | }
60 | // 是否有侧边菜单
61 | if (type === SET_SIDER_MENU_IS_HAS) {
62 | newState.siderMenuIsHas = payload
63 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
64 | return newState
65 | }
66 | // 改变宽度的时候,是否自动隐藏侧边菜单
67 | if (type === SET_AUTO_SHOW_SIDER) {
68 | newState.autoHoldSiderIsShow = payload
69 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
70 | return newState
71 | }
72 | // 侧边菜单的颜色
73 | if (type === SET_SIDER_MENU_THEME) {
74 | newState.siderMenuTheme = payload
75 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
76 | return newState
77 | }
78 | // 侧边菜单展开还是收缩
79 | if (type === SET_COLLAPSED) {
80 | newState.siderMenuIsCollapsed = payload
81 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
82 | return newState
83 | }
84 | // 侧边菜单是否有LOGO
85 | if (type === SET_SIDER_MENU_IS_HAS_LOGO) {
86 | newState.siderMenuIshHasLogo = payload
87 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
88 | return newState
89 | }
90 |
91 | // 是否有顶部菜单
92 | if (type === SET_TOP_MENU_IS_HAS) {
93 | newState.topMenuIsHas = payload
94 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
95 | return newState
96 | }
97 | // 顶部菜单是否有logo
98 | if (type === SET_TOP_MENU_IS_HAS_LOGO) {
99 | newState.topMenuIsHasLogo = payload
100 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
101 | return newState
102 | }
103 | // 顶部菜单颜色
104 | if (type === SET_TOP_MENU_THEME) {
105 | newState.topMenuTheme = payload
106 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
107 | return newState
108 | }
109 |
110 | // 是否有顶部面包屑
111 | if (type === SET_BREADCRUMB_IS_HAS) {
112 | newState.breadCrumbIsHas = payload
113 | localStorage.setItem('userCurrentConfig', JSON.stringify(newState))
114 | return newState
115 | }
116 | return state
117 | }
118 |
--------------------------------------------------------------------------------
/src/store/reducers/reducers.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import config from './configReducer/configReducer'
3 |
4 | export default combineReducers({
5 | config
6 | })
7 |
--------------------------------------------------------------------------------
/src/store/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose, Store } from 'redux'
2 | import reducer from './reducers/reducers'
3 | import thunk from 'redux-thunk'
4 | import { RootState, IAction } from 'typings/store'
5 |
6 | // @ts-ignore
7 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
8 |
9 | const store: Store = createStore(
10 | reducer,
11 | composeEnhancers(
12 | applyMiddleware(
13 | thunk
14 | )
15 | )
16 | )
17 |
18 | export default store
19 |
--------------------------------------------------------------------------------
/src/styles/globalAntd.module.less:
--------------------------------------------------------------------------------
1 |
2 | // 语言切换组件
3 | :global(.ToggleLang) {
4 | width: 48px;
5 | height: 48px;
6 | font-size: 24px;
7 | cursor: pointer;
8 | display: flex !important;
9 | align-items: center;
10 | justify-content: center;
11 | }
12 |
13 | :global(.ToggleLang:hover) {
14 | background: #F8F8F8;
15 | }
16 |
17 | // :global{
18 | // .ant-message-notice-content{
19 | // border-radius: 5px !important;
20 | // padding: 0 !important;
21 | // .ant-message-success {
22 | // min-width: 400px;
23 | // padding: 10px 16px;
24 | // border: 1px solid;
25 | // border-radius: 5px;
26 | // color: #13ce66;
27 | // background-color: #f6ffed;
28 | // border: 1px solid #b7eb8f;
29 | // text-align: start;
30 | // text-indent: 10px;
31 | // line-height: 24px;
32 | // }
33 | // .ant-message-error {
34 | // min-width: 400px;
35 | // padding: 10px 16px;
36 | // border: 1px solid;
37 | // border-radius: 5px;
38 | // color: #ff4949;
39 | // background: #ffeded;
40 | // border-color: #ffb6b6;
41 | // text-align: start;
42 | // text-indent: 10px;
43 | // line-height: 24px;
44 | // }
45 | // .ant-message-warning {
46 | // min-width: 400px;
47 | // padding: 10px 16px;
48 | // border: 1px solid;
49 | // border-radius: 5px;
50 | // color: #ffba00;
51 | // background: #fff8e6;
52 | // border-color: #ffe399;
53 | // text-align: start;
54 | // text-indent: 10px;
55 | // line-height: 24px;
56 | // }
57 | // .ant-message-info {
58 | // min-width: 400px;
59 | // padding: 10px 16px;
60 | // border: 1px solid;
61 | // border-radius: 5px;
62 | // border-color: #ccc;
63 | // background-color: #edf2fb;
64 | // color: #a2a6ac;
65 | // text-align: start;
66 | // text-indent: 10px;
67 | // line-height: 24px;
68 | // }
69 | // }
70 | // }
--------------------------------------------------------------------------------
/src/styles/reset.less:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
49 | html, body, #root {
50 | width: 100%;
51 | height: 100%;
52 | }
53 | * {
54 | box-sizing: border-box;
55 | }
--------------------------------------------------------------------------------
/src/typings/accountsManage.d.ts:
--------------------------------------------------------------------------------
1 | import { ModalProps } from 'antd'
2 |
3 | export interface AccountRecord {
4 | accountsOrder: string,
5 | name: string,
6 | loginAccount: string,
7 | accountPassword: string,
8 | department: string[],
9 | accountStatus: number,
10 | phoneNumber: string,
11 | belongToRoles: string[],
12 | createTime: number,
13 | updateTime: number,
14 | description?: string
15 | }
16 |
17 | interface IAccountTable {
18 | toggleModalVisibleMethod: (visible: boolean, title?: string | undefined, record?: AccountRecord | undefined) => void
19 | tableList: AccountRecord[] | never[],
20 | pageTotal: number,
21 | changePage:(page: number, pageSize?: number | undefined) => void,
22 | tableLoading: boolean,
23 | accountQueryMethod: () => Promise
24 | paging: {pageNo: number,pageSize: number | undefined}
25 | }
26 |
27 | // 账号管理模态框
28 | export interface IAccountModal extends ModalProps {
29 | detail?: AccountRecord
30 | }
--------------------------------------------------------------------------------
/src/typings/breadcrumbItem.d.ts:
--------------------------------------------------------------------------------
1 | export interface BreadcrumbItem {
2 | menuNameId: string,
3 | menuDefaultName: string,
4 | icon?: string,
5 | path?: string
6 | }
--------------------------------------------------------------------------------
/src/typings/config.d.ts:
--------------------------------------------------------------------------------
1 | import { obj } from "typings/typings"
2 |
3 | type TLocale = 'zh_CN' | 'en_US'
4 | type TTheme = 'dark' | 'light' | undefined
5 |
6 | export interface TConfig extends obj{
7 | // 默认国际化语言配置
8 | locale: TLocale,
9 | // 是否有侧边菜单
10 | siderMenuIsHas?: boolean,
11 | // 改变宽度的时候,是否自动显示/隐藏侧边菜单
12 | autoHoldSiderIsShow?: boolean,
13 | // 侧边菜单的颜色
14 | siderMenuTheme?: TTheme,
15 | // 侧边菜单的是否收起
16 | siderMenuIsCollapsed?: boolean,
17 | // 侧边菜单是否有LOGO
18 | siderMenuIshHasLogo: boolean,
19 | // 是否有顶部菜单
20 | topMenuIsHas?: boolean,
21 | // 顶部菜单颜色
22 | topMenuTheme: TTheme,
23 | // 顶部菜单是否有logo
24 | topMenuIsHasLogo: boolean,
25 | // 是否有顶部面包屑
26 | breadCrumbIsHas?: boolean,
27 | // 是否有备案号
28 | isHasCopyright: boolean
29 | }
--------------------------------------------------------------------------------
/src/typings/rolesManage.d.ts:
--------------------------------------------------------------------------------
1 | import { ModalProps } from 'antd'
2 |
3 | export interface RolesRecord {
4 | rolesOrder: number,
5 | rolesName: string,
6 | authCharacter: string,
7 | rolesStatus: number,
8 | createBy: number,
9 | description?: string
10 | }
11 |
12 | // 角色管理Table
13 | export const titleMap: {[key:string]: string} = {
14 | 1: '创建角色',
15 | 2: '编辑角色',
16 | }
17 |
18 | interface IRolesTable {
19 | toggleModalVisibleMethod: (visible: boolean, title?: string | undefined, record?: RolesRecord | undefined) => void
20 | }
21 |
22 | // 角色管理模态框
23 | export interface IRolesModal extends ModalProps {
24 | rowList?: RolesRecord
25 | }
--------------------------------------------------------------------------------
/src/typings/router.d.ts:
--------------------------------------------------------------------------------
1 | export interface IRouter {
2 | Ppath: string | '',
3 | auth: boolean,
4 | icon: string,
5 | menuDefaultName: string,
6 | menuId: number,
7 | menuNameId: string,
8 | menuPId: number | '',
9 | menuType: number,
10 | path: string | '',
11 | [propName: string]: any
12 | }
--------------------------------------------------------------------------------
/src/typings/store.d.ts:
--------------------------------------------------------------------------------
1 | import { TConfig } from 'typings/config'
2 |
3 | export interface RootState {
4 | config: TConfig,
5 | lang: {[key:string]: string}
6 | }
7 |
8 | export interface IAction {
9 | type: string;
10 | payload?: any;
11 | }
12 |
--------------------------------------------------------------------------------
/src/typings/systemConfig.d.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export interface ISiderMenuConfig {
4 | label: string,
5 | value: string,
6 | type: string,
7 | render?: () => React.ReactElement
8 | }
--------------------------------------------------------------------------------
/src/typings/typings.d.ts:
--------------------------------------------------------------------------------
1 | export interface obj {
2 | [key: string]: any
3 | }
--------------------------------------------------------------------------------
/src/utils/globalConstantParams.ts:
--------------------------------------------------------------------------------
1 | // 常量层
2 | export const ZERO_TYPE: string = '0'
3 | export const FIRST_TYPE: string = '1'
4 | export const SECOND_TYPE: string = '2'
5 |
--------------------------------------------------------------------------------
/src/utils/public.ts:
--------------------------------------------------------------------------------
1 | import SparkMD5 from 'spark-md5'
2 |
3 | /**
4 | * 元素滚动到顶部
5 | * @param traget id 选择器
6 | */
7 | export const scrollIntoView = (traget: string) => {
8 | const tragetElem = document.querySelector(traget)
9 | tragetElem?.scrollTo({
10 | top: 0,
11 | behavior: 'smooth'
12 | })
13 | }
14 |
15 | /**
16 | * 字符串转 Unit8Array 的方法
17 | * @param str string
18 | * @returns {Uint8Array}
19 | */
20 | export const stringToUint8Array = (str: string) => {
21 | const arr = []
22 | for (let i = 0, j = str.length; i < j; ++i) {
23 | arr.push(str.charCodeAt(i))
24 | }
25 | return new Uint8Array(arr)
26 | }
27 |
28 | /**
29 | * @description 数据请求对数据异或加解密处理,不存在token的情况
30 | * OR 是否需要异或处理
31 | */
32 | export const encDecWithOutTK = (data: ArrayBuffer, OR?: boolean) => {
33 | if (data === null || data.byteLength === 0) {
34 | return data
35 | }
36 | const unit8 = new Uint8Array(data)
37 | if (OR) {
38 | // 使用密钥字节数组循环加密或解密
39 | const result = new Uint8Array(unit8.byteLength)
40 | for (let i = 0; i < unit8.byteLength; i++) {
41 | result[i] = unit8[i] ^ (i & 0x000000FF)
42 | }
43 | return result
44 | } else {
45 | return unit8
46 | }
47 | }
48 |
49 | /**
50 | * @description 数据请求对数据异或加解密处理,存在token的情况
51 | * data 原始数据
52 | * token 解密异或处理
53 | */
54 | export const encDecWithTK = (data: ArrayBuffer, dtToken?: string | null) => {
55 | if (data === null || data.byteLength === 0) {
56 | return data
57 | }
58 |
59 | const unit8 = new Uint8Array(data)
60 |
61 | // 使用密钥字节数组循环加密或解密
62 | if (dtToken) {
63 | const mdDtToken = SparkMD5.hash(SparkMD5.hash(dtToken))
64 | const key = stringToUint8Array(mdDtToken)
65 | const result = new Uint8Array(unit8.byteLength)
66 | for (let i = 0; i < unit8.byteLength; i++) {
67 | result[i] = (unit8[i] ^ key[i % key.byteLength])
68 | }
69 | return result
70 | } else {
71 | return unit8
72 | }
73 | }
74 |
75 | /**
76 | * 对文件进行MD5加密
77 | */
78 | export const fileToMD5 = (former: any): Promise => {
79 | return new Promise(function (resolve, reject) {
80 | var blobSlice = File.prototype.slice
81 | var file = former
82 | var chunkSize = 2097152 // Read in chunks of 2MB
83 | var chunks = Math.ceil(file.size / chunkSize)
84 | var currentChunk = 0
85 | var spark = new SparkMD5.ArrayBuffer()
86 | var fileReader = new FileReader()
87 |
88 | function loadNext() {
89 | var start = currentChunk * chunkSize
90 | var end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
91 |
92 | fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
93 | }
94 |
95 | fileReader.onload = function (e) {
96 | // @ts-ignore
97 | spark.append(e.target!.result) // Append array buffer
98 | currentChunk++
99 |
100 | if (currentChunk < chunks) {
101 | loadNext()
102 | } else {
103 | resolve(spark.end())
104 | }
105 | }
106 |
107 | fileReader.onerror = function () {
108 | reject(new Error('oops, something went wrong.'))
109 | }
110 |
111 | loadNext()
112 | })
113 | }
114 |
115 | // 下载excel文件
116 | export function downloadExcel(fileName: string, ret: Blob) {
117 | const a = document.createElement('a')
118 |
119 | const blob = new Blob([ret], { type: 'application/vnd.ms-excel' })
120 |
121 | const url = window.URL.createObjectURL(blob)
122 |
123 | a.setAttribute('href', url)
124 |
125 | const date = new Date()
126 | const h = date.getHours()
127 | const m = date.getMinutes()
128 | const s = date.getSeconds()
129 | let time = date.toLocaleDateString()
130 | time = time + ` ${h}_${m}_${s}`
131 | const reportName = fileName + '_' + time + '.xls'
132 |
133 | a.setAttribute('download', reportName)
134 |
135 | a.click()
136 | a.remove()
137 | }
138 |
139 | /**
140 | * 比较版本号
141 | * @param version1 版本号
142 | * @param version2 版本号
143 | * @description version1 > version2 返回1 ;version1 < version2 返回-1 ; 其他情况返回0
144 | * @returns
145 | */
146 | export const compareVersion = (version1:string, version2: string) => {
147 | const arr1 = version1.split('.')
148 | const arr2 = version2.split('.')
149 | const length1 = arr1.length
150 | const length2 = arr2.length
151 | const minlength = Math.min(length1, length2)
152 | let i = 0
153 | for (i; i < minlength; i++) {
154 | const a = parseInt(arr1[i])
155 | const b = parseInt(arr2[i])
156 | if (a > b) {
157 | return 1
158 | } else if (a < b) {
159 | return -1
160 | }
161 | }
162 | if (length1 > length2) {
163 | for (let j = i; j < length1; j++) {
164 | if (parseInt(arr1[j]) !== 0) {
165 | return 1
166 | }
167 | }
168 | return 0
169 | } else if (length1 < length2) {
170 | for (let j = i; j < length2; j++) {
171 | if (parseInt(arr2[j]) !== 0) {
172 | return -1
173 | }
174 | }
175 | return 0
176 | }
177 | return 0
178 | }
179 |
180 | /**
181 | * 返回min到max之间的一个随机整数
182 | * @param min 最小数
183 | * @param max 最大数
184 | * @returns
185 | * @description 包后不包前
186 | */
187 | export const minToMaxRandom = (min:number, max: number) => {
188 | return Math.floor(Math.random() * (max - min)) + min
189 | }
190 |
191 | /**
192 | * 返回一个十六进制的颜色,例如#FFFFFF
193 | * @returns
194 | */
195 | export const colorRandomTo16 = () => {
196 | var str = '#'
197 | for (var i = 1; i <= 6; i++) {
198 | str += minToMaxRandom(0, 15).toString(16)
199 | }
200 | return str
201 | }
202 |
203 | /**
204 | * 测算元素距离页面的距离
205 | * @param dom 测算元素距离页面的距离
206 | * @returns
207 | */
208 | export const getDistance = (dom: any) => {
209 | let totalLeft = 0
210 | let totalTop = 0
211 | do {
212 | totalLeft += dom.offsetLeft
213 | totalTop += dom.offsetTop
214 | // 下一次的dom节点就是本次dom节点的最近的有定位的父元素
215 | dom = dom.offsetParent
216 | } while (dom.nodeName !== 'BODY')
217 |
218 | return {
219 | left: totalLeft,
220 | top: totalTop
221 | }
222 | }
223 |
224 | /**
225 | * 封装一个函数,返回鼠标按键,要求:左0 中1 右2
226 | * @param e
227 | * @returns
228 | */
229 | export const getButton = (e: any) => {
230 | // 普通的函数
231 | if (e) {
232 | // 如果接到的e确实有值,说明e不是undefined,说明当前浏览器不是IE678
233 | return e.button
234 | } else {
235 | // 就是IE678
236 | // @ts-ignore
237 | switch (window.event.button) {
238 | case 1:
239 | return 0
240 | case 4:
241 | return 1
242 | case 2:
243 | return 2
244 | }
245 | }
246 | }
247 |
248 | // 区分是不是安卓/ios
249 | export const isIos = () => {
250 | const userAgent = window.navigator.userAgent
251 | if (/iphone/i.test(userAgent)) {
252 | return true
253 | } else {
254 | return false
255 | }
256 | }
257 | // 是不是使用微信内核浏览器
258 | export const isWxBrowser = () => {
259 | const userAgent = window.navigator.userAgent
260 | if (/MicroMessenger/i.test(userAgent)) {
261 | return true
262 | } else {
263 | return false
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "baseUrl": "./src"
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------