├── .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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
47 |
48 |
49 | );
50 | };
51 |
52 | PrimaryLayout.propTypes = {
53 | history: PropTypes.objectOf(PropTypes.any),
54 | location: PropTypes.objectOf(PropTypes.any),
55 | };
56 |
57 | export default PrimaryLayout;
58 |
--------------------------------------------------------------------------------
/src/routers/Skuunit/columns.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const getColumns = ({
4 | handleDelete,
5 | }) => {
6 | return [
7 | {
8 | title: '名称',
9 | dataIndex: 'skuUnitName',
10 | width: 230,
11 | key: 'skuUnitName',
12 | },
13 | {
14 | title: '集合名称',
15 | dataIndex: 'skuSetName',
16 | key: 'skuSetName',
17 | },
18 | {
19 | title: '操作',
20 | key: 'action',
21 | width: 100,
22 | render: (text, record) => (
23 |
24 | handleDelete(record.skuUnitId, record.skuUnitName)}>删除
25 |
26 | ),
27 | },
28 | ];
29 | };
30 |
31 | export default getColumns;
32 |
--------------------------------------------------------------------------------
/src/routers/Skuunit/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | Table, Modal,
5 | } from 'antd';
6 | import { bindActionCreators } from 'redux';
7 | import { connect } from 'react-redux';
8 |
9 | import { Pages } from 'components';
10 | import getColumns from './columns';
11 |
12 | import { PAGE_DATA } from 'constants/constant';
13 | import {
14 | showSkuunitList,
15 | deleteSkuUnit,
16 | } from 'reduxDir/skuunit/skuunit';
17 |
18 | const confirm = (msg, onOk, onCancel) => {
19 | Modal.confirm({
20 | title: msg,
21 | onOk() {
22 | onOk && onOk();
23 | },
24 | onCancel() {
25 | onCancel && onCancel();
26 | },
27 | });
28 | };
29 |
30 | class Skuunit extends Component {
31 | static propTypes = {
32 | data: PropTypes.shape({
33 | dataList: PropTypes.arrayOf(PropTypes.any),
34 | isLoading: PropTypes.bool,
35 | totalNumber: PropTypes.number,
36 | }),
37 | showSkuunitList: PropTypes.func,
38 | deleteSkuUnit: PropTypes.func,
39 | };
40 |
41 | state = {
42 | pageNo: PAGE_DATA.pageNo,
43 | pageSize: PAGE_DATA.pageSize,
44 | };
45 |
46 | componentDidMount() {
47 | this.loadTable();
48 | }
49 |
50 | getFetchListParams = () => {
51 | const {
52 | page, pageSize,
53 | } = this.state;
54 |
55 | return {
56 | page,
57 | pageSize,
58 | };
59 | }
60 |
61 | /**
62 | * @resetPage: 表示是否重置Pages组件
63 | * @pageInfo: Pages组件的参数
64 | */
65 | loadTable = (resetPage = true, pageInfo) => {
66 | let pageNo;
67 | let pageSize;
68 | if (!resetPage) {
69 | pageNo = pageInfo ? pageInfo.page : this.state.pageNo;
70 | pageSize = pageInfo ? pageInfo.pageSize : this.state.pageSize;
71 | } else {
72 | pageNo = PAGE_DATA.pageNo;
73 | pageSize = PAGE_DATA.pageSize;
74 | }
75 |
76 | this.setState({
77 | pageNo,
78 | pageSize,
79 | }, () => this.props.showSkuunitList(this.getFetchListParams()));
80 | }
81 |
82 | deleteSingleUnit = (unitId, unitName) => {
83 | confirm(`删除单元:${unitName} ?`, () => {
84 | this.props.deleteSkuUnit([unitId]);
85 | });
86 | }
87 |
88 | render() {
89 | const { dataList, isLoading, totalNumber } = this.props.data;
90 | const {
91 | pageNo, pageSize,
92 | } = this.state;
93 |
94 | const columns = getColumns({
95 | handleDelete: this.deleteSingleUnit,
96 | });
97 |
98 | return (
99 |
100 |
108 |
114 |
115 | );
116 | }
117 | }
118 |
119 |
120 | export default connect(
121 | state => ({
122 | data: state.skuunit,
123 | }),
124 | dispatch => bindActionCreators({
125 | showSkuunitList,
126 | deleteSkuUnit,
127 | }, dispatch),
128 | )(Skuunit);
129 |
--------------------------------------------------------------------------------
/src/routers/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * 路由主入口:可以在该页面进行登录权限控制
3 | */
4 | import React from 'react';
5 | import {
6 | Route, Switch, withRouter,
7 | } from 'react-router-dom';
8 | import { hot } from 'react-hot-loader';
9 |
10 | import { LocaleProvider } from 'antd';
11 | import zhCN from 'antd/lib/locale-provider/zh_CN';
12 | // UI示范,如果不需要目前的UI框架,直接替换该组件即可
13 | import PrimaryLayout from './PrimaryLayout';
14 |
15 | const Root = (props) => {
16 | // const { location, history, match } = props;
17 | return (
18 |
19 | } />
20 |
21 | );
22 | };
23 |
24 | export default hot(module)(withRouter(Root));
25 |
--------------------------------------------------------------------------------
/src/routers/index.less:
--------------------------------------------------------------------------------
1 | .main-content {
2 | margin: 24px 16px;
3 | padding: 24px;
4 | background: #fff;
5 | min-height: 280;
6 | }
--------------------------------------------------------------------------------
/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | PC端React+redux脚手架
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const Env = require('./build/env');
3 | const baseConfig = require('./build/webpack.base.conf');
4 | const prodConfig = require('./build/webpack.prod.conf');
5 | const devConfig = require('./build/webpack.dev.conf');
6 |
7 | let config = devConfig;
8 |
9 | if (Env.isQA() || Env.isProduction()) {
10 | config = prodConfig;
11 | }
12 |
13 | module.exports = merge(baseConfig, config);
14 |
--------------------------------------------------------------------------------