├── .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 | [](https://github.com/now1then/react-web-pro/master/LICENSE)
6 |
7 | [](https://github.com/webpack/webpack) [](https://github.com/facebook/react) [](https://github.com/ReactTraining/react-router) [](https://github.com/axios/axios) [](https://ant.design/index-cn) [](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 | 
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 |
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 |
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 |
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 |
} onClick={() => pageStore.openModal('new')}>
81 | 新建
82 |
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 |
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 |
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 |
} onClick={() => pageStore.openModal({ type: 'new' })}>
90 | 新建
91 |
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 |
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 |
--------------------------------------------------------------------------------