├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── asset ├── css │ ├── app.less │ ├── base.less │ ├── icon.less │ ├── reset.less │ ├── variables.less │ └── vendor_antd.less └── img │ ├── avatar.png │ └── logo.svg ├── build ├── env.js ├── postcss.config.js ├── publicPath.js ├── theme.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── favicon.ico ├── index.html ├── mock ├── config │ └── mockConfig.json ├── data │ └── skuunit │ │ ├── deleteSkuUnit.json │ │ ├── showSkuUnitList.json │ │ └── updateSkuUnitPause.json └── template │ └── query │ └── table.template ├── package.json ├── src ├── app.jsx ├── common │ └── request.js ├── components │ ├── ErrorBoundary.jsx │ ├── Layout │ │ ├── Footer.jsx │ │ ├── Header.jsx │ │ ├── Sider.jsx │ │ └── index.css │ ├── Pages │ │ ├── index.css │ │ └── index.jsx │ └── index.jsx ├── constants │ ├── constant.js │ └── url.js ├── redux │ ├── configureStore.js │ ├── reducers.js │ └── skuunit │ │ ├── api.js │ │ └── skuunit.js └── routers │ ├── PrimaryLayout.jsx │ ├── Skuunit │ ├── columns.jsx │ └── index.jsx │ ├── index.jsx │ └── index.less ├── template.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-proposal-decorators", 9 | { 10 | "legacy": true 11 | } 12 | ], 13 | "@babel/plugin-proposal-class-properties", 14 | "@babel/plugin-proposal-export-default-from", 15 | "@babel/plugin-transform-runtime", 16 | "@babel/plugin-syntax-dynamic-import", 17 | "react-hot-loader/babel", 18 | [ 19 | "import", 20 | { 21 | "libraryName": "antd", 22 | "libraryDirectory": "es", 23 | "style": true 24 | } 25 | ], 26 | [ 27 | "transform-imports", 28 | { 29 | "react-router": { 30 | "transform": "react-router/${member}", 31 | "preventFullImport": true 32 | } 33 | } 34 | ] 35 | ], 36 | "env": { 37 | "production": { 38 | "plugins": [ 39 | [ 40 | "transform-react-remove-prop-types", 41 | { 42 | "mode": "wrap", 43 | "ignoreFilenames": [ 44 | "node_modules" 45 | ] 46 | } 47 | ] 48 | ] 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "es6": true, 6 | "commonjs": true, 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 6, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "modules": true, 15 | "experimentalObjectRestSpread": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react", 20 | "react-hooks" 21 | ], 22 | "rules": { 23 | "react-hooks/rules-of-hooks": "error", 24 | "react-hooks/exhaustive-deps": "warn", 25 | // 推荐规则 0="off", 1="warn", 2="error" 26 | "no-compare-neg-zero": 2, 27 | "no-cond-assign": 2, 28 | "no-console": 0,// 不采用 构建时自己去除 29 | "no-constant-condition": 2,// 采用 30 | "no-control-regex": 2,// 采用 31 | "no-debugger": 0,// node端采用,web端不采用 32 | "no-dupe-args": 2, // 采用 33 | "no-dupe-keys": 2,// 采用 34 | "no-duplicate-case": 2,// 采用 35 | "no-empty": 2, // 采用 36 | "no-empty-character-class": 2,// 采用 37 | "no-ex-assign": 2, // 采用 38 | "no-extra-boolean-cast": 2, // 采用 39 | "no-extra-parens": 0, // 不采用 40 | "no-extra-semi": 2,// 采用 41 | "no-func-assign": 2,// 采用 42 | "no-inner-declarations": 2, // 采用 43 | "no-invalid-regexp": 2,// 采用 44 | "no-irregular-whitespace": 2,// 采用 45 | "no-obj-calls": 2,// 采用 46 | "no-regex-spaces": 2,// 采用 47 | "no-sparse-arrays": 2,// 采用 48 | "no-unexpected-multiline": 2,// 采用 49 | "no-unreachable": 2,// 采用 50 | "no-unsafe-finally": 2,// 采用 51 | "no-unsafe-negation": 2,// 采用 52 | "use-isnan": 2,// 采用 53 | "valid-typeof": 2,// 采用 54 | "no-case-declarations": 2, // 采用 55 | "no-empty-pattern": 2,// 采用 56 | "no-fallthrough": 2,// 采用 57 | "no-global-assign": 2,// 采用 58 | "no-octal": 2,// 采用 59 | "no-redeclare": 2,// 采用 60 | "no-self-assign": 2,// 采用 61 | "no-unused-labels": 2,// 采用 62 | "no-useless-escape": 2,// 采用 63 | "no-delete-var": 2,// 采用 64 | "no-undef": 2,// 采用 65 | "no-unused-vars": 2,// 采用 66 | "no-mixed-spaces-and-tabs": 2,// 采用 67 | "constructor-super": 2,// 采用 68 | "no-class-assign": 2,// 采用 69 | "no-const-assign": 2,// 采用 70 | "no-dupe-class-members": 2,// 采用 71 | "no-new-symbol": 2,// 采用 72 | "no-this-before-super": 2,// 采用 73 | "require-yield": 2,// 采用 74 | 75 | // 拓展规则 76 | "for-direction": 2,// 采用 77 | "getter-return": 2,// 采用 78 | "no-await-in-loop": 2,// 采用 79 | "no-prototype-builtins": 2,// 采用 80 | "no-template-curly-in-string": 2,// 采用 81 | // valid-jsdoc 不采用,基础组件强制使用 82 | "accessor-pairs": 2,// 采用 83 | "array-callback-return": 2,// 采用 84 | "block-scoped-var": 2,// 采用 85 | "class-methods-use-this": 1,//采用(warning,可能报错很多) 86 | "curly": 1,// 采用 warning 87 | "default-case": 2,// 采用 88 | "eqeqeq": 1,// 采用 warning 89 | "guard-for-in": 2,// 采用 90 | "no-caller": 2, // 采用 91 | "no-eval": 2,// 采用 92 | "no-extend-native": 2,// 采用 93 | "no-extra-label": 2,// 采用 94 | "no-floating-decimal": 2,// 采用 95 | "no-implied-eval": 2,// 采用 96 | // 临时关闭,有误报 97 | "no-invalid-this": 0,// 采用 98 | "no-iterator": 2,// 采用 99 | "no-labels": 2,// 采用 100 | "no-lone-blocks": 2,// 采用 101 | "no-throw-literal": 2,// 采用 102 | "no-unmodified-loop-condition": 1,// 采用 warning 103 | "no-useless-concat": 2,// 采用 104 | "no-useless-return": 2,// 采用 105 | "radix": 2,// 采用 106 | "require-await": 2,// 采用 107 | 108 | //关于Node.js或在浏览器中使用CommonJS的相关规则 109 | "callback-return": 2,// 采用 110 | 111 | // 临时关闭 112 | "global-require": 0,// 采用 113 | "handle-callback-err": 2,// 采用 114 | "no-buffer-constructor": 2,// 采用 115 | "no-mixed-requires": 2,// 采用 116 | "no-new-require": 2,// 采用 117 | 118 | // 代码风格 119 | // 在数组开括号后和闭括号前强制换行 120 | "array-bracket-newline": 0, // 不采用 121 | // 禁止或强制在括号内使用空格 122 | "array-bracket-spacing": [2, "never"], // 采用,禁止在数组括号内出现空格, 123 | // 强制数组元素间出现换行 124 | "array-element-newline": 0, // 不采用 125 | // 禁止或强制在代码块中开括号前和闭括号后有空格 126 | "block-spacing": [2, "always"], // 采用,要求使用一个或多个空格 127 | // 强制在代码块中使用一致的大括号风格 128 | "brace-style": [2, "1tbs", { allowSingleLine: true }], // 强制 one true brace style(一种代码风格,将大括号放在控制语句或声明语句同一行的位置) 129 | // 可以有例外情况, allowSingleLine允许块的开括号和闭括号在 同一行 130 | // 要求使用骆驼拼写法 131 | "camelcase": [2, { properties: "never" }], // 采用,但不检查属性名称 132 | // 强制或禁止对注释的第一个字母大写 133 | "capitalized-comments": 0, // 不采用 134 | // 要求或禁止使用拖尾逗号 135 | "comma-dangle": [2, { 136 | arrays: "always-multiline", 137 | objects: "always-multiline", 138 | imports: "always-multiline", 139 | exports: "always-multiline", 140 | functions: "always-multiline", 141 | }], // 采用,当最后一个元素或属性与闭括号 ] 或 } 在 不同的行时,要求使用拖尾逗号;当在 同一行时,禁止使用拖尾逗号。 142 | // 强制在逗号周围使用空格 143 | "comma-spacing": [2, { before: false, after: true }], // 采用,禁止在逗号前使用空格,要求在逗号后使用一个或多个空格 144 | // 逗号风格 145 | "comma-style": [2, "last", { // last 要求逗号放在数组元素、对象属性或变量声明之后,且在同一行 146 | exceptions: { // 额外规则 包含与 JavaScript 代码的抽象语法树 (AST) 的节点类型对应的属性: 147 | ArrayExpression: false, // 忽略数组字面量的逗号风格 148 | ArrayPattern: false, // 忽略数组的解构赋值语句中的逗号风格 149 | ArrowFunctionExpression: false, // 忽略箭头函数表达式的参数中的逗号风格 150 | CallExpression: false, // 忽略函数调用的参数中的逗号风格 151 | FunctionDeclaration: false, // 忽略函数声明的参数中的逗号风格 152 | FunctionExpression: false, // 忽略函数表达式的参数中的逗号风格 153 | ImportDeclaration: false, // 忽略 import 语句中的逗号风格 154 | ObjectExpression: false, // 忽略对象字面量的逗号风格 155 | ObjectPattern: false, // 忽略对象的解构赋值中的逗号风格 156 | VariableDeclaration: false, // 忽略变量声明的逗号风格 157 | NewExpression: false, // 忽略构造函数表达式参数中的逗号风格 158 | } // !!! 注意,以上配置全为false,即不忽略 159 | }], // 采用 160 | // 禁止或强制在计算属性中使用空格 161 | "computed-property-spacing": [2, 'never'], // 采用,禁止在计算属性内使用空格 162 | // 要求一致的 This 163 | "consistent-this": 0, // 不采用 164 | // 要求或禁止文件末尾保留一行空行 165 | "eol-last": [2, 'always'], // 采用 强制使用换行 (LF) 166 | // 要求或禁止在函数标识符和其调用之间有空格 167 | "func-call-spacing": [2, 'never'], // 禁止在函数名和开括号之间有空格 168 | // 要求函数名与赋值给它们的变量名或属性名相匹配 169 | "func-name-matching": 0, // 不采用 170 | // 要求或禁止命名的 function 表达式 171 | "func-names": 1, // 警告 172 | // 强制 function 声明或表达式的一致性 173 | "func-style": 0, // 不采用 174 | // 强制在函数括号内使用一致的换行 175 | "function-paren-newline": [2, 'consistent'], // 采用, 要求每个括号使用一致的换行。如果一个括号有换行,另一个括号没有换行,则报错。 176 | // 禁止使用指定的标识符 177 | "id-blacklist": 0, // 不采用 178 | // 强制标识符的最小和最大长度 179 | "id-length": 0, // 不采用 180 | // 要求标识符匹配一个指定的正则表达式 181 | "id-match": 0, // 不采用 182 | // 强制隐式返回的箭头函数体的位置 183 | "implicit-arrow-linebreak": [2, 'beside'], // 采用 禁止在箭头函数体之前出现换行 184 | // 强制使用一致的缩进 185 | "indent": [2, 2, { 186 | SwitchCase: 1, 187 | VariableDeclarator: 1, 188 | outerIIFEBody: 1, 189 | // MemberExpression: null, 190 | FunctionDeclaration: { 191 | parameters: 1, 192 | body: 1 193 | }, 194 | FunctionExpression: { 195 | parameters: 1, 196 | body: 1 197 | }, 198 | CallExpression: { 199 | arguments: 1 200 | }, 201 | ArrayExpression: 1, 202 | ObjectExpression: 1, 203 | ImportDeclaration: 1, 204 | flatTernaryExpressions: false, 205 | // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js 206 | ignoredNodes: ['JSXElement', 'JSXElement > *', 'JSXAttribute', 'JSXIdentifier', 'JSXNamespacedName', 'JSXMemberExpression', 'JSXSpreadAttribute', 'JSXExpressionContainer', 'JSXOpeningElement', 'JSXClosingElement', 'JSXText', 'JSXEmptyExpression', 'JSXSpreadChild'], 207 | ignoreComments: false 208 | }], // 采用 一般2个缩进,特殊语句见配置 209 | // 强制在 JSX 属性中一致地使用双引号或单引号 210 | "jsx-quotes": 0, // 不采用 211 | // 强制在对象字面量的属性中键和值之间使用一致的间距 212 | "key-spacing": [2, { beforeColon: false, afterColon: true }], // 采用, 禁止在对象字面量的键和冒号之间存在空格,要求在对象字面量的冒号和值之间存在至少有一个空格 213 | // 强制在关键字前后使用一致的空格 214 | "keyword-spacing": [2, { 215 | before: true, // 要求在关键字之前至少有一个空格 216 | after: true, // 要求在关键字之后至少有一个空格 217 | overrides: { // 允许覆盖指定的关键字的空格风格 218 | return: { after: true }, 219 | throw: { after: true }, 220 | case: { after: true } 221 | } 222 | }], // 采用 223 | // 强制行注释的位置 224 | "line-comment-position": 0, // 不采用 225 | // 强制使用一致的换行风格 226 | "linebreak-style": [2, 'unix'], // 采用,强制使用 Unix 换行符: \n。 227 | // 要求在注释周围有空行 228 | "lines-around-comment": 0, //不采用 229 | // 要求或禁止类成员之间出现空行 230 | "lines-between-class-members": [2, 'always', { exceptAfterSingleLine: false }],// 采用 231 | // 强制可嵌套的块的最大深度 232 | "max-depth": 0, // 不采用 233 | // 强制一行的最大长度 234 | "max-len": [2, 100, 2, { 235 | ignoreUrls: true, 236 | ignoreComments: false, 237 | ignoreRegExpLiterals: true, 238 | ignoreStrings: true, 239 | ignoreTemplateLiterals: true, 240 | }],// 采用,最长100,tab字符宽度为2 241 | // 强制最大行数 242 | "max-lines": 0, // 不采用 243 | // 强制回调函数最大嵌套深度 244 | "max-nested-callbacks": 0, // 不采用 245 | // 强制函数定义中最多允许的参数数量 246 | "max-params": 0, //不采用 247 | // 强制函数块最多允许的的语句数量 248 | "max-statements": 0, // 不采用 249 | // 强制每一行中所允许的最大语句数量 250 | "max-statements-per-line": 0, // 不采用 251 | // 强制对多行注释使用特定风格 252 | "multiline-comment-style": 0, // 不采用 253 | // 要求或禁止在三元操作数中间换行 254 | "multiline-ternary": 0, // 不采用 255 | // 要求构造函数首字母大写 256 | "new-cap": [2, { 257 | newIsCap: true, // 要求调用 new 操作符时有首字母大小的函数 258 | newIsCapExceptions: [], 259 | capIsNew: false, // 要求调用首字母大写的函数时有 new 操作符 260 | capIsNewExceptions: ['Immutable.Map', 'Immutable.Set', 'Immutable.List'], // 允许调用指定的首字母大写的函数时没有 new 操作符 261 | }], // 采用 262 | // 要求调用无参构造函数时有圆括号 263 | "new-parens": 2, // 采用 264 | // 要求方法链中每个调用都有一个换行符 265 | "newline-per-chained-call": [2, { ignoreChainWithDepth: 4 }], // 允许在同一行成链的深度为4 266 | // 禁用 Array 构造函数 267 | "no-array-constructor": 2, // 采用 268 | // 禁用按位运算符 269 | "no-bitwise": 2, // 采用 270 | // 禁用 continue 语句 271 | "no-continue": 2, // 采用 272 | // 禁止在代码后使用内联注释 273 | "no-inline-comments": 0, // 不采用 274 | // 禁止 if 作为唯一的语句出现在 else 语句中 275 | "no-lonely-if": 2, // 采用 276 | // 禁止混合使用不同的操作符 277 | "no-mixed-operators": [2, { 278 | groups: [ 279 | ['%', '**'], 280 | ['%', '+'], 281 | ['%', '-'], 282 | ['%', '*'], 283 | ['%', '/'], 284 | ['**', '+'], 285 | ['**', '-'], 286 | ['**', '*'], 287 | ['**', '/'], 288 | ['&', '|', '^', '~', '<<', '>>', '>>>'], 289 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='], 290 | ['&&', '||'], 291 | ['in', 'instanceof'] 292 | ], 293 | allowSamePrecedence: false 294 | }], // 采用 295 | // 禁止连续赋值 296 | "no-multi-assign": 2, // 采用 297 | // 禁止出现多行空行 298 | "no-multiple-empty-lines": [2, { max: 2, maxEOF: 0 }], // 采用 299 | // 禁用否定的表达式 300 | "no-negated-condition": 0, // 不采用 301 | // 禁用嵌套的三元表达式 302 | "no-nested-ternary": 2, // 采用 303 | // 禁用 Object 的构造函数 304 | "no-new-object": 2, // 采用 305 | // 禁用一元操作符 ++ 和 -- 306 | // 临时关闭 307 | "no-plusplus": 0, // 采用 308 | // 禁用特定的语法 309 | "no-restricted-syntax": [ 310 | 2, 311 | { 312 | selector: 'ForInStatement', 313 | message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.', 314 | }, 315 | { 316 | selector: 'ForOfStatement', 317 | message: 'iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.', 318 | }, 319 | { 320 | selector: 'LabeledStatement', 321 | message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', 322 | }, 323 | { 324 | selector: 'WithStatement', 325 | message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', 326 | }, 327 | ], // 采用 328 | // 禁用 tab 329 | "no-tabs": 2, // 采用 330 | // 禁用三元操作符 331 | "no-ternary": 0, //不采用 332 | // 禁用行尾空格 333 | "no-trailing-spaces": [2, { 334 | skipBlankLines: false, 335 | ignoreComments: false, 336 | }], // 采用 337 | // 禁止标识符中有悬空下划线(临时关闭) 338 | /* "no-underscore-dangle": [2, { 339 | allow: [], 340 | allowAfterThis: false, 341 | allowAfterSuper: false, 342 | enforceInMethodNames: false, 343 | }], */ // 采用 344 | // 禁止可以在有更简单的可替代的表达式时使用三元操作符 345 | "no-unneeded-ternary": [2, { defaultAssignment: false }], 346 | // 禁止属性前有空白 347 | "no-whitespace-before-property": 2, //采用 348 | // 强制单个语句的位置 349 | "nonblock-statement-body-position": [2, 'beside', { overrides: {} }], // 采用 350 | // 强制大括号内换行符的一致性 351 | "object-curly-newline": [2, { 352 | ObjectExpression: { minProperties: 4, multiline: true, consistent: true }, 353 | ObjectPattern: { minProperties: 4, multiline: true, consistent: true }, 354 | ImportDeclaration: { minProperties: 4, multiline: true, consistent: true }, 355 | ExportDeclaration: { minProperties: 4, multiline: true, consistent: true }, 356 | }],// 采用 357 | // 强制在大括号中使用一致的空格 358 | "object-curly-spacing": [2, 'always'], // 采用 359 | // 强制将对象的属性放在不同的行上 360 | "object-property-newline": [2, { 361 | allowAllPropertiesOnSameLine: true, 362 | }], 363 | // 强制函数中的变量要么一起声明要么分开声明 364 | "one-var": [2, 'never'], // 采用, 要求每个作用域有多个变量声明 365 | // 要求或禁止在变量声明周围换行 366 | "one-var-declaration-per-line": [2, 'always'], // 采用,强制每个变量声明都换行 367 | // 要求或禁止在可能的情况下使用简化的赋值操作符 368 | "operator-assignment": [2, 'always'], //采用,要求尽可能地简化赋值操作 369 | // 强制操作符使用一致的换行符 370 | "operator-linebreak": [2, 'before', { overrides: { '=': 'none' } }], // 采用, 要求把换行符放在操作符前面 371 | // 要求或禁止块内填充 372 | "padded-blocks": [2, { blocks: 'never', classes: 'never', switches: 'never' }], // 采用 373 | // 要求或禁止在语句间填充空行 374 | "padding-line-between-statements": 0, // 不采用 375 | // 要求对象字面量属性名称用引号括起来 376 | "quote-props": [2, 'as-needed', { keywords: false, unnecessary: true, numbers: false }], //采用 377 | // 强制使用一致的反勾号、双引号或单引号 378 | "quotes": [2, 'single', { avoidEscape: true }], // 采用 379 | // 要求使用 JSDoc 注释 380 | "require-jsdoc": 0, // 不采用 381 | // 要求或禁止使用分号代替 ASI 382 | "semi": [2, 'always'], // 采用,要求在语句末尾使用分号 383 | // 强制分号之前和之后使用一致的空格 384 | "semi-spacing": ['error', { before: false, after: true }], // 采用 385 | // 强制分号的位置 386 | "semi-style": [2, 'last'], // 采用,强制分号出现在句子末尾。 387 | // 要求对象属性按序排列 388 | "sort-keys": 0, // 不采用 389 | // 要求同一个声明块中的变量按顺序排列 390 | "sort-vars": 0, // 不采用 391 | // 强制在块之前使用一致的空格 392 | "space-before-blocks": 2, // 采用 393 | // 强制在 function的左括号之前使用一致的空格 394 | "space-before-function-paren": [2, { 395 | anonymous: 'always', 396 | named: 'never', 397 | asyncArrow: 'always' 398 | }], // 采用 399 | // 强制在圆括号内使用一致的空格 400 | "space-in-parens": [2, 'never'], // 采用,强制圆括号内没有空格 401 | // 要求操作符周围有空格 402 | "space-infix-ops": 2, // 采用 403 | // 强制在一元操作符前后使用一致的空格 404 | "space-unary-ops": [2, { 405 | words: true, 406 | nonwords: false, 407 | overrides: { 408 | }, 409 | }], // 采用 410 | // 强制在注释中 // 或 /* 使用一致的空格 411 | "spaced-comment": [2, 'always', { 412 | line: { 413 | exceptions: ['-', '+'], 414 | markers: ['=', '!'], // space here to support sprockets directives 415 | }, 416 | block: { 417 | exceptions: ['-', '+'], 418 | markers: ['=', '!'], // space here to support sprockets directives 419 | balanced: true, 420 | } 421 | }], // 采用 422 | // 强制在 switch 的冒号左右有空格 423 | "switch-colon-spacing": [2, { after: true, before: false }], // 采用 424 | // 要求或禁止在模板标记和它们的字面量之间有空格 425 | "template-tag-spacing": [2, 'never'], // 禁止在一个标记的函数和它的模板字面量之间有空格 426 | // 要求或禁止 Unicode 字节顺序标记 (BOM) 427 | "unicode-bom": [2, 'never'], // 采用 428 | // 要求正则表达式被括号括起来 429 | "wrap-regex": 0, // 不采用 430 | 431 | 432 | // react相关配置 433 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 434 | 'react/display-name': ['off', { ignoreTranspilerName: false }], 435 | 436 | // Forbid certain propTypes (any, array, object) 437 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-prop-types.md 438 | 'react/forbid-prop-types': ['error', { 439 | forbid: ['any', 'array', 'object'], 440 | checkContextTypes: true, 441 | checkChildContextTypes: true, 442 | }], 443 | 444 | // Forbid certain props on DOM Nodes 445 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-dom-props.md 446 | 'react/forbid-dom-props': ['off', { forbid: [] }], 447 | 448 | // Enforce boolean attributes notation in JSX 449 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 450 | 'react/jsx-boolean-value': ['error', 'never', { always: [] }], 451 | 452 | // Validate closing bracket location in JSX 453 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md 454 | 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], 455 | 456 | // Validate closing tag location in JSX 457 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md 458 | 'react/jsx-closing-tag-location': 'error', 459 | 460 | // Enforce or disallow spaces inside of curly braces in JSX attributes 461 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md 462 | 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }], 463 | 464 | // Enforce event handler naming conventions in JSX 465 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md 466 | 'react/jsx-handler-names': ['off', { 467 | eventHandlerPrefix: 'handle', 468 | eventHandlerPropPrefix: 'on', 469 | }], 470 | 471 | // Validate props indentation in JSX 472 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md 473 | 'react/jsx-indent-props': ['error', 2], 474 | 475 | // Validate JSX has key prop when in array or iterator 476 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md 477 | 'react/jsx-key': 'off', 478 | 479 | // Limit maximum of props on a single line in JSX 480 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md 481 | 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], 482 | 483 | // Prevent usage of .bind() in JSX props 484 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md 485 | 'react/jsx-no-bind': ['error', { 486 | ignoreRefs: true, 487 | allowArrowFunctions: true, 488 | allowBind: false, 489 | }], 490 | 491 | // Prevent duplicate props in JSX 492 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md 493 | 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], 494 | 495 | // Prevent usage of unwrapped JSX strings 496 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md 497 | 'react/jsx-no-literals': ['off', { noStrings: true }], 498 | 499 | // Disallow undeclared variables in JSX 500 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 501 | 'react/jsx-no-undef': 'error', 502 | 503 | // Enforce PascalCase for user-defined JSX components 504 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md 505 | 'react/jsx-pascal-case': ['error', { 506 | allowAllCaps: true, 507 | ignore: [], 508 | }], 509 | 510 | // Enforce propTypes declarations alphabetical sorting 511 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md 512 | 'react/sort-prop-types': ['off', { 513 | ignoreCase: true, 514 | callbacksLast: false, 515 | requiredFirst: false, 516 | sortShapeProp: true, 517 | }], 518 | 519 | // Deprecated in favor of react/jsx-sort-props 520 | 'react/jsx-sort-prop-types': 'off', 521 | 522 | // Enforce props alphabetical sorting 523 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 524 | 'react/jsx-sort-props': ['off', { 525 | ignoreCase: true, 526 | callbacksLast: false, 527 | shorthandFirst: false, 528 | shorthandLast: false, 529 | noSortAlphabetically: false, 530 | reservedFirst: true, 531 | }], 532 | 533 | // Enforce defaultProps declarations alphabetical sorting 534 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-sort-default-props.md 535 | 'react/jsx-sort-default-props': ['off', { 536 | ignoreCase: true, 537 | }], 538 | 539 | // Prevent React to be incorrectly marked as unused 540 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 541 | 'react/jsx-uses-react': ['error'], 542 | 543 | // Prevent variables used in JSX to be incorrectly marked as unused 544 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 545 | 'react/jsx-uses-vars': 'error', 546 | 547 | // Prevent usage of dangerous JSX properties 548 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md 549 | 'react/no-danger': 'warn', 550 | 551 | // Prevent usage of deprecated methods 552 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md 553 | 'react/no-deprecated': ['error'], 554 | 555 | // Prevent usage of setState in componentDidMount 556 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md 557 | // this is necessary for server-rendering 558 | 'react/no-did-mount-set-state': 'off', 559 | 560 | // Prevent usage of setState in componentDidUpdate 561 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 562 | 'react/no-did-update-set-state': 'error', 563 | 564 | // Prevent usage of setState in componentWillUpdate 565 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-will-update-set-state.md 566 | 'react/no-will-update-set-state': 'error', 567 | 568 | // Prevent direct mutation of this.state 569 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md 570 | 'react/no-direct-mutation-state': 'off', 571 | 572 | // Prevent usage of isMounted 573 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md 574 | 'react/no-is-mounted': 'error', 575 | 576 | // Prevent multiple component definition per file 577 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 578 | 'react/no-multi-comp': ['error', { ignoreStateless: true }], 579 | 580 | // Prevent usage of setState 581 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md 582 | 'react/no-set-state': 'off', 583 | 584 | // Prevent using string references 585 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md 586 | 'react/no-string-refs': 'error', 587 | 588 | // Prevent usage of unknown DOM property 589 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 590 | 'react/no-unknown-property': 'error', 591 | 592 | // Require ES6 class declarations over React.createClass 593 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md 594 | 'react/prefer-es6-class': ['error', 'always'], 595 | 596 | // Require stateless functions when not using lifecycle methods, setState or ref 597 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md 598 | 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }], 599 | 600 | // Prevent missing props validation in a React component definition 601 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 602 | 'react/prop-types': ['error', { 603 | ignore: [], 604 | customValidators: [], 605 | skipUndeclared: false 606 | }], 607 | 608 | // Prevent missing React when using JSX 609 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 610 | 'react/react-in-jsx-scope': 'error', 611 | 612 | // Require render() methods to return something 613 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md 614 | 'react/require-render-return': 'error', 615 | 616 | // Prevent extra closing tags for components without children 617 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 618 | 'react/self-closing-comp': 'error', 619 | 620 | // Enforce component methods order 621 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/sort-comp.md 622 | 'react/sort-comp': ['error', { 623 | order: [ 624 | 'static-methods', 625 | 'instance-variables', 626 | 'lifecycle', 627 | '/^on.+$/', 628 | 'getters', 629 | 'setters', 630 | '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', 631 | 'instance-methods', 632 | 'everything-else', 633 | 'rendering', 634 | ], 635 | groups: { 636 | lifecycle: [ 637 | 'displayName', 638 | 'propTypes', 639 | 'contextTypes', 640 | 'childContextTypes', 641 | 'mixins', 642 | 'statics', 643 | 'defaultProps', 644 | 'constructor', 645 | 'getDefaultProps', 646 | 'getInitialState', 647 | 'state', 648 | 'getChildContext', 649 | 'componentWillMount', 650 | 'componentDidMount', 651 | 'componentWillReceiveProps', 652 | 'shouldComponentUpdate', 653 | 'componentWillUpdate', 654 | 'componentDidUpdate', 655 | 'componentWillUnmount', 656 | ], 657 | rendering: [ 658 | '/^render.+$/', 659 | 'render' 660 | ], 661 | }, 662 | }], 663 | 664 | // Prevent missing parentheses around multilines JSX 665 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-wrap-multilines.md 666 | 'react/jsx-wrap-multilines': ['error', { 667 | declaration: 'parens-new-line', 668 | assignment: 'parens-new-line', 669 | return: 'parens-new-line', 670 | arrow: 'parens-new-line', 671 | condition: 'parens-new-line', 672 | logical: 'parens-new-line', 673 | prop: 'parens-new-line', 674 | }], 675 | 676 | // 当元素为多行时,要求JSX元素中的第一个属性位于新行上。 677 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md 678 | 'react/jsx-first-prop-new-line': [2, 'multiline-multiprop'], // 采用,如果JSX标签占用多个行并且有多个属性,则第一个属性应该总是放置在一个新行上。这是默认值。 679 | 680 | // 在JSX的等号两侧是否强制留有空格 681 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md 682 | 'react/jsx-equals-spacing': [2, 'never'], // 采用,等号周围的不允许空格 683 | 684 | // 强制jsx的缩进风格 685 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md 686 | 'react/jsx-indent': [2, 2], // 采用,两个空格作为缩进 687 | 688 | // 不允许不安全的target='_blank'用法, 具体见连接说明 689 | // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-no-target-blank.md 690 | 'react/jsx-no-target-blank': [2, { enforceDynamicLinks: 'always' }], // 采用,如果是动态链接则不强制 691 | 692 | // 只有.jsx文件允许写JSX语法 693 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md 694 | 'react/jsx-filename-extension': [2, { extensions: ['.jsx'] }],// 采用 695 | 696 | // 防止JS注释意外的作为文本注入到JSX中 697 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md 698 | 'react/jsx-no-comment-textnodes': 2, // 采用,jsx的注释问题 699 | 700 | // 不允许使用react或者reactdom的render方法的返回值 701 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md 702 | 'react/no-render-return-value': 2, // 采用,建议采用ref 703 | 704 | // 要求有shouldComponentUpdate方法, 或者采用PureRenderMixin 705 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-optimization.md 706 | 'react/require-optimization': [0, { allowDecorators: [] }], // 不采用 707 | 708 | // 禁止使用findDOMNode()方法 709 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md 710 | 'react/no-find-dom-node': 2, // 采用 711 | 712 | // 在Components中禁用一些特定的props 713 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-component-props.md 714 | 'react/forbid-component-props': 0, // 不采用 715 | 716 | // 禁用特定元素 717 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-elements.md 718 | 'react/forbid-elements': 0, // 不采用 719 | 720 | // 避免在children和dangerouslySetInnerHTML属性共存时出现问题 721 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md 722 | 'react/no-danger-with-children': 2, // 采用 723 | 724 | // 防止未使用的propType定义 725 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md 726 | 'react/no-unused-prop-types': [2, { 727 | customValidators: [ 728 | ], 729 | skipShapeProps: true, 730 | }], // 采用 731 | 732 | // 要求样式的值为对象或者变量 733 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md 734 | 'react/style-prop-object': 2, // 采用 735 | 736 | // 禁止无效字符的出现 737 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md 738 | 'react/no-unescaped-entities': 2, // 采用 739 | 740 | // 禁止通过children props来传值 741 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md 742 | 'react/no-children-prop': 2, // 采用 743 | 744 | // 在JSX打开和关闭括号内和周围验证空格 745 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-tag-spacing.md 746 | 'react/jsx-tag-spacing': [2, { 747 | closingSlash: 'never', 748 | beforeSelfClosing: 'always', 749 | afterOpening: 'never', 750 | beforeClosing: 'never', 751 | }], // 采用, 不留空格 752 | 753 | // 强制在闭JSX元素标签之前留空格 754 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md 755 | // Deprecated in favor of jsx-tag-spacing 756 | 'react/jsx-space-before-closing': 0, // 不采用 757 | 758 | // 禁止使用数组的索引作为元素的key属性 759 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md 760 | 'react/no-array-index-key': 2, // 采用, 原因可以见链接 761 | 762 | // 强制给每个不是必须的属性定义一个默认值 763 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/require-default-props.md 764 | 'react/require-default-props': [0, { 765 | forbidDefaultForRequired: true, 766 | }], // 采用, 写了isRequired的属性则禁止设置默认值 767 | 768 | // 禁止使用没有被export的prototype 769 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md 770 | 'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }], // 设置为警告级别 771 | 772 | // 不要给单标签的jsx元素传入children 773 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md 774 | 'react/void-dom-elements-no-children': 2, // 采用 775 | 776 | // 强制所有的默认prop都对应一个非必须的proptype 777 | // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/default-props-match-prop-types.md 778 | 'react/default-props-match-prop-types': [2, { allowRequiredDefaults: false }], // 采用 779 | 780 | // 继承React.PureComponent时,禁止使用shouldComponentUpdate 781 | // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/no-redundant-should-component-update.md 782 | 'react/no-redundant-should-component-update': 2, // 采用s 783 | 784 | // 禁止存在未使用的state值 785 | // https://github.com/yannickcr/eslint-plugin-react/pull/1103/ 786 | 'react/no-unused-state': 2, // 采用 787 | 788 | // 强制布尔值属性命名的的一致性 789 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/boolean-prop-naming.md 790 | 'react/boolean-prop-naming': 0, // 不采用 791 | 792 | // 防止常见的套管缺陷 793 | // https://github.com/yannickcr/eslint-plugin-react/blob/73abadb697034b5ccb514d79fb4689836fe61f91/docs/rules/no-typos.md 794 | 'react/no-typos': 2, // 采用 795 | 796 | // 禁止在props和children中使用无意义的大括号 797 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md 798 | 'react/jsx-curly-brace-presence': [2, { props: 'never', children: 'never' }], // 采用 799 | 800 | // 每行一个jsx元素 801 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-one-expression-per-line.md 802 | // TODO: re-enable when an option for text children is available 803 | 'react/jsx-one-expression-per-line': 0, // 不采用 804 | 805 | // 强制使用desconstruct的一致性 806 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/destructuring-assignment.md 807 | // TODO: re-enable when component detection is fixed 808 | 'react/destructuring-assignment': 0, // 不采用 809 | 810 | // 禁止在this.setState中使用this.state 811 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-access-state-in-setstate.md 812 | 'react/no-access-state-in-setstate': 2, // 采用 813 | 814 | // 禁止使用没有显式type属性的按钮元素 815 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/button-has-type.md 816 | 'react/button-has-type': [2, { 817 | button: true, 818 | submit: true, 819 | reset: false, 820 | }], // 采用 821 | 822 | // 确保内联标签两边有空格 823 | 'react/jsx-child-element-spacing': 0, // 不采用 824 | 825 | // 禁止在无状态组件中使用this 826 | // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-this-in-sfc.md 827 | 'react/no-this-in-sfc': 2, // 采用 828 | 829 | // 检查jsx最大深度 830 | // https://github.com/yannickcr/eslint-plugin-react/blob/abe8381c0d6748047224c430ce47f02e40160ed0/docs/rules/jsx-max-depth.md 831 | 'react/jsx-max-depth': 0, // 不采用 832 | 833 | // 禁止在jsx props之间存在多个空格 834 | // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-props-no-multi-spaces.md 835 | 'react/jsx-props-no-multi-spaces': 2, // 采用 836 | 837 | // 不允许使用UNSAFE_ methods 838 | // https://github.com/yannickcr/eslint-plugin-react/blob/157cc932be2cfaa56b3f5b45df6f6d4322a2f660/docs/rules/no-unsafe.md 839 | 'react/no-unsafe': 0, // 不采用 840 | } 841 | }; 842 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | yarn-error.log 4 | target 5 | node 6 | src/_tmp 7 | static 8 | dist 9 | coverage 10 | rekit_temp_app 11 | _book 12 | .nyc_output 13 | .tmp 14 | .DS_Store 15 | Thumbs.db 16 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-starter 2 | 3 | PC 端 react+redux 的脚手架,持续进行更新迭代。 4 | 5 | 6 | ## 特点 7 | 8 | 1. 当中集成了当前react生态的几个主流技术栈: 9 | - react 16.12.0 10 | - ant design 3.26.2 11 | - react-router 5.1.2 12 | - redux 4.0.4 13 | - redux-thunk 2.2.0 14 | - webpack 4.41.2 15 | 16 | 2. 符合目前前后端分离项目的开发、部署、测试、上线各环节及操作的要求。 17 | 3. 优化了传统redux-thunk中action、reducer书写上的不便。 18 | 4. 根据路由地址,懒加载所需模块,就是说目前脚手架中提供的示例代码无需删除,只需要确保路由无效即可 19 | 5. 增加错误边界,捕捉嵌套组件在生命过程中发生的错误 20 | 6. 封装 axios(`src/common/request.js`),对失败的请求进行了统一处理,使用方法: 21 | 22 | ``` 23 | import { get, post, put, del } from 'common/request'; 24 | post(URL, params).then(res => {}); 25 | 26 | // 如果需要自行处理请求失败的情况,也可以直接引入 axios 实例 27 | import axios from 'common/request'; 28 | axios.post(URL, params) 29 | .then(res => {}) 30 | .catch(error => {}) 31 | ``` 32 | 33 | ## 文件目录 34 | 35 | > 注意:带 ✎ 号的表示示例配置,可以根据自己的项目配置自行替换;带 +/- 号的表示示例组件,可保留修改或删除 36 | 37 | ``` 38 | ├─asset // ========================= 静态资源目录 39 | │ ├─css 40 | | | ├─app.less // ========================= 样式文件入口 41 | | | ├─base.less // ========================= 基础样式 42 | | | ├─icon.less // ========================= iconfont 样式(✎) 43 | | | ├─reset.less // ========================= 样式重置文件 44 | | | ├─variables.less // ========================= 变量文件(✎) 45 | | | └─vendor_antd.less // ========================= 专门用于覆盖 antd 样式(✎) 46 | │ ├─font // ========================= iconfont文件,在icon.less文件中引用 47 | | | ├─iconfont.eot(+/-) 48 | | | ├─iconfont.svg(+/-) 49 | | | ├─iconfont.ttf(+/-) 50 | | | └─iconfont.woff(+/-) 51 | │ └─img // ========================= 图片资源存放目录 52 | │ 53 | ├─build // ========================= webpack 相关配置目录 54 | │ ├─postcss.config.js // ========================= postcss 相关配置 55 | │ ├─publicPath.js // ========================= 发布路径(✎) 56 | │ ├─theme.js // ========================= antd 主题配置(✎) 57 | │ ├─webpack.base.conf.js // ========================= webpack 公用项配置 58 | │ ├─webpack.dev.conf.js // ========================= webpack 开发/联调环境项配置 59 | │ └─webpack.prod.conf.js // ========================= webpack 生产/测试环境项配置 60 | │ 61 | ├─mock // ========================= mock 文件目录 62 | │ ├─config 63 | │ ├─data 64 | │ └─template 65 | │ 66 | ├─src // ========================= js 源码目录 67 | │ ├─common // ========================= 公共模块目录 68 | │ │ └─request.js // ========================= 对axios进行封装 69 | │ │ 70 | │ ├─components // ========================= 纯组件目录 71 | │ │ ├─Layout // ========================= 布局相关组件目录 72 | │ │ │ ├─Footer.jsx(+/-) 73 | │ │ │ ├─Header.jsx(+/-) 74 | │ │ │ └─Sider.jsx(+/-) 75 | │ │ ├─Pages(+/-) // ========================= 分页组件 76 | │ │ ├─ErrorBoundary.jsx // ========================= 错误边界组件 77 | │ │ └─index.js(+/-) 78 | │ │ 79 | │ ├─constants 80 | │ │ ├─constant.js // ========================= 常量集中管理目录(✎) 81 | │ │ └─url.js // ========================= url集中管理目录(✎) 82 | │ │ 83 | │ ├─redux // ========================= redux 处理目录 84 | │ │ ├─skuunit(+/-) 85 | │ │ ├─configureStore.js // ========================= store/中间件配置 86 | │ │ └─reducers.js // ========================= 所有reducer入口(✎) 87 | │ │ 88 | │ ├─routers // ========================= 按路由划分的业务模块目录 89 | │ │ ├─Skuunit(+/-) 90 | │ │ ├─index.jsx(✎) 91 | │ │ └─PrimaryLayout.jsx(+/-) 92 | │ │ 93 | │ └─app.jsx // ========================= 项目入口 94 | │ 95 | ├─.babelrc // ========================= babel配置文件 96 | ├─.eslintignore 97 | ├─.eslintrc.js // ========================= eslint规则文件 98 | ├─.gitignore 99 | ├─index.html 100 | ├─package-lock.json 101 | ├─package.json 102 | ├─README.md(✎) 103 | ├─template.html(✎) // ========================= html嵌入模板 104 | └─webpack.config.js // ========================= webpack配置入口 105 | 106 | ``` 107 | 108 | ## Changelog 109 | 110 | - 2019.07.15 111 | - [x] src目录支持使用less 112 | - [x] 兼容IE11 113 | 114 | - 2019.06.11 115 | - [x] 更改UI 116 | 117 | - 2019.06.03 118 | - [x] 升级babel 7 119 | - [x] 更新示例组件 120 | - [x] 修复eslint报错 121 | - [x] 完善README.md 122 | 123 | - 2019.05.22 124 | - [x] 完成基础版本,给出规范目录结构,满足打包构建路由懒加载数据管理错误处理一条龙服务。 125 | 126 | 127 | ## babel7依赖说明 128 | 129 | 基础依赖: 130 | 131 | - @babel/cli: 为babel的脚手架工具 132 | - @babel/core: babel-core是作为babel的核心存在,babel的核心api都在这个模块里面,比如:transform,用于字符串转码得到AST 133 | - @babel/plugin-proposal-class-properties: 解析class类的属性 134 | - @babel/plugin-proposal-decorators: 解析装饰器模式语法,如使用react-redux的@connect 135 | - @babel/plugin-proposal-export-default-from: 解析export xxx from 'xxx'语法 136 | - @babel/plugin-transform-runtime: 防止转换后的代码重复 137 | - @babel/preset-env : 官方解释“用于编写下一代JavaScript的编译器”,编译成浏览器认识的JavaScript标准 138 | - @babel/preset-react: 用于编译react的jsx,开发react应用必备 139 | - babel-loader: 就是用于编译JavaScript代码 140 | - babel-plugin-import: 用于进行按需加载 141 | 142 | 代码优化相关依赖: 143 | 144 | - [babel-plugin-transform-imports](https://www.npmjs.com/package/babel-plugin-transform-imports):去除未使用的模块 145 | - [babel-plugin-transform-react-remove-prop-types](https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types): 生产环境下移除不必要的 React propTypes,以减少代码体积 146 | 147 | 148 | ## 参考文章 149 | 150 | - 2019.06.03 151 | - [Webpack4+Babel7优化70%速度](https://juejin.im/post/5c763885e51d457380771ab0#heading-11) 152 | - [一口(很长的)气了解 babel](https://juejin.im/post/5c19c5e0e51d4502a232c1c6#heading-0) 153 | - 2019.05.22 154 | - [Web Performance Optimization with webpack](https://developers.google.com/web/fundamentals/performance/webpack/) 155 | - [Webpack 4 配置最佳实践](https://juejin.im/post/5b304f1f51882574c72f19b0#heading-1) 156 | - [Webpack 4 默认分包策略](https://panjiachen.github.io/awesome-bookmarks/blog/webpack/webpack4-b.html) 157 | - [webpack-libs-optimizations](https://github.com/GoogleChromeLabs/webpack-libs-optimizations) 158 | 159 | 160 | 朋友们如果有一些对本项目得建议,或者想法欢迎去 github 提 issues,我将持续改进优化该项目 161 | -------------------------------------------------------------------------------- /asset/css/app.less: -------------------------------------------------------------------------------- 1 | /* 样式文件入口 */ 2 | @import 'reset.less'; 3 | @import 'variables.less'; 4 | @import 'icon.less'; 5 | @import 'base.less'; 6 | 7 | // 样式覆盖 8 | @import url("vendor_antd.less"); 9 | -------------------------------------------------------------------------------- /asset/css/base.less: -------------------------------------------------------------------------------- 1 | /* 基础样式 */ 2 | html, body { 3 | font-size: 14px; 4 | height: 100%; 5 | } 6 | 7 | #app { 8 | height: 100%; 9 | } 10 | 11 | .fl { 12 | .fl(); 13 | } 14 | .fr { 15 | .fr(); 16 | } 17 | 18 | .ml15 { 19 | margin-left: 5px; 20 | } 21 | .ml10 { 22 | margin-left: 10px; 23 | } 24 | .ml15 { 25 | margin-left: 15px; 26 | } 27 | .ml20 { 28 | margin-left: 20px; 29 | } 30 | .mr5 { 31 | margin-right: 5px; 32 | } 33 | .mr10 { 34 | margin-right: 10px; 35 | } 36 | .mr15 { 37 | margin-right: 15px; 38 | } 39 | .mr20 { 40 | margin-right: 20px; 41 | } 42 | .m15 { 43 | margin: 15px; 44 | } 45 | .mt15 { 46 | margin-top: 15px; 47 | } 48 | .mt30 { 49 | margin-top: 30px; 50 | } 51 | .mt45 { 52 | margin-top: 45px; 53 | } 54 | .mb10 { 55 | margin-bottom: 10px; 56 | } 57 | .mb15 { 58 | margin-bottom: 15px; 59 | } 60 | .mb20 { 61 | margin-bottom: 20px; 62 | } -------------------------------------------------------------------------------- /asset/css/icon.less: -------------------------------------------------------------------------------- 1 | /* iconfont样式 */ 2 | @font-face { 3 | font-family: "iconfont"; 4 | // src: url('../font/iconfont.eot'); /* IE9*/ 5 | // src: url('../font/iconfont.eot') format('embedded-opentype'), /* IE6-IE8 */ 6 | // url('../font/iconfont.woff') format('woff'), /* chrome, firefox */ 7 | // url('../font/iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 8 | // url('../font/iconfont.svg') format('svg'); /* iOS 4.1- */ 9 | } 10 | 11 | .iconfont { 12 | font-family: "iconfont" !important; 13 | font-size: 16px; 14 | font-style: normal; 15 | display: inline-block; 16 | vertical-align: baseline; 17 | text-align: center; 18 | text-transform: none; 19 | line-height: 1; 20 | text-rendering: optimizeLegibility; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /asset/css/reset.less: -------------------------------------------------------------------------------- 1 | /* 样式重置文件 */ 2 | 3 | // 4 | // 1. Remove default margin padding. 5 | // 6 | 7 | html, body, div, ul, li, h1, h2, h3, h4, h5, h6, p, dl, dt, dd, ol, form, input, textarea, th, td, select { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | // HTML5 display definitions 13 | // ========================================================================== 14 | 15 | // 16 | // Correct `block` display not defined for any HTML5 element in IE 8/9. 17 | // Correct `block` display not defined for `details` or `summary` in IE 10/11 18 | // and Firefox. 19 | // Correct `block` display not defined for `main` in IE 11. 20 | // 21 | 22 | article, 23 | aside, 24 | details, 25 | figcaption, 26 | figure, 27 | footer, 28 | header, 29 | hgroup, 30 | main, 31 | menu, 32 | nav, 33 | section, 34 | summary { 35 | display: block; 36 | } 37 | 38 | // 39 | // 1. Correct `inline-block` display not defined in IE 8/9. 40 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 41 | // 42 | 43 | audio, 44 | canvas, 45 | progress, 46 | video { 47 | display: inline-block; 48 | vertical-align: baseline; 49 | } 50 | 51 | // 52 | // Prevent modern browsers from displaying `audio` without controls. 53 | // Remove excess height in iOS 5 devices. 54 | // 55 | 56 | audio:not([controls]) { 57 | display: none; 58 | height: 0; 59 | } 60 | 61 | // 62 | // Address `[hidden]` styling not present in IE 8/9/10. 63 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 64 | // 65 | 66 | [hidden], 67 | template { 68 | display: none; 69 | } 70 | 71 | // Links 72 | // ========================================================================== 73 | 74 | // 75 | // Remove the gray background color from active links in IE 10. 76 | // 77 | 78 | a { 79 | background-color: transparent; 80 | } 81 | 82 | // 83 | // Improve readability of focused elements when they are also in an 84 | // active/hover state. 85 | // 86 | 87 | a:active, 88 | a:hover { 89 | outline: 0; 90 | } 91 | // Text-level semantics 92 | // ========================================================================== 93 | 94 | // 95 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome. 96 | // 97 | 98 | abbr[title] { 99 | border-bottom: 1px dotted; 100 | } 101 | 102 | // 103 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 104 | // 105 | 106 | b, 107 | strong { 108 | font-weight: bold; 109 | } 110 | 111 | // 112 | // Address styling not present in Safari and Chrome. 113 | // 114 | 115 | dfn { 116 | font-style: italic; 117 | } 118 | 119 | // 120 | // Unified "h1" - "h6" font weight 121 | // 122 | 123 | h1, h2, h3, h4, h5, h6{font-weight:normal;} 124 | 125 | // 126 | // Remove ul-ol the default styles 127 | // 128 | 129 | ul,ol { 130 | list-style: none; 131 | } 132 | 133 | 134 | // 135 | // Address styling not present in IE 8/9. 136 | // 137 | 138 | mark { 139 | background: #ff0; 140 | color: #000; 141 | } 142 | 143 | // 144 | // Address inconsistent and variable font size in all browsers. 145 | // 146 | 147 | small { 148 | font-size: 80%; 149 | } 150 | 151 | // 152 | // Prevent `sub` and `sup` affecting `line-height` in all browsers. 153 | // 154 | 155 | sub, 156 | sup { 157 | font-size: 75%; 158 | line-height: 0; 159 | position: relative; 160 | vertical-align: baseline; 161 | } 162 | 163 | sup { 164 | top: -0.5em; 165 | } 166 | 167 | sub { 168 | bottom: -0.25em; 169 | } 170 | 171 | // Embedded content 172 | // ========================================================================== 173 | 174 | // 175 | // Remove border when inside `a` element in IE 8/9/10. 176 | // 177 | 178 | img { 179 | border: 0; 180 | } 181 | 182 | // 183 | // Correct overflow not hidden in IE 9/10/11. 184 | // 185 | 186 | svg:not(:root) { 187 | overflow: hidden; 188 | } 189 | 190 | // Grouping content 191 | // ========================================================================== 192 | 193 | // 194 | // Address margin not present in IE 8/9 and Safari. 195 | // 196 | 197 | figure { 198 | margin: 1em 40px; 199 | } 200 | 201 | // 202 | // Address differences between Firefox and other browsers. 203 | // 204 | 205 | hr { 206 | box-sizing: content-box; 207 | height: 0; 208 | } 209 | 210 | // 211 | // Contain overflow in all browsers. 212 | // 213 | 214 | pre { 215 | overflow: auto; 216 | } 217 | 218 | // 219 | // Address odd `em`-unit font size rendering in all browsers. 220 | // 221 | 222 | code, 223 | kbd, 224 | pre, 225 | samp { 226 | font-family: monospace, monospace; 227 | font-size: 1em; 228 | } 229 | 230 | // Forms 231 | // ========================================================================== 232 | 233 | // 234 | // Known limitation: by default, Chrome and Safari on OS X allow very limited 235 | // styling of `select`, unless a `border` property is set. 236 | // 237 | 238 | // 239 | // 1. Correct color not being inherited. 240 | // Known issue: affects color of disabled elements. 241 | // 2. Correct font properties not being inherited. 242 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 243 | // 244 | 245 | button, 246 | input, 247 | optgroup, 248 | select, 249 | textarea { 250 | color: inherit; // 1 251 | font: inherit; // 2 252 | margin: 0; // 3 253 | } 254 | 255 | // 256 | // Address `overflow` set to `hidden` in IE 8/9/10/11. 257 | // 258 | 259 | button { 260 | overflow: visible; 261 | } 262 | 263 | // 264 | // Address inconsistent `text-transform` inheritance for `button` and `select`. 265 | // All other form control elements do not inherit `text-transform` values. 266 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 267 | // Correct `select` style inheritance in Firefox. 268 | // 269 | 270 | button, 271 | select { 272 | text-transform: none; 273 | } 274 | 275 | // 276 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 277 | // and `video` controls. 278 | // 2. Correct inability to style clickable `input` types in iOS. 279 | // 3. Improve usability and consistency of cursor style between image-type 280 | // `input` and others. 281 | // 282 | 283 | button, 284 | html input[type="button"], // 1 285 | input[type="reset"], 286 | input[type="submit"] { 287 | -webkit-appearance: button; // 2 288 | cursor: pointer; // 3 289 | } 290 | 291 | // 292 | // Re-set default cursor for disabled elements. 293 | // 294 | 295 | button[disabled], 296 | html input[disabled] { 297 | cursor: default; 298 | } 299 | 300 | // 301 | // Remove inner padding and border in Firefox 4+. 302 | // 303 | 304 | button::-moz-focus-inner, 305 | input::-moz-focus-inner { 306 | border: 0; 307 | padding: 0; 308 | } 309 | 310 | // 311 | // Address Firefox 4+ setting `line-height` on `input` using `!important` in 312 | // the UA stylesheet. 313 | // 314 | 315 | input { 316 | line-height: normal; 317 | } 318 | 319 | // 320 | // It's recommended that you don't attempt to style these elements. 321 | // Firefox's implementation doesn't respect box-sizing, padding, or width. 322 | // 323 | // 1. Address box sizing set to `content-box` in IE 8/9/10. 324 | // 2. Remove excess padding in IE 8/9/10. 325 | // 326 | 327 | input[type="checkbox"], 328 | input[type="radio"] { 329 | box-sizing: border-box; // 1 330 | padding: 0; // 2 331 | } 332 | 333 | // 334 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain 335 | // `font-size` values of the `input`, it causes the cursor style of the 336 | // decrement button to change from `default` to `text`. 337 | // 338 | 339 | input[type="number"]::-webkit-inner-spin-button, 340 | input[type="number"]::-webkit-outer-spin-button { 341 | height: auto; 342 | } 343 | 344 | // 345 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome. 346 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 347 | // 348 | 349 | input[type="search"] { 350 | -webkit-appearance: textfield; // 1 351 | box-sizing: content-box; //2 352 | } 353 | 354 | // 355 | // Remove inner padding and search cancel button in Safari and Chrome on OS X. 356 | // Safari (but not Chrome) clips the cancel button when the search input has 357 | // padding (and `textfield` appearance). 358 | // 359 | 360 | input[type="search"]::-webkit-search-cancel-button, 361 | input[type="search"]::-webkit-search-decoration { 362 | -webkit-appearance: none; 363 | } 364 | 365 | // 366 | // Define consistent border, margin, and padding. 367 | // 368 | 369 | fieldset { 370 | border: 1px solid #c0c0c0; 371 | margin: 0 2px; 372 | padding: 0.35em 0.625em 0.75em; 373 | } 374 | 375 | // 376 | // 1. Correct `color` not being inherited in IE 8/9/10/11. 377 | // 2. Remove padding so people aren't caught out if they zero out fieldsets. 378 | // 379 | 380 | legend { 381 | border: 0; // 1 382 | padding: 0; // 2 383 | } 384 | 385 | // 386 | // 1.Don't allow the user to zoom 387 | // 2.Remove default vertical scrollbar in IE 8/9/10/11. 388 | // 389 | 390 | textarea { 391 | resize: none; 392 | overflow: auto; 393 | } 394 | 395 | // 396 | // Don't inherit the `font-weight` (applied by a rule above). 397 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 398 | // 399 | 400 | optgroup { 401 | font-weight: bold; 402 | } 403 | 404 | // Tables 405 | // ========================================================================== 406 | 407 | // 408 | // Remove most spacing between table cells. 409 | // 410 | 411 | table { 412 | border-collapse: collapse; 413 | border-spacing: 0; 414 | } 415 | 416 | td, 417 | th { 418 | padding: 0; 419 | } 420 | 421 | 422 | em, b, i { 423 | font-style: normal; 424 | font-weight: normal; 425 | } 426 | 427 | 428 | /* �������� */ 429 | input, select { 430 | vertical-align:baseline 431 | } 432 | 433 | 434 | 435 | 436 | 437 | 438 | -------------------------------------------------------------------------------- /asset/css/variables.less: -------------------------------------------------------------------------------- 1 | // 2 | // ==变量 3 | // -------------------------------------------------- 4 | 5 | 6 | //== 颜色 7 | // 8 | //## 灰色及品牌标记颜色引导. 9 | 10 | @gray-base: #000; 11 | @gray-darker: lighten(@gray-base, 13.5%); // #222 12 | @gray-dark: lighten(@gray-base, 20%); // #333 13 | @gray-dim: lighten(@gray-base, 25%); // #404040 系统主要文字颜色 14 | @gray: lighten(@gray-base, 33.5%); // #555 15 | @gray-light: lighten(@gray-base, 46.7%); // #777 16 | @gray-minor: lighten(@gray-base, 59.2%); // #979797 系统次要文字颜色 17 | @gray-lighter: lighten(@gray-base, 93.5%); // #eee 18 | 19 | 20 | 21 | @brand-link: #2577FF; //链接 22 | @brand-hover: #094D86;; //链接hover 23 | @brand-success: #3BA861; //状态成功、正常 24 | @brand-warning: #f0ad4e; //提示警告 25 | @brand-danger: #B01207; //危险、失败 26 | @brand-forbid: #b4b4b4; //不可用 27 | @title-background: #E9EBF1; //标题、条幅背景色 28 | 29 | 30 | 31 | //== Scaffolding 32 | // 33 | //## Settings for some of the most global styles. 34 | 35 | //** Background color for ``. 36 | @body-bg: #f1f2f3; 37 | //** Global text color on ``. 38 | @text-color: @gray-dim; 39 | 40 | //** Global textual link color. 41 | @link-color: @brand-link; 42 | //** Link hover color set via `darken()` function. 43 | @link-hover-color: @brand-hover; 44 | //** Link hover decoration. 45 | @link-hover-decoration: underline; 46 | 47 | 48 | //== Typography 49 | // 50 | //## Font, line-height, and color for body text, headings, and more. 51 | 52 | @font-family-sans-serif: "Microsoft Yahei", Arial , Tahoma , Helvetica, sans-serif; 53 | @font-family-serif: Georgia, "Times New Roman", Times, serif; 54 | //** Default monospace fonts for ``, ``, and `
`.
 55 | @font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
 56 | @font-family-base:        @font-family-sans-serif;
 57 | 
 58 | @font-size-base:          14px;
 59 | @font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
 60 | @font-size-medium:        ceil((@font-size-base * 1.14)); // ~16px 
 61 | @font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
 62 | 
 63 | @font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
 64 | @font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
 65 | @font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
 66 | @font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
 67 | @font-size-h5:            @font-size-base;
 68 | @font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
 69 | 
 70 | //** Unit-less `line-height` for use in components like buttons.
 71 | @line-height-base:        1.428571429; // 20/14
 72 | //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
 73 | @line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
 74 | 
 75 | //** By default, this inherits from the ``.
 76 | @headings-font-family:    inherit;
 77 | @headings-font-weight:    500;
 78 | @headings-line-height:    1.1;
 79 | @headings-color:          inherit;
 80 | 
 81 | 
 82 | 
 83 | //== Components
 84 | //
 85 | //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
 86 | 
 87 | @padding-base-vertical:     6px;
 88 | @padding-base-horizontal:   12px;
 89 | 
 90 | @padding-large-vertical:    10px;
 91 | @padding-large-horizontal:  16px;
 92 | 
 93 | @padding-small-vertical:    5px;
 94 | @padding-small-horizontal:  10px;
 95 | 
 96 | @padding-xs-vertical:       1px;
 97 | @padding-xs-horizontal:     5px;
 98 | 
 99 | @line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
100 | @line-height-small:         1.5;
101 | 
102 | @border-radius-base:        4px;
103 | @border-radius-large:       6px;
104 | @border-radius-small:       3px;
105 | 
106 | 
107 | // Medium screen / desktop
108 | @screen-md:                  992px;
109 | @screen-md-min:              @screen-md;
110 | // Large screen / wide desktop
111 | @screen-lg:                  1200px;
112 | @screen-lg-min:              @screen-lg;
113 | 
114 | 
115 | //
116 | //==混合变量
117 | //
118 | // WebKit-style focus
119 | .tab-focus() {
120 |   // WebKit-specific. Other browsers will keep their default outline style.
121 |   outline: 5px auto -webkit-focus-ring-color;
122 |   outline-offset: -2px;
123 | }
124 | 
125 | // 隐藏文本
126 | .text-hide() {
127 |   font: 0/0 a;
128 |   color: transparent;
129 |   text-shadow: none;
130 |   background-color: transparent;
131 |   border: 0;
132 | }
133 | 
134 | //
135 | //混合工具变量
136 | //
137 | 
138 | //去除a和label的虚线
139 | .remove_dotted(){
140 |   a,label {blr:~'expression(this.onFocus=this.blur())'}
141 |   a,label {outline:none;}
142 | }
143 | .font(@size:14px){
144 |     font-size:@size;
145 | }
146 | .h100(){
147 |     height:100%;
148 | }
149 | .w100(){
150 |     width:100%;
151 | }
152 | //边框设置
153 | .border(@w:1px,@c:#eee){
154 |     border:@w solid @c;
155 | }
156 | //定位
157 | .pos(r){
158 |     position:relative;
159 | }
160 | .pos(a){
161 |     position:absolute;
162 | }
163 | .pos(f){
164 |     position:fixed;
165 | }
166 | //背景图片,.bg("..images/1.png");
167 | .bg(@url){
168 |     background:url(@url) no-repeat;
169 | }
170 | //浮动,div{.fr;}
171 | .fl(){
172 |     float:left;
173 | }
174 | .fr(){
175 |     float:right;
176 | }
177 | 
178 | .list-sn(){
179 |     list-style:none;
180 | }
181 | //垂直居中
182 | .pos-box-cc(@w,@h,@pos:absolute){
183 |     width:@w;
184 |     height:@h;
185 |     left:50%;
186 |     top:50%;
187 |     margin:-@w/2 0 0 -@h/2;
188 |     position: @pos;
189 | }
190 | // 居中对齐一个块级元素
191 | .center-block() {
192 |   display: block;
193 |   margin-left: auto;
194 |   margin-right: auto;
195 | }
196 | 
197 | //文字居中
198 | .tc(){
199 |     text-align:center;
200 | }
201 | //文字垂直居中
202 | .tcc(@h){
203 |     text-align:center;
204 |     line-height:@h;
205 | }
206 | .l-h(@h){
207 |     height:@h;
208 |     line-height:@h;
209 | }
210 | 
211 | // 文本溢出
212 | // 需要inline-block或块适当的样式
213 | .text-overflow() {
214 |   overflow: hidden;
215 |   text-overflow: ellipsis;
216 |   white-space: nowrap;
217 | }
218 | 
219 | //display
220 | .d-b(){
221 |     display:block;
222 | }
223 | .d-i(){
224 |     display:inline;
225 | }
226 | .d-ib(){
227 |     display:inline-block;
228 |     *display:inline;
229 |     *zoom:1;
230 | }
231 | .d-t(){
232 |     display:table;
233 | }
234 | .d-n(){
235 |     display:none;
236 | }
237 | .t-n(@p:none){
238 |     text-decoration:@p;
239 | }
240 | .tc(){
241 |     text-align:center;
242 | }
243 | .tl(){
244 |     text-align:left;
245 | }
246 | .tr(){
247 |     text-align:right;
248 | }
249 | .va-m(){
250 |   vertical-align: middle;
251 | }
252 | 
253 | //图标距离
254 | 
255 | .m-icon-left(@l-distance){
256 |   margin-left:@l-distance;
257 | }
258 | .m-icon-right(@r-distance){
259 |   margin-right:@r-distance;
260 | }
261 | 
262 | 
263 | //圆角
264 | .radius(@r){
265 |     -webkit-border-radius:@r;
266 |        -moz-border-radius:@r;
267 |             border-radius:@r;
268 | }
269 | 
270 | //消息圆角
271 | 
272 | .message(@me-t,@me-r){
273 |   background-color:#F44336;
274 |   width: 8px;
275 |   height: 8px;
276 |   .pos(a);
277 |   top:@me-t;
278 |   right:@me-r;
279 | }
280 | 
281 | //三角形
282 | .triangle(top,@w:5px,@c:#556066){
283 |     border-width:@w;
284 |     border-color:transparent transparent @c transparent;
285 |     border-style:dashed dashed solid dashed;
286 | }
287 | 
288 | .triangle(bottom,@w:5px,@c:#556066){
289 |     border-width:@w;
290 |     border-color:@c transparent transparent transparent;
291 |     border-style:solid dashed dashed dashed;
292 | 
293 | }
294 | .triangle(left,@w:5px,@c:#556066){
295 |     border-width:@w;
296 |     border-color:transparent @c transparent transparent;
297 |     border-style:dashed dashed dashed solid;
298 | }
299 | 
300 | .triangle(right,@w:5px,@c:#556066){
301 |     border-width:@w;
302 |     border-color:transparent transparent transparent @c;
303 |     border-style:dashed solid dashed dashed;
304 | 
305 | }
306 | .triangle(@_){
307 |     width:0;
308 |     height:0;
309 |     overflow:hidden;
310 |     font-size:0;
311 | }
312 | .clearfix(){
313 |   *zoom: 1;
314 |   &:before,
315 |   &:after{
316 |     display:table;
317 |     content: "";
318 |   }
319 |   &:after {
320 |     clear: both;
321 |   }
322 | }
323 | 
324 | .box-sizing(@box){
325 |   -webkit-box-sizing:@box;
326 |      -moz-box-sizing:@box;
327 |       -ms-box-sizing:@box;
328 |        -o-box-sizing:@box;
329 |               sizing:@box;
330 | }
331 | .box-shadow(@shadow){
332 |   -webkit-box-shadow:@shadow;
333 |      -moz-box-shadow:@shadow;
334 |       -ms-box-shadow:@shadow;
335 |        -o-box-shadow:@shadow;
336 |               shadow:@shadow;
337 | }
338 | 
339 | //过度
340 | .transition(@trans){
341 |     -webkit-transition:@trans;
342 |        -moz-transition:@trans;
343 |         -ms-transition:@trans;
344 |          -o-transition:@trans;
345 |             transition:@trans;
346 | }
347 | .transform-origin(@origin){
348 |     -webkit-transition-origin:@origin;
349 |        -moz-transition-origin:@origin;
350 |         -ms-transition-origin:@origin;
351 |          -o-transition-origin:@origin;
352 |             transition-origin:@origin;
353 | }
354 | .transform(@transform){
355 |     -webkit-transform:@transform;
356 |        -moz-transform:@transform;
357 |         -ms-transform:@transform;
358 |          -o-transform:@transform;
359 |             transform:@transform;
360 | }
361 | .create3d(@h){
362 |   -webkit-perspective:@h;
363 |           perspective:@h;
364 | }
365 | .use3d(){
366 |     -webkit-transform-style:preserve-3d;
367 |             transform-style:preserve-3d;
368 | }
369 | 
370 | //动画
371 | .animation(@as){
372 |     -webkit-animation:@as;
373 |        -moz-animation:@as;
374 |          -o-animation:@as;
375 |             animation:@as;
376 | }
377 | .trans3d(){
378 |     -webkit-transform-style:preserve-3d;
379 |        -moz-transform-style:preserve-3d;
380 |             transform-style:preserve-3d;
381 | }
382 | .trans-origin(@to){
383 |   -webkit-transform-origin:@to;
384 |      -moz-transform-origin:@to;
385 |           transform-origin:@to;
386 | }
387 | 
388 | 
389 | 


--------------------------------------------------------------------------------
/asset/css/vendor_antd.less:
--------------------------------------------------------------------------------
1 | .newAnt {
2 |   .ant-layout.ant-layout-has-sider {
3 |     height: 100%;
4 |   }
5 | }
6 | 


--------------------------------------------------------------------------------
/asset/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangkun-Jser/react-starter/df46946e8e9deeadcc221979acc1f9a48dca0b92/asset/img/avatar.png


--------------------------------------------------------------------------------
/asset/img/logo.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     Group 28 Copy 5
 5 |     Created with Sketch.
 6 |     
 7 |         
 8 |             
 9 |             
10 |         
11 |         
12 |             
13 |             
14 |             
15 |         
16 |         
17 |             
18 |             
19 |             
20 |         
21 |         
22 |             
23 |             
24 |             
25 |         
26 |     
27 |     
28 |         
42 |     
43 | 


--------------------------------------------------------------------------------
/build/env.js:
--------------------------------------------------------------------------------
  1 | 
  2 | const ENVS = {
  3 |   PUB: ['pub', 'production'],
  4 |   QA: 'qa',
  5 |   PROFILE: 'profile',
  6 |   DEV: ['dev', 'development'],
  7 | };
  8 | 
  9 | const ENVS_ARR = [];
 10 | 
 11 | Object.keys(ENVS).forEach(key => {
 12 |   const value = ENVS[key];
 13 |   if (Array.isArray(value)) {
 14 |     value.forEach(i => ENVS_ARR.push(i));
 15 |   } else {
 16 |     ENVS_ARR.push(value);
 17 |   }
 18 | });
 19 | 
 20 | let currentEnv = process.env.NODE_ENV;
 21 | 
 22 | /**
 23 |  * 判断当前环境参数是否符合规范
 24 |  * @param {string} env
 25 |  */
 26 | function checkEnv(env) {
 27 |   if (!env) {
 28 |     throw new Error('NODE_ENV is not defined');
 29 |     return false;
 30 |   }
 31 | 
 32 |   if (ENVS_ARR.indexOf(env) === -1) {
 33 |     throw new Error(`NODE_ENV must be one of ${JSON.stringify(ENVS_ARR)},currentEnv is:${env}`);
 34 |   }
 35 | 
 36 |   return true;
 37 | }
 38 | 
 39 | // 线上环境
 40 | const isProduction = () => {
 41 |   return checkEnv(currentEnv) && ENVS.PUB.includes(currentEnv);
 42 | };
 43 | 
 44 | // 非线上环境
 45 | const isNotProduction = () => !isProduction();
 46 | 
 47 | // 测试环境
 48 | const isQA = () => {
 49 |   return checkEnv(currentEnv) && currentEnv === ENVS.QA;
 50 | };
 51 | 
 52 | // 联调环境
 53 | const isProfile = () => {
 54 |   return checkEnv(currentEnv) && currentEnv === ENVS.PROFILE;
 55 | };
 56 | 
 57 | // 开发环境
 58 | // const isDev = () => !(isProfile() || isProduction() || isQA());
 59 | const isDev = () => {
 60 |   return checkEnv(currentEnv) && ENVS.DEV.includes(currentEnv);
 61 | };
 62 | 
 63 | /**
 64 |  * 获取所有环境变量
 65 |  * @returns ENVS_ARR
 66 |  */
 67 | function getEnvs() {
 68 |   return ENVS_ARR;
 69 | }
 70 | 
 71 | /**
 72 |  * 设置环境变量
 73 |  * @param {string} env
 74 |  */
 75 | function setEnv(env) {
 76 |   if (ENVS_ARR.indexOf(env) === -1) {
 77 |     throw new Error(`NODE_ENV provided must be one of ${JSON.stringify(ENVS_ARR)},your param is:${env}`);
 78 |   }
 79 |   currentEnv = env;
 80 |   console.log(`NODE_ENV is set successfully,currentEnv is ${env}`);
 81 | }
 82 | 
 83 | /**
 84 |  * 设置线上环境
 85 |  */
 86 | const setProduction = () => setEnv(ENVS.PUB[0]);
 87 | 
 88 | /**
 89 |  * 设置测试环境
 90 |  */
 91 | const setQA = () => setEnv(ENVS.QA);
 92 | 
 93 | /**
 94 |  * 设置联调环境
 95 |  */
 96 | const setProfile = () => setEnv(ENVS.PROFILE);
 97 | 
 98 | /**
 99 |  * 设置开发环境
100 |  */
101 | const setDev = () => setEnv(ENVS.DEV[0]);
102 | 
103 | /**
104 |  * 开发和线上环境配置,默认为开发环境配置,适用于前后端未分离项目(用hash做缓存)
105 |  * @param  {*} devConfig 开发环境配置
106 |  * @param  {*} pubConfig 线上环境配置
107 |  * @returns (isQA() || isProduction()) ? pubConfig : devConfig
108 |  */
109 | function env(devConfig, pubConfig) {
110 |   if (!pubConfig && pubConfig !== '') {
111 |     pubConfig = devConfig;
112 |   }
113 | 
114 |   if (isQA() || isProduction()) {
115 |     return pubConfig;
116 |   }
117 | 
118 |   return devConfig;
119 | }
120 | 
121 | module.exports = {
122 |   getEnvs,
123 |   setEnv,
124 |   setProduction,
125 |   setQA,
126 |   setProfile,
127 |   setDev,
128 |   env,
129 |   isProduction,
130 |   isNotProduction,
131 |   isQA,
132 |   isProfile,
133 |   isDev,
134 |   PUB: ENVS.PUB[0],
135 |   QA: ENVS.QA,
136 |   PROFILE: ENVS.PROFILE,
137 |   DEV: ENVS.DEV[0],
138 | };
139 | 


--------------------------------------------------------------------------------
/build/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   plugins: [
3 |     require('autoprefixer'),
4 |   ],
5 | };
6 | 


--------------------------------------------------------------------------------
/build/publicPath.js:
--------------------------------------------------------------------------------
 1 | let pkg = require('../package.json');
 2 | const Env = require('./env');
 3 | 
 4 | // 不同环境下的静态资源的url根路径
 5 | let PRODUCT_STATIC_URL = 'http://static.hello.com';
 6 | let QA_STATIC_URL = 'http://qa.static.hello.com';
 7 | let PROFILE_STATIC_URL = 'http://dev.static.hello.com'; // 联调环境静态资源存放路径
 8 | let DEV_STATIC_URL = '/dist';
 9 | 
10 | // 根据环境来获取不同的静态资源部署的根路径
11 | let publicPath = (function getPublicPath() {
12 |   let staticUrl = DEV_STATIC_URL;
13 |   if (Env.isDev()) {
14 |     return staticUrl;
15 |   }
16 | 
17 |   if (Env.isProduction()) {
18 |     staticUrl = PRODUCT_STATIC_URL;
19 |   }
20 | 
21 |   if (Env.isQA()) {
22 |     staticUrl = QA_STATIC_URL;
23 |   }
24 | 
25 |   if (Env.isProfile()) {
26 |     staticUrl = PROFILE_STATIC_URL;
27 |   }
28 | 
29 |   return `${staticUrl}/${pkg.name}/${pkg.version}`;
30 | })();
31 | 
32 | module.exports = publicPath;
33 | 


--------------------------------------------------------------------------------
/build/theme.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   'font-family': 'Microsoft Yahei, Arial , Tahoma , Helvetica, sans-serif',
3 | };
4 | 


--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
  1 | const path = require('path');
  2 | const webpack = require('webpack');
  3 | 
  4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  5 | const CleanWebpackPlugin = require('clean-webpack-plugin');
  6 | 
  7 | const resolve = path.resolve;
  8 | const publicPath = require('./publicPath');
  9 | const theme = require('./theme.js');
 10 | const postcssConfigPath = resolve(__dirname, './postcss.config.js');
 11 | 
 12 | module.exports = {
 13 |   entry: {
 14 |     app: './src/app.jsx',
 15 |   },
 16 |   output: {
 17 |     path: resolve(__dirname, '../dist'),
 18 |     filename: '[name].js',
 19 |     publicPath: publicPath + '/',
 20 |     chunkFilename: '[name].js',
 21 |     crossOriginLoading: 'anonymous',
 22 |   },
 23 |   resolve: {
 24 |     extensions: ['.js', '.jsx', '.less', '.scss', '.css'],
 25 |     alias: {
 26 |       common: resolve('src/common'),
 27 |       components: resolve('src/components'),
 28 |       constants: resolve('src/constants'),
 29 |       modules: resolve('src/modules'),
 30 |       reduxDir: resolve('src/redux'),
 31 |     },
 32 |   },
 33 |   module: {
 34 |     rules: [
 35 |       {
 36 |         test: /\.(js|jsx)$/i,
 37 |         exclude: /node_modules/,
 38 |         use: {
 39 |           loader: 'babel-loader',
 40 |         },
 41 |       },
 42 |       {
 43 |         test: /\.css/i,
 44 |         include: [
 45 |           resolve('src'),
 46 |         ],
 47 |         use: [
 48 |           MiniCssExtractPlugin.loader,
 49 |           {
 50 |             loader: 'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
 51 |           },
 52 |           {
 53 |             loader: 'postcss-loader',
 54 |             options: {
 55 |               sourceMap: true,
 56 |               config: {
 57 |                 path: postcssConfigPath,
 58 |               },
 59 |             },
 60 |           },
 61 |         ],
 62 |       },
 63 |       {
 64 |         test: /\.less$/,
 65 |         include: [resolve('src')],
 66 |         use: [
 67 |           MiniCssExtractPlugin.loader,
 68 |           {
 69 |             loader:
 70 |               'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
 71 |             options: {
 72 |               modules: true,
 73 |             },
 74 |           },
 75 |           {
 76 |             loader: 'postcss-loader',
 77 |             options: {
 78 |               sourceMap: true,
 79 |               config: {
 80 |                 path: postcssConfigPath,
 81 |               },
 82 |             },
 83 |           },
 84 |           {
 85 |             loader: 'less-loader',
 86 |             options: {
 87 |               javascriptEnabled: true,
 88 |               modifyVars: theme,
 89 |             },
 90 |           },
 91 |         ],
 92 |       },
 93 |       {
 94 |         test: /\.less/i,
 95 |         include: [
 96 |           resolve('asset/css'),
 97 |           resolve('node_modules/antd/'),
 98 |         ],
 99 |         use: [
100 |           MiniCssExtractPlugin.loader,
101 |           {
102 |             loader: 'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
103 |             options: {
104 |               minimize: true, // css压缩
105 |             },
106 |           },
107 |           {
108 |             loader: 'postcss-loader',
109 |             options: {
110 |               sourceMap: true,
111 |               config: {
112 |                 path: postcssConfigPath,
113 |               },
114 |             },
115 |           },
116 |           {
117 |             loader: 'less-loader',
118 |             options: {
119 |               javascriptEnabled: true,
120 |               modifyVars: theme,
121 |             },
122 |           },
123 |         ],
124 |       },
125 |       {
126 |         test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
127 |         use: ['file-loader?limit=1000&name=files/[md5:hash:base64:10].[ext]'],
128 |       },
129 |       {
130 |         test: /\.(html|htm)$/,
131 |         use: 'html-withimg-loader',
132 |       },
133 |     ],
134 |   },
135 |   plugins: [
136 |     new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /^\.\/zh\-cn$/),
137 |     // extract css
138 |     new MiniCssExtractPlugin({
139 |       filename: '[name].css',
140 |       chunkFilename: '[name].css',
141 |     }),
142 |     // clean output bundle directory
143 |     new CleanWebpackPlugin(),
144 |   ],
145 | };
146 | 


--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
 1 | let mockport = 8000;
 2 | 
 3 | process.argv.forEach(function setMockPort(val, index) {
 4 |   if (val === '--env.mockport') {
 5 |     mockport = process.argv[index + 1];
 6 |     return false;
 7 |   }
 8 | });
 9 | 
10 | module.exports = {
11 |   mode: 'development',
12 |   devtool: 'source-map',
13 |   devServer: {
14 |     // 服务器外部可访问要声明host
15 |     host: '0.0.0.0',
16 |     hot: true,
17 |     inline: true,
18 |     open: true,
19 |     proxy: {
20 |       '**/*.action': {
21 |         target: 'http://localhost:' + mockport,
22 |         /* bypass: function bypass(req, res, proxyOptions) {
23 |           // handle default jsp action
24 |           if (req.headers.accept.indexOf('html') != -1) {
25 |             return 'index.html';
26 |           }
27 |         }, */
28 |       },
29 |     },
30 |   },
31 | };
32 | 


--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
 1 | const path = require('path');
 2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
 3 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 4 | 
 5 | module.exports = {
 6 |   mode: 'production',
 7 |   plugins: [
 8 |     // analyze bundle size
 9 |     // new BundleAnalyzerPlugin(),
10 |     new HtmlWebpackPlugin({
11 |       template: path.resolve(process.cwd(), './template.html'),
12 |       filename: path.resolve(process.cwd(), './dist', 'index.html'), // 写入的文件
13 |     }),
14 |   ],
15 |   optimization: {
16 |     splitChunks: {
17 |       name: true,
18 |       cacheGroups: {
19 |         default: {
20 |           minChunks: 2,
21 |           priority: -20,
22 |           reuseExistingChunk: true,
23 |         },
24 |         libs: {
25 |           test: /[\\/]node_modules[\\/]/,
26 |           priority: -10,
27 |         },
28 |         styles: {
29 |           test: /(\.less|\.css)$/,
30 |           priority: -10,
31 |         },
32 |       },
33 |     },
34 |   },
35 | };
36 | 


--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangkun-Jser/react-starter/df46946e8e9deeadcc221979acc1f9a48dca0b92/favicon.ico


--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 |   
 6 |   
 7 |   PC端React+redux脚手架
 8 |   
 9 | 
10 | 
11 | 
12 |   
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /mock/config/mockConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSource": ["json", "template", "server"], 3 | "json": { 4 | "path": "/mock/data/", 5 | "wrap": true 6 | }, 7 | "server": [{ 8 | "host": "http://localhost:8080/mock", 9 | "serverParams": { 10 | "index": 1 11 | }, 12 | "statusCode": [200], 13 | "rejectUnauthorized": false, 14 | "secureProtocol": "SSLv3_method", 15 | "cookie": "", 16 | "proxy": "" 17 | }, { 18 | "host": "http://localhost:8081/", 19 | "serverParams": { 20 | "index": 2 21 | }, 22 | "statusCode": [200], 23 | "rejectUnauthorized": false, 24 | "secureProtocol": "SSLv3_method", 25 | "cookie": "", 26 | "proxy": "" 27 | }], 28 | "template": { 29 | "path": "/mock/template/" 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /mock/data/skuunit/deleteSkuUnit.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "value": "success", 4 | "success": { 5 | "flag": "0", 6 | "msg": [ 7 | "" 8 | ], 9 | "data": [] 10 | }, 11 | "error": { 12 | "flag": "1", 13 | "msg": [ 14 | "input error" 15 | ], 16 | "data": [] 17 | } 18 | } -------------------------------------------------------------------------------- /mock/data/skuunit/showSkuUnitList.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "value": "success", 4 | "success": { 5 | "flag": "0", 6 | "msg": ["系统繁忙"], 7 | "data": { 8 | "dataList": [ 9 | { 10 | "skuUnitId": "185715847_1", 11 | "skuUnitName": "1单元名称", 12 | "isPause": 1, 13 | "skuSetId": "23489551", 14 | "skuSetName": "上海鲜花", 15 | "groupCount": 5, 16 | "planCount": 2, 17 | "denyKeyCount": 9, 18 | "checkStatus": 1, 19 | "checkOkCount": 3000, 20 | "checkRejectCount": 200, 21 | "ideas": [ 22 | { 23 | "deveiceType": 2, 24 | "ideaType": 2 25 | } 26 | ] 27 | }, 28 | { 29 | "skuUnitId": "185715847_2", 30 | "skuUnitName": "2单元名称2", 31 | "isPause": 0, 32 | "skuSetId": "23489556", 33 | "skuSetName": "沈阳鲜花", 34 | "groupCount": 5, 35 | "planCount": 2, 36 | "denyKeyCount": 7, 37 | "checkStatus": 1, 38 | "checkOkCount": 3000, 39 | "checkRejectCount": 200, 40 | "ideas": [ 41 | { 42 | "deveiceType": 1, 43 | "ideaType": 2 44 | }, 45 | { 46 | "deveiceType": 2, 47 | "ideaType": 1 48 | } 49 | ] 50 | }, 51 | { 52 | "skuUnitId": "185715847_3", 53 | "skuUnitName": "3单元名称", 54 | "isPause": 0, 55 | "skuSetId": "23489555", 56 | "skuSetName": "兰州鲜花", 57 | "groupCount": 5, 58 | "planCount": 2, 59 | "denyKeyCount": 12, 60 | "checkStatus": 0, 61 | "checkOkCount": 3000, 62 | "checkRejectCount": 200, 63 | "ideas": [ 64 | { 65 | "deveiceType": 2, 66 | "ideaType": 1 67 | } 68 | ] 69 | }, 70 | { 71 | "skuUnitId": "185715847_4", 72 | "skuUnitName": "4单元名称", 73 | "isPause": 1, 74 | "skuSetId": "23489552", 75 | "skuSetName": "北京二手车", 76 | "groupCount": 5, 77 | "planCount": 2, 78 | "denyKeyCount": 4, 79 | "checkStatus": 1, 80 | "checkOkCount": 3000, 81 | "checkRejectCount": 200, 82 | "ideas": [ 83 | { 84 | "deveiceType": 2, 85 | "ideaType": 2 86 | } 87 | ] 88 | }, 89 | { 90 | "skuUnitId": "185715847_5", 91 | "skuUnitName": "5单元名称", 92 | "isPause": 0, 93 | "skuSetId": "23489553", 94 | "skuSetName": "香港鲜花", 95 | "groupCount": 5, 96 | "planCount": 2, 97 | "denyKeyCount": 7, 98 | "checkStatus": -1, 99 | "checkOkCount": 3000, 100 | "checkRejectCount": 200, 101 | "ideas": [ 102 | { 103 | "deveiceType": 1, 104 | "ideaType": 1 105 | } 106 | ] 107 | } 108 | ], 109 | "totalNumber": 50 110 | } 111 | }, 112 | "error": { 113 | "flag": "1", 114 | "msg": [ 115 | "系统繁忙" 116 | ], 117 | "data": [] 118 | } 119 | } -------------------------------------------------------------------------------- /mock/data/skuunit/updateSkuUnitPause.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "value": "success", 4 | "success": { 5 | "flag": "0", 6 | "msg": [ 7 | "修改失败" 8 | ], 9 | "data": [] 10 | }, 11 | "error": { 12 | "flag": "1", 13 | "msg": [ 14 | "input error" 15 | ], 16 | "data": [] 17 | } 18 | } -------------------------------------------------------------------------------- /mock/template/query/table.template: -------------------------------------------------------------------------------- 1 | { 2 | 'table|1-10': [ 3 | { 4 | 'id|100-1000': 100, 5 | 'name|1': ['A','B','C','D','E','F','G','H','I','J','K','L','M','N'], 6 | 'height|50-300': 50, 7 | 'weight|50-100.1-10': 50, 8 | 'age|1-100': 1, 9 | 'email': '@EMAIL' 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-starter", 3 | "description": "react+redux的脚手架,持续进行更新迭代", 4 | "version": "1.0.1", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development webpack-dev-server --progress --colors", 8 | "profile": "cross-env NODE_ENV=profile webpack --progress --colors --display-modules", 9 | "qa": "cross-env NODE_ENV=qa webpack --progress --colors", 10 | "pub": "cross-env NODE_ENV=production webpack --progress --colors ", 11 | "lint": "eslint src" 12 | }, 13 | "dependencies": { 14 | "antd": "^3.26.2", 15 | "axios": "^0.18.1", 16 | "babel-polyfill": "^6.26.0", 17 | "lodash": "^4.17.15", 18 | "moment": "^2.24.0", 19 | "prop-types": "^15.7.2", 20 | "react": "^16.12.0", 21 | "react-dom": "^16.12.0", 22 | "react-redux": "^5.1.2", 23 | "react-router-dom": "^5.1.2", 24 | "redux": "^4.0.4", 25 | "redux-thunk": "^2.2.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/cli": "^7.7.5", 29 | "@babel/core": "^7.7.5", 30 | "@babel/plugin-proposal-class-properties": "^7.7.4", 31 | "@babel/plugin-proposal-decorators": "^7.7.4", 32 | "@babel/plugin-proposal-export-default-from": "^7.7.4", 33 | "@babel/plugin-syntax-dynamic-import": "^7.7.4", 34 | "@babel/plugin-transform-runtime": "^7.7.6", 35 | "@babel/preset-env": "^7.7.6", 36 | "@babel/preset-react": "^7.7.4", 37 | "@hot-loader/react-dom": "^16.11.0", 38 | "autoprefixer": "^8.6.5", 39 | "babel-eslint": "^10.0.3", 40 | "babel-loader": "^8.0.6", 41 | "babel-plugin-import": "^1.13.0", 42 | "babel-plugin-transform-imports": "^1.5.1", 43 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 44 | "clean-webpack-plugin": "^2.0.2", 45 | "css-loader": "^0.28.9", 46 | "eslint": "^5.16.0", 47 | "eslint-plugin-react": "^7.17.0", 48 | "eslint-plugin-react-hooks": "^1.7.0", 49 | "file-loader": "^1.1.11", 50 | "html-webpack-plugin": "^3.2.0", 51 | "html-withimg-loader": "^0.1.16", 52 | "less": "^3.10.3", 53 | "less-loader": "^4.1.0", 54 | "mini-css-extract-plugin": "^0.4.5", 55 | "postcss-loader": "^2.1.6", 56 | "react-hot-loader": "^4.12.18", 57 | "redux-logger": "^3.0.6", 58 | "url-loader": "^1.1.2", 59 | "cross-env": "^6.0.3", 60 | "webpack": "^4.41.2", 61 | "webpack-bundle-analyzer": "^3.6.0", 62 | "webpack-cli": "^3.3.10", 63 | "webpack-dev-server": "^3.9.0", 64 | "webpack-merge": "^4.2.2" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/zhangkun-Jser/react-starter.git" 69 | }, 70 | "keywords": [], 71 | "author": "zk", 72 | "license": "ISC", 73 | "bugs": { 74 | "url": "https://github.com/zhangkun-Jser/react-starter/issues" 75 | }, 76 | "homepage": "https://github.com/zhangkun-Jser/react-starter", 77 | "eslintIgnore": [ 78 | "dist/**" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /src/app.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * 项目入口 3 | */ 4 | import 'babel-polyfill'; 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | import { HashRouter } from 'react-router-dom'; 9 | 10 | import '../asset/css/app.less'; 11 | import store from './redux/configureStore'; 12 | import Root from './routers'; 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | , 20 | document.getElementById('app'), 21 | ); 22 | -------------------------------------------------------------------------------- /src/common/request.js: -------------------------------------------------------------------------------- 1 | // 参考:https://ykloveyxk.github.io/2017/02/25/axios%E5%85%A8%E6%94%BB%E7%95%A5/ 2 | 3 | import OriginAxios from 'axios'; 4 | import { message } from 'antd'; 5 | 6 | const axios = OriginAxios.create({ 7 | timeout: 20000, 8 | }); 9 | 10 | export function get(url, data) { 11 | return axios.get(url, { 12 | params: data, 13 | }); 14 | } 15 | 16 | // By default, axios serializes JavaScript objects to JSON 17 | export function post(url, data) { 18 | return axios({ 19 | url, 20 | method: 'post', 21 | data, 22 | }); 23 | } 24 | 25 | // By default, axios serializes JavaScript objects to JSON 26 | export function put(url, data) { 27 | return axios({ 28 | url, 29 | method: 'put', 30 | data, 31 | }); 32 | } 33 | 34 | export function del(url, data) { 35 | return axios({ 36 | url, 37 | method: 'delete', 38 | data, 39 | }); 40 | } 41 | 42 | // Add a request interceptor 43 | axios.interceptors.request.use( 44 | function config(config) { 45 | // Do something before request is sent 46 | return config; 47 | }, 48 | function error(error) { 49 | // Do something with request error 50 | console.log('request error, HTTP CODE: ', error.response.status); 51 | return Promise.reject(error); 52 | }, 53 | ); 54 | 55 | // 返回状态判断(添加响应拦截器) 56 | axios.interceptors.response.use( 57 | res => { 58 | if (res.data && res.data.flag === 1) { 59 | let errorMsg = res.data.msg; 60 | message.error(errorMsg); 61 | return Promise.reject(errorMsg); 62 | } 63 | 64 | return res; 65 | }, 66 | error => { 67 | // 用户登录的时候会拿到一个基础信息,比如用户名,token,过期时间戳 68 | // 直接丢localStorage或者sessionStorage 69 | if (error.response.status === 401) { 70 | // 若是接口访问的时候没有发现有鉴权的基础信息,直接返回登录页 71 | // history.push('login'); 72 | window.location = '/login.html'; 73 | } 74 | }, 75 | ); 76 | 77 | export default axios; 78 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | // 错误边界 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | class ErrorBoundary extends Component { 6 | static propTypes = { 7 | children: PropTypes.oneOfType([PropTypes.any]), 8 | }; 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { hasError: false }; 13 | } 14 | 15 | static getDerivedStateFromError() { 16 | // Update state so the next render will show the fallback UI. 17 | return { hasError: true }; 18 | } 19 | 20 | componentDidCatch() { 21 | // You can also log the error to an error reporting service 22 | // logErrorToMyService(error, info); 23 | this.setState({ 24 | hasError: true, 25 | }); 26 | } 27 | 28 | render() { 29 | if (this.state.hasError) { 30 | // You can render any custom fallback UI 31 | return

Something went wrong.

; 32 | } 33 | 34 | return this.props.children; 35 | } 36 | } 37 | 38 | export default ErrorBoundary; 39 | -------------------------------------------------------------------------------- /src/components/Layout/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.css'; 3 | 4 | export default () => ( 5 |
Copyright© 2019 Sogou Biztech. All Rights Reserved.
6 | ); 7 | -------------------------------------------------------------------------------- /src/components/Layout/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | Layout, Icon, Menu, Dropdown, Avatar, 5 | } from 'antd'; 6 | 7 | import styles from './index.css'; 8 | import AVATAR from '../../../asset/img/avatar.png'; 9 | 10 | const { Header } = Layout; 11 | 12 | const HeaderComp = ({ 13 | collapsed = false, 14 | onToggle, 15 | }) => { 16 | const menu = ( 17 | 18 | 个人中心 19 | 设置 20 | 21 | 退出登录 22 | 23 | ); 24 | 25 | const handleToggle = () => { 26 | onToggle && onToggle(); 27 | }; 28 | 29 | return ( 30 |
31 | 36 |
37 | 38 | 39 | 40 | dev 41 | 42 | 43 |
44 |
45 | ); 46 | }; 47 | 48 | 49 | HeaderComp.propTypes = { 50 | collapsed: PropTypes.bool, 51 | onToggle: PropTypes.func, 52 | }; 53 | 54 | export default HeaderComp; 55 | -------------------------------------------------------------------------------- /src/components/Layout/Sider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | import { Menu, Layout, Icon } from 'antd'; 5 | 6 | import styles from './index.css'; 7 | import LOGO from '../../../asset/img/logo.svg'; 8 | 9 | const { Sider } = Layout; 10 | 11 | const SiderComp = ({ 12 | history, 13 | pathname, 14 | collapsed = false, 15 | }) => { 16 | const pathArr = pathname.split('/').filter(i => i); 17 | const homeKey = 'home'; 18 | 19 | const getDefaultSelectedKeys = () => { 20 | const selectKey = pathArr.length ? pathArr[pathArr.length - 1] : ''; 21 | return selectKey ? [selectKey] : [homeKey]; 22 | }; 23 | 24 | const [selectedKeys, setSelectedKeys] = useState(getDefaultSelectedKeys()); 25 | 26 | const handleSelect = ({ selectedKeys }) => { 27 | setSelectedKeys(selectedKeys); 28 | history.push(`/${selectedKeys}`); 29 | }; 30 | 31 | window.onhashchange = () => { 32 | setSelectedKeys(getDefaultSelectedKeys()); 33 | }; 34 | 35 | return ( 36 | 41 |
42 | 43 | 44 | {!collapsed &&

脚手架

} 45 | 46 |
47 | 53 | 54 | 55 | 欢迎 56 | 57 | 58 | 59 | nav 2 60 | 61 | 62 |
63 | ); 64 | }; 65 | 66 | SiderComp.propTypes = { 67 | history: PropTypes.objectOf(PropTypes.any), 68 | pathname: PropTypes.string, 69 | collapsed: PropTypes.bool, 70 | }; 71 | 72 | export default SiderComp; 73 | -------------------------------------------------------------------------------- /src/components/Layout/index.css: -------------------------------------------------------------------------------- 1 | /* Header */ 2 | .header { 3 | background: #fff; 4 | padding: 0 20px; 5 | position: relative; 6 | } 7 | 8 | .header-right { 9 | position: absolute; 10 | top: 0; 11 | right: 24px; 12 | bottom: 0; 13 | } 14 | 15 | .header-menu { 16 | width: 150px; 17 | } 18 | 19 | .user-name { 20 | margin-left: 10px; 21 | } 22 | 23 | .account { 24 | color: rgba(0, 0, 0, 0.65); 25 | cursor: pointer; 26 | padding: 0 12px; 27 | display: inline-block; 28 | transition: all .3s; 29 | height: 100%; 30 | } 31 | 32 | .account i { 33 | font-size: 16px; 34 | vertical-align: middle; 35 | } 36 | 37 | .account:hover { 38 | background: #e6f7ff; 39 | } 40 | 41 | /* Sider */ 42 | .trigger { 43 | font-size: 18px; 44 | line-height: 64px; 45 | padding: 0 10px; 46 | cursor: pointer; 47 | transition: color .3s; 48 | } 49 | 50 | .trigger:hover { 51 | color: #1890ff; 52 | } 53 | 54 | .menu-logo { 55 | position: relative; 56 | height: 64px; 57 | padding-left: 24px; 58 | overflow: hidden; 59 | line-height: 64px; 60 | background: #001529; 61 | transition: all 0.3s; 62 | } 63 | 64 | .menu-logo a { 65 | color: #1890FF; 66 | text-decoration: none; 67 | background-color: transparent; 68 | outline: none; 69 | cursor: pointer; 70 | transition: color 0.3s; 71 | } 72 | 73 | .menu-logo img { 74 | display: inline-block; 75 | width: 32px; 76 | vertical-align: middle; 77 | } 78 | 79 | .menu-logo h1 { 80 | display: inline-block; 81 | margin: 0 0 0 12px; 82 | color: white; 83 | font-weight: 600; 84 | font-size: 20px; 85 | vertical-align: middle; 86 | } 87 | 88 | 89 | /* Footer */ 90 | .footer { 91 | text-align: center; 92 | font-size: 14px; 93 | padding: 20px; 94 | } -------------------------------------------------------------------------------- /src/components/Pages/index.css: -------------------------------------------------------------------------------- 1 | .page-wrapper { 2 | margin: 16px 0px 16px 16px; 3 | line-height: 32px; 4 | overflow: hidden; 5 | } -------------------------------------------------------------------------------- /src/components/Pages/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 表格分页组件 3 | */ 4 | import React, { Component } from 'react'; 5 | import { Pagination } from 'antd'; 6 | import PropTypes from 'prop-types'; 7 | import styles from './index.css'; 8 | 9 | class Pages extends Component { 10 | static propTypes = { 11 | pageSizeOptions: PropTypes.arrayOf(PropTypes.string), 12 | defaultCurrent: PropTypes.number, 13 | total: PropTypes.number, 14 | current: PropTypes.number, 15 | pageSize: PropTypes.number, 16 | loadTable: PropTypes.func, 17 | style: PropTypes.objectOf(PropTypes.any), 18 | } 19 | 20 | static defaultProps = { 21 | pageSizeOptions: ['10', '20', '30', '40'], 22 | defaultCurrent: 1, 23 | total: 0, 24 | current: 1, 25 | pageSize: 10, 26 | style: { 27 | float: 'right', 28 | }, 29 | }; 30 | 31 | state = { 32 | current: this.props.current, 33 | pageSize: this.props.pageSize, 34 | }; 35 | 36 | static getDerivedStateFromProps(props) { 37 | return { 38 | current: props.current, 39 | pageSize: props.pageSize, 40 | total: props.total, 41 | }; 42 | } 43 | 44 | handlePageChange = (page, pageSize) => { 45 | this.loadTable({ 46 | page, 47 | pageSize, 48 | }); 49 | } 50 | 51 | handlePageSizeChange = (current, pageSize) => { 52 | this.loadTable({ 53 | page: this.props.defaultCurrent, 54 | pageSize, 55 | }); 56 | } 57 | 58 | loadTable({ page, pageSize }) { 59 | this.setState({ 60 | current: page, 61 | pageSize, 62 | }); 63 | this.props.loadTable(false, { 64 | page, 65 | pageSize, 66 | }); 67 | } 68 | 69 | reset() { 70 | const { current, pageSize } = this.defaultProps; 71 | this.setState({ current, pageSize }); 72 | } 73 | 74 | render() { 75 | const { total, pageSizeOptions, style } = this.props; 76 | const { current, pageSize } = this.state; 77 | 78 | return ( 79 |
80 | 共{total}条 81 | 91 |
92 | ); 93 | } 94 | } 95 | 96 | export default Pages; 97 | -------------------------------------------------------------------------------- /src/components/index.jsx: -------------------------------------------------------------------------------- 1 | import Header from './Layout/Header'; 2 | import Sider from './Layout/Sider'; 3 | import Footer from './Layout/Footer'; 4 | import Pages from './Pages'; 5 | import ErrorBoundary from './ErrorBoundary'; 6 | 7 | export { 8 | Header, 9 | Sider, 10 | Footer, 11 | Pages, 12 | ErrorBoundary, 13 | }; 14 | -------------------------------------------------------------------------------- /src/constants/constant.js: -------------------------------------------------------------------------------- 1 | export const ACTION_TYPE_ADD_ERROR = 'app/ADD_ERROR'; 2 | 3 | // 分页默认信息 4 | export const PAGE_DATA = { 5 | pageNo: 1, 6 | pageSize: 10, 7 | }; 8 | -------------------------------------------------------------------------------- /src/constants/url.js: -------------------------------------------------------------------------------- 1 | // 单元列表查询展示 2 | export const SHOW_UNIT_LIST_URL = '/skuunit/showSkuUnitList.action'; 3 | // 删除单元 4 | export const DELETE_UNIT_URL = '/skuunit/deleteSkuUnit.action'; 5 | -------------------------------------------------------------------------------- /src/redux/configureStore.js: -------------------------------------------------------------------------------- 1 | import { compose, createStore, applyMiddleware } from 'redux'; 2 | import logger from 'redux-logger'; 3 | import thunk from 'redux-thunk'; 4 | 5 | import reducer from './reducers'; 6 | 7 | const middleware = [thunk]; 8 | 9 | const isNotProduction = process.env.NODE_ENV !== 'production'; 10 | if (isNotProduction) { 11 | middleware.push(logger); 12 | } 13 | 14 | // 判断当前浏览器是否安装了 REDUX_DEVTOOL 插件 15 | const shouldCompose = isNotProduction 16 | && typeof window === 'object' 17 | && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; 18 | 19 | const composeEnhancers = shouldCompose 20 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 21 | // Specify here name, actionsBlacklist, actionsCreators and other options 22 | }) 23 | : compose; 24 | 25 | /* 26 | 调用 applyMiddleware ,使用 middleware 来增强 createStore 27 | */ 28 | const configureStore = composeEnhancers(applyMiddleware(...middleware))(createStore); 29 | 30 | const store = configureStore(reducer); 31 | window.Store = store; 32 | 33 | if (module.hot) { 34 | module.hot.accept('./reducers.js', () => { 35 | console.log('reducer changed'); 36 | store.replaceReducer(require('./reducers').default); 37 | }); 38 | } 39 | 40 | export default store; 41 | -------------------------------------------------------------------------------- /src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import skuunit from './skuunit/skuunit'; 3 | 4 | const reducers = { 5 | skuunit, 6 | }; 7 | 8 | export default combineReducers(reducers); 9 | -------------------------------------------------------------------------------- /src/redux/skuunit/api.js: -------------------------------------------------------------------------------- 1 | import { get, post } from 'common/request'; 2 | import { 3 | SHOW_UNIT_LIST_URL, 4 | DELETE_UNIT_URL, 5 | } from 'constants/url'; 6 | 7 | /** 8 | * 单元列表查询展示 9 | * @param {*} params 10 | */ 11 | export function showSkuunitListApi(params) { 12 | if (!params) { 13 | return Promise.reject('params is wrong'); 14 | } 15 | return get(SHOW_UNIT_LIST_URL, params) 16 | .then(res => { 17 | // 开发时调试等待效果; 18 | return new Promise((resolve) => { 19 | setTimeout(() => { 20 | return resolve(res); 21 | }, 1000); 22 | }); 23 | }) 24 | .then(res => res.data); 25 | } 26 | 27 | /** 28 | * 删除单元 29 | * @param {} params 30 | */ 31 | export function deleteSkuUnitApi(params) { 32 | if (!params) { 33 | return Promise.reject('params is wrong'); 34 | } 35 | return post(DELETE_UNIT_URL, params) 36 | .then(res => res.data); 37 | } 38 | -------------------------------------------------------------------------------- /src/redux/skuunit/skuunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: niuxiaoyu 3 | * description: 单元 4 | * date: 2018/6/6 5 | */ 6 | import { ACTION_TYPE_ADD_ERROR } from 'constants/constant'; 7 | 8 | import { 9 | showSkuunitListApi, 10 | deleteSkuUnitApi, 11 | } from './api'; 12 | 13 | const SHOW_LOADING = 'skuunit/SHOW_LOADING'; 14 | const HIDE_LOADING = 'skuunit/HIDE_LOADING'; 15 | const SHOW_SKU_UNIT_LIST = 'skuunit/SHOW_SKU_UNIT_LIST'; 16 | const DELETE_SKUUNIT = 'skuunit/DELETE_SKUUNIT'; 17 | 18 | const initialState = { 19 | dataList: [], 20 | isLoading: false, 21 | error: '', 22 | }; 23 | 24 | export default function reducer(state = initialState, action = {}) { 25 | switch (action.type) { 26 | case SHOW_LOADING: 27 | return Object.assign({}, state, { 28 | isLoading: true, 29 | }); 30 | case HIDE_LOADING: 31 | return Object.assign({}, state, { 32 | isLoading: false, 33 | }); 34 | case SHOW_SKU_UNIT_LIST: 35 | return Object.assign({}, state, { 36 | dataList: action.payload.data.dataList, 37 | totalNumber: action.payload.data.totalNumber, 38 | }); 39 | case DELETE_SKUUNIT: 40 | return Object.assign({}, state, { 41 | dataList: _deleteskuUnits(state.dataList, action.payload), 42 | }); 43 | default: 44 | return state; 45 | } 46 | } 47 | 48 | /** 49 | * 删除单元 50 | * @param {*} source 51 | * @param {*} skuUnitIdList 52 | */ 53 | const _deleteskuUnits = (source, params) => { 54 | const { skuUnitIdList } = params; 55 | const newSource = []; 56 | for (let i = 0; i < source.length; i++) { 57 | const target = skuUnitIdList.find(skuUnitId => skuUnitId === source[i].skuUnitId); 58 | if (!target) { 59 | newSource.push(source[i]); 60 | } 61 | } 62 | return newSource; 63 | }; 64 | 65 | /** 66 | * 单元列表查询展示 67 | * @param {*} params 68 | */ 69 | export function showSkuunitList(planName) { 70 | return async dispatch => { 71 | try { 72 | dispatch({ type: SHOW_LOADING }); 73 | const res = await showSkuunitListApi(planName); 74 | await dispatch({ type: SHOW_SKU_UNIT_LIST, payload: res }); 75 | dispatch({ type: HIDE_LOADING }); 76 | } catch (err) { 77 | dispatch({ type: HIDE_LOADING }); 78 | console.log(err); 79 | } 80 | }; 81 | } 82 | 83 | /** 84 | * 删除单元 85 | * @param {} skuUnitIdList 86 | */ 87 | export function deleteSkuUnit(skuUnitIdList) { 88 | return async dispatch => { 89 | try { 90 | const param = { skuUnitIdList }; 91 | const res = await deleteSkuUnitApi(param); 92 | dispatch({ type: DELETE_SKUUNIT, payload: param, res }); 93 | } catch (err) { 94 | dispatch({ type: ACTION_TYPE_ADD_ERROR, payload: { errorMsg: err } }); 95 | } 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /src/routers/PrimaryLayout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useState, lazy } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Switch, Route, Redirect } from 'react-router-dom'; 4 | import { Layout, Spin } from 'antd'; 5 | 6 | import styles from './index.less'; 7 | import { 8 | Header, Sider, Footer, ErrorBoundary, 9 | } from 'components'; 10 | const { Content } = Layout; 11 | 12 | const Skuunit = lazy(() => import(/* webpackChunkName: "home" */ './Skuunit')); 13 | 14 | const PrimaryLayout = (props) => { 15 | const [collapsed, setCollapsed] = useState(false); 16 | 17 | const toggleCollapse = () => { 18 | setCollapsed(!collapsed); 19 | }; 20 | 21 | return ( 22 | 23 | 28 | 29 |
33 | 34 |
35 | }> 36 | 37 | 38 | 39 |
nav2
} /> 40 | 41 |
42 |
43 |
44 |
45 |
46 |