├── .babelrc.js
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── .stylelintrc.js
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── buildConfig
├── channel
│ ├── common
│ │ ├── index.ts
│ │ ├── menuConfig.js
│ │ └── route.ts
│ └── mobile
│ │ ├── index.js
│ │ ├── menuConfig.js
│ │ └── route.js
└── defaultConfig.js
├── jest.config.js
├── mock-server
├── .gitignore
├── README.md
├── nest-cli.json
├── package.json
├── src
│ ├── app.module.ts
│ ├── controller
│ │ ├── app
│ │ │ ├── app.controller.spec.ts
│ │ │ └── app.controller.ts
│ │ ├── cats
│ │ │ ├── cats.controller.spec.ts
│ │ │ └── cats.controller.ts
│ │ └── demo
│ │ │ ├── demo.controller.spec.ts
│ │ │ └── demo.controller.ts
│ ├── main.ts
│ ├── proxy
│ │ ├── api
│ │ │ └── common.ts
│ │ ├── config.ts
│ │ ├── consoleStyle.ts
│ │ └── mock.ts
│ └── service
│ │ └── app.service.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.build.tsbuildinfo
├── tsconfig.json
└── tslint.json
├── package.json
├── postcss.config.js
├── scripts
├── auto-get-module
│ └── index.js
├── build.js
├── build_dll.js
├── builder.js
├── create-module
│ ├── index.js
│ └── templates
│ │ ├── base-service
│ │ ├── http.ts
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
│ │ └── base
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
├── dev.js
├── nginx.conf
└── webpack
│ ├── config.js
│ ├── utils.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ ├── webpack.dll.conf.js
│ └── webpack.prod.conf.js
├── src
├── App
│ ├── index.tsx
│ └── store.ts
├── assets
│ ├── 0326_1.jpg
│ ├── plant-01.svg
│ ├── sidebar-backup-2.jpg
│ ├── sidebar-backup.jpg
│ ├── sidebar.jpg
│ └── 中国年.svg
├── components
│ ├── base
│ │ ├── Button
│ │ │ └── index.tsx
│ │ ├── Checkbox
│ │ │ └── index.tsx
│ │ ├── DynamicForm
│ │ │ ├── index.js
│ │ │ ├── lib
│ │ │ │ ├── BaseForm.js
│ │ │ │ ├── InputFactory.js
│ │ │ │ ├── Item.js
│ │ │ │ ├── Text.js
│ │ │ │ ├── config.js
│ │ │ │ ├── inputMap.js
│ │ │ │ ├── utils.js
│ │ │ │ └── validators.js
│ │ │ └── style.scss
│ │ ├── ErrorMsgBoxHOC
│ │ │ └── index.tsx
│ │ ├── IconButton
│ │ │ └── index.tsx
│ │ ├── Input
│ │ │ └── index.tsx
│ │ ├── Loading
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ │ ├── Skeleton
│ │ │ └── index.tsx
│ │ ├── Table
│ │ │ └── index.tsx
│ │ └── Toast
│ │ │ └── index.tsx
│ └── business
│ │ ├── AuthFilterHOC.tsx
│ │ ├── Bar
│ │ └── index.tsx
│ │ ├── Layout
│ │ ├── index.tsx
│ │ └── style.scss
│ │ ├── Menu
│ │ └── index.tsx
│ │ └── SubList
│ │ ├── index.tsx
│ │ └── style.scss
├── constants
│ └── common
│ │ ├── Auth.ts
│ │ ├── channel.ts
│ │ └── lang
│ │ ├── en.ts
│ │ └── zh.ts
├── http
│ ├── config.ts
│ └── index.ts
├── index.html
├── index.tsx
├── modules
│ ├── Page1
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
│ ├── Page2
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
│ ├── Page3
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
│ └── user
│ │ ├── index.tsx
│ │ └── list
│ │ ├── index.tsx
│ │ ├── store.ts
│ │ └── style.scss
├── routes
│ ├── AuthRoute.tsx
│ ├── history.ts
│ └── index.tsx
├── service
│ ├── common
│ │ ├── app.ts
│ │ ├── channel.ts
│ │ ├── i18n.ts
│ │ ├── theme.ts
│ │ └── user.ts
│ ├── index.ts
│ └── natur-service.ts
├── store
│ ├── common
│ │ ├── channel.store.ts
│ │ ├── loading.store.ts
│ │ ├── no.devtool.ts
│ │ ├── persist.ts
│ │ ├── redux.devtool.ts
│ │ ├── router.store.ts
│ │ ├── toast.store.ts
│ │ └── user.store.ts
│ ├── index.ts
│ └── lazyModule.ts
├── theme
│ ├── material.ts
│ ├── native
│ │ ├── config.ts
│ │ └── theme.scss
│ ├── r.ts
│ └── unit.ts
└── utils
│ ├── color.js
│ ├── devToolInit.ts
│ ├── hooks.ts
│ ├── index.js
│ ├── ocr
│ └── index.js
│ ├── regExps.ts
│ ├── styles
│ ├── base.scss
│ ├── func.scss
│ ├── index.scss
│ └── mixin.scss
│ └── validator.ts
├── tsconfig.json
└── typings
├── custom-typings.d.ts
├── font.d.ts
├── images.d.ts
├── index.d.ts
└── style.d.ts
/.babelrc.js:
--------------------------------------------------------------------------------
1 | const materialConfig = [
2 | [
3 | "babel-plugin-import",
4 | {
5 | libraryName: "@material-ui/core",
6 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules
7 | libraryDirectory: "esm",
8 | camel2DashComponentName: false
9 | },
10 | "core"
11 | ],
12 | [
13 | "babel-plugin-import",
14 | {
15 | libraryName: "@material-ui/icons",
16 | // Use "'libraryDirectory': ''," if your bundler does not support ES modules
17 | libraryDirectory: "esm",
18 | camel2DashComponentName: false
19 | },
20 | "icons"
21 | ]
22 | ];
23 |
24 | module.exports = {
25 | plugins: [
26 | "lodash",
27 | ...materialConfig,
28 | "@babel/plugin-syntax-dynamic-import",
29 | "@babel/plugin-proposal-export-default-from",
30 | "@babel/plugin-proposal-object-rest-spread",
31 | // [
32 | // "@babel/plugin-transform-runtime",
33 | // {
34 | // // "absoluteRuntime": false,
35 | // // "corejs": 3,
36 | // // "helpers": true,
37 | // // "regenerator": true,
38 | // // "useESModules": false
39 | // }
40 | // ],
41 | "@babel/plugin-proposal-async-generator-functions",
42 | "@babel/plugin-proposal-function-bind",
43 | ["@babel/plugin-proposal-decorators", { legacy: true }],
44 | ["@babel/plugin-proposal-class-properties", { loose: true }]
45 | ],
46 | presets: [
47 | [
48 | "@babel/preset-env",
49 | {
50 | useBuiltIns: 'entry',
51 | targets: {
52 | ie: '9',
53 | },
54 | corejs: 3,
55 | modules: false
56 | }
57 | ],
58 | "@babel/preset-react",
59 | "@babel/preset-typescript"
60 | ]
61 | };
62 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | scripts
2 | postcss.config.js
3 | redux.devtool.ts
4 | mock-server
5 | dist
6 | src/store/natur-persist
7 | natur-persist
8 | natur-service.ts
9 | _natur-service.ts
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | env: {
6 | browser: true,
7 | // "commonjs": true,
8 | es6: true,
9 | },
10 | extends: [
11 | // "eslint:recommended",
12 | // "plugin:react/recommended"
13 | 'airbnb',
14 | ],
15 | // extends: "eslint:recommended",
16 | globals: {
17 | $: true,
18 | process: true,
19 | __dirname: true,
20 | },
21 | parser: 'babel-eslint',
22 | parserOptions: {
23 | //es6的module模式
24 | sourceType: 'module',
25 | ecmaFeatures: {
26 | experimentalObjectRestSpread: true,
27 | jsx: true,
28 | },
29 | ecmaVersion: 11, // es2020
30 | },
31 | settings: {
32 | 'import/ignore': ['node_modules', 'DynamicForm', '.s?css', '@w*'],
33 | },
34 | plugins: ['react', 'react-hooks', 'import', 'jsx-a11y'],
35 |
36 | overrides: [{
37 | files: ['**/*.ts', '**/*.tsx'],
38 | parser: '@typescript-eslint/parser',
39 | parserOptions: {
40 | ecmaVersion: 11,
41 | sourceType: 'module',
42 | ecmaFeatures: {
43 | jsx: true,
44 | },
45 | // typescript-eslint specific options
46 | warnOnUnsupportedTypeScriptVersion: true,
47 | },
48 | plugins: ['@typescript-eslint'],
49 | // If adding a typescript-eslint version of an existing ESLint rule,
50 | // make sure to disable the ESLint rule here.
51 | rules: {
52 | // TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906)
53 | 'default-case': 'off',
54 | // 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
55 | 'no-dupe-class-members': 'off',
56 | // Add TypeScript specific rules (and turn off ESLint equivalents)
57 | // '@typescript-eslint/no-angle-bracket-type-assertion': 'warn',
58 | 'no-array-constructor': 'off',
59 | '@typescript-eslint/no-array-constructor': 'warn',
60 | '@typescript-eslint/no-namespace': 'error',
61 | 'no-unused-vars': 1,
62 | '@typescript-eslint/no-unused-vars': [
63 | 'warn',
64 | {
65 | args: 'none',
66 | ignoreRestSiblings: true,
67 | },
68 | ],
69 | 'no-useless-constructor': 'off',
70 | '@typescript-eslint/no-useless-constructor': 'warn',
71 | },
72 | }],
73 | rules: {
74 | 'import/no-unresolved': 0,
75 | 'import/extensions': 0,
76 | 'import/order': 0,
77 | 'import/prefer-default-export': 0,
78 |
79 | 'react/prop-types': 0,
80 | 'react/jsx-filename-extension': 0,
81 | 'react/prefer-stateless-function': 0,
82 | 'react/jsx-indent': [2, 'tab'],
83 | 'react/jsx-indent-props': [2, 'tab'],
84 | 'react/jsx-tag-spacing': 0,
85 | 'react/jsx-props-no-spreading': 0,
86 | 'react/require-default-props': 0,
87 | // // @off 同构应用需要在 didMount 里写 setState
88 | 'react/no-did-mount-set-state': 0,
89 | 'react/button-has-type': 0,
90 | "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
91 | "react-hooks/exhaustive-deps": "warn", // Checks effect dependencies
92 |
93 | 'jsx-a11y/anchor-is-valid': 0,
94 | 'jsx-a11y/click-events-have-key-events': 0,
95 | 'jsx-a11y/mouse-events-have-key-events': 0,
96 | 'jsx-a11y/no-noninteractive-element-interactions': 0,
97 | 'jsx-a11y/no-static-element-interactions': 0,
98 | 'jsx-a11y/aria-role': 0,
99 | 'jsx-a11y/alt-text': 0,
100 | 'jsx-a11y/heading-has-content': 0,
101 | 'jsx-a11y/anchor-has-content': 0,
102 |
103 | 'no-return-assign': 0,
104 | 'consistent-return': 0,
105 | 'no-console': 0,
106 | 'no-plusplus': 0,
107 | 'linebreak-style': 0,
108 | 'no-unused-expressions': 0,
109 | // 0、1、2分别表示不开启检查、警告、错误
110 | indent: [2, 'tab', { SwitchCase: 1 }], // tab缩进
111 | // 圈复杂度
112 | complexity: [2, 9],
113 | 'max-params': [2, 7],
114 | 'max-depth': [2, 4],
115 | 'no-multiple-empty-lines': 0,
116 | 'max-len': [
117 | 'error',
118 | {
119 | code: 150,
120 | tabWidth: 4,
121 | ignoreComments: true,
122 | ignoreUrls: true,
123 | ignoreStrings: true,
124 | ignoreTemplateLiterals: true,
125 | ignoreRegExpLiterals: true,
126 | },
127 | ],
128 | 'no-tabs': 0,
129 | 'object-curly-newline': [
130 | 0,
131 | {
132 | ObjectExpression: 'always',
133 | ObjectPattern: { multiline: true },
134 | ImportDeclaration: 'never',
135 | ExportDeclaration: {
136 | multiline: true,
137 | },
138 | },
139 | ],
140 | 'object-curly-spacing': 0,
141 |
142 | 'arrow-parens': [2, 'as-needed'],
143 | // 最大回调层数
144 | 'max-nested-callbacks': [2, 3],
145 | 'no-unused-vars': [
146 | 1,
147 | {
148 | argsIgnorePattern: '^React',
149 | varsIgnorePattern: '[Rr]eact|[Ss]tyle',
150 | },
151 | ],
152 | 'no-extra-boolean-cast': 0,
153 | 'array-callback-return': 0,
154 | 'no-param-reassign': 0,
155 | 'jsx-quotes': [0, 'prefer-double'], //强制在JSX属性(jsx-quotes)中一致使用双引号
156 | 'no-underscore-dangle': 0,
157 | 'quote-props': 0,
158 | // "no-native-reassign": 2,//不能重写native对象
159 | // // if while function 后面的{必须与if在同一行,java风格。
160 | // "brace-style": [2, "1tbs", { "allowSingleLine": true }],
161 | // // 双峰驼命名格式
162 | // "camelcase": 2,
163 | // // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
164 | // "computed-property-spacing": [2,"never"],
165 | // //允许箭头函数可以省略小括号
166 | // 'arrow-parens': 0,
167 | // 'no-extra-semi': 2, // 不允许多余的分号
168 | // //允许使用async-await函数
169 | // 'generator-star-spacing': 0,
170 | // //在开发环境开启debugger功能,生产环境禁止使用debugger
171 | // 'no-debugger': process.env.NODE_ENV === 'development' ? 0 : 2,
172 | // "quotes": [2, "single"], //单引号
173 | // "no-var": 2, //对var警告
174 | // "semi": ["error", "always"], //不强制使用分号
175 | // "no-irregular-whitespace": 0, //不规则的空白不允许
176 | // "no-alert": 2, //禁止使用alert confirm prompt
177 | // "no-lone-blocks": 0, //禁止不必要的嵌套块
178 | // "no-class-assign": 2, //禁止给类赋值
179 | // "no-cond-assign": 2, //禁止在条件表达式中使用赋值语句
180 | // "no-const-assign": 2, //禁止修改const声明的变量
181 | // "no-delete-var": 2, //不能对var声明的变量使用delete操作符
182 | // "no-dupe-keys": 2, //在创建对象字面量时不允许键重复
183 | // "no-duplicate-case": 2, //switch中的case标签不能重复
184 | // "no-dupe-args": 2, //函数参数不能重复
185 | // "no-empty": 2, //块语句中的内容不能为空
186 | // "no-func-assign": 2, //禁止重复的函数声明
187 | // "no-invalid-this": 0, //禁止无效的this,只能用在构造器,类,对象字面量
188 | // "no-redeclare": 2, //禁止重复声明变量
189 | // "no-spaced-func": 2, //函数调用时 函数名与()之间不能有空格
190 | // "no-this-before-super": 0, //在调用super()之前不能使用this或super
191 | // "no-undef": 2, //不能有未定义的变量
192 | // "no-use-before-define": 2, //未定义前不能使用
193 | // // "camelcase": 0, //强制驼峰法命名
194 | // "no-mixed-spaces-and-tabs": 0, //禁止混用tab和空格
195 | // "prefer-arrow-callback": 0, //比较喜欢箭头回调
196 | // "arrow-spacing": 0, //=>的前/后括号
197 | //
198 | // // 禁止在 componentDidMount 里面使用 setState
199 |
200 | // // 禁止在 componentDidUpdate 里面使用 setState
201 | // 'react/no-did-update-set-state': 2,
202 | // // 禁止拼写错误
203 |
204 | // 'react/no-typos': 2,
205 | // // 禁止使用字符串 ref
206 | // 'react/no-string-refs': 2,
207 | // // @fixable 禁止出现 HTML 中的属性,如 class
208 | // 'react/no-unknown-property': 2,
209 | // // 禁止出现未使用的 propTypes
210 | // // @off 不强制要求写 propTypes
211 | // 'react/no-unused-prop-types': 2,
212 | // // 出现 jsx 的地方必须 import React
213 | // // @off 已经在 no-undef 中限制了
214 | // 'react/react-in-jsx-scope': 0,
215 | // // 非 required 的 prop 必须有 defaultProps
216 | // // @off 不强制要求写 propTypes
217 | // 'react/require-default-props': 0,
218 | // // render 方法中必须有返回值
219 | // 'react/require-render-return': 2,
220 | // // @fixable 组件内没有 children 时,必须使用自闭和写法
221 | // // @off 没必要限制
222 | // 'react/self-closing-comp': 0,
223 | // // style 属性的取值必须是 object
224 | // 'react/style-prop-object': 2,
225 | // // HTML 中的自闭和标签禁止有 children
226 | // 'react/void-dom-elements-no-children': 2,
227 | // // 数组中的 jsx 必须有 key
228 | // 'react/jsx-key': 2,
229 | // // 禁止在 jsx 中使用像注释的字符串
230 | // 'react/jsx-no-comment-textnodes': 2,
231 | // // 禁止出现重复的 props
232 | // 'react/jsx-no-duplicate-props': 2,
233 | // // 禁止使用未定义的 jsx elemet
234 | // 'react/jsx-no-undef': 2,
235 | // // jsx 文件必须 import React
236 | // 'react/jsx-uses-react': 2,
237 | // // 定义了的 jsx element 必须使用
238 | // 'react/jsx-uses-vars': 2,
239 | // // @fixable 多行的 jsx 必须有括号包起来
240 | // // @off 没必要限制
241 | // 'react/jsx-wrap-multilines': 2,
242 | // "react/no-array-index-key": 2, // 遍历出来的节点必须加key
243 | // "react/no-children-prop": 2, // 禁止使用children作为prop
244 | // "react/no-direct-mutation-state": 2, // 禁止直接this.state = 方式修改state 必须使用setState
245 | },
246 | };
247 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | /yarn-error.log
4 | /yarn.lock
5 | yarn.lock
6 | /tsconfig-backup.json
7 | /package-lock.json
8 | /scripts/dll
9 | .DS_Store
10 | .stylelintcache
11 | /.idea
12 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | phantomjs_cdnurl=http://cnpmjs.org/downloads
2 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
3 | registry=https://registry.npm.taobao.org
4 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'extends': [
3 | 'stylelint-config-standard',
4 | 'stylelint-config-recommended',
5 | ],
6 | 'plugins': ['stylelint-order'],
7 | 'rules': {
8 | 'order/order': [
9 | // "at-rules",
10 | // "declarations",
11 | 'custom-properties',
12 | 'dollar-variables',
13 | 'rules',
14 | ],
15 | 'order/properties-order': [
16 | 'position',
17 | 'z-index',
18 | 'top',
19 | 'bottom',
20 | 'left',
21 | 'right',
22 | 'float',
23 | 'clear',
24 | 'columns',
25 | 'columns-width',
26 | 'columns-count',
27 | 'column-rule',
28 | 'column-rule-width',
29 | 'column-rule-style',
30 | 'column-rule-color',
31 | 'column-fill',
32 | 'column-span',
33 | 'column-gap',
34 | 'display',
35 | 'grid',
36 | 'grid-template-rows',
37 | 'grid-template-columns',
38 | 'grid-template-areas',
39 | 'grid-auto-rows',
40 | 'grid-auto-columns',
41 | 'grid-auto-flow',
42 | 'grid-column-gap',
43 | 'grid-row-gap',
44 | 'grid-template',
45 | 'grid-template-rows',
46 | 'grid-template-columns',
47 | 'grid-template-areas',
48 | 'grid-gap',
49 | 'grid-row-gap',
50 | 'grid-column-gap',
51 | 'grid-area',
52 | 'grid-row-start',
53 | 'grid-row-end',
54 | 'grid-column-start',
55 | 'grid-column-end',
56 | 'grid-column',
57 | 'grid-column-start',
58 | 'grid-column-end',
59 | 'grid-row',
60 | 'grid-row-start',
61 | 'grid-row-end',
62 | 'flex',
63 | 'flex-grow',
64 | 'flex-shrink',
65 | 'flex-basis',
66 | 'flex-flow',
67 | 'flex-direction',
68 | 'flex-wrap',
69 | 'justify-content',
70 | 'align-content',
71 | 'align-items',
72 | 'align-self',
73 | 'order',
74 | 'table-layout',
75 | 'empty-cells',
76 | 'caption-side',
77 | 'border-collapse',
78 | 'border-spacing',
79 | 'list-style',
80 | 'list-style-type',
81 | 'list-style-position',
82 | 'list-style-image',
83 | 'ruby-align',
84 | 'ruby-merge',
85 | 'ruby-position',
86 | 'box-sizing',
87 | 'width',
88 | 'min-width',
89 | 'max-width',
90 | 'height',
91 | 'min-height',
92 | 'max-height',
93 | 'padding',
94 | 'padding-top',
95 | 'padding-right',
96 | 'padding-bottom',
97 | 'padding-left',
98 | 'margin',
99 | 'margin-top',
100 | 'margin-right',
101 | 'margin-bottom',
102 | 'margin-left',
103 | 'border',
104 | 'border-width',
105 | 'border-top-width',
106 | 'border-right-width',
107 | 'border-bottom-width',
108 | 'border-left-width',
109 | 'border-style',
110 | 'border-top-style',
111 | 'border-right-style',
112 | 'border-bottom-style',
113 | 'border-left-style',
114 | 'border-color',
115 | 'border-top-color',
116 | 'border-right-color',
117 | 'border-bottom-color',
118 | 'border-left-color',
119 | 'border-image',
120 | 'border-image-source',
121 | 'border-image-slice',
122 | 'border-image-width',
123 | 'border-image-outset',
124 | 'border-image-repeat',
125 | 'border-top',
126 | 'border-top-width',
127 | 'border-top-style',
128 | 'border-top-color',
129 | 'border-top',
130 | 'border-right-width',
131 | 'border-right-style',
132 | 'border-right-color',
133 | 'border-bottom',
134 | 'border-bottom-width',
135 | 'border-bottom-style',
136 | 'border-bottom-color',
137 | 'border-left',
138 | 'border-left-width',
139 | 'border-left-style',
140 | 'border-left-color',
141 | 'border-radius',
142 | 'border-top-right-radius',
143 | 'border-bottom-right-radius',
144 | 'border-bottom-left-radius',
145 | 'border-top-left-radius',
146 | 'outline',
147 | 'outline-width',
148 | 'outline-color',
149 | 'outline-style',
150 | 'outline-offset',
151 | 'overflow',
152 | 'overflow-x',
153 | 'overflow-y',
154 | 'resize',
155 | 'visibility',
156 | 'font',
157 | 'font-style',
158 | 'font-variant',
159 | 'font-weight',
160 | 'font-stretch',
161 | 'font-size',
162 | 'font-family',
163 | 'font-synthesis',
164 | 'font-size-adjust',
165 | 'font-kerning',
166 | 'line-height',
167 | 'text-align',
168 | 'text-align-last',
169 | 'vertical-align',
170 | 'text-overflow',
171 | 'text-justify',
172 | 'text-transform',
173 | 'text-indent',
174 | 'text-emphasis',
175 | 'text-emphasis-style',
176 | 'text-emphasis-color',
177 | 'text-emphasis-position',
178 | 'text-decoration',
179 | 'text-decoration-color',
180 | 'text-decoration-style',
181 | 'text-decoration-line',
182 | 'text-underline-position',
183 | 'text-shadow',
184 | 'white-space',
185 | 'overflow-wrap',
186 | 'word-wrap',
187 | 'word-break',
188 | 'line-break',
189 | 'hyphens',
190 | 'letter-spacing',
191 | 'word-spacing',
192 | 'quotes',
193 | 'tab-size',
194 | 'orphans',
195 | 'writing-mode',
196 | 'text-combine-upright',
197 | 'unicode-bidi',
198 | 'text-orientation',
199 | 'direction',
200 | 'text-rendering',
201 | 'font-feature-settings',
202 | 'font-language-override',
203 | 'image-rendering',
204 | 'image-orientation',
205 | 'image-resolution',
206 | 'shape-image-threshold',
207 | 'shape-outside',
208 | 'shape-margin',
209 | 'color',
210 | 'background',
211 | 'background-image',
212 | 'background-position',
213 | 'background-size',
214 | 'background-repeat',
215 | 'background-origin',
216 | 'background-clip',
217 | 'background-attachment',
218 | 'background-color',
219 | 'background-blend-mode',
220 | 'isolation',
221 | 'clip-path',
222 | 'mask',
223 | 'mask-image',
224 | 'mask-mode',
225 | 'mask-position',
226 | 'mask-size',
227 | 'mask-repeat',
228 | 'mask-origin',
229 | 'mask-clip',
230 | 'mask-composite',
231 | 'mask-type',
232 | 'filter',
233 | 'box-shadow',
234 | 'opacity',
235 | 'transform-style',
236 | 'transform',
237 | 'transform-box',
238 | 'transform-origin',
239 | 'perspective',
240 | 'perspective-origin',
241 | 'backface-visibility',
242 | 'transition',
243 | 'transition-property',
244 | 'transition-duration',
245 | 'transition-timing-function',
246 | 'transition-delay',
247 | 'animation',
248 | 'animation-name',
249 | 'animation-duration',
250 | 'animation-timing-function',
251 | 'animation-delay',
252 | 'animation-iteration-count',
253 | 'animation-direction',
254 | 'animation-fill-mode',
255 | 'animation-play-state',
256 | 'scroll-behavior',
257 | 'scroll-snap-type',
258 | 'scroll-snap-destination',
259 | 'scroll-snap-coordinate',
260 | 'cursor',
261 | 'touch-action',
262 | 'caret-color',
263 | 'ime-mode',
264 | 'object-fit',
265 | 'object-position',
266 | 'content',
267 | 'counter-reset',
268 | 'counter-increment',
269 | 'will-change',
270 | 'pointer-events',
271 | 'all',
272 | 'page-break-before',
273 | 'page-break-after',
274 | 'page-break-inside',
275 | 'widows',
276 | ],
277 | 'selector-type-case': null,
278 | 'selector-type-no-unknown': null,
279 | 'indentation': 'tab',
280 | 'color-no-invalid-hex': true,
281 | 'font-family-no-missing-generic-family-keyword': null,
282 | 'font-family-name-quotes': null,
283 | 'function-url-quotes': 'always',
284 | 'at-rule-no-unknown': null,
285 | 'no-eol-whitespace': null,
286 | 'selector-attribute-quotes': 'always',
287 | 'string-quotes': 'single',
288 | 'selector-pseudo-element-colon-notation': null,
289 | 'at-rule-no-vendor-prefix': true,
290 | 'media-feature-name-no-vendor-prefix': null,
291 | 'media-feature-name-no-unknown': null,
292 | 'property-no-vendor-prefix': null,
293 | 'selector-no-vendor-prefix': true,
294 | 'value-no-vendor-prefix': true,
295 | 'selector-pseudo-class-no-unknown': null,
296 | 'shorthand-property-no-redundant-values': null,
297 | 'at-rule-empty-line-before': null,
298 | 'at-rule-name-space-after': null,
299 | 'comment-empty-line-before': null,
300 | 'declaration-bang-space-before': null,
301 | 'declaration-empty-line-before': null,
302 | 'function-comma-newline-after': null,
303 | 'function-name-case': null,
304 | 'function-parentheses-newline-inside': null,
305 | 'function-max-empty-lines': null,
306 | 'function-whitespace-after': null,
307 | 'number-leading-zero': null,
308 | 'number-no-trailing-zeros': null,
309 | 'rule-empty-line-before': null,
310 | 'selector-combinator-space-after': null,
311 | 'selector-list-comma-newline-after': null,
312 | // "selector-pseudo-element-colon-notation": null,
313 | 'unit-no-unknown': null,
314 | 'no-descending-specificity': null,
315 | 'value-list-max-empty-lines': null,
316 | },
317 | };
318 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Chrome",
9 | "type": "chrome",
10 | "request": "launch",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}",
13 | "sourceMaps":true,
14 | // "sourceMapPathOverrides": {
15 | // "webpack:///src/*": "${webRoot}/*"
16 | // }
17 | },
18 | {
19 | "type": "node",
20 | "request": "launch",
21 | "name": "auto-get-module",
22 | "program": "${workspaceFolder}/scripts/auto-get-module/index.js"
23 | },
24 | {
25 | "type": "node",
26 | "request": "launch",
27 | "name": "npm run dev",
28 | "runtimeExecutable": "npm",
29 | "runtimeArgs": [
30 | "run-script",
31 | "dev"
32 | ],
33 | "port": 8080
34 | },
35 |
36 | {
37 | "type": "node",
38 | "request": "launch",
39 | "name": "build",
40 | "program": "${workspaceFolder}/scripts/build.js"
41 | },
42 | {
43 | "type": "node",
44 | "request": "launch",
45 | "name": "build_dll",
46 | "program": "${workspaceFolder}/scripts/build_dll.js"
47 | },
48 | {
49 | "type": "node",
50 | "request": "launch",
51 | "name": "create page",
52 | "program": "${workspaceFolder}/scripts/create-module/index.js"
53 | },
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true,
3 | "editor.fontSize": 18,
4 | "prettier.singleQuote": true,
5 | "prettier.useTabs": true,
6 | "prettier.trailingComma": "all",
7 | "javascript.implicitProjectConfig.experimentalDecorators": true,
8 | "emmet.includeLanguages": {
9 | "javascript": "javascriptreact"
10 | },
11 | "files.associations": {
12 | "*.html": "html"
13 | },
14 | "emmet.showExpandedAbbreviation": "always",
15 | "emmet.triggerExpansionOnTab": true,
16 | "editor.foldingStrategy": "indentation",
17 | "files.autoSave": "off",
18 | "diffEditor.ignoreTrimWhitespace": true,
19 | "css.validate": false,
20 | "scss.validate": false,
21 | "less.validate": false,
22 | "stylelint.enable": true,
23 | "editor.codeActionsOnSave": {
24 | "source.fixAll.eslint": true,
25 | "source.fixAll.stylelint": true
26 | },
27 | "typescript.tsdk": "node_modules/typescript/lib"
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 王小刚
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/buildConfig/channel/common/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | // const { protocol, hostname, port } = window.location;
4 |
5 | // const origin:string = `${protocol}//${hostname}${port ? `:${port}` : ''}`;
6 |
7 | // const baseUrl:string = `${origin}/factoring-scf-web`;
8 |
9 | type TUrlConfig = {
10 | development: string,
11 | testing: string,
12 | production: string,
13 | }
14 |
15 | type TConfig = {
16 | development: string;
17 | testing: string;
18 | production: string;
19 | }
20 | const env: keyof TConfig = (process.env.NODE_ENV as keyof TConfig) || 'production';
21 |
22 | console.log(process.env.PROJECT_ENV);
23 |
24 | const serverUrlConfig: TConfig = {
25 | // development: '/web/v1',
26 | development: '',
27 | testing: '',
28 | production: '',
29 | // testing: '/web/v1',
30 | // production: '/web/v1',
31 | };
32 |
33 | export default {
34 | serverUrl: serverUrlConfig[env],
35 | };
36 |
--------------------------------------------------------------------------------
/buildConfig/channel/common/menuConfig.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon } from 'antd';
3 |
4 | // { //一级菜单demo
5 | // key: '01', 唯一标记
6 | // icon: 'apple',图标
7 | // text: 'app',文案
8 | // auth: 'ap2',权限名称
9 | // authLevel: 20,权限等级
10 | // link: {跳转路径
11 | // pathname: 'cangku',
12 | // query: {
13 | // id: 1,
14 | // }
15 | // },
16 | // disabled: false,
17 | // },
18 | // { 二级菜单demo
19 | // key: 'sub1',
20 | // title: User,
21 | // children: [
22 | // { //子菜单数组
23 | // key: 'sub1-1',
24 | // text: 'Tttt',
25 | // link: {
26 | // pathname: 'tttt',
27 | // query: {
28 | // id: 'Tom',
29 | // }
30 | // },
31 | // },
32 | // {
33 | // key: 'sub1-2',
34 | // text: 'Bill',
35 | // link: {
36 | // pathname: 'result',
37 | // query: {
38 | // id: 'Bill',
39 | // }
40 | // },
41 | // }, {
42 | // key: 'sub1-3',
43 | // text: 'Alex',
44 | // link: {
45 | // pathname: 'result',
46 | // query: {
47 | // id: 'Alex',
48 | // }
49 | // },
50 | // },]
51 | // },
52 |
53 | export default [
54 |
55 | ];
56 |
--------------------------------------------------------------------------------
/buildConfig/channel/common/route.ts:
--------------------------------------------------------------------------------
1 |
2 | import loadabel from '@loadable/component';
3 |
4 | const routes = [
5 | {
6 | path: '/page1',
7 | component: loadabel(() => import('@/modules/Page1')) as React.ComponentClass | React.FC,
8 | },
9 | {
10 | path: '/page2',
11 | component: loadabel(() => import('@/modules/Page2')) as React.ComponentClass | React.FC,
12 | },
13 | {
14 | path: '/page3',
15 | component: loadabel(() => import('@/modules/Page3')) as React.ComponentClass | React.FC,
16 | },
17 | ];
18 |
19 | export default routes;
20 |
--------------------------------------------------------------------------------
/buildConfig/channel/mobile/index.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | serverUrlConfig: {
4 | development: 'http://dev/api',
5 | testing: 'http://test/api',
6 | production: 'http://prd/api',
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/buildConfig/channel/mobile/menuConfig.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Icon} from 'antd'
3 |
4 | // { //一级菜单demo
5 | // key: '01', 唯一标记
6 | // icon: 'apple',图标
7 | // text: 'app',文案
8 | // auth: 'ap2',权限名称
9 | // authLevel: 20,权限等级
10 | // link: {跳转路径
11 | // pathname: 'cangku',
12 | // query: {
13 | // id: 1,
14 | // }
15 | // },
16 | // disabled: false,
17 | // },
18 | // { 二级菜单demo
19 | // key: 'sub1',
20 | // title: User,
21 | // children: [
22 | // { //子菜单数组
23 | // key: 'sub1-1',
24 | // text: 'Tttt',
25 | // link: {
26 | // pathname: 'tttt',
27 | // query: {
28 | // id: 'Tom',
29 | // }
30 | // },
31 | // },
32 | // {
33 | // key: 'sub1-2',
34 | // text: 'Bill',
35 | // link: {
36 | // pathname: 'result',
37 | // query: {
38 | // id: 'Bill',
39 | // }
40 | // },
41 | // }, {
42 | // key: 'sub1-3',
43 | // text: 'Alex',
44 | // link: {
45 | // pathname: 'result',
46 | // query: {
47 | // id: 'Alex',
48 | // }
49 | // },
50 | // },]
51 | // },
52 |
53 | export default [
54 |
55 | {
56 | key: '基础管理',
57 | title: 基础管理,
58 | auth: '',
59 | children: [
60 | {
61 | key: '首页',
62 | text: '首页',
63 | auth: '',
64 | link: 'home',
65 | },
66 | ]
67 | },
68 |
69 | ];
70 |
--------------------------------------------------------------------------------
/buildConfig/channel/mobile/route.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/',
4 | key: 'app',
5 | breadcrumbName: '首页', // 面包屑的名字
6 | component: require('@/app/index'),
7 | indexRoute: {
8 | breadcrumbName: 'Home',
9 | component: require('@/pages/Home'),
10 | },
11 | childRoutes: [],
12 | },
13 | {
14 | path: '/login',
15 | component: require('@/layout/Login'),
16 | },
17 | ];
18 |
--------------------------------------------------------------------------------
/buildConfig/defaultConfig.js:
--------------------------------------------------------------------------------
1 | const defaultChannel = 'common';
2 | const defaultProject = 'src';
3 |
4 | module.exports = {
5 | defaultChannel,
6 | defaultProject,
7 | };
8 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | testPathIgnorePatterns: [
4 | 'mock-server/test/**/*.js',
5 | // "react-natural-store/test/hooks.test.js",
6 | // "react-natural-store/test/inject.test.js",
7 | // "react-natural-store/test/utils.test.js",
8 | // "react-natural-store/test/createStore.test.js",
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/mock-server/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | yarn.lock
13 |
14 | # OS
15 | .DS_Store
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/mock-server/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 | ## Description
28 |
29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
30 |
31 | ## Installation
32 |
33 | ```bash
34 | $ npm install
35 | ```
36 |
37 | ## Running the app
38 |
39 | ```bash
40 | # development
41 | $ npm run start
42 |
43 | # watch mode
44 | $ npm run start:dev
45 |
46 | # production mode
47 | $ npm run start:prod
48 | ```
49 |
50 | ## Test
51 |
52 | ```bash
53 | # unit tests
54 | $ npm run test
55 |
56 | # e2e tests
57 | $ npm run test:e2e
58 |
59 | # test coverage
60 | $ npm run test:cov
61 | ```
62 |
63 | ## Support
64 |
65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
66 |
67 | ## Stay in touch
68 |
69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
70 | - Website - [https://nestjs.com](https://nestjs.com/)
71 | - Twitter - [@nestframework](https://twitter.com/nestframework)
72 |
73 | ## License
74 |
75 | Nest is [MIT licensed](LICENSE).
76 |
--------------------------------------------------------------------------------
/mock-server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/mock-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mock-server",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "license": "MIT",
7 | "scripts": {
8 | "prebuild": "rimraf dist",
9 | "build": "nest build",
10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
11 | "start": "nest start",
12 | "dev": "nest start --watch",
13 | "start:dev": "nest start --watch",
14 | "start:debug": "nest start --debug --watch",
15 | "start:prod": "node dist/main",
16 | "create:controller": "nest g controller",
17 | "lint": "tslint -p tsconfig.json -c tslint.json",
18 | "test": "jest",
19 | "test:watch": "jest --watch",
20 | "test:cov": "jest --coverage",
21 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
22 | "test:e2e": "jest --config ./test/jest-e2e.json"
23 | },
24 | "dependencies": {
25 | "@nestjs/common": "^6.7.2",
26 | "@nestjs/core": "^6.7.2",
27 | "@nestjs/platform-express": "^6.7.2",
28 | "http-proxy-middleware": "^0.20.0",
29 | "mockjs": "^1.1.0",
30 | "reflect-metadata": "^0.1.13",
31 | "rimraf": "^3.0.0",
32 | "rxjs": "^6.5.3"
33 | },
34 | "devDependencies": {
35 | "@nestjs/cli": "^6.9.0",
36 | "@nestjs/schematics": "^6.7.0",
37 | "@nestjs/testing": "^6.7.1",
38 | "@types/express": "^4.17.1",
39 | "@types/jest": "^24.0.18",
40 | "@types/node": "^13.1.6",
41 | "@types/supertest": "^2.0.8",
42 | "jest": "^24.9.0",
43 | "prettier": "^1.18.2",
44 | "supertest": "^4.0.2",
45 | "ts-jest": "^24.1.0",
46 | "ts-loader": "^6.1.1",
47 | "ts-node": "^8.4.1",
48 | "tsconfig-paths": "^3.9.0",
49 | "tslint": "^5.20.0",
50 | "typescript": "^3.6.3"
51 | },
52 | "jest": {
53 | "moduleFileExtensions": [
54 | "js",
55 | "json",
56 | "ts"
57 | ],
58 | "rootDir": "src",
59 | "testRegex": ".spec.ts$",
60 | "transform": {
61 | "^.+\\.(t|j)s$": "ts-jest"
62 | },
63 | "coverageDirectory": "../coverage",
64 | "testEnvironment": "node"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/mock-server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AppService } from './service/app.service';
3 | import { CatsController } from './controller/cats/cats.controller';
4 | import { AppController } from './controller/app/app.controller';
5 | import { DemoController } from './controller/demo/demo.controller';
6 |
7 | @Module({
8 | imports: [],
9 | controllers: [CatsController, AppController, DemoController],
10 | providers: [AppService],
11 | })
12 | export class AppModule {}
13 |
--------------------------------------------------------------------------------
/mock-server/src/controller/app/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AppController } from './app.controller';
3 |
4 | describe('App Controller', () => {
5 | let controller: AppController;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | controllers: [AppController],
10 | }).compile();
11 |
12 | controller = module.get(AppController);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(controller).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/mock-server/src/controller/app/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 |
3 | @Controller('app')
4 | export class AppController {
5 | @Get()
6 | get() {
7 | return 'app'
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mock-server/src/controller/cats/cats.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { CatsController } from './cats.controller';
3 |
4 | describe('Cats Controller', () => {
5 | let controller: CatsController;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | controllers: [CatsController],
10 | }).compile();
11 |
12 | controller = module.get(CatsController);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(controller).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/mock-server/src/controller/cats/cats.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 |
3 | @Controller('cats')
4 | export class CatsController {
5 | @Get()
6 | getHello(): string {
7 | return 'cats';
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mock-server/src/controller/demo/demo.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { DemoController } from './demo.controller';
3 |
4 | describe('Demo Controller', () => {
5 | let controller: DemoController;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | controllers: [DemoController],
10 | }).compile();
11 |
12 | controller = module.get(DemoController);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(controller).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/mock-server/src/controller/demo/demo.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 |
3 | @Controller('demo')
4 | export class DemoController {
5 | @Get()
6 | get() {
7 | return 'demo';
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mock-server/src/main.ts:
--------------------------------------------------------------------------------
1 | import {NestFactory} from '@nestjs/core';
2 | import {AppModule} from './app.module';
3 | import isMockApi, {isMock, allIsMock} from './proxy/mock';
4 | import config from './proxy/config';
5 | import consoleStyle from './proxy/consoleStyle';
6 | const proxy = require('http-proxy-middleware');
7 |
8 | const commonProxy = proxy({
9 | target: config.devApiUrl,
10 | changeOrigin: true,
11 | autoRewrite: true,
12 | });
13 |
14 | async function bootstrap() {
15 | const app = await NestFactory.create(AppModule);
16 | app.setGlobalPrefix(config.baseUrl);
17 | app.use(config.baseUrl, (req, res, next) => {
18 | if (isMockApi(req.url)) {
19 | console.log(consoleStyle.inverse, 'mock接口:' + req.url);
20 | next();
21 | } else {
22 | commonProxy(req, res, next);
23 | }
24 | });
25 | setTimeout(() => {
26 | if (isMock) {
27 | if (allIsMock) {
28 | console.log(consoleStyle.cyan, `当前所有接口都在mock模式!`);
29 | console.log(consoleStyle.cyan, `当前所有接口都在mock模式!`);
30 | console.log(consoleStyle.cyan, `当前所有接口都在mock模式!`);
31 | } else {
32 | console.log(consoleStyle.blue, `当前部分接口在mock模式!`);
33 | console.log(consoleStyle.blue, `当前部分接口在mock模式!`);
34 | console.log(consoleStyle.blue, `当前部分接口在mock模式!`);
35 | }
36 | } else {
37 | console.log(consoleStyle.magenta, `当前不在mock模式!`);
38 | console.log(consoleStyle.magenta, `当前不在mock模式!`);
39 | console.log(consoleStyle.magenta, `当前不在mock模式!`);
40 | }
41 | }, 0)
42 | await app.listen(8089);
43 | }
44 |
45 | bootstrap();
46 |
--------------------------------------------------------------------------------
/mock-server/src/proxy/api/common.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | '/mine',
3 | ];
4 |
--------------------------------------------------------------------------------
/mock-server/src/proxy/config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | port: 3001,
3 | baseUrl: '/api',
4 | devApiUrl: 'http://localhost:3000',
5 | };
6 |
--------------------------------------------------------------------------------
/mock-server/src/proxy/consoleStyle.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | bold : '\x1B[1m%s\x1B[22m',
3 | italic : '\x1B[3m%s\x1B[23m',
4 | underline : '\x1B[4m%s\x1B[24m',
5 | inverse : '\x1B[7m%s\x1B[27m',
6 | strikethrough : '\x1B[9m%s\x1B[29m',
7 | white : '\x1B[37m%s\x1B[39m',
8 | grey : '\x1B[90m%s\x1B[39m',
9 | black : '\x1B[30m%s\x1B[39m',
10 | blue : '\x1B[34m%s\x1B[39m',
11 | cyan : '\x1B[36m%s\x1B[39m',
12 | green : '\x1B[32m%s\x1B[39m',
13 | magenta : '\x1B[35m%s\x1B[39m',
14 | red : '\x1B[31m%s\x1B[39m',
15 | yellow : '\x1B[33m%s\x1B[39m',
16 | whiteBG : '\x1B[47m%s\x1B[49m',
17 | greyBG : '\x1B[49;5;8m%s\x1B[49m',
18 | blackBG : '\x1B[40m%s\x1B[49m',
19 | blueBG : '\x1B[44m%s\x1B[49m',
20 | cyanBG : '\x1B[46m%s\x1B[49m',
21 | greenBG : '\x1B[42m%s\x1B[49m',
22 | magentaBG : '\x1B[45m%s\x1B[49m',
23 | redBG : '\x1B[41m%s\x1B[49m',
24 | yellowBG : '\x1B[43m%s\x1B[49m',
25 | };
26 |
--------------------------------------------------------------------------------
/mock-server/src/proxy/mock.ts:
--------------------------------------------------------------------------------
1 | import commonApi from './api/common';
2 |
3 | export const isMock = true; // mock开关
4 | export const allIsMock = true; // 是否所有接口都走mock
5 |
6 | const list = [
7 | ...commonApi,
8 | 'demo/233',
9 | 'cats'
10 | ];
11 |
12 | export default url => list.some(item => {
13 | if (isMock && allIsMock) {
14 | return true;
15 | }
16 | if (isMock) {
17 | return url.indexOf(item) > -1;
18 | }
19 | return false;
20 | });
21 |
--------------------------------------------------------------------------------
/mock-server/src/service/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | getHello(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/mock-server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/mock-server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mock-server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/mock-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es2017",
9 | "sourceMap": true,
10 | "outDir": "./dist",
11 | "rootDir": "./src/",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "typeRoots": [
15 | "./node_modules/@types"
16 | ]
17 | },
18 | "exclude": [
19 | "node_modules",
20 | "../node_modules/",
21 | "dist"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/mock-server/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended"],
4 | "jsRules": {
5 | "no-unused-expression": true
6 | },
7 | "rules": {
8 | "quotemark": [true, "single"],
9 | "member-access": [false],
10 | "no-console": false,
11 | "no-var-requires": false,
12 | "ordered-imports": [false],
13 | "max-line-length": [true, 150],
14 | "member-ordering": [false],
15 | "interface-name": [false],
16 | "arrow-parens": false,
17 | "object-literal-sort-keys": false
18 | },
19 | "rulesDirectory": []
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-app",
3 | "version": "1.0.0",
4 | "description": "react project",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "cross-env NODE_ENV=development node scripts/dev.js --PROJECT_ENV development",
8 | "mock": "cd mock-server && npm run start:dev",
9 | "build:dll": "node scripts/build_dll.js",
10 | "build:dev": "cross-env NODE_ENV=development node scripts/build.js --PROJECT_ENV development",
11 | "build:stg": "cross-env NODE_ENV=development node scripts/build.js --PROJECT_ENV stg",
12 | "build:prd": "cross-env NODE_ENV=production node scripts/build.js --PROJECT_ENV production",
13 | "create:module": "node scripts/create-module/index.js",
14 | "lint": "lint-staged",
15 | "cc": "cd ./mock-server && npm run create:controller"
16 | },
17 | "husky": {
18 | "hooks": {
19 | "pre-commit": "lint-staged"
20 | }
21 | },
22 | "lint-staged": {
23 | "src/**/*.{js,jsx,ts,tsx}": [
24 | "eslint --fix"
25 | ]
26 | },
27 | "keywords": [
28 | "webpack",
29 | "react",
30 | "starter",
31 | "typescript",
32 | "template"
33 | ],
34 | "author": "empty916",
35 | "license": "MIT",
36 | "devDependencies": {
37 | "@babel/cli": "^7.8.4",
38 | "@babel/core": "^7.8.4",
39 | "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
40 | "@babel/plugin-proposal-class-properties": "^7.8.3",
41 | "@babel/plugin-proposal-decorators": "^7.8.3",
42 | "@babel/plugin-proposal-export-default-from": "^7.8.3",
43 | "@babel/plugin-proposal-function-bind": "^7.8.3",
44 | "@babel/plugin-proposal-object-rest-spread": "^7.8.3",
45 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
46 | "@babel/plugin-transform-react-jsx-self": "^7.8.3",
47 | "@babel/plugin-transform-runtime": "^7.8.3",
48 | "@babel/preset-env": "^7.8.4",
49 | "@babel/preset-react": "^7.8.3",
50 | "@babel/preset-typescript": "^7.8.3",
51 | "@types/classnames": "^2.2.8",
52 | "@types/color": "^3.0.0",
53 | "@types/crypto-js": "^3.1.44",
54 | "@types/d3": "^5.7.2",
55 | "@types/history": "^4.7.5",
56 | "@types/hoist-non-react-statics": "^3.3.1",
57 | "@types/loadable__component": "^5.10.0",
58 | "@types/lodash": "^4.14.134",
59 | "@types/mui-datatables": "^3.4.1",
60 | "@types/qs": "^6.9.2",
61 | "@types/react": "^17.0.43",
62 | "@types/react-dom": "^17.0.14",
63 | "@types/react-motion": "^0.0.29",
64 | "@types/react-router-dom": "5.1.2",
65 | "@typescript-eslint/eslint-plugin": "^2.19.2",
66 | "@typescript-eslint/parser": "^2.19.2",
67 | "@webpack-cli/generate-loader": "^0.1.5",
68 | "@webpack-cli/init": "^0.1.5",
69 | "@webpack-cli/serve": "^0.1.5",
70 | "add-asset-html-webpack-plugin": "^3.1.3",
71 | "babel-eslint": "^10.0.3",
72 | "babel-loader": "^8.0.6",
73 | "babel-plugin-import": "^1.13.0",
74 | "babel-plugin-lodash": "^3.3.4",
75 | "chalk": "^4.0.0",
76 | "chokidar": "^3.4.2",
77 | "clean-webpack-plugin": "^2.0.2",
78 | "cross-env": "^5.2.0",
79 | "css-loader": "^2.1.1",
80 | "cv-script": "^0.0.6",
81 | "eslint": "^6.8.0",
82 | "eslint-config-airbnb": "^18.0.1",
83 | "eslint-friendly-formatter": "^4.0.1",
84 | "eslint-loader": "^3.0.3",
85 | "eslint-plugin-import": "^2.17.3",
86 | "eslint-plugin-jsx-a11y": "^6.2.1",
87 | "eslint-plugin-react": "^7.13.0",
88 | "eslint-plugin-react-hooks": "1.x",
89 | "file-loader": "^3.0.1",
90 | "fork-ts-checker-webpack-plugin": "^4.0.3",
91 | "friendly-errors-webpack-plugin": "^1.7.0",
92 | "glob": "^7.1.6",
93 | "happypack": "^5.0.1",
94 | "html-loader": "^0.5.5",
95 | "html-webpack-plugin": "3.2.0",
96 | "http-server": "^0.11.1",
97 | "husky": "^4.2.5",
98 | "inline-chunk": "^2.0.0",
99 | "json-loader": "^0.5.7",
100 | "less": "^3.10.3",
101 | "less-loader": "^5.0.0",
102 | "lint-staged": "^10.1.3",
103 | "lodash-webpack-plugin": "^0.11.5",
104 | "match-lazy-module": "^1.0.3",
105 | "mini-css-extract-plugin": "^0.6.0",
106 | "minimist": "^1.2.0",
107 | "nodemon": "^2.0.2",
108 | "optimize-css-assets-webpack-plugin": "^5.0.1",
109 | "ora": "^3.4.0",
110 | "postcss": "^7.0.16",
111 | "postcss-cssnext": "^3.1.0",
112 | "postcss-loader": "^3.0.0",
113 | "progress-bar-webpack-plugin": "^2.1.0",
114 | "react-dev-utils": "^10.2.1",
115 | "redux": "^4.0.4",
116 | "rimraf": "^2.6.3",
117 | "sass": "^1.26.10",
118 | "sass-loader": "8.0.1",
119 | "script-ext-html-webpack-plugin": "^2.1.4",
120 | "shelljs": "^0.8.3",
121 | "source-map-loader": "^0.2.4",
122 | "style-loader": "^0.23.1",
123 | "stylelint": "10.1.0",
124 | "stylelint-config-recommended": "^2.2.0",
125 | "stylelint-config-standard": "^18.3.0",
126 | "stylelint-order": "^3.0.1",
127 | "stylelint-webpack-plugin": "^0.10.5",
128 | "svg-sprite-loader": "^4.1.6",
129 | "terser-webpack-plugin": "^1.2.4",
130 | "typescript": "^3.9.7",
131 | "url-loader": "^1.1.2",
132 | "webpack": "4.39.0",
133 | "webpack-bundle-analyzer": "^3.6.0",
134 | "webpack-cli": "^3.3.2",
135 | "webpack-dev-server": "^3.3.1",
136 | "webpack-merge": "^4.2.1"
137 | },
138 | "dependencies": {
139 | "@date-io/date-fns": "1.x",
140 | "@loadable/component": "^5.10.1",
141 | "@material-ui/core": "^4.11.0",
142 | "@material-ui/icons": "^4.9.1",
143 | "@material-ui/lab": "^4.0.0-alpha.49",
144 | "@material-ui/pickers": "^3.2.10",
145 | "axios": "0.19.2",
146 | "classnames": "^2.2.6",
147 | "color": "^3.1.2",
148 | "convert-key": "^1.0.3",
149 | "core-js": "^3.6.5",
150 | "crypto-js": "^4.0.0",
151 | "date-fns": "^2.16.1",
152 | "dayjs": "^1.8.19",
153 | "decimal.js": "^10.2.0",
154 | "delay-load": "^1.0.0",
155 | "formik": "2.1.4",
156 | "formik-material-ui": "^2.0.1",
157 | "formik-material-ui-lab": "^0.0.4",
158 | "formik-material-ui-pickers": "^0.0.10",
159 | "history": "^4.9.0",
160 | "hoist-non-react-statics": "^3.3.2",
161 | "jss": "^10.4.0",
162 | "jss-plugin-default-unit": "^10.4.0",
163 | "lodash": "^4.17.15",
164 | "mui-datatables": "^3.4.1",
165 | "natur": "^2.1.10",
166 | "natur-immer": "^1.0.6-beta3",
167 | "natur-persist": "^1.2.5",
168 | "natur-promise-watcher": "^1.0.1",
169 | "natur-service": "^2.1.6",
170 | "qs": "^6.9.3",
171 | "react": "16.10.1",
172 | "react-dom": "16.10.1",
173 | "react-router": "^5.1.2",
174 | "react-router-dom": "^5.1.2",
175 | "regenerator-runtime": "^0.13.5"
176 | },
177 | "browserslist": [
178 | "> 1%",
179 | "last 2 versions",
180 | "not ie < 9"
181 | ]
182 | }
183 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const AUTOPREFIXER_BROWSERS = [
2 | 'Android 2.3',
3 | 'Android >= 4',
4 | 'Chrome >= 35',
5 | 'Firefox >= 31',
6 | 'Explorer >= 8',
7 | 'iOS >= 7',
8 | 'Opera >= 12',
9 | 'Safari >= 7.1'
10 | ]
11 |
12 | module.exports = {
13 | plugins: [
14 | require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS })
15 | ]
16 | }
--------------------------------------------------------------------------------
/scripts/auto-get-module/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const matchLazyModule = require('match-lazy-module');
3 |
4 | const project = "src";
5 | const slash = "/";
6 | const projectPath = path
7 | .join(__dirname, "..", "..", project)
8 | .replace(/\\|\//g, slash);
9 | const moduleDirName = "modules";
10 | const moduleBasePath = path.join(__dirname, "..", "..", project, moduleDirName);
11 | const matchFileName = "store";
12 |
13 | const importPathPrefix = "@";
14 | const fileName = path.join(__dirname, '..', '..', 'src', 'store', "lazyModule.ts");
15 |
16 |
17 | const generateLazyModule = matchLazyModule({
18 | projectPath,
19 | moduleBasePath,
20 | moduleDirName,
21 | matchFileName,
22 | importPathPrefix,
23 | fileName,
24 | exclude: /user/,
25 | });
26 |
27 | module.exports = generateLazyModule;
28 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const builder = require('./builder');
2 | const prodWebpackConfig = require('./webpack/webpack.prod.conf');
3 | const devWebpackConfig = require('./webpack/webpack.dev.conf');
4 | const autoGetModule = require('./auto-get-module');
5 |
6 | const isProd = process.env.NODE_ENV === 'production';
7 |
8 | autoGetModule();
9 | if (isProd) {
10 | builder(prodWebpackConfig, 'production package');
11 | } else {
12 | builder(devWebpackConfig, 'development package');
13 | }
14 |
--------------------------------------------------------------------------------
/scripts/build_dll.js:
--------------------------------------------------------------------------------
1 | // const rimraf = require('rimraf');
2 | // const path = require('path');
3 | const builder = require('./builder');
4 | const createWebpackConfig = require('./webpack/webpack.dll.conf');
5 |
6 | const devWebpackConfig = createWebpackConfig('development');
7 | const prodWebpackConfig = createWebpackConfig('production');
8 |
9 | builder(devWebpackConfig, 'development dll');
10 | builder(prodWebpackConfig, 'production dll');
11 |
--------------------------------------------------------------------------------
/scripts/builder.js:
--------------------------------------------------------------------------------
1 | // https://github.com/shelljs/shelljs
2 | 'use strict'
3 |
4 | require('shelljs/global')
5 | const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
6 | const ora = require('ora') //在执行脚本的过程中,用于在终端中显示一个类似loading的标记
7 | const chalk = require('chalk') //用于在终端中显示彩色文字
8 | // const fs = require('fs')
9 | // const path = require('path')
10 | // const merge = require('webpack-merge') //深拷贝对象
11 | // const rimraf = require('rimraf') //用于删除文件和文件夹
12 | const {getArg} = require('./webpack/utils');
13 | // const {distPath} = require('./webpack/config');
14 |
15 | const { site, project } = getArg();
16 |
17 | /**
18 | * 创建编译器函数
19 | * @param {*} webpackConfig
20 | */
21 | const doCompiler = async webpackConfig => {
22 | // const ProgressPlugin = require('webpack/lib/ProgressPlugin');
23 | const webpack = require('webpack');
24 |
25 | const compiler = webpack([webpackConfig]);
26 | // compiler.apply(new ProgressPlugin());
27 | compiler.hooks.done.tap("done", function(stats) {
28 | var rawMessages = stats.toJson({}, true);
29 | var messages = formatWebpackMessages(rawMessages);
30 | if (!messages.errors.length && !messages.warnings.length) {
31 | console.log(chalk.green.bold("🎉 编译成功!\n"));
32 | }
33 | if (messages.errors.length) {
34 | console.log(chalk.red.bold('❌ 编译失败!'));
35 | messages.errors.forEach(e => console.log(e));
36 | return;
37 | }
38 | if (messages.warnings.length) {
39 | console.log(chalk.yellow.bold("🙅 编译警告!"));
40 | messages.warnings.forEach(w => console.log(w));
41 | }
42 | });
43 | return await new Promise((resolve, reject) => {
44 | compiler.run((err, stats) => {
45 | if (err) throw reject(err);
46 | // process.stdout.write(stats.toString({
47 | // colors: true,
48 | // modules: false,
49 | // children: false,
50 | // chunks: false,
51 | // chunkModules: false
52 | // }) + '\n\n')
53 | resolve(stats);
54 | })
55 | });
56 | }
57 |
58 | /**
59 | * 打包编译函数
60 | */
61 | module.exports = async function builder(webpackConfig, processName = `${site} ${project}`) {
62 | // const spinner = ora(`building ${processName} ...`)
63 | // spinner.start()
64 | await doCompiler(webpackConfig);
65 | // spinner.stop()
66 | console.log(chalk.cyan(`Build ${processName} complete.\n`))
67 | }
68 |
--------------------------------------------------------------------------------
/scripts/create-module/index.js:
--------------------------------------------------------------------------------
1 | const CvScript = require('cv-script').default;
2 | const path = require('path');
3 | const { fileDataMap, moduleNameQuestion } = require('cv-script/dist/utils');
4 |
5 | // 模板地址
6 | const templatePath = path.join(__dirname, 'templates');
7 | const distPath = path.join(__dirname, '..', '..', 'src', 'modules');
8 |
9 |
10 | const cvs = new CvScript({
11 | // questions: [moduleNameQuestion],
12 | templateDirPath: templatePath,
13 | // templateFilePath: templatePath,
14 | distPath: distPath,
15 | fileDataMaps: [fileDataMap],
16 | });
17 |
18 | cvs.start();
19 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base-service/http.ts:
--------------------------------------------------------------------------------
1 | // import http from '@/http';
2 | import ck from 'convert-key';
3 |
4 | const dataMap = {
5 | userName: 'name',
6 | userAge: 'age',
7 | }
8 |
9 | type dataMap = {
10 | userName: 'name',
11 | userAge: 'age',
12 | }
13 |
14 | const ckData = ck(dataMap);
15 |
16 | const myData = ckData({
17 | userName: 'tom',
18 | userAge: 14,
19 | })
20 | console.log(myData.name === 'tom');
21 | console.log(myData.age === 14);
22 |
23 | export default {
24 |
25 | }
--------------------------------------------------------------------------------
/scripts/create-module/templates/base-service/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { inject } from '@/store';
3 | import style from './style.scss';
4 |
5 |
6 | const injector = inject('template');
7 |
8 | const Template: React.FC = ({template}) => {
9 | const {state, maps} = template;
10 | return (
11 |
12 | {state.name}
13 | {maps.nameSplit}
14 |
15 | );
16 | };
17 |
18 | export default injector(Template);
19 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base-service/store.ts:
--------------------------------------------------------------------------------
1 | export const state = {
2 | name: 'template',
3 | };
4 |
5 | export const maps = {
6 | nameSplit: ['name', (name: string) => name.split('')],
7 | };
8 |
9 | export const actions = {
10 | update: (newState: any) => newState,
11 | asyncUpdate: async (newState: any) => {
12 | await new Promise(res => setTimeout(res, 3000));
13 | return newState;
14 | },
15 | };
16 |
17 | type State = typeof state;
18 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base-service/style.scss:
--------------------------------------------------------------------------------
1 | .template {
2 | box-sizing: border-box;
3 | width: 100%;
4 | height: 100%;
5 | background-color: #fff;
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { inject } from '@/store';
3 | import style from './style.scss';
4 |
5 |
6 | const injector = inject('template');
7 |
8 | const Template: React.FC = ({template}) => {
9 | const {state, maps} = template;
10 | return (
11 |
12 | {state.name}
13 | {maps.nameSplit}
14 |
15 | );
16 | };
17 |
18 | export default injector(Template);
19 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base/store.ts:
--------------------------------------------------------------------------------
1 | export const state = {
2 | name: 'template',
3 | };
4 |
5 | export const maps = {
6 | nameSplit: ['name', (name: string) => name.split('')],
7 | };
8 |
9 | export const actions = {
10 | update: (newState: any) => newState,
11 | asyncUpdate: async (newState: any) => {
12 | await new Promise(res => setTimeout(res, 3000));
13 | return newState;
14 | },
15 | };
16 |
17 | type State = typeof state;
18 |
--------------------------------------------------------------------------------
/scripts/create-module/templates/base/style.scss:
--------------------------------------------------------------------------------
1 | .template {
2 | box-sizing: border-box;
3 | width: 100%;
4 | height: 100%;
5 | background-color: #fff;
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const chalk = require("chalk");
3 | const WebpackDevServer = require("webpack-dev-server");
4 | const autoGetModule = require("./auto-get-module");
5 | const webpackConfig = require("./webpack/webpack.dev.conf");
6 | const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
7 | const openBrowser = require('react-dev-utils/openBrowser');
8 | const clearConsole = require('react-dev-utils/clearConsole');
9 | const chokidar = require('chokidar');
10 | const path = require('path');
11 |
12 |
13 | // const webpackConfig = createWebpackConfig();
14 | const compiler = webpack(webpackConfig);
15 | // 获取本机电脑IP
16 | function getIP() {
17 | let interfaces = require('os').networkInterfaces();
18 | for (var devName in interfaces) {
19 | var iface = interfaces[devName];
20 | for (var i = 0; i < iface.length; i++) {
21 | let alias = iface[i];
22 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
23 | return alias.address
24 | }
25 | }
26 | }
27 | }
28 |
29 | const { port = 8080, host = "localhost" } = webpackConfig.devServer;
30 |
31 | compiler.hooks.invalid.tap("invalid", function() {
32 | clearConsole();
33 | console.log(chalk.yellow.bold("🍼 编译中..."));
34 | });
35 |
36 | chokidar.watch(path.resolve(__dirname, '../src/modules'))
37 | .on('all', autoGetModule);
38 |
39 | let hasOpenedBrowser = false;
40 |
41 | compiler.hooks.done.tap("done", function(stats) {
42 | var rawMessages = stats.toJson({}, true);
43 | var messages = formatWebpackMessages(rawMessages);
44 | if (!messages.errors.length && !messages.warnings.length) {
45 | console.log(chalk.green.bold("🎉 编译成功!\n"));
46 | setTimeout(() => {
47 | console.log('本机网络: ' + chalk.cyan.bold(`http://localhost:${port}`));
48 | console.log('局域网络: ' + chalk.cyan.bold(`http://${getIP()}:${port}`));
49 | }, 0)
50 | }
51 | if (messages.errors.length) {
52 | console.log(chalk.red.bold('❌ 编译失败!'));
53 | messages.errors.forEach(e => console.log(e));
54 | return;
55 | }
56 | if (messages.warnings.length) {
57 | console.log(chalk.yellow.bold("🙅 编译警告!"));
58 | messages.warnings.forEach(w => console.log(w));
59 | }
60 | if (!hasOpenedBrowser) {
61 | hasOpenedBrowser = true;
62 | setTimeout(() => {
63 | openBrowser(`http://localhost:${port}`);
64 | }, 0);
65 | }
66 | });
67 |
68 | // compiler.hooks.watchRun.tap("autoGetModule", autoGetModule);
69 |
70 | const server = new WebpackDevServer(compiler, {
71 | ...webpackConfig.devServer
72 | });
73 |
74 | server.listen(port, host, () => {
75 | clearConsole();
76 | console.log(chalk.yellow.bold("🍼 编译中..."));
77 | });
78 |
--------------------------------------------------------------------------------
/scripts/nginx.conf:
--------------------------------------------------------------------------------
1 |
2 | user nginx;
3 | worker_processes 1;
4 |
5 | error_log /var/log/nginx/error.log warn;
6 | pid /var/run/nginx.pid;
7 |
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 |
13 |
14 | http {
15 | include /etc/nginx/mime.types;
16 | default_type application/octet-stream;
17 |
18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
19 | '$status $body_bytes_sent "$http_referer" '
20 | '"$http_user_agent" "$http_x_forwarded_for"';
21 |
22 | access_log /var/log/nginx/access.log main;
23 |
24 | sendfile on;
25 | #tcp_nopush on;
26 |
27 | keepalive_timeout 65;
28 |
29 | #gzip on;
30 | server {
31 | listen 80;
32 | server_name localhost;
33 | gzip on;
34 | gzip_buffers 32 4K;
35 | gzip_comp_level 6;
36 | gzip_min_length 1k;
37 | gzip_types application/javascript text/css text/xml;
38 | gzip_disable "MSIE [1-6]\."; #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
39 | gzip_vary on;
40 |
41 | # charset utf-8;
42 | #access_log /var/log/nginx/host.access.log main;
43 |
44 | location / {
45 | root /usr/share/nginx/html/rpt;
46 | try_files $uri $uri/ /index.html;
47 | index index.html index.htm;
48 | }
49 | }
50 |
51 |
52 | # include /etc/nginx/conf.d/*.conf;
53 | }
54 |
--------------------------------------------------------------------------------
/scripts/webpack/config.js:
--------------------------------------------------------------------------------
1 | const { getArg, getPath } = require('./utils');
2 |
3 | const { channel, project, PROJECT_ENV } = getArg();
4 |
5 | const publicPath = PROJECT_ENV === 'development' ? '/' : '/';
6 |
7 | module.exports = {
8 | dllPath: getPath('scripts', 'dll'),
9 | distPath: getPath('dist', channel, project),
10 | publicPath,
11 | jsPublicPath: publicPath,
12 | cssPublicPath: publicPath,
13 | imgPublicPath: publicPath
14 | };
15 |
--------------------------------------------------------------------------------
/scripts/webpack/utils.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const minimist = require('minimist');
4 | const glob = require('glob');
5 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7 |
8 | const {
9 | defaultChannel,
10 | defaultProject,
11 | } = require('../../buildConfig/defaultConfig');
12 |
13 | const getPath = (...$path) => path.join(__dirname, '..', '..', ...$path);
14 |
15 | const getArg = () => {
16 | const arg = minimist(process.argv.slice(2));
17 | const res = {
18 | ...arg,
19 | project: arg.project || defaultProject,
20 | channel: arg.channel || defaultChannel,
21 | PROJECT_ENV: arg.PROJECT_ENV || process.env.NODE_ENV || 'production',
22 | };
23 | return res;
24 | };
25 |
26 | module.exports.getPath = getPath;
27 | module.exports.getArg = getArg;
28 |
29 | // dll 通用配置 start
30 | const { dllPath, jsPublicPath, cssPublicPath } = require('./config');
31 |
32 | const { channel, project } = getArg();
33 | const getAddAssethtmlPluginsConfig = mode => glob.sync(`${dllPath}/${mode}/*.dll.js`).map(
34 | dllPath => new AddAssetHtmlPlugin({
35 | filepath: dllPath,
36 | includeSourcemap: false,
37 | typeOfAsset: 'js',
38 | outputPath: 'js',
39 | publicPath: jsPublicPath.endsWith('/') ? `${jsPublicPath}js/` : `${jsPublicPath}/js/`,
40 | }),
41 | );
42 | const getDllReferencePluginsConfig = mode => glob.sync(`${dllPath}/${mode}/*.json`).map(
43 | dllJsonPath => new webpack.DllReferencePlugin({
44 | // name: 'js/reactDll_1.0.0.dll.js',
45 | manifest: dllJsonPath,
46 | }),
47 | );
48 |
49 | module.exports.addDllPluginsConfig = mode => [
50 | // new webpack.DllReferencePlugin({
51 | // // name: 'js/reactDll_1.0.0.dll.js',
52 | // manifest: getPath('server', 'dll', mode, 'reactDll.json'),
53 | // }),
54 | ...getDllReferencePluginsConfig(mode),
55 | ...getAddAssethtmlPluginsConfig(mode),
56 | ];
57 |
58 |
59 | const cssModule = [
60 | new RegExp('src/|\modules'),
61 | new RegExp('src//|\components'),
62 | ]
63 | module.exports.createStyleLoader = (mode, isDev = mode === 'development') => [
64 | {
65 | test: /\.(s?css)$/,
66 | include: cssModule,
67 | use: [
68 | // isDev ? 'style-loader' :
69 | {
70 | loader: MiniCssExtractPlugin.loader,
71 | options: {
72 | hmr: isDev,
73 | publicPath: cssPublicPath,
74 | },
75 | },
76 | {
77 | loader: 'css-loader',
78 | options: {
79 | modules: true,
80 | importLoaders: 1,
81 | localIdentName: isDev
82 | ? '[local]-[hash:base64:8]'
83 | : '[hash:base64:16]',
84 | },
85 | },
86 | isDev ? null : {
87 | loader: 'postcss-loader',
88 | },
89 | {
90 | loader: 'sass-loader',
91 | },
92 | ].filter(Boolean),
93 | },
94 | {
95 | test: /\.(s?css)$/,
96 | exclude: cssModule,
97 | use: [
98 | // isDev ? 'style-loader' :
99 | {
100 | loader: MiniCssExtractPlugin.loader,
101 | options: {
102 | hmr: isDev,
103 | publicPath: cssPublicPath,
104 | },
105 | },
106 | {
107 | loader: 'css-loader',
108 | },
109 | isDev ? null : {
110 | loader: 'postcss-loader',
111 | },
112 | {
113 | loader: 'sass-loader',
114 | },
115 | ].filter(Boolean),
116 | },
117 | {
118 | test: /\.less$/,
119 | include: cssModule,
120 | use: [
121 | // isDev ? 'style-loader' :
122 | {
123 | loader: MiniCssExtractPlugin.loader,
124 | options: {
125 | hmr: isDev,
126 | publicPath: cssPublicPath,
127 | },
128 | },
129 | {
130 | loader: 'css-loader',
131 | options: {
132 | modules: true,
133 | importLoaders: 1,
134 | localIdentName: isDev
135 | ? '[local]-[hash:base64:8]'
136 | : '[hash:base64:16]',
137 | },
138 | },
139 | isDev ? null : {
140 | loader: 'postcss-loader',
141 | },
142 | {
143 | loader: 'less-loader',
144 | },
145 | ].filter(Boolean),
146 | },
147 | {
148 | test: /\.less$/,
149 | exclude: cssModule,
150 | use: [
151 | {
152 | loader: MiniCssExtractPlugin.loader,
153 | options: {
154 | hmr: isDev,
155 | publicPath: cssPublicPath,
156 | },
157 | },
158 | {
159 | loader: 'css-loader',
160 | },
161 | isDev ? null : {
162 | loader: 'postcss-loader',
163 | },
164 | {
165 | loader: 'less-loader',
166 | },
167 | ].filter(Boolean),
168 | },
169 | ];
170 |
171 | module.exports.createStylePlugin = (mode, isDev) => new MiniCssExtractPlugin(isDev ? {
172 | chunkFilename: 'style/[name].[contenthash:8].css',
173 | }: {
174 | filename: 'style/[name].[contenthash:8].css',
175 | });
176 |
177 | // dll 通用配置 end
178 |
--------------------------------------------------------------------------------
/scripts/webpack/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | // const path = require('path');
2 | const webpack = require("webpack");
3 | const HappyPack = require("happypack");
4 | const TerserPlugin = require("terser-webpack-plugin");
5 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
6 | const HtmlWebpackPlugin = require("html-webpack-plugin");
7 | const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
8 | const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");
9 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
10 | const clearConsole = require('react-dev-utils/clearConsole');
11 | const chalk = require('chalk');
12 | const InlineChunk = require('inline-chunk');
13 |
14 | const {
15 | getPath,
16 | getArg,
17 | addDllPluginsConfig,
18 | createStyleLoader,
19 | createStylePlugin
20 | } = require("./utils");
21 |
22 | const { distPath, publicPath } = require("./config");
23 |
24 | const { project, channel, PROJECT_ENV } = getArg();
25 |
26 | const mode = process.env.NODE_ENV;
27 | const isDev = mode === "development";
28 |
29 | module.exports = {
30 | performance: false,
31 | entry: {
32 | index: getPath(project, "index")
33 | },
34 | output: {
35 | path: distPath
36 | },
37 | resolve: {
38 | // 设置模块导入规则,import/require时会直接在这些目录找文件
39 | modules: [
40 | getPath(`${project}/components/business`),
41 | getPath(`${project}/components/base`),
42 | getPath("common/components/business"),
43 | getPath("common/components/base"),
44 | "node_modules"
45 | ],
46 | // import导入时省略后缀
47 | extensions: [".ts", ".tsx", ".js", ".jsx", ".scss", ".css"],
48 | // import导入时别名
49 | alias: {
50 | // common
51 | "@": getPath(`${project}`),
52 | "@base": getPath(`${project}/components/base`),
53 | "@history": getPath(`${project}/routes/history`),
54 | "@biz": getPath(`${project}/components/business`),
55 | "@styles": getPath(`${project}/utils/styles`),
56 | "@channel": getPath(`buildConfig/channel/${channel}`)
57 | }
58 | },
59 | module: {
60 | rules: [
61 | {
62 | test: /\.(j|t)s(x)?$/,
63 | exclude: /node_modules/,
64 | loader: "happypack/loader?id=babel"
65 | },
66 | ...createStyleLoader(mode, isDev),
67 | {
68 | test: /\.(png|jpe?g|gif)(\?.*)?$/,
69 | use: [
70 | {
71 | loader: "url-loader",
72 | options: {
73 | name: "img/[name].[ext]",
74 | limit: 2048,
75 | fallback: "file-loader"
76 | }
77 | }
78 | ]
79 | },
80 | {
81 | test: /\.svg$/,
82 | use: [
83 | {
84 | loader: "svg-sprite-loader",
85 | options: {
86 | symbolId: "icon-[name]"
87 | }
88 | }
89 | ]
90 | },
91 | {
92 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
93 | use: "url-loader"
94 | },
95 | {
96 | test: /\.html$/,
97 | use: "html-loader",
98 | include: new RegExp(`(/|\)${project}`),
99 | exclude: new RegExp(`(/|\)${project}(/|\)index.html`)
100 | },
101 | {
102 | test: /\.json$/,
103 | type: "javascript/auto",
104 | use: "json-loader"
105 | }
106 | ]
107 | },
108 | optimization: {
109 | splitChunks: {
110 | cacheGroups: {
111 | vendors: {
112 | priority: 0,
113 | name: 'vendors',
114 | test: new RegExp(`((/|\)${project})|((/|\)node_modules)`),
115 | // test: /\/node_modules/,
116 | minChunks: 2
117 | },
118 | styles: {
119 | priority: 9,
120 | name: "styles",
121 | test: /\.(s)?css$/,
122 | }
123 | },
124 | chunks: "all",
125 | minChunks: 1,
126 | minSize: 0,
127 | name: true
128 | },
129 | minimizer: [
130 | new TerserPlugin({
131 | test: /\.js(\?.*)?$/i,
132 | parallel: true,
133 | cache: true,
134 | sourceMap: true,
135 | // warnings: false,
136 | terserOptions: {
137 | output: {
138 | comments: false
139 | },
140 | compress: {
141 | warnings: false,
142 | drop_debugger: true,
143 | drop_console: true
144 | }
145 | }
146 | }),
147 | new OptimizeCSSAssetsPlugin()
148 | ]
149 | },
150 | plugins: [
151 | new ProgressBarPlugin({
152 | summary: false,
153 | complete: '■',
154 | width: 20,
155 | // complete: '=',
156 | // format: `building ${chalk.cyan.bold(':bar ')} :percent ( :elapseds )`,
157 | // format: `🍵 ${chalk.cyan.bold(':bar ')}${chalk.yellow(':percent ( :elapseds )')}`,
158 | format: '🍵 ' + chalk.cyan.bold(':bar ') + chalk.cyan.bold(':percent ') + chalk.yellow.bold('( :elapseds )'),
159 | customSummary: (times) => {
160 | clearConsole();
161 | setTimeout(() => {
162 | console.log(chalk.yellow.bold('⏰ 编译时间:' + times));
163 | }, 0)
164 | }
165 | }),
166 | new LodashModuleReplacementPlugin({
167 | collections: true,
168 | paths: true
169 | }),
170 | new webpack.DefinePlugin({
171 | "process.env.PROJECT_ENV": JSON.stringify(PROJECT_ENV),
172 | "process.env.BASE_URL": JSON.stringify(publicPath)
173 | }),
174 | new HappyPack({
175 | id: "babel",
176 | threads: 1,
177 | verbose: false,
178 | loaders: [
179 | {
180 | loader: "babel-loader",
181 | cacheDirectory: true
182 | }
183 | ]
184 | }),
185 | createStylePlugin(mode, isDev),
186 | new HtmlWebpackPlugin({
187 | title: "react project template",
188 | filename: "index.html",
189 | template: `./${project}/index.html`,
190 | // favicon: isDev ? '' : `${project}/favicon.ico`,
191 | // 防止各channel项目一样时,不生成html文件
192 | cache: false,
193 | minify: {
194 | removeComments: true,
195 | collapseWhitespace: true,
196 | removeRedundantAttributes: true,
197 | useShortDoctype: true,
198 | removeEmptyAttributes: true,
199 | removeStyleLinkTypeAttributes: true,
200 | keepClosingSlash: true,
201 | minifyJS: true,
202 | minifyCSS: true,
203 | minifyURLs: true
204 | },
205 | inject: true
206 | // hash: true,
207 | }),
208 | new ScriptExtHtmlWebpackPlugin({
209 | defaultAttribute: "defer"
210 | }),
211 | new InlineChunk({
212 | name: 'theme',
213 | test: new RegExp(`(/|\)${project}(/|\)theme(/|\)native`),
214 | }),
215 | ...addDllPluginsConfig(mode)
216 | ]
217 | };
218 |
--------------------------------------------------------------------------------
/scripts/webpack/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const merge = require('webpack-merge');
3 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
4 | const StyleLintPlugin = require('stylelint-webpack-plugin');
5 | const baseConfig = require('./webpack.base.conf');
6 | const eslintFormatter = require('react-dev-utils/eslintFormatter');
7 |
8 | const {
9 | getPath,
10 | getArg,
11 | } = require('./utils');
12 |
13 | const { jsPublicPath } = require('./config');
14 | const { project, channel, PROJECT_ENV } = getArg();
15 |
16 | const mode = 'development';
17 | const isDev = true;
18 |
19 | module.exports = merge(baseConfig, {
20 | mode,
21 | devtool: 'inline-source-map',
22 | // devtool: 'source-map',
23 | output: {
24 | chunkFilename: 'js/[name].js',
25 | filename: 'js/[name].js',
26 | publicPath: jsPublicPath
27 | },
28 | resolve: {
29 | alias: {
30 | '@redux-devtool': getPath(project, 'store', 'common', 'redux.devtool.ts'),
31 | },
32 | },
33 | module: {
34 | rules: [
35 | {
36 | test: /\.(j|t)s(x)?$/,
37 | include: getPath(project),
38 | // include: [getPath(project)],
39 | loader: 'eslint-loader',
40 | exclude: /node_modules/,
41 | enforce: 'pre',
42 | options: {
43 | // formatter: require('eslint-friendly-formatter'),
44 | formatter: eslintFormatter,
45 | },
46 | },
47 | ],
48 | },
49 | plugins: [
50 | new ForkTsCheckerWebpackPlugin({
51 | // tsconfig: './server/tsconfig.json',
52 | async: false,
53 | useTypescriptIncrementalApi: true,
54 | checkSyntacticErrors: true,
55 | silent: true,
56 | memoryLimit: 1024,
57 | compilerOptions: {
58 | paths: {
59 | '@channel': [`buildConfig/channel/${channel}/index.ts`],
60 | '@channel/*': [`buildConfig/channel/${channel}/*`],
61 | "@history": [`${project}/routes/history.ts`],
62 | '@': [`${project}`],
63 | '@/*': [`${project}/*`],
64 | '@redux-devtool': [`${project}/store/common/redux.devtool.ts`],
65 | "@base/*": [`${project}/components/base/*`],
66 | "@biz/*": [`${project}/components/business/*`],
67 | },
68 | },
69 | }),
70 | // new webpack.HotModuleReplacementPlugin({
71 | // // Options...
72 | // })
73 | ],
74 | devServer: {
75 | // open: true,
76 | progress: false,
77 | // compress: true,
78 | quiet: true,
79 | clientLogLevel: 'none',
80 | stats: 'errors-only',
81 | overlay: true,
82 | noInfo: true,
83 | port: 8080,
84 | // hot: true,
85 | host: '0.0.0.0',
86 | historyApiFallback: true,
87 | proxy: {
88 | '/api': {
89 | target: 'http://localhost:8090',
90 | }
91 | }
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/scripts/webpack/webpack.dll.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const merge = require('webpack-merge');
4 | const HappyPack = require('happypack');
5 | const CleanWebpackPlugin = require('clean-webpack-plugin');
6 | // const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
7 |
8 | const TerserPlugin = require('terser-webpack-plugin');
9 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
10 |
11 | // const baseConfig = require('./webpack.base.conf');
12 | const { getPath, getArg } = require('./utils');
13 | const { dllPath: dllBasePath } = require('./config');
14 |
15 | const { project } = getArg();
16 |
17 | // const { channel, project } = getArg();
18 | const dllVersion = '1.0.0';
19 | /**
20 | * @param {*} mode = ['development', 'production']
21 | */
22 | module.exports = mode => {
23 | const dllPath = path.resolve(dllBasePath, mode);
24 | // getPath('scripts', 'dll', mode);
25 | const isDev = mode === 'development';
26 | return {
27 | entry: {
28 | utilsDll: [
29 | 'core-js/stable',
30 | 'regenerator-runtime/runtime',
31 | 'axios',
32 | 'convert-key',
33 | 'delay-load',
34 | // 'natur-persist',
35 | // 'natur-service',
36 | 'dayjs',
37 | 'qs',
38 | // 'color',
39 | // 'lodash/curry',
40 | // 'lodash/cloneDeep',
41 | // 'lodash/fp/pipe',
42 | // 'lodash/fp/curry',
43 | // 'react-motion',
44 | ],
45 | baseDll: [
46 | 'react',
47 | 'react-dom',
48 | 'react-router',
49 | 'react-router-dom',
50 | 'hoist-non-react-statics',
51 | 'history',
52 | 'classnames',
53 | // 'natur',
54 | // 'react-redux',
55 | // 'redux',
56 | // 'redux-thunk',
57 | // 'reselect',
58 | ],
59 | // fpDll: [
60 | // 'lodash/curry',
61 | // 'lodash/cloneDeep',
62 | // 'lodash/fp/pipe',
63 | // ],
64 | },
65 | mode,
66 | devtool: isDev ? 'eval-source-map' : false,
67 | output: {
68 | filename: `[name]_${dllVersion}.dll.js`,
69 | path: dllPath,
70 | library: '[name]',
71 | },
72 | module: {
73 | rules: [
74 | {
75 | test: /\.js$/,
76 | include: [getPath(project)],
77 | // loader: 'babel-loader',
78 | loader: 'happypack/loader?id=babel',
79 | },
80 | {
81 | test: /\.(s?css)$/,
82 | use: [
83 | {
84 | loader: 'style-loader',
85 | },
86 | {
87 | loader: 'css-loader',
88 | },
89 | {
90 | loader: 'postcss-loader',
91 | },
92 | {
93 | loader: 'sass-loader',
94 | },
95 | ],
96 | },
97 | ],
98 | },
99 | optimization: {
100 | minimizer: [
101 | new TerserPlugin({
102 | test: /\.js(\?.*)?$/i,
103 | parallel: true,
104 | cache: true,
105 | terserOptions: {
106 | output: {
107 | comments: false,
108 | },
109 | compress: {
110 | warnings: false,
111 | drop_debugger: true,
112 | drop_console: true,
113 | },
114 | },
115 | }),
116 | new OptimizeCSSAssetsPlugin(),
117 | ],
118 | },
119 | plugins: [
120 | new HappyPack({
121 | id: 'babel',
122 | threads: 1,
123 | loaders: [
124 | {
125 | loader: 'babel-loader',
126 | cacheDirectory: true,
127 | },
128 | ],
129 | }),
130 | new webpack.DllPlugin({
131 | name: '[name]', // json文件名
132 | path: path.join(dllPath, '[name].json'), // 生成映射表json文件地址
133 | }),
134 | // 删除文件
135 | new CleanWebpackPlugin(),
136 | ],
137 | };
138 | };
139 |
--------------------------------------------------------------------------------
/scripts/webpack/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const CleanWebpackPlugin = require('clean-webpack-plugin');
3 |
4 | const baseConfig = require('./webpack.base.conf');
5 | const {
6 | getArg,
7 | getPath,
8 | } = require('./utils');
9 |
10 | const { project, channel } = getArg();
11 | const { jsPublicPath } = require('./config');
12 | // const mode = 'production';
13 | const mode = process.env.NODE_ENV;
14 | const isDev = false;
15 | module.exports = merge(baseConfig, {
16 | mode,
17 | devtool: false,
18 | // devtool: 'source-map',
19 | output: {
20 | chunkFilename: 'js/[name].[chunkhash].js',
21 | filename: 'js/[name].[chunkhash].js',
22 | publicPath: jsPublicPath,
23 | },
24 | resolve: {
25 | alias: {
26 | '@redux-devtool': getPath(project, 'store', 'common', 'no.devtool.ts'),
27 | },
28 | },
29 | plugins: [
30 | // 删除文件
31 | new CleanWebpackPlugin(),
32 | // new BundleAnalyzerPlugin(),
33 | ],
34 | });
35 |
--------------------------------------------------------------------------------
/src/App/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch } from 'react-router-dom';
3 | import { CssBaseline } from '@material-ui/core';
4 | import { create } from 'jss';
5 | import defaultUnit from 'jss-plugin-default-unit';
6 | import unitConfig from '@/theme/unit';
7 | import { ThemeProvider, jssPreset, StylesProvider } from '@material-ui/core/styles';
8 | import materialTheme from '@/theme/material';
9 | import history from '@history';
10 | import routes from '@/routes';
11 | import '@/service';
12 | import '@/theme/native/theme.scss';
13 | import '@/service/common/theme';
14 | import { inject } from '@/store';
15 | import DateFnsUtils from '@date-io/date-fns';
16 | import zhLocale from 'date-fns/locale/zh-CN';
17 |
18 | import { MuiPickersUtilsProvider } from '@material-ui/pickers';
19 | import Toast from '@/components/base/Toast';
20 | import Loading from '@/components/base/Loading';
21 | import AuthRoute from '@/routes/AuthRoute';
22 |
23 | const injector = inject(['router', {}]);
24 |
25 | const plugins = jssPreset().plugins.slice();
26 | plugins[4] = defaultUnit(unitConfig);
27 |
28 | const jss = create({
29 | plugins,
30 | insertionPoint: document.getElementById('jss-insertion-point')!,
31 | });
32 |
33 | class LocalizedUtils extends DateFnsUtils {
34 | dateFormat = 'yyyy-MM-dd';
35 |
36 | yearFormat = 'yyyy';
37 |
38 | yearMonthFormat = 'yyyy-MM';
39 |
40 | dateTime12hFormat= 'yyyy-MM-dd HH:mm:ss';
41 |
42 | dateTime24hFormat= 'yyyy-MM-dd HH:mm:ss';
43 | }
44 |
45 |
46 | const App: React.FC = ({router}) => {
47 | React.useState(() => {
48 | router.actions.updateLocation(history.location);
49 | history.listen(router.actions.updateLocation);
50 | });
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 | {routes.map((route, index) => (
59 |
60 | ))}
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default injector(App);
71 |
--------------------------------------------------------------------------------
/src/App/store.ts:
--------------------------------------------------------------------------------
1 | // import theme from '@/service/theme';
2 | import zhLang from '@/constants/common/lang/zh';
3 | import enLang from '@/constants/common/lang/en';
4 | import { ThunkParams } from 'natur/dist/middlewares';
5 |
6 | const state = {
7 | name: 'app',
8 | lang: 'zh' as 'zh'|'en',
9 | isMenuOpen: true,
10 | menuData: [
11 | {
12 | icon: 'home',
13 | title: '首页',
14 | to: '/',
15 | },
16 | {
17 | icon: 'menu_book',
18 | title: 'PAGES',
19 | children: [
20 | {
21 | icon: 'airline_seat_flat_angled',
22 | title: 'page2',
23 | to: '/page2',
24 | },
25 | {
26 | icon: 'airline_seat_individual_suite',
27 | title: 'page3',
28 | to: '/page3',
29 | },
30 | ],
31 | },
32 | {
33 | icon: 'supervisor_account',
34 | title: '用户管理',
35 | children: [
36 | {
37 | icon: 'person',
38 | title: '用户',
39 | to: '/user/list',
40 | },
41 | ],
42 | },
43 | ],
44 | };
45 |
46 | const actions = {
47 | update: (name: string) => ({name}),
48 | setLang: (lang: 'zh' | 'en') => ({lang}),
49 | openMenu: () => ({isMenuOpen: true}),
50 | closeMenu: () => ({isMenuOpen: false}),
51 | toggleMenu: () => ({getState}: ThunkParams) => ({isMenuOpen: !getState().isMenuOpen}),
52 | };
53 |
54 | const maps = {
55 | getLangData: ['lang', (lang: 'zh' | 'en') => {
56 | if (lang === 'zh') {
57 | return zhLang;
58 | }
59 | return enLang;
60 | }],
61 | };
62 |
63 |
64 |
65 | export default {
66 | state,
67 | maps,
68 | actions,
69 | };
70 |
71 |
--------------------------------------------------------------------------------
/src/assets/0326_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/src/assets/0326_1.jpg
--------------------------------------------------------------------------------
/src/assets/plant-01.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/sidebar-backup-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/src/assets/sidebar-backup-2.jpg
--------------------------------------------------------------------------------
/src/assets/sidebar-backup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/src/assets/sidebar-backup.jpg
--------------------------------------------------------------------------------
/src/assets/sidebar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/src/assets/sidebar.jpg
--------------------------------------------------------------------------------
/src/components/base/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import Button from '@material-ui/core/Button';
2 | import AuthFilterHOC from '@biz/AuthFilterHOC';
3 |
4 |
5 | export default AuthFilterHOC(Button);
6 |
--------------------------------------------------------------------------------
/src/components/base/Checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from 'formik-material-ui';
2 | import ErrorMsgBoxHOC from '../ErrorMsgBoxHOC';
3 |
4 | export default ErrorMsgBoxHOC(Checkbox);
5 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Form} from 'antd';
3 | import './style.scss';
4 |
5 | import BaseForm from './lib/BaseForm';
6 | import {addInputType} from './lib/config';
7 | import {inputTypeMaps} from './lib/inputMap';
8 | import InputFactory, {createInputFactory} from './lib/InputFactory';
9 | import * as validators from './lib/validators';
10 |
11 | /**
12 | * 添加用户自定义的组件
13 | * 自定义组件添加需要实现prop.onChange的调用以及prop.value的填充
14 | * 添加完成使用GetInput.is({type, prop, options})方式生成配置
15 | * @param type 'table'
16 | * @param component MyTable
17 | */
18 | const extend = (type, component) => {
19 | if (!!inputTypeMaps[type]) {
20 | throw new Error(`${type} 已经存在!`);
21 | }
22 | addInputType(type);
23 | inputTypeMaps[type] = component;
24 | };
25 |
26 | // 受控组件,formFields的值更改时要用原内存地址,否则表单验证会有bug
27 | const DynamicForm = Form.create({
28 | onFieldsChange(props, changedFields) {
29 | for (let key in changedFields) { // eslint-disable-line
30 | props.onChange({
31 | name: key,
32 | value: changedFields[key].value || '',
33 | });
34 | }
35 | },
36 | })(BaseForm);
37 |
38 | export {
39 | extend,
40 | InputFactory,
41 | validators,
42 | createInputFactory,
43 | };
44 |
45 | export default DynamicForm;
46 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/BaseForm.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {PropTypes} from 'prop-types';
3 | import classnames from 'classnames';
4 | import {Form, Row, Col} from 'antd';
5 | import InputFactory from './InputFactory';
6 | import MyFormItem from './Item';
7 | import {createFormItem} from './utils';
8 | import {
9 | defaultFormItemLayout,
10 | // defaultButtonItemLayout,
11 | // tailFormItemLayout,
12 | LAYOUT,
13 | } from './config';
14 |
15 | // const FormItem = Form.Item;
16 |
17 | class BaseForm extends Component {
18 | static LAYOUT = LAYOUT;
19 |
20 | componentDidUpdate() {
21 | this.setInitialValue(this.props.formFields);
22 | }
23 |
24 | onSubmit = e => {
25 | const {onSubmit, form} = this.props;
26 | e.preventDefault();
27 | form.validateFieldsAndScroll(onSubmit);
28 | };
29 |
30 | getFieldsValue = () => this.form.getFieldsValue();
31 |
32 | setInitialValue = formFields => this.initialValue = this.formatFormValues(this.filterNoConfigFields(formFields));
33 | setFields = formFields => this.props.form.setFields(this.formatFormValues(this.filterNoConfigFields(formFields)));
34 | setFieldsValue = formFields => this.props.form.setFieldsValue(this.filterNoConfigFields(formFields));
35 |
36 | getFormItemLayout = () => {
37 | const {layout} = this.props;
38 | const formItemLayout = layout === LAYOUT.HORIZONTAL ? defaultFormItemLayout : null;
39 | // const buttonItemLayout = layout === LAYOUT.HORIZONTAL ? defaultButtonItemLayout : null;
40 | return formItemLayout;
41 | };
42 | getChildren = () => {
43 | return this.props.children;
44 | };
45 | filterNoConfigFields = formFields => {
46 | const { fieldsConfig } = this.props;
47 | const newFormFields = Object.keys(fieldsConfig)
48 | .filter(key => formFields[key] !== undefined)
49 | .reduce((obj, key) => {
50 | obj[key] = formFields[key];
51 | return obj;
52 | }, {});
53 | return newFormFields;
54 | }
55 |
56 | createFormItemByConfig = formItemConfig => {
57 | const itemLayout = this.getFormItemLayout();
58 | const { form, formItemLayout } = this.props;
59 | const _formItemLayout = { ...formItemLayout };
60 | const layoutProps = ['span', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
61 | layoutProps.forEach(layoutProp => {
62 | if (!!formItemConfig.props[layoutProp]) {
63 | _formItemLayout[layoutProp] = formItemConfig.props[layoutProp];
64 | }
65 | });
66 | const { getFieldDecorator } = form;
67 | if (!!_formItemLayout && Object.keys(_formItemLayout).length) {
68 | return (
69 |
70 |
75 |
76 | );
77 | }
78 | return (
79 |
85 | );
86 | };
87 |
88 | validateFieldsAndScroll = cb => this.props.form.validateFieldsAndScroll(cb);
89 |
90 | /**
91 | * 数据格式转换
92 | * { name: 'tom' } => { name: { value: 'tom; } }
93 | * @param values
94 | * @returns {{}}
95 | */
96 | formatFormValues = values => {
97 | const res = {};
98 |
99 | for (let key in values) { // eslint-disable-line
100 | res[key] = {value: values[key]};
101 | }
102 | return res;
103 | };
104 |
105 | resetFields = resetValue => {
106 | if (resetValue) {
107 | this.props.form.setFields(this.formatFormValues(this.filterNoConfigFields(resetValue)));
108 | } else {
109 | setTimeout(() => this.props.form.setFields(this.initialValue), 16);
110 | }
111 | };
112 |
113 | /* eslint-disable */
114 | createFormItemsConfig({formFields, fieldsConfig}) {
115 | const formItems = [];
116 | for (let key in formFields) {
117 | if (formFields.hasOwnProperty(key) && fieldsConfig.hasOwnProperty(key)) {
118 | let fieldConfig = fieldsConfig[key];
119 |
120 | if (fieldConfig instanceof InputFactory) {
121 | fieldConfig = fieldConfig.getInput();
122 | }
123 | const params = {
124 | ...fieldConfig,
125 | name: key,
126 | value: formFields[key],
127 | };
128 | formItems.push(createFormItem(params));
129 | }
130 | }
131 | return formItems;
132 | }
133 | /* eslint-enable */
134 |
135 | render() {
136 | const {layout, gutter} = this.props;
137 | const {className, style} = this.props;
138 | const formItems = this.createFormItemsConfig(this.props);
139 | const formProps = {
140 | style,
141 | className,
142 | layout,
143 | onSubmit: this.onSubmit,
144 | };
145 | const formCls = classnames('dynamic-form', className);
146 | return (
147 |
153 | );
154 | }
155 | }
156 |
157 | BaseForm.defaultProps = {
158 | layout: LAYOUT.HORIZONTAL,
159 | onChange: () => {
160 | },
161 | onSubmit: () => {
162 | },
163 | formItemLayout: {},
164 | gutter: 0,
165 | };
166 | BaseForm.propTypes = {
167 | formFields: PropTypes.object.isRequired, // eslint-disable-line
168 | fieldsConfig: PropTypes.object.isRequired, // eslint-disable-line
169 | layout: PropTypes.oneOf([LAYOUT.HORIZONTAL, LAYOUT.INLINE, LAYOUT.VERTICAL]),
170 | onChange: PropTypes.func,
171 | onSubmit: PropTypes.func,
172 | formItemLayout: PropTypes.object, // eslint-disable-line
173 | gutter: PropTypes.number,
174 | };
175 |
176 | export default BaseForm;
177 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/InputFactory.js:
--------------------------------------------------------------------------------
1 | import cloneDeep from 'lodash/cloneDeep';
2 | import {defaultConfig, INPUT_TYPE} from './config';
3 | /**
4 | * 生成dynamicform配置的工具类
5 | */
6 | export default class InputFactory {
7 | input = {};
8 |
9 | constructor(arg = '') {
10 | if (typeof arg === 'string') {
11 | this.input = {
12 | label: arg,
13 | type: defaultConfig.type,
14 | };
15 | } else if (typeof arg === 'object') {
16 | if (arg instanceof InputFactory === true) {
17 | arg = arg.getInput();
18 | }
19 | this.init(arg);
20 | }
21 | }
22 |
23 | init(input) {
24 | this.input = cloneDeep(input);
25 | return this;
26 | }
27 |
28 | fieldOption(config) {
29 | if (!this.input.fieldOption) {
30 | this.input.fieldOption = {};
31 | }
32 | this.input.fieldOption = {
33 | ...this.input.fieldOption,
34 | ...config,
35 | };
36 | return this;
37 | }
38 |
39 | rule(rule) {
40 | if (!Array.isArray(this.input.rules)) {
41 | this.input.rules = [];
42 | }
43 | this.input.rules.push(rule);
44 | return this;
45 | }
46 |
47 | removeRules() {
48 | this.input.rules = [];
49 | return this;
50 | }
51 |
52 | prop(prop) {
53 | if (!this.input.props) {
54 | this.input.props = {};
55 | }
56 | this.input.props = {
57 | ...this.input.props,
58 | ...prop,
59 | };
60 | return this;
61 | }
62 |
63 | itemProp(prop) {
64 | if (!this.input.itemProps) {
65 | this.input.itemProps = {};
66 | }
67 | this.input.itemProps = {
68 | ...this.input.itemProps,
69 | ...prop,
70 | };
71 | return this;
72 | }
73 |
74 | label(label) {
75 | this.input.label = label;
76 | return this;
77 | }
78 |
79 | trigger(ListenerType = 'onChange') {
80 | return this.fieldOption({
81 | validateTrigger: ListenerType,
82 | });
83 | }
84 | span(colNum) {
85 | if (!!colNum) {
86 | return this.itemProp({span: colNum});
87 | }
88 | return this;
89 | }
90 | layout(config) {
91 | if (!!config) {
92 | return this.itemProp(config);
93 | }
94 | return this;
95 | }
96 |
97 | isButton(text, onClick) {
98 | this.input = {
99 | // label,
100 | type: INPUT_TYPE.BUTTON,
101 | props: {onClick},
102 | };
103 | return this;
104 | }
105 |
106 | isDisable() {
107 | return this.prop({
108 | disabled: true,
109 | });
110 | }
111 |
112 | isEnable() {
113 | return this.prop({
114 | disabled: false,
115 | });
116 | }
117 |
118 | isRequired(message = `请输入${this.input.label}!`) {
119 | return this.rule({
120 | required: true,
121 | message,
122 | });
123 | }
124 | isNotRequired() {
125 | if (Array.isArray(this.input.rules)) {
126 | const target = this.input.rules.find(rule => rule.required === true);
127 | if (!!target) {
128 | target.required = false;
129 | }
130 | }
131 | return this;
132 | // return this.rule({
133 | // required: false,
134 | // });
135 | }
136 |
137 | ph(placeholder = `请输入${this.input.label}`) {
138 | return this.prop({placeholder});
139 | }
140 |
141 | /**
142 | * 为表单元素添加验证方法
143 | * 可以是已经存在的验证type,或者自定义的验证方法
144 | * @param validator function
145 | * @returns {InputFactory}
146 | */
147 | validator(validator) {
148 | if (!validator) {
149 | throw new Error('没有validator参数');
150 | }
151 | if (typeof validator !== 'function') {
152 | throw new Error('validator参数应该是一个函数!');
153 | }
154 | return this.rule({validator});
155 | }
156 |
157 | on(ListenerName, listener) {
158 | const lowerCaseReg = /^[a-z]*$/;
159 | if (lowerCaseReg.test(ListenerName.charAt(0))) {
160 | throw new Error(`事件名称:${ListenerName}的首字母应该是大写!`);
161 | }
162 | return this.prop({
163 | [`on${ListenerName}`]: listener,
164 | });
165 | }
166 |
167 | isPassword() {
168 | return this.prop({
169 | type: 'password',
170 | });
171 | }
172 |
173 | isRadio(options) {
174 | if (!options) throw new Error('没有传options');
175 | this.input.type = INPUT_TYPE.RADIO;
176 | this.input.options = options;
177 | return this;
178 | }
179 |
180 | isCheckbox(options) {
181 | if (!options) throw new Error('没有传options');
182 | this.input.type = INPUT_TYPE.CHECKBOX;
183 | this.input.options = options;
184 | return this;
185 | }
186 |
187 | // select框带搜索功能时,的过滤选项方法
188 | filterOption(input, option) { // eslint-disable-line
189 | const reg = new RegExp(input, 'i');
190 | return reg.test(option.props.children);
191 | }
192 |
193 | isSelect(options, config = {showSearch: false, multiple: false}) {
194 | const {showSearch, multiple} = config;
195 | if (!options) throw new Error('没有传options');
196 | this.input.type = INPUT_TYPE.SELECT;
197 | this.input.options = options;
198 | if (showSearch) {
199 | this.prop({
200 | filterOption: this.filterOption,
201 | showSearch,
202 | });
203 | }
204 | if (multiple) {
205 | this.prop({
206 | mode: 'multiple',
207 | });
208 | }
209 | return this;
210 | }
211 |
212 | isDatePicker(format = 'YYYY-MM-DD') {
213 | this.input.type = INPUT_TYPE.DATEPICKER;
214 | return this.prop({
215 | format,
216 | });
217 | }
218 |
219 | isDateRangePicker(format = 'YYYY-MM-DD') {
220 | this.input.type = INPUT_TYPE.RANGEPICKER;
221 | return this.prop({
222 | format,
223 | });
224 | }
225 |
226 | isTextArea() {
227 | this.input.type = INPUT_TYPE.TEXTAREA;
228 | return this;
229 | }
230 |
231 | isText() {
232 | this.input.type = INPUT_TYPE.TEXT;
233 | return this;
234 | }
235 |
236 | is({type, prop, options}) {
237 | const _type = type.toUpperCase();
238 | if (!INPUT_TYPE[_type]) {
239 | throw new Error(`${type} 类型的组件不存在!`);
240 | }
241 |
242 | this.input.type = INPUT_TYPE[_type];
243 | /* eslint-disable */
244 | !!options && (this.input.options = options);
245 | !!prop && this.prop(prop);
246 | /* eslint-enable */
247 | return this;
248 | }
249 | clone() {
250 | return new InputFactory(this);
251 | }
252 |
253 | getInput() {
254 | return {...this.input};
255 | }
256 | }
257 |
258 | export const createInputFactory = label => new InputFactory(label);
259 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/Item.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {PropTypes} from 'prop-types';
3 |
4 | import {
5 | Radio,
6 | Checkbox,
7 | } from 'antd';
8 |
9 | import {
10 | inputTypeMaps,
11 | FormItem,
12 | Option,
13 | } from './inputMap';
14 |
15 |
16 | import {
17 | INPUT_TYPE,
18 | defaultConfig,
19 | } from './config';
20 |
21 | const optionsComponents = {
22 | [INPUT_TYPE.SELECT]: Option,
23 | [INPUT_TYPE.RADIO]: Radio,
24 | [INPUT_TYPE.CHECKBOX]: Checkbox,
25 | };
26 |
27 | class MyFormItem extends Component {
28 | constructor(props) {
29 | super(props);
30 | this.initValue(props.config.input.value);
31 | }
32 | getFormItemComponent = ({input, rules, fieldOption}) => {
33 | const {
34 | type = defaultConfig.type,
35 | name,
36 | props,
37 | value,
38 | options,
39 | children,
40 | } = input;
41 |
42 | const {getFieldDecorator} = this.props;
43 | const InputComponent = inputTypeMaps[type];
44 | const FormFieldOptions = {
45 | rules: rules || defaultConfig.rules,
46 | ...fieldOption,
47 | initialValue: value,
48 | };
49 | const _props = { ...props };
50 | if (type === 'Text' && !!options) {
51 | let selectedOption = {};
52 | if (Array.isArray(value)) {
53 | selectedOption.text = options.filter(item => value.includes(item.value)).map(({text}) => text).join(',');
54 | } else {
55 | selectedOption = options.find(item => item.value === value);
56 | }
57 | _props.textValue = selectedOption ? selectedOption.text : value;
58 | }
59 |
60 | const optionsChildren = this.createOptions(type, options);
61 | let wrappedInput = ;
62 | if (optionsChildren || children) {
63 | wrappedInput = {optionsChildren || children};
64 | }
65 | return getFieldDecorator(name, FormFieldOptions)(wrappedInput);
66 | };
67 |
68 | createOptions = (type, options) => {
69 | if (!type || !options) return null;
70 |
71 | const Comp = optionsComponents[type] || Option;
72 | const optionsChildren = options.map((item, index) => {
73 | const {text, ...optionProps} = item;
74 | return {text};
75 | });
76 | return optionsChildren;
77 | };
78 | initValue(value) {
79 | this.initialValue = value;
80 | }
81 | render() {
82 | const {layout, config} = this.props;
83 | const itemProps = config.props;
84 | return (
85 |
86 | {this.getFormItemComponent(config)}
87 |
88 | );
89 | }
90 | }
91 |
92 | MyFormItem.prototypes = {
93 | layout: PropTypes.object.isRequired,
94 | config: PropTypes.object.isRequired,
95 | getFieldDecorator: PropTypes.func.isRequired,
96 | };
97 |
98 | export default MyFormItem;
99 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/Text.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import classnames from 'classnames';
3 | import Moment from 'moment';
4 | import dynamicFormStyle from '../style.scss';
5 |
6 | const isNull = value => {
7 | if (value === null || value === undefined || value === '' || value.length === 0) {
8 | return true;
9 | }
10 | return false;
11 | };
12 |
13 | class Text extends Component {
14 | render() {
15 | const {value, textValue, format, style, className, ...restProps} = this.props; // eslint-disable-line
16 |
17 | let showText = textValue || value;
18 |
19 | if (isNull(value)) {
20 | showText = '无';
21 | }
22 | if (Array.isArray(value) && value.length) {
23 | showText = textValue || value.join(',');
24 | }
25 | if (Moment.isMoment(value)) {
26 | showText = value.format(format);
27 | }
28 | const cls = classnames(className, dynamicFormStyle.text);
29 | return {showText}
;
30 | }
31 | }
32 |
33 | export default Text;
34 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/config.js:
--------------------------------------------------------------------------------
1 | // 普通form item布局
2 | export const defaultFormItemLayout = {
3 | labelCol: {
4 | xs: {span: 24},
5 | sm: {span: 5},
6 | },
7 | wrapperCol: {
8 | xs: {span: 24},
9 | sm: {span: 12},
10 | },
11 | };
12 |
13 | // 按钮布局
14 | export const defaultButtonItemLayout = {
15 | wrapperCol: {
16 | xs: {span: 24},
17 | sm: {span: 12},
18 | },
19 | };
20 |
21 | // 最后一个item的布局
22 | export const tailFormItemLayout = {
23 | wrapperCol: {
24 | xs: {
25 | span: 24,
26 | offset: 0,
27 | },
28 | sm: {
29 | span: 16,
30 | offset: 8,
31 | },
32 | },
33 | };
34 |
35 | export const INPUT_TYPE = {
36 | RATE: 'rate',
37 | BUTTON: 'button',
38 | SWITCH: 'switch',
39 | SELECT: 'select',
40 | SLIDER: 'slider',
41 | INPUT: 'input',
42 | CHECKBOX: 'checkbox',
43 | RADIO: 'radio',
44 | NUMBER: 'number',
45 | DATEPICKER: 'datePicker',
46 | MONTHPICKER: 'monthPicker',
47 | RANGEPICKER: 'rangePicker',
48 | WEEKPICKER: 'weekPicker',
49 | TEXTAREA: 'textArea',
50 | TEXT: 'Text',
51 | };
52 |
53 | export const addInputType = type => {
54 | const upperCaseType = type.toUpperCase();
55 | if (!!INPUT_TYPE[upperCaseType]) {
56 | throw new Error(`${type} 已经存在!`);
57 | }
58 | INPUT_TYPE[upperCaseType] = type;
59 | };
60 |
61 | export const LAYOUT = {
62 | HORIZONTAL: 'horizontal',
63 | VERTICAL: 'vertical',
64 | INLINE: 'inline',
65 | };
66 |
67 | export const defaultConfig = {
68 | type: 'input',
69 | rules: [{
70 | required: false,
71 | }],
72 | props: {
73 | type: 'text',
74 | },
75 | };
76 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/inputMap.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | Form,
5 | Select,
6 | InputNumber,
7 | Switch,
8 | Radio,
9 | Slider,
10 | Button,
11 | Rate,
12 | Input,
13 | Checkbox,
14 | DatePicker,
15 | } from 'antd';
16 |
17 | import Text from './Text';
18 |
19 | const {MonthPicker, RangePicker, WeekPicker} = DatePicker;
20 |
21 | // export Radio;
22 | // export Checkbox;
23 | export const FormItem = Form.Item;
24 | export const {Option} = Select;
25 | export const RadioButton = Radio.Button;
26 | export const RadioGroup = Radio.Group;
27 | export const CheckboxGroup = Checkbox.Group;
28 | export const {TextArea} = Input;
29 | export const inputTypeMaps = {
30 | rate: Rate,
31 | button: Button,
32 | switch: Switch,
33 | select: Select,
34 | slider: Slider,
35 | input: Input,
36 | checkbox: CheckboxGroup,
37 | radio: RadioGroup,
38 | number: InputNumber,
39 | datePicker: DatePicker,
40 | monthPicker: MonthPicker,
41 | rangePicker: RangePicker,
42 | weekPicker: WeekPicker,
43 | textArea: TextArea,
44 | Text,
45 | };
46 |
47 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/utils.js:
--------------------------------------------------------------------------------
1 | export const getUniqID = () => Math.random().toString(36).substr(2, 6);
2 |
3 | export const createFormItem = config => {
4 | const {
5 | label,
6 | itemProps,
7 | options,
8 | type,
9 | name,
10 | value,
11 | rules,
12 | children,
13 | props,
14 | fieldOption,
15 | } = config;
16 | const formItem = {
17 | props: {
18 | key: name || getUniqID(),
19 | },
20 | input: {
21 | type,
22 | },
23 | };
24 | formItem.input.value = value;
25 | formItem.props.label = label;
26 | formItem.input.name = name;
27 | formItem.input.children = children;
28 | formItem.input.props = props;
29 | formItem.input.options = options;
30 | formItem.rules = rules;
31 | formItem.fieldOption = fieldOption;
32 | formItem.props = {
33 | ...formItem.props,
34 | ...itemProps,
35 | };
36 | return formItem;
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/lib/validators.js:
--------------------------------------------------------------------------------
1 | export const phoneValidator = (rule, value, callback) => {
2 | const numReg = /^[\d-]*$/; // 电话限定字符串正则
3 | const phoneReg = /^1\d{10}$/; // 手机正则
4 | const telReg = /^\d{3,4}-?\d{3,8}$/; // 固定电话正则
5 |
6 | if (!value) {
7 | return callback();
8 | }
9 | if (!numReg.test(value)) {
10 | callback('电话必须是数字或者"-"!');
11 | } else if (!phoneReg.test(value) && !telReg.test(value)) {
12 | callback('电话格式错误!');
13 | }
14 | // Note: 必须总是返回一个 callback,否则 validateFieldsAndScroll 无法响应
15 | return callback();
16 | };
17 |
18 | export const numValidator = (rule, value, callback) => {
19 | if (!value) {
20 | return callback();
21 | }
22 |
23 | if (!/^\d*$/.test(value)) {
24 | callback('必须是数字!');
25 | }
26 | // Note: 必须总是返回一个 callback,否则 validateFieldsAndScroll 无法响应
27 | return callback();
28 | };
29 |
30 | export const moneyValidator = (rule, value, callback) => {
31 | if (!value) {
32 | return callback();
33 | }
34 |
35 | if (!/^-?\d+(\.\d*)?$/.test(value)) {
36 | callback('请输入正确的金额格式!');
37 | }
38 | // Note: 必须总是返回一个 callback,否则 validateFieldsAndScroll 无法响应
39 | return callback();
40 | };
41 |
42 |
--------------------------------------------------------------------------------
/src/components/base/DynamicForm/style.scss:
--------------------------------------------------------------------------------
1 | @import '~@site/theme/style';
2 |
3 | .text {
4 | display: inline-block;
5 | overflow: hidden;
6 | text-align: left;
7 | text-overflow: ellipsis;
8 | white-space: nowrap;
9 | user-select: all;
10 | color: #333;
11 | }
12 |
13 | :global {
14 | $borderColor: $defaultMainColor;
15 | $hoverColor: lighten($defaultMainColor, 40);
16 | $shadow: 0 0 0 2px lighten($defaultMainColor, 40);
17 | .dynamic-form {
18 | .small {
19 | width: 200px;
20 | height: 24px;
21 | }
22 | // .ant-form-explain {
23 | // display: table-cell;
24 | // }
25 | }
26 | @mixin inputActiveBorder($color) {
27 | .ant-input:not([disabled]):hover,
28 | .ant-input:focus {
29 | border-color: $color !important;
30 | box-shadow: 0 0 0 2px lighten($color, 40) !important;
31 | }
32 | }
33 | @include inputActiveBorder($defaultMainColor);
34 | .has-error {
35 | @include inputActiveBorder(#f5222d);
36 | }
37 | .ant-form-item-label > label,
38 | .ant-form-item, {
39 | color: #696969;
40 | }
41 | .ant-input-lg,
42 | .ant-select-lg {
43 | font-size: 14px!important;
44 | color: #696969!important;
45 | }
46 | .ant-form-item-label label:after {
47 | position: relative;
48 | top: -0.5px;
49 | margin: 0 8px 0 2px;
50 | content: '';
51 | }
52 | .ant-form-explain {
53 | text-align: left;
54 | }
55 | .is-validating.has-feedback .ant-form-item-children-icon {
56 | color: $defaultMainColor;
57 | }
58 |
59 | // 日期input 样式覆盖
60 | .ant-calendar-today .ant-calendar-date {
61 | border-color: $borderColor !important;
62 | color: $defaultMainColor !important;
63 | }
64 | .ant-calendar-selected-date .ant-calendar-date {
65 | background: $defaultMainColor !important;
66 | }
67 | .ant-calendar-date:hover,
68 | .ant-calendar-selected-day .ant-calendar-date {
69 | background: lighten($defaultMainColor, 40) !important;
70 | }
71 | .ant-calendar-header a:hover {
72 | color: $defaultMainColor !important;
73 | }
74 | .ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled) {
75 | border-color: $borderColor !important;
76 | }
77 | .ant-calendar-tbody .ant-calendar-active-week .ant-calendar-today .ant-calendar-date {
78 | padding: 0;
79 | margin-left: 6px;
80 | }
81 | .ant-calendar-tbody .ant-calendar-active-week .ant-calendar-today .ant-calendar-date:before {
82 | border: none;
83 | }
84 | // select input 样式覆盖
85 | .ant-select-open {
86 | .ant-select-selection {
87 | border-color: $borderColor !important;
88 | box-shadow: $shadow !important;
89 | }
90 | }
91 | .ant-select-focused {
92 | .ant-select-selection,
93 | .ant-select-selection:focus,
94 | .ant-select-selection:active {
95 | border-color: $borderColor !important;
96 | box-shadow: $shadow !important;
97 | }
98 | }
99 | .ant-select-selection:focus,
100 | .ant-select-selection:active {
101 | border-color: $borderColor !important;
102 | box-shadow: $shadow !important;
103 | }
104 | .ant-select-selection:hover {
105 | border-color: $borderColor !important;
106 | }
107 |
108 | .ant-select-dropdown-menu-item:hover,
109 | .ant-select-dropdown-menu-item-active {
110 | background-color: $hoverColor !important;
111 | }
112 | .ant-select-dropdown-menu-item-selected {
113 | background-color: lighten($defaultMainColor, 50) !important;
114 | }
115 |
116 | // checkbox样式覆盖
117 | .ant-checkbox-wrapper:hover .ant-checkbox-inner,
118 | .ant-checkbox-input:focus + .ant-checkbox-inner,
119 | .ant-checkbox:hover .ant-checkbox-inner,
120 | .ant-checkbox-checked .ant-checkbox-inner,
121 | .ant-checkbox-checked:after {
122 | border-color: $borderColor !important;
123 | }
124 | .ant-checkbox-checked .ant-checkbox-inner {
125 | background-color: $defaultMainColor !important;
126 | }
127 |
128 | // radio样式覆盖
129 | .ant-radio-wrapper:hover .ant-radio-inner,
130 | .ant-radio-input:focus + .ant-radio-inner,
131 | .ant-radio:hover .ant-radio-inner,
132 | .ant-radio-checked .ant-radio-inner,
133 | .ant-radio-checked:after {
134 | border-color: $borderColor !important;
135 | }
136 | .ant-radio-checked .ant-radio-inner::after,
137 | .ant-radio-inner:after {
138 | background-color: $defaultMainColor !important;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/base/ErrorMsgBoxHOC/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ErrorMessage } from 'formik';
3 | import { Box } from '@material-ui/core';
4 |
5 | type AnyFun = (...arg: any) => any;
6 |
7 | const ErrorMsgBoxHOC = (Comp: C) => {
8 | const WithErrorComponent: React.FC[0]> = props => {
9 | const {field} = props;
10 | const {name} = field;
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 | >
18 | );
19 | };
20 | return WithErrorComponent;
21 | };
22 | export default ErrorMsgBoxHOC;
23 |
--------------------------------------------------------------------------------
/src/components/base/IconButton/index.tsx:
--------------------------------------------------------------------------------
1 | import IconButton from '@material-ui/core/IconButton';
2 | import AuthFilterHOC from '@biz/AuthFilterHOC';
3 |
4 |
5 | export default AuthFilterHOC(IconButton);
6 |
--------------------------------------------------------------------------------
/src/components/base/Input/index.tsx:
--------------------------------------------------------------------------------
1 | import Input from '@material-ui/core/TextField';
2 |
3 | export default Input;
4 |
--------------------------------------------------------------------------------
/src/components/base/Loading/index.tsx:
--------------------------------------------------------------------------------
1 | import { inject } from '@/store';
2 | import { Box, CircularProgress, Dialog, Zoom } from '@material-ui/core';
3 | import React from 'react';
4 |
5 | import styles from './style.scss';
6 |
7 | const paperProps = {
8 | elevation: 10,
9 | };
10 |
11 | const injector = inject(['loading', {
12 | state: ['showLoading', 'loadingText'],
13 | }]);
14 |
15 |
16 | const Loading = ({loading}: typeof injector.type) => (
17 |
44 | );
45 | export default injector(Loading);
46 |
--------------------------------------------------------------------------------
/src/components/base/Loading/style.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | :global {
3 | .MuiBackdrop-root {
4 | background-color: rgba(235, 110, 110, 0);
5 | }
6 | .MuiCircularProgress-colorPrimary {
7 | color: white;
8 | }
9 | }
10 | z-index: 9999999!important;
11 | }
12 | .scrollPaper, .paperScrollPaper {
13 | border-radius: 5px;
14 | background-color: transparent;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/base/Skeleton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Skeleton from '@material-ui/lab/Skeleton';
3 | import { Box } from '@material-ui/core';
4 |
5 | export default () => (
6 |
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/components/base/Table/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MUIDataTable, { MUIDataTableProps } from 'mui-datatables';
3 | import TableFooter from '@material-ui/core/TableFooter';
4 | import TableRow from '@material-ui/core/TableRow';
5 | import TableCell from '@material-ui/core/TableCell';
6 | import TablePagination from '@material-ui/core/TablePagination';
7 |
8 |
9 | type TableProps<
10 | D extends {},
11 | RSI extends number | string,
12 | IDName extends keyof D = keyof D,
13 | > = Omit & {
14 | /**
15 | * 分页数据
16 | */
17 | pagination: {
18 | /** 总数据量 */
19 | total: number;
20 | /** 当前页数,从1开始算 */
21 | page: number;
22 | /** 每页的数据有多少行 */
23 | rowsPerPage: number;
24 | },
25 | /**
26 | * 当分页页数改变时
27 | */
28 | onPageChange?: (page: number) => void;
29 | /**
30 | * 当,当前的每页数据有多少行的配置更新时
31 | */
32 | onRowsPerPageChange?: (page: number) => void;
33 | /**
34 | * 当表格选中项更新时
35 | */
36 | onRowSelectionChange?: (newRowsSelected: RSI[]) => void;
37 | /**
38 | * 表格标题
39 | */
40 | title?: string;
41 | /**
42 | * 被选中的项目id数组,可以是index索引,
43 | * 也可以是数据的id,如果是id数据的话,必须要声明idName的值,
44 | * 也就是每行数据的id数据对应的名字
45 | * */
46 | rowsSelected?: RSI[];
47 | /**
48 | * 数据的id对应的名字
49 | */
50 | idName?: IDName;
51 | data: D[];
52 | };
53 |
54 |
55 | const Table = <
56 | D extends {},
57 | RSI extends number | string,
58 | >({
59 | options,
60 | data,
61 | pagination,
62 | onPageChange,
63 | onRowsPerPageChange,
64 | onRowSelectionChange,
65 | idName,
66 | rowsSelected = [],
67 | title = '',
68 | ...restProps
69 | }: TableProps) => {
70 | const _rowsSelected = React.useMemo(() => {
71 | if (idName) {
72 | return data.reduce((idxs, d, currentIndex) => {
73 | if (rowsSelected.indexOf(d[idName] as any) > -1) {
74 | idxs.push(currentIndex);
75 | }
76 | return idxs;
77 | }, [] as number[]);
78 | }
79 | return rowsSelected;
80 | }, [data, idName, rowsSelected]);
81 | const _options = React.useMemo(() => ({
82 | filterType: 'checkbox' as 'checkbox',
83 | jumpToPage: true,
84 | selectToolbarPlacement: 'none',
85 | tableId: 'name',
86 | print: false,
87 | searchable: false,
88 | search: false,
89 | serverSide: true,
90 | download: false,
91 | filter: false,
92 | rowsSelected: _rowsSelected,
93 | rowsPerPageOptions: options?.rowsPerPageOptions || [5, 10, 15, 20, 25],
94 | textLabels: {
95 | selectedRows: {
96 | text: '行已选择',
97 | delete: '删除',
98 | deleteAria: '删除所选数据',
99 | },
100 | toolbar: {
101 | viewColumns: '需要显示的列',
102 | },
103 | viewColumns: {
104 | title: '显示列',
105 | titleAria: '显示/隐藏 表格列',
106 | },
107 | pagination: {
108 | next: '下一页',
109 | previous: '上一页',
110 | rowsPerPage: '每页行数:',
111 | displayRows: '共',
112 | jumpToPage: '跳转到',
113 | },
114 | },
115 | customFooter: (
116 | count: number,
117 | page: number,
118 | rowsPerPage: number,
119 | changeRowsPerPage: any,
120 | changePage: any,
121 | ) => (
122 |
123 |
124 |
125 | `${from}-${to} 共 ${_count !== -1 ? _count : `more than ${to}`} 条数据`}
132 | onChangePage={(event, np: number) => changePage(np)}
133 | onChangeRowsPerPage={e => changeRowsPerPage(e.target.value)}
134 | />
135 |
136 |
137 |
138 |
139 | ),
140 | onChangePage: (np: number) => onPageChange && onPageChange(np + 1),
141 | onChangeRowsPerPage: (size: number) => onRowsPerPageChange && onRowsPerPageChange(size),
142 | onRowSelectionChange: (currentRowsSelected: any, allRowsSelected: any, selectedIndexs: any) => {
143 | if (onRowSelectionChange) {
144 | if (idName) {
145 | // @ts-ignore
146 | onRowSelectionChange(selectedIndexs.map((si: number) => data[si][idName]));
147 | } else {
148 | onRowSelectionChange(selectedIndexs);
149 | }
150 | }
151 | },
152 | ...options,
153 | }), [
154 | _rowsSelected,
155 | options,
156 | pagination.total,
157 | pagination.rowsPerPage,
158 | pagination.page,
159 | onPageChange,
160 | onRowsPerPageChange,
161 | onRowSelectionChange,
162 | idName,
163 | data,
164 | ]);
165 |
166 |
167 | return (
168 |
174 | );
175 | };
176 | export default Table;
177 |
--------------------------------------------------------------------------------
/src/components/base/Toast/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Snackbar, { SnackbarOrigin } from '@material-ui/core/Snackbar';
3 | import { Alert } from '@material-ui/lab';
4 | import { inject } from '@/store';
5 | import { Box, Slide } from '@material-ui/core';
6 |
7 |
8 | const injector = inject('toast');
9 |
10 | const mt50 = {
11 | marginTop: 50,
12 | };
13 | const alertStyle = {
14 | marginTop: 20,
15 | };
16 |
17 | const position: SnackbarOrigin = {
18 | vertical: 'top',
19 | horizontal: 'center',
20 | };
21 |
22 | function Toast({toast}: typeof injector.type) {
23 | return (
24 |
29 |
32 | {
33 | toast.state.map(ti => (
34 |
35 | {ti.text}
36 |
37 | ))
38 | }
39 |
40 |
41 | );
42 | }
43 |
44 | export default injector(Toast);
45 |
--------------------------------------------------------------------------------
/src/components/business/AuthFilterHOC.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { AuthType } from '@/constants/common/Auth';
3 | import copyStatic from 'hoist-non-react-statics';
4 | import {inject, StoreType} from '@/store';
5 |
6 | const authCheckType = (props: any): [string, AuthType][] => {
7 | const res:[string, AuthType][] = [];
8 | if (props.authLevel !== undefined) {
9 | res.push([props.authLevel, 'level']);
10 | }
11 | if (props.authRole !== undefined) {
12 | res.push([props.authRole, 'role']);
13 | }
14 | if (props.auth !== undefined) {
15 | res.push([props.auth, 'auth']);
16 | }
17 | if (res.length === 0) {
18 | res.push([props.auth, 'auth']);
19 | }
20 | return res;
21 | };
22 |
23 |
24 | type AuthProps = {
25 | auth?: string;
26 | authLevel?: string;
27 | authRole?: string;
28 | };
29 |
30 | type Ref = {
31 | forwardRef?: any,
32 | }
33 |
34 | type UserStoreModule = Pick;
35 |
36 | type AuthFilterProps = T & AuthProps & Ref & UserStoreModule;
37 |
38 | /**
39 | * 创建权限过滤器高阶组件
40 | * 控制需要权限验证模块的权限验证,
41 | * 如果用户有权限就显示该模块,
42 | * 没有就不显示。
43 | * @param {React.ComponentClass | React.FC} WrappedComponent 需要控制权限的组件
44 | * @returns {Component} 封装后的组件,可以通过权限属性控制组件显隐
45 | *
46 | */
47 | function AuthFilterHOC = AuthFilterProps>(WrappedComponent: React.ComponentClass | React.FC) {
48 | class AuthFilter extends Component {
49 | render() {
50 | const { auth, authLevel, authRole, forwardRef, user, ...props } = this.props;
51 | const checkTypes = authCheckType({auth, authLevel, authRole});
52 | const authIsValid = checkTypes.some(ct => user.maps.hasAuth(...ct));
53 | if (forwardRef) {
54 | (props as any).ref = forwardRef;
55 | }
56 | if (authIsValid) {
57 | return ;
58 | }
59 | return null;
60 | }
61 | }
62 | let RefAuthFilter = AuthFilter;
63 | if (typeof (WrappedComponent as any).render === 'function') {
64 | RefAuthFilter = React.forwardRef((props, ref) => {
65 | const newProps: any = {
66 | ...props,
67 | forwardRef: ref,
68 | };
69 | return ;
70 | }) as any;
71 | }
72 | RefAuthFilter = copyStatic(RefAuthFilter, WrappedComponent);
73 | return inject(['user', {maps: ['hasAuth']}])(RefAuthFilter as React.ComponentClass);
74 | }
75 |
76 | export default AuthFilterHOC;
77 |
--------------------------------------------------------------------------------
/src/components/business/Bar/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { AppBar, Toolbar, Typography } from '@material-ui/core';
4 | import IconButton from '@base/IconButton';
5 | import Button from '@base/Button';
6 | import Menu from '@material-ui/icons/Menu';
7 | import { inject } from '@/store';
8 |
9 |
10 | const InjectApp = inject(['app', {}]);
11 |
12 | const Bar:React.FC = ({app}) => (
13 |
14 |
15 |
16 |
17 |
18 | News
19 |
20 |
21 |
22 | );
23 |
24 | export default InjectApp(Bar);
25 |
--------------------------------------------------------------------------------
/src/components/business/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid } from '@material-ui/core';
3 | import { Switch, Route } from 'react-router';
4 | import AppMenu from '@biz/Menu';
5 | import Bar from '@biz/Bar';
6 | import { AppRoute } from '@/routes';
7 | import styles from './style.scss';
8 | import AuthRoute from '@/routes/AuthRoute';
9 |
10 | const f1 = {
11 | flex: 1,
12 | };
13 |
14 | const Layout: React.FC<{routes: AppRoute[], indexRoute: AppRoute['component']}> = ({routes, indexRoute}) => (
15 |
16 |
17 |
18 |
19 |
20 | {routes.map((route, index) => (
21 |
22 | ))}
23 | { indexRoute && }
24 |
25 |
26 |
27 | );
28 | export default Layout;
29 |
--------------------------------------------------------------------------------
/src/components/business/Layout/style.scss:
--------------------------------------------------------------------------------
1 | .right {
2 | & > div {
3 | min-height: calc(100vh - 64px);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/business/Menu/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Drawer,
4 | List,
5 | ListItem,
6 | ListItemIcon,
7 | ListItemText,
8 | Icon,
9 | } from '@material-ui/core';
10 | import {
11 | makeStyles,
12 | Theme,
13 | ThemeProvider,
14 | } from '@material-ui/core/styles';
15 | import { inject } from '@/store';
16 | import clsx from 'classnames';
17 | import { menuTheme } from '@/theme/material';
18 |
19 | // icon
20 | import SubList from '@biz/SubList';
21 | import { Link, useLocation } from 'react-router-dom';
22 | import sideBarBgImg from '@/assets/sidebar.jpg';
23 |
24 | const drawerWidth = 260;
25 |
26 | const useStyles = makeStyles((theme: Theme) => ({
27 | drawer: {
28 | position: 'relative',
29 | zIndex: 1,
30 | flexShrink: 0,
31 | width: drawerWidth,
32 | whiteSpace: 'nowrap',
33 | },
34 | paper: {
35 | backgroundImage: `url(${sideBarBgImg})`,
36 | backgroundPosition: 'center',
37 | backgroundSize: 'cover',
38 | backgroundColor: '#fff',
39 | },
40 | drawerOpen: {
41 | width: drawerWidth,
42 | transition:
43 | theme.transitions.create('width', {
44 | easing: theme.transitions.easing.sharp,
45 | duration: theme.transitions.duration.leavingScreen,
46 | }),
47 | },
48 | drawerClose: {
49 | width: theme.spacing(8),
50 | overflowX: 'hidden',
51 | transition:
52 | theme.transitions.create('width', {
53 | easing: theme.transitions.easing.sharp,
54 | duration: theme.transitions.duration.leavingScreen,
55 | }),
56 | },
57 | listWrapper: {
58 | width: '100%',
59 | minHeight: '100%',
60 | overflow: 'auto',
61 | overflowX: 'hidden',
62 | backgroundColor: 'rgba(0, 0, 0, 0.6)',
63 | },
64 | list: {
65 | position: 'relative',
66 | zIndex: 4,
67 | padding: '0 10px',
68 | paddingBottom: '100px',
69 | },
70 | }));
71 | const injector = inject([
72 | 'app',
73 | { state: ['isMenuOpen', 'menuData'] },
74 | ]);
75 | const AppMenu: React.FC = ({ app }) => {
76 | const classes = useStyles();
77 | const { isMenuOpen, menuData } = app.state;
78 | const [$open, setOpen] = React.useState(isMenuOpen);
79 | React.useEffect(() => {
80 | if (isMenuOpen === false) {
81 | setOpen(false);
82 | }
83 | }, [isMenuOpen, setOpen]);
84 | const open = React.useMemo(() => {
85 | if (isMenuOpen) {
86 | return isMenuOpen;
87 | }
88 | return $open;
89 | }, [isMenuOpen, $open]);
90 | const openMenu = React.useCallback(() => {
91 | if (!isMenuOpen) {
92 | setOpen(true);
93 | }
94 | }, [setOpen, isMenuOpen]);
95 | const closeMenu = React.useCallback(() => {
96 | if (!isMenuOpen) {
97 | setOpen(false);
98 | }
99 | }, [setOpen, isMenuOpen]);
100 |
101 | const { pathname } = useLocation();
102 | return (
103 |
104 |
120 |
121 |
122 | {menuData.map((item: any) => {
123 | if (!item.children) {
124 | return (
125 |
132 | {item.icon && (
133 |
134 | {item.icon}
135 |
136 | )}
137 |
138 |
139 | );
140 | }
141 | const isSubListSelected = item.children.some(
142 | ({ to }: any) => pathname.includes(to),
143 | );
144 | return (
145 | {item.icon}}
151 | open={isSubListSelected}
152 | >
153 | {item.children.map((subItem: any) => (
154 |
161 | {subItem.icon && (
162 |
163 | {subItem.icon}
164 |
165 | )}
166 |
167 |
168 | ))}
169 |
170 | );
171 | })}
172 |
173 |
174 |
175 |
176 | );
177 | };
178 |
179 | export default injector(AppMenu);
180 |
--------------------------------------------------------------------------------
/src/components/business/SubList/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { List, ListItem, ListItemIcon, Collapse, ListItemText } from '@material-ui/core';
4 | import { makeStyles, useTheme } from '@material-ui/core/styles';
5 | import ExpandLess from '@material-ui/icons/ExpandLess';
6 | // import {} from '@material-ui/core/styles';
7 | import cls from 'classnames';
8 | import styles from './style.scss';
9 |
10 | type SubListItemProps = {
11 | open: boolean,
12 | icon: React.ReactElement,
13 | title: React.ReactNode | string,
14 | onClick?: (event: React.MouseEvent) => void,
15 | isMenuOpen: boolean,
16 | pl?: number,
17 | style?: any,
18 | selected?: boolean,
19 | };
20 |
21 | const useStyle = makeStyles(theme => ({
22 | subList: {
23 | '&:hover': {
24 | backgroundColor: `${theme.palette.action.hover}!important`,
25 | boxShadow: 'none',
26 | },
27 | },
28 | subListSelected: {
29 | backgroundColor: `${theme.palette.action.hover}!important`,
30 | boxShadow: 'none !important',
31 | },
32 | }));
33 |
34 | const _ListItem: any = ListItem;
35 |
36 | const getName = (ele: any) => ele?.type?.displayName;
37 | const isListItem = (ele: any): ele is React.ReactElement => React.isValidElement(ele) && (getName(ele) === _ListItem.displayName);
38 |
39 | const SubList: React.FC = ({
40 | open: initOpen,
41 | icon,
42 | title,
43 | onClick,
44 | children,
45 | pl = 30,
46 | style,
47 | selected,
48 | isMenuOpen,
49 | }) => {
50 | const [open, setOpen] = React.useState(initOpen);
51 | const classes = useStyle();
52 | const theme = useTheme();
53 | React.useEffect(() => {
54 | if (selected) {
55 | setOpen(true);
56 | }
57 | // else {
58 | // setOpen(false);
59 | // }
60 | }, [selected]);
61 | const iconJsx = React.useMemo(() => !!icon && (
62 |
63 | {icon}
64 |
65 | ), [icon]);
66 | const $onClick = React.useCallback((event: React.MouseEvent) => {
67 | setOpen(!open);
68 | if (onClick) {
69 | onClick(event);
70 | }
71 | }, [open, onClick]);
72 | return (
73 |
74 |
84 | {iconJsx}
85 |
86 | {title}
87 |
88 |
92 |
93 |
94 |
95 | {
96 | React.Children.map(children, child => (isListItem(child) ? React.cloneElement(child as React.ReactElement, {
97 | style: {paddingLeft: isMenuOpen ? pl : (theme.overrides?.MuiListItem?.gutters as any).paddingLeft},
98 | }) : child))
99 | }
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default SubList;
107 |
--------------------------------------------------------------------------------
/src/components/business/SubList/style.scss:
--------------------------------------------------------------------------------
1 | .upper-arrow {
2 | transform: rotate(90deg);
3 | transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
4 | }
5 | .down-arrow {
6 | transform: rotate(180deg);
7 | }
8 |
--------------------------------------------------------------------------------
/src/constants/common/Auth.ts:
--------------------------------------------------------------------------------
1 | export type AuthType = 'auth' | 'level' | 'role';
2 |
3 | export default {
4 | LOGIN_AUTH: 'login',
5 | };
6 |
--------------------------------------------------------------------------------
/src/constants/common/channel.ts:
--------------------------------------------------------------------------------
1 | export const CHANNEL_NAME = {
2 | PLATFORM: 'PLATFORM',
3 | CHANNEL_A: 'CHANNELA',
4 | };
5 |
6 | export const CHANNEL_FEATURE_CONFIG = {
7 | [CHANNEL_NAME.PLATFORM]: ['platform'],
8 | [CHANNEL_NAME.CHANNEL_A]: ['channel_a'],
9 | };
10 |
--------------------------------------------------------------------------------
/src/constants/common/lang/en.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | hello: 'hello!',
3 | name: 'name',
4 | };
5 |
--------------------------------------------------------------------------------
/src/constants/common/lang/zh.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | hello: '你好!',
3 | name: '名字',
4 | };
5 |
--------------------------------------------------------------------------------
/src/http/config.ts:
--------------------------------------------------------------------------------
1 | const development = {
2 | server: 'http://localhost:/api',
3 | };
4 | const stg = {
5 | server: '/api',
6 | };
7 | const production = {
8 | server: '/api',
9 | };
10 |
11 | const config = {
12 | development,
13 | stg,
14 | production,
15 | };
16 |
17 | type WithProjectEnv = {
18 | PROJECT_ENV: 'development' | 'stg' | 'production',
19 | }
20 |
21 | export default config[(process.env as WithProjectEnv).PROJECT_ENV];
22 |
--------------------------------------------------------------------------------
/src/http/index.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosRequestConfig } from 'axios';
2 | import { dateFormatting } from '@/utils';
3 | import SHA256 from 'crypto-js/sha256';
4 | import channel from './config';
5 | import history from '../routes/history';
6 |
7 | const { server: serverUrl } = channel;
8 |
9 |
10 | // 字典排序
11 | const azSort = (data: {[p: string]: any}) => {
12 | const obj: {[p: string]: any} = {};
13 | Object.keys(data)
14 | .sort()
15 | .forEach(i => {
16 | obj[i] = data[i];
17 | });
18 | let tmpStr = '';
19 | /* eslint-disable */
20 | for (const i in obj) {
21 | tmpStr += `${i}=${obj[i]}&`;
22 | }
23 | /* eslint-enable */
24 | tmpStr = tmpStr.substr(0, tmpStr.length - 1);
25 | // console.log('----->.....', tmpStr);
26 | return tmpStr;
27 | };
28 |
29 | const createRequestId = () => Math.random().toString(36).substr(2, 12) + Date.now();
30 |
31 | // 生成唯一串
32 | let requestId: string;
33 | const refreshId = () => requestId = createRequestId();
34 |
35 | history.listen(refreshId);
36 |
37 | // 拼接参数
38 | // requestNo规则: 请求参数+url+authCode+ Uid + time => SHA256 保证唯一且同一请求不变
39 | const appendParams = (data = {}, url: string) => ({
40 | chnId: '10003',
41 | requestType: 'PC',
42 | requestNo: SHA256(`${JSON.stringify(data)}${url}${window.localStorage.authCode ? window.localStorage.authCode : ''}${requestId}`).toString().substring(0, 32),
43 | requestTime: dateFormatting('yyyy-MM-dd hh:mm:ss', new Date().toString()),
44 | authCode: window.localStorage.authCode ? window.localStorage.authCode : '',
45 | });
46 |
47 | // 默认配置
48 | const config = {
49 | baseURL: serverUrl,
50 | headers: {
51 | 'Content-Type': 'application/json;charset=utf-8',
52 | },
53 | timeout: 1000 * 20,
54 | withCredentials: true,
55 | };
56 | // 创建实例对象
57 | const instance = axios.create(config);
58 |
59 | const createRequestRecords = (requestNo: string) => ({
60 | requestNo,
61 | timestamp: Date.now(),
62 | });
63 |
64 | type TRequestCache = {
65 | [p: string]: {
66 | requestNo: string;
67 | timestamp: number;
68 | }
69 | };
70 | const timeLimit = 666;// 前端接口请求防重复点击阀值
71 | const requestCache: TRequestCache = {};
72 | interface IAxiosRequestConfig extends AxiosRequestConfig {
73 | loading?: boolean;
74 | }
75 | // 请求拦截器
76 | instance.interceptors.request.use((requestConfig: IAxiosRequestConfig) => {
77 | // 请求之前,搞些事情
78 | const { loading = true, data, url: _url } = requestConfig;
79 | const url = _url || '';
80 | let params = appendParams(data, url);
81 | // 前端防重判断
82 | if (requestCache[url] && requestCache[url].requestNo === params.requestNo) {
83 | const { timestamp } = requestCache[url];
84 | // const { timeLimit } = requestCache;
85 | const now = Date.now();
86 | if (now - timestamp < timeLimit) {
87 | requestCache[url].timestamp = now;
88 | throw new Error('请勿重复操作!');
89 | } else {
90 | refreshId();
91 | params = appendParams(data, url);
92 | }
93 | }
94 | requestCache[url] = createRequestRecords(params.requestNo);
95 | const beforeSort = { ...data, ...params }; // 拼接参数
96 | const afterSort = azSort(beforeSort); // 参数排序
97 |
98 | const signData = SHA256(afterSort); // 参数加密
99 | requestConfig.data = { ...beforeSort, sign: signData };
100 |
101 | return requestConfig;
102 | },
103 | error => Promise.reject(error));
104 |
105 | // 响应拦截器
106 | instance.interceptors.response.use(
107 | response => {
108 | // nativeLog(response);
109 | // 服务端响应成功时,搞些事情
110 | // console.log(`请求成功----->${JSON.stringify(response)}`);
111 | const { responseCode } = response.data;
112 | // console.log('-----.', responseCode);
113 | if (responseCode === '3001') {
114 | // message.error('登录超时,请重新登录', 1);
115 | history.replace('/login');
116 | }
117 | if (responseCode !== '200') {
118 | throw response; // eslint-disable-line
119 | }
120 | // console.log('-----.', responseCode);
121 | return response;
122 | },
123 | error => {
124 | // 服务端响应失败时,搞些事情
125 | if (error.message) {
126 | // message.error(error.message, 2);
127 | } else {
128 | // message.error('请求失败,请重试', 2);
129 | refreshId();
130 | }
131 | return Promise.reject(error);
132 | },
133 | );
134 |
135 | export default instance;
136 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 | Document
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | // import '@babel/polyfill';
2 | import 'core-js/stable';
3 | import 'regenerator-runtime/runtime';
4 | import '@/utils/devToolInit';
5 | import React from 'react';
6 | import ReactDom from 'react-dom';
7 | import { Router } from 'react-router-dom';
8 | import history from './routes/history';
9 | import './store';
10 | import App from './App';
11 | import '@/service/common/user';
12 |
13 | const content = (
14 |
15 |
16 |
17 | );
18 |
19 | ReactDom.render(content, document.querySelector('#app'));
20 |
--------------------------------------------------------------------------------
/src/modules/Page1/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@base/Button';
3 | import Input from '@base/Input';
4 | import { Box } from '@material-ui/core';
5 | import qs from 'qs';
6 | import history from '@/routes/history';
7 | import { inject } from '@/store';
8 | import AccountCircle from '@material-ui/icons/AccountCircle';
9 | import InputAdornment from '@material-ui/core/InputAdornment';
10 |
11 | const injector = inject('page1', 'user', 'page2');
12 |
13 | const Page1: React.FC<{location: Location} & typeof injector.type> = ({user, location}) => {
14 | const redirectPath = React.useMemo(() => qs.parse(location?.search?.slice(1))?.redirect, [location]) as string | undefined;
15 | const login = React.useCallback(() => {
16 | history.replace({pathname: redirectPath || '/'});
17 | }, [redirectPath]);
18 | // React.useEffect(() => {
19 | // page1.actions.asyncChangePageName('0000');
20 | // page1.actions.asyncChangePageName1('111');
21 | // page1.actions.asyncChangePageName2('222');
22 | // }, [page1.actions]);
23 | return (
24 | <>
25 |
26 |
27 |
39 |
40 |
41 | ),
42 | }}
43 | />
44 |
45 |
46 |
47 |
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 |
55 | export default injector(Page1);
56 |
--------------------------------------------------------------------------------
/src/modules/Page1/store.ts:
--------------------------------------------------------------------------------
1 |
2 | export const state = {
3 | pageName: 'page1',
4 | };
5 |
6 | export const actions = {
7 | changePageName: (newPageName: string) => ({
8 | pageName: newPageName,
9 | }),
10 | asyncChangePageName: async (newPageName: string) => {
11 | await new Promise(res => setTimeout(res, 3000));
12 | return {
13 | pageName: newPageName,
14 | };
15 | },
16 | asyncChangePageName1: async (newPageName: string) => {
17 | await new Promise(res => setTimeout(res, 3000));
18 | return {
19 | pageName: newPageName,
20 | };
21 | },
22 | asyncChangePageName2: async (newPageName: string) => {
23 | await new Promise(res => setTimeout(res, 3000));
24 | return {
25 | pageName: newPageName,
26 | };
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/src/modules/Page1/style.scss:
--------------------------------------------------------------------------------
1 | .page1-list {
2 | display: flex;
3 | justify-content: center;
4 | box-sizing: border-box;
5 | width: 100%;
6 | // height: 100%;
7 | border: 2px solid blue;
8 | background-color: #fff;
9 | div {
10 | display: flex;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/Page2/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import Button from '@base/IconButton';
3 | // import Input from '@base/Input';
4 | // import Icon from '@material-ui/core/Icon';
5 | import styles from './style.scss';
6 | import {
7 | Button,
8 | LinearProgress,
9 | FormControlLabel,
10 | Radio,
11 | } from '@material-ui/core';
12 | import { inject } from '@/store';
13 | import { TextField, RadioGroup } from 'formik-material-ui';
14 | import { Field, useFormik, FormikProvider } from 'formik';
15 | import Checkbox from '@/components/base/Checkbox';
16 | import ErrorMsgBoxHOC from '@/components/base/ErrorMsgBoxHOC';
17 | import { DatePicker } from 'formik-material-ui-pickers';
18 | import Table from '@/components/base/Table';
19 |
20 | const injector = inject(
21 | [
22 | 'page2',
23 | {
24 | state: [s => s.rowsSelected],
25 | maps: ['countIsOdd'],
26 | },
27 | ],
28 | [
29 | 'app',
30 | {
31 | state: [s => s.name],
32 | maps: ['getLangData'],
33 | },
34 | ],
35 | );
36 |
37 | type PageProps = typeof injector.type;
38 |
39 | const _RadioGroup = ErrorMsgBoxHOC(RadioGroup);
40 |
41 | const columns = [
42 | {
43 | name: 'name',
44 | label: 'Name',
45 | options: {
46 | filter: true,
47 | sort: true,
48 | },
49 | },
50 | {
51 | name: 'company',
52 | label: 'Company',
53 | options: {
54 | filter: true,
55 | sort: false,
56 | },
57 | },
58 | {
59 | name: 'city',
60 | label: 'City',
61 | options: {
62 | filter: true,
63 | sort: false,
64 | },
65 | },
66 | {
67 | name: 'state',
68 | label: 'State',
69 | options: {
70 | filter: true,
71 | sort: false,
72 | },
73 | },
74 | ];
75 |
76 | const data = [
77 | {
78 | id: '1',
79 | name: 'Joe James',
80 | company: 'Test Corp',
81 | city: 'Yonkers',
82 | state: 'NY',
83 | },
84 | {
85 | id: '2',
86 | name: 'John Walsh',
87 | company: 'Test Corp',
88 | city: 'Hartford',
89 | state: 'CT',
90 | },
91 | {
92 | id: '3',
93 | name: 'Bob Herm',
94 | company: 'Test Corp',
95 | city: 'Tampa',
96 | state: 'FL',
97 | },
98 | {
99 | id: '4',
100 | name: 'James Houston',
101 | company: 'Test Corp',
102 | city: 'Dallas',
103 | state: 'TX',
104 | },
105 | ];
106 |
107 | const Page2: React.FC = ({ page2, app }) => {
108 | const { actions } = page2;
109 | app.actions.closeMenu;
110 | const formikbag = useFormik({
111 | initialValues: {
112 | email: '',
113 | date: '',
114 | password: '',
115 | checked: false,
116 | activity: '',
117 | },
118 | onSubmit: (values, { setSubmitting }) => {
119 | setSubmitting(false);
120 | actions.changePageName(values.email);
121 | // console.log(JSON.stringify(values, null, 2));
122 | },
123 | });
124 | const { isSubmitting, submitForm } = formikbag;
125 |
126 | // const changePage2 = (e: React.ChangeEvent) => actions.changePageName(e.target.value);
127 | return (
128 |
129 |
141 |
142 |
149 |
150 | (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(v)
156 | ? 'Invalid email address'
157 | : undefined)}
158 | />
159 |
160 | (!!v ? undefined : 'checked required!')}
165 | />
166 |
167 | (!!v ? undefined : 'acivity required!')}
171 | >
172 | }
175 | label="Painting"
176 | disabled={isSubmitting}
177 | />
178 | }
181 | label="Drawing"
182 | disabled={isSubmitting}
183 | />
184 | }
187 | label="None"
188 | disabled
189 | />
190 |
191 |
192 |
198 | {isSubmitting && }
199 |
200 |
208 |
209 |
210 | );
211 | };
212 |
213 | export default injector(Page2);
214 |
--------------------------------------------------------------------------------
/src/modules/Page2/store.ts:
--------------------------------------------------------------------------------
1 | import { ThunkParams } from 'natur/dist/middlewares';
2 |
3 | export const state = {
4 | pageName: 'page2',
5 | count: 1,
6 | // rowsSelected: ['1', '2'],
7 | rowsSelected: [0, 2],
8 | };
9 | export const maps = {
10 | pageNameSplit: ['pageName', (pageName: string) => pageName.split('')],
11 | countIsOdd: ['count', (count: number) => count % 2 !== 0],
12 | countObj: ['count', (count: number) => ({count})],
13 | test: ['count', () => () => true],
14 | };
15 | export const actions = {
16 | updateRowsSelected: (rowsSelected: State['rowsSelected']) => ({rowsSelected}),
17 | changePageName: (newPageName: string) => {
18 | console.log('changePageName: ', newPageName);
19 | return {
20 | pageName: newPageName,
21 | };
22 | },
23 | asyncChangePageName: async (newPageName: string) => {
24 | await new Promise(res => setTimeout(res, 10000));
25 | return {
26 | pageName: newPageName,
27 | };
28 | },
29 | inc: () => ({getState}: ThunkParams) => ({count: getState().count + 1}),
30 | };
31 |
32 | type State = typeof state;
33 |
--------------------------------------------------------------------------------
/src/modules/Page2/style.scss:
--------------------------------------------------------------------------------
1 | @import '~@/utils/styles/func.scss';
2 | // @import '../../utils/styles/func.scss';
3 |
4 | .page2 {
5 | box-sizing: border-box;
6 | width: 100%;
7 | // height: 100vh;
8 | padding: r(20);
9 | background-image: url('~@/assets/0326_1.jpg');
10 | // background-image: url('../../assets/0326_1.jpg');
11 | background-size: cover;
12 | // border: 2px solid blue;
13 | background-color: #fff;
14 | }
15 |
--------------------------------------------------------------------------------
/src/modules/Page3/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { inject } from '@/store';
3 | import styles from './style.scss';
4 | import { Box } from '@material-ui/core';
5 |
6 | const injector = inject('page3');
7 | const Page3: React.FC = () => (
8 |
9 | page3
10 |
11 | );
12 | export default injector(Page3);
13 |
--------------------------------------------------------------------------------
/src/modules/Page3/store.ts:
--------------------------------------------------------------------------------
1 | export const state = {
2 | name: 'page3',
3 | };
4 |
5 | export const maps = {
6 | nameSplit: ['name', (name: string) => name.split('')],
7 | };
8 |
9 | export const actions = {
10 | update: (newState: any) => newState,
11 | asyncUpdate: async (newState: any) => {
12 | await new Promise(res => setTimeout(res, 3000));
13 | return newState;
14 | },
15 | };
16 |
17 | type State = typeof state;
18 |
19 | export type InjectPage3ModuleType = {
20 | state: State,
21 | maps: {
22 | nameSplit: string[],
23 | },
24 | actions: {
25 | update(s: State): State,
26 | asyncUpdate(s: State): Promise,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/modules/Page3/style.scss:
--------------------------------------------------------------------------------
1 | .Page3 {
2 | box-sizing: border-box;
3 | width: 100%;
4 | height: 100%;
5 | background-color: #fff;
6 | }
7 |
--------------------------------------------------------------------------------
/src/modules/user/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch } from 'react-router-dom';
3 | import { Box } from '@material-ui/core';
4 | import { inject } from '@/store';
5 | import AuthRoute from '@/routes/AuthRoute';
6 | import { AppRoute } from '@/routes';
7 |
8 | const injector = inject('user');
9 |
10 | const User: React.FC> = ({routes = []}) => (
11 |
12 | 用户管理
13 |
14 | {routes.map((route, index: number) => (
15 |
16 | ))}
17 |
18 |
19 | );
20 | export default injector(User);
21 |
--------------------------------------------------------------------------------
/src/modules/user/list/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import history from '@/routes/history';
3 | import { Box } from '@material-ui/core';
4 | import style from './style.scss';
5 | import { inject } from '@/store';
6 |
7 | const goPage1 = () => history.push({
8 | pathname: '/page1',
9 | search: '?id=1',
10 | });
11 | const injector = inject('user');
12 |
13 | const UserList: React.FC = ({user}: any) => (
14 |
15 | 当前登录用户名:
16 |
17 |
20 |
21 | );
22 |
23 | export default injector(UserList);
24 |
--------------------------------------------------------------------------------
/src/modules/user/list/store.ts:
--------------------------------------------------------------------------------
1 | import { ThunkParams } from 'natur/dist/middlewares';
2 |
3 | const state = {
4 | name: 'userList',
5 | age: 11,
6 | };
7 |
8 | const maps = {
9 | nameSplit: ['name', (s: any) => s, (name: string) => name.split('')],
10 | add1: [(s: any) => s.name, (name: string) => {
11 | console.log('run add1');
12 | return `${name}1`;
13 | }],
14 | addAny: [(s: any) => s.name, (name: string) => (a1: string) => `${name}${a1}`],
15 | };
16 |
17 | const actions = {
18 | returnUndef: () => undefined,
19 | thunkReturnUndef: () => () => undefined,
20 | asyncThunkReturnUndef: () => () => Promise.resolve(undefined),
21 | asyncReturnUndef: () => Promise.resolve(undefined),
22 |
23 | update: (params: any) => ({getState, setState}: ThunkParams) => setState({
24 | ...getState(),
25 | ...params,
26 | }),
27 | asyncUpdate: () => async ({getState, setState}: any) => {
28 | setState({
29 | ...getState(),
30 | age: parseInt(getState().age, 10) + 1,
31 | });
32 | },
33 | };
34 |
35 |
36 | export {
37 | state,
38 | maps,
39 | actions,
40 | };
41 |
--------------------------------------------------------------------------------
/src/modules/user/list/style.scss:
--------------------------------------------------------------------------------
1 | .user-list {
2 | box-sizing: border-box;
3 | width: 100%;
4 | height: 100%;
5 | background-color: #fff;
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/AuthRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect, RouteProps } from 'react-router-dom';
3 | import qs from 'qs';
4 | import { inject } from '@/store';
5 | import AUTH from '@/constants/common/Auth';
6 | import { AppRoute } from '.';
7 |
8 | const injector = inject(
9 | 'user',
10 | ['app', {}],
11 | );
12 |
13 | type AuthRouteProps = AppRoute & typeof injector.type & RouteProps;
14 |
15 |
16 | export const redirectWithoutAuth = {
17 | [AUTH.LOGIN_AUTH]: '/login',
18 | };
19 |
20 | function AuthRoute({routes, component, indexRoute, user, auth, ...rest}: AuthRouteProps) {
21 | const Com = component;
22 | const render = React.useCallback(
23 | (props: any) => (
24 |
25 | ),
26 | [indexRoute, routes],
27 | );
28 | if (user.maps.hasAuth(auth)) {
29 | return (
30 |
34 | );
35 | }
36 | const fromSearch = qs.stringify({
37 | redirect: `${rest.location?.pathname}${rest.location?.search}`,
38 | });
39 | return (
40 |
46 | );
47 | }
48 |
49 |
50 | export default injector(AuthRoute);
51 |
--------------------------------------------------------------------------------
/src/routes/history.ts:
--------------------------------------------------------------------------------
1 | import {
2 | // createHashHistory,
3 | createBrowserHistory,
4 | // createMemoryHistory,
5 | } from 'history';
6 |
7 | // const currentHistory = createHashHistory();
8 | // const currentHistory = createMemoryHistory();
9 | const currentHistory = createBrowserHistory({
10 | // basename: '/web',
11 | basename: process.env.BASE_URL,
12 | });
13 | export default currentHistory;
14 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import loadabel, { LoadableComponent } from '@loadable/component';
3 | import Auth from '@/constants/common/Auth';
4 | import Loading from '@base/Skeleton';
5 | // import Loading from '@base/Loading';
6 | import UserWrapper from '@/modules/user';
7 | import delayLoad from 'delay-load';
8 | import Layout from '@biz/Layout';
9 |
10 | const _loadabel = (c: () => Promise, time: number = 700) => loadabel(() => delayLoad(c, time), {
11 | fallback: ,
12 | });
13 |
14 | export const Index = _loadabel(() => import('@/modules/Page2'));
15 |
16 | export type AppRoute = {
17 | path: string,
18 | auth?: string,
19 | component: React.ComponentClass | LoadableComponent | React.FC,
20 | indexRoute?: React.ComponentClass | LoadableComponent | React.FC,
21 | name?: string,
22 | routes?: AppRoute[],
23 | };
24 |
25 | export type Routes = AppRoute[];
26 |
27 | const routes:Routes = [
28 | {
29 | path: '/login',
30 | name: 'login',
31 | component: _loadabel(() => import('@/modules/Page1')),
32 | },
33 | {
34 | path: '/',
35 | component: Layout,
36 | // auth: Auth.LOGIN_AUTH,
37 | indexRoute: Index,
38 | routes: [
39 | {
40 | path: '/page2',
41 | name: 'page2',
42 | component: Index,
43 | },
44 | {
45 | path: '/page3',
46 | name: 'page3',
47 | component: _loadabel(() => import('@/modules/Page3')),
48 | },
49 | {
50 | path: '/user',
51 | name: 'user',
52 | component: UserWrapper,
53 | auth: Auth.LOGIN_AUTH,
54 | routes: [
55 | {
56 | path: '/user/list',
57 | name: 'user-list',
58 | component: _loadabel(() => import('@/modules/user/list')),
59 | },
60 | ],
61 | },
62 | ],
63 | },
64 |
65 | ];
66 |
67 | export default routes;
68 |
--------------------------------------------------------------------------------
/src/service/common/app.ts:
--------------------------------------------------------------------------------
1 | import NaturService from '../natur-service';
2 |
3 |
4 | class AppService extends NaturService {
5 | get langData() {
6 | return this.store.getModule('app').maps.getLangData;
7 | }
8 |
9 | showLoading() {
10 | this.store.dispatch('loading', 'show');
11 | }
12 |
13 | hideLoading() {
14 | this.store.dispatch('loading', 'hide');
15 | }
16 | }
17 |
18 | const appService = new AppService();
19 |
20 | export default appService;
21 |
--------------------------------------------------------------------------------
/src/service/common/channel.ts:
--------------------------------------------------------------------------------
1 | import NaturService from '../natur-service';
2 | import React from 'react';
3 | import store from '@/store';
4 |
5 |
6 | class ChannelService extends NaturService {
7 | get channel() {
8 | return this.store.getModule('channel');
9 | }
10 |
11 | /** 判断当前渠道是否包含某功能,功能配置在constants/channel中 */
12 | has(featureName: string) {
13 | return this.channel.maps.has(featureName);
14 | }
15 |
16 | /** 根据功能名称执行对应的方法,功能配置在constants/channel中 */
17 | featureSwitch(features: {[k: string]: Function}) {
18 | return this.channel.maps.switch(features);
19 | }
20 | }
21 |
22 | export const useChannel = () => {
23 | const unsubRef = React.useRef();
24 | const [channel, updateChannel] = React.useState(() => {
25 | unsubRef.current = () => store.subscribe('channel', () => updateChannel(store.getModule('channel')));
26 | return store.getModule('channel');
27 | });
28 | React.useEffect(() => () => {
29 | unsubRef.current && unsubRef.current();
30 | }, []);
31 | return {
32 | /** 判断当前渠道是否包含某功能,功能配置在constants/channel中 */
33 | has: channel.maps.has,
34 | /** 根据功能名称执行对应的方法,功能配置在constants/channel中 */
35 | featureSwitch: channel.maps.switch,
36 | };
37 | };
38 |
39 | const channelService = new ChannelService(store);
40 |
41 | export default channelService;
42 |
--------------------------------------------------------------------------------
/src/service/common/i18n.ts:
--------------------------------------------------------------------------------
1 | import appService from '@/service/common/app';
2 | import store from '@/store';
3 | import { useState, useEffect } from 'react';
4 |
5 | export const t = (key: keyof typeof appService.langData) => appService.langData[key];
6 |
7 | const getStoreLang = () => store.getModule('app').state.lang;
8 |
9 | export const useI18n = () => {
10 | const [lang, setLang] = useState(getStoreLang());
11 | useEffect(() => store.subscribe('app', () => {
12 | if (getStoreLang() !== lang) {
13 | setLang(getStoreLang());
14 | }
15 | }), [lang]);
16 | return t;
17 | };
18 |
--------------------------------------------------------------------------------
/src/service/common/theme.ts:
--------------------------------------------------------------------------------
1 | import curry from 'lodash/curry';
2 | import clone from 'lodash/cloneDeep';
3 | // import { curry, cloneDeep as clone } from 'lodash';
4 | import cstThemeConfig from '@/theme/native/config';
5 |
6 | type IThemeConfig = {
7 | [propName: string]: string;
8 | };
9 |
10 | class Theme {
11 | private themeConfig: IThemeConfig = {};
12 |
13 | private themeDom: HTMLElement | null;
14 |
15 | private themeTextBackup: string = '';
16 |
17 | constructor(selector: string, themeConfig: IThemeConfig) {
18 | this.themeDom = document.querySelector(selector);
19 | if (this.themeDom) {
20 | this.themeTextBackup = this.themeDom.innerHTML;
21 | }
22 | this.themeConfig = clone(themeConfig);
23 | this.syncTheme();
24 | }
25 |
26 | // 根据主题配置,同步主题
27 | private syncTheme() {
28 | if (!this.themeDom) {
29 | return;
30 | }
31 | const themeStyleText = Object.keys(this.themeConfig).reduce(
32 | (styleText, key) => styleText.replace(key, this.themeConfig[key]),
33 | this.themeTextBackup,
34 | );
35 | this.themeDom.innerHTML = themeStyleText;
36 | }
37 |
38 | changeColor(oldColor: string, newColor: string) {
39 | if (oldColor === newColor) {
40 | return;
41 | }
42 | Object.keys(this.themeConfig).forEach(key => {
43 | if (this.themeConfig[key] === oldColor) {
44 | this.themeConfig[key] = newColor;
45 | }
46 | });
47 | this.syncTheme();
48 | }
49 |
50 | set(prop: string, value: string) {
51 | if (this.themeConfig[prop] === undefined) {
52 | throw new Error(`主题配置中没有${prop}属性!`);
53 | }
54 | if (this.themeConfig[prop] === value) {
55 | return;
56 | }
57 | this.themeConfig[prop] = value;
58 | this.syncTheme();
59 | }
60 |
61 | resetConfig(themeConfig: IThemeConfig) {
62 | this.themeConfig = themeConfig;
63 | this.syncTheme();
64 | }
65 | }
66 |
67 | let themeInstance: Theme | null = null;
68 | const createTheme = curry((selector: string, themeConfig: IThemeConfig) => {
69 | if (themeInstance === null) {
70 | themeInstance = new Theme(selector, themeConfig);
71 | }
72 | return themeInstance;
73 | });
74 |
75 | export default createTheme('#theme-css', cstThemeConfig);
76 |
--------------------------------------------------------------------------------
/src/service/common/user.ts:
--------------------------------------------------------------------------------
1 | import { AuthType } from '@/constants/common/Auth';
2 | import NaturService from '../natur-service';
3 |
4 | class UserService extends NaturService {
5 | start() {
6 | this.watch('user', ({actionName, state}) => {
7 | if (actionName === 'updateName' && state) {
8 | this.dispatch('page2', 'changePageName', state?.name);
9 | }
10 | });
11 | this.watch('page1', ({actionName, state}) => {
12 | if (actionName === 'asyncChangePageName2' && state) {
13 | this.dispatch('page2', 'asyncChangePageName', state?.pageName);
14 | }
15 | });
16 | }
17 |
18 | get isLogin() {
19 | return this.store.getModule('user').maps.isLogin;
20 | }
21 |
22 | hasAuth(auth: string | undefined, authType?: AuthType) {
23 | return this.store.getModule('user').maps.hasAuth(auth, authType);
24 | }
25 | }
26 |
27 | const userService = new UserService();
28 |
29 | export default userService;
30 |
--------------------------------------------------------------------------------
/src/service/index.ts:
--------------------------------------------------------------------------------
1 | export { default as appService } from './common/app';
2 | export { default as userService } from './common/user';
3 | export { default as channelService } from './common/channel';
4 |
--------------------------------------------------------------------------------
/src/service/natur-service.ts:
--------------------------------------------------------------------------------
1 | import NaturService from 'natur-service';
2 | import store, { LM, M } from '@/store';
3 |
4 |
5 | export default class extends NaturService{
6 | constructor(_store: typeof store = store) {
7 | super(_store);
8 | this.start();
9 | }
10 | start() {}
11 | dispatch: NaturService['dispatch'] = (...arg) => {
12 | return super.dispatch(arg[0], arg[1], ...(arg as any).slice(2)).catch(e => {
13 | if (e?.code === 0) {
14 | return;
15 | }
16 | throw e;
17 | }) as any;
18 | }
19 | };
--------------------------------------------------------------------------------
/src/store/common/channel.store.ts:
--------------------------------------------------------------------------------
1 | import { CHANNEL_FEATURE_CONFIG, CHANNEL_NAME } from '@/constants/common/channel';
2 |
3 | type CHANNEL_NAME_KEYS = keyof typeof CHANNEL_NAME;
4 |
5 | const has = (channelName: CHANNEL_NAME_KEYS) => (featureName: string) => {
6 | const channelFeatures = CHANNEL_FEATURE_CONFIG[channelName];
7 | if (/^!/.test(featureName)) {
8 | return (
9 | !channelFeatures
10 | || !channelFeatures.includes(featureName.slice(1))
11 | );
12 | }
13 | return channelFeatures && channelFeatures.includes(featureName);
14 | };
15 |
16 | const channelStore = {
17 | state: {
18 | channelName: CHANNEL_NAME.PLATFORM,
19 | },
20 | actions: {
21 | updateChannel: (channelName: CHANNEL_NAME_KEYS) => {
22 | if (CHANNEL_NAME[channelName] === undefined) {
23 | throw new Error('feature updateChannel: 无效的channelId!');
24 | }
25 | return {
26 | channelName,
27 | };
28 | },
29 | },
30 | maps: {
31 | has: ['channelName', has],
32 | switch: [
33 | 'channelName',
34 | (channelName: CHANNEL_NAME_KEYS) => {
35 | const hasFeature = has(channelName);
36 | return (features: {[k: string]: Function}) => Object.keys(features).forEach(
37 | featureName => hasFeature(featureName) && features[featureName](),
38 | );
39 | },
40 | ],
41 | },
42 | };
43 |
44 | export default channelStore;
45 |
--------------------------------------------------------------------------------
/src/store/common/loading.store.ts:
--------------------------------------------------------------------------------
1 | import { ThunkParams } from 'natur';
2 |
3 | const createShowLoadingState = (oldState: State) => {
4 | const state = {...oldState};
5 | if (state.loadingCount <= 0) {
6 | state.showLoading = true;
7 | state.loadingCount = 0;
8 | }
9 | state.loadingCount += 1;
10 | return state;
11 | };
12 |
13 | const createHideLoadingState = (oldState: State) => {
14 | const state = {...oldState};
15 | state.loadingCount -= 1;
16 | if (state.loadingCount <= 0) {
17 | state.showLoading = false;
18 | state.loadingCount = 0;
19 | }
20 | return state;
21 | };
22 |
23 | const state = {
24 | loadingCount: 0,
25 | showLoading: false,
26 | loadingText: '加载中',
27 | loadingZIndex: 100,
28 | };
29 |
30 | type State = typeof state;
31 |
32 | export default {
33 | state,
34 | actions: {
35 | show: () => ({getState}: ThunkParams) => createShowLoadingState(getState()),
36 | hide: () => ({getState}: ThunkParams) => createHideLoadingState(getState()),
37 | changeLoadingText: (loadingText: string) => ({loadingText}),
38 | changeLoadingZIndex: (loadingZIndex: number) => ({
39 | loadingZIndex,
40 | }),
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/store/common/no.devtool.ts:
--------------------------------------------------------------------------------
1 |
2 | const md = () => (next: any) => (record: any) => next(record);
3 | export default md;
4 |
--------------------------------------------------------------------------------
/src/store/common/persist.ts:
--------------------------------------------------------------------------------
1 | import createPersistMiddleware from 'natur-persist';
2 |
3 |
4 | const { middleware: localStorageMiddleware, getData, clearData } = createPersistMiddleware({
5 | name: '_data',
6 | time: 300,
7 | include: ['user', 'app'],
8 | // exclude: [/router/i],
9 | specific: {
10 | user: 0,
11 | },
12 | });
13 |
14 | export {
15 | localStorageMiddleware,
16 | getData,
17 | clearData,
18 | };
19 |
--------------------------------------------------------------------------------
/src/store/common/redux.devtool.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createStore } from 'redux';
3 |
4 |
5 | const root = (state: Object = {}, actions: any):Object => ({
6 | ...state,
7 | ...actions.state,
8 | });
9 |
10 | const createMiddleware = () => {
11 | if (process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__) {
12 | const devMiddleware = window.__REDUX_DEVTOOLS_EXTENSION__();
13 | const store = createStore(root, devMiddleware);
14 | return () => (next: any) => (record: any) => {
15 | store.dispatch({
16 | type: `${record.moduleName}/${record.actionName}`,
17 | state: {
18 | [record.moduleName]: record.state,
19 | },
20 | });
21 | return next(record);
22 | }
23 | }
24 | return () => (next: any) => (record: any) => next(record);;
25 | }
26 |
27 | export default createMiddleware();
28 |
--------------------------------------------------------------------------------
/src/store/common/router.store.ts:
--------------------------------------------------------------------------------
1 | import { Location } from 'history';
2 |
3 | export default {
4 | state: {
5 | location: null,
6 | },
7 | maps: {},
8 | actions: {
9 | updateLocation: (location: Location) => ({location}),
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/src/store/common/toast.store.ts:
--------------------------------------------------------------------------------
1 | import { ThunkParams } from 'natur';
2 |
3 |
4 | type ToastType = 'info' | 'success' | 'warning' | 'error';
5 |
6 | type ToastItem = {
7 | text: string;
8 | duration: number;
9 | show: boolean;
10 | id: string;
11 | type: ToastType;
12 | };
13 |
14 | const createToastItem = ({
15 | text,
16 | duration = 3000,
17 | type = 'info',
18 | }: {
19 | text: string;
20 | duration?: number;
21 | type?: ToastType;
22 | }) => ({
23 | text,
24 | duration,
25 | show: true,
26 | type,
27 | id: Math.random().toString(36).slice(2),
28 | });
29 |
30 |
31 | const state = [] as ToastItem[];
32 |
33 |
34 | const createToastAction = (type: ToastType = 'info') => (
35 | text: string,
36 | duration: number = 3000,
37 | ) => ({
38 | getState,
39 | dispatch,
40 | }: ThunkParams) => {
41 | const toastItem = createToastItem({text, duration, type});
42 | // 隐藏toast,触发toast ui退出动画
43 | setTimeout(() => dispatch('toast/hide', toastItem.id), duration);
44 | // 触发toast ui退出动画3秒后,toast ui应该已经退出完成了,此时删除toast数据
45 | setTimeout(() => dispatch('toast/remove', toastItem.id), duration + 3000);
46 | return [
47 | ...getState(),
48 | toastItem,
49 | ];
50 | };
51 |
52 |
53 |
54 | export default {
55 | state,
56 | actions: {
57 | info: createToastAction('info'),
58 | success: createToastAction('success'),
59 | warning: createToastAction('warning'),
60 | error: createToastAction('error'),
61 | hide: (id: string) => ({
62 | getState,
63 | }: ThunkParams) => getState().map(i => (i.id === id ? { ...i, show: false } : i)),
64 | remove: (id: string) => ({
65 | getState,
66 | }: ThunkParams) => getState().filter(i => i.id !== id),
67 | },
68 | };
69 |
--------------------------------------------------------------------------------
/src/store/common/user.store.ts:
--------------------------------------------------------------------------------
1 | import AUTH, {AuthType} from '@/constants/common/Auth';
2 |
3 |
4 | const getStateNameIsExist = (state: any) => !!state.name;
5 |
6 | const store = {
7 | state: {
8 | name: '',
9 | level: 1,
10 | role: 'admin',
11 | },
12 | maps: {
13 | isLogin: [getStateNameIsExist, (isExist: boolean) => isExist],
14 | hasAuth: [getStateNameIsExist, 'level', 'role', (isExist:boolean, level: number, role: string) => (auth: string | undefined, type: AuthType = 'auth') => {
15 | if (auth === undefined) {
16 | return true;
17 | }
18 | switch (type) {
19 | case 'auth':
20 | if (auth === AUTH.LOGIN_AUTH) {
21 | return !!isExist;
22 | }
23 | return !auth;
24 | case 'level':
25 | return parseInt(auth, 10) >= level;
26 | case 'role':
27 | return role === 'admin' || role === auth;
28 | default:
29 | return true;
30 | }
31 | }],
32 | },
33 | actions: {
34 | updateName: (event: any) => ({name: event?.target?.value || ''}),
35 | },
36 | };
37 |
38 | type State = typeof store.state;
39 |
40 |
41 | export default store;
42 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore, createInject } from 'natur';
2 | import {
3 | promiseMiddleware,
4 | shallowEqualMiddleware,
5 | // thunkMiddleware,
6 | filterUndefinedMiddleware,
7 | fillObjectRestDataMiddleware,
8 | } from 'natur/dist/middlewares';
9 | import devTool from '@redux-devtool';
10 | import history from '@history';
11 | import { localStorageMiddleware, getData, clearData } from './common/persist';
12 | import app from '../App/store';
13 | import user from './common/user.store';
14 | import channel from './common/channel.store';
15 | import toast from './common/toast.store';
16 | import loading from './common/loading.store';
17 | import router from './common/router.store';
18 | import lazyModuleConfig from './lazyModule';
19 | import { createPromiseWatcherMiddleware } from 'natur-promise-watcher';
20 | import { thunkMiddleware } from 'natur-immer';
21 |
22 |
23 | const { modules: lazyModules } = lazyModuleConfig;
24 |
25 | const modules = {
26 | app,
27 | user,
28 | router,
29 | toast,
30 | loading,
31 | channel,
32 | };
33 |
34 | export type M = typeof modules;
35 | export type LM = typeof lazyModules;
36 |
37 | const {
38 | collectPromiseMiddleware,
39 | } = createPromiseWatcherMiddleware();
40 |
41 | const store = createStore(modules, lazyModules, {
42 | initStates: getData(),
43 | middlewares: [
44 | thunkMiddleware,
45 | collectPromiseMiddleware,
46 | promiseMiddleware,
47 | fillObjectRestDataMiddleware,
48 | shallowEqualMiddleware,
49 | filterUndefinedMiddleware,
50 | devTool,
51 | localStorageMiddleware,
52 | ],
53 | });
54 |
55 | export type StoreType = typeof store.type;
56 |
57 |
58 | function clearDataAtLoginPage(shouldResetState: boolean = true) {
59 | if (history.location.pathname.includes('login') && getData()) {
60 | clearData();
61 | shouldResetState && store.globalResetStates({ exclude: [/^router$/] });
62 | }
63 | }
64 |
65 | clearDataAtLoginPage(false);
66 |
67 | history.listen(() => clearDataAtLoginPage());
68 |
69 | // (window as any).store = store;
70 |
71 | export default store;
72 | export const inject = createInject({
73 | storeGetter: () => store,
74 | });
75 |
76 | // const m = store.getModule;
77 |
78 | // store.getModule('loading');
79 | // store.dispatch('page1', 'asyncChangePageName', '');
80 |
81 |
--------------------------------------------------------------------------------
/src/store/lazyModule.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | modules: {
4 | page1: () => import(/* webpackChunkName:"page1" */ '@/modules/Page1/store'),
5 | page2: () => import(/* webpackChunkName:"page2" */ '@/modules/Page2/store'),
6 | page3: () => import(/* webpackChunkName:"page3" */ '@/modules/Page3/store'),
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/src/theme/material.ts:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from '@material-ui/core/styles';
2 | import { zhCN } from '@material-ui/core/locale';
3 | import { Shadows } from '@material-ui/core/styles/shadows';
4 | import r from './r';
5 | // import { lightBlue } from '@material-ui/core/colors';
6 |
7 | const defaultShadow: Shadows = [
8 | 'none',
9 | '0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)',
10 | '0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)',
11 | '0px 3px 3px -2px rgba(0,0,0,0.2),0px 3px 4px 0px rgba(0,0,0,0.14),0px 1px 8px 0px rgba(0,0,0,0.12)',
12 | '0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)',
13 | '0px 3px 5px -1px rgba(0,0,0,0.2),0px 5px 8px 0px rgba(0,0,0,0.14),0px 1px 14px 0px rgba(0,0,0,0.12)',
14 | '0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)',
15 | '0px 4px 5px -2px rgba(0,0,0,0.2),0px 7px 10px 1px rgba(0,0,0,0.14),0px 2px 16px 1px rgba(0,0,0,0.12)',
16 | '0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)',
17 | '0px 5px 6px -3px rgba(0,0,0,0.2),0px 9px 12px 1px rgba(0,0,0,0.14),0px 3px 16px 2px rgba(0,0,0,0.12)',
18 | '0px 6px 6px -3px rgba(0,0,0,0.2),0px 10px 14px 1px rgba(0,0,0,0.14),0px 4px 18px 3px rgba(0,0,0,0.12)',
19 | '0px 6px 7px -4px rgba(0,0,0,0.2),0px 11px 15px 1px rgba(0,0,0,0.14),0px 4px 20px 3px rgba(0,0,0,0.12)',
20 | '0px 7px 8px -4px rgba(0,0,0,0.2),0px 12px 17px 2px rgba(0,0,0,0.14),0px 5px 22px 4px rgba(0,0,0,0.12)',
21 | '0px 7px 8px -4px rgba(0,0,0,0.2),0px 13px 19px 2px rgba(0,0,0,0.14),0px 5px 24px 4px rgba(0,0,0,0.12)',
22 | '0px 7px 9px -4px rgba(0,0,0,0.2),0px 14px 21px 2px rgba(0,0,0,0.14),0px 5px 26px 4px rgba(0,0,0,0.12)',
23 | '0px 8px 9px -5px rgba(0,0,0,0.2),0px 15px 22px 2px rgba(0,0,0,0.14),0px 6px 28px 5px rgba(0,0,0,0.12)',
24 | '0px 8px 10px -5px rgba(0,0,0,0.2),0px 16px 24px 2px rgba(0,0,0,0.14),0px 6px 30px 5px rgba(0,0,0,0.12)',
25 | '0px 8px 11px -5px rgba(0,0,0,0.2),0px 17px 26px 2px rgba(0,0,0,0.14),0px 6px 32px 5px rgba(0,0,0,0.12)',
26 | '0px 9px 11px -5px rgba(0,0,0,0.2),0px 18px 28px 2px rgba(0,0,0,0.14),0px 7px 34px 6px rgba(0,0,0,0.12)',
27 | '0px 9px 12px -6px rgba(0,0,0,0.2),0px 19px 29px 2px rgba(0,0,0,0.14),0px 7px 36px 6px rgba(0,0,0,0.12)',
28 | '0px 10px 13px -6px rgba(0,0,0,0.2),0px 20px 31px 3px rgba(0,0,0,0.14),0px 8px 38px 7px rgba(0,0,0,0.12)',
29 | '0px 10px 13px -6px rgba(0,0,0,0.2),0px 21px 33px 3px rgba(0,0,0,0.14),0px 8px 40px 7px rgba(0,0,0,0.12)',
30 | '0px 10px 14px -6px rgba(0,0,0,0.2),0px 22px 35px 3px rgba(0,0,0,0.14),0px 8px 42px 7px rgba(0,0,0,0.12)',
31 | '0px 11px 14px -7px rgba(0,0,0,0.2),0px 23px 36px 3px rgba(0,0,0,0.14),0px 9px 44px 8px rgba(0,0,0,0.12)',
32 | '0px 11px 15px -7px rgba(0,0,0,0.2),0px 24px 38px 3px rgba(0,0,0,0.14),0px 9px 46px 8px rgba(0,0,0,0.12)',
33 | ];
34 |
35 | const mainShadow: Shadows = defaultShadow.slice() as Shadows;
36 |
37 | mainShadow[24] = '0 12px 20px -10px rgba(33, 150, 243,.28), 0 4px 20px 0 rgba(0, 0, 0,.12), 0 7px 8px -5px rgba(33, 150, 243,.2)';
38 |
39 | const theme = createMuiTheme(
40 | {
41 | palette: {
42 | primary: {
43 | light: '#64b5f6',
44 | main: '#2196f3',
45 | dark: '#1976d2',
46 | contrastText: '#fff',
47 | },
48 | secondary: {
49 | // light: '#64b5f6',
50 | main: '#2196f3',
51 | // dark: '#1976d2',
52 | // contrastText: '#fff',
53 | },
54 | },
55 | shape: {
56 | borderRadius: 0,
57 | },
58 | shadows: mainShadow,
59 | props: {
60 | MuiTextField: {
61 | variant: 'outlined',
62 | size: 'small',
63 | type: 'text',
64 | },
65 | // MuiCheckbox: {
66 | // color: 'primary',
67 | // },
68 | // MuiRadio: {
69 | // color: 'primary',
70 | // },
71 | },
72 | overrides: {
73 | MuiOutlinedInput: {
74 | inputMarginDense: {
75 | paddingTop: 9.5,
76 | paddingBottom: 9.5,
77 | },
78 | root: {
79 | '&$focused $notchedOutline': {
80 | borderWidth: 1,
81 | // borderColor: 'blue',
82 | },
83 | '&:hover $notchedOutline': {
84 | // borderColor: '#2196f3',
85 | },
86 | },
87 | },
88 | // @ts-ignore
89 | MUIDataTableHeadCell: {
90 | sortAction: {
91 | display: 'flex',
92 | alignItems: 'center',
93 | },
94 | },
95 | MuiAlert: {
96 | root: {
97 | minWidth: 250,
98 | },
99 | },
100 | // date picker的主题样式覆盖,详情请看
101 | // @ts-ignore
102 | // MuiPickersDay: {
103 | // day: {
104 | // color: lightBlue.A700,
105 | // },
106 | // daySelected: {
107 | // backgroundColor: lightBlue['400'],
108 | // },
109 | // dayDisabled: {
110 | // color: lightBlue['100'],
111 | // },
112 | // current: {
113 | // color: lightBlue['900'],
114 | // },
115 | // },
116 | // MuiPickersModal: {
117 | // dialogAction: {
118 | // color: lightBlue['400'],
119 | // },
120 | // },
121 | // MuiButton: {
122 | // root: {
123 | // padding: `${r(6)} ${r(16)}`,
124 | // },
125 | // },
126 | },
127 | spacing: px => r(px * 8),
128 | },
129 | zhCN,
130 | );
131 |
132 | export const menuTheme = createMuiTheme(
133 | {
134 | palette: {
135 | type: 'dark',
136 | action: {
137 | hover: 'rgba(200, 200, 200, 0.2)',
138 | hoverOpacity: 0.2,
139 | selected: 'rgba(200, 200, 200, 0.2)',
140 | selectedOpacity: 0.2,
141 | },
142 | },
143 | overrides: {
144 | MuiDrawer: {
145 | paperAnchorDockedLeft: {
146 | borderRight: 'none',
147 | },
148 | },
149 | MuiListItem: {
150 | root: {
151 | marginTop: 10,
152 | borderRadius: 0,
153 | '&$selected,&$selected:hover': {
154 | boxShadow: mainShadow[24],
155 | backgroundColor: theme.palette.primary.main,
156 | },
157 | transition:
158 | 'all cubic-bezier(0.4, 0, 0.2, 1) 0.3s!important',
159 | },
160 | gutters: {
161 | paddingLeft: 10,
162 | },
163 | },
164 | MuiListItemIcon: {
165 | root: {
166 | minWidth: 45,
167 | },
168 | },
169 | },
170 | },
171 | zhCN,
172 | );
173 |
174 | export default theme;
175 |
--------------------------------------------------------------------------------
/src/theme/native/config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | cardBgColor: 'green',
3 | };
4 |
--------------------------------------------------------------------------------
/src/theme/native/theme.scss:
--------------------------------------------------------------------------------
1 | .card {
2 | color: #fff;
3 | background-color: cardBgColor;
4 | }
5 |
--------------------------------------------------------------------------------
/src/theme/r.ts:
--------------------------------------------------------------------------------
1 | // const r = ($px: number) => `${($px / 375) * 100}vw`;
2 | const r = ($px: number) => $px;
3 |
4 | export default r;
5 |
--------------------------------------------------------------------------------
/src/theme/unit.ts:
--------------------------------------------------------------------------------
1 | // import r from './r';
2 |
3 | import { Options } from 'jss-plugin-default-unit';
4 |
5 | const px = 'px';
6 | const ms = 'ms';
7 | const percent = '%';
8 | // const vw = r;
9 | const current = px;
10 | /**
11 | * Generated jss-plugin-default-unit CSS property units
12 | *
13 | * @type object
14 | */
15 | const options: Options = {
16 | // Animation properties
17 | 'animation-delay': ms,
18 | 'animation-duration': ms,
19 |
20 | // Background properties
21 | 'background-position': current,
22 | 'background-position-x': current,
23 | 'background-position-y': current,
24 | 'background-size': current,
25 |
26 | // Border Properties
27 | border: px,
28 | 'border-bottom': px,
29 | 'border-bottom-left-radius': px,
30 | 'border-bottom-right-radius': px,
31 | 'border-bottom-width': px,
32 | 'border-left': px,
33 | 'border-left-width': px,
34 | 'border-radius': px,
35 | 'border-right': px,
36 | 'border-right-width': px,
37 | 'border-top': px,
38 | 'border-top-left-radius': px,
39 | 'border-top-right-radius': px,
40 | 'border-top-width': px,
41 | 'border-width': px,
42 |
43 | // Margin properties
44 | margin: current,
45 | 'margin-bottom': current,
46 | 'margin-left': current,
47 | 'margin-right': current,
48 | 'margin-top': current,
49 |
50 | // Padding properties
51 | padding: current,
52 | 'padding-bottom': current,
53 | 'padding-left': current,
54 | 'padding-right': current,
55 | 'padding-top': current,
56 |
57 | // Mask properties
58 | 'mask-position-x': current,
59 | 'mask-position-y': current,
60 | 'mask-size': current,
61 |
62 | // Width and height properties
63 | height: current,
64 | width: current,
65 | 'min-height': current,
66 | 'max-height': current,
67 | 'min-width': current,
68 | 'max-width': current,
69 |
70 | // Position properties
71 | bottom: current,
72 | left: current,
73 | top: current,
74 | right: current,
75 |
76 | // Shadow properties
77 | 'box-shadow': current,
78 | 'text-shadow': current,
79 |
80 | // Column properties
81 | 'column-gap': current,
82 | 'column-rule': current,
83 | 'column-rule-width': current,
84 | 'column-width': current,
85 |
86 | // Font and text properties
87 | 'font-size': current,
88 | 'font-size-delta': current,
89 | 'letter-spacing': current,
90 | 'text-indent': current,
91 | 'text-stroke': current,
92 | 'text-stroke-width': current,
93 | 'word-spacing': current,
94 |
95 | // Motion properties
96 | motion: current,
97 | 'motion-offset': current,
98 |
99 | // Outline properties
100 | outline: current,
101 | 'outline-offset': current,
102 | 'outline-width': current,
103 |
104 | // Perspective properties
105 | perspective: current,
106 | 'perspective-origin-x': percent,
107 | 'perspective-origin-y': percent,
108 |
109 | // Transform properties
110 | 'transform-origin': percent,
111 | 'transform-origin-x': percent,
112 | 'transform-origin-y': percent,
113 | 'transform-origin-z': percent,
114 |
115 | // Transition properties
116 | 'transition-delay': ms,
117 | 'transition-duration': ms,
118 |
119 | // Alignment properties
120 | 'vertical-align': current,
121 | 'flex-basis': current,
122 |
123 | // Some random properties
124 | 'shape-margin': current,
125 | size: current,
126 |
127 | // Grid properties
128 | grid: current,
129 | 'grid-gap': current,
130 | 'grid-row-gap': current,
131 | 'grid-column-gap': current,
132 | 'grid-template-rows': current,
133 | 'grid-template-columns': current,
134 | 'grid-auto-rows': current,
135 | 'grid-auto-columns': current,
136 |
137 | // Not existing properties.
138 | // Used to avoid issues with jss-plugin-expand integration.
139 | 'box-shadow-x': current,
140 | 'box-shadow-y': current,
141 | 'box-shadow-blur': current,
142 | 'box-shadow-spread': current,
143 | 'font-line-height': current,
144 | 'text-shadow-x': current,
145 | 'text-shadow-y': current,
146 | 'text-shadow-blur': current,
147 | };
148 |
149 |
150 | export default options;
151 |
--------------------------------------------------------------------------------
/src/utils/color.js:
--------------------------------------------------------------------------------
1 | import pipe from 'lodash/fp/pipe';
2 | import Color from 'color';
3 |
4 | const hexColorStrWithHeaderReg = /^#(([0-9a-z]{3})|([0-9a-z]{6}))$/i;
5 | const hexColorStrWithOutHeaderReg = /^(([0-9a-z]{3})|([0-9a-z]{6}))$/i;
6 | const rgbaColorStrReg = /^rgb(a)?\(([\d.]{1,3}(,)?(\s)?){3,4}\)$/i;
7 |
8 | /**
9 | * 校验、并过滤,16进制颜色的‘#’
10 | * @param {*} hexColorStr
11 | */
12 | function filterHexColorHeader(hexColorStr) {
13 | if (
14 | !hexColorStrWithHeaderReg.test(hexColorStr)
15 | && !hexColorStrWithOutHeaderReg.test(hexColorStr)
16 | ) {
17 | throw new Error('hex color format error!');
18 | }
19 | if (hexColorStrWithHeaderReg.test(hexColorStr)) {
20 | return hexColorStr.slice(1);
21 | }
22 | return hexColorStr;
23 | }
24 | function pad2(num) {
25 | let t = num.toString(16);
26 | if (t.length === 1) t = `0${t}`;
27 | return t;
28 | }
29 | function _toNum3(colorStr) {
30 | if (colorStr.length === 3) {
31 | colorStr = colorStr[0]
32 | + colorStr[0]
33 | + colorStr[1]
34 | + colorStr[1]
35 | + colorStr[2]
36 | + colorStr[2];
37 | }
38 | const r = parseInt(colorStr.slice(0, 2), 16);
39 | const g = parseInt(colorStr.slice(2, 4), 16);
40 | const b = parseInt(colorStr.slice(4, 6), 16);
41 | return [r, g, b];
42 | }
43 | const toNum3 = pipe(filterHexColorHeader, _toNum3);
44 |
45 | function mix(color1, color2, weight1, alpha1, alpha2) {
46 | color1 = color1.replace('#', '');
47 | color2 = color2.replace('#', '');
48 | if (weight1 === undefined) weight1 = 0.5;
49 | if (alpha1 === undefined) alpha1 = 1;
50 | if (alpha2 === undefined) alpha2 = 1;
51 |
52 | const w = 2 * weight1 - 1;
53 | const alphaDelta = alpha1 - alpha2;
54 | const w1 = ((w * alphaDelta === -1 ? w : (w + alphaDelta) / (1 + w * alphaDelta)) + 1) / 2;
55 | const w2 = 1 - w1;
56 |
57 | const nums1 = toNum3(color1);
58 | const nums2 = toNum3(color2);
59 | const r = Math.round(w1 * nums1[0] + w2 * nums2[0]);
60 | const g = Math.round(w1 * nums1[1] + w2 * nums2[1]);
61 | const b = Math.round(w1 * nums1[2] + w2 * nums2[2]);
62 | return `#${pad2(r)}${pad2(g)}${pad2(b)}`;
63 | }
64 |
65 | /**
66 | * 改变颜色深度
67 | * @param {*} colorStr 16进制格式的颜色字符串
68 | * @param {*} rate 比率0-1
69 | */
70 | function rgba(colorStr, rate) {
71 | const nums = toNum3(colorStr);
72 | let r = nums[0];
73 | let g = nums[1];
74 | let b = nums[2];
75 | return (
76 | (r = Math.round((1 - rate) * r)),
77 | (g = Math.round((1 - rate) * g)),
78 | (b = Math.round((1 - rate) * b)),
79 | (r = pad2(r)),
80 | (g = pad2(g)),
81 | (b = pad2(b)),
82 | `#${r}${g}${b}`
83 | );
84 | }
85 |
86 | /**
87 | * 转化16进制的颜色值为rgba的颜色值
88 | * 如果传入rgba格式的颜色,则直接返回,不作处理
89 | * @param {*} colorStr 16进制的颜色值 #fff、#ffffff
90 | * @param {*} opacity 透明度
91 | */
92 | function toRgbaColor(colorStr, opacity = 1) {
93 | if (typeof colorStr !== 'string') {
94 | throw new Error('color str should be string type');
95 | }
96 | if (rgbaColorStrReg.test(colorStr)) {
97 | const numPikcerReg = /(\d+,)/g;
98 | const rgbArr = colorStr.match(numPikcerReg).join('').split(',');
99 | rgbArr.length = 3;
100 | return `rgba(${rgbArr.join(',')}, ${opacity})`;
101 | }
102 | if (colorStr.startsWith('#')) {
103 | colorStr = colorStr.slice(1);
104 | }
105 | return `rgba(${toNum3(colorStr).join(',')}, ${opacity})`;
106 | }
107 |
108 | /**
109 | * 转换rgba格式的颜色值到16进制的颜色值
110 | * 如果是16进制的颜色,则直接返回
111 | * @param {*} rgbaColorStr rgba格式的颜色值
112 | * return 16进制的颜色值
113 | */
114 | function toHexColor(rgbaColorStr) {
115 | if (
116 | hexColorStrWithHeaderReg.test(rgbaColorStr)
117 | || hexColorStrWithOutHeaderReg.test(rgbaColorStr)
118 | ) {
119 | return rgbaColorStr;
120 | }
121 | if (!rgbaColorStrReg.test(rgbaColorStr)) {
122 | throw new Error('rgba color format error!');
123 | }
124 | const numPikcerReg = /(\d+,)/g;
125 | const rgbArr = rgbaColorStr.match(numPikcerReg).join('').split(',');
126 | const rate = 0;
127 | let r = rgbArr[0];
128 | let g = rgbArr[1];
129 | let b = rgbArr[2];
130 | return (
131 | (r = Math.round((1 - rate) * r)),
132 | (g = Math.round((1 - rate) * g)),
133 | (b = Math.round((1 - rate) * b)),
134 | (r = pad2(r)),
135 | (g = pad2(g)),
136 | (b = pad2(b)),
137 | `#${r}${g}${b}`
138 | );
139 | }
140 |
141 | /**
142 | * 淡化颜色
143 | * @param {*} colorStr 颜色值,支持16进制的颜色和rgba格式
144 | * @param {*} weight 淡化比重 0-1
145 | */
146 | function lighten(colorStr, weight) {
147 | colorStr = toHexColor(colorStr);
148 | return mix('fff', colorStr, weight);
149 | }
150 |
151 | /**
152 | * 加深颜色
153 | * @param {*} colorStr 颜色值,支持16进制的颜色和rgba格式
154 | * @param {*} weight 淡化比重 0-1
155 | */
156 | function darken(colorStr, weight) {
157 | colorStr = toHexColor(colorStr);
158 | return mix('000', colorStr, weight);
159 | }
160 |
161 |
162 | class _Color extends Color {
163 | lighten(ratio) {
164 | const oldValue = this.string();
165 | super.lighten(ratio);
166 | const newValue = this.string();
167 | if (oldValue === newValue) {
168 | return new _Color(lighten(oldValue, ratio));
169 | }
170 | return this;
171 | }
172 | }
173 |
174 | export default _Color;
175 |
176 | // export {
177 | // lighten, // 淡化
178 | // darken, // 加深
179 | // mix, // 混合
180 | // toNum3,
181 | // rgba,
182 | // toRgbaColor,
183 | // toHexColor,
184 | // pad2,
185 | // };
186 |
--------------------------------------------------------------------------------
/src/utils/devToolInit.ts:
--------------------------------------------------------------------------------
1 | (function devToolInit() {
2 | if (process.env.NODE_ENV !== 'development') {
3 | if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'object') {
4 | window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function none() {};
5 | }
6 | }
7 | }());
8 |
--------------------------------------------------------------------------------
/src/utils/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 |
3 |
4 | /* eslint-disable */
5 |
6 | export const useInit = (fn: Function) => {
7 | useState(fn);
8 | };
9 | export const useMounted = (fn: Function) => {
10 | useEffect(() => { fn(); }, []);
11 | };
12 | export const useUnMount = (fn: () => any) => {
13 | useEffect(() => fn, []);
14 | };
15 | export const useUpdated = (fn: Function) => {
16 | const isMounted = useRef(false);
17 | useEffect(() => {
18 | if (isMounted.current) {
19 | fn();
20 | }
21 | }, [fn]);
22 | useEffect(() => {
23 | if (isMounted.current === false) {
24 | isMounted.current = true;
25 | }
26 | return () => { isMounted.current = false; };
27 | }, []);
28 | };
29 |
30 | export const useInterval = (fn: () => any, delay:number) => {
31 | const cb = useRef(() => {});
32 | useEffect(() => {
33 | cb.current = fn;
34 | }, [fn]);
35 | useEffect(() => {
36 | const runCb = () => cb.current();
37 | const id = setInterval(runCb, delay);
38 | return () => clearInterval(id);
39 | }, [delay]);
40 | };
41 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import _cloneDeep from 'lodash/cloneDeep';
3 |
4 | // const _env = process.env.NODE_ENV;
5 |
6 | export const getUniqID = () => Math.random().toString(36).substr(2, 6);
7 |
8 | export const formatMoney = (money, n) => {
9 | if (!money || !(money = parseFloat(money))) {
10 | money = 0;
11 | }
12 | n = n > 0 && n <= 3 ? n : 2;
13 | money = money.toFixed(n);
14 | const l = money.split('.')[0].split('').reverse();
15 | const r = money.split('.')[1];
16 | let t = '';
17 | for (let i = 0; i < l.length; i++) {
18 | t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? ',' : '');
19 | }
20 | return `${t.split('').reverse().join('')}.${r}`;
21 | };
22 |
23 | export const unformatMoney = num => {
24 | if (!!num) {
25 | return num.replace(/,/g, '');
26 | }
27 | return num;
28 | };
29 |
30 | export const dateFormatting = (fmt, dateStr) => {
31 | if (!dateStr) return '';
32 | dateStr = dateStr.replace(new RegExp('-', 'g'), '\/');
33 | const date = new Date(dateStr);
34 | const o = {
35 | 'M+': date.getMonth() + 1, // 月份
36 | 'd+': date.getDate(), // 日
37 | 'h+': date.getHours(), // 小时
38 | 'm+': date.getMinutes(), // 分
39 | 's+': date.getSeconds(), // 秒
40 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
41 | 'S': date.getMilliseconds(), // 毫秒
42 | };
43 | if (/(y+)/.test(fmt)) {
44 | fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length));
45 | }
46 | for (const k in o) {
47 | if (new RegExp(`(${k})`).test(fmt)) {
48 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)));
49 | }
50 | }
51 | return fmt;
52 | };
53 |
54 |
55 | export const cloneDeep = _cloneDeep || (obj => JSON.parse(JSON.stringify(obj)));
56 |
57 | export const pipePromise = promiseArr => () => new Promise((resolve, reject) => {
58 | const promiseChain = promiseArr.reduce(
59 | (lastPromise, promise) => () => lastPromise().then(promise).catch(err => {
60 | reject(err);
61 | return Promise.reject(err);
62 | }),
63 | () => Promise.resolve(),
64 | );
65 | promiseChain().then(resolve);
66 | });
67 |
68 | export const composePromise = promiseArr => () => new Promise((resolve, reject) => {
69 | const promiseChain = promiseArr.reduceRight(
70 | (lastPromise, promise) => () => lastPromise().then(promise).catch(err => {
71 | reject(err);
72 | return Promise.reject(err);
73 | }),
74 | () => Promise.resolve(),
75 | );
76 | promiseChain().then(resolve);
77 | });
78 |
79 | if (Object.defineProperty) {
80 | Object.defineProperty(Promise, 'pipe', {
81 | value: pipePromise,
82 | enumerable: false,
83 | configurable: false,
84 | writable: false,
85 | });
86 | Object.defineProperty(Promise, 'compose', {
87 | value: composePromise,
88 | enumerable: false,
89 | configurable: false,
90 | writable: false,
91 | });
92 | } else {
93 | Promise.pipe = pipePromise;
94 | Promise.compose = composePromise;
95 | }
96 |
--------------------------------------------------------------------------------
/src/utils/ocr/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 图片元素进行压缩,返回base64
3 | * @param img
4 | * @returns {string}
5 | */
6 | export const compress = img => {
7 | let {width} = img;
8 | let {height} = img;
9 | const canvas = document.createElement('canvas');
10 | const ctx = canvas.getContext('2d');
11 | const tCanvas = document.createElement('canvas');
12 | const tctx = tCanvas.getContext('2d');
13 |
14 | // 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
15 | let ratio;
16 | if ((ratio = width * height / 5000000) > 1) {
17 | ratio = Math.sqrt(ratio);
18 | width /= ratio;
19 | height /= ratio;
20 | } else {
21 | ratio = 1;
22 | }
23 | canvas.width = width;
24 | canvas.height = height;
25 |
26 | // 铺底色
27 | ctx.fillStyle = '#fff';
28 | ctx.fillRect(0, 0, canvas.width, canvas.height);
29 |
30 | // 如果图片像素大于100万则使用瓦片绘制
31 | let count;
32 | if ((count = width * height / 1000000) > 1) {
33 | count = ~~(Math.sqrt(count) + 1); // 计算要分成多少块瓦片
34 |
35 | // 计算每块瓦片的宽和高
36 | const nw = ~~(width / count);
37 | const nh = ~~(height / count);
38 | tCanvas.width = nw;
39 | tCanvas.height = nh;
40 |
41 | for (let i = 0; i < count; i++) {
42 | for (let j = 0; j < count; j++) {
43 | tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
44 |
45 | ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
46 | }
47 | }
48 | } else {
49 | ctx.drawImage(img, 0, 0, width, height);
50 | }
51 | // 进行最小压缩
52 | const ndata = canvas.toDataURL('image/jpeg', 0.8);
53 | tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
54 | return ndata;
55 | };
56 |
57 | /**
58 | * 把base64字符串转换成blob二进制流
59 | * @param baseStr
60 | * @param type
61 | * @returns {*}
62 | */
63 | export const getBlob = (baseStr, type) => {
64 | let blob;
65 | try {
66 | const text = window.atob(baseStr.split(',')[1]);
67 | const buffer = new ArrayBuffer(text.length);
68 | const ubuffer = new Uint8Array(buffer);
69 |
70 |
71 | for (let i = 0; i < text.length; i++) {
72 | ubuffer[i] = text.charCodeAt(i);
73 | }
74 |
75 | const Builder = window.WebKitBlobBuilder || window.MozBlobBuilder;
76 |
77 |
78 | if (Builder) {
79 | const builder = new Builder();
80 | builder.append(buffer);
81 | blob = builder.getBlob(type);
82 | } else {
83 | blob = new window.Blob([buffer], {type});
84 | }
85 | } catch (e) {
86 | // alert(e)
87 | }
88 | return blob;
89 | };
90 |
--------------------------------------------------------------------------------
/src/utils/regExps.ts:
--------------------------------------------------------------------------------
1 | export const numberReg = /^\d*$/;
2 |
3 | // 模块懒加载失败报错
4 | export const chunkLoadingErrorReg = /^loading chunk [^]+? failed\.$/i;
5 |
--------------------------------------------------------------------------------
/src/utils/styles/base.scss:
--------------------------------------------------------------------------------
1 | html {
2 | -webkit-text-size-adjust: none;
3 | }
4 |
5 | body,
6 | button,
7 | input,
8 | select,
9 | textarea {
10 | font: normal 14px/1.5 'Hiragino Sans GB', 'Microsoft YaHei', 'hei', Arial, 'Lucida Grande', Verdana;
11 | }
12 |
13 | body,
14 | div,
15 | dl,
16 | dt,
17 | dd,
18 | ul,
19 | ol,
20 | li,
21 | h1,
22 | h2,
23 | h3,
24 | h4,
25 | h5,
26 | h6,
27 | pre,
28 | code,
29 | form,
30 | fieldset,
31 | legend,
32 | input,
33 | textarea,
34 | p,
35 | blockquote,
36 | th,
37 | td,
38 | hr,
39 | button,
40 | article,
41 | aside,
42 | details,
43 | figcaption,
44 | figure,
45 | footer,
46 | header,
47 | hgroup,
48 | menu,
49 | nav,
50 | section {
51 | padding: 0;
52 | margin: 0;
53 | }
54 |
55 | /* base.css start */
56 |
57 | ul {
58 | list-style: none;
59 | }
60 |
61 | th {
62 | text-align: left;
63 | }
64 |
65 | h1,
66 | h2,
67 | h3,
68 | h4,
69 | h5,
70 | h6 {
71 | font-weight: normal;
72 | font-size: 100%;
73 | }
74 |
75 | q:before,
76 | q:after {
77 | content: ' ';
78 | }
79 |
80 | abbr,
81 | acronym {
82 | border: 0;
83 | }
84 |
85 | label >* {
86 | pointer-events: none;
87 | }
88 |
89 | /* base.css end */
90 | * {
91 | -webkit-font-smoothing: antialiased;
92 | }
93 | body {
94 | min-width: 320px;
95 | color: #000;
96 | background: #f8f8f8;
97 | -webkit-font-smoothing: antialiased;
98 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
99 | }
100 |
101 | img {
102 | display: block;
103 | width: 100%;
104 | }
105 |
106 | input {
107 | -moz-box-sizing: border-box;
108 | -webkit-box-sizing: border-box;
109 | box-sizing: border-box;
110 | width: 100%;
111 | border: 0;
112 | border-radius: 0;
113 | outline: none;
114 | background: transparent;
115 | -webkit-appearance: none;
116 | }
117 |
118 | select {
119 | -moz-box-sizing: border-box;
120 | -webkit-box-sizing: border-box;
121 | box-sizing: border-box;
122 | width: 100%;
123 | border: 0;
124 | border-radius: 0;
125 | background: transparent;
126 | -webkit-appearance: none;
127 | }
128 |
129 | h1 {
130 | font: 600 1.286em/2 Tahoma;
131 | }
132 |
133 | /* ul {
134 | list-style: none;
135 | } */
136 |
137 | a {
138 | text-decoration: none;
139 | color: #000;
140 | }
141 |
142 | em,
143 | b,
144 | i {
145 | font-style: normal;
146 | font-weight: normal;
147 | }
148 |
--------------------------------------------------------------------------------
/src/utils/styles/func.scss:
--------------------------------------------------------------------------------
1 | // $ratio: 750/640;
2 | // $browser-default-font-size: 14;
3 | // $browser-default-font-size-px: 14px !default;
4 |
5 | // @function r($px, $isFixed:true) {
6 | // @if $px != 0 {
7 | // @return if($isFixed, $px / $ratio + 0.01, $px / $ratio) / $browser-default-font-size * 1rem;
8 | // }
9 | // @else {
10 | // @return 0;
11 | // }
12 | // }
13 |
14 | // @function r2($px, $isFixed:true) {
15 | // @return r($px/2, $isFixed);
16 | // }
17 |
18 | @function r($px) {
19 | @if $px != 0 {
20 | @return $px / 750 * 100vw;
21 | }
22 | @else {
23 | @return 0;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import 'base';
2 | @import 'func';
3 | @import 'mixin';
4 |
--------------------------------------------------------------------------------
/src/utils/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @import 'func';
2 |
3 | @mixin retinaImg($imgName,$width,$height) {
4 | width: r($width);
5 | height: r($height);
6 | background-image: url('../../images/' + $imgName + '@2x.png');
7 | background-size: r($width) r($height);
8 | background-repeat: no-repeat;
9 | @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
10 | background-image: url('../../images/' + $imgName + '@3x.png');
11 | }
12 | }
13 |
14 | //@mixin border-1px($borderKind:top,$borderColor:#979797) {
15 | // &::after {
16 | // position: absolute;
17 | // left: 0;
18 | // top: 0;
19 | // content: '';
20 | // width: 100%;
21 | // border-#{$borderKind}: r(1) solid $borderColor;
22 | // @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
23 | // transform: scaleY(0.5);
24 | // }
25 | // @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
26 | // transform: scaleY(0.33);
27 | // }
28 | // }
29 | //}
30 |
31 | // 单边1px方法
32 | @mixin border-1px($borderKind:top,$borderColor:#979797) {
33 | border-#{$borderKind}: 1px solid $borderColor;
34 | @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
35 | border-#{$borderKind}: 0.5px solid $borderColor;
36 | }
37 | @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
38 | border-#{$borderKind}: 0.3px solid $borderColor;
39 | }
40 | }
41 |
42 | // 全边1px方法
43 | @mixin border-1px-all($borderColor:#979797) {
44 | border: 1px solid $borderColor;
45 | @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
46 | border: 0.5px solid $borderColor;
47 | }
48 | @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
49 | border: 0.3px solid $borderColor;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/utils/validator.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/src/utils/validator.ts
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
7 | "lib": ["esnext", "dom", "dom.iterable"], /* Specify library files to be included in the compilation. */
8 | "allowJs": true, /* Allow javascript files to be compiled. */
9 | "checkJs": false, /* Report errors in .js files. */
10 | "skipLibCheck": true,
11 | "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "declarationDir": "./lib/@types/react-store",
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./lib/react-store", /* Concatenate and emit output to single file. */
17 | "outDir": "./dist/common/src/", /* Redirect output structure to the directory. */
18 | // "rootDir": "./src/react-store", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | "noEmit": false, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | "isolatedModules": false, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | "resolveJsonModule": true,
43 | /* Module Resolution Options */
44 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
45 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
46 | "paths": {
47 | "@channel": ["buildConfig/channel/common/index.ts"],
48 | "@channel/*": ["buildConfig/channel/common/*"],
49 | "@history": ["src/routes/history.ts"],
50 | // "@": ["src"],
51 | "@/*": ["src/*"],
52 | "@base/*": ["src/components/base/*"],
53 | "@biz/*": ["src/components/business/*"],
54 | "@redux-devtool": ["src/store/common/redux.devtool.ts"],
55 | }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
56 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
57 | "typeRoots": [
58 | "node_modules/@types",
59 | "typings"
60 | ], /* List of folders to include type definitions from. */
61 | // "types": [], /* Type declaration files to be included in compilation. */
62 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
63 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
64 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
65 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
66 |
67 | /* Source Map Options */
68 | "sourceRoot": "./dist/common/src/", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
69 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
70 | "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
71 | "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
72 |
73 | /* Experimental Options */
74 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */
75 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
76 | },
77 | "exclude": [
78 | "node_modules",
79 | "@channel",
80 | "scripts",
81 | "createPage",
82 | "mock-server"
83 | ],
84 | "include": [
85 | "src",
86 | "typings"
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/typings/custom-typings.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare module '*.json' {
3 | const value: any;
4 | export default value;
5 | }
6 |
7 | declare module 'json!*' {
8 | const value: any;
9 | export default value;
10 | }
11 |
12 | interface Window {
13 | /**
14 | * 浏览器的react调试工具插件
15 | */
16 | __REACT_DEVTOOLS_GLOBAL_HOOK__?: {
17 | inject: Function
18 | };
19 | /**
20 | * 浏览器的redux调试工具插件
21 | */
22 | __REDUX_DEVTOOLS_EXTENSION__?: Function;
23 | }
24 |
--------------------------------------------------------------------------------
/typings/font.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.woff2';
2 | declare module '*.eot';
3 | declare module '*.ttf';
4 | declare module '*.otf';
5 |
--------------------------------------------------------------------------------
/typings/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg';
2 | declare module '*.png';
3 | declare module '*.jpg';
4 | declare module '*.jpeg';
5 | declare module '*.gif';
6 | declare module '*.bmp';
7 | declare module '*.tiff';
8 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/empty916/react-app/e2004483b9030168c7b3ba97c4858fa5061a191f/typings/index.d.ts
--------------------------------------------------------------------------------
/typings/style.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.scss';
3 | declare module '*.sass';
4 |
--------------------------------------------------------------------------------