├── .babelrc ├── .editorconfig ├── .eslintcache ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .prettierignore ├── .prettierrc.js ├── .stylelintrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── commitlint.config.js ├── mock └── test.json ├── package.json ├── public ├── nowthen.jpg └── 演示.gif └── src ├── assets └── images │ ├── login_bg.jpg │ ├── nowthen.jpg │ └── welcome.jpg ├── components └── LoadingPage │ └── index.js ├── index.html ├── layouts ├── BasicLayout │ ├── index.js │ └── style.less ├── BlankLayout │ └── index.js ├── MainFooter │ └── index.js ├── MainHeader │ ├── index.js │ └── style.less └── SiderMenu │ ├── index.js │ └── style.less ├── main.js ├── pages ├── Exception │ ├── 403.js │ ├── 404.js │ └── 500.js ├── FormDemo │ ├── SearchForm.js │ ├── index.js │ ├── newModal.js │ ├── store.js │ └── style.less ├── Home │ ├── index.js │ ├── store.js │ └── style.less ├── Login │ ├── index.js │ └── style.less ├── System │ ├── GroovySet │ │ ├── index.js │ │ ├── newModal.js │ │ ├── store.js │ │ └── style.less │ ├── Star │ │ └── index.js │ └── User │ │ └── index.js └── Welcome │ ├── index.js │ └── style.less ├── routers ├── AppRouter.js └── config.js ├── services └── newRequest.js ├── stores ├── commonStore.js ├── globalStore.js └── index.js ├── styles ├── main.less └── resetAntd.less └── utils ├── constant.js ├── index.js └── renderRoutes.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/transform-runtime", 8 | [ 9 | "@babel/plugin-proposal-decorators", 10 | { 11 | "legacy": true 12 | } 13 | ], 14 | ["@babel/plugin-proposal-class-properties", { "loose": true }], 15 | [ 16 | "import", 17 | { 18 | "libraryName": "antd", 19 | "libraryDirectory": "es", 20 | "style": "css" // `style: true` 会加载 less 文件 21 | } 22 | ] 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,py}] 4 | charset = utf-8 5 | 6 | [*.{js,jsx,ts,tsx,vue}] 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/Users/nowthen/work/local/template/react-web-pro/src/components/LoadingPage/index.js":"1","/Users/nowthen/work/local/template/react-web-pro/src/layouts/BasicLayout/index.js":"2","/Users/nowthen/work/local/template/react-web-pro/src/layouts/BlankLayout/index.js":"3","/Users/nowthen/work/local/template/react-web-pro/src/layouts/MainFooter/index.js":"4","/Users/nowthen/work/local/template/react-web-pro/src/layouts/MainHeader/index.js":"5","/Users/nowthen/work/local/template/react-web-pro/src/layouts/SiderMenu/index.js":"6","/Users/nowthen/work/local/template/react-web-pro/src/main.js":"7","/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/403.js":"8","/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/404.js":"9","/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/500.js":"10","/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/SearchForm.js":"11","/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/index.js":"12","/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/newModal.js":"13","/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/store.js":"14","/Users/nowthen/work/local/template/react-web-pro/src/pages/Home/index.js":"15","/Users/nowthen/work/local/template/react-web-pro/src/pages/Home/store.js":"16","/Users/nowthen/work/local/template/react-web-pro/src/pages/Login/index.js":"17","/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/index.js":"18","/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/newModal.js":"19","/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/store.js":"20","/Users/nowthen/work/local/template/react-web-pro/src/pages/System/Star/index.js":"21","/Users/nowthen/work/local/template/react-web-pro/src/pages/System/User/index.js":"22","/Users/nowthen/work/local/template/react-web-pro/src/pages/Welcome/index.js":"23","/Users/nowthen/work/local/template/react-web-pro/src/routers/AppRouter.js":"24","/Users/nowthen/work/local/template/react-web-pro/src/routers/config.js":"25","/Users/nowthen/work/local/template/react-web-pro/src/services/newRequest.js":"26","/Users/nowthen/work/local/template/react-web-pro/src/stores/commonStore.js":"27","/Users/nowthen/work/local/template/react-web-pro/src/stores/globalStore.js":"28","/Users/nowthen/work/local/template/react-web-pro/src/stores/index.js":"29","/Users/nowthen/work/local/template/react-web-pro/src/utils/constant.js":"30","/Users/nowthen/work/local/template/react-web-pro/src/utils/index.js":"31","/Users/nowthen/work/local/template/react-web-pro/src/utils/renderRoutes.js":"32"},{"size":253,"mtime":1607510199692,"results":"33","hashOfConfig":"34"},{"size":624,"mtime":1607591634307,"results":"35","hashOfConfig":"34"},{"size":112,"mtime":1607510199693,"results":"36","hashOfConfig":"34"},{"size":151,"mtime":1607591634308,"results":"37","hashOfConfig":"34"},{"size":1416,"mtime":1607591634308,"results":"38","hashOfConfig":"34"},{"size":2755,"mtime":1607592817417,"results":"39","hashOfConfig":"34"},{"size":585,"mtime":1607591634309,"results":"40","hashOfConfig":"34"},{"size":339,"mtime":1607591634310,"results":"41","hashOfConfig":"34"},{"size":342,"mtime":1607591634310,"results":"42","hashOfConfig":"34"},{"size":333,"mtime":1607591634311,"results":"43","hashOfConfig":"34"},{"size":3042,"mtime":1607591634311,"results":"44","hashOfConfig":"34"},{"size":2524,"mtime":1607592893293,"results":"45","hashOfConfig":"34"},{"size":2182,"mtime":1607591634312,"results":"46","hashOfConfig":"34"},{"size":3635,"mtime":1607591634312,"results":"47","hashOfConfig":"34"},{"size":777,"mtime":1607591634312,"results":"48","hashOfConfig":"34"},{"size":955,"mtime":1607592893356,"results":"49","hashOfConfig":"34"},{"size":1667,"mtime":1607591634313,"results":"50","hashOfConfig":"34"},{"size":2708,"mtime":1607591634314,"results":"51","hashOfConfig":"34"},{"size":4607,"mtime":1607591634315,"results":"52","hashOfConfig":"34"},{"size":4204,"mtime":1607591634315,"results":"53","hashOfConfig":"34"},{"size":66,"mtime":1607592840967,"results":"54","hashOfConfig":"34"},{"size":66,"mtime":1607592840967,"results":"55","hashOfConfig":"34"},{"size":141,"mtime":1607591634316,"results":"56","hashOfConfig":"34"},{"size":1393,"mtime":1607591634317,"results":"57","hashOfConfig":"34"},{"size":3190,"mtime":1607591634317,"results":"58","hashOfConfig":"34"},{"size":4326,"mtime":1607592840967,"results":"59","hashOfConfig":"34"},{"size":104,"mtime":1607510199698,"results":"60","hashOfConfig":"34"},{"size":519,"mtime":1607591634318,"results":"61","hashOfConfig":"34"},{"size":382,"mtime":1607591634318,"results":"62","hashOfConfig":"34"},{"size":978,"mtime":1607510199699,"results":"63","hashOfConfig":"34"},{"size":2390,"mtime":1607593154951,"results":"64","hashOfConfig":"34"},{"size":669,"mtime":1607592840967,"results":"65","hashOfConfig":"34"},{"filePath":"66","messages":"67","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1kap956",{"filePath":"68","messages":"69","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"70","messages":"71","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"72","messages":"73","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"74","messages":"75","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"76","messages":"77","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"78","messages":"79","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"80","messages":"81","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"82","messages":"83","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"84","messages":"85","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"86","messages":"87","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"92","messages":"93","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"94","messages":"95","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"96","messages":"97","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"98","messages":"99","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"102","messages":"103","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"104","messages":"105","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"106","messages":"107","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"108","messages":"109","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"110","messages":"111","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"112","messages":"113","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"114","messages":"115","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"116","messages":"117","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"118","messages":"119","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"120","messages":"121","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"122","messages":"123","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"124","messages":"125","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"126","messages":"127","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"128","messages":"129","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/nowthen/work/local/template/react-web-pro/src/components/LoadingPage/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/layouts/BasicLayout/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/layouts/BlankLayout/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/layouts/MainFooter/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/layouts/MainHeader/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/layouts/SiderMenu/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/main.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/403.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/404.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Exception/500.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/SearchForm.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/newModal.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/FormDemo/store.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Home/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Home/store.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Login/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/newModal.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/System/GroovySet/store.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/System/Star/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/System/User/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/pages/Welcome/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/routers/AppRouter.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/routers/config.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/services/newRequest.js",["130","131","132","133"],"/Users/nowthen/work/local/template/react-web-pro/src/stores/commonStore.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/stores/globalStore.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/stores/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/utils/constant.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/utils/index.js",[],"/Users/nowthen/work/local/template/react-web-pro/src/utils/renderRoutes.js",[],{"ruleId":"134","severity":1,"message":"135","line":21,"column":5,"nodeType":"136","messageId":"137","endLine":21,"endColumn":14},{"ruleId":"134","severity":1,"message":"135","line":28,"column":5,"nodeType":"136","messageId":"137","endLine":28,"endColumn":14},{"ruleId":"138","severity":1,"message":"139","line":110,"column":14,"nodeType":"140","messageId":"141","endLine":110,"endColumn":45},{"ruleId":"138","severity":1,"message":"139","line":112,"column":14,"nodeType":"140","messageId":"141","endLine":112,"endColumn":32},"func-names","Unexpected unnamed function.","FunctionExpression","unnamed","prefer-promise-reject-errors","Expected the Promise rejection reason to be an Error.","CallExpression","rejectAnError"] -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb', 'prettier'], 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true, 7 | jquery: true, 8 | }, 9 | parser: 'babel-eslint', 10 | plugins: ['react', 'babel'], 11 | rules: { 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'warning' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warning' : 'off', 14 | 'prefer-destructuring': 0, 15 | 'prefer-promise-reject-errors': 1, 16 | 'no-else-return': 0, 17 | 'import/no-extraneous-dependencies': 0, 18 | 'import/no-unresolved': [2, { ignore: ['^@/'] }], 19 | 'react/jsx-props-no-spreading': 0, 20 | 'react/jsx-one-expression-per-line': 0, 21 | 'react/prop-types': 0, 22 | 'react/forbid-prop-types': 0, 23 | 'react/jsx-indent': 0, 24 | 'react/jsx-wrap-multilines': [ 25 | 'error', 26 | { 27 | declaration: false, 28 | assignment: false, 29 | }, 30 | ], 31 | 'react/jsx-filename-extension': [ 32 | 1, 33 | { 34 | extensions: ['.js', '.jsx'], 35 | }, 36 | ], 37 | 'jsx-a11y/no-static-element-interactions': 0, 38 | 'jsx-a11y/anchor-has-content': 0, 39 | 'jsx-a11y/click-events-have-key-events': 0, 40 | 'jsx-a11y/anchor-is-valid': 0, 41 | // 'comma-dangle': ['error', 'always-multiline'], 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | yarn.lock 10 | package-lock.json 11 | 12 | .cache 13 | .DS_store 14 | .idea/ 15 | .history 16 | .vscode 17 | 18 | # Output of 'npm pack' 19 | *.tgz 20 | 21 | .env 22 | .env.test 23 | 24 | .npm 25 | 26 | node_modules 27 | dist 28 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | package.json 3 | .umi 4 | .umi-production 5 | lib/ 6 | es/ 7 | dist/ 8 | coverage/ 9 | LICENSE 10 | yarn.lock 11 | yarn-error.log 12 | *.sh 13 | .gitignore 14 | .npmignore 15 | .prettierignore 16 | .DS_Store 17 | .editorconfig 18 | .eslintignore 19 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | printWidth: 120, 5 | // overrides: [ 6 | // { 7 | // files: '.prettierrc', 8 | // options: { 9 | // parser: 'json', 10 | // }, 11 | // }, 12 | // ], 13 | }; 14 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "declaration-empty-line-before": null, 5 | "no-descending-specificity": null, 6 | "selector-pseudo-class-no-unknown": null, 7 | "selector-pseudo-element-colon-notation": null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2 | ## V2.0.0 3 | `2020-12-12` 4 | 5 | * 升级项目中全部依赖库版本,主要包括:react17、antd4、webpack5... 6 | * 增加 commitlint 规范git提交; 7 | * 增加 husky + lint-staged + Prettier 规范代码提交校验 8 | * 优化 eslint + prettier 规范项目代码风格 9 | 10 | 11 | ## V1.0.0 12 | 第一版 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nowThen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [react-web-pro](https://github.com/now1then/react-web-pro) 2 | 3 | 🌈 🚀 基于 webpack4.0 从零搭建的 React 中后台项目框架模板。🚀 4 | 5 | [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/now1then/react-web-pro/master/LICENSE) 6 | 7 | [![webpack](https://img.shields.io/badge/webpack-5.10.0-green)](https://github.com/webpack/webpack) [![react](https://img.shields.io/badge/react-17.0.1-green)](https://github.com/facebook/react) [![react-router](https://img.shields.io/badge/react--router-5.2.0-green)](https://github.com/ReactTraining/react-router) [![axios](https://img.shields.io/badge/axios-0.21.0-green)](https://github.com/axios/axios) [![ant-design](https://img.shields.io/badge/ant--design-4.9.2-green.svg)](https://ant.design/index-cn) [![mobx](https://img.shields.io/badge/mobx-5.15.0-green.svg)](https://github.com/mobxjs/mobx) 8 | 9 | ## 说明 10 | 11 | 本项目为 React 中后台项目框架模板,方便快速进行具体项目开发。包括 Webpack4.0 配置及打包优化、React 全家桶使用(React + React-router + Axios + Mobx + Antd)、ESLint 等项目开发规范等。 12 | 13 | 项目 Git 地址:[https://github.com/now1then/react-web-pro](https://github.com/now1then/react-web-pro); 14 | 15 | 项目介绍文章链接-语雀:[https://www.yuque.com/nowthen/longroad/exeuw7](https://www.yuque.com/nowthen/longroad/exeuw7); 16 | 17 | 在线演示地址: 18 | 19 | 项目页面演示: 20 | ![演示gif](/public/演示.gif) 21 | 22 | ### 技术栈 23 | 24 | 涉及的技术栈均采用当前最新的版本和语法: 25 | 26 | - 使用 Webpack5.0 构建项目(不使用 create-react-app、umi 等脚手架); 27 | - 使用 Babel7 配置转换 ES6、React、Mobx 等语法; 28 | - React 版本 V17.0.1,全部采用函数化 Hooks 特性开发项目组件; 29 | - 采用 React-router5 工具 配置项目路由; 30 | - 采用 Mobx5 + Hooks 实现项目数据状态管理; 31 | - 封装 Axios 库实现与后台 http 请求交互; 32 | - UI 库采用流行的 Ant-design4.0 组件库; 33 | - 完整项目实现及模块结构拆分; 34 | 35 | ### 目录结构 36 | 37 | ``` 38 | ├── build // webpack配置 39 | │   ├── webpack.common.js // webpack通用配置 40 | │   ├── webpack.dev.js // webpack开发环境配置 41 | │   └── webpack.prod.js // webpack生产环境配置 42 | ├── dist // 打包输出目录 43 | ├── public // 项目公开目录 44 | ├── src // src开发目录 45 | │   ├── assets // 静态资源 46 | │   ├── components // 公共组件 47 | │   ├── layouts // 页面布局组件 48 | │   ├── modules // 公共业务模块 49 | │   ├── pages // 具体业务页面 50 | │   ├── routers // 项目路由配置 51 | │   ├── services // axios服务等相关 52 | │   ├── stores // 全局公共 mobx store 53 | │   ├── styles // 存放公共样式 54 | │   ├── utils // 工具库/通用函数 55 | │   ├── index.html // 入口html页面 56 | │   └── main.js // 项目入口文件 57 | ├── .babelrc // babel配置 58 | ├── .editorconfig // 项目格式配置 59 | ├── .eslintrc.js // ESLint配置 60 | ├── .gitignore // git 忽略配置 61 | ├── .postcssrc.js // postcss配置 62 | ├── package.json // 依赖包配置 63 | └── README.md // 项目说明 64 | ``` 65 | 66 | ## CLI 构建命令 67 | 68 | ### 克隆项目 69 | 70 | ```bash 71 | git clone git@github.com:now1then/react-web-pro.git 72 | ``` 73 | 74 | ### 初始化依赖配置 75 | 76 | ```bash 77 | yarn install 78 | ``` 79 | 80 | ### 开发环境 启动运行 81 | 82 | ```bash 83 | yarn start 84 | ``` 85 | 86 | ### 生产环境 打包构建 87 | 88 | ```bash 89 | yarn build //生产环境 打包构建 90 | 91 | yarn build:report // 图形化分析打包文件大小; 92 | 93 | yarn build:watch // 方便排查生产环境打包后文件的错误信息(文件source map); 94 | ``` 95 | 96 | ## More 97 | -------------------------------------------------------------------------------- /build/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin'); 6 | const os = require('os'); 7 | const HappyPack = require('happypack'); 8 | 9 | const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 10 | 11 | const srcDir = path.join(__dirname, '../src'); 12 | const devMode = process.env.NODE_ENV !== 'production'; 13 | 14 | module.exports = { 15 | entry: { 16 | main: path.join(__dirname, '../src/main.js'), 17 | }, 18 | output: { 19 | path: path.join(__dirname, '../dist'), 20 | filename: '[name].[chunkhash:8].js', 21 | // publicPath: "/", 22 | chunkFilename: 'chunk/[name].[chunkhash:8].js', 23 | }, 24 | module: { 25 | rules: [ 26 | // { 27 | // test: /\.(js|jsx)$/, 28 | // include: [srcDir], 29 | // loader: 'eslint-loader', 30 | // enforce: 'pre', 31 | // options: { 32 | // fix: true, 33 | // }, 34 | // }, 35 | { 36 | test: /\.(js|jsx)$/, 37 | include: [srcDir], 38 | exclude: /(node_modules|bower_components)/, 39 | use: ['happypack/loader?id=happybabel'], 40 | }, 41 | { 42 | test: /\.less$/, 43 | use: [ 44 | devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 45 | 'css-loader', 46 | 'postcss-loader', 47 | 'less-loader', 48 | ], 49 | }, 50 | { 51 | test: /\.css$/, 52 | use: [ 53 | devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 54 | 'css-loader', 55 | 'postcss-loader', 56 | ], 57 | }, 58 | { 59 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 60 | use: ['url-loader'], 61 | include: [srcDir], 62 | }, 63 | { 64 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 65 | use: ['url-loader'], 66 | include: [srcDir], 67 | }, 68 | { 69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 70 | use: ['url-loader'], 71 | include: [srcDir], 72 | }, 73 | ], 74 | }, 75 | plugins: [ 76 | // 开启 happypack 的线程池 77 | new HappyPack({ 78 | id: 'happybabel', 79 | loaders: ['babel-loader?cacheDirectory=true'], 80 | threadPool: happyThreadPool, 81 | // cache: true, 82 | verbose: true, 83 | }), 84 | new HtmlWebpackPlugin({ 85 | template: `${srcDir}/index.html`, 86 | }), 87 | new CopyWebpackPlugin({ 88 | patterns: [ 89 | { 90 | from: `${srcDir}/assets/images/nowthen.jpg`, 91 | to: 'nowthen.jpg', 92 | }, 93 | ], 94 | }), 95 | new AntdDayjsWebpackPlugin() 96 | ], 97 | resolve: { 98 | alias: { 99 | '@': srcDir, 100 | '@pages': `${srcDir}/pages`, 101 | }, 102 | }, 103 | // optimization: { 104 | // removeAvailableModules: true, // 删除已解决的chunk (默认 true) 105 | // removeEmptyChunks: true, // 删除空的chunks (默认 true) 106 | // mergeDuplicateChunks: true // 合并重复的chunk (默认 true) 107 | // } 108 | }; 109 | -------------------------------------------------------------------------------- /build/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const {merge} = require('webpack-merge'); 3 | // const path = require('path'); 4 | 5 | const commonConfig = require('./webpack.common'); 6 | 7 | module.exports = merge(commonConfig, { 8 | mode: 'development', 9 | // 开发环境本地启动的服务配置 10 | devServer: { 11 | port: 9001, 12 | hot: true, 13 | open: true, 14 | historyApiFallback: true, 15 | compress: true, 16 | // 接口代理转发 17 | proxy: { 18 | '/testapi': { 19 | target: 'https://www.easy-mock.com/mock/5dff0acd5b188e66c6e07329/react-template', 20 | changeOrigin: true, 21 | secure: false, 22 | pathRewrite: { '^/testapi': '' }, 23 | }, 24 | }, 25 | }, 26 | plugins: [ new webpack.HotModuleReplacementPlugin()], 27 | devtool: 'eval-source-map', 28 | // optimization: { 29 | // moduleIds: 'named', 30 | // }, 31 | }); 32 | -------------------------------------------------------------------------------- /build/webpack.prod.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: nowthen 4 | * @Date: 2020-12-09 18:36:39 5 | * @LastEditors: nowthen 6 | * @LastEditTime: 2020-12-10 09:56:20 7 | * @FilePath: /react-web-pro/build/webpack.prod.js 8 | */ 9 | const webpack = require('webpack'); 10 | const { merge } = require('webpack-merge'); 11 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 12 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 13 | const WebpackBundleAnalyzer = require('webpack-bundle-analyzer'); 14 | 15 | const commonConfig = require('./webpack.common'); 16 | 17 | let config = merge(commonConfig, { 18 | mode: 'production', 19 | plugins: [ 20 | new CleanWebpackPlugin(), 21 | new MiniCssExtractPlugin({ 22 | filename: '[name].[contenthash:8].css', 23 | chunkFilename: 'chunk/[id].[contenthash:8].css', 24 | }), 25 | new webpack.ids.HashedModuleIdsPlugin(), 26 | ], 27 | // optimization: { 28 | // splitChunks: { 29 | // cacheGroups: { 30 | // commons: { 31 | // name: "commons", 32 | // chunks: "initial", 33 | // minChunks: 2 34 | // } 35 | // } 36 | // } 37 | // } 38 | performance: { 39 | maxEntrypointSize: 400000, 40 | maxAssetSize: 800000, 41 | }, 42 | optimization: { 43 | runtimeChunk: { 44 | name: 'manifest', 45 | }, 46 | splitChunks: { 47 | chunks: 'all', // 默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效 48 | cacheGroups: { 49 | dll: { 50 | test: /[\\/]node_modules[\\/](react|react-dom|react-dom-router|babel-polyfill|mobx|mobx-react|mobx-react-dom|antd|@ant-design)/, 51 | minChunks: 1, 52 | priority: 2, 53 | name: 'dll', 54 | }, 55 | codeMirror: { 56 | test: /[\\/]node_modules[\\/](react-codemirror|codemirror)/, 57 | minChunks: 1, 58 | priority: 2, 59 | name: 'codemirror', 60 | }, 61 | vendors: { 62 | test: /[\\/]node_modules[\\/]/, 63 | minChunks: 1, 64 | priority: 1, 65 | name: 'vendors', 66 | }, 67 | }, 68 | }, 69 | }, 70 | }); 71 | 72 | if (process.env.npm_lifecycle_event === 'build:watch') { 73 | config = merge(config, { 74 | devtool: 'cheap-source-map', 75 | }); 76 | } 77 | if (process.env.npm_lifecycle_event === 'build:report') { 78 | const BundleAnalyzerPlugin = WebpackBundleAnalyzer.BundleAnalyzerPlugin; 79 | config.plugins.push(new BundleAnalyzerPlugin()); 80 | } 81 | 82 | module.exports = config; 83 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | [ 8 | "feat", 9 | "fix", 10 | "docs", 11 | "style", 12 | "refactor", 13 | "test", 14 | "chore", 15 | "revert", 16 | "perf", 17 | "merge" 18 | ] 19 | ], 20 | "subject-full-stop": [0, "never"], 21 | "subject-case": [0, "never"] 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /mock/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "haha": 1 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-web-pro", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": { 6 | "name": "nowthen", 7 | "email": "rnlvwyx@gmail.com" 8 | }, 9 | "description": "🌈 🚀基于webpack4.0 从零搭建的React中后台项目框架模板。🚀", 10 | "keywords": [ 11 | "react-web-pro", 12 | "react", 13 | "mobx", 14 | "webpack4", 15 | "react-template", 16 | "nowthen" 17 | ], 18 | "license": "MIT", 19 | "scripts": { 20 | "start": "webpack serve --color --progress --config build/webpack.dev.js", 21 | "build": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js", 22 | "build:watch": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js", 23 | "build:report": "yarn build:watch", 24 | "prettier": "prettier --write 'src/**/*.{js,jsx,tsx,ts,less,md,json}'", 25 | "lint:style": "stylelint \"src/**/*.less\" --syntax less", 26 | "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx ./src", 27 | "lint:fix": "yarn lint:js --fix" 28 | }, 29 | "husky": { 30 | "hooks": { 31 | "pre-commit": "lint-staged", 32 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 33 | } 34 | }, 35 | "lint-staged": { 36 | "gitDir": "../", 37 | "**/*.{less,md,json}": [ 38 | "prettier" 39 | ], 40 | "**/*.{js,jsx,ts,tsx}": [ 41 | "prettier", 42 | "yarn run lint:js" 43 | ] 44 | }, 45 | "dependencies": { 46 | "@ant-design/icons": "^4.3.0", 47 | "antd": "^4.9.2", 48 | "axios": "^0.21.0", 49 | "dayjs": "^1.9.7", 50 | "mobx": "^5.15.0", 51 | "mobx-react": "^6.1.4", 52 | "mobx-react-router": "^4.1.0", 53 | "react": "^17.0.1", 54 | "react-codemirror": "^1.0.0", 55 | "react-dom": "^17.0.1", 56 | "react-router": "^5.2.0", 57 | "react-router-dom": "^5.2.0" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.12.9", 61 | "@babel/plugin-proposal-class-properties": "^7.12.1", 62 | "@babel/plugin-proposal-decorators": "^7.12.1", 63 | "@babel/plugin-transform-runtime": "^7.12.1", 64 | "@babel/preset-env": "^7.15.6", 65 | "@babel/preset-react": "^7.14.5", 66 | "@commitlint/cli": "^11.0.0", 67 | "@commitlint/config-conventional": "^11.0.0", 68 | "antd-dayjs-webpack-plugin": "^1.0.1", 69 | "autoprefixer": "^10.1.0", 70 | "babel-eslint": "^10.1.0", 71 | "babel-loader": "^8.2.2", 72 | "babel-plugin-import": "^1.13.3", 73 | "clean-webpack-plugin": "^3.0.0", 74 | "copy-webpack-plugin": "^6.4.0", 75 | "core-js": "^3.8.1", 76 | "cross-env": "^7.0.3", 77 | "css-loader": "^5.0.1", 78 | "eslint": "^7.15.0", 79 | "eslint-config-airbnb": "^18.2.1", 80 | "eslint-config-prettier": "^7.0.0", 81 | "eslint-loader": "^4.0.2", 82 | "eslint-plugin-babel": "^5.3.1", 83 | "eslint-plugin-import": "^2.22.1", 84 | "eslint-plugin-jsx-a11y": "^6.4.1", 85 | "eslint-plugin-react": "^7.21.5", 86 | "eslint-plugin-react-hooks": "^4.2.0", 87 | "happypack": "^5.0.1", 88 | "html-webpack-plugin": "^4.5.0", 89 | "husky": "^4.3.5", 90 | "less": "^3.12.2", 91 | "less-loader": "^7.1.0", 92 | "lint-staged": "^10.5.3", 93 | "mini-css-extract-plugin": "^1.3.2", 94 | "postcss": "^8.2.0", 95 | "postcss-loader": "^4.1.0", 96 | "prettier": "^2.2.1", 97 | "style-loader": "^2.0.0", 98 | "stylelint": "^13.8.0", 99 | "stylelint-config-prettier": "^8.0.2", 100 | "stylelint-config-standard": "^20.0.0", 101 | "url-loader": "^4.1.1", 102 | "webpack": "^5.53.0", 103 | "webpack-bundle-analyzer": "^4.4.2", 104 | "webpack-cli": "^4.8.0", 105 | "webpack-dev-middleware": "^5.1.0", 106 | "webpack-dev-server": "^4.2.1", 107 | "webpack-merge": "^5.8.0" 108 | }, 109 | "browserslist": [ 110 | "> 1%", 111 | "last 2 versions", 112 | "not ie <= 10" 113 | ], 114 | "engines": { 115 | "node": ">=10.0.0" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /public/nowthen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/now1then/react-web-pro/4fa89a78888e3b89f6ca5af13d8f914c1d748497/public/nowthen.jpg -------------------------------------------------------------------------------- /public/演示.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/now1then/react-web-pro/4fa89a78888e3b89f6ca5af13d8f914c1d748497/public/演示.gif -------------------------------------------------------------------------------- /src/assets/images/login_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/now1then/react-web-pro/4fa89a78888e3b89f6ca5af13d8f914c1d748497/src/assets/images/login_bg.jpg -------------------------------------------------------------------------------- /src/assets/images/nowthen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/now1then/react-web-pro/4fa89a78888e3b89f6ca5af13d8f914c1d748497/src/assets/images/nowthen.jpg -------------------------------------------------------------------------------- /src/assets/images/welcome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/now1then/react-web-pro/4fa89a78888e3b89f6ca5af13d8f914c1d748497/src/assets/images/welcome.jpg -------------------------------------------------------------------------------- /src/components/LoadingPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | const LoadingPage = () => ( 5 |
6 | 7 |
8 | ); 9 | 10 | export default LoadingPage; 11 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 管理平台 9 | 10 | 11 | 12 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout } from 'antd'; 3 | import SiderMenu from '../SiderMenu'; 4 | import MainHeader from '../MainHeader'; 5 | // import MainFooter from "../MainFooter"; 6 | 7 | import './style.less'; 8 | 9 | const BasicLayout = ({ route, children }) => ( 10 | 11 | 12 | {/* 左侧菜单导航 */} 13 | 14 | 15 | 16 | {children} 17 | {/* */} 18 | 19 | 20 | 21 | ); 22 | 23 | export default BasicLayout; 24 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout/style.less: -------------------------------------------------------------------------------- 1 | .main-layout { 2 | height: 100vh; 3 | .main-layout-right { 4 | height: 100vh; 5 | } 6 | .main-layout-content { 7 | height: calc(100% - 64px); 8 | overflow: auto; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BlankLayout = ({ children }) => <>{children}; 4 | 5 | export default BlankLayout; 6 | -------------------------------------------------------------------------------- /src/layouts/MainFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout } from 'antd'; 3 | 4 | const MainFooter = () => 底部; 5 | 6 | export default MainFooter; 7 | -------------------------------------------------------------------------------- /src/layouts/MainHeader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout, Dropdown, Menu, Row, Col } from 'antd'; 3 | import { SmileOutlined, LogoutOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; 4 | import { Link } from 'react-router-dom'; 5 | import { observer } from 'mobx-react'; 6 | 7 | import { appStores } from '@/stores'; 8 | import './style.less'; 9 | 10 | const menu = ( 11 | 12 | 13 | 14 | 个人信息 15 | 16 | 17 | 18 | 19 | 20 |   退出登录 21 | 22 | 23 | 24 | ); 25 | 26 | const MainHeader = () => { 27 | const { globalStore } = appStores(); 28 | return ( 29 | 30 | 31 | 32 | 33 | {globalStore.collapsed ? : } 34 | 35 | 36 | 37 | 38 |
39 | 40 | {globalStore.userInfo.loginName} 41 |
42 |
43 | 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default observer(MainHeader); 50 | -------------------------------------------------------------------------------- /src/layouts/MainHeader/style.less: -------------------------------------------------------------------------------- 1 | .main-header { 2 | background: #fff; 3 | padding: 0; 4 | z-index: 100; 5 | box-shadow: 0 1px 4px 0 rgba(0, 21, 41, 0.12); 6 | .trigger { 7 | font-size: 18px; 8 | line-height: 64px; 9 | padding: 0 16px; 10 | cursor: pointer; 11 | transition: color 0.3s; 12 | 13 | &:hover { 14 | color: #1890ff; 15 | } 16 | } 17 | 18 | .user-info { 19 | cursor: pointer; 20 | padding: 0 10px; 21 | &:hover { 22 | color: #1890ff; 23 | background: rgba(0, 0, 0, 0.045); 24 | } 25 | .user-img { 26 | vertical-align: middle; 27 | height: 32px; 28 | width: 32px; 29 | border-radius: 32px; 30 | border: 1px solid rgb(221, 221, 221); 31 | background: url('~@/assets/images/nowthen.jpg') no-repeat; 32 | background-size: cover; 33 | display: inline-block; 34 | } 35 | .user-name { 36 | margin-left: 10px; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/layouts/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: nowthen 4 | * @Date: 2020-12-09 18:36:39 5 | * @LastEditors: nowthen 6 | * @LastEditTime: 2020-12-10 17:33:37 7 | * @FilePath: /react-web-pro/src/layouts/SiderMenu/index.js 8 | */ 9 | import React, { useEffect, useState, useMemo } from 'react'; 10 | import { Link, useLocation } from 'react-router-dom'; 11 | import { observer } from 'mobx-react'; 12 | import { Layout, Menu, Row } from 'antd'; 13 | import { RocketTwoTone } from '@ant-design/icons'; 14 | 15 | import { appStores } from '@/stores'; 16 | import './style.less'; 17 | 18 | const renderMenuItem = (target) => 19 | target 20 | .filter((item) => item.path && item.name) 21 | .map((subMenu) => { 22 | if (subMenu.childRoutes && !!subMenu.childRoutes.find((child) => child.path && child.name)) { 23 | return ( 24 | 28 | {!!subMenu.icon && subMenu.icon} 29 | {subMenu.name} 30 | 31 | } 32 | > 33 | {renderMenuItem(subMenu.childRoutes)} 34 | 35 | ); 36 | } 37 | return ( 38 | 39 | 40 | 41 | {!!subMenu.icon && subMenu.icon} 42 | {subMenu.name} 43 | 44 | 45 | 46 | ); 47 | }); 48 | 49 | const SiderMenu = ({ routes }) => { 50 | const { pathname } = useLocation(); 51 | // console.log(pathname); 52 | const { globalStore } = appStores(); 53 | const [openKeys, setOpenKeys] = useState([]); 54 | 55 | useEffect(() => { 56 | const list = pathname.split('/').splice(1); 57 | setOpenKeys(list.map((item, index) => `/${list.slice(0, index + 1).join('/')}`)); 58 | }, []); 59 | 60 | const getSelectedKeys = useMemo(() => { 61 | const list = pathname.split('/').splice(1); 62 | return list.map((item, index) => `/${list.slice(0, index + 1).join('/')}`); 63 | }, [pathname]); 64 | 65 | const onOpenChange = (keys) => { 66 | setOpenKeys(keys); 67 | }; 68 | 69 | return ( 70 | 71 | 72 | 73 | {/* */} 74 | 75 | {!globalStore.collapsed && {globalStore.appTitle}} 76 | 77 | 78 | 87 | {renderMenuItem(routes)} 88 | 89 | 90 | ); 91 | }; 92 | 93 | export default observer(SiderMenu); 94 | -------------------------------------------------------------------------------- /src/layouts/SiderMenu/style.less: -------------------------------------------------------------------------------- 1 | .main-left-slider { 2 | box-shadow: 0 2px 6px 4px rgba(26, 37, 51, 0.31); 3 | 4 | .main-logo { 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | flex-wrap: nowrap; 9 | height: 64px; 10 | color: #fff; 11 | font-size: 18px; 12 | border-bottom: 1px solid rgba(118, 141, 152, 0.2); 13 | .app-name { 14 | margin-left: 12px; 15 | vertical-align: text-bottom; 16 | font-size: 20px; 17 | font-weight: 400; 18 | // color: #fff; 19 | text-overflow: clip; 20 | white-space: nowrap; 21 | // text-rendering: optimizeLegibility; 22 | // -webkit-font-smoothing: antialiased; 23 | // -moz-osx-font-smoothing: grayscale; 24 | background-image: -webkit-linear-gradient(bottom, #13e367, #fff); 25 | -webkit-background-clip: text; 26 | -webkit-text-fill-color: transparent; 27 | } 28 | } 29 | 30 | .main-menu { 31 | height: calc(100% - 64px); 32 | overflow: auto; 33 | } 34 | 35 | .ant-menu-submenu-title, 36 | .ant-menu-item { 37 | // color: #ecf5ff; 38 | z-index: 0; 39 | } 40 | 41 | .ant-menu-submenu-title::before, 42 | .ant-menu-item::before { 43 | content: ''; 44 | width: 0; 45 | height: 100%; 46 | display: block; 47 | //background: rgba(82, 204, 78, 0.3); 48 | background: rgba(24, 144, 255, 0.5); 49 | box-shadow: 0 0 175px 0 rgba(24, 144, 255, 1); 50 | z-index: -1; 51 | position: absolute; 52 | left: 0; 53 | right: 0; 54 | transition: all 0.6s; 55 | } 56 | 57 | // .ant-menu-submenu-title:hover::before, 58 | // .ant-menu-item:hover::before { 59 | // width: 100%; 60 | // } 61 | 62 | .ant-menu-submenu-title, 63 | .ant-menu-item { 64 | &:hover { 65 | color: #fff; 66 | &::before { 67 | width: 100%; 68 | } 69 | } 70 | > a { 71 | &:hover { 72 | color: #fff; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { ConfigProvider } from 'antd'; 4 | import zhCN from 'antd/es/locale/zh_CN'; 5 | import dayjs from 'dayjs'; 6 | import 'dayjs/locale/zh-cn'; 7 | 8 | import './styles/main.less'; 9 | import AppRouter from './routers/AppRouter'; 10 | 11 | dayjs.locale('zh-cn'); 12 | 13 | const App = () => ( 14 | 15 | 16 | 17 | ); 18 | 19 | ReactDom.render(, document.getElementById('app')); 20 | 21 | // 热更新 22 | if (module.hot) { 23 | module.hot.accept((err) => { 24 | if (err) { 25 | console.error('module.hot,', err); 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Result, Button } from 'antd'; 4 | 5 | export default () => ( 6 | 12 | 13 | 14 | } 15 | /> 16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Result, Button } from 'antd'; 4 | 5 | export default () => ( 6 | 12 | 13 | 14 | } 15 | /> 16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Result, Button } from 'antd'; 4 | 5 | export default () => ( 6 | 12 | 13 | 14 | } 15 | /> 16 | ); 17 | -------------------------------------------------------------------------------- /src/pages/FormDemo/SearchForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react'; 2 | import { Form, Input, Button, DatePicker, message } from 'antd'; 3 | import dayjs from 'dayjs'; 4 | 5 | import { observer } from 'mobx-react'; 6 | // import { appStores } from '@/stores'; 7 | 8 | import Store from './store'; 9 | 10 | const SearchForm = () => { 11 | const pageStore = useContext(Store); 12 | // const { commonStore } = appStores(); 13 | 14 | useEffect(() => { 15 | // commonStore.qrySupplierList({ all: true }); 16 | }, []); 17 | 18 | const [endOpen, setEndOpen] = useState(false); 19 | const [form] = Form.useForm(); 20 | const { getFieldValue } = form; 21 | 22 | const { searchParams = {} } = pageStore; 23 | 24 | const handleSubmit = (values) => { 25 | if (values.gmtEnd.diff(values.gmtBegin, 'days') > 90) { 26 | message.warning('查询日期的选择范围不能超过90天'); 27 | return; 28 | } 29 | pageStore.searchParams.name = values.name; 30 | pageStore.searchParams.gmtBegin = values.gmtBegin.format('YYYY-MM-DD'); 31 | pageStore.searchParams.gmtEnd = values.gmtEnd.format('YYYY-MM-DD'); 32 | pageStore.qryTableDate(); 33 | // console.log(values); 34 | }; 35 | 36 | const handleStartOpenChange = (open) => { 37 | if (!open) { 38 | setEndOpen(true); 39 | } 40 | }; 41 | const handleEndOpenChange = (open) => { 42 | setEndOpen(open); 43 | }; 44 | // 查询日期 -禁用开始日期 45 | const disabledStartDate = (date) => { 46 | const endDate = getFieldValue('gmtEnd'); 47 | if (!date || !endDate) { 48 | return false; 49 | } 50 | // if (date.valueOf() < dayjs(new Date()).subtract(1, 'year')) { 51 | // return true; 52 | // } 53 | return date.valueOf() > endDate.valueOf(); 54 | }; 55 | // 查询日期 -禁用结束日期 56 | const disabledEndDate = (date) => { 57 | const startDate = getFieldValue('gmtBegin'); 58 | if (!date || !startDate) { 59 | return false; 60 | } 61 | if (date.valueOf() > new Date()) { 62 | return true; 63 | } 64 | return date.valueOf() < startDate.valueOf(); 65 | }; 66 | 67 | return ( 68 |
69 | 70 | 71 | 72 | 73 | 81 | 82 | 83 | 91 | 92 | 95 |
96 | ); 97 | }; 98 | 99 | export default observer(SearchForm); 100 | -------------------------------------------------------------------------------- /src/pages/FormDemo/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo } from 'react'; 2 | import { Table, Button, Switch, Row, Divider } from 'antd'; 3 | import { PlusOutlined } from '@ant-design/icons'; 4 | import { observer } from 'mobx-react'; 5 | import { toJS } from 'mobx'; 6 | 7 | import Store from './store'; 8 | import NewModal from './newModal'; 9 | import SearchForm from './SearchForm'; 10 | import './style.less'; 11 | 12 | const FormDemoPage = () => { 13 | const pageStore = React.useContext(Store); 14 | // console.log(pageStore); 15 | // 页面加载获取数据 16 | useEffect(() => { 17 | pageStore.qryTableDate(); 18 | }, []); 19 | 20 | const columns = useMemo( 21 | () => [ 22 | { 23 | title: '项目名称', 24 | dataIndex: 'name', 25 | ellipsis: true, 26 | }, 27 | { 28 | title: '标识', 29 | dataIndex: 'id', 30 | ellipsis: true, 31 | }, 32 | { 33 | title: '状态', 34 | dataIndex: 'status', 35 | width: 100, 36 | render: (text, record, index) => ( 37 | pageStore.statusChange(type, record, index)} 43 | /> 44 | ), 45 | }, 46 | { 47 | title: '创建时间', 48 | dataIndex: 'createDate', 49 | width: 185, 50 | }, 51 | { 52 | title: '操作', 53 | dataIndex: 'operation', 54 | width: 150, 55 | render: (text, record) => ( 56 | 57 | 60 | 61 | 69 | 70 | ), 71 | }, 72 | ], 73 | [pageStore], 74 | ); 75 | 76 | return ( 77 |
78 | 79 | 80 | 83 | 91 | 92 | 93 | ); 94 | }; 95 | 96 | export default observer(FormDemoPage); 97 | -------------------------------------------------------------------------------- /src/pages/FormDemo/newModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import { Form, Input, Select, Modal } from 'antd'; 4 | import { toJS } from 'mobx'; 5 | import Store from './store'; 6 | 7 | const status = [ 8 | { key: true, label: '正常' }, 9 | { key: false, label: '停用' }, 10 | ]; 11 | 12 | const myModal = () => { 13 | const pageStore = React.useContext(Store); 14 | const modalData = toJS(pageStore.modalData); 15 | const [form] = Form.useForm(); 16 | 17 | const handleSubmit = () => { 18 | form.validateFields().then((values) => { 19 | pageStore.addNew(values); 20 | }); 21 | }; 22 | 23 | return ( 24 | pageStore.setData({ newModalVisible: false })} 29 | okButtonProps={{ loading: pageStore.newLoading }} 30 | > 31 |
32 | 38 | 39 | 40 | 49 | 50 | 51 | 57 | 64 | 65 | 66 |
67 | ); 68 | }; 69 | 70 | export default observer(myModal); 71 | -------------------------------------------------------------------------------- /src/pages/FormDemo/store.js: -------------------------------------------------------------------------------- 1 | import { observable, action, computed } from 'mobx'; 2 | import { message } from 'antd'; 3 | import { createContext } from 'react'; 4 | import dayjs from 'dayjs'; 5 | 6 | import request from '@/services/newRequest'; 7 | 8 | class CompanySetStore { 9 | @observable tableData = [ 10 | { 11 | name: '阿里巴巴', 12 | id: 'alibaba', 13 | createDate: '2019-11-11 11:11', 14 | status: true, 15 | }, 16 | { 17 | name: '蚂蚁金服', 18 | id: 'ant', 19 | createDate: '2019-12-12 12:12', 20 | status: false, 21 | }, 22 | ]; 23 | 24 | @observable loading = false; 25 | 26 | @observable statusLoading = false; 27 | 28 | @observable newModalVisible = false; 29 | 30 | @observable newLoading = false; 31 | 32 | @observable modalType = 'new'; 33 | 34 | @observable modalData = {}; 35 | 36 | @observable pagination = { 37 | size: 'small', 38 | pageSize: 10, 39 | currentPage: 1, 40 | total: 0, 41 | showSizeChanger: true, 42 | onChange: (currentP, size) => { 43 | this.qryTableDate(currentP, size); 44 | }, 45 | onShowSizeChange: (currentP, size) => { 46 | this.qryTableDate(currentP, size); 47 | }, 48 | showTotal: (totalP) => `共 ${totalP} 条记录`, 49 | }; 50 | 51 | @observable searchParams = { 52 | name: undefined, 53 | gmtBegin: dayjs(new Date()).subtract(7, 'days').format('YYYY-MM-DD'), 54 | gmtEnd: dayjs(new Date()).format('YYYY-MM-DD'), 55 | }; 56 | 57 | @computed get modalTitle() { 58 | let res = '项目'; 59 | if (this.modalType === 'edit') { 60 | res = `编辑${res}`; 61 | } else if (this.modalType === 'new') { 62 | res = `新增${res}`; 63 | } 64 | return res; 65 | } 66 | 67 | @action.bound setData(data = {}) { 68 | Object.entries(data).forEach((item) => { 69 | this[item[0]] = item[1]; 70 | }); 71 | } 72 | 73 | @action.bound openModal(type, record = {}) { 74 | this.modalType = type; 75 | this.newModalVisible = true; 76 | this.modalData = { 77 | name: record.name, 78 | id: record.id, 79 | status: record.status, 80 | }; 81 | } 82 | 83 | // 列表数据 84 | @action.bound 85 | async qryTableDate(page = 1, size = this.pagination.pageSize) { 86 | this.loading = true; 87 | const res = await request({ 88 | url: '/user/list', 89 | method: 'post', 90 | data: { page, size, ...this.searchParams }, 91 | }); 92 | 93 | if (res.success) { 94 | const resData = res.data || {}; 95 | this.tableData = resData.listData || []; 96 | this.pagination.total = resData.total; 97 | this.pagination.currentPage = page; 98 | this.pagination.pageSize = size; 99 | } 100 | this.loading = false; 101 | } 102 | 103 | // 状态切换 104 | @action.bound 105 | async statusChange(type, record, index) { 106 | this.statusLoading = true; 107 | this.tableData[index].statusLoading = true; 108 | const res = await request({ 109 | url: '/user/status/mod', 110 | method: 'post', 111 | data: { 112 | id: record.id, 113 | status: type, 114 | }, 115 | }); 116 | if (res.success) { 117 | message.success('状态切换成功!'); 118 | // this.qryTableDate(); 119 | this.tableData[index].status = type; 120 | } 121 | this.statusLoading = false; 122 | this.tableData[index].statusLoading = false; 123 | } 124 | 125 | // 新建厂商 126 | @action.bound 127 | async addNew(data) { 128 | this.newLoading = true; 129 | const res = await request({ 130 | url: '/user/add', 131 | method: 'post', 132 | data, 133 | }); 134 | if (res.success) { 135 | message.success('新建成功!'); 136 | this.newModalVisible = false; 137 | this.qryTableDate(); 138 | } 139 | this.newLoading = false; 140 | } 141 | 142 | // 删除 143 | @action.bound 144 | async delOne(data) { 145 | this.recordLoding = true; 146 | const res = await request({ 147 | url: '/user/delete', 148 | method: 'post', 149 | data: { no: data }, 150 | }); 151 | if (res.success) { 152 | message.success('删除成功!'); 153 | this.qryTableDate(); 154 | } 155 | this.recordLoding = false; 156 | } 157 | } 158 | 159 | export default createContext(new CompanySetStore()); 160 | -------------------------------------------------------------------------------- /src/pages/FormDemo/style.less: -------------------------------------------------------------------------------- 1 | .page-form-demo { 2 | .operation { 3 | .ant-btn { 4 | padding: 0; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import { Button } from 'antd'; 4 | import Store from './store'; 5 | 6 | import './style.less'; 7 | 8 | const HomePage = () => { 9 | // useContext 订阅mobx数据 10 | const pageStore = useContext(Store); 11 | // useState state状态 12 | const [num, setNum] = useState(0); 13 | // useEffect副作用 14 | useEffect(() => { 15 | pageStore.qryTableDate(); 16 | }, []); 17 | 18 | return ( 19 |
20 |

{pageStore.pageTitle}

21 |
22 | num值:{num} 23 | 26 |
27 |
28 | ); 29 | }; 30 | 31 | export default observer(HomePage); 32 | -------------------------------------------------------------------------------- /src/pages/Home/store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: nowthen 4 | * @Date: 2020-12-09 18:36:39 5 | * @LastEditors: nowthen 6 | * @LastEditTime: 2020-12-10 17:34:53 7 | * @FilePath: /react-web-pro/src/pages/Home/store.js 8 | */ 9 | import { createContext } from 'react'; 10 | import { observable, action } from 'mobx'; 11 | import request from '@/services/newRequest'; 12 | 13 | class HomeStore { 14 | @observable tableData = []; 15 | 16 | @observable pageTitle = 'Home主页'; 17 | 18 | @observable loading = false; 19 | 20 | @action.bound setData(data = {}) { 21 | Object.entries(data).forEach((item) => { 22 | this[item[0]] = item[1]; 23 | }); 24 | } 25 | 26 | // 列表数据 27 | @action.bound 28 | async qryTableDate(page = 1, size = 10) { 29 | this.loading = true; 30 | const res = await request({ 31 | url: '/list', 32 | method: 'post', 33 | data: { page, size }, 34 | }); 35 | 36 | if (res.success) { 37 | const resData = res.data || {}; 38 | console.log(resData); 39 | } 40 | this.loading = false; 41 | } 42 | } 43 | 44 | export default createContext(new HomeStore()); 45 | -------------------------------------------------------------------------------- /src/pages/Home/style.less: -------------------------------------------------------------------------------- 1 | .page-home { 2 | .title { 3 | color: red; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/Login/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | import { Form, Input, Checkbox, Button, message } from 'antd'; 4 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 5 | import { observer } from 'mobx-react'; 6 | 7 | import { appStores } from '@/stores'; 8 | import './style.less'; 9 | 10 | const LoginPage = () => { 11 | const history = useHistory(); 12 | const { globalStore } = appStores(); 13 | 14 | const handleSubmit = (values) => { 15 | console.log('登录信息 ', values); 16 | message.success('登录成功,即将跳转...', 2); 17 | setTimeout(() => { 18 | history.push('/'); 19 | }, 2000); 20 | }; 21 | 22 | return ( 23 |
24 |
25 |
欢迎登录 {globalStore.appTitle}
26 | 27 | } placeholder="用户名" /> 28 | 29 | 30 | } 32 | type="password" 33 | placeholder="密码" 34 | /> 35 | 36 | 37 | 记住我 38 | 39 | 忘记密码 40 | 41 | 44 | 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default observer(LoginPage); 51 | -------------------------------------------------------------------------------- /src/pages/Login/style.less: -------------------------------------------------------------------------------- 1 | .page-login { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | background: url('~@/assets/images/login_bg.jpg') no-repeat; 8 | background-size: 100% 100%; 9 | .login-title { 10 | padding-bottom: 20px; 11 | text-align: center; 12 | font-size: 28px; 13 | font-family: cursive; 14 | background: #90ff4d; 15 | background-image: -webkit-linear-gradient(60deg, #13e367, #f29322); 16 | -webkit-background-clip: text; 17 | -webkit-text-fill-color: transparent; 18 | } 19 | .login-form { 20 | position: relative; 21 | padding: 30px; 22 | width: 400px; 23 | box-shadow: 0 0 10px 0 rgb(42, 93, 157); 24 | border-radius: 6px; 25 | background: linear-gradient(rgb(75, 128, 186, 0.86), rgba(192, 207, 230, 0.8)); 26 | } 27 | .login-form-forgot { 28 | float: right; 29 | } 30 | .login-form-button { 31 | width: 100%; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/System/GroovySet/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from 'react'; 2 | import { Row, Button, Table, Switch, Divider } from 'antd'; 3 | import { PlusOutlined } from '@ant-design/icons'; 4 | import { observer } from 'mobx-react'; 5 | import { toJS } from 'mobx'; 6 | 7 | import Store from './store'; 8 | import NewModal from './newModal'; 9 | 10 | import './style.less'; 11 | 12 | // groovy脚本管理 13 | const GroovySet = () => { 14 | const pageStore = useContext(Store); 15 | 16 | // 组件加载获取数据 17 | useEffect(() => { 18 | // pageStore.qryTableDate(); 19 | }, []); 20 | 21 | const columns = [ 22 | { 23 | title: '脚本名称', 24 | dataIndex: 'name', 25 | ellipsis: true, 26 | }, 27 | { 28 | title: '脚本标识', 29 | dataIndex: 'no', 30 | ellipsis: true, 31 | }, 32 | { 33 | title: '状态', 34 | dataIndex: 'online', 35 | width: 90, 36 | render: (text, record) => ( 37 | pageStore.onlineChange(type, record)} 43 | /> 44 | ), 45 | }, 46 | { 47 | title: '创建时间', 48 | dataIndex: 'createDate', 49 | width: 185, 50 | }, 51 | { 52 | title: '修改时间', 53 | dataIndex: 'updateDate', 54 | width: 185, 55 | }, 56 | { 57 | title: '操作', 58 | dataIndex: 'operation', 59 | width: 160, 60 | render: (text, record) => ( 61 | 62 | 65 | 66 | 73 | 74 | 82 | 83 | ), 84 | }, 85 | ]; 86 | 87 | return ( 88 |
89 | 92 | 93 |
101 | 102 | 103 | 104 | ); 105 | }; 106 | export default observer(GroovySet); 107 | -------------------------------------------------------------------------------- /src/pages/System/GroovySet/newModal.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState, createRef } from 'react'; 2 | import { Form, Input, Modal, Icon, Spin, message } from 'antd'; 3 | import { observer } from 'mobx-react'; 4 | import CodeMirror from 'react-codemirror'; 5 | import { groovyTemp } from '@/utils/constant'; 6 | import Store from './store'; 7 | 8 | import 'codemirror/mode/groovy/groovy'; 9 | import 'codemirror/lib/codemirror.css'; 10 | import 'codemirror/theme/material.css'; 11 | import 'codemirror/addon/selection/active-line'; 12 | // import 'codemirror/addon/display/fullscreen'; 13 | // import 'codemirror/addon/display/fullscreen.css'; 14 | 15 | import 'codemirror/addon/fold/foldgutter.css'; 16 | import 'codemirror/addon/fold/foldcode'; 17 | import 'codemirror/addon/fold/foldgutter'; 18 | import 'codemirror/addon/fold/brace-fold'; 19 | import 'codemirror/addon/fold/comment-fold'; 20 | 21 | import 'codemirror/addon/edit/matchbrackets'; 22 | 23 | const options = { 24 | lineNumbers: true, // 显示行 25 | mode: 'groovy', // 语法 26 | theme: 'material', // 主题风格 27 | readOnly: false, // 是否只可读 28 | extraKeys: { Ctrl: 'autocomplete' }, 29 | foldGutter: true, // 代码折叠 30 | gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], // 代码折叠 31 | matchBrackets: true, // 自动匹配括号 32 | styleActiveLine: true, // 当前行高亮 33 | // hintOptions: { completeSingle: true }, 34 | }; 35 | 36 | const myModal = () => { 37 | 38 | const codeRef = createRef(); 39 | const [form] = Form.useForm(); 40 | 41 | const pageStore = useContext(Store); 42 | const { modalData } = pageStore; 43 | const [code, setCode] = useState(modalData.content || groovyTemp); 44 | const [fullscreen, setFullscreen] = useState(false); 45 | 46 | useEffect(() => { 47 | setCode(modalData.content || groovyTemp); 48 | }, [modalData]); 49 | 50 | // CodeMirror value={code}无效。 暂使用 ref setValue更新 51 | useEffect(() => { 52 | if (codeRef.current) { 53 | const mirror = codeRef.current.getCodeMirror(); 54 | mirror.setValue(code); 55 | } 56 | }, [code]); 57 | 58 | const handleSubmit = () => { 59 | form.validateFields().then((values) => { 60 | const groovyCode = codeRef.current && codeRef.current.getCodeMirror().getValue(); 61 | if (!groovyCode) { 62 | message.error('请输入groovy脚本!'); 63 | return; 64 | } 65 | if (pageStore.modalType === 'edit') { 66 | pageStore.modEdit({ no: values.no, name: values.name, content: groovyCode }); 67 | } 68 | if (pageStore.modalType === 'new') { 69 | pageStore.addEdit({ no: values.no, name: values.name, content: groovyCode }); 70 | } 71 | }); 72 | }; 73 | 74 | const onCodeChange = () => { 75 | // console.log(code, codeRef); 76 | // setCode(code); 77 | // const mirror = codeRef.current.getCodeMirror(); 78 | // mirror.setValue(code); 79 | }; 80 | 81 | return ( 82 | pageStore.setData({ newModalVisible: false })} 92 | > 93 | 94 |
95 | 101 | 102 | 103 | 112 | 113 | 114 | 115 | {fullscreen ? ( 116 | setFullscreen(false)} /> 117 | ) : ( 118 | setFullscreen(true)} /> 119 | )} 120 | 121 | 128 |
129 |
130 | ); 131 | }; 132 | 133 | export default observer(myModal); 134 | -------------------------------------------------------------------------------- /src/pages/System/GroovySet/store.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { observable, action, computed } from 'mobx'; 3 | import { message } from 'antd'; 4 | import request from '@/services/newRequest'; 5 | 6 | const demoData = [ 7 | { 8 | name: '脚本1', 9 | no: 'jiaoben1', 10 | online: false, 11 | createDate: '2019-11-11', 12 | updateDate: '2019-12-12', 13 | content: 'test', 14 | }, 15 | ]; 16 | 17 | class GroovySetStore { 18 | @observable tableData = demoData; 19 | 20 | @observable loading = false; 21 | 22 | @observable onlineLoading = false; 23 | 24 | @observable newModalVisible = false; 25 | 26 | @observable recordLoding = false; 27 | 28 | @observable newLoading = false; 29 | 30 | @observable modalType = 'new'; // edit, new, show 31 | 32 | @observable modalNo = ''; // 弹窗 标识 33 | 34 | @observable modalData = {}; // 弹窗的数据 35 | 36 | @observable pagination = { 37 | size: 'small', 38 | pageSize: 10, 39 | current: 1, 40 | total: 0, 41 | showSizeChanger: true, 42 | onChange: (currentP, size) => { 43 | this.qryTableDate(currentP, size); 44 | }, 45 | onShowSizeChange: (currentP, size) => { 46 | this.qryTableDate(currentP, size); 47 | }, 48 | showTotal: (totalP) => `共 ${totalP} 条记录`, 49 | }; 50 | 51 | @computed get ModalTitle() { 52 | let res = 'Groovy脚本'; 53 | if (this.modalType === 'edit') { 54 | res = `编辑${res}`; 55 | } else if (this.modalType === 'new') { 56 | res = `新增${res}`; 57 | } 58 | return res; 59 | } 60 | 61 | @action.bound setData(data = {}) { 62 | Object.entries(data).forEach((item) => { 63 | this[item[0]] = item[1]; 64 | }); 65 | } 66 | 67 | // 弹窗 68 | @action.bound openModal(data = {}) { 69 | this.newModalVisible = true; 70 | this.modalType = data.type; 71 | this.modalNo = data.no; 72 | if (data.type === 'edit' || data.type === 'show') { 73 | this.qryDetail(data.no); 74 | } else { 75 | this.modalData = {}; 76 | } 77 | } 78 | 79 | // 列表数据 80 | @action.bound 81 | async qryTableDate(page = 1, size = this.pagination.pageSize) { 82 | this.loading = true; 83 | const res = await request({ 84 | url: '/script/page', 85 | method: 'post', 86 | data: { page, size }, 87 | }); 88 | 89 | if (res.success) { 90 | const resData = res.data || {}; 91 | this.tableData = resData.listData || []; 92 | this.pagination.total = resData.total; 93 | this.pagination.current = page; 94 | this.pagination.pageSize = size; 95 | } 96 | setTimeout(() => { 97 | this.loading = false; 98 | }, 500); 99 | } 100 | 101 | // 状态切换 102 | @action.bound 103 | async onlineChange(type, record) { 104 | this.onlineLoading = true; 105 | const res = await request({ 106 | url: '/script/online', 107 | method: 'post', 108 | data: { 109 | no: record.no, 110 | online: type, 111 | }, 112 | }); 113 | if (res.success) { 114 | message.success('状态切换成功!'); 115 | this.qryTableDate(); 116 | } 117 | this.onlineLoading = false; 118 | } 119 | 120 | // 新建脚本 121 | @action.bound 122 | async addEdit(data) { 123 | this.newLoading = true; 124 | // const res = await request.post('/testapi/supplier/add', { 125 | // body: data 126 | // }) 127 | const res = await request({ 128 | url: '/script/add', 129 | // url: '/supplier/add', 130 | method: 'post', 131 | data, 132 | }); 133 | if (res.success) { 134 | message.success('新建成功!'); 135 | this.newModalVisible = false; 136 | this.qryTableDate(); 137 | } 138 | this.newLoading = false; 139 | } 140 | 141 | // 修改脚本 142 | @action.bound 143 | async modEdit(data) { 144 | this.newLoading = true; 145 | const res = await request({ 146 | url: '/script/edit', 147 | method: 'post', 148 | data, 149 | }); 150 | if (res.success) { 151 | message.success('修改成功!'); 152 | this.newModalVisible = false; 153 | this.qryTableDate(); 154 | } 155 | this.newLoading = false; 156 | } 157 | 158 | // 删除 159 | @action.bound 160 | async delOne(data) { 161 | this.recordLoding = true; 162 | const res = await request({ 163 | url: '/script/delete', 164 | // url: '/supplier/add', 165 | method: 'post', 166 | data: { no: data }, 167 | }); 168 | if (res.success) { 169 | message.success('删除成功!'); 170 | this.qryTableDate(); 171 | } 172 | this.recordLoding = false; 173 | } 174 | 175 | @action.bound 176 | async qryDetail(data) { 177 | this.loading = true; 178 | const res = await request({ 179 | url: '/script', 180 | method: 'post', 181 | data: { no: data }, 182 | }); 183 | 184 | if (res.success) { 185 | this.modalData = res.data || {}; 186 | } 187 | this.loading = false; 188 | } 189 | } 190 | 191 | export default createContext(new GroovySetStore()); 192 | -------------------------------------------------------------------------------- /src/pages/System/GroovySet/style.less: -------------------------------------------------------------------------------- 1 | .page_groovy_set { 2 | padding: 30px 20px 20px; 3 | margin-bottom: 20px; 4 | background: #fff; 5 | .operation { 6 | .ant-btn { 7 | padding: 0; 8 | } 9 | } 10 | } 11 | 12 | .code-mirror-modal { 13 | .ant-modal-body { 14 | position: relative; 15 | padding-bottom: 10px; 16 | } 17 | .form-area { 18 | // margin-bottom: 15px; 19 | min-height: 60px; 20 | } 21 | .ant-form-inline .ant-form-item-with-help { 22 | margin-bottom: 0; 23 | } 24 | .ant-spin-nested-loading, 25 | .ant-spin-container { 26 | height: 100%; 27 | } 28 | .ReactCodeMirror { 29 | border: 1px solid #ccc; 30 | border-radius: 4px; 31 | } 32 | 33 | .full-screen-icon { 34 | font-size: 22px; 35 | position: absolute; 36 | right: 10px; 37 | top: 70px; 38 | z-index: 10; 39 | color: #fff; 40 | background: rgba(136, 136, 136, 0.3); 41 | cursor: pointer; 42 | &:hover { 43 | // font-size: 26px; 44 | transform: scale(1.2); 45 | background: rgba(217, 217, 217, 0.45); 46 | } 47 | } 48 | .full-screen-exit-icon { 49 | font-size: 26px; 50 | position: absolute; 51 | right: 10px; 52 | top: 70px; 53 | z-index: 10; 54 | color: #fff; 55 | background: rgba(217, 217, 217, 0.45); 56 | cursor: pointer; 57 | &:hover { 58 | // font-size: 22px; 59 | background: rgba(136, 136, 136, 0.3); 60 | transform: scale(0.8); 61 | } 62 | } 63 | } 64 | 65 | .code-mirror-modal-fullScreen { 66 | top: 0; 67 | width: 100% !important; 68 | padding: 0; 69 | .ant-modal-body { 70 | height: calc(100vh - 108px); 71 | } 72 | .ReactCodeMirror { 73 | height: calc(100% - 60px); 74 | .CodeMirror { 75 | height: 100%; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/pages/System/Star/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () =>
Star
; 4 | -------------------------------------------------------------------------------- /src/pages/System/User/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () =>
User
; 4 | -------------------------------------------------------------------------------- /src/pages/Welcome/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './style.less'; 4 | 5 | const WelcomePage = () =>
; 6 | 7 | export default WelcomePage; 8 | -------------------------------------------------------------------------------- /src/pages/Welcome/style.less: -------------------------------------------------------------------------------- 1 | .page-welcome { 2 | width: 100%; 3 | height: 100%; 4 | background: url('~@/assets/images/welcome.jpg') no-repeat; 5 | background-size: 100% 100%; 6 | } 7 | -------------------------------------------------------------------------------- /src/routers/AppRouter.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; 3 | 4 | import LoadingPage from '@/components/LoadingPage'; 5 | import config from './config'; 6 | 7 | const renderRoutes = (routes) => { 8 | if (!Array.isArray(routes)) { 9 | return null; 10 | } 11 | 12 | return ( 13 | 14 | {routes.map((route, index) => { 15 | if (route.redirect) { 16 | return ( 17 | 24 | ); 25 | } 26 | 27 | return ( 28 | { 34 | const renderChildRoutes = renderRoutes(route.childRoutes); 35 | if (route.component) { 36 | return ( 37 | }> 38 | {renderChildRoutes} 39 | 40 | ); 41 | } 42 | return renderChildRoutes; 43 | }} 44 | /> 45 | ); 46 | })} 47 | 48 | ); 49 | }; 50 | 51 | const AppRouter = () => {renderRoutes(config)}; 52 | 53 | export default AppRouter; 54 | -------------------------------------------------------------------------------- /src/routers/config.js: -------------------------------------------------------------------------------- 1 | import React, { lazy } from 'react'; 2 | import { 3 | HomeOutlined, 4 | SettingFilled, 5 | SmileOutlined, 6 | FormOutlined, 7 | UserOutlined, 8 | StarOutlined, 9 | WarningOutlined, 10 | FrownOutlined, 11 | } from '@ant-design/icons'; 12 | 13 | import BasicLayout from '@/layouts/BasicLayout'; 14 | import BlankLayout from '@/layouts/BlankLayout'; 15 | 16 | const config = [ 17 | { 18 | path: '/', 19 | component: BlankLayout, // 空白页布局 20 | childRoutes: [ 21 | // 子菜单路由 22 | { 23 | path: '/login', // 路由路径 24 | name: '登录页', // 菜单名称 (不设置,则不展示在菜单栏中) 25 | component: lazy(() => import('@/pages/Login')), // 懒加载 路由组件 26 | }, 27 | { 28 | path: '/', 29 | // exact: true, 30 | component: BasicLayout, // 基本布局 31 | childRoutes: [ 32 | { 33 | path: '/welcome', 34 | name: '欢迎页', 35 | icon: , 36 | component: lazy(() => import('@/pages/Welcome')), 37 | }, 38 | { 39 | path: '/home', 40 | name: 'home主页', 41 | icon: , 42 | component: lazy(() => import('@/pages/Home')), 43 | }, 44 | { 45 | path: '/formDemo', 46 | name: '表单演示', 47 | icon: , 48 | component: lazy(() => import('@/pages/FormDemo')), 49 | }, 50 | { 51 | path: '/system', 52 | name: '系统管理', 53 | icon: , 54 | childRoutes: [ 55 | { 56 | path: '/system/groovySet', 57 | name: 'Groovy脚本管理', 58 | component: lazy(() => import('@/pages/System/GroovySet')), 59 | }, 60 | { 61 | path: '/system/user', 62 | name: '用户配置', 63 | icon: , 64 | component: lazy(() => import('@/pages/System/User')), 65 | }, 66 | { 67 | path: '/system/star', 68 | name: '个人中心', 69 | icon: , 70 | component: lazy(() => import('@/pages/System/Star')), 71 | }, 72 | ], 73 | }, 74 | { 75 | path: '/exception', 76 | name: '异常页', 77 | // exact: true, 78 | icon: , 79 | childRoutes: [ 80 | { 81 | path: '/exception/403', 82 | name: '403', 83 | icon: , 84 | component: lazy(() => import('@/pages/Exception/403')), 85 | }, 86 | { 87 | path: '/exception/404', 88 | name: '404', 89 | exact: true, 90 | icon: , 91 | component: lazy(() => import('@/pages/Exception/404')), 92 | }, 93 | { 94 | path: '/exception/500', 95 | name: '500', 96 | icon: , 97 | component: lazy(() => import('@/pages/Exception/500')), 98 | }, 99 | ], 100 | }, 101 | { path: '/', exact: true, redirect: '/welcome' }, 102 | { path: '*', exact: true, redirect: '/exception/404' }, 103 | ], 104 | }, 105 | ], 106 | }, 107 | ]; 108 | 109 | export default config; 110 | -------------------------------------------------------------------------------- /src/services/newRequest.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | /* eslint-disable no-param-reassign */ 3 | 4 | /* 5 | * 接口请求封装 6 | * @Author: nowThen 7 | * @Date: 2019-08-14 12:00:02 8 | */ 9 | import axios from 'axios'; // 引入axios 10 | import Qs from 'qs'; // 引入qs模块,用来序列化post类型的数据 11 | import { message } from 'antd'; // 提示框 12 | import { autoMatch, checkStatus } from '../utils/index'; // 附近处理函数 13 | 14 | let inError = false; 15 | // 创建axios实例 16 | const instance = axios.create({ 17 | // baseURL: process.env.BASE_URL, 18 | timeout: 15000, // 请求超时时间 19 | // `transformRequest` 允许在向服务器发送前,修改请求数据 20 | transformRequest: [ 21 | function (data) { 22 | // 对 data 进行任意转换处理 23 | return data; 24 | }, 25 | ], 26 | // `transformResponse` 在传递给 then/catch 前,允许修改响应数据 27 | transformResponse: [ 28 | function (data) { 29 | // console.log(data); 30 | // 对 data 进行任意转换处理 31 | return JSON.parse(data); 32 | }, 33 | ], 34 | headers: { 35 | 'Cache-Control': 'no-cache', 36 | }, 37 | }); 38 | 39 | // 实例添加请求拦截器 40 | instance.interceptors.request.use( 41 | (config) => { 42 | // 在发送请求之前做处理... 43 | config.headers = Object.assign( 44 | config.method === 'get' 45 | ? { 46 | Accept: 'application/json', 47 | 'Content-Type': 'application/json; charset=UTF-8', 48 | } 49 | : { 50 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 51 | }, 52 | config.headers, 53 | ); 54 | // config.headers.token = sessionStorage.getItem(`${projectPrefix}_token_`); 55 | 56 | if (config.method === 'post') { 57 | const contentType = config.headers['Content-Type']; 58 | // 根据Content-Type转换data格式 59 | if (contentType) { 60 | if (contentType.includes('multipart')) { 61 | // 类型 'multipart/form-data;' 62 | // config.data = data; 63 | } else if (contentType.includes('json')) { 64 | // 类型 'application/json;' 65 | // 服务器收到的raw body(原始数据) "{name:"nowThen",age:"18"}"(普通字符串) 66 | config.data = JSON.stringify(config.data); 67 | } else { 68 | // 类型 'application/x-www-form-urlencoded;' 69 | // 服务器收到的raw body(原始数据) name=nowThen&age=18 70 | config.data = Qs.stringify(config.data); 71 | } 72 | } 73 | } 74 | return Promise.resolve(config); 75 | }, 76 | (error) => 77 | // 对请求错误做处理... 78 | Promise.reject(error) 79 | , 80 | ); 81 | 82 | // 实例添加响应拦截器 83 | instance.interceptors.response.use( 84 | (response) => { 85 | // 对响应数据做处理,以下根据实际数据结构改动!!... 86 | const { code } = response.data || {}; 87 | if (code === 109 || code === 108) { 88 | // 请求超时,跳转登录页 89 | if (!inError) { 90 | message.warning('登录超时,即将跳转到登录页面...'); 91 | inError = true; 92 | setTimeout(() => { 93 | message.destroy(); 94 | window.location.href = '/login'; 95 | inError = false; 96 | }, 2000); 97 | } 98 | 99 | return Promise.resolve({}); 100 | } else if (response) { 101 | return Promise.resolve(checkStatus(response)); 102 | } 103 | }, 104 | (error) => { 105 | // 对响应错误做处理... 106 | // console.log(error); 107 | if (error.response) { 108 | return Promise.reject(checkStatus(error.response)); 109 | } else if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) { 110 | return Promise.reject({ msg: '请求超时' }); 111 | } else { 112 | return Promise.reject({}); 113 | } 114 | }, 115 | ); 116 | 117 | const request = async (opt) => { 118 | const options = { 119 | method: 'get', 120 | ifHandleError: true, // 是否统一处理接口失败(提示) 121 | 122 | ...opt, 123 | }; 124 | // 匹配接口前缀 开发环境则通过proxy配置转发请求; 生产环境根据实际配置 125 | options.baseURL = autoMatch(options.prefix); 126 | try { 127 | const res = await instance(options); 128 | // console.log(res); 129 | if (!res.success && options.ifHandleError) { 130 | // 自定义参数,是否允许全局提示错误信息 131 | message.error(res.message || '请求处理失败!'); 132 | } 133 | return res; 134 | } catch (err) { 135 | if (options.ifHandleError) { 136 | // 自定义参数,是否允许全局提示错误信息 137 | message.error(err.message || err.msg || '请求处理失败!'); 138 | } 139 | return err; 140 | } 141 | }; 142 | 143 | export default request; 144 | -------------------------------------------------------------------------------- /src/stores/commonStore.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx'; 2 | 3 | export default class CommonStore { 4 | @observable loading = false; 5 | } 6 | -------------------------------------------------------------------------------- /src/stores/globalStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | // import request from '@/services/request'; 3 | 4 | export default class GlobalStore { 5 | @observable appTitle = '服务管理平台'; 6 | 7 | @observable collapsed = false; // 菜单收起展开 8 | 9 | @observable userInfo = { 10 | // 当前用户信息 11 | loginName: 'nowThen', 12 | }; 13 | 14 | @action.bound toggleCollapsed() { 15 | this.collapsed = !this.collapsed; 16 | } 17 | 18 | @action.bound setData(data = {}) { 19 | Object.entries(data).forEach((item) => { 20 | this[item[0]] = item[1]; 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const context = {}; 4 | 5 | const req = require.context('.', true, /Store$/); 6 | req.keys().forEach((key) => { 7 | const name = key.match(/([a-zA-Z0-9].*)$/)[1]; 8 | const Store = req(key).default; 9 | context[name] = new Store(); 10 | }); 11 | 12 | export const storesContext = React.createContext(context); 13 | 14 | export function appStores() { 15 | return React.useContext(storesContext); 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/main.less: -------------------------------------------------------------------------------- 1 | @import './resetAntd.less'; 2 | 3 | html { 4 | background-color: #f0f2f5; 5 | } 6 | 7 | #app { 8 | min-width: 1200px; 9 | width: 100vw; 10 | height: 100vh; 11 | } 12 | 13 | .page-content { 14 | margin: 12px; 15 | padding: 16px; 16 | background: #fff; 17 | } 18 | -------------------------------------------------------------------------------- /src/styles/resetAntd.less: -------------------------------------------------------------------------------- 1 | // #app { 2 | .ant-layout-sider { 3 | min-height: 100vh; 4 | } 5 | 6 | // override antd left sider 7 | .ant-menu, 8 | .ant-menu-submenu > .ant-menu { 9 | background: #435067; 10 | color: #e8f0ff; 11 | } 12 | 13 | .ant-menu-item > a { 14 | color: #e8f0ff; 15 | } 16 | .ant-menu-dark .ant-menu-item-selected > a, 17 | .ant-menu-dark .ant-menu-item-selected > a:hover { 18 | color: #fff; 19 | } 20 | .ant-layout-sider-dark { 21 | background: #435067; 22 | } 23 | 24 | .ant-menu-dark .ant-menu-inline.ant-menu-sub { 25 | background: #435067; 26 | box-shadow: none; 27 | } 28 | 29 | .ant-menu.ant-menu-dark .ant-menu-item-selected, 30 | .ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected { 31 | background: #1890ff; 32 | } 33 | 34 | .ant-menu-item:hover, 35 | .ant-menu-item-active, 36 | .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, 37 | .ant-menu-submenu-active, 38 | .ant-menu-submenu-title:hover { 39 | color: #fff; 40 | } 41 | // } 42 | -------------------------------------------------------------------------------- /src/utils/constant.js: -------------------------------------------------------------------------------- 1 | // 参数类型 2 | export const paramTypes = [ 3 | { label: '变量', value: 1 }, 4 | { label: '定值', value: 2 }, 5 | ]; 6 | // 传参类型 7 | export const paramVarTypes = [ 8 | { label: '字符', value: 'string' }, 9 | { label: '数字', value: 'number' }, 10 | { label: '小数', value: 'decimal' }, 11 | { label: '布尔', value: 'boolean' }, 12 | { label: '日期', value: 'datetime' }, 13 | ]; 14 | 15 | // Groovy脚本 模板 16 | export const groovyTemp = `import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * groovy脚本实现类 22 | */ 23 | public class GroovyCallerImpl implements GroovyCaller{ 24 | 25 | /** 26 | * 解析 27 | * @param inMap 标识入参 28 | * @param resultInMap 数据返回入参 29 | * @return 30 | */ 31 | @Override 32 | public Map parse(Map inMap, Map> resultInMap) { 33 | 34 | Map result = new HashMap<>(); 35 | // TODO 请在此处补充需要实现的业务逻辑 36 | 37 | 38 | return result; 39 | } 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: nowThen 3 | * @Date: 2019-08-14 16:14:30 4 | */ 5 | 6 | const isDev = process.env.NODE_ENV === 'development'; // 开发 or 生产 7 | 8 | // 匹配接口前缀 9 | export function autoMatch(prefix) { 10 | let baseUrl = ''; 11 | if (isDev) { 12 | // 开发环境 通过proxy配置转发请求; 13 | baseUrl = `/${prefix || 'testapi'}`; 14 | } else { 15 | // 生产环境 根据实际配置 根据 prefix 匹配url; 16 | // 配置来源 根据实际应用场景更改配置。(1.从全局读取;2.线上配置中心读取) 17 | // switch (prefix) { 18 | // case 'baidu': 19 | // baseUrl = window.LOCAL_CONFIG.baidu; 20 | // break; 21 | // case 'alipay': 22 | // baseUrl = window.LOCAL_CONFIG.alipay; 23 | // break; 24 | // default: 25 | // baseUrl = window.LOCAL_CONFIG.default; 26 | // } 27 | } 28 | return baseUrl; 29 | } 30 | 31 | export function checkStatus(response) { 32 | const status = response.status || -1000; // -1000 自己定义,连接错误的status 33 | if ((status >= 200 && status < 300) || status === 304) { 34 | // 如果http状态码正常,则直接返回数据 35 | return response.data; 36 | } else { 37 | let errorInfo = ''; 38 | switch (status) { 39 | case -1: 40 | errorInfo = '远程服务响应失败,请稍后重试'; 41 | break; 42 | case 400: 43 | errorInfo = '400:错误请求'; 44 | break; 45 | case 401: 46 | errorInfo = '401:访问令牌无效或已过期'; 47 | break; 48 | case 403: 49 | errorInfo = '403:拒绝访问'; 50 | break; 51 | case 404: 52 | errorInfo = '404:资源不存在'; 53 | break; 54 | case 405: 55 | errorInfo = '405:请求方法未允许'; 56 | break; 57 | case 408: 58 | errorInfo = '408:请求超时'; 59 | break; 60 | case 500: 61 | errorInfo = '500:访问服务失败'; 62 | break; 63 | case 501: 64 | errorInfo = '501:未实现'; 65 | break; 66 | case 502: 67 | errorInfo = '502:无效网关'; 68 | break; 69 | case 503: 70 | errorInfo = '503:服务不可用'; 71 | break; 72 | default: 73 | errorInfo = `连接错误`; 74 | } 75 | return { 76 | status, 77 | msg: errorInfo, 78 | }; 79 | } 80 | } 81 | 82 | export function splitUrl(url) { 83 | const str = url.split('?')[1]; 84 | const items = (str && str.split('&')) || []; 85 | let arr = {}; 86 | const json = {}; 87 | for (let i = 0; i < items.length; i += 1) { 88 | arr = items[i].split('='); 89 | json[arr[0]] = arr[1]; 90 | } 91 | return json; 92 | } 93 | -------------------------------------------------------------------------------- /src/utils/renderRoutes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router'; 3 | 4 | function renderRoutes(routes, extraProps = {}, switchProps = {}) { 5 | return routes ? ( 6 | 7 | {routes.map((route, i) => ( 8 | 14 | route.render ? ( 15 | route.render({ ...props, ...extraProps, route }) 16 | ) : ( 17 | 18 | )} 19 | /> 20 | ))} 21 | 22 | ) : null; 23 | } 24 | 25 | export default renderRoutes; 26 | --------------------------------------------------------------------------------