├── .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 | Nest Logo 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 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 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 |
148 | 149 | {formItems.map(this.createFormItemByConfig)} 150 | {this.getChildren()} 151 | 152 |
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 | 23 | 33 | 34 | 39 | {loading.state.loadingText} 40 | 41 | 42 | 43 | 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 | --------------------------------------------------------------------------------