├── .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 | 3 | 4 | Group 21 5 | Created with Sketch. 6 | 7 | 8 | 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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /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 |
43 | {modalFormList && 44 | modalFormList.map((item: any, index: number) => { 45 | return ( 46 | 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 |
59 | 60 | { 61 | formList && formList.map((item: any, index:number) => { 62 | if (index < count) { 63 | return ( 64 | 65 | 73 | { 74 | item.render 75 | ? item.render 76 | : 81 | } 82 | 83 | 84 | ) 85 | } 86 | return null 87 | }) 88 | } 89 | { 90 | count % 2 === 0 91 | ? 92 | : null 93 | } 94 | 98 | 99 | 102 | 105 | { 106 | formList && formList.length > 4 107 | ? 115 | : null 116 | } 117 | 118 | 119 | 120 |
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 | 101 | {MenuIshHasLogo ? : null} 102 | {createMenu(menuList)} 103 | 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 | 40 | { 41 | LangMap && LangMap.map((item:ILangMap) => { 42 | return ( 43 | { reduxToggleLangMethod(item.value) }} 46 | style={{ 47 | minWidth: 140 48 | }} 49 | > 50 | 51 | {`${item.icon}`} 52 | {init(item.nameId)} 53 | 54 | 55 | ) 56 | }) 57 | } 58 | 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 | 25 | } >{init('page.header.personCenter')} 26 | } >{init('page.header.personConfig')} 27 | 28 | } onClick={logout}> 29 | {init('page.header.logOut')} 30 | 31 | 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 |
74 | 75 |
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: 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 |
76 | 81 |
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: } 31 | placeholder={init('page.login.usernameplaceholder')} 32 | size="large" 33 | /> 34 | 35 | 36 |
37 | 41 | } 43 | type="password" 44 | placeholder={init('page.login.usernameplaceholder')} 45 | size="large" 46 | /> 47 | 48 |
49 |
50 | 51 | 54 | 55 |
56 | 57 | 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 |
27 | 28 |
29 | 33 | } 35 | placeholder={init('page.login.phonenumplaceholder')} 36 | size="large" 37 | /> 38 | 39 |
40 |
41 | 45 | 46 | } 48 | placeholder={init('page.login.vcodeplaceholder')} 49 | size="large" 50 | /> 51 | 74 | 75 | 76 |
77 |
78 | 79 | 82 | 83 |
84 |
85 | 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 | --------------------------------------------------------------------------------