├── .editorconfig
├── .eslintcache
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .husky
├── .gitignore
├── commit-msg
└── pre-commit
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── babel.config.js
├── commitlint.config.js
├── env
├── dev.env
├── prod.env
└── test.env
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.tsx
├── assets
│ ├── css
│ │ ├── common.scss
│ │ ├── core.scss
│ │ ├── index.scss
│ │ └── init.scss
│ ├── fonts
│ │ ├── iconfont.eot
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ └── iconfont.woff
│ ├── images
│ │ ├── 01.png
│ │ ├── 02.png
│ │ ├── 404.png
│ │ └── tip-pop-bg.png
│ └── js
│ │ └── a.js
├── common
│ ├── AppContext.tsx
│ ├── AppEvent.ts
│ ├── Resolution.ts
│ └── TContext.tsx
├── components
│ ├── BaseComponent
│ │ └── index.tsx
│ ├── Button
│ │ ├── ButtonCheck.ts
│ │ └── index.tsx
│ ├── DropDownMenu
│ │ ├── index.scss
│ │ ├── index.tsx
│ │ └── popUtils.ts
│ ├── ErrorBoundary
│ │ ├── GlobalError
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── ViewError
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── NoMatch
│ │ └── index.tsx
│ ├── Page
│ │ ├── index.scss
│ │ └── index.tsx
│ └── ResolutionCom
│ │ └── ResolutionComp.tsx
├── index.scss
├── index.tsx
├── pages
│ ├── Home
│ │ ├── HomeChild
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ │ ├── index.scss
│ │ └── index.tsx
│ └── Page1
│ │ ├── index.scss
│ │ └── index.tsx
├── router
│ ├── RouterUI.tsx
│ └── index.ts
├── services
│ ├── BaseService.ts
│ ├── ServerResponseManager.ts
│ ├── api
│ │ ├── index.ts
│ │ └── user
│ │ │ └── index.ts
│ ├── axios.ts
│ └── serviceConfig.ts
├── store
│ ├── actionCreaters
│ │ ├── actionCreator.ts
│ │ ├── index.ts
│ │ └── user.ts
│ ├── actions
│ │ └── user.ts
│ ├── connect.ts
│ ├── index.ts
│ └── reducers
│ │ ├── history.ts
│ │ ├── index.ts
│ │ └── user.ts
├── types
│ ├── IContext.ts
│ ├── IRedux.ts
│ ├── IRouterPage.ts
│ ├── enum.ts
│ └── service
│ │ ├── responseData.ts
│ │ └── user.ts
├── typings
│ ├── custom.d.ts
│ └── global.d.ts
└── util
│ ├── DomUtil.ts
│ ├── generateId.ts
│ ├── handleUrlParams.ts
│ ├── index.ts
│ ├── stringUtil.ts
│ └── variableTypeDetection.ts
├── tsconfig.eslint.json
├── tsconfig.json
├── webpack
├── webpack.base.js
├── webpack.dev.js
├── webpack.prod.js
└── webpackUtils
│ ├── plugins.js
│ ├── resolve.js
│ ├── util.js
│ └── variable.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | indent_style = space
10 | indent_size = 2
11 | trim_trailing_whitespace = true
12 |
13 | [*.{js,json,ts,tsx}]
14 | indent_style = space
15 | indent_size = 4
16 | quote_type = single
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"E:\\react脚手架\\react-project-tmp\\src\\App.tsx":"1","E:\\react脚手架\\react-project-tmp\\src\\common\\AppContext.tsx":"2","E:\\react脚手架\\react-project-tmp\\src\\common\\AppEvent.ts":"3","E:\\react脚手架\\react-project-tmp\\src\\common\\Resolution.ts":"4","E:\\react脚手架\\react-project-tmp\\src\\common\\TContext.tsx":"5","E:\\react脚手架\\react-project-tmp\\src\\components\\BaseComponent\\index.tsx":"6","E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\GlobalError\\index.tsx":"7","E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\index.tsx":"8","E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\ViewError\\index.tsx":"9","E:\\react脚手架\\react-project-tmp\\src\\components\\NoMatch\\index.tsx":"10","E:\\react脚手架\\react-project-tmp\\src\\components\\Page\\index.tsx":"11","E:\\react脚手架\\react-project-tmp\\src\\components\\ResolutionCom\\ResolutionComp.tsx":"12","E:\\react脚手架\\react-project-tmp\\src\\index.tsx":"13","E:\\react脚手架\\react-project-tmp\\src\\pages\\Home\\HomeChild\\index.tsx":"14","E:\\react脚手架\\react-project-tmp\\src\\pages\\Home\\index.tsx":"15","E:\\react脚手架\\react-project-tmp\\src\\pages\\Page1\\index.tsx":"16","E:\\react脚手架\\react-project-tmp\\src\\router\\index.ts":"17","E:\\react脚手架\\react-project-tmp\\src\\router\\RouterUI.tsx":"18","E:\\react脚手架\\react-project-tmp\\src\\services\\api\\index.ts":"19","E:\\react脚手架\\react-project-tmp\\src\\services\\api\\user\\index.ts":"20","E:\\react脚手架\\react-project-tmp\\src\\services\\axios.ts":"21","E:\\react脚手架\\react-project-tmp\\src\\services\\BaseService.ts":"22","E:\\react脚手架\\react-project-tmp\\src\\services\\ServerResponseManager.ts":"23","E:\\react脚手架\\react-project-tmp\\src\\services\\serviceConfig.ts":"24","E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\actionCreator.ts":"25","E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\index.ts":"26","E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\user.ts":"27","E:\\react脚手架\\react-project-tmp\\src\\store\\actions\\user.ts":"28","E:\\react脚手架\\react-project-tmp\\src\\store\\connect.ts":"29","E:\\react脚手架\\react-project-tmp\\src\\store\\index.ts":"30","E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\history.ts":"31","E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\index.ts":"32","E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\user.ts":"33","E:\\react脚手架\\react-project-tmp\\src\\types\\enum.ts":"34","E:\\react脚手架\\react-project-tmp\\src\\types\\IContext.ts":"35","E:\\react脚手架\\react-project-tmp\\src\\types\\IRedux.ts":"36","E:\\react脚手架\\react-project-tmp\\src\\types\\IRouterPage.ts":"37","E:\\react脚手架\\react-project-tmp\\src\\types\\service\\responseData.ts":"38","E:\\react脚手架\\react-project-tmp\\src\\types\\service\\user.ts":"39","E:\\react脚手架\\react-project-tmp\\src\\typings\\custom.d.ts":"40","E:\\react脚手架\\react-project-tmp\\src\\typings\\global.d.ts":"41","E:\\react脚手架\\react-project-tmp\\src\\util\\generateId.ts":"42","E:\\react脚手架\\react-project-tmp\\src\\util\\handleUrlParams.ts":"43","E:\\react脚手架\\react-project-tmp\\src\\util\\index.ts":"44","E:\\react脚手架\\react-project-tmp\\src\\util\\stringUtil.ts":"45","E:\\react脚手架\\react-project-tmp\\src\\util\\variableTypeDetection.ts":"46"},{"size":745,"mtime":1631865620552,"results":"47","hashOfConfig":"48"},{"size":966,"mtime":1631780635956,"results":"49","hashOfConfig":"48"},{"size":851,"mtime":1631780648059,"results":"50","hashOfConfig":"48"},{"size":496,"mtime":1631780654021,"results":"51","hashOfConfig":"48"},{"size":174,"mtime":1631780658403,"results":"52","hashOfConfig":"48"},{"size":1377,"mtime":1631867195005,"results":"53","hashOfConfig":"48"},{"size":1208,"mtime":1631780669327,"results":"54","hashOfConfig":"48"},{"size":1901,"mtime":1631867195006,"results":"55","hashOfConfig":"48"},{"size":716,"mtime":1631780675795,"results":"56","hashOfConfig":"48"},{"size":101,"mtime":1631780762626,"results":"57","hashOfConfig":"48"},{"size":537,"mtime":1631780781012,"results":"58","hashOfConfig":"48"},{"size":2549,"mtime":1631781703438,"results":"59","hashOfConfig":"48"},{"size":1135,"mtime":1631781232725,"results":"60","hashOfConfig":"48"},{"size":1153,"mtime":1631867197445,"results":"61","hashOfConfig":"48"},{"size":1818,"mtime":1631780926541,"results":"62","hashOfConfig":"48"},{"size":1053,"mtime":1631867197450,"results":"63","hashOfConfig":"48"},{"size":496,"mtime":1631780964308,"results":"64","hashOfConfig":"48"},{"size":1482,"mtime":1631781076121,"results":"65","hashOfConfig":"48"},{"size":132,"mtime":1631781090049,"results":"66","hashOfConfig":"48"},{"size":346,"mtime":1631781086569,"results":"67","hashOfConfig":"48"},{"size":2032,"mtime":1631781093866,"results":"68","hashOfConfig":"48"},{"size":1393,"mtime":1631781097209,"results":"69","hashOfConfig":"48"},{"size":1312,"mtime":1631781755143,"results":"70","hashOfConfig":"48"},{"size":285,"mtime":1631781134065,"results":"71","hashOfConfig":"48"},{"size":773,"mtime":1631781139372,"results":"72","hashOfConfig":"48"},{"size":104,"mtime":1631781144930,"results":"73","hashOfConfig":"48"},{"size":468,"mtime":1631781147752,"results":"74","hashOfConfig":"48"},{"size":173,"mtime":1631781150929,"results":"75","hashOfConfig":"48"},{"size":1642,"mtime":1631781167579,"results":"76","hashOfConfig":"48"},{"size":1325,"mtime":1631781909159,"results":"77","hashOfConfig":"48"},{"size":2263,"mtime":1631781963681,"results":"78","hashOfConfig":"48"},{"size":220,"mtime":1631781159576,"results":"79","hashOfConfig":"48"},{"size":1249,"mtime":1631781163012,"results":"80","hashOfConfig":"48"},{"size":79,"mtime":1631781186297,"results":"81","hashOfConfig":"48"},{"size":151,"mtime":1631781189275,"results":"82","hashOfConfig":"48"},{"size":290,"mtime":1631781191898,"results":"83","hashOfConfig":"48"},{"size":347,"mtime":1631781195117,"results":"84","hashOfConfig":"48"},{"size":358,"mtime":1631781181042,"results":"85","hashOfConfig":"48"},{"size":258,"mtime":1631781184123,"results":"86","hashOfConfig":"48"},{"size":0,"mtime":1628059377292,"results":"87","hashOfConfig":"48"},{"size":254,"mtime":1631781201828,"results":"88","hashOfConfig":"48"},{"size":1608,"mtime":1631867197455,"results":"89","hashOfConfig":"48"},{"size":1533,"mtime":1631867197460,"results":"90","hashOfConfig":"48"},{"size":959,"mtime":1631781218482,"results":"91","hashOfConfig":"48"},{"size":2237,"mtime":1631781221672,"results":"92","hashOfConfig":"48"},{"size":526,"mtime":1631781223949,"results":"93","hashOfConfig":"48"},{"filePath":"94","messages":"95","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"o1ppuz",{"filePath":"96","messages":"97","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"98","messages":"99","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"102","messages":"103","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"104","messages":"105","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"106","messages":"107","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"108","messages":"109","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"110","messages":"111","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"112","messages":"113","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"114","messages":"115","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"116","messages":"117","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"118","messages":"119","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"120","messages":"121","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"122","messages":"123","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"124","messages":"125","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"126","messages":"127","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"128","messages":"129","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"130","messages":"131","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"132","messages":"133","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"134","messages":"135","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"136","messages":"137","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"138","messages":"139","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"140","messages":"141","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"142","messages":"143","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"144","messages":"145","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"146","messages":"147","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"148","messages":"149","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"150","messages":"151","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"152","messages":"153","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"154","messages":"155","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"156","messages":"157","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"158","messages":"159","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"160","messages":"161","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"162","messages":"163","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"164","messages":"165","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"166","messages":"167","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"168","messages":"169","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"170","messages":"171","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"172","messages":"173","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"174","messages":"175","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"176","messages":"177","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"178","messages":"179","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"180","messages":"181","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"182","messages":"183","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"184","messages":"185","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"E:\\react脚手架\\react-project-tmp\\src\\App.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\common\\AppContext.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\common\\AppEvent.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\common\\Resolution.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\common\\TContext.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\BaseComponent\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\GlobalError\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\ErrorBoundary\\ViewError\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\NoMatch\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\Page\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\components\\ResolutionCom\\ResolutionComp.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\pages\\Home\\HomeChild\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\pages\\Home\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\pages\\Page1\\index.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\router\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\router\\RouterUI.tsx",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\api\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\api\\user\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\axios.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\BaseService.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\ServerResponseManager.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\services\\serviceConfig.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\actionCreator.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\actionCreaters\\user.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\actions\\user.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\connect.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\history.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\store\\reducers\\user.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\enum.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\IContext.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\IRedux.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\IRouterPage.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\service\\responseData.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\types\\service\\user.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\typings\\custom.d.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\typings\\global.d.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\util\\generateId.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\util\\handleUrlParams.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\util\\index.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\util\\stringUtil.ts",[],"E:\\react脚手架\\react-project-tmp\\src\\util\\variableTypeDetection.ts",[]]
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.html
2 | node_modules
3 | coverage
4 | dist
5 | env
6 | postcss.config.js
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const prettierOptions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'));
5 |
6 | module.exports = {
7 | root: true,
8 | parser: '@typescript-eslint/parser',
9 | // 使用 airbnb 拓展插件规范相关库
10 | // prettier 已内置了许多相关插件
11 | extends: ['airbnb-typescript', 'prettier'],
12 | // 拓展和支持相关能力的插件库
13 | plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
14 | env: {
15 | //指定代码的运行环境
16 | jest: true,
17 | browser: true,
18 | node: true,
19 | es6: true,
20 | },
21 | parserOptions: {
22 | //指定要使用的es版本
23 | ecmaVersion: 6,
24 | //向后兼容
25 | createDefaultProgram: true,
26 | sourceType: 'module',
27 | ecmaFeatures: {
28 | jsx: true,
29 | globalReturn: true,
30 | },
31 | project: './tsconfig.eslint.json',
32 | // project: {},
33 | },
34 | settings: {
35 | 'import/parsers': {
36 | '@typescript-eslint/parser': ['.ts', '.tsx'],
37 | },
38 | 'import/resolver': {
39 | webpack: {
40 | config: './webpack/webpack.base.js',
41 | },
42 | typescript: {
43 | alwaysTryTypes: true,
44 | directory: './tsconfig.json',
45 | },
46 | },
47 | react: {
48 | version: 'detect',
49 | },
50 | },
51 |
52 | rules: {
53 | 'jsx-no-lambda': 0,
54 | semi: [2, 'always'],
55 | '@typescript-eslint/interface-name-prefix': 0,
56 | '@typescript-eslint/no-empty-interface': 0,
57 | 'object-shorthand': [0, 'never'],
58 | //单引号
59 | quotes: 'off',
60 | '@typescript-eslint/quotes': 'off',
61 | '@typescript-eslint/no-var-requires': 0,
62 | 'member-ordering': 0,
63 | 'object-literal-sort-keys': 0,
64 | 'no-shadowed-variable': 0,
65 | 'no-consecutive-blank-lines': 0,
66 | 'no-string-literal': 0,
67 | 'jsx-no-multiline-js': 0,
68 | 'jsx-boolean-value': 0,
69 | 'arrow-parens': 0,
70 | 'no-implicit-dependencies': 0,
71 | 'no-submodule-imports': 0,
72 | 'no-case-declarations': 1,
73 | '@typescript-eslint/no-empty-function': 0,
74 | '@typescript-eslint/indent': 'off',
75 | 'jsx-alignment': 0,
76 | 'jsx-wrap-multiline': 0,
77 | '@typescript-eslint/camelcase': 0,
78 | 'prettier/prettier': ['error', prettierOptions],
79 | 'arrow-body-style': [2, 'as-needed'],
80 | 'class-methods-use-this': 0,
81 | 'import/extensions': 'off',
82 | 'no-param-reassign': 1,
83 | 'jsx-a11y/aria-props': 2,
84 | 'jsx-a11y/heading-has-content': 0,
85 | 'jsx-a11y/label-has-associated-control': [
86 | 2,
87 | {
88 | // NOTE: If this error triggers, either disable it or add
89 | // your custom components, labels and attributes via these options
90 | // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md
91 | controlComponents: ['Input'],
92 | },
93 | ],
94 | 'jsx-a11y/label-has-for': 0,
95 | 'jsx-a11y/mouse-events-have-key-events': 2,
96 | 'jsx-a11y/role-has-required-aria-props': 2,
97 | 'jsx-a11y/role-supports-aria-props': 2,
98 | 'max-len': 0,
99 | 'newline-per-chained-call': 0,
100 | 'no-confusing-arrow': 0,
101 | 'no-console': 'off',
102 | 'no-use-before-define': 0,
103 | 'prefer-template': 2,
104 | 'react-hooks/rules-of-hooks': 'error',
105 | 'react/forbid-prop-types': 0,
106 | 'react/jsx-first-prop-new-line': [2, 'multiline'],
107 | //jsx 不允许显示js后缀
108 | 'react/jsx-filename-extension': 'off',
109 | // 如果属性值为 true, 可以直接省略
110 | 'react/jsx-boolean-value': 1,
111 | // 对于没有子元素的标签来说总是自己关闭标签
112 | 'react/self-closing-comp': 1,
113 | // 当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去
114 | 'react/jsx-no-bind': 0,
115 | // React中函数的先后顺序
116 | 'react/sort-comp': 'off',
117 | // React组件名使用帕斯卡命名, 实例使用骆驼式命名
118 | 'react/jsx-pascal-case': 1,
119 | // didmount不使用setState
120 | 'react/no-did-mount-set-state': 0,
121 | // didupdate不使用setState
122 | 'react/no-did-update-set-state': 1,
123 | // 禁止使用嵌套的三目运算
124 | 'no-nested-ternary': 'off',
125 | // 解构赋值
126 | 'react/destructuring-assignment': [0, 'always'],
127 | // 属性验证
128 | 'react/prop-types': 'off',
129 |
130 | // 多余的依赖
131 | 'import/no-extraneous-dependencies': 'off',
132 | // jsx关闭位置
133 | 'react/jsx-closing-tag-location': 1,
134 | // 多行
135 | 'react/jsx-wrap-multilines': 'off',
136 | // 一行一个表达式
137 | 'react/jsx-one-expression-per-line': 'off',
138 | // will update不能使用setState
139 | 'react/no-will-update-set-state': 1,
140 | // setState 使用state
141 | 'react/no-access-state-in-setstate': 1,
142 | // button 需要类型
143 | 'react/button-has-type': 1,
144 | // jsx 参考: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules
145 | // JSX属性值强制使用双引号
146 | 'jsx-quotes': 1,
147 | // 使用click必须有其他keybord事件
148 | 'jsx-a11y/click-events-have-key-events': 'off',
149 | //
150 | 'jsx-a11y/no-noninteractive-element-interactions': 'off',
151 | // alt
152 | 'jsx-a11y/alt-text': 2,
153 | // 交互的需要role
154 | 'jsx-a11y/no-static-element-interactions': 'off',
155 | // 锚无效
156 | 'jsx-a11y/anchor-is-valid': 'warn',
157 | 'jsx-a11y/anchor-has-content': 'warn',
158 | 'react/jsx-no-target-blank': 0,
159 | 'react/jsx-props-no-spreading': 0,
160 | 'react/jsx-uses-vars': 2,
161 | 'react/require-default-props': 0,
162 | 'react/require-extension': 0,
163 | 'require-yield': 0,
164 | 'space-before-function-paren': 0,
165 | indent: 'off',
166 | },
167 | globals: {
168 | document: false,
169 | window: false,
170 | eruda: false,
171 | Stats: false,
172 | },
173 | };
174 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | # testing
4 | /coverage
5 | # production
6 | /dist
7 | eslintError.html
8 | .eslintcache
9 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # npx --no-install pretty-quick --staged
5 | npx --no-install lint-staged
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
5 | # package.json
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid",
9 | "endOfLine": "auto"
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // 保存时格式化
3 | "editor.formatOnSave": true,
4 | // 相当于是 vscode 保存时调用的钩子事件
5 | "editor.codeActionsOnSave": {
6 | // 保存时使用 ESLint 修复可修复错误
7 | "source.fixAll.eslint": true
8 | },
9 | // 针对对应文件设置默认格式化插件
10 | "[javascript]": {
11 | "editor.defaultFormatter": "esbenp.prettier-vscode"
12 | },
13 | "[javascriptreact]": {
14 | "editor.defaultFormatter": "esbenp.prettier-vscode"
15 | },
16 | "[typescript]": {
17 | "editor.defaultFormatter": "esbenp.prettier-vscode"
18 | },
19 | "[typescriptreact]": {
20 | "editor.defaultFormatter": "esbenp.prettier-vscode"
21 | },
22 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Welcome to react-project-template 👋
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | > react 项目模板
12 |
13 | ### !!! 最新vite+router v6 + react 请点击 🏠 [Homepage](https://github.com/qyjandroid/react-hooks-vite)
14 |
15 | ### 🏠 [Homepage](https://github.com/qyjandroid/react-project-template)
16 |
17 | ## Prerequisites
18 |
19 | - npm >=6.14.6
20 | - node >=12.18.4
21 |
22 | ## Install
23 |
24 | ```sh
25 | yarn install
26 | ```
27 |
28 | ## Usage
29 |
30 | ```sh
31 | yarn run start
32 | ```
33 |
34 | ## 项目目录结构
35 | ```markdown
36 | react-project-template
37 | ├── .babelrc # babel配置
38 | ├── Webpack # webpack公用配置目录
39 | │ │ ├──plugins # 公用插件集合
40 | │ │ ├──resolve # webpack resolve配置
41 | │ │ ├──utils # webpack 工具类
42 | │ │ ├──variable # webpack 变量配置
43 | │ ├── webpack.base.js # Webpack 基础配置文件
44 | │ ├── webpack.dev.js # Webpack 开发环境配置文件
45 | │ └── webpack.prod.js # Webpack 生产环境配置文件
46 | ├── yarn.lock # 锁定 npm 包依赖版本文件
47 | ├── package.json
48 | ├── postcss.config.js # 自动兼容 CSS3 样式配置文件
49 | ├── .editorconfig # IDE格式化规范
50 | ├── .eslintignore # eslint忽略文件配置
51 | ├── .eslintrc.js # eslint配置文件
52 | ├── .prettierignore # prettierc忽略文件配置
53 | ├── .prettierrc # prettierc配置文件
54 | ├── .husky # 配置git提交钩子
55 | ├── .commitlint.config.js # 配置git提交规范
56 | ├── tsconfig.eslint.js # eslint检查typescript配置项配置文件
57 | ├── eslintError.html # eslint报告文件
58 | ├── public # 存放html模板
59 | ├── README.md
60 | ├── src
61 | │ ├── assets # 存放会被 Webpack 处理的静态资源文件:一般是自己写的 js、css 或者图片等静态资源
62 | │ │ ├── fonts # iconfont 目录
63 | │ │ ├── images # 图片资源目录
64 | │ │ ├── css # 全局样式目录
65 | │ │ │ ├── common.scss # 全局通用样式目录
66 | │ │ │ ├── core.scss # 全局sass 变量目录,直接使用,不需要引用,全局已统一引入。
67 | │ │ │ └── init.scss # 全局初始化css
68 | │ │ └── js # 全局js
69 | │ ├── common # 存放项目通用文件
70 | │ │ ├── Resolution.ts # 布局适配配置中心
71 | │ │ └── AppContext.ts # 全局App上下文
72 | │ ├── components # 项目中通用的业务组件目录
73 | │ ├── config # 项目配置文件
74 | │ ├── pages # 项目页面目录
75 | │ ├── typings # 项目中d.ts 声明文件目录
76 | │ ├── types # 项目中声明文件
77 | │ │ ├── service # 项目中服务相关声明文件
78 | │ │ ├── enum.ts # 项目中枚举类型
79 | │ │ ├── IContext.ts # 全局App上下文声明
80 | │ │ ├── IRedux.ts # redux相关声明
81 | │ │ └── IRouterPage.ts # 路由相关声明
82 | │ ├── uiLibrary # 组件库
83 | │ ├── routes # 路由目录
84 | │ │ ├── index.tsx # 路由配置入口文件
85 | │ │ └── RouterUI.tsx # 路由转换
86 | │ ├── services # 和后端相关的文件目录
87 | │ │ ├── api # 调用后端接口定义目录
88 | │ │ │ ├── index.ts
89 | │ │ ├── axios.ts # 基于 axios 二次封装
90 | │ │ ├── BaseService.ts # 基础请求服务类型
91 | │ │ ├── ServerResponseManager.ts # 服务返回统一管理
92 | │ │ ├── serviceConfig.ts # 服务地址配置文件
93 | │ ├── store # redux 仓库
94 | │ │ ├── actionCreaters # action创建与分发绑定
95 | │ │ ├── action # 项目中action
96 | │ │ ├── reducers # 项目中reducers
97 | │ │ │ ├──history # 项目中路由相关history
98 | │ │ ├── index.ts # 全局 store 获取
99 | │ │ ├── connect.ts # react 页面与store 连接
100 | │ ├── utils # 全局通用工具函数目录
101 | │ ├── App.tsx # App全局
102 | │ ├── index.tsx # 项目入口文件
103 | │ ├── index.scss # 项目入口引入的scss
104 | └── tsconfig.json # TS 配置文件
105 | ```
106 |
107 | ## Tips
108 |
109 | - 项目中引入了 `core.scss `,直接使用,不需要@import
110 |
111 | - 构建项目时会自动兼容 CSS3 样式,所以不需要自己去写浏览器兼容样式
112 |
113 | - 项目支持配置式路由
114 |
115 | - 项目中集成了 `connected-react-router` ,路由存储在store中,界面直接从store获取
116 |
117 | - 项目中默认配置了一些常用工具函数
118 |
119 | - 项目中针对 `axios` 做了二次封装
120 |
121 | - 项目直接使用px即可
122 |
123 | - 项目大量使用装饰器,来简化代码
124 |
125 |
126 | ## 脚手架结合使用
127 |
128 | - 需要使用该项目模板,可以通过 [quanyj-react-cli](https://github.com/qyjandroid/react-cli) 脚手架获取
129 | - Install
130 | ```js
131 | npm install quanyj-react-cli -g
132 | ```
133 | - create project
134 | ```js
135 | react-cli create 项目名称 -f
136 | ```
137 |
138 |
139 | ## TodoList
140 | - [x] eslint
141 | - [x] commit 提交检查
142 | - [ ] webpack 编写Ts语法
143 | - [ ] Vite 支持
144 | - [ ] yml 文件 (CI/CD)
145 |
146 |
147 | ## Author
148 |
149 | 👤 **quanyj **
150 |
151 | * 掘金: https://juejin.cn/user/923245496789255
152 | * Github: [@qyjandroid](https://github.com/qyjandroid)
153 |
154 | ## 🤝 Contributing
155 |
156 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/qyjandroid/react-project-template/issues).
157 |
158 | ## Show your support
159 |
160 | Give a ⭐️ if this project helped you!
161 |
162 | ***
163 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
164 |
165 |
166 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | var isDev = false;
2 | if (process.env.NODE_ENV === 'dev') {
3 | isDev = true;
4 | }
5 |
6 | module.exports = function (api) {
7 | api.cache(true);
8 | const presets = [
9 | [
10 | '@babel/preset-react',
11 | {
12 | development: isDev,
13 | },
14 | ],
15 | [
16 | '@babel/preset-env',
17 | {
18 | targets: {
19 | browsers: ['>0.25%', 'not ie 11', 'not op_mini all'],
20 | },
21 | },
22 | ],
23 | [
24 | '@babel/preset-typescript',
25 | {
26 | isTSX: true,
27 | allExtensions: true,
28 | },
29 | ],
30 | ];
31 | const plugins = [
32 | [
33 | '@babel/plugin-proposal-decorators',
34 | {
35 | legacy: true,
36 | },
37 | ],
38 | [
39 | '@babel/plugin-transform-runtime',
40 | {
41 | corejs: 3,
42 | regenerator: true,
43 | },
44 | ],
45 | ];
46 |
47 | // if (isDev) {
48 | // plugins.push([
49 | // 'react-refresh/babel',
50 | // {
51 | // skipEnvCheck: true,
52 | // },
53 | // ]).filter(Boolean);
54 | // }
55 |
56 | return {
57 | // 这个不设置的话,webpack 魔法注释会被删除,魔法注释用于分包
58 | comments: true,
59 | presets,
60 | plugins,
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | // git commit 规范
2 | // <类型>[可选的作用域]: <描述>
3 | //git commit -m 'feat: 增加 xxx 功能'
4 | //git commit -m 'bug: 修复 xxx 功能'
5 | // # 主要type
6 | // feat: 增加新功能
7 | // fix: 修复bug
8 | //build: 主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
9 | //ci: 主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
10 | //docs: 文档更新
11 | //perf: 性能,体验优化
12 | //refactor: 代码重构时使用
13 | // style: 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
14 | // refactor: 代码重构时使用
15 | // revert: 执行git revert打印的message
16 | //chore: 不属于以上类型的其他类型
17 | // test: 添加测试或者修改现有测试
18 |
19 | module.exports = {
20 | extends: ['@commitlint/config-conventional'],
21 | };
22 |
--------------------------------------------------------------------------------
/env/dev.env:
--------------------------------------------------------------------------------
1 | ENV=dev
2 | CDN_ROOT=
3 | C_API_SERVER=
--------------------------------------------------------------------------------
/env/prod.env:
--------------------------------------------------------------------------------
1 | ENV=prod
2 | CDN_ROOT=https://xxx.com
3 | C_API_SERVER=
4 |
--------------------------------------------------------------------------------
/env/test.env:
--------------------------------------------------------------------------------
1 | ENV=test
2 | CDN_ROOT=
3 | C_API_SERVER=
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-project-template",
3 | "version": "1.0.0",
4 | "description": "react 项目模板",
5 | "main": "src/index.tsx",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com.cnpmjs.org/qyjandroid/react-project-template.git"
9 | },
10 | "author": "quanyj ",
11 | "license": "MIT",
12 | "scripts": {
13 | "start": "cross-env NODE_ENV=dev webpack serve --config webpack/webpack.dev.js",
14 | "build:test": "cross-env NODE_ENV=test webpack --config webpack/webpack.dev.js",
15 | "build:prod": "cross-env NODE_ENV=prod webpack --config webpack/webpack.prod.js",
16 | "type-check": "tsc --watch",
17 | "lint": "eslint src/**/*.{ts,tsx} -f html --cache -o ./eslintError.html",
18 | "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
19 | "prepare": "husky install"
20 | },
21 | "bugs": {
22 | "url": "https://github.com/qyjandroid/react-project-template/issues"
23 | },
24 | "dependencies": {
25 | "@babel/runtime-corejs3": "^7.15.4",
26 | "axios": "^0.21.1",
27 | "connected-react-router": "^6.9.1",
28 | "events": "^3.3.0",
29 | "immer": "^9.0.5",
30 | "lodash": "^4.17.21",
31 | "react": "^17.0.2",
32 | "react-dom": "^17.0.2",
33 | "react-modal": "^3.14.3",
34 | "react-redux": "^7.2.4",
35 | "react-router-dom": "^5.2.0",
36 | "redux": "^4.1.1",
37 | "redux-devtools-extension": "^2.13.9",
38 | "typescript": "^4.3.5"
39 | },
40 | "devDependencies": {
41 | "@babel/cli": "^7.14.8",
42 | "@babel/core": "^7.14.8",
43 | "@babel/plugin-proposal-decorators": "^7.14.5",
44 | "@babel/plugin-transform-react-jsx-source": "^7.14.5",
45 | "@babel/plugin-transform-runtime": "^7.15.0",
46 | "@babel/preset-env": "^7.14.8",
47 | "@babel/preset-react": "^7.14.5",
48 | "@babel/preset-typescript": "^7.14.5",
49 | "@commitlint/cli": "^13.1.0",
50 | "@commitlint/config-conventional": "^13.1.0",
51 | "@types/lodash": "^4.14.172",
52 | "@types/react": "^17.0.15",
53 | "@types/react-dom": "^17.0.9",
54 | "@types/react-router-dom": "^5.1.8",
55 | "@typescript-eslint/eslint-plugin": "^4.31.1",
56 | "@typescript-eslint/parser": "^4.31.1",
57 | "babel-loader": "^8.2.2",
58 | "clean-webpack-plugin": "^4.0.0-alpha.0",
59 | "copy-webpack-plugin": "^9.0.1",
60 | "cross-env": "^7.0.3",
61 | "css-loader": "^6.2.0",
62 | "dotenv": "^10.0.0",
63 | "dotenv-webpack": "^7.0.3",
64 | "eslint": "^7.32.0",
65 | "eslint-config-airbnb-base": "^14.2.1",
66 | "eslint-config-airbnb-typescript": "^14.0.0",
67 | "eslint-config-prettier": "^8.3.0",
68 | "eslint-plugin-import": "^2.24.2",
69 | "eslint-plugin-jsx-a11y": "^6.4.1",
70 | "eslint-plugin-prettier": "^4.0.0",
71 | "eslint-plugin-react": "^7.25.1",
72 | "eslint-plugin-react-hooks": "^4.2.0",
73 | "html-webpack-plugin": "^5.3.2",
74 | "husky": "^7.0.2",
75 | "lint-staged": "^11.1.2",
76 | "mini-css-extract-plugin": "^2.1.0",
77 | "postcss": "8.3.6",
78 | "postcss-flexbugs-fixes": "5.0.2",
79 | "postcss-import": "14.0.2",
80 | "postcss-loader": "6.1.1",
81 | "postcss-preset-env": "7.8.0",
82 | "postcss-pxtorem": "6.0.0",
83 | "prettier": "^2.4.0",
84 | "sass": "^1.55.0",
85 | "sass-loader": "^12.6.0",
86 | "style-loader": "^3.2.1",
87 | "style-resources-loader": "^1.4.1",
88 | "thread-loader": "^3.0.4",
89 | "webpack": "^5.74.0",
90 | "webpack-cli": "^4.10.0",
91 | "webpack-dev-server": "^3.11.3",
92 | "webpack-merge": "^5.8.0"
93 | },
94 | "browserslist": {
95 | "development": [
96 | "last 1 chrome version",
97 | "last 1 firefox version",
98 | "last 1 safari version"
99 | ],
100 | "production": [
101 | "ie > 10",
102 | ">1%",
103 | "not dead",
104 | "not op_mini all"
105 | ]
106 | },
107 | "lint-staged": {
108 | "*.{ts,tsx}": [
109 | "yarn run lint",
110 | "git add --force"
111 | ],
112 | "*.{md,json}": [
113 | "git add --force"
114 | ]
115 | },
116 | "engines": {
117 | "npm": ">=6.14.6",
118 | "node": ">=12.18.4"
119 | },
120 | "peerDependencies": {
121 | "eslint-config-airbnb-base": "^14.2.1"
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-flexbugs-fixes'),
4 | require('postcss-import'),
5 | require('postcss-preset-env'),
6 | require('postcss-pxtorem')({
7 | rootValue: 100,
8 | unitPrecision: 5,
9 | minPixelValue: 2, // 设置要替换的最小像素值
10 | propWhiteList: ['*'], // Enables converting of all properties – default is just font sizes.
11 | selectorBlackList: ['.ig-'], // 忽略的选择器 .ig- 表示 .ig- 开头的都不会转换
12 | }),
13 | ],
14 | };
15 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import routerConfig from './router/index';
3 | import RouterUI from '@/router/RouterUI';
4 | import AppContext from './common/AppContext';
5 | import { IAppContext } from '@/types/IContext';
6 |
7 | document.title = 'React App';
8 |
9 | class App extends React.Component {
10 | private a = 2;
11 |
12 | getLiveContextValue = (): IAppContext => ({
13 | token: '123',
14 | uid: 1233,
15 | test: this.test,
16 | });
17 |
18 | test = () => {
19 | console.log('aa=2=', this.a);
20 | };
21 |
22 | render() {
23 | console.log('abc331');
24 | return (
25 | <>
26 |
27 |
28 |
29 | >
30 | );
31 | }
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/assets/css/common.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/css/common.scss
--------------------------------------------------------------------------------
/src/assets/css/core.scss:
--------------------------------------------------------------------------------
1 | //注意,此文件只能存放变量,不允许写css样式,此文件为全局引入,写入css样式会出现多次打包问题。
2 |
3 | $ani: all 0.3s cubic-bezier(0.445, 0.05, 0.55, 0.95);
4 |
5 | @mixin absolute($top: 0, $right: 0, $bottom: 0, $left: 0) {
6 | position: absolute;
7 | top: $top;
8 | right: $right;
9 | left: $left;
10 | bottom: $bottom;
11 | }
12 |
13 | @mixin wh($w: 100%, $h: 100%) {
14 | width: $w;
15 | height: $h;
16 | }
17 |
18 | @mixin relative($w, $h) {
19 | position: relative;
20 | @include wh($w, $h);
21 | }
22 | @mixin ellipsis() {
23 | overflow: hidden;
24 | text-overflow: ellipsis; //文本溢出显示省略号
25 | white-space: nowrap; //文本不会换行(单行文本溢出)
26 | }
27 |
28 | @mixin ani($name: $ani) {
29 | transition: $name;
30 | }
31 |
32 | @mixin activeBg($url, $isDisabled: false, $size: 100% 100%, $repeat: no-repeat, $suffix: ".png", $pre: "~@images/") {
33 | background: url($pre + $url + $suffix) $repeat;
34 | background-size: $size;
35 | opacity: 1;
36 | &:hover {
37 | background: url($pre + $url + "-hover" + $suffix) $repeat;
38 | background-size: $size;
39 | opacity: 1;
40 | }
41 | &:active {
42 | background: url($pre + $url + "-active" + $suffix) $repeat;
43 | background-size: $size;
44 | opacity: 0.8;
45 | }
46 | @if $isDisabled {
47 | &.disabled {
48 | background: url($pre + $url + "-disabled" + $suffix) $repeat;
49 | background-size: $size;
50 | &:hover {
51 | background: url($pre + $url + "-disabled" + $suffix) $repeat;
52 | background-size: $size;
53 | }
54 | &:active {
55 | background: url($pre + $url + "-disabled" + $suffix) $repeat;
56 | background-size: $size;
57 | }
58 | }
59 | }
60 | }
61 |
62 | @mixin bgFill($url) {
63 | background: url("~@images/" + $url) no-repeat center;
64 | background-size: 100% 100%;
65 | }
66 |
67 | @mixin verticalAlign($width: 0, $height: 0, $border: 0, $fontSize: 0.1rem) {
68 | width: 2 * $width;
69 | height: 2 * $height;
70 | border-width: 2 * $border;
71 | font-size: 2 * $fontSize;
72 | line-height: 2 * $height;
73 | transform: scale(0.5, 0.5);
74 | transform-origin: center;
75 | }
76 |
77 | @mixin flex() {
78 | display: flex;
79 | flex-direction: row;
80 | }
81 | @mixin flex-col() {
82 | display: flex;
83 | flex-direction: column;
84 | }
85 |
--------------------------------------------------------------------------------
/src/assets/css/index.scss:
--------------------------------------------------------------------------------
1 | @import "./common.scss";
2 | @import "./init.scss";
3 |
4 | .text1 {
5 | font-size: 30px;
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/css/init.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | position: relative;
4 | margin: 0;
5 | font-family: "微软雅黑", "思源黑体", "黑体";
6 | -webkit-user-drag: none;
7 | user-select: none;
8 | width: 100%;
9 | height: 100%;
10 | }
11 | * {
12 | box-sizing: border-box;
13 | }
14 |
15 | h1 {
16 | font-size: 2em;
17 | margin: 0.67em 0;
18 | }
19 |
20 | ul,
21 | li {
22 | list-style: none;
23 | margin: 0;
24 | padding: 0;
25 | }
26 |
27 | a {
28 | color: #fff;
29 | background-color: transparent;
30 | }
31 |
32 | #root {
33 | position: relative;
34 | width: 100%;
35 | height: 100%;
36 | font-size: 25px;
37 | }
38 |
39 | ::-webkit-scrollbar {
40 | width: 6px;
41 | height: 10px;
42 | }
43 | ::-webkit-scrollbar-thumb {
44 | border-radius: 5px;
45 | box-shadow: inset 0 0 0.05rem rgba(69, 68, 73, 0.4);
46 | background: #82adda;
47 | }
48 | ::-webkit-scrollbar-track {
49 | box-shadow: inset 0 0 0.05rem rgba(69, 68, 73, 0.4);
50 | border-radius: 0;
51 | background: #0e2e6a;
52 | }
53 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/fonts/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
132 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/fonts/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/images/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/images/01.png
--------------------------------------------------------------------------------
/src/assets/images/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/images/02.png
--------------------------------------------------------------------------------
/src/assets/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/images/404.png
--------------------------------------------------------------------------------
/src/assets/images/tip-pop-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/assets/images/tip-pop-bg.png
--------------------------------------------------------------------------------
/src/assets/js/a.js:
--------------------------------------------------------------------------------
1 | const a=1;
--------------------------------------------------------------------------------
/src/common/AppContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentClass } from 'react';
2 | import BaseComponent from '@/components/BaseComponent';
3 | import TContext from './TContext';
4 | import { IAppContext } from '@/types/IContext';
5 |
6 | const AppContext = TContext({} as any);
7 |
8 | export default AppContext;
9 |
10 | export const { Consumer } = AppContext;
11 |
12 | export const { Provider } = AppContext;
13 |
14 | export function withAppContext(Com: React.ComponentType) {
15 | return class extends BaseComponent
{
16 | render() {
17 | const { props } = this;
18 | return (
19 | {context => }
20 | );
21 | }
22 | };
23 | }
24 |
25 | export type ComponentDecorator
= >(WrappedComponent: T) => T;
26 |
27 | // export const withRouter: ComponentDecorator = nativeWithRouter as any
28 |
29 | export const withAppContextDecorators: ComponentDecorator = withAppContext as any;
30 |
--------------------------------------------------------------------------------
/src/common/AppEvent.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 |
3 | const PREFIX = 'app_';
4 |
5 | export function getAppEventType(type: string) {
6 | return `${PREFIX}${type}`;
7 | }
8 |
9 | /**
10 | *
11 | * app全局事件触发
12 | * @class AppEventEmitterUtils
13 | */
14 | class AppEventEmitterUtils {
15 | private emitter: EventEmitter;
16 |
17 | constructor() {
18 | this.emitter = new EventEmitter();
19 | }
20 |
21 | off = (type, cb) => this.emitter.off(getAppEventType(type), cb);
22 |
23 | emit = (type, data?) => this.emitter.emit(getAppEventType(type), data);
24 |
25 | on = (type, cb) => this.emitter.on(getAppEventType(type), cb);
26 |
27 | destroy() {
28 | if (this.emitter) {
29 | // 移除全部监听
30 | this.emitter.removeAllListeners();
31 | }
32 | }
33 | }
34 | const appEventEmitter = new AppEventEmitterUtils();
35 | export default appEventEmitter;
36 |
37 | export enum AppEventType {
38 | InvalidToken = 'invalid-token',
39 | }
40 |
--------------------------------------------------------------------------------
/src/common/Resolution.ts:
--------------------------------------------------------------------------------
1 | import { DeviceType } from '@/types/enum';
2 |
3 | export default class Resolution {
4 | static DesignScreen = {
5 | [DeviceType.PC]: {
6 | WIDTH: 845,
7 | RATIO: 100,
8 | },
9 | [DeviceType.MOBILE]: {
10 | WIDTH: 750,
11 | RATIO: 100,
12 | },
13 | [DeviceType.TV]: {
14 | WIDTH: 1920,
15 | RATIO: 100,
16 | },
17 | };
18 |
19 | /**
20 | * 获取设计分辨率
21 | */
22 | static getDesign(deviceType: DeviceType = DeviceType.MOBILE) {
23 | return Resolution.DesignScreen[deviceType];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/common/TContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default function (initValue: T) {
4 | const context: React.Context = React.createContext(initValue);
5 | return context;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/BaseComponent/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import produce from 'immer';
3 |
4 | /**
5 | *
6 | *
7 | * @class BaseComponent
8 | * @extends {React.Component}
9 | * @template P
10 | * @template S
11 | */
12 |
13 | /**
14 | * UI基础组件
15 | */
16 | export default class BaseComponent
extends React.PureComponent
{
17 | constructor(props) {
18 | super(props);
19 | console.log('BaseComponent');
20 | }
21 |
22 | /**
23 | * 重写setState方法
24 | * @param obj
25 | * @param callBack
26 | */
27 | setState(
28 | obj:
29 | | ((prevState: Readonly, props: Readonly) => Pick | S | null)
30 | | (Pick | S | null),
31 | callBack?: () => void,
32 | ): void {
33 | let fun;
34 | if (obj.constructor === Object) {
35 | fun = draft => {
36 | const keys = Object.keys(obj);
37 | // eslint-disable-next-line
38 | keys.map(item => {
39 | // eslint-disable-next-line
40 | draft[item] = obj[item];
41 | });
42 | };
43 | } else if (obj.constructor === Function) {
44 | fun = obj;
45 | }
46 |
47 | super.setState(produce(fun), () => {
48 | if (callBack) {
49 | callBack();
50 | }
51 | });
52 | }
53 |
54 | componentDidCatch(error, info) {
55 | console.log('错误信息==', this.constructor.name, '=组件出错:', error, info);
56 | // 你同样可以将错误日志上报给服务器
57 | return false;
58 | }
59 |
60 | render() {
61 | return
;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/Button/ButtonCheck.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *按钮点击间隔检测
3 | *
4 | * @export
5 | * @class Check
6 | */
7 | class ButtonCheck {
8 | private timeDelay: number;
9 |
10 | private time: number;
11 |
12 | constructor(timeDelay = 300) {
13 | this.timeDelay = timeDelay;
14 | this.time = null;
15 | }
16 |
17 | setTimeDelay(timeDelay = 300) {
18 | this.timeDelay = timeDelay;
19 | }
20 |
21 | check() {
22 | const curTime = new Date().valueOf();
23 | if (this.time) {
24 | // 如果按钮检测
25 | if (curTime - this.time <= this.timeDelay) {
26 | return false;
27 | }
28 | }
29 | this.time = curTime;
30 | return true;
31 | }
32 | }
33 |
34 | export default ButtonCheck;
35 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseComponent from '../BaseComponent/index';
3 | import ButtonCheck from './ButtonCheck';
4 | import { setTimeout, classNames, isMobile } from '@/util/index';
5 |
6 | interface IProps {
7 | disabled?: boolean;
8 | className: string;
9 | statId?: number;
10 | clickId?: string;
11 | style?: any;
12 | onClick?: Function;
13 | statMap?: any;
14 | children?: any;
15 | onMouseLeave?: any;
16 | onMouseEnter?: any;
17 | delayTime?: number;
18 | stopPropagation?: boolean;
19 | }
20 | export default class Button extends BaseComponent {
21 | private curTimeout: any;
22 |
23 | private timeout: any;
24 |
25 | private buttonCheck;
26 |
27 | private isMob;
28 |
29 | static defaultProps = {
30 | disabled: false,
31 | };
32 |
33 | state = {
34 | clicked: false,
35 | };
36 |
37 | constructor(props) {
38 | super(props);
39 | this.buttonCheck = new ButtonCheck(props.delayTime);
40 | this.isMob = isMobile();
41 | }
42 |
43 | onItemClick = (e: any) => {
44 | const { stopPropagation, disabled, delayTime, onClick } = this.props;
45 | if (stopPropagation) {
46 | e.nativeEvent.stopImmediatePropagation();
47 | e.stopPropagation();
48 | e.preventDefault();
49 | }
50 | if (!this.buttonCheck.check()) return;
51 | try {
52 | if (disabled === true) return;
53 | this.curTimeout = setTimeout(() => {
54 | this.setState({ clicked: true });
55 | }, 10);
56 | if (this.timeout) {
57 | this.timeout();
58 | }
59 | this.timeout = setTimeout(() => {
60 | this.setState({ clicked: false });
61 | }, delayTime || 200);
62 | if (onClick) {
63 | onClick(e);
64 | }
65 | } catch (error) {
66 | console.log(error);
67 | }
68 | };
69 |
70 | componentWillUnmount = () => {
71 | if (this.curTimeout) {
72 | this.curTimeout();
73 | }
74 | if (this.timeout) {
75 | this.timeout();
76 | }
77 | };
78 |
79 | render() {
80 | const { className, style, statId, clickId, disabled, ...other } = this.props;
81 | const { clicked } = this.state;
82 | const styles = disabled || this.isMob ? style : { cursor: 'pointer', ...style };
83 | return (
84 |
90 | {this.props.children}
91 |
92 | );
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/DropDownMenu/index.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/components/DropDownMenu/index.scss
--------------------------------------------------------------------------------
/src/components/DropDownMenu/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseComponent from '../BaseComponent';
3 | import { isContains, addEventListenerWrap } from './popUtils';
4 | import './index.scss';
5 | import Button from '../Button';
6 |
7 | interface Props {
8 | /**
9 | * 按钮
10 | * @memberOf Props
11 | */
12 | renderBtnView: () => JSX.Element;
13 |
14 | /**
15 | *
16 | *
17 | * 弹窗内容
18 | * @memberOf Props
19 | */
20 | renderPopContentView: () => JSX.Element;
21 |
22 | /**
23 | *
24 | * 弹窗根节点范围
25 | * @memberOf Props
26 | */
27 | getRootDomNode?: () => HTMLElement;
28 |
29 | /**
30 | * 样式
31 | * @type {string}
32 | * @memberOf Props
33 | */
34 | className?: string;
35 | }
36 |
37 | interface State {
38 | /**
39 | *
40 | * 是否显示pop
41 | * @type {boolean}
42 | * @memberOf State
43 | */
44 | showPop: boolean;
45 | }
46 |
47 | /**
48 | *
49 | * 更多-下拉菜单弹窗
50 | * @export
51 | * @class TipPop
52 | * @extends {BaseComponent}
53 | */
54 | export default class DropDownMenu extends BaseComponent {
55 | private domListener = null;
56 |
57 | private popupRef = null;
58 |
59 | constructor(props) {
60 | super(props);
61 | this.state = {
62 | showPop: false,
63 | };
64 | this.popupRef = React.createRef();
65 | }
66 |
67 | componentDidUpdate(_privProps, prevState) {
68 | if (!prevState.showPop && this.state.showPop) {
69 | //弹窗状态发生改变,从隐藏到显示,添加监听器
70 | this.setListener();
71 | } else if (prevState.showPop && !this.state.showPop) {
72 | ////弹窗状态发生改变,从隐藏到显示,取消监听器
73 | this.cancelListener();
74 | }
75 | }
76 |
77 | /**
78 | *
79 | *
80 | * 设置监听
81 | * @memberOf DropDownMenu
82 | */
83 | setListener = () => {
84 | //获取根节点
85 | const rootDom = this.getRootDomNode();
86 | //默认取消一次监听
87 | this.cancelListener();
88 | this.domListener = addEventListenerWrap(
89 | rootDom,
90 | 'click',
91 | event => {
92 | const { target } = event;
93 | const root = this.getRootDomNode();
94 | const popupNode = this.getPopupDomNode();
95 | //判断是根节点框中的点击事件,并且不是弹窗区域,为任意点击消失区域。
96 | if (isContains(root, target) && !isContains(popupNode, target)) {
97 | console.log('直接关闭===', target, isContains(popupNode, target));
98 | //直接关闭
99 | this.hidePop();
100 | }
101 | },
102 | true,
103 | );
104 | };
105 |
106 | /**
107 | *
108 | *
109 | * 取消监听
110 | * @memberOf DropDownMenu
111 | */
112 | cancelListener = () => {
113 | if (this.domListener) {
114 | this.domListener?.remove();
115 | this.domListener = null;
116 | }
117 | };
118 |
119 | /**
120 | *
121 | * 获取pop弹窗节点
122 | * @returns
123 | *
124 | * @memberOf DropDownMenu
125 | */
126 | getPopupDomNode() {
127 | return this.popupRef.current || null;
128 | }
129 |
130 | /**
131 | *
132 | *
133 | * 获取默认根节点
134 | * @memberOf DropDownMenu
135 | */
136 | getRootDomNode = (): HTMLElement => {
137 | const { getRootDomNode } = this.props;
138 | if (getRootDomNode) {
139 | return getRootDomNode();
140 | }
141 | return window.document.body;
142 | };
143 |
144 | /**
145 | *
146 | *
147 | * 显示弹窗
148 | * @memberOf DropDownMenu
149 | */
150 | showPop = () => {
151 | const { showPop } = this.state;
152 | console.log('点击===', showPop);
153 | //这里弹窗打开,再次点击按钮,可以关闭弹窗
154 | if (showPop) {
155 | this.setState({
156 | showPop: false,
157 | });
158 | return;
159 | }
160 |
161 | this.setState({
162 | showPop: true,
163 | });
164 | };
165 |
166 | /**
167 | *
168 | *
169 | * 隐藏弹窗
170 | * @memberOf DropDownMenu
171 | */
172 | hidePop = () => {
173 | this.setState({
174 | showPop: false,
175 | });
176 | };
177 |
178 | render() {
179 | const { className } = this.props;
180 | const { showPop } = this.state;
181 | return (
182 |
183 |
186 | {showPop ? (
187 |
{this.props.renderPopContentView()}
188 | ) : null}
189 |
190 | );
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/components/DropDownMenu/popUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * 判断是否包含
4 | * @export
5 | * @param {(Node | null | undefined)} root
6 | * @param {Node} [n]
7 | * @returns
8 | */
9 | export function isContains(root: Node | null | undefined, n?: Node) {
10 | if (!root) {
11 | return false;
12 | }
13 |
14 | return root.contains(n);
15 | }
16 |
17 | /**
18 | *
19 | * 添加监听
20 | * @export
21 | * @param {any} target
22 | * @param {any} eventType
23 | * @param {any} cb
24 | * @param {any} [option]
25 | * @returns
26 | */
27 | export function addEventListenerWrap(target, eventType, cb, option?) {
28 | if (target.addEventListener) {
29 | target.addEventListener(eventType, cb, option);
30 | }
31 | return {
32 | remove: () => {
33 | if (target.removeEventListener) {
34 | target.removeEventListener(eventType, cb);
35 | }
36 | },
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary/GlobalError/index.scss:
--------------------------------------------------------------------------------
1 | .error-custom-modal-style {
2 | background-color: #1766bf;
3 | z-index: 9999;
4 | background-size: 100% 100%;
5 | }
6 | .error-custom-style {
7 | height: 100%;
8 | width: 100%;
9 | background-size: 100% 100%;
10 | }
11 |
12 | .error-boundary-modal-overlay {
13 | position: absolute;
14 | top: 0;
15 | width: 100%;
16 | bottom: 0;
17 | }
18 |
19 | .error-boundary-modal-overlay::before {
20 | width: 100%;
21 | height: 100%;
22 | content: "";
23 | position: absolute;
24 | top: 0;
25 | left: 0;
26 | z-index: -1;
27 | }
28 |
29 | /* 禁止图片拖拽*/
30 | .error-boundary-modal-overlay {
31 | position: absolute;
32 | top: 0;
33 | width: 100%;
34 | bottom: 0;
35 | &::before {
36 | // background: rgba(0, 0, 0, 0.4);
37 | // background-size: 100% 100%;
38 | width: 100%;
39 | height: 100%;
40 | content: "";
41 | position: absolute;
42 | top: 0;
43 | left: 0;
44 | z-index: -1;
45 | }
46 | }
47 | /* Error Boundary */
48 | .error-boundary {
49 | font-size: 30px;
50 | position: absolute;
51 | top: 50%;
52 | left: 50%;
53 | transform: translate(-50%, -50%);
54 | width: 100%;
55 | text-align: center;
56 | color: #fff;
57 | &:focus {
58 | outline: none;
59 | }
60 | .reload {
61 | margin-left: 10px;
62 | color: #9810fd;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary/GlobalError/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { classNames } from '@/util/index';
3 | import Modal from 'react-modal';
4 | import './index.scss';
5 |
6 | export interface IGlobalErrorProps {
7 | message?: string;
8 | isDialog?: boolean;
9 | }
10 |
11 | const onReload = (e: React.MouseEvent) => {
12 | e.preventDefault();
13 | window.location.href = window.location.href.split('#')[0];
14 | };
15 |
16 | const GlobalError: React.FC = ({ message, isDialog }) => {
17 | if (isDialog === false) {
18 | return (
19 |
20 |
21 | {message}
22 |
23 | 刷新
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | return (
31 |
37 |
38 | {message}
39 |
40 | 刷新
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | GlobalError.defaultProps = {
48 | message: '页面崩溃啦!',
49 | };
50 |
51 | export default GlobalError;
52 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary/ViewError/index.scss:
--------------------------------------------------------------------------------
1 | .error-custom-style-page {
2 | position: relative;
3 | height: 100%;
4 | width: 100%;
5 | font-size: 20px;
6 | .error-custom-style-mantle-page {
7 | position: relative;
8 | width: 100%;
9 | height: 100%;
10 | .error-boundary-page {
11 | width: 100%;
12 | position: absolute;
13 | top: 50%;
14 | left: 50%;
15 | transform: translate(-50%, -50%);
16 | width: 100%;
17 | text-align: center;
18 | color: #fff;
19 | &:focus {
20 | outline: none;
21 | }
22 | .reload-page {
23 | margin-left: 10px;
24 | color: #9810fd;
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary/ViewError/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.scss';
3 |
4 | export interface IViewErrorProps {
5 | message?: string;
6 | }
7 |
8 | const onReload = (e: React.MouseEvent) => {
9 | e.preventDefault();
10 | window.location.href = window.location.href.split('#')[0];
11 | };
12 |
13 | const ViewError: React.FC = ({ message }) => (
14 |
15 |
16 |
17 | {message}
18 |
19 | 刷新
20 |
21 |
22 |
23 |
24 | );
25 |
26 | ViewError.defaultProps = {
27 | message: '加载失败,请重试!',
28 | };
29 |
30 | export default ViewError;
31 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GlobalError from './GlobalError';
3 | import ViewError from './ViewError';
4 |
5 | type ErrComType = 'view' | 'global';
6 |
7 | interface IErrorBoundaryProps {
8 | type: ErrComType;
9 | message?: string;
10 | isDialog?: boolean;
11 | }
12 |
13 | interface State {
14 | hasError: boolean;
15 | error?: any;
16 | }
17 |
18 | export default class ErrorBoundary extends React.Component {
19 | static defaultProps: IErrorBoundaryProps = {
20 | type: 'global',
21 | message: '页面崩溃啦!',
22 | isDialog: false,
23 | };
24 |
25 | constructor(props) {
26 | super(props);
27 | this.state = {
28 | hasError: false,
29 | };
30 | }
31 |
32 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
33 | static getDerivedStateFromError(_error: Error) {
34 | return { hasError: true };
35 | }
36 |
37 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
38 | // componentDidCatch(error: Error, errorInfo: ErrorInfo) {
39 | componentDidCatch(error: any, errorInfo: any) {
40 | console.log('ErrorBoundary', error, errorInfo);
41 | this.setState({
42 | error,
43 | });
44 | //TODO 上报错误日志
45 | }
46 |
47 | getErrorComponent(type: ErrComType) {
48 | switch (type) {
49 | case 'view':
50 | return ViewError;
51 | default:
52 | return GlobalError;
53 | }
54 | }
55 |
56 | renderErrorComponent() {
57 | // const { message } = this.props;
58 | // const { error } = this.state;
59 | // const Com = this.getErrorComponent(type);
60 |
61 | return 123
;
62 | //return this.getErrorComponent()
63 | // return ;
64 | }
65 |
66 | render() {
67 | if (this.state.hasError) {
68 | return this.renderErrorComponent();
69 | }
70 | return this.props.children;
71 | }
72 | }
73 |
74 | export function withErrorBoundary(Com: any, options: IErrorBoundaryProps & T) {
75 | return (props: any) => (
76 |
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/NoMatch/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NoMatch = () => No Match
;
4 |
5 | export default NoMatch;
6 |
--------------------------------------------------------------------------------
/src/components/Page/index.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/components/Page/index.scss
--------------------------------------------------------------------------------
/src/components/Page/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './index.scss';
3 | import BaseComponent from '@/components/BaseComponent';
4 | import { RouteComponentProps } from 'react-router-dom';
5 |
6 | /**
7 | *
8 | * 页面基础组件
9 | * @class Page
10 | * @extends {BaseComponent}
11 | */
12 | class Page extends BaseComponent
{
13 | constructor(props) {
14 | super(props);
15 | //TODO 可以加入页面访问统计等需求
16 | console.log('page');
17 | }
18 |
19 | render() {
20 | return
;
21 | }
22 | }
23 |
24 | export default Page;
25 |
--------------------------------------------------------------------------------
/src/components/ResolutionCom/ResolutionComp.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Resolutions from '@/common/Resolution';
3 |
4 | interface IResolutionComProps {
5 | WIDTH?: number;
6 | }
7 |
8 | export default class ResolutionCom extends React.Component {
9 | componentDidMount(): void {
10 | this.resize();
11 | window.onresize = this.resize;
12 | }
13 |
14 | resize = (): void => {
15 | //获取设计稿的尺寸
16 | const design = Resolutions.getDesign();
17 | const html = document.documentElement;
18 | const getHtmlFs = () => parseFloat(window.getComputedStyle(html, null)['font-size']);
19 | const getScreenWidth = () => {
20 | let htmlWidth = 0;
21 | try {
22 | const htmlElement = document.documentElement;
23 | htmlWidth = Math.max(
24 | htmlElement.offsetWidth || 0,
25 | htmlElement.clientWidth || 0,
26 | htmlElement.getBoundingClientRect().width || 0,
27 | );
28 | // 读取失败,其他方式读取
29 | if (!htmlWidth || htmlWidth <= 0) {
30 | if (window.orientation == 180 || window.orientation == 0) {
31 | //竖屏
32 | htmlWidth =
33 | window.innerWidth ||
34 | (window.screen && window.screen.width) ||
35 | (window.screen && window.screen.availWidth) ||
36 | 0;
37 | } else if (window.orientation == 90 || window.orientation == -90) {
38 | //横屏
39 | htmlWidth =
40 | window.innerHeight ||
41 | (window.screen && window.screen.height) ||
42 | (window.screen && window.screen.availHeight) ||
43 | 0;
44 | }
45 | }
46 | } catch (e) {
47 | console.log('获取屏幕宽度出错');
48 | }
49 | return htmlWidth | 0;
50 | };
51 | const WIDTH = this.props.WIDTH || design.WIDTH,
52 | //第一次进来没有设置过html标签font-size的时候
53 | screenWidth = getScreenWidth(),
54 | htmlFs = getHtmlFs(),
55 | mediaFs = (design.RATIO / WIDTH) * screenWidth; //获取页面宽度 设备宽度/fontSize=设计稿(750)/100=7.5;
56 |
57 | html.style.fontSize = `${mediaFs}px`; //根据页面大小算出font-size
58 |
59 | //以下是特殊处理 试过一台htc下的某个浏览器设置字体大小后再获取font-size会比所设的值会相对变小 所以设置大一点让它font-size的结果是想设的结果
60 |
61 | if (htmlFs !== mediaFs && Math.abs(htmlFs - mediaFs) > 2) {
62 | html.style.fontSize = '100px';
63 | html.style.fontSize = `${(100 / getHtmlFs()) * mediaFs}px`;
64 | }
65 | };
66 |
67 | render(): React.ReactNode {
68 | return this.props.children;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "iconfont";
3 | src: url("./assets/fonts/iconfont.ttf");
4 | }
5 |
6 | html,
7 | body {
8 | height: 100%;
9 | font-family: "iconfont" !important;
10 | margin: 0;
11 | color: #fff;
12 | }
13 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from '@/App';
4 | import { Provider } from 'react-redux';
5 | import history from '@/store/reducers/history';
6 | import configureStore from '@/store';
7 | import { ConnectedRouter } from 'connected-react-router';
8 | import './index.scss';
9 | import '@/assets/css/index.scss';
10 | import ResolutionCom from '@/components/ResolutionCom/ResolutionComp';
11 | import ErrorBoundary from './components/ErrorBoundary';
12 | import Modal from 'react-modal';
13 |
14 | try {
15 | const store = configureStore({});
16 | const rootElement = document.getElementById('root');
17 |
18 | Modal.setAppElement(rootElement);
19 |
20 | const renderApp = () => {
21 | ReactDOM.render(
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ,
31 | document.getElementById('root'),
32 | );
33 | };
34 | renderApp();
35 | } catch (e) {
36 | console.log('e');
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Home/HomeChild/index.scss:
--------------------------------------------------------------------------------
1 | .home-child {
2 | position: relative;
3 | width: 200px;
4 | height: 200px;
5 | background: red;
6 | color: #fff;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/Home/HomeChild/index.tsx:
--------------------------------------------------------------------------------
1 | import connect from '@/store/connect';
2 | import React from 'react';
3 | import BaseComponent from '@/components/BaseComponent';
4 | import { push } from 'connected-react-router';
5 |
6 | import './index.scss';
7 |
8 | interface IHomeChildProps {
9 | pathname?: string;
10 | search?: string;
11 | hash?: string;
12 | push?: (path: string, s?: any) => void;
13 | }
14 |
15 | const mapStateToProps = {
16 | pathname: 'router.location.pathname',
17 | search: 'router.location.search',
18 | hash: 'router.location.hash',
19 | };
20 |
21 | const mapDispatchToProps = {
22 | push,
23 | };
24 |
25 | @connect(mapStateToProps, mapDispatchToProps)
26 | class HomeChild extends BaseComponent {
27 | handleGoPage = () => {
28 | this.props.push('/page1', { a: 1 });
29 | };
30 |
31 | render() {
32 | const { pathname, search, hash } = this.props;
33 | return (
34 |
35 |
pathname::{pathname}
36 |
search::{search}
37 |
hash::{hash}
38 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default HomeChild;
47 |
--------------------------------------------------------------------------------
/src/pages/Home/index.scss:
--------------------------------------------------------------------------------
1 | .home {
2 | position: relative;
3 | width: 100%;
4 | .bg1 {
5 | position: relative;
6 | width: 200px;
7 | height: 300px;
8 | @include bgFill("01.png");
9 | }
10 | .img1 {
11 | position: absolute;
12 | right: 0;
13 | top: 0;
14 | width: 200px;
15 | height: 300px;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Page from '@/components/Page';
3 | import { UPDATE_USER_ID } from '@/store/actions/user';
4 | import connect from '@/store/connect';
5 | import CPng from '@/assets//images/02.png';
6 | import HomeChild from './HomeChild';
7 | import { withAppContextDecorators } from '@/common/AppContext';
8 | import './index.scss';
9 | import { IAppContext } from '@/types/IContext';
10 |
11 | interface IHomeProps {
12 | userId: number;
13 | updateId: (id: number) => void;
14 | }
15 |
16 | interface IHomeState {
17 | count: number;
18 | }
19 |
20 | const mapStateToProps = {
21 | userId: 'user.userId',
22 | };
23 |
24 | const mapDispatchToProps = {
25 | updateId: UPDATE_USER_ID,
26 | };
27 |
28 | type HomeProps = IHomeProps & IAppContext;
29 | //装饰器由下到上依次调用结果函数
30 | //参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
31 | @withAppContextDecorators
32 | @connect(mapStateToProps, mapDispatchToProps)
33 | class Home extends Page {
34 | //重要,代表这个IAppContext是默认自带的,不需要外界传入,直接注入的。
35 | static defaultProps: IAppContext;
36 |
37 | constructor(props) {
38 | super(props);
39 | this.state = {
40 | count: 0,
41 | };
42 | }
43 |
44 | componentDidMount = () => {
45 | setTimeout(() => {
46 | this.props.updateId(5);
47 | }, 3000);
48 | console.log(this.props.test());
49 | this.setState({
50 | count: 1,
51 | });
52 | };
53 |
54 | render() {
55 | const { userId } = this.props;
56 | const { count } = this.state;
57 | console.log('this.props==', this.props);
58 | return (
59 |
60 |
Hel4lo, World1!{userId}
61 |
{count}
62 |

63 |
64 |
65 | );
66 | }
67 | }
68 |
69 | export default Home;
70 |
--------------------------------------------------------------------------------
/src/pages/Page1/index.scss:
--------------------------------------------------------------------------------
1 | .page1 {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | background: red;
6 | color: #fff;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/Page1/index.tsx:
--------------------------------------------------------------------------------
1 | import Page from '@/components/Page';
2 | import React from 'react';
3 | import connect from '@/store/connect';
4 | import { goBack, CallHistoryMethodAction } from 'connected-react-router';
5 | import './index.scss';
6 |
7 | interface IPageProps {
8 | pathname: string;
9 | search: string;
10 | hash: string;
11 | goBack: () => CallHistoryMethodAction;
12 | }
13 |
14 | const mapStateToProps = {
15 | pathname: 'router.location.pathname',
16 | search: 'router.location.search',
17 | hash: 'router.location.hash',
18 | };
19 |
20 | const mapDispatchToProps = {
21 | goBack: goBack,
22 | };
23 |
24 | @connect(mapStateToProps, mapDispatchToProps)
25 | class Page1 extends Page {
26 | componentDidMount = () => {
27 | console.log('获取到的location===', this.props);
28 | };
29 |
30 | handleGoBack = () => {
31 | this.props.goBack();
32 | };
33 |
34 | render() {
35 | return (
36 |
37 |
Hello, Page122222!
38 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default Page1;
47 |
--------------------------------------------------------------------------------
/src/router/RouterUI.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import BaseComponent from '@/components/BaseComponent';
3 | import { Route, Switch, withRouter, RouteComponentProps } from 'react-router-dom';
4 | import { IRouterPage } from '@/types/IRouterPage';
5 | import NoMatch from '@/components/NoMatch';
6 |
7 | interface IRouterUIProps {
8 | routers: IRouterPage[];
9 | }
10 |
11 | type IProps = IRouterUIProps & RouteComponentProps;
12 |
13 | /**
14 | *
15 | * 渲染路由
16 | * @class RouterUI
17 | * @extends {BaseComponent}
18 | */
19 | class RouterUI extends BaseComponent {
20 | /**
21 | * 生成router
22 | * @param {*} routers
23 | * @param {*} container
24 | * @param {*} recur 是否递归
25 | */
26 | renderRouter = (routers: IRouterPage[] = []) =>
27 | routers.map(router => {
28 | let { path, exact } = router;
29 | return (
30 | this.renderPage(router)}
35 | />
36 | );
37 | });
38 |
39 | renderPage = (router: IRouterPage) => {
40 | const { component, path, loadingFallback } = router;
41 | const Page = component;
42 | return (
43 |
44 |
45 |
46 | );
47 | };
48 |
49 | render() {
50 | const { routers } = this.props;
51 | return (
52 |
53 | {this.renderRouter(routers)}
54 |
55 |
56 | );
57 | }
58 | }
59 |
60 | export default withRouter(RouterUI);
61 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // named imports for React.lazy: https://github.com/facebook/react/issues/14603#issuecomment-726551598
4 | export function lazyImport(name) {
5 | return React.lazy(
6 | () => import(/* webpackChunkName: "[request]",webpackPrefetch: true */ `@/pages/${name}`),
7 | );
8 | }
9 |
10 | // 使用[request] 支持实际解析的文件名
11 | // React.lazy(
12 | // () => import(/* webpackChunkName: "page1",webpackPrefetch: true */ '@/pages/Page1'),
13 | // )
14 |
15 | export default {
16 | routes: [
17 | {
18 | exact: true,
19 | path: '/',
20 | isDynamic: true,
21 | component: lazyImport('Home'),
22 | },
23 | {
24 | exact: true,
25 | path: '/page1',
26 | isDynamic: true,
27 | component: lazyImport('Page1'),
28 | },
29 | ],
30 | };
31 |
--------------------------------------------------------------------------------
/src/services/BaseService.ts:
--------------------------------------------------------------------------------
1 | import { AxiosRequestConfig } from 'axios';
2 | import getConfig from './serviceConfig';
3 | import { get, post } from './axios';
4 |
5 | const apiConfig = getConfig();
6 |
7 | export default class BaseService {
8 | private baseUrl: string;
9 |
10 | constructor(baseUrl: string) {
11 | this.baseUrl = baseUrl;
12 | }
13 |
14 | private handelGetUrl = (url: string, params: any): string => {
15 | let curUrl = url;
16 | if (params) {
17 | const paramsArray: string[] = [];
18 | Object.keys(params).forEach(key => {
19 | if (params[key] !== undefined) {
20 | paramsArray.push(`${key}=${params[key]}`);
21 | }
22 | });
23 | if (url.search(/\?/) === -1) {
24 | curUrl += `?${paramsArray.join('&')}`;
25 | } else {
26 | curUrl += `&${paramsArray.join('&')}`;
27 | }
28 | }
29 | return curUrl;
30 | };
31 |
32 | getFullPath = (path: string) => `${this.baseUrl}/${path}`;
33 |
34 | getByQuery = (path: string, data: any = {}, config?: AxiosRequestConfig): Promise => {
35 | const curPath = this.handelGetUrl(path, data);
36 | return this.get(curPath, config);
37 | };
38 |
39 | get = (path: string, config?: AxiosRequestConfig): Promise =>
40 | get(this.getFullPath(path), config);
41 |
42 | post = (path: string, data: any = {}, config?: AxiosRequestConfig): Promise =>
43 | post(this.getFullPath(path), data, config);
44 | }
45 |
46 | export const apiService = new BaseService(apiConfig.API_SERVER);
47 |
--------------------------------------------------------------------------------
/src/services/ServerResponseManager.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError, AxiosResponse } from 'axios';
2 |
3 | /**
4 | * 针对请求成功:返回的 code 码做不同的响应处理
5 | */
6 | class ServerResponseSuccessManager {
7 | /**
8 | * 状态码解析器
9 | * @param response
10 | */
11 | codeParser(response: AxiosResponse) {
12 | const code = response?.data?.errCode;
13 | const resData = response?.data?.data;
14 | const parser = {
15 | '10010': () => {
16 | this.handleCodeIs10010(resData);
17 | },
18 | default: () => console.log('code 无法识别'),
19 | };
20 | return parser[code] ? parser[code]() : parser.default;
21 | }
22 |
23 | /**
24 | * 状态码为 10010 的响应处理
25 | * @param resData
26 | */
27 | handleCodeIs10010(resData) {
28 | if (resData === 'TOKEN_INVALID') {
29 | //appEventEmitter.emit("live-token-invalid");
30 | // setTimeout(() => {
31 | // window.location.href = '/login';
32 | // }, 1000);
33 | }
34 | }
35 | }
36 |
37 | /**
38 | * 针对请求失败的响应处理
39 | */
40 | class ServerResponseFailedManager {
41 | /**
42 | * 请求失败时,需要提示的信息
43 | */
44 | getErrorMessage(error: AxiosError) {
45 | console.error('error.response==', error.response);
46 | }
47 | }
48 |
49 | export const serverResponseSuccessManager = new ServerResponseSuccessManager();
50 | export const serverResponseFailedManager = new ServerResponseFailedManager();
51 |
--------------------------------------------------------------------------------
/src/services/api/index.ts:
--------------------------------------------------------------------------------
1 | import * as user from './user';
2 |
3 | // api 中的 key 以单个模块命名,防止接口重名
4 | export const api = {
5 | user,
6 | };
7 |
--------------------------------------------------------------------------------
/src/services/api/user/index.ts:
--------------------------------------------------------------------------------
1 | import { apiService } from '@/services/BaseService';
2 | import { IResponseData } from '@/types/service/responseData';
3 | import { IUserNameAndHeadInfo } from '@/types/service/user';
4 |
5 | // 获取用户信息
6 | export const getUserInfo = (id: number) =>
7 | apiService.post>('action-common/app/getUserInfo', { id });
8 |
--------------------------------------------------------------------------------
/src/services/axios.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosRequestConfig, AxiosResponse, AxiosError, AxiosInstance } from 'axios';
2 | import {
3 | serverResponseFailedManager,
4 | serverResponseSuccessManager,
5 | } from '@/services/ServerResponseManager';
6 |
7 | const axiosInstance: AxiosInstance = axios.create({
8 | timeout: 15000,
9 | headers: { 'Content-Type': 'application/json' },
10 | });
11 |
12 | //axiosInstance.defaults.headers.common.platform = "PC";
13 |
14 | /**
15 | * request 拦截器
16 | */
17 | axiosInstance.interceptors.request.use(
18 | (config: AxiosRequestConfig): AxiosRequestConfig => {
19 | // config.url = host + config.url;
20 | if (!navigator.onLine) {
21 | //toast("网络连接已断开,请稍后重试");
22 | }
23 | return config;
24 | },
25 | (error: AxiosError) => Promise.reject(error),
26 | );
27 |
28 | /**
29 | * response 拦截器
30 | */
31 | axiosInstance.interceptors.response.use(
32 | (response: AxiosResponse) => {
33 | if (response.data.code === 200) {
34 | return response;
35 | }
36 | // 针对请求成功:返回的 code 码做不同的响应
37 | serverResponseSuccessManager.codeParser(response);
38 | },
39 | (error: AxiosError) => {
40 | // 针对请求失败:应该提示的错误信息
41 | serverResponseFailedManager.getErrorMessage(error);
42 | return Promise.reject(error.response);
43 | },
44 | );
45 |
46 | export function setAuthorization(token) {
47 | console.log('request:setAuthorization', token);
48 | axiosInstance.defaults.headers.common.Authorization = token;
49 | }
50 |
51 | // TODO:: add 拦截器(拦截之后返回T??),预设等等,
52 | export function get(url: string, config?: AxiosRequestConfig): Promise {
53 | return axiosInstance.get(url, config).then((res: AxiosResponse) => res && res.data);
54 | }
55 |
56 | export function post(url: string, data: any, config?: AxiosRequestConfig): Promise {
57 | return axiosInstance.post(url, data, config).then((res: AxiosResponse) => res.data);
58 | }
59 |
60 | export function getBlob(url: string): Promise {
61 | return axios
62 | .get(url, {
63 | responseType: 'blob',
64 | })
65 | .then(res => res.data);
66 | }
67 |
68 | export default axiosInstance;
69 |
--------------------------------------------------------------------------------
/src/services/serviceConfig.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: quanyj
3 | * @Date: 2021-08-09 11:21:01
4 | * @Last Modified by: quanyj
5 | * @Last Modified time: 2021-09-16 16:32:13
6 | * 服务配置获取
7 | */
8 |
9 | const CONFIG = {
10 | API_SERVER: process.env.C_API_SERVER,
11 | };
12 |
13 | export default function getConfig() {
14 | return CONFIG;
15 | }
16 |
--------------------------------------------------------------------------------
/src/store/actionCreaters/actionCreator.ts:
--------------------------------------------------------------------------------
1 | import { IActionParam } from '@/types/IRedux';
2 | import { Dispatch } from 'redux';
3 | import { getStore } from '..';
4 |
5 | let innerDispatch: Dispatch;
6 |
7 | function getDispatch() {
8 | if (!innerDispatch) {
9 | innerDispatch = getStore().dispatch;
10 | }
11 | return innerDispatch;
12 | }
13 |
14 | export default function actionCreator(actionType?: string) {
15 | return function fn(_target: any, name: string, descriptor: PropertyDescriptor): void {
16 | // console.log("messageMethod", target);
17 | console.log('actionCreator', actionType, name, descriptor);
18 |
19 | const type = actionType || name;
20 | // eslint-disable-next-line no-param-reassign
21 | descriptor.value = (payload: any) => {
22 | getDispatch()({
23 | type,
24 | payload,
25 | });
26 | };
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/store/actionCreaters/index.ts:
--------------------------------------------------------------------------------
1 | import BaseUserInfoCreator from './user';
2 |
3 | export const userInfoActions = new BaseUserInfoCreator();
4 |
--------------------------------------------------------------------------------
/src/store/actionCreaters/user.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-empty-function */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | /* eslint-disable class-methods-use-this */
4 | import { UPDATE_USER_ID, UPDATE_SSO_TOKEN } from '@/store/actions/user';
5 | import actionCreator from './actionCreator';
6 |
7 | export default class BaseUserInfo {
8 | @actionCreator(UPDATE_USER_ID)
9 | updateUserJJUid(_data: number) {}
10 |
11 | @actionCreator(UPDATE_SSO_TOKEN)
12 | updateUserToken(_data: string) {}
13 | }
14 |
--------------------------------------------------------------------------------
/src/store/actions/user.ts:
--------------------------------------------------------------------------------
1 | export const UPDATE_SSO_TOKEN = 'UPDATE_SSO_TOKEN';
2 | export const UPDATE_USER_ID = 'UPDATE_USER_ID';
3 |
4 | export const UPDATE_USER_NAME_HEAD_ID = 'UPDATE_USER_NAME_HEAD_ID';
5 |
--------------------------------------------------------------------------------
/src/store/connect.ts:
--------------------------------------------------------------------------------
1 | import * as rr from 'react-redux';
2 | import * as _ from 'lodash';
3 |
4 | const mapStateToProps = (filter: any) => (state: any) => {
5 | const keys = Object.keys(filter);
6 | if (!filter || keys.length === 0) {
7 | return state;
8 | }
9 | const retState = keys.reduce((obj, name: string) => {
10 | const path = filter[name];
11 | if (typeof path === 'object') {
12 | // eslint-disable-next-line
13 | obj[name] = mapStateToProps(path)(state[name]);
14 | } else {
15 | // eslint-disable-next-line
16 | obj[name] = typeof path === 'boolean' ? _.get(state, name) : _.get(state, path);
17 | }
18 | return obj;
19 | }, {});
20 | return retState;
21 | };
22 |
23 | const mapDispatchToProps = (filter: Record) => dispatch => {
24 | const keys = Object.keys(filter);
25 | if (!filter || keys.length === 0) {
26 | return {};
27 | }
28 | const ret = Object.keys(filter).reduce((obj, name) => {
29 | const path = filter[name];
30 | if (typeof path !== 'string') {
31 | obj[name] = (...args) => {
32 | dispatch(path(...args));
33 | };
34 | } else {
35 | const paths = path.split('.');
36 | const type = paths.length === 1 ? paths[0] : paths[paths.length - 1];
37 | // eslint-disable-next-line
38 | obj[name] = payload => dispatch({ type, payload });
39 | }
40 |
41 | return obj;
42 | }, {});
43 |
44 | return ret;
45 | };
46 |
47 | const connect =
48 | (stateFilter: any = {}, dispatchFilter: any = {}): Function =>
49 | target => {
50 | const ret = rr.connect(mapStateToProps(stateFilter), mapDispatchToProps(dispatchFilter), null, {
51 | forwardRef: true,
52 | })(target);
53 | ret.compName = target.name;
54 | return ret;
55 | };
56 |
57 | export default connect;
58 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore, Store, compose, applyMiddleware } from 'redux';
2 | import rootReducer from './reducers/index';
3 | import { routerMiddleware } from 'connected-react-router';
4 | import history from './reducers/history';
5 | import { IActionParam } from '@/types/IRedux';
6 |
7 | let store = null;
8 | export function getStore() {
9 | return store as Store<
10 | any,
11 | {
12 | type: string;
13 | payload?: any;
14 | }
15 | >;
16 | }
17 |
18 | export default function configureStore(preloadedState?: any): Store {
19 | const composeEnhancer: typeof compose =
20 | (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
21 | store = createStore(
22 | rootReducer, // root reducer with router state
23 | preloadedState,
24 | composeEnhancer(
25 | applyMiddleware(
26 | routerMiddleware(history), // 用于 dispatching history actions,
27 | ),
28 | ),
29 | );
30 |
31 | console.log('process.env.ENV ===', process.env.ENV);
32 | if (process.env.ENV == 'dev') {
33 | // eslint-disable-next-line no-underscore-dangle
34 | (window as any).__store = store;
35 | }
36 |
37 | return store;
38 | }
39 |
--------------------------------------------------------------------------------
/src/store/reducers/history.ts:
--------------------------------------------------------------------------------
1 | import { createHashHistory } from 'history';
2 | import produce from 'immer';
3 | import { LOCATION_CHANGE } from 'connected-react-router';
4 | import { IActionParam } from '@/types/IRedux';
5 |
6 | let history = createHashHistory();
7 | export default history;
8 |
9 | //import { push } from 'connected-react-router';
10 | //提供了push,go,goBack,replace,block,goForward方法。
11 | //push("/home") || push({pathname:"/home",search:"name=1",hash:"1"})
12 | //history 可以分为两部分,切换和修改,切换历史状态:back,forward,go对应浏览器的后退,跳转,前进。history.go(2);//前进两次
13 |
14 | //push 把页面状态保存在state对象中,当页面回来的时候,可以通过event.state获取到state对象。
15 |
16 | //查看connected-react-router 的connectRouter 方法,使immer与history 结合使用
17 | export interface HistoryState {
18 | location: any;
19 | action: any;
20 | }
21 |
22 | const injectQuery = location => {
23 | if (location && location.query) {
24 | // Don't inject query if it already exists in history
25 | return location;
26 | }
27 |
28 | const searchQuery = location && location.search;
29 |
30 | if (typeof searchQuery !== 'string' || searchQuery.length === 0) {
31 | return {
32 | ...location,
33 | query: {},
34 | };
35 | }
36 |
37 | // Ignore the `?` part of the search string e.g. ?username=codejockie
38 | const search = searchQuery.substring(1);
39 | // Split the query string on `&` e.g. ?username=codejockie&name=Kennedy
40 | const queries = search.split('&');
41 | // Contruct query
42 | const query = queries.reduce((acc, currentQuery) => {
43 | // Split on `=`, to get key and value
44 | const [queryKey, queryValue] = currentQuery.split('=');
45 | return {
46 | ...acc,
47 | [queryKey]: queryValue,
48 | };
49 | }, {});
50 |
51 | return {
52 | ...location,
53 | query,
54 | };
55 | };
56 |
57 | const initHistoryState: HistoryState = {
58 | location: injectQuery(history.location),
59 | action: history.action,
60 | };
61 |
62 | /* eslint-disable no-param-reassign */
63 | export const reducer = produce((draft: HistoryState, actionParam: IActionParam) => {
64 | if (actionParam.type === LOCATION_CHANGE) {
65 | const { location, action } = actionParam.payload;
66 | draft.action = action;
67 | draft.location = injectQuery(location);
68 | return draft;
69 | }
70 | return draft;
71 | }, initHistoryState);
72 |
--------------------------------------------------------------------------------
/src/store/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import user from './user';
3 | import { reducer } from './history';
4 |
5 | export default combineReducers({
6 | router: reducer, //必须是router connectRouter(history)
7 | user,
8 | });
9 |
--------------------------------------------------------------------------------
/src/store/reducers/user.ts:
--------------------------------------------------------------------------------
1 | import { IActionParam, IRequestStatus } from '@/types/IRedux';
2 | import produce from 'immer';
3 |
4 | import { UPDATE_SSO_TOKEN, UPDATE_USER_ID, UPDATE_USER_NAME_HEAD_ID } from '../actions/user';
5 |
6 | export interface UserState {
7 | status: IRequestStatus;
8 | token: string;
9 | SSOToken: string;
10 | userId: number;
11 | nickName: string;
12 | headId: number;
13 | }
14 |
15 | const initialState: UserState = {
16 | token: null,
17 | SSOToken: null,
18 | status: IRequestStatus.none,
19 | nickName: '',
20 | userId: 0,
21 | headId: 0,
22 | };
23 |
24 | /* eslint-disable no-param-reassign */
25 | const reducer = produce((draft: UserState, action: IActionParam) => {
26 | switch (action.type) {
27 | case UPDATE_USER_ID:
28 | console.log('当前的用户信息==UPDATE_USER_ID=', action.payload);
29 | draft.userId = action.payload;
30 | return draft;
31 | case UPDATE_SSO_TOKEN:
32 | console.log('当前的用户信息==UPDATE_SSO_TOKEN=', action.payload);
33 | // draft.SSOToken = action.payload;
34 | draft.token = action.payload;
35 | return draft;
36 | case UPDATE_USER_NAME_HEAD_ID:
37 | draft.nickName = action.payload.name;
38 | draft.headId = action.payload.headId;
39 | return draft;
40 | default:
41 | return draft;
42 | }
43 | }, initialState);
44 |
45 | export default reducer;
46 |
--------------------------------------------------------------------------------
/src/types/IContext.ts:
--------------------------------------------------------------------------------
1 | export interface IAppContext {
2 | /**
3 | * 用户ID
4 | */
5 | uid?: number;
6 |
7 | /**
8 | * token
9 | */
10 | token?: string;
11 |
12 | test: () => void;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/IRedux.ts:
--------------------------------------------------------------------------------
1 | import { AnyAction } from 'redux';
2 |
3 | export interface IActionParam extends AnyAction {
4 | type: string;
5 | payload: any;
6 | }
7 |
8 | export enum IRequestStatus {
9 | none = 'none',
10 | done = 'done',
11 | error = 'error',
12 | requesting = 'requesting',
13 | }
14 |
15 | export default {
16 | IRequestStatus,
17 | };
18 |
--------------------------------------------------------------------------------
/src/types/IRouterPage.ts:
--------------------------------------------------------------------------------
1 | export interface IRouterPage {
2 | /**
3 | * 页面组件
4 | *
5 | * @type {any}
6 | * @memberof IRouterPage
7 | */
8 | component?: any;
9 | /**
10 | * 当前路由路径
11 | */
12 | path: string;
13 | /**
14 | * 是否严格匹配路由
15 | */
16 | exact?: boolean;
17 | /**
18 | * 动态加载路由时的提示文案
19 | */
20 | loadingFallback?: string;
21 | }
22 |
--------------------------------------------------------------------------------
/src/types/enum.ts:
--------------------------------------------------------------------------------
1 | export enum DeviceType {
2 | PC = 'pc',
3 | MOBILE = 'mobile',
4 | TV = 'tv',
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/service/responseData.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: quanyj
3 | * @Date: 2021-08-09 11:20:20
4 | * @Last Modified by: quanyj
5 | * @Last Modified time: 2021-09-16 16:33:00
6 | * 服务返回数据模型
7 | */
8 |
9 | export interface IResponseData {
10 | errCode: number;
11 | errMsg?: string;
12 | data: T;
13 | }
14 |
15 | export type RSBoolean = IResponseData;
16 |
17 | export type RSString = IResponseData;
18 |
--------------------------------------------------------------------------------
/src/types/service/user.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: quanyj
3 | * @Date: 2021-08-09 11:19:50
4 | * @Last Modified by: quanyj
5 | * @Last Modified time: 2021-09-16 16:33:03
6 | * 服务返回用户模块数据模型
7 | */
8 |
9 | export interface IUserNameAndHeadInfo {
10 | name: string;
11 | headId: number;
12 | }
13 |
--------------------------------------------------------------------------------
/src/typings/custom.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qyjandroid/react-project-template/6309d4b15dbcbd7a2b108d14ab0954c8ce08a9c1/src/typings/custom.d.ts
--------------------------------------------------------------------------------
/src/typings/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 | declare module '*.scss';
4 | declare module '*.svg';
5 | declare module '*.png';
6 | declare module '*.jpg';
7 | declare module '*.jpeg';
8 | declare module '*.gif';
9 | declare module '*.bmp';
10 | declare module '*.tiff';
11 |
--------------------------------------------------------------------------------
/src/util/DomUtil.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * 判断是否包含
4 | * @export
5 | * @param {(Node | null | undefined)} root
6 | * @param {Node} [n]
7 | * @returns
8 | */
9 | export function isContains(root: Node | null | undefined, n?: Node) {
10 | if (!root) {
11 | return false;
12 | }
13 |
14 | return root.contains(n);
15 | }
16 |
17 | /**
18 | *
19 | * 添加监听
20 | * @export
21 | * @param {any} target
22 | * @param {any} eventType
23 | * @param {any} cb
24 | * @param {any} [option]
25 | * @returns
26 | */
27 | export function addEventListenerWrap(target, eventType, cb, option?) {
28 | if (target.addEventListener) {
29 | //捕获阶段,从上到下捕获,option传true
30 | target.addEventListener(eventType, cb, option);
31 | }
32 | return {
33 | remove: () => {
34 | if (target.removeEventListener) {
35 | target.removeEventListener(eventType, cb);
36 | }
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/src/util/generateId.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 生成随机的 UUID
3 | * @param len
4 | * @param radix
5 | */
6 | export function generateRandomUUID(len: number, radix: number) {
7 | let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
8 | let uuid = [],
9 | i;
10 | const curRadix = radix || chars.length;
11 | if (len) {
12 | for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * curRadix)];
13 | } else {
14 | let r;
15 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
16 | uuid[14] = '4';
17 | for (i = 0; i < 36; i++) {
18 | if (!uuid[i]) {
19 | r = 0 | (Math.random() * 16);
20 | uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r];
21 | }
22 | }
23 | }
24 | return uuid.join('');
25 | }
26 |
27 | /**
28 | * 生成随机的ID
29 | * @param randomLength
30 | */
31 | export function generateRandomID(randomLength: number) {
32 | // 引入时间戳 随机数前置 36进制 加入随机数长度控制
33 | // Number.prototype.toString([radix])中的 radix 指定要用于数字到字符串的转换的基数(从2到36)
34 | // 如果未指定 radix 参数,则默认值为 10,所有不在范围内的基数会报错
35 | // Math.random().toString(36)代表36进制
36 | // console.log(new Number(1337).valueOf().toString());
37 | // 带时间戳
38 | return Number(Math.random().toString().substr(3, randomLength) + Date.now()).toString(36);
39 | }
40 |
41 | export function generateRandomString(len: number) {
42 | let i = 0,
43 | str = '',
44 | base = 19968,
45 | range = 10;
46 | // 19968 至 40869
47 | while (i < len) {
48 | i++;
49 | let lower = parseInt(`${Math.random() * range}`);
50 | str += String.fromCharCode(base + lower);
51 | }
52 | return str;
53 | }
54 |
--------------------------------------------------------------------------------
/src/util/handleUrlParams.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取 URL 参数
3 | * @param name
4 | */
5 | export function getUrlParam(name: string) {
6 | // 构造一个含有目标参数的正则表达式对象
7 | const reg = new RegExp(`(^|&)${name.toLowerCase()}=([^&]*)(&|$)`);
8 | // 匹配目标参数
9 | const r = window.location.search.substr(1).toLowerCase().match(reg);
10 | if (r != null) {
11 | //返回参数值
12 | return unescape(r[2]);
13 | }
14 | return null;
15 | }
16 | /**
17 | * url添加参数
18 | * @param {string} url - 需要添加参数的url
19 | * @param {object} params - 添加的参数,参数是'key:value'形式
20 | * @param {boolean} [encode=false] - 返回的url是否需要编码
21 | * @returns {string}
22 | */
23 | export function addParams({
24 | url = '',
25 | params = {},
26 | encode = false,
27 | }: {
28 | url?: string;
29 | params: object;
30 | encode?: boolean;
31 | }) {
32 | if (!Object.keys(params).length) {
33 | return url;
34 | }
35 | const curUrl = decodeURIComponent(url);
36 | const [hostStr, searchStr] = curUrl.split('?');
37 | let newParams = params;
38 | if (curUrl.includes('?')) {
39 | const oldParams = {};
40 | searchStr.split('&').forEach(val => {
41 | const newVal = val.split('=');
42 | oldParams[newVal[0]] = newVal[1];
43 | });
44 | // 合并、去重参数
45 | newParams = { ...oldParams, ...params };
46 | }
47 | let [paramsStr, i] = ['', 1];
48 | for (const [key, val] of Object.entries(newParams)) {
49 | paramsStr += i > 1 ? `&${key}=${val}` : `${key}=${val}`;
50 | i++;
51 | }
52 | const baseUrl = `${hostStr}?${paramsStr}`;
53 | return encode ? encodeURIComponent(baseUrl) : baseUrl;
54 | }
55 |
--------------------------------------------------------------------------------
/src/util/index.ts:
--------------------------------------------------------------------------------
1 | /** 获取当前开发环境 */
2 | export function getEnv() {
3 | return {
4 | isDev: process.env.ENV === 'dev', // 本地开发环境
5 | isProd: process.env.ENV === 'prod', // 生产环境
6 | isTest: process.env.ENV === 'test', //测试环境
7 | };
8 | }
9 |
10 | export function classNames(...args: any) {
11 | const classes = [];
12 | for (let i = 0; i < args.length; i += 1) {
13 | const arg = args[i];
14 | if (arg) {
15 | const argType = typeof arg;
16 |
17 | if (argType === 'string' || argType === 'number') {
18 | classes.push((this && this[arg]) || arg);
19 | } else if (Array.isArray(arg)) {
20 | classes.push(classNames(...arg));
21 | } else if (argType === 'object') {
22 | const hasOwn = {}.hasOwnProperty;
23 | Object.keys(arg).forEach(key => {
24 | if (hasOwn.call(arg, key) && arg[key]) {
25 | classes.push((this && this[key]) || key);
26 | }
27 | });
28 | }
29 | }
30 | }
31 |
32 | return classes.join(' ');
33 | }
34 |
35 | export function isMobile() {
36 | const flag = navigator.userAgent.match(
37 | /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i,
38 | );
39 | if (flag) {
40 | return true;
41 | }
42 | return false;
43 | }
44 |
45 | export function setTimeout(fn: () => void, time: number) {
46 | const timer = window.setTimeout(fn, time);
47 | return () => {
48 | window.clearTimeout(timer);
49 | };
50 | }
51 |
52 | export function setInterval(fn: () => void, time: number) {
53 | const timer = window.setInterval(fn, time);
54 | return () => {
55 | window.clearInterval(timer);
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/src/util/stringUtil.ts:
--------------------------------------------------------------------------------
1 | /** 这个文件中封装了一些常用的工具函数 **/
2 |
3 | /**
4 | 保留N位小数
5 | @param {Number|String} str 待处理数字
6 | @param {Number} x 保留几位小数点
7 | @return {Number|String} 处理成功返回字符串,处理失败返回原值
8 | */
9 | export function pointX(str, x = 0) {
10 | if (!str && str !== 0) {
11 | return str;
12 | }
13 | const temp = Number(str);
14 | if (temp === 0) {
15 | return temp.toFixed(x);
16 | }
17 | return temp ? temp.toFixed(x) : str;
18 | }
19 |
20 | /**
21 | 去掉字符串两端空格
22 | @param {String} str 待处理字符串
23 | @return {String} 处理后的字符串
24 | */
25 | export function trim(str) {
26 | const reg = /^\s*|\s*$/g;
27 | return str.replace(reg, '');
28 | }
29 |
30 | /**
31 | 给字符串打马赛克
32 | 如:将123456转换为1****6,最多将字符串中间6个字符变成*
33 | 如果字符串长度小于等于2,将不会有效果
34 | @param {String} str 待处理字符串
35 | @return {String} 处理后的字符串
36 | */
37 | export function addMosaic(str) {
38 | const s = String(str);
39 | const length = s.length;
40 | const howMuch = (() => {
41 | if (s.length <= 2) {
42 | return s.length;
43 | }
44 | const l = s.length - 2;
45 | if (l <= 6) {
46 | return l;
47 | }
48 | return 6;
49 | })();
50 | const start = Math.floor((length - howMuch) / 2);
51 | const ret = s.split('').map((v, i) => {
52 | if (i >= start && i < start + howMuch) {
53 | return '*';
54 | }
55 | return v;
56 | });
57 | return ret.join('');
58 | }
59 |
60 | /**
61 | 字符串加密 简单的加密方法
62 | @param {String} code 待处理字符串
63 | @return {String} 加密后的字符串
64 | */
65 | export function compile(code) {
66 | let c = String.fromCharCode(code.charCodeAt(0) + code.length);
67 | for (let i = 1; i < code.length; i++) {
68 | c += String.fromCharCode(code.charCodeAt(i) + code.charCodeAt(i - 1));
69 | }
70 | return c;
71 | }
72 |
73 | /**
74 | 字符串解谜 对应上面的字符串加密方法
75 | @param {String} code 加密的字符串
76 | @return {String} 解密后的字符串
77 | */
78 | export function unCompile(code) {
79 | let c = String.fromCharCode(code.charCodeAt(0) - code.length);
80 | for (let i = 1; i < code.length; i++) {
81 | c += String.fromCharCode(code.charCodeAt(i) - c.charCodeAt(i - 1));
82 | }
83 | return c;
84 | }
85 |
--------------------------------------------------------------------------------
/src/util/variableTypeDetection.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 检测变量类型
3 | * @param type
4 | */
5 | function isType(type) {
6 | return function (value): boolean {
7 | return Object.prototype.toString.call(value) === `[object ${type}]`;
8 | };
9 | }
10 |
11 | export const variableTypeDetection = {
12 | isNumber: isType('Number'),
13 | isString: isType('String'),
14 | isBoolean: isType('Boolean'),
15 | isNull: isType('Null'),
16 | isUndefined: isType('Undefined'),
17 | isSymbol: isType('Symbol'),
18 | isFunction: isType('Function'),
19 | isObject: isType('Object'),
20 | isArray: isType('Array'),
21 | };
22 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | // extend your base config so you don't have to redefine your compilerOptions
3 | "extends": "./tsconfig.json",
4 | "include": [
5 | "**/*.ts",
6 | "**/*.tsx",
7 | "**/*.js",
8 | "**/*.jsx"
9 | ],
10 | "exclude": [
11 | "node_modules/**",
12 | "dist/**",
13 | "coverage/**"
14 | ]
15 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* 开启增量编译:TS 编译器在第一次编译的时候,会生成一个存储编译信息的文件,下一次编译的时候,会根据这个文件进行增量的编译,以此提高 TS 的编译速度 */
4 | // "incremental": true,
5 | // 指定存储增量编译信息的文件位置
6 | // "tsBuildInfoFile": "./",
7 | // 显示诊断信息
8 | // "diagnostics": true,
9 | /* 指定模块代码的生成方式: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
10 | // "module": "commonjs",
11 | /* 指定 ECMAScript 的目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
12 | "rootDir": "./src",
13 | "target": "es5", // 指定 ECMAScript 目标版本
14 | "module": "ESNext", //指定生成那个模块的代码
15 | "strict": true, // 开启所有的严格检查配置
16 | "esModuleInterop": true, // 允许 export = xxx 导出 ,并使用 import xxx form "module-name" 导入
17 | "outDir": "dist",
18 | /* 指定要包含在编译中的库文件——引用类库——即申明文件,如果输出的模块方式是 es5,就会默认引入 "dom","es5","scripthost" 。如果在 TS 中想要使用一些 ES6 以上版本的语法,就需要引入相关的类库 */
19 | "lib": [
20 | "webworker",
21 | "dom",
22 | "es5",
23 | "es2015",
24 | "es2016",
25 | "es2015.promise",
26 | "dom.iterable",
27 | "scripthost",
28 | "esnext",
29 | ], // 要包含在编译中的依赖库文件列表
30 | "allowJs": true, // 允许编译 JavaScript 文件
31 | // 检查 JS 文件
32 | "checkJs": true,
33 | "skipLibCheck": true, // 跳过所有声明文件的类型检查
34 | "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
35 | "resolveJsonModule": true, // 允许使用 .json 扩展名导入的模块
36 | "noEmit": true, // 不输出(意思是不编译代码,只执行类型检查)
37 | /* react 模式下:直接将 JSX 编译成 JS,会生成 React.createElement 的形式,在使用前不需要再进行转换操作了,输出文件的扩展名为 .js */
38 | /* preserve 模式下:不会将 JSX 编译成 JS,生成代码中会保留 JSX,以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有 .jsx 扩展名 */
39 | /* react-native 模式下:相当于 preserve,它也保留了所有的 JSX,但是输出文件的扩展名是 .js */
40 | "jsx": "react", // 在.tsx文件中支持JSX
41 | "sourceMap": true, // 生成相应的.map文件
42 | "declaration": true, // 生成相应的.d.ts文件
43 | "allowUmdGlobalAccess": true,
44 | "experimentalDecorators": true, // 启用对ES装饰器的实验性支持
45 | "moduleResolution": "node", // 制定模块解析策略
46 | "baseUrl": "./",
47 | "incremental": true, // 通过从以前的编译中读取/写入信息到磁盘上的文件来启用增量编译
48 | "forceConsistentCasingInFileNames": true,
49 | /* 当目标是ES5或ES3的时候提供对for-of、扩展运算符和解构赋值中对于迭代器的完整支持 */
50 | "downlevelIteration": true,
51 | "noUnusedLocals": true,
52 | "noUnusedParameters": true,
53 | "noImplicitReturns": true,
54 | "noFallthroughCasesInSwitch": true,
55 | // 不允许使用隐式的 any 类型
56 | "noImplicitAny": false,
57 | // 不允许 this 有隐式的 any 类型,即 this 必须有明确的指向
58 | "noImplicitThis": false,
59 | // 不允许把 null、undefined 赋值给其他类型变量
60 | "strictNullChecks": false,
61 | "paths": {
62 | //别名
63 | "@/*": [
64 | "./src/*"
65 | ],
66 | "@images/*": [
67 | "./src/assets/images/*"
68 | ],
69 | }
70 | },
71 | "include": [
72 | "src"
73 | ],
74 | "exclude": [
75 | "node_modules",
76 | "dist"
77 | ] // *** 不进行类型检查的文件 ***
78 | }
--------------------------------------------------------------------------------
/webpack/webpack.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
3 | const variable = require('./webpackUtils/variable');
4 | const resolveConfig = require('./webpackUtils/resolve');
5 | const plugins = require('./webpackUtils/plugins');
6 | const { SRC_PATH, DIST_PATH, IS_DEV, IS_PRO, getCDNPath } = variable;
7 |
8 | const config = {
9 | entry: {
10 | index: path.join(SRC_PATH, 'index.tsx'),
11 | },
12 | output: {
13 | path: DIST_PATH,
14 | filename: IS_DEV ? 'js/[name].bundle.js' : 'js/[name].[contenthash:8].bundle.js',
15 | publicPath: getCDNPath(),
16 | globalObject: 'this',
17 | chunkFilename: IS_DEV ? 'js/[name].chunk.js' : 'js/[name].[contenthash:8].chunk.js',
18 | assetModuleFilename: 'assets/[hash][ext][query]',
19 | clean: true,
20 | },
21 | //loader的执行顺序默认从右到左,多个loader用[],字符串只用一个loader,也可以是对象的格式
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(tsx?|js)$/,
26 | include: [SRC_PATH],
27 | use: [
28 | {
29 | loader: 'thread-loader',
30 | options: {
31 | workers: require('os').cpus().length * 2,
32 | parallel: true,
33 | },
34 | },
35 | {
36 | loader: 'babel-loader', // 这是一个webpack优化点,使用缓存
37 | options: {
38 | cacheDirectory: true,
39 | },
40 | },
41 | ],
42 | exclude: [/node_modules/, /public/, /(.|_)min\.js$/],
43 | },
44 | {
45 | test: /\.css$|\.scss$/i,
46 | // include: [SRC_PATH],
47 | // exclude: /node_modules/, // 取消匹配node_modules里面的文件
48 | use: [
49 | IS_DEV ? 'style-loader' : MiniCssExtractPlugin.loader,
50 | {
51 | loader: 'css-loader',
52 | options: {
53 | modules: false,
54 | sourceMap: !IS_PRO,
55 | },
56 | },
57 | {
58 | loader: 'postcss-loader',
59 | options: {
60 | postcssOptions: {
61 | plugins: [
62 | [
63 | 'postcss-preset-env',
64 | {
65 | // 其他选项
66 | },
67 | ],
68 | ],
69 | },
70 | },
71 | },
72 | 'sass-loader',
73 | {
74 | loader: 'style-resources-loader',
75 | options: {
76 | patterns: path.resolve(SRC_PATH, 'assets', 'css', 'core.scss'),
77 | },
78 | },
79 | ],
80 | },
81 | {
82 | test: /\.(png|jpg|gif|jpeg|webp|svg)$/,
83 | type: 'asset/resource',
84 | generator: {
85 | // publicPath: '../',
86 | filename: 'assets/images/[hash][ext][query]',
87 | },
88 | },
89 | {
90 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
91 | type: 'asset/resource',
92 | generator: {
93 | // publicPath: '../',
94 | filename: 'assets/fonts/[hash][ext][query]',
95 | },
96 | },
97 | ],
98 | },
99 | resolve: resolveConfig,
100 | plugins: plugins.getPlugins(),
101 | };
102 |
103 | module.exports = config;
104 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const webpackMerge = require('webpack-merge');
3 | const baseConfig = require('./webpack.base');
4 | const variable = require('./webpackUtils/variable');
5 |
6 | const { DIST_PATH } = variable;
7 | //引入
8 |
9 | const config = {
10 | mode: 'development',
11 | cache: { type: 'memory' },
12 | devtool: 'eval-cheap-module-source-map',
13 | stats: 'errors-only',
14 | watchOptions: {
15 | aggregateTimeout: 500,
16 | poll: 1000,
17 | ignored: /node_modules/,
18 | },
19 | devServer: {
20 | open: 'chrome',
21 | contentBase: DIST_PATH,
22 | compress: true, //是否启用gzip压缩
23 | publicPath: '/',
24 | host: 'localhost',
25 | port: 9093,
26 | // hot: true,
27 | disableHostCheck: true,
28 | stats: 'errors-only',
29 | proxy: {
30 | // "/service": {
31 | // target: "http://localhost:3000"
32 | // }
33 | },
34 | },
35 | };
36 | const mergedConfig = webpackMerge.merge(baseConfig, config);
37 |
38 | mergedConfig.plugins = mergedConfig.plugins.filter(Boolean);
39 |
40 | module.exports = mergedConfig;
41 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const webpackMerge = require('webpack-merge');
2 | const baseConfig = require('./webpack.base');
3 |
4 | const config = {
5 | mode: 'production',
6 | cache: { type: 'filesystem', buildDependencies: { config: [__filename] } },
7 | output: {
8 | pathinfo: false, //优化
9 | },
10 | optimization: {
11 | minimize: true, //开启压缩
12 | moduleIds: 'deterministic', //单独模块id,模块内容变化再更新
13 | splitChunks: {
14 | chunks: 'all', // 匹配的块的类型:initial(初始块),async(按需加载的异步块),all(所有块)
15 | automaticNameDelimiter: '-',
16 | cacheGroups: {
17 | // 项目第三方组件
18 | vendor: {
19 | name: 'vendors',
20 | enforce: true, // ignore splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests
21 | test: /[\\/]node_modules[\\/]/,
22 | priority: 10,
23 | },
24 | // 项目公共组件
25 | default: {
26 | minSize: 0, // 分离后的最小块文件大小默认3000
27 | name: 'common', // 用以控制分离后代码块的命名
28 | minChunks: 3, // 最小共用次数
29 | priority: 10, // 优先级,多个分组冲突时决定把代码放在哪块
30 | reuseExistingChunk: true,
31 | },
32 | },
33 | },
34 | },
35 | };
36 |
37 | const mergedConfig = webpackMerge.merge(baseConfig, config);
38 | module.exports = mergedConfig;
39 |
--------------------------------------------------------------------------------
/webpack/webpackUtils/plugins.js:
--------------------------------------------------------------------------------
1 | // const cleanWebpackPlugin = require("clean-webpack-plugin") ;
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | // const CopyPlugin = require("copy-webpack-plugin");
5 | const variable = require('./variable');
6 | const DotenvPlugin = require('dotenv-webpack');
7 | const path = require('path');
8 |
9 | // const {CleanWebpackPlugin}=cleanWebpackPlugin;
10 | const { PUBLIC_PATH, DIST_PATH, ENV_CONFIG_PATH, IS_DEV, SRC_PATH } = variable;
11 |
12 | function getHTMLPlugins() {
13 | const indexHtmlPlugin = new HtmlWebpackPlugin({
14 | template: path.join(PUBLIC_PATH, 'index.html'),
15 | // filename: path.join(DIST_PATH, 'index.html'),
16 | filename: 'index.html',
17 | inject: true, //true 插入body底部,head:插入head标签,false:不生成js文件
18 | // hash: true, // 会在打包好的bundle.js后面加上hash串
19 | title: '',
20 | minify: {
21 | removeComments: true, // 删除注释
22 | collapseWhitespace: true,
23 | minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
24 | minifyJS: true, // 压缩 HTML 中出现的 JS 代码
25 | },
26 | });
27 |
28 | return [indexHtmlPlugin];
29 | }
30 |
31 | function getPlugins() {
32 | // clean
33 | // const cleanPlugin = new CleanWebpackPlugin({
34 | // cleanOnceBeforeBuildPatterns: ["**/*", '!dll', '!dll/*.*']
35 | // });
36 |
37 | const miniCssPlugin = new MiniCssExtractPlugin({
38 | filename: IS_DEV ? 'css/[name].css' : 'css/[name].[contenthash:8].css',
39 | chunkFilename: IS_DEV ? 'css/[name].chunk.css' : 'css/[name].[contenthash:8].chunk.css',
40 | // 常遇到如下警告,Conflicting order. Following module has been added:…。
41 | // 此警告意思为在不同的js中引用相同的css时,先后顺序不一致。也就是说,在1.js中先后引入a.css和b.css,而在2.js中引入的却是b.css和a.css,此时会有这个warning。
42 | ignoreOrder: true,
43 | });
44 |
45 | const dotenvPlugin = new DotenvPlugin({
46 | path: ENV_CONFIG_PATH,
47 | });
48 |
49 | // const copyPlugin=new CopyPlugin({
50 | // patterns: [
51 | // { from: path.resolve(SRC_PATH,"assets"), to: path.resolve(DIST_PATH,"assets") },
52 | // ],
53 | // });
54 |
55 | return [
56 | // cleanPlugin,
57 | // copyPlugin,
58 | ...getHTMLPlugins(),
59 | dotenvPlugin,
60 | miniCssPlugin,
61 | ];
62 | }
63 |
64 | module.exports = {
65 | getPlugins,
66 | };
67 |
--------------------------------------------------------------------------------
/webpack/webpackUtils/resolve.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const variable = require('./variable');
3 | const { SRC_PATH, ROOT_PATH } = variable;
4 |
5 | module.exports = {
6 | extensions: ['.tsx', '.ts', '.js', '.json'],
7 | modules: [path.resolve(ROOT_PATH, 'node_modules')],
8 | // 查找 package.json main
9 | mainFields: ['main'],
10 | alias: {
11 | '@': SRC_PATH,
12 | '@images': path.resolve(SRC_PATH, 'assets/images'),
13 | '@fonts': path.resolve(SRC_PATH, 'assets/fonts'),
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/webpack/webpackUtils/util.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const packageConfig = require('../../package.json');
3 |
4 | function readFile(curPath) {
5 | const content = fs.readFileSync(curPath, 'utf-8');
6 | return content;
7 | }
8 |
9 | /**
10 | *
11 | * 获取版本
12 | * @export
13 | * @returns
14 | */
15 | function getVersion() {
16 | //const version = readFile(path.join(__dirname, '../../version')).trim(); // return version;
17 | return packageConfig.version || '1.0.0';
18 | }
19 |
20 | /**
21 | *
22 | * 获取测试版本
23 | * @export
24 | * @returns
25 | */
26 | function getTestVersion() {
27 | return packageConfig.testVersion || '1.0.0';
28 | }
29 |
30 | /**
31 | *
32 | * 获取env
33 | * @returns
34 | */
35 | function getEnv() {
36 | return process.env.NODE_ENV || 'dev';
37 | }
38 |
39 | module.exports = {
40 | readFile,
41 | getVersion,
42 | getTestVersion,
43 | getEnv,
44 | };
45 |
--------------------------------------------------------------------------------
/webpack/webpackUtils/variable.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpackUtils = require('./util');
3 | const dotenv = require('dotenv');
4 |
5 | const { config: loadConfig } = dotenv;
6 | //__dirname 为当前目录文件路径
7 |
8 | const NODE_ENV = webpackUtils.getEnv();
9 |
10 | //构建目录
11 | const DIST_PATH = path.resolve(__dirname, '../../', 'dist');
12 | //源码目录
13 | const SRC_PATH = path.resolve(__dirname, '../../', 'src');
14 | //public 目录
15 | const PUBLIC_PATH = path.resolve(__dirname, '../../', 'public');
16 | //根节点目录
17 | const ROOT_PATH = path.resolve(__dirname, '../../');
18 | //是否是产线环境
19 | const IS_PRO = NODE_ENV === 'prod';
20 | //是否是开发环境
21 | const IS_DEV = NODE_ENV === 'dev';
22 |
23 | //当前构建的版本
24 | const version = webpackUtils.getVersion();
25 |
26 | function getCDNPath() {
27 | return IS_PRO ? `${process.env.CDN_ROOT}/${version}/` : './';
28 | }
29 |
30 | const ENV_CONFIG_PATH = path.resolve(ROOT_PATH, 'env', `${NODE_ENV}.env`);
31 |
32 | //webpack 读取env 配置
33 | loadConfig({
34 | path: ENV_CONFIG_PATH,
35 | });
36 |
37 | console.log('::NODE_ENV', NODE_ENV);
38 |
39 | console.log('version', version);
40 |
41 | module.exports = {
42 | DIST_PATH,
43 | SRC_PATH,
44 | PUBLIC_PATH,
45 | ROOT_PATH,
46 | IS_PRO,
47 | IS_DEV,
48 | getCDNPath,
49 | ENV_CONFIG_PATH,
50 | };
51 |
--------------------------------------------------------------------------------