Repository: zhangkun-Jser/react-kit Branch: master Commit: df46946e8e9d Files: 48 Total size: 89.8 KB Directory structure: gitextract_wmw74cji/ ├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── asset/ │ └── css/ │ ├── app.less │ ├── base.less │ ├── icon.less │ ├── reset.less │ ├── variables.less │ └── vendor_antd.less ├── build/ │ ├── env.js │ ├── postcss.config.js │ ├── publicPath.js │ ├── theme.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@babel/preset-env", "@babel/preset-react" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-export-default-from", "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "react-hot-loader/babel", [ "import", { "libraryName": "antd", "libraryDirectory": "es", "style": true } ], [ "transform-imports", { "react-router": { "transform": "react-router/${member}", "preventFullImport": true } } ] ], "env": { "production": { "plugins": [ [ "transform-react-remove-prop-types", { "mode": "wrap", "ignoreFilenames": [ "node_modules" ] } ] ] } } } ================================================ FILE: .eslintignore ================================================ node_modules dist ================================================ FILE: .eslintrc.js ================================================ module.exports = { "env": { "node": true, "browser": true, "es6": true, "commonjs": true, }, "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 6, "sourceType": "module", "ecmaFeatures": { "jsx": true, "modules": true, "experimentalObjectRestSpread": true } }, "plugins": [ "react", "react-hooks" ], "rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", // 推荐规则 0="off", 1="warn", 2="error" "no-compare-neg-zero": 2, "no-cond-assign": 2, "no-console": 0,// 不采用 构建时自己去除 "no-constant-condition": 2,// 采用 "no-control-regex": 2,// 采用 "no-debugger": 0,// node端采用,web端不采用 "no-dupe-args": 2, // 采用 "no-dupe-keys": 2,// 采用 "no-duplicate-case": 2,// 采用 "no-empty": 2, // 采用 "no-empty-character-class": 2,// 采用 "no-ex-assign": 2, // 采用 "no-extra-boolean-cast": 2, // 采用 "no-extra-parens": 0, // 不采用 "no-extra-semi": 2,// 采用 "no-func-assign": 2,// 采用 "no-inner-declarations": 2, // 采用 "no-invalid-regexp": 2,// 采用 "no-irregular-whitespace": 2,// 采用 "no-obj-calls": 2,// 采用 "no-regex-spaces": 2,// 采用 "no-sparse-arrays": 2,// 采用 "no-unexpected-multiline": 2,// 采用 "no-unreachable": 2,// 采用 "no-unsafe-finally": 2,// 采用 "no-unsafe-negation": 2,// 采用 "use-isnan": 2,// 采用 "valid-typeof": 2,// 采用 "no-case-declarations": 2, // 采用 "no-empty-pattern": 2,// 采用 "no-fallthrough": 2,// 采用 "no-global-assign": 2,// 采用 "no-octal": 2,// 采用 "no-redeclare": 2,// 采用 "no-self-assign": 2,// 采用 "no-unused-labels": 2,// 采用 "no-useless-escape": 2,// 采用 "no-delete-var": 2,// 采用 "no-undef": 2,// 采用 "no-unused-vars": 2,// 采用 "no-mixed-spaces-and-tabs": 2,// 采用 "constructor-super": 2,// 采用 "no-class-assign": 2,// 采用 "no-const-assign": 2,// 采用 "no-dupe-class-members": 2,// 采用 "no-new-symbol": 2,// 采用 "no-this-before-super": 2,// 采用 "require-yield": 2,// 采用 // 拓展规则 "for-direction": 2,// 采用 "getter-return": 2,// 采用 "no-await-in-loop": 2,// 采用 "no-prototype-builtins": 2,// 采用 "no-template-curly-in-string": 2,// 采用 // valid-jsdoc 不采用,基础组件强制使用 "accessor-pairs": 2,// 采用 "array-callback-return": 2,// 采用 "block-scoped-var": 2,// 采用 "class-methods-use-this": 1,//采用(warning,可能报错很多) "curly": 1,// 采用 warning "default-case": 2,// 采用 "eqeqeq": 1,// 采用 warning "guard-for-in": 2,// 采用 "no-caller": 2, // 采用 "no-eval": 2,// 采用 "no-extend-native": 2,// 采用 "no-extra-label": 2,// 采用 "no-floating-decimal": 2,// 采用 "no-implied-eval": 2,// 采用 // 临时关闭,有误报 "no-invalid-this": 0,// 采用 "no-iterator": 2,// 采用 "no-labels": 2,// 采用 "no-lone-blocks": 2,// 采用 "no-throw-literal": 2,// 采用 "no-unmodified-loop-condition": 1,// 采用 warning "no-useless-concat": 2,// 采用 "no-useless-return": 2,// 采用 "radix": 2,// 采用 "require-await": 2,// 采用 //关于Node.js或在浏览器中使用CommonJS的相关规则 "callback-return": 2,// 采用 // 临时关闭 "global-require": 0,// 采用 "handle-callback-err": 2,// 采用 "no-buffer-constructor": 2,// 采用 "no-mixed-requires": 2,// 采用 "no-new-require": 2,// 采用 // 代码风格 // 在数组开括号后和闭括号前强制换行 "array-bracket-newline": 0, // 不采用 // 禁止或强制在括号内使用空格 "array-bracket-spacing": [2, "never"], // 采用,禁止在数组括号内出现空格, // 强制数组元素间出现换行 "array-element-newline": 0, // 不采用 // 禁止或强制在代码块中开括号前和闭括号后有空格 "block-spacing": [2, "always"], // 采用,要求使用一个或多个空格 // 强制在代码块中使用一致的大括号风格 "brace-style": [2, "1tbs", { allowSingleLine: true }], // 强制 one true brace style(一种代码风格,将大括号放在控制语句或声明语句同一行的位置) // 可以有例外情况, allowSingleLine允许块的开括号和闭括号在 同一行 // 要求使用骆驼拼写法 "camelcase": [2, { properties: "never" }], // 采用,但不检查属性名称 // 强制或禁止对注释的第一个字母大写 "capitalized-comments": 0, // 不采用 // 要求或禁止使用拖尾逗号 "comma-dangle": [2, { arrays: "always-multiline", objects: "always-multiline", imports: "always-multiline", exports: "always-multiline", functions: "always-multiline", }], // 采用,当最后一个元素或属性与闭括号 ] 或 } 在 不同的行时,要求使用拖尾逗号;当在 同一行时,禁止使用拖尾逗号。 // 强制在逗号周围使用空格 "comma-spacing": [2, { before: false, after: true }], // 采用,禁止在逗号前使用空格,要求在逗号后使用一个或多个空格 // 逗号风格 "comma-style": [2, "last", { // last 要求逗号放在数组元素、对象属性或变量声明之后,且在同一行 exceptions: { // 额外规则 包含与 JavaScript 代码的抽象语法树 (AST) 的节点类型对应的属性: ArrayExpression: false, // 忽略数组字面量的逗号风格 ArrayPattern: false, // 忽略数组的解构赋值语句中的逗号风格 ArrowFunctionExpression: false, // 忽略箭头函数表达式的参数中的逗号风格 CallExpression: false, // 忽略函数调用的参数中的逗号风格 FunctionDeclaration: false, // 忽略函数声明的参数中的逗号风格 FunctionExpression: false, // 忽略函数表达式的参数中的逗号风格 ImportDeclaration: false, // 忽略 import 语句中的逗号风格 ObjectExpression: false, // 忽略对象字面量的逗号风格 ObjectPattern: false, // 忽略对象的解构赋值中的逗号风格 VariableDeclaration: false, // 忽略变量声明的逗号风格 NewExpression: false, // 忽略构造函数表达式参数中的逗号风格 } // !!! 注意,以上配置全为false,即不忽略 }], // 采用 // 禁止或强制在计算属性中使用空格 "computed-property-spacing": [2, 'never'], // 采用,禁止在计算属性内使用空格 // 要求一致的 This "consistent-this": 0, // 不采用 // 要求或禁止文件末尾保留一行空行 "eol-last": [2, 'always'], // 采用 强制使用换行 (LF) // 要求或禁止在函数标识符和其调用之间有空格 "func-call-spacing": [2, 'never'], // 禁止在函数名和开括号之间有空格 // 要求函数名与赋值给它们的变量名或属性名相匹配 "func-name-matching": 0, // 不采用 // 要求或禁止命名的 function 表达式 "func-names": 1, // 警告 // 强制 function 声明或表达式的一致性 "func-style": 0, // 不采用 // 强制在函数括号内使用一致的换行 "function-paren-newline": [2, 'consistent'], // 采用, 要求每个括号使用一致的换行。如果一个括号有换行,另一个括号没有换行,则报错。 // 禁止使用指定的标识符 "id-blacklist": 0, // 不采用 // 强制标识符的最小和最大长度 "id-length": 0, // 不采用 // 要求标识符匹配一个指定的正则表达式 "id-match": 0, // 不采用 // 强制隐式返回的箭头函数体的位置 "implicit-arrow-linebreak": [2, 'beside'], // 采用 禁止在箭头函数体之前出现换行 // 强制使用一致的缩进 "indent": [2, 2, { SwitchCase: 1, VariableDeclarator: 1, outerIIFEBody: 1, // MemberExpression: null, FunctionDeclaration: { parameters: 1, body: 1 }, FunctionExpression: { parameters: 1, body: 1 }, CallExpression: { arguments: 1 }, ArrayExpression: 1, ObjectExpression: 1, ImportDeclaration: 1, flatTernaryExpressions: false, // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js ignoredNodes: ['JSXElement', 'JSXElement > *', 'JSXAttribute', 'JSXIdentifier', 'JSXNamespacedName', 'JSXMemberExpression', 'JSXSpreadAttribute', 'JSXExpressionContainer', 'JSXOpeningElement', 'JSXClosingElement', 'JSXText', 'JSXEmptyExpression', 'JSXSpreadChild'], ignoreComments: false }], // 采用 一般2个缩进,特殊语句见配置 // 强制在 JSX 属性中一致地使用双引号或单引号 "jsx-quotes": 0, // 不采用 // 强制在对象字面量的属性中键和值之间使用一致的间距 "key-spacing": [2, { beforeColon: false, afterColon: true }], // 采用, 禁止在对象字面量的键和冒号之间存在空格,要求在对象字面量的冒号和值之间存在至少有一个空格 // 强制在关键字前后使用一致的空格 "keyword-spacing": [2, { before: true, // 要求在关键字之前至少有一个空格 after: true, // 要求在关键字之后至少有一个空格 overrides: { // 允许覆盖指定的关键字的空格风格 return: { after: true }, throw: { after: true }, case: { after: true } } }], // 采用 // 强制行注释的位置 "line-comment-position": 0, // 不采用 // 强制使用一致的换行风格 "linebreak-style": [2, 'unix'], // 采用,强制使用 Unix 换行符: \n。 // 要求在注释周围有空行 "lines-around-comment": 0, //不采用 // 要求或禁止类成员之间出现空行 "lines-between-class-members": [2, 'always', { exceptAfterSingleLine: false }],// 采用 // 强制可嵌套的块的最大深度 "max-depth": 0, // 不采用 // 强制一行的最大长度 "max-len": [2, 100, 2, { ignoreUrls: true, ignoreComments: false, ignoreRegExpLiterals: true, ignoreStrings: true, ignoreTemplateLiterals: true, }],// 采用,最长100,tab字符宽度为2 // 强制最大行数 "max-lines": 0, // 不采用 // 强制回调函数最大嵌套深度 "max-nested-callbacks": 0, // 不采用 // 强制函数定义中最多允许的参数数量 "max-params": 0, //不采用 // 强制函数块最多允许的的语句数量 "max-statements": 0, // 不采用 // 强制每一行中所允许的最大语句数量 "max-statements-per-line": 0, // 不采用 // 强制对多行注释使用特定风格 "multiline-comment-style": 0, // 不采用 // 要求或禁止在三元操作数中间换行 "multiline-ternary": 0, // 不采用 // 要求构造函数首字母大写 "new-cap": [2, { newIsCap: true, // 要求调用 new 操作符时有首字母大小的函数 newIsCapExceptions: [], capIsNew: false, // 要求调用首字母大写的函数时有 new 操作符 capIsNewExceptions: ['Immutable.Map', 'Immutable.Set', 'Immutable.List'], // 允许调用指定的首字母大写的函数时没有 new 操作符 }], // 采用 // 要求调用无参构造函数时有圆括号 "new-parens": 2, // 采用 // 要求方法链中每个调用都有一个换行符 "newline-per-chained-call": [2, { ignoreChainWithDepth: 4 }], // 允许在同一行成链的深度为4 // 禁用 Array 构造函数 "no-array-constructor": 2, // 采用 // 禁用按位运算符 "no-bitwise": 2, // 采用 // 禁用 continue 语句 "no-continue": 2, // 采用 // 禁止在代码后使用内联注释 "no-inline-comments": 0, // 不采用 // 禁止 if 作为唯一的语句出现在 else 语句中 "no-lonely-if": 2, // 采用 // 禁止混合使用不同的操作符 "no-mixed-operators": [2, { groups: [ ['%', '**'], ['%', '+'], ['%', '-'], ['%', '*'], ['%', '/'], ['**', '+'], ['**', '-'], ['**', '*'], ['**', '/'], ['&', '|', '^', '~', '<<', '>>', '>>>'], ['==', '!=', '===', '!==', '>', '>=', '<', '<='], ['&&', '||'], ['in', 'instanceof'] ], allowSamePrecedence: false }], // 采用 // 禁止连续赋值 "no-multi-assign": 2, // 采用 // 禁止出现多行空行 "no-multiple-empty-lines": [2, { max: 2, maxEOF: 0 }], // 采用 // 禁用否定的表达式 "no-negated-condition": 0, // 不采用 // 禁用嵌套的三元表达式 "no-nested-ternary": 2, // 采用 // 禁用 Object 的构造函数 "no-new-object": 2, // 采用 // 禁用一元操作符 ++ 和 -- // 临时关闭 "no-plusplus": 0, // 采用 // 禁用特定的语法 "no-restricted-syntax": [ 2, { selector: 'ForInStatement', 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.', }, { selector: 'ForOfStatement', 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.', }, { selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', }, { selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', }, ], // 采用 // 禁用 tab "no-tabs": 2, // 采用 // 禁用三元操作符 "no-ternary": 0, //不采用 // 禁用行尾空格 "no-trailing-spaces": [2, { skipBlankLines: false, ignoreComments: false, }], // 采用 // 禁止标识符中有悬空下划线(临时关闭) /* "no-underscore-dangle": [2, { allow: [], allowAfterThis: false, allowAfterSuper: false, enforceInMethodNames: false, }], */ // 采用 // 禁止可以在有更简单的可替代的表达式时使用三元操作符 "no-unneeded-ternary": [2, { defaultAssignment: false }], // 禁止属性前有空白 "no-whitespace-before-property": 2, //采用 // 强制单个语句的位置 "nonblock-statement-body-position": [2, 'beside', { overrides: {} }], // 采用 // 强制大括号内换行符的一致性 "object-curly-newline": [2, { ObjectExpression: { minProperties: 4, multiline: true, consistent: true }, ObjectPattern: { minProperties: 4, multiline: true, consistent: true }, ImportDeclaration: { minProperties: 4, multiline: true, consistent: true }, ExportDeclaration: { minProperties: 4, multiline: true, consistent: true }, }],// 采用 // 强制在大括号中使用一致的空格 "object-curly-spacing": [2, 'always'], // 采用 // 强制将对象的属性放在不同的行上 "object-property-newline": [2, { allowAllPropertiesOnSameLine: true, }], // 强制函数中的变量要么一起声明要么分开声明 "one-var": [2, 'never'], // 采用, 要求每个作用域有多个变量声明 // 要求或禁止在变量声明周围换行 "one-var-declaration-per-line": [2, 'always'], // 采用,强制每个变量声明都换行 // 要求或禁止在可能的情况下使用简化的赋值操作符 "operator-assignment": [2, 'always'], //采用,要求尽可能地简化赋值操作 // 强制操作符使用一致的换行符 "operator-linebreak": [2, 'before', { overrides: { '=': 'none' } }], // 采用, 要求把换行符放在操作符前面 // 要求或禁止块内填充 "padded-blocks": [2, { blocks: 'never', classes: 'never', switches: 'never' }], // 采用 // 要求或禁止在语句间填充空行 "padding-line-between-statements": 0, // 不采用 // 要求对象字面量属性名称用引号括起来 "quote-props": [2, 'as-needed', { keywords: false, unnecessary: true, numbers: false }], //采用 // 强制使用一致的反勾号、双引号或单引号 "quotes": [2, 'single', { avoidEscape: true }], // 采用 // 要求使用 JSDoc 注释 "require-jsdoc": 0, // 不采用 // 要求或禁止使用分号代替 ASI "semi": [2, 'always'], // 采用,要求在语句末尾使用分号 // 强制分号之前和之后使用一致的空格 "semi-spacing": ['error', { before: false, after: true }], // 采用 // 强制分号的位置 "semi-style": [2, 'last'], // 采用,强制分号出现在句子末尾。 // 要求对象属性按序排列 "sort-keys": 0, // 不采用 // 要求同一个声明块中的变量按顺序排列 "sort-vars": 0, // 不采用 // 强制在块之前使用一致的空格 "space-before-blocks": 2, // 采用 // 强制在 function的左括号之前使用一致的空格 "space-before-function-paren": [2, { anonymous: 'always', named: 'never', asyncArrow: 'always' }], // 采用 // 强制在圆括号内使用一致的空格 "space-in-parens": [2, 'never'], // 采用,强制圆括号内没有空格 // 要求操作符周围有空格 "space-infix-ops": 2, // 采用 // 强制在一元操作符前后使用一致的空格 "space-unary-ops": [2, { words: true, nonwords: false, overrides: { }, }], // 采用 // 强制在注释中 // 或 /* 使用一致的空格 "spaced-comment": [2, 'always', { line: { exceptions: ['-', '+'], markers: ['=', '!'], // space here to support sprockets directives }, block: { exceptions: ['-', '+'], markers: ['=', '!'], // space here to support sprockets directives balanced: true, } }], // 采用 // 强制在 switch 的冒号左右有空格 "switch-colon-spacing": [2, { after: true, before: false }], // 采用 // 要求或禁止在模板标记和它们的字面量之间有空格 "template-tag-spacing": [2, 'never'], // 禁止在一个标记的函数和它的模板字面量之间有空格 // 要求或禁止 Unicode 字节顺序标记 (BOM) "unicode-bom": [2, 'never'], // 采用 // 要求正则表达式被括号括起来 "wrap-regex": 0, // 不采用 // react相关配置 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md 'react/display-name': ['off', { ignoreTranspilerName: false }], // Forbid certain propTypes (any, array, object) // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-prop-types.md 'react/forbid-prop-types': ['error', { forbid: ['any', 'array', 'object'], checkContextTypes: true, checkChildContextTypes: true, }], // Forbid certain props on DOM Nodes // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-dom-props.md 'react/forbid-dom-props': ['off', { forbid: [] }], // Enforce boolean attributes notation in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 'react/jsx-boolean-value': ['error', 'never', { always: [] }], // Validate closing bracket location in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], // Validate closing tag location in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md 'react/jsx-closing-tag-location': 'error', // Enforce or disallow spaces inside of curly braces in JSX attributes // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }], // Enforce event handler naming conventions in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md 'react/jsx-handler-names': ['off', { eventHandlerPrefix: 'handle', eventHandlerPropPrefix: 'on', }], // Validate props indentation in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md 'react/jsx-indent-props': ['error', 2], // Validate JSX has key prop when in array or iterator // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md 'react/jsx-key': 'off', // Limit maximum of props on a single line in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], // Prevent usage of .bind() in JSX props // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md 'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true, allowBind: false, }], // Prevent duplicate props in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], // Prevent usage of unwrapped JSX strings // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md 'react/jsx-no-literals': ['off', { noStrings: true }], // Disallow undeclared variables in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md 'react/jsx-no-undef': 'error', // Enforce PascalCase for user-defined JSX components // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md 'react/jsx-pascal-case': ['error', { allowAllCaps: true, ignore: [], }], // Enforce propTypes declarations alphabetical sorting // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md 'react/sort-prop-types': ['off', { ignoreCase: true, callbacksLast: false, requiredFirst: false, sortShapeProp: true, }], // Deprecated in favor of react/jsx-sort-props 'react/jsx-sort-prop-types': 'off', // Enforce props alphabetical sorting // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md 'react/jsx-sort-props': ['off', { ignoreCase: true, callbacksLast: false, shorthandFirst: false, shorthandLast: false, noSortAlphabetically: false, reservedFirst: true, }], // Enforce defaultProps declarations alphabetical sorting // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-sort-default-props.md 'react/jsx-sort-default-props': ['off', { ignoreCase: true, }], // Prevent React to be incorrectly marked as unused // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md 'react/jsx-uses-react': ['error'], // Prevent variables used in JSX to be incorrectly marked as unused // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md 'react/jsx-uses-vars': 'error', // Prevent usage of dangerous JSX properties // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md 'react/no-danger': 'warn', // Prevent usage of deprecated methods // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md 'react/no-deprecated': ['error'], // Prevent usage of setState in componentDidMount // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md // this is necessary for server-rendering 'react/no-did-mount-set-state': 'off', // Prevent usage of setState in componentDidUpdate // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md 'react/no-did-update-set-state': 'error', // Prevent usage of setState in componentWillUpdate // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-will-update-set-state.md 'react/no-will-update-set-state': 'error', // Prevent direct mutation of this.state // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md 'react/no-direct-mutation-state': 'off', // Prevent usage of isMounted // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md 'react/no-is-mounted': 'error', // Prevent multiple component definition per file // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md 'react/no-multi-comp': ['error', { ignoreStateless: true }], // Prevent usage of setState // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md 'react/no-set-state': 'off', // Prevent using string references // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md 'react/no-string-refs': 'error', // Prevent usage of unknown DOM property // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md 'react/no-unknown-property': 'error', // Require ES6 class declarations over React.createClass // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md 'react/prefer-es6-class': ['error', 'always'], // Require stateless functions when not using lifecycle methods, setState or ref // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }], // Prevent missing props validation in a React component definition // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md 'react/prop-types': ['error', { ignore: [], customValidators: [], skipUndeclared: false }], // Prevent missing React when using JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md 'react/react-in-jsx-scope': 'error', // Require render() methods to return something // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md 'react/require-render-return': 'error', // Prevent extra closing tags for components without children // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md 'react/self-closing-comp': 'error', // Enforce component methods order // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/sort-comp.md 'react/sort-comp': ['error', { order: [ 'static-methods', 'instance-variables', 'lifecycle', '/^on.+$/', 'getters', 'setters', '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', 'instance-methods', 'everything-else', 'rendering', ], groups: { lifecycle: [ 'displayName', 'propTypes', 'contextTypes', 'childContextTypes', 'mixins', 'statics', 'defaultProps', 'constructor', 'getDefaultProps', 'getInitialState', 'state', 'getChildContext', 'componentWillMount', 'componentDidMount', 'componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate', 'componentWillUnmount', ], rendering: [ '/^render.+$/', 'render' ], }, }], // Prevent missing parentheses around multilines JSX // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-wrap-multilines.md 'react/jsx-wrap-multilines': ['error', { declaration: 'parens-new-line', assignment: 'parens-new-line', return: 'parens-new-line', arrow: 'parens-new-line', condition: 'parens-new-line', logical: 'parens-new-line', prop: 'parens-new-line', }], // 当元素为多行时,要求JSX元素中的第一个属性位于新行上。 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md 'react/jsx-first-prop-new-line': [2, 'multiline-multiprop'], // 采用,如果JSX标签占用多个行并且有多个属性,则第一个属性应该总是放置在一个新行上。这是默认值。 // 在JSX的等号两侧是否强制留有空格 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md 'react/jsx-equals-spacing': [2, 'never'], // 采用,等号周围的不允许空格 // 强制jsx的缩进风格 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md 'react/jsx-indent': [2, 2], // 采用,两个空格作为缩进 // 不允许不安全的target='_blank'用法, 具体见连接说明 // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-no-target-blank.md 'react/jsx-no-target-blank': [2, { enforceDynamicLinks: 'always' }], // 采用,如果是动态链接则不强制 // 只有.jsx文件允许写JSX语法 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md 'react/jsx-filename-extension': [2, { extensions: ['.jsx'] }],// 采用 // 防止JS注释意外的作为文本注入到JSX中 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md 'react/jsx-no-comment-textnodes': 2, // 采用,jsx的注释问题 // 不允许使用react或者reactdom的render方法的返回值 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md 'react/no-render-return-value': 2, // 采用,建议采用ref // 要求有shouldComponentUpdate方法, 或者采用PureRenderMixin // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-optimization.md 'react/require-optimization': [0, { allowDecorators: [] }], // 不采用 // 禁止使用findDOMNode()方法 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md 'react/no-find-dom-node': 2, // 采用 // 在Components中禁用一些特定的props // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-component-props.md 'react/forbid-component-props': 0, // 不采用 // 禁用特定元素 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-elements.md 'react/forbid-elements': 0, // 不采用 // 避免在children和dangerouslySetInnerHTML属性共存时出现问题 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md 'react/no-danger-with-children': 2, // 采用 // 防止未使用的propType定义 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md 'react/no-unused-prop-types': [2, { customValidators: [ ], skipShapeProps: true, }], // 采用 // 要求样式的值为对象或者变量 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md 'react/style-prop-object': 2, // 采用 // 禁止无效字符的出现 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md 'react/no-unescaped-entities': 2, // 采用 // 禁止通过children props来传值 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md 'react/no-children-prop': 2, // 采用 // 在JSX打开和关闭括号内和周围验证空格 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-tag-spacing.md 'react/jsx-tag-spacing': [2, { closingSlash: 'never', beforeSelfClosing: 'always', afterOpening: 'never', beforeClosing: 'never', }], // 采用, 不留空格 // 强制在闭JSX元素标签之前留空格 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md // Deprecated in favor of jsx-tag-spacing 'react/jsx-space-before-closing': 0, // 不采用 // 禁止使用数组的索引作为元素的key属性 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md 'react/no-array-index-key': 2, // 采用, 原因可以见链接 // 强制给每个不是必须的属性定义一个默认值 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/require-default-props.md 'react/require-default-props': [0, { forbidDefaultForRequired: true, }], // 采用, 写了isRequired的属性则禁止设置默认值 // 禁止使用没有被export的prototype // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md 'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }], // 设置为警告级别 // 不要给单标签的jsx元素传入children // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md 'react/void-dom-elements-no-children': 2, // 采用 // 强制所有的默认prop都对应一个非必须的proptype // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/default-props-match-prop-types.md 'react/default-props-match-prop-types': [2, { allowRequiredDefaults: false }], // 采用 // 继承React.PureComponent时,禁止使用shouldComponentUpdate // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/no-redundant-should-component-update.md 'react/no-redundant-should-component-update': 2, // 采用s // 禁止存在未使用的state值 // https://github.com/yannickcr/eslint-plugin-react/pull/1103/ 'react/no-unused-state': 2, // 采用 // 强制布尔值属性命名的的一致性 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/boolean-prop-naming.md 'react/boolean-prop-naming': 0, // 不采用 // 防止常见的套管缺陷 // https://github.com/yannickcr/eslint-plugin-react/blob/73abadb697034b5ccb514d79fb4689836fe61f91/docs/rules/no-typos.md 'react/no-typos': 2, // 采用 // 禁止在props和children中使用无意义的大括号 // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md 'react/jsx-curly-brace-presence': [2, { props: 'never', children: 'never' }], // 采用 // 每行一个jsx元素 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-one-expression-per-line.md // TODO: re-enable when an option for text children is available 'react/jsx-one-expression-per-line': 0, // 不采用 // 强制使用desconstruct的一致性 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/destructuring-assignment.md // TODO: re-enable when component detection is fixed 'react/destructuring-assignment': 0, // 不采用 // 禁止在this.setState中使用this.state // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-access-state-in-setstate.md 'react/no-access-state-in-setstate': 2, // 采用 // 禁止使用没有显式type属性的按钮元素 // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/button-has-type.md 'react/button-has-type': [2, { button: true, submit: true, reset: false, }], // 采用 // 确保内联标签两边有空格 'react/jsx-child-element-spacing': 0, // 不采用 // 禁止在无状态组件中使用this // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-this-in-sfc.md 'react/no-this-in-sfc': 2, // 采用 // 检查jsx最大深度 // https://github.com/yannickcr/eslint-plugin-react/blob/abe8381c0d6748047224c430ce47f02e40160ed0/docs/rules/jsx-max-depth.md 'react/jsx-max-depth': 0, // 不采用 // 禁止在jsx props之间存在多个空格 // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-props-no-multi-spaces.md 'react/jsx-props-no-multi-spaces': 2, // 采用 // 不允许使用UNSAFE_ methods // https://github.com/yannickcr/eslint-plugin-react/blob/157cc932be2cfaa56b3f5b45df6f6d4322a2f660/docs/rules/no-unsafe.md 'react/no-unsafe': 0, // 不采用 } }; ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log yarn-error.log target node src/_tmp static dist coverage rekit_temp_app _book .nyc_output .tmp .DS_Store Thumbs.db .vscode ================================================ FILE: README.md ================================================ # react-starter PC 端 react+redux 的脚手架,持续进行更新迭代。 ## 特点 1. 当中集成了当前react生态的几个主流技术栈: - react 16.12.0 - ant design 3.26.2 - react-router 5.1.2 - redux 4.0.4 - redux-thunk 2.2.0 - webpack 4.41.2 2. 符合目前前后端分离项目的开发、部署、测试、上线各环节及操作的要求。 3. 优化了传统redux-thunk中action、reducer书写上的不便。 4. 根据路由地址,懒加载所需模块,就是说目前脚手架中提供的示例代码无需删除,只需要确保路由无效即可 5. 增加错误边界,捕捉嵌套组件在生命过程中发生的错误 6. 封装 axios(`src/common/request.js`),对失败的请求进行了统一处理,使用方法: ``` import { get, post, put, del } from 'common/request'; post(URL, params).then(res => {}); // 如果需要自行处理请求失败的情况,也可以直接引入 axios 实例 import axios from 'common/request'; axios.post(URL, params) .then(res => {}) .catch(error => {}) ``` ## 文件目录 > 注意:带 ✎ 号的表示示例配置,可以根据自己的项目配置自行替换;带 +/- 号的表示示例组件,可保留修改或删除 ``` ├─asset // ========================= 静态资源目录 │ ├─css | | ├─app.less // ========================= 样式文件入口 | | ├─base.less // ========================= 基础样式 | | ├─icon.less // ========================= iconfont 样式(✎) | | ├─reset.less // ========================= 样式重置文件 | | ├─variables.less // ========================= 变量文件(✎) | | └─vendor_antd.less // ========================= 专门用于覆盖 antd 样式(✎) │ ├─font // ========================= iconfont文件,在icon.less文件中引用 | | ├─iconfont.eot(+/-) | | ├─iconfont.svg(+/-) | | ├─iconfont.ttf(+/-) | | └─iconfont.woff(+/-) │ └─img // ========================= 图片资源存放目录 │ ├─build // ========================= webpack 相关配置目录 │ ├─postcss.config.js // ========================= postcss 相关配置 │ ├─publicPath.js // ========================= 发布路径(✎) │ ├─theme.js // ========================= antd 主题配置(✎) │ ├─webpack.base.conf.js // ========================= webpack 公用项配置 │ ├─webpack.dev.conf.js // ========================= webpack 开发/联调环境项配置 │ └─webpack.prod.conf.js // ========================= webpack 生产/测试环境项配置 │ ├─mock // ========================= mock 文件目录 │ ├─config │ ├─data │ └─template │ ├─src // ========================= js 源码目录 │ ├─common // ========================= 公共模块目录 │ │ └─request.js // ========================= 对axios进行封装 │ │ │ ├─components // ========================= 纯组件目录 │ │ ├─Layout // ========================= 布局相关组件目录 │ │ │ ├─Footer.jsx(+/-) │ │ │ ├─Header.jsx(+/-) │ │ │ └─Sider.jsx(+/-) │ │ ├─Pages(+/-) // ========================= 分页组件 │ │ ├─ErrorBoundary.jsx // ========================= 错误边界组件 │ │ └─index.js(+/-) │ │ │ ├─constants │ │ ├─constant.js // ========================= 常量集中管理目录(✎) │ │ └─url.js // ========================= url集中管理目录(✎) │ │ │ ├─redux // ========================= redux 处理目录 │ │ ├─skuunit(+/-) │ │ ├─configureStore.js // ========================= store/中间件配置 │ │ └─reducers.js // ========================= 所有reducer入口(✎) │ │ │ ├─routers // ========================= 按路由划分的业务模块目录 │ │ ├─Skuunit(+/-) │ │ ├─index.jsx(✎) │ │ └─PrimaryLayout.jsx(+/-) │ │ │ └─app.jsx // ========================= 项目入口 │ ├─.babelrc // ========================= babel配置文件 ├─.eslintignore ├─.eslintrc.js // ========================= eslint规则文件 ├─.gitignore ├─index.html ├─package-lock.json ├─package.json ├─README.md(✎) ├─template.html(✎) // ========================= html嵌入模板 └─webpack.config.js // ========================= webpack配置入口 ``` ## Changelog - 2019.07.15 - [x] src目录支持使用less - [x] 兼容IE11 - 2019.06.11 - [x] 更改UI - 2019.06.03 - [x] 升级babel 7 - [x] 更新示例组件 - [x] 修复eslint报错 - [x] 完善README.md - 2019.05.22 - [x] 完成基础版本,给出规范目录结构,满足打包构建路由懒加载数据管理错误处理一条龙服务。 ## babel7依赖说明 基础依赖: - @babel/cli: 为babel的脚手架工具 - @babel/core: babel-core是作为babel的核心存在,babel的核心api都在这个模块里面,比如:transform,用于字符串转码得到AST - @babel/plugin-proposal-class-properties: 解析class类的属性 - @babel/plugin-proposal-decorators: 解析装饰器模式语法,如使用react-redux的@connect - @babel/plugin-proposal-export-default-from: 解析export xxx from 'xxx'语法 - @babel/plugin-transform-runtime: 防止转换后的代码重复 - @babel/preset-env : 官方解释“用于编写下一代JavaScript的编译器”,编译成浏览器认识的JavaScript标准 - @babel/preset-react: 用于编译react的jsx,开发react应用必备 - babel-loader: 就是用于编译JavaScript代码 - babel-plugin-import: 用于进行按需加载 代码优化相关依赖: - [babel-plugin-transform-imports](https://www.npmjs.com/package/babel-plugin-transform-imports):去除未使用的模块 - [babel-plugin-transform-react-remove-prop-types](https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types): 生产环境下移除不必要的 React propTypes,以减少代码体积 ## 参考文章 - 2019.06.03 - [Webpack4+Babel7优化70%速度](https://juejin.im/post/5c763885e51d457380771ab0#heading-11) - [一口(很长的)气了解 babel](https://juejin.im/post/5c19c5e0e51d4502a232c1c6#heading-0) - 2019.05.22 - [Web Performance Optimization with webpack](https://developers.google.com/web/fundamentals/performance/webpack/) - [Webpack 4 配置最佳实践](https://juejin.im/post/5b304f1f51882574c72f19b0#heading-1) - [Webpack 4 默认分包策略](https://panjiachen.github.io/awesome-bookmarks/blog/webpack/webpack4-b.html) - [webpack-libs-optimizations](https://github.com/GoogleChromeLabs/webpack-libs-optimizations) 朋友们如果有一些对本项目得建议,或者想法欢迎去 github 提 issues,我将持续改进优化该项目 ================================================ FILE: asset/css/app.less ================================================ /* 样式文件入口 */ @import 'reset.less'; @import 'variables.less'; @import 'icon.less'; @import 'base.less'; // 样式覆盖 @import url("vendor_antd.less"); ================================================ FILE: asset/css/base.less ================================================ /* 基础样式 */ html, body { font-size: 14px; height: 100%; } #app { height: 100%; } .fl { .fl(); } .fr { .fr(); } .ml15 { margin-left: 5px; } .ml10 { margin-left: 10px; } .ml15 { margin-left: 15px; } .ml20 { margin-left: 20px; } .mr5 { margin-right: 5px; } .mr10 { margin-right: 10px; } .mr15 { margin-right: 15px; } .mr20 { margin-right: 20px; } .m15 { margin: 15px; } .mt15 { margin-top: 15px; } .mt30 { margin-top: 30px; } .mt45 { margin-top: 45px; } .mb10 { margin-bottom: 10px; } .mb15 { margin-bottom: 15px; } .mb20 { margin-bottom: 20px; } ================================================ FILE: asset/css/icon.less ================================================ /* iconfont样式 */ @font-face { font-family: "iconfont"; // src: url('../font/iconfont.eot'); /* IE9*/ // src: url('../font/iconfont.eot') format('embedded-opentype'), /* IE6-IE8 */ // url('../font/iconfont.woff') format('woff'), /* chrome, firefox */ // url('../font/iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ // url('../font/iconfont.svg') format('svg'); /* iOS 4.1- */ } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; display: inline-block; vertical-align: baseline; text-align: center; text-transform: none; line-height: 1; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } ================================================ FILE: asset/css/reset.less ================================================ /* 样式重置文件 */ // // 1. Remove default margin padding. // html, body, div, ul, li, h1, h2, h3, h4, h5, h6, p, dl, dt, dd, ol, form, input, textarea, th, td, select { margin: 0; padding: 0; } // HTML5 display definitions // ========================================================================== // // Correct `block` display not defined for any HTML5 element in IE 8/9. // Correct `block` display not defined for `details` or `summary` in IE 10/11 // and Firefox. // Correct `block` display not defined for `main` in IE 11. // article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } // // 1. Correct `inline-block` display not defined in IE 8/9. // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. // audio, canvas, progress, video { display: inline-block; vertical-align: baseline; } // // Prevent modern browsers from displaying `audio` without controls. // Remove excess height in iOS 5 devices. // audio:not([controls]) { display: none; height: 0; } // // Address `[hidden]` styling not present in IE 8/9/10. // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. // [hidden], template { display: none; } // Links // ========================================================================== // // Remove the gray background color from active links in IE 10. // a { background-color: transparent; } // // Improve readability of focused elements when they are also in an // active/hover state. // a:active, a:hover { outline: 0; } // Text-level semantics // ========================================================================== // // Address styling not present in IE 8/9/10/11, Safari, and Chrome. // abbr[title] { border-bottom: 1px dotted; } // // Address style set to `bolder` in Firefox 4+, Safari, and Chrome. // b, strong { font-weight: bold; } // // Address styling not present in Safari and Chrome. // dfn { font-style: italic; } // // Unified "h1" - "h6" font weight // h1, h2, h3, h4, h5, h6{font-weight:normal;} // // Remove ul-ol the default styles // ul,ol { list-style: none; } // // Address styling not present in IE 8/9. // mark { background: #ff0; color: #000; } // // Address inconsistent and variable font size in all browsers. // small { font-size: 80%; } // // Prevent `sub` and `sup` affecting `line-height` in all browsers. // sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } // Embedded content // ========================================================================== // // Remove border when inside `a` element in IE 8/9/10. // img { border: 0; } // // Correct overflow not hidden in IE 9/10/11. // svg:not(:root) { overflow: hidden; } // Grouping content // ========================================================================== // // Address margin not present in IE 8/9 and Safari. // figure { margin: 1em 40px; } // // Address differences between Firefox and other browsers. // hr { box-sizing: content-box; height: 0; } // // Contain overflow in all browsers. // pre { overflow: auto; } // // Address odd `em`-unit font size rendering in all browsers. // code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } // Forms // ========================================================================== // // Known limitation: by default, Chrome and Safari on OS X allow very limited // styling of `select`, unless a `border` property is set. // // // 1. Correct color not being inherited. // Known issue: affects color of disabled elements. // 2. Correct font properties not being inherited. // 3. Address margins set differently in Firefox 4+, Safari, and Chrome. // button, input, optgroup, select, textarea { color: inherit; // 1 font: inherit; // 2 margin: 0; // 3 } // // Address `overflow` set to `hidden` in IE 8/9/10/11. // button { overflow: visible; } // // Address inconsistent `text-transform` inheritance for `button` and `select`. // All other form control elements do not inherit `text-transform` values. // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. // Correct `select` style inheritance in Firefox. // button, select { text-transform: none; } // // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` // and `video` controls. // 2. Correct inability to style clickable `input` types in iOS. // 3. Improve usability and consistency of cursor style between image-type // `input` and others. // button, html input[type="button"], // 1 input[type="reset"], input[type="submit"] { -webkit-appearance: button; // 2 cursor: pointer; // 3 } // // Re-set default cursor for disabled elements. // button[disabled], html input[disabled] { cursor: default; } // // Remove inner padding and border in Firefox 4+. // button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } // // Address Firefox 4+ setting `line-height` on `input` using `!important` in // the UA stylesheet. // input { line-height: normal; } // // It's recommended that you don't attempt to style these elements. // Firefox's implementation doesn't respect box-sizing, padding, or width. // // 1. Address box sizing set to `content-box` in IE 8/9/10. // 2. Remove excess padding in IE 8/9/10. // input[type="checkbox"], input[type="radio"] { box-sizing: border-box; // 1 padding: 0; // 2 } // // Fix the cursor style for Chrome's increment/decrement buttons. For certain // `font-size` values of the `input`, it causes the cursor style of the // decrement button to change from `default` to `text`. // input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } // // 1. Address `appearance` set to `searchfield` in Safari and Chrome. // 2. Address `box-sizing` set to `border-box` in Safari and Chrome. // input[type="search"] { -webkit-appearance: textfield; // 1 box-sizing: content-box; //2 } // // Remove inner padding and search cancel button in Safari and Chrome on OS X. // Safari (but not Chrome) clips the cancel button when the search input has // padding (and `textfield` appearance). // input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } // // Define consistent border, margin, and padding. // fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } // // 1. Correct `color` not being inherited in IE 8/9/10/11. // 2. Remove padding so people aren't caught out if they zero out fieldsets. // legend { border: 0; // 1 padding: 0; // 2 } // // 1.Don't allow the user to zoom // 2.Remove default vertical scrollbar in IE 8/9/10/11. // textarea { resize: none; overflow: auto; } // // Don't inherit the `font-weight` (applied by a rule above). // NOTE: the default cannot safely be changed in Chrome and Safari on OS X. // optgroup { font-weight: bold; } // Tables // ========================================================================== // // Remove most spacing between table cells. // table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } em, b, i { font-style: normal; font-weight: normal; } /* �������� */ input, select { vertical-align:baseline } ================================================ FILE: asset/css/variables.less ================================================ // // ==变量 // -------------------------------------------------- //== 颜色 // //## 灰色及品牌标记颜色引导. @gray-base: #000; @gray-darker: lighten(@gray-base, 13.5%); // #222 @gray-dark: lighten(@gray-base, 20%); // #333 @gray-dim: lighten(@gray-base, 25%); // #404040 系统主要文字颜色 @gray: lighten(@gray-base, 33.5%); // #555 @gray-light: lighten(@gray-base, 46.7%); // #777 @gray-minor: lighten(@gray-base, 59.2%); // #979797 系统次要文字颜色 @gray-lighter: lighten(@gray-base, 93.5%); // #eee @brand-link: #2577FF; //链接 @brand-hover: #094D86;; //链接hover @brand-success: #3BA861; //状态成功、正常 @brand-warning: #f0ad4e; //提示警告 @brand-danger: #B01207; //危险、失败 @brand-forbid: #b4b4b4; //不可用 @title-background: #E9EBF1; //标题、条幅背景色 //== Scaffolding // //## Settings for some of the most global styles. //** Background color for `
`. @body-bg: #f1f2f3; //** Global text color on ``. @text-color: @gray-dim; //** Global textual link color. @link-color: @brand-link; //** Link hover color set via `darken()` function. @link-hover-color: @brand-hover; //** Link hover decoration. @link-hover-decoration: underline; //== Typography // //## Font, line-height, and color for body text, headings, and more. @font-family-sans-serif: "Microsoft Yahei", Arial , Tahoma , Helvetica, sans-serif; @font-family-serif: Georgia, "Times New Roman", Times, serif; //** Default monospace fonts for ``, ``, and ``.
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
@font-family-base: @font-family-sans-serif;
@font-size-base: 14px;
@font-size-large: ceil((@font-size-base * 1.25)); // ~18px
@font-size-medium: ceil((@font-size-base * 1.14)); // ~16px
@font-size-small: ceil((@font-size-base * 0.85)); // ~12px
@font-size-h1: floor((@font-size-base * 2.6)); // ~36px
@font-size-h2: floor((@font-size-base * 2.15)); // ~30px
@font-size-h3: ceil((@font-size-base * 1.7)); // ~24px
@font-size-h4: ceil((@font-size-base * 1.25)); // ~18px
@font-size-h5: @font-size-base;
@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px
//** Unit-less `line-height` for use in components like buttons.
@line-height-base: 1.428571429; // 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
//** By default, this inherits from the ``.
@headings-font-family: inherit;
@headings-font-weight: 500;
@headings-line-height: 1.1;
@headings-color: inherit;
//== Components
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
@padding-base-vertical: 6px;
@padding-base-horizontal: 12px;
@padding-large-vertical: 10px;
@padding-large-horizontal: 16px;
@padding-small-vertical: 5px;
@padding-small-horizontal: 10px;
@padding-xs-vertical: 1px;
@padding-xs-horizontal: 5px;
@line-height-large: 1.3333333; // extra decimals for Win 8.1 Chrome
@line-height-small: 1.5;
@border-radius-base: 4px;
@border-radius-large: 6px;
@border-radius-small: 3px;
// Medium screen / desktop
@screen-md: 992px;
@screen-md-min: @screen-md;
// Large screen / wide desktop
@screen-lg: 1200px;
@screen-lg-min: @screen-lg;
//
//==混合变量
//
// WebKit-style focus
.tab-focus() {
// WebKit-specific. Other browsers will keep their default outline style.
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
// 隐藏文本
.text-hide() {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
//
//混合工具变量
//
//去除a和label的虚线
.remove_dotted(){
a,label {blr:~'expression(this.onFocus=this.blur())'}
a,label {outline:none;}
}
.font(@size:14px){
font-size:@size;
}
.h100(){
height:100%;
}
.w100(){
width:100%;
}
//边框设置
.border(@w:1px,@c:#eee){
border:@w solid @c;
}
//定位
.pos(r){
position:relative;
}
.pos(a){
position:absolute;
}
.pos(f){
position:fixed;
}
//背景图片,.bg("..images/1.png");
.bg(@url){
background:url(@url) no-repeat;
}
//浮动,div{.fr;}
.fl(){
float:left;
}
.fr(){
float:right;
}
.list-sn(){
list-style:none;
}
//垂直居中
.pos-box-cc(@w,@h,@pos:absolute){
width:@w;
height:@h;
left:50%;
top:50%;
margin:-@w/2 0 0 -@h/2;
position: @pos;
}
// 居中对齐一个块级元素
.center-block() {
display: block;
margin-left: auto;
margin-right: auto;
}
//文字居中
.tc(){
text-align:center;
}
//文字垂直居中
.tcc(@h){
text-align:center;
line-height:@h;
}
.l-h(@h){
height:@h;
line-height:@h;
}
// 文本溢出
// 需要inline-block或块适当的样式
.text-overflow() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
//display
.d-b(){
display:block;
}
.d-i(){
display:inline;
}
.d-ib(){
display:inline-block;
*display:inline;
*zoom:1;
}
.d-t(){
display:table;
}
.d-n(){
display:none;
}
.t-n(@p:none){
text-decoration:@p;
}
.tc(){
text-align:center;
}
.tl(){
text-align:left;
}
.tr(){
text-align:right;
}
.va-m(){
vertical-align: middle;
}
//图标距离
.m-icon-left(@l-distance){
margin-left:@l-distance;
}
.m-icon-right(@r-distance){
margin-right:@r-distance;
}
//圆角
.radius(@r){
-webkit-border-radius:@r;
-moz-border-radius:@r;
border-radius:@r;
}
//消息圆角
.message(@me-t,@me-r){
background-color:#F44336;
width: 8px;
height: 8px;
.pos(a);
top:@me-t;
right:@me-r;
}
//三角形
.triangle(top,@w:5px,@c:#556066){
border-width:@w;
border-color:transparent transparent @c transparent;
border-style:dashed dashed solid dashed;
}
.triangle(bottom,@w:5px,@c:#556066){
border-width:@w;
border-color:@c transparent transparent transparent;
border-style:solid dashed dashed dashed;
}
.triangle(left,@w:5px,@c:#556066){
border-width:@w;
border-color:transparent @c transparent transparent;
border-style:dashed dashed dashed solid;
}
.triangle(right,@w:5px,@c:#556066){
border-width:@w;
border-color:transparent transparent transparent @c;
border-style:dashed solid dashed dashed;
}
.triangle(@_){
width:0;
height:0;
overflow:hidden;
font-size:0;
}
.clearfix(){
*zoom: 1;
&:before,
&:after{
display:table;
content: "";
}
&:after {
clear: both;
}
}
.box-sizing(@box){
-webkit-box-sizing:@box;
-moz-box-sizing:@box;
-ms-box-sizing:@box;
-o-box-sizing:@box;
sizing:@box;
}
.box-shadow(@shadow){
-webkit-box-shadow:@shadow;
-moz-box-shadow:@shadow;
-ms-box-shadow:@shadow;
-o-box-shadow:@shadow;
shadow:@shadow;
}
//过度
.transition(@trans){
-webkit-transition:@trans;
-moz-transition:@trans;
-ms-transition:@trans;
-o-transition:@trans;
transition:@trans;
}
.transform-origin(@origin){
-webkit-transition-origin:@origin;
-moz-transition-origin:@origin;
-ms-transition-origin:@origin;
-o-transition-origin:@origin;
transition-origin:@origin;
}
.transform(@transform){
-webkit-transform:@transform;
-moz-transform:@transform;
-ms-transform:@transform;
-o-transform:@transform;
transform:@transform;
}
.create3d(@h){
-webkit-perspective:@h;
perspective:@h;
}
.use3d(){
-webkit-transform-style:preserve-3d;
transform-style:preserve-3d;
}
//动画
.animation(@as){
-webkit-animation:@as;
-moz-animation:@as;
-o-animation:@as;
animation:@as;
}
.trans3d(){
-webkit-transform-style:preserve-3d;
-moz-transform-style:preserve-3d;
transform-style:preserve-3d;
}
.trans-origin(@to){
-webkit-transform-origin:@to;
-moz-transform-origin:@to;
transform-origin:@to;
}
================================================
FILE: asset/css/vendor_antd.less
================================================
.newAnt {
.ant-layout.ant-layout-has-sider {
height: 100%;
}
}
================================================
FILE: build/env.js
================================================
const ENVS = {
PUB: ['pub', 'production'],
QA: 'qa',
PROFILE: 'profile',
DEV: ['dev', 'development'],
};
const ENVS_ARR = [];
Object.keys(ENVS).forEach(key => {
const value = ENVS[key];
if (Array.isArray(value)) {
value.forEach(i => ENVS_ARR.push(i));
} else {
ENVS_ARR.push(value);
}
});
let currentEnv = process.env.NODE_ENV;
/**
* 判断当前环境参数是否符合规范
* @param {string} env
*/
function checkEnv(env) {
if (!env) {
throw new Error('NODE_ENV is not defined');
return false;
}
if (ENVS_ARR.indexOf(env) === -1) {
throw new Error(`NODE_ENV must be one of ${JSON.stringify(ENVS_ARR)},currentEnv is:${env}`);
}
return true;
}
// 线上环境
const isProduction = () => {
return checkEnv(currentEnv) && ENVS.PUB.includes(currentEnv);
};
// 非线上环境
const isNotProduction = () => !isProduction();
// 测试环境
const isQA = () => {
return checkEnv(currentEnv) && currentEnv === ENVS.QA;
};
// 联调环境
const isProfile = () => {
return checkEnv(currentEnv) && currentEnv === ENVS.PROFILE;
};
// 开发环境
// const isDev = () => !(isProfile() || isProduction() || isQA());
const isDev = () => {
return checkEnv(currentEnv) && ENVS.DEV.includes(currentEnv);
};
/**
* 获取所有环境变量
* @returns ENVS_ARR
*/
function getEnvs() {
return ENVS_ARR;
}
/**
* 设置环境变量
* @param {string} env
*/
function setEnv(env) {
if (ENVS_ARR.indexOf(env) === -1) {
throw new Error(`NODE_ENV provided must be one of ${JSON.stringify(ENVS_ARR)},your param is:${env}`);
}
currentEnv = env;
console.log(`NODE_ENV is set successfully,currentEnv is ${env}`);
}
/**
* 设置线上环境
*/
const setProduction = () => setEnv(ENVS.PUB[0]);
/**
* 设置测试环境
*/
const setQA = () => setEnv(ENVS.QA);
/**
* 设置联调环境
*/
const setProfile = () => setEnv(ENVS.PROFILE);
/**
* 设置开发环境
*/
const setDev = () => setEnv(ENVS.DEV[0]);
/**
* 开发和线上环境配置,默认为开发环境配置,适用于前后端未分离项目(用hash做缓存)
* @param {*} devConfig 开发环境配置
* @param {*} pubConfig 线上环境配置
* @returns (isQA() || isProduction()) ? pubConfig : devConfig
*/
function env(devConfig, pubConfig) {
if (!pubConfig && pubConfig !== '') {
pubConfig = devConfig;
}
if (isQA() || isProduction()) {
return pubConfig;
}
return devConfig;
}
module.exports = {
getEnvs,
setEnv,
setProduction,
setQA,
setProfile,
setDev,
env,
isProduction,
isNotProduction,
isQA,
isProfile,
isDev,
PUB: ENVS.PUB[0],
QA: ENVS.QA,
PROFILE: ENVS.PROFILE,
DEV: ENVS.DEV[0],
};
================================================
FILE: build/postcss.config.js
================================================
module.exports = {
plugins: [
require('autoprefixer'),
],
};
================================================
FILE: build/publicPath.js
================================================
let pkg = require('../package.json');
const Env = require('./env');
// 不同环境下的静态资源的url根路径
let PRODUCT_STATIC_URL = 'http://static.hello.com';
let QA_STATIC_URL = 'http://qa.static.hello.com';
let PROFILE_STATIC_URL = 'http://dev.static.hello.com'; // 联调环境静态资源存放路径
let DEV_STATIC_URL = '/dist';
// 根据环境来获取不同的静态资源部署的根路径
let publicPath = (function getPublicPath() {
let staticUrl = DEV_STATIC_URL;
if (Env.isDev()) {
return staticUrl;
}
if (Env.isProduction()) {
staticUrl = PRODUCT_STATIC_URL;
}
if (Env.isQA()) {
staticUrl = QA_STATIC_URL;
}
if (Env.isProfile()) {
staticUrl = PROFILE_STATIC_URL;
}
return `${staticUrl}/${pkg.name}/${pkg.version}`;
})();
module.exports = publicPath;
================================================
FILE: build/theme.js
================================================
module.exports = {
'font-family': 'Microsoft Yahei, Arial , Tahoma , Helvetica, sans-serif',
};
================================================
FILE: build/webpack.base.conf.js
================================================
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const resolve = path.resolve;
const publicPath = require('./publicPath');
const theme = require('./theme.js');
const postcssConfigPath = resolve(__dirname, './postcss.config.js');
module.exports = {
entry: {
app: './src/app.jsx',
},
output: {
path: resolve(__dirname, '../dist'),
filename: '[name].js',
publicPath: publicPath + '/',
chunkFilename: '[name].js',
crossOriginLoading: 'anonymous',
},
resolve: {
extensions: ['.js', '.jsx', '.less', '.scss', '.css'],
alias: {
common: resolve('src/common'),
components: resolve('src/components'),
constants: resolve('src/constants'),
modules: resolve('src/modules'),
reduxDir: resolve('src/redux'),
},
},
module: {
rules: [
{
test: /\.(js|jsx)$/i,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css/i,
include: [
resolve('src'),
],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
config: {
path: postcssConfigPath,
},
},
},
],
},
{
test: /\.less$/,
include: [resolve('src')],
use: [
MiniCssExtractPlugin.loader,
{
loader:
'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
options: {
modules: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
config: {
path: postcssConfigPath,
},
},
},
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
{
test: /\.less/i,
include: [
resolve('asset/css'),
resolve('node_modules/antd/'),
],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader?importLoader=1&modules&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
options: {
minimize: true, // css压缩
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
config: {
path: postcssConfigPath,
},
},
},
{
loader: 'less-loader',
options: {
javascriptEnabled: true,
modifyVars: theme,
},
},
],
},
{
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: ['file-loader?limit=1000&name=files/[md5:hash:base64:10].[ext]'],
},
{
test: /\.(html|htm)$/,
use: 'html-withimg-loader',
},
],
},
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /^\.\/zh\-cn$/),
// extract css
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css',
}),
// clean output bundle directory
new CleanWebpackPlugin(),
],
};
================================================
FILE: build/webpack.dev.conf.js
================================================
let mockport = 8000;
process.argv.forEach(function setMockPort(val, index) {
if (val === '--env.mockport') {
mockport = process.argv[index + 1];
return false;
}
});
module.exports = {
mode: 'development',
devtool: 'source-map',
devServer: {
// 服务器外部可访问要声明host
host: '0.0.0.0',
hot: true,
inline: true,
open: true,
proxy: {
'**/*.action': {
target: 'http://localhost:' + mockport,
/* bypass: function bypass(req, res, proxyOptions) {
// handle default jsp action
if (req.headers.accept.indexOf('html') != -1) {
return 'index.html';
}
}, */
},
},
},
};
================================================
FILE: build/webpack.prod.conf.js
================================================
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'production',
plugins: [
// analyze bundle size
// new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(process.cwd(), './template.html'),
filename: path.resolve(process.cwd(), './dist', 'index.html'), // 写入的文件
}),
],
optimization: {
splitChunks: {
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
libs: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
styles: {
test: /(\.less|\.css)$/,
priority: -10,
},
},
},
},
};
================================================
FILE: index.html
================================================
PC端React+redux脚手架
================================================
FILE: mock/config/mockConfig.json
================================================
{
"dataSource": ["json", "template", "server"],
"json": {
"path": "/mock/data/",
"wrap": true
},
"server": [{
"host": "http://localhost:8080/mock",
"serverParams": {
"index": 1
},
"statusCode": [200],
"rejectUnauthorized": false,
"secureProtocol": "SSLv3_method",
"cookie": "",
"proxy": ""
}, {
"host": "http://localhost:8081/",
"serverParams": {
"index": 2
},
"statusCode": [200],
"rejectUnauthorized": false,
"secureProtocol": "SSLv3_method",
"cookie": "",
"proxy": ""
}],
"template": {
"path": "/mock/template/"
}
}
================================================
FILE: mock/data/skuunit/deleteSkuUnit.json
================================================
{
"enabled": true,
"value": "success",
"success": {
"flag": "0",
"msg": [
""
],
"data": []
},
"error": {
"flag": "1",
"msg": [
"input error"
],
"data": []
}
}
================================================
FILE: mock/data/skuunit/showSkuUnitList.json
================================================
{
"enabled": true,
"value": "success",
"success": {
"flag": "0",
"msg": ["系统繁忙"],
"data": {
"dataList": [
{
"skuUnitId": "185715847_1",
"skuUnitName": "1单元名称",
"isPause": 1,
"skuSetId": "23489551",
"skuSetName": "上海鲜花",
"groupCount": 5,
"planCount": 2,
"denyKeyCount": 9,
"checkStatus": 1,
"checkOkCount": 3000,
"checkRejectCount": 200,
"ideas": [
{
"deveiceType": 2,
"ideaType": 2
}
]
},
{
"skuUnitId": "185715847_2",
"skuUnitName": "2单元名称2",
"isPause": 0,
"skuSetId": "23489556",
"skuSetName": "沈阳鲜花",
"groupCount": 5,
"planCount": 2,
"denyKeyCount": 7,
"checkStatus": 1,
"checkOkCount": 3000,
"checkRejectCount": 200,
"ideas": [
{
"deveiceType": 1,
"ideaType": 2
},
{
"deveiceType": 2,
"ideaType": 1
}
]
},
{
"skuUnitId": "185715847_3",
"skuUnitName": "3单元名称",
"isPause": 0,
"skuSetId": "23489555",
"skuSetName": "兰州鲜花",
"groupCount": 5,
"planCount": 2,
"denyKeyCount": 12,
"checkStatus": 0,
"checkOkCount": 3000,
"checkRejectCount": 200,
"ideas": [
{
"deveiceType": 2,
"ideaType": 1
}
]
},
{
"skuUnitId": "185715847_4",
"skuUnitName": "4单元名称",
"isPause": 1,
"skuSetId": "23489552",
"skuSetName": "北京二手车",
"groupCount": 5,
"planCount": 2,
"denyKeyCount": 4,
"checkStatus": 1,
"checkOkCount": 3000,
"checkRejectCount": 200,
"ideas": [
{
"deveiceType": 2,
"ideaType": 2
}
]
},
{
"skuUnitId": "185715847_5",
"skuUnitName": "5单元名称",
"isPause": 0,
"skuSetId": "23489553",
"skuSetName": "香港鲜花",
"groupCount": 5,
"planCount": 2,
"denyKeyCount": 7,
"checkStatus": -1,
"checkOkCount": 3000,
"checkRejectCount": 200,
"ideas": [
{
"deveiceType": 1,
"ideaType": 1
}
]
}
],
"totalNumber": 50
}
},
"error": {
"flag": "1",
"msg": [
"系统繁忙"
],
"data": []
}
}
================================================
FILE: mock/data/skuunit/updateSkuUnitPause.json
================================================
{
"enabled": true,
"value": "success",
"success": {
"flag": "0",
"msg": [
"修改失败"
],
"data": []
},
"error": {
"flag": "1",
"msg": [
"input error"
],
"data": []
}
}
================================================
FILE: mock/template/query/table.template
================================================
{
'table|1-10': [
{
'id|100-1000': 100,
'name|1': ['A','B','C','D','E','F','G','H','I','J','K','L','M','N'],
'height|50-300': 50,
'weight|50-100.1-10': 50,
'age|1-100': 1,
'email': '@EMAIL'
}
]
}
================================================
FILE: package.json
================================================
{
"name": "react-starter",
"description": "react+redux的脚手架,持续进行更新迭代",
"version": "1.0.1",
"main": "app.js",
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --progress --colors",
"profile": "cross-env NODE_ENV=profile webpack --progress --colors --display-modules",
"qa": "cross-env NODE_ENV=qa webpack --progress --colors",
"pub": "cross-env NODE_ENV=production webpack --progress --colors ",
"lint": "eslint src"
},
"dependencies": {
"antd": "^3.26.2",
"axios": "^0.18.1",
"babel-polyfill": "^6.26.0",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.4",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/plugin-proposal-export-default-from": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.6",
"@babel/preset-react": "^7.7.4",
"@hot-loader/react-dom": "^16.11.0",
"autoprefixer": "^8.6.5",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"babel-plugin-import": "^1.13.0",
"babel-plugin-transform-imports": "^1.5.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"clean-webpack-plugin": "^2.0.2",
"css-loader": "^0.28.9",
"eslint": "^5.16.0",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^1.7.0",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"html-withimg-loader": "^0.1.16",
"less": "^3.10.3",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.4.5",
"postcss-loader": "^2.1.6",
"react-hot-loader": "^4.12.18",
"redux-logger": "^3.0.6",
"url-loader": "^1.1.2",
"cross-env": "^6.0.3",
"webpack": "^4.41.2",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-merge": "^4.2.2"
},
"repository": {
"type": "git",
"url": "https://github.com/zhangkun-Jser/react-starter.git"
},
"keywords": [],
"author": "zk",
"license": "ISC",
"bugs": {
"url": "https://github.com/zhangkun-Jser/react-starter/issues"
},
"homepage": "https://github.com/zhangkun-Jser/react-starter",
"eslintIgnore": [
"dist/**"
]
}
================================================
FILE: src/app.jsx
================================================
/*
* 项目入口
*/
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import '../asset/css/app.less';
import store from './redux/configureStore';
import Root from './routers';
ReactDOM.render(
,
document.getElementById('app'),
);
================================================
FILE: src/common/request.js
================================================
// 参考:https://ykloveyxk.github.io/2017/02/25/axios%E5%85%A8%E6%94%BB%E7%95%A5/
import OriginAxios from 'axios';
import { message } from 'antd';
const axios = OriginAxios.create({
timeout: 20000,
});
export function get(url, data) {
return axios.get(url, {
params: data,
});
}
// By default, axios serializes JavaScript objects to JSON
export function post(url, data) {
return axios({
url,
method: 'post',
data,
});
}
// By default, axios serializes JavaScript objects to JSON
export function put(url, data) {
return axios({
url,
method: 'put',
data,
});
}
export function del(url, data) {
return axios({
url,
method: 'delete',
data,
});
}
// Add a request interceptor
axios.interceptors.request.use(
function config(config) {
// Do something before request is sent
return config;
},
function error(error) {
// Do something with request error
console.log('request error, HTTP CODE: ', error.response.status);
return Promise.reject(error);
},
);
// 返回状态判断(添加响应拦截器)
axios.interceptors.response.use(
res => {
if (res.data && res.data.flag === 1) {
let errorMsg = res.data.msg;
message.error(errorMsg);
return Promise.reject(errorMsg);
}
return res;
},
error => {
// 用户登录的时候会拿到一个基础信息,比如用户名,token,过期时间戳
// 直接丢localStorage或者sessionStorage
if (error.response.status === 401) {
// 若是接口访问的时候没有发现有鉴权的基础信息,直接返回登录页
// history.push('login');
window.location = '/login.html';
}
},
);
export default axios;
================================================
FILE: src/components/ErrorBoundary.jsx
================================================
// 错误边界
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ErrorBoundary extends Component {
static propTypes = {
children: PropTypes.oneOfType([PropTypes.any]),
};
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch() {
// You can also log the error to an error reporting service
// logErrorToMyService(error, info);
this.setState({
hasError: true,
});
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return Something went wrong.
;
}
return this.props.children;
}
}
export default ErrorBoundary;
================================================
FILE: src/components/Layout/Footer.jsx
================================================
import React from 'react';
import styles from './index.css';
export default () => (
Copyright© 2019 Sogou Biztech. All Rights Reserved.
);
================================================
FILE: src/components/Layout/Header.jsx
================================================
import React from 'react';
import PropTypes from 'prop-types';
import {
Layout, Icon, Menu, Dropdown, Avatar,
} from 'antd';
import styles from './index.css';
import AVATAR from '../../../asset/img/avatar.png';
const { Header } = Layout;
const HeaderComp = ({
collapsed = false,
onToggle,
}) => {
const menu = (
);
const handleToggle = () => {
onToggle && onToggle();
};
return (
dev
);
};
HeaderComp.propTypes = {
collapsed: PropTypes.bool,
onToggle: PropTypes.func,
};
export default HeaderComp;
================================================
FILE: src/components/Layout/Sider.jsx
================================================
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Menu, Layout, Icon } from 'antd';
import styles from './index.css';
import LOGO from '../../../asset/img/logo.svg';
const { Sider } = Layout;
const SiderComp = ({
history,
pathname,
collapsed = false,
}) => {
const pathArr = pathname.split('/').filter(i => i);
const homeKey = 'home';
const getDefaultSelectedKeys = () => {
const selectKey = pathArr.length ? pathArr[pathArr.length - 1] : '';
return selectKey ? [selectKey] : [homeKey];
};
const [selectedKeys, setSelectedKeys] = useState(getDefaultSelectedKeys());
const handleSelect = ({ selectedKeys }) => {
setSelectedKeys(selectedKeys);
history.push(`/${selectedKeys}`);
};
window.onhashchange = () => {
setSelectedKeys(getDefaultSelectedKeys());
};
return (
{!collapsed && 脚手架
}
);
};
SiderComp.propTypes = {
history: PropTypes.objectOf(PropTypes.any),
pathname: PropTypes.string,
collapsed: PropTypes.bool,
};
export default SiderComp;
================================================
FILE: src/components/Layout/index.css
================================================
/* Header */
.header {
background: #fff;
padding: 0 20px;
position: relative;
}
.header-right {
position: absolute;
top: 0;
right: 24px;
bottom: 0;
}
.header-menu {
width: 150px;
}
.user-name {
margin-left: 10px;
}
.account {
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
padding: 0 12px;
display: inline-block;
transition: all .3s;
height: 100%;
}
.account i {
font-size: 16px;
vertical-align: middle;
}
.account:hover {
background: #e6f7ff;
}
/* Sider */
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 10px;
cursor: pointer;
transition: color .3s;
}
.trigger:hover {
color: #1890ff;
}
.menu-logo {
position: relative;
height: 64px;
padding-left: 24px;
overflow: hidden;
line-height: 64px;
background: #001529;
transition: all 0.3s;
}
.menu-logo a {
color: #1890FF;
text-decoration: none;
background-color: transparent;
outline: none;
cursor: pointer;
transition: color 0.3s;
}
.menu-logo img {
display: inline-block;
width: 32px;
vertical-align: middle;
}
.menu-logo h1 {
display: inline-block;
margin: 0 0 0 12px;
color: white;
font-weight: 600;
font-size: 20px;
vertical-align: middle;
}
/* Footer */
.footer {
text-align: center;
font-size: 14px;
padding: 20px;
}
================================================
FILE: src/components/Pages/index.css
================================================
.page-wrapper {
margin: 16px 0px 16px 16px;
line-height: 32px;
overflow: hidden;
}
================================================
FILE: src/components/Pages/index.jsx
================================================
/**
* 表格分页组件
*/
import React, { Component } from 'react';
import { Pagination } from 'antd';
import PropTypes from 'prop-types';
import styles from './index.css';
class Pages extends Component {
static propTypes = {
pageSizeOptions: PropTypes.arrayOf(PropTypes.string),
defaultCurrent: PropTypes.number,
total: PropTypes.number,
current: PropTypes.number,
pageSize: PropTypes.number,
loadTable: PropTypes.func,
style: PropTypes.objectOf(PropTypes.any),
}
static defaultProps = {
pageSizeOptions: ['10', '20', '30', '40'],
defaultCurrent: 1,
total: 0,
current: 1,
pageSize: 10,
style: {
float: 'right',
},
};
state = {
current: this.props.current,
pageSize: this.props.pageSize,
};
static getDerivedStateFromProps(props) {
return {
current: props.current,
pageSize: props.pageSize,
total: props.total,
};
}
handlePageChange = (page, pageSize) => {
this.loadTable({
page,
pageSize,
});
}
handlePageSizeChange = (current, pageSize) => {
this.loadTable({
page: this.props.defaultCurrent,
pageSize,
});
}
loadTable({ page, pageSize }) {
this.setState({
current: page,
pageSize,
});
this.props.loadTable(false, {
page,
pageSize,
});
}
reset() {
const { current, pageSize } = this.defaultProps;
this.setState({ current, pageSize });
}
render() {
const { total, pageSizeOptions, style } = this.props;
const { current, pageSize } = this.state;
return (
共{total}条
);
}
}
export default Pages;
================================================
FILE: src/components/index.jsx
================================================
import Header from './Layout/Header';
import Sider from './Layout/Sider';
import Footer from './Layout/Footer';
import Pages from './Pages';
import ErrorBoundary from './ErrorBoundary';
export {
Header,
Sider,
Footer,
Pages,
ErrorBoundary,
};
================================================
FILE: src/constants/constant.js
================================================
export const ACTION_TYPE_ADD_ERROR = 'app/ADD_ERROR';
// 分页默认信息
export const PAGE_DATA = {
pageNo: 1,
pageSize: 10,
};
================================================
FILE: src/constants/url.js
================================================
// 单元列表查询展示
export const SHOW_UNIT_LIST_URL = '/skuunit/showSkuUnitList.action';
// 删除单元
export const DELETE_UNIT_URL = '/skuunit/deleteSkuUnit.action';
================================================
FILE: src/redux/configureStore.js
================================================
import { compose, createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import reducer from './reducers';
const middleware = [thunk];
const isNotProduction = process.env.NODE_ENV !== 'production';
if (isNotProduction) {
middleware.push(logger);
}
// 判断当前浏览器是否安装了 REDUX_DEVTOOL 插件
const shouldCompose = isNotProduction
&& typeof window === 'object'
&& window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
const composeEnhancers = shouldCompose
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify here name, actionsBlacklist, actionsCreators and other options
})
: compose;
/*
调用 applyMiddleware ,使用 middleware 来增强 createStore
*/
const configureStore = composeEnhancers(applyMiddleware(...middleware))(createStore);
const store = configureStore(reducer);
window.Store = store;
if (module.hot) {
module.hot.accept('./reducers.js', () => {
console.log('reducer changed');
store.replaceReducer(require('./reducers').default);
});
}
export default store;
================================================
FILE: src/redux/reducers.js
================================================
import { combineReducers } from 'redux';
import skuunit from './skuunit/skuunit';
const reducers = {
skuunit,
};
export default combineReducers(reducers);
================================================
FILE: src/redux/skuunit/api.js
================================================
import { get, post } from 'common/request';
import {
SHOW_UNIT_LIST_URL,
DELETE_UNIT_URL,
} from 'constants/url';
/**
* 单元列表查询展示
* @param {*} params
*/
export function showSkuunitListApi(params) {
if (!params) {
return Promise.reject('params is wrong');
}
return get(SHOW_UNIT_LIST_URL, params)
.then(res => {
// 开发时调试等待效果;
return new Promise((resolve) => {
setTimeout(() => {
return resolve(res);
}, 1000);
});
})
.then(res => res.data);
}
/**
* 删除单元
* @param {} params
*/
export function deleteSkuUnitApi(params) {
if (!params) {
return Promise.reject('params is wrong');
}
return post(DELETE_UNIT_URL, params)
.then(res => res.data);
}
================================================
FILE: src/redux/skuunit/skuunit.js
================================================
/**
* author: niuxiaoyu
* description: 单元
* date: 2018/6/6
*/
import { ACTION_TYPE_ADD_ERROR } from 'constants/constant';
import {
showSkuunitListApi,
deleteSkuUnitApi,
} from './api';
const SHOW_LOADING = 'skuunit/SHOW_LOADING';
const HIDE_LOADING = 'skuunit/HIDE_LOADING';
const SHOW_SKU_UNIT_LIST = 'skuunit/SHOW_SKU_UNIT_LIST';
const DELETE_SKUUNIT = 'skuunit/DELETE_SKUUNIT';
const initialState = {
dataList: [],
isLoading: false,
error: '',
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case SHOW_LOADING:
return Object.assign({}, state, {
isLoading: true,
});
case HIDE_LOADING:
return Object.assign({}, state, {
isLoading: false,
});
case SHOW_SKU_UNIT_LIST:
return Object.assign({}, state, {
dataList: action.payload.data.dataList,
totalNumber: action.payload.data.totalNumber,
});
case DELETE_SKUUNIT:
return Object.assign({}, state, {
dataList: _deleteskuUnits(state.dataList, action.payload),
});
default:
return state;
}
}
/**
* 删除单元
* @param {*} source
* @param {*} skuUnitIdList
*/
const _deleteskuUnits = (source, params) => {
const { skuUnitIdList } = params;
const newSource = [];
for (let i = 0; i < source.length; i++) {
const target = skuUnitIdList.find(skuUnitId => skuUnitId === source[i].skuUnitId);
if (!target) {
newSource.push(source[i]);
}
}
return newSource;
};
/**
* 单元列表查询展示
* @param {*} params
*/
export function showSkuunitList(planName) {
return async dispatch => {
try {
dispatch({ type: SHOW_LOADING });
const res = await showSkuunitListApi(planName);
await dispatch({ type: SHOW_SKU_UNIT_LIST, payload: res });
dispatch({ type: HIDE_LOADING });
} catch (err) {
dispatch({ type: HIDE_LOADING });
console.log(err);
}
};
}
/**
* 删除单元
* @param {} skuUnitIdList
*/
export function deleteSkuUnit(skuUnitIdList) {
return async dispatch => {
try {
const param = { skuUnitIdList };
const res = await deleteSkuUnitApi(param);
dispatch({ type: DELETE_SKUUNIT, payload: param, res });
} catch (err) {
dispatch({ type: ACTION_TYPE_ADD_ERROR, payload: { errorMsg: err } });
}
};
}
================================================
FILE: src/routers/PrimaryLayout.jsx
================================================
import React, { Suspense, useState, lazy } from 'react';
import PropTypes from 'prop-types';
import { Switch, Route, Redirect } from 'react-router-dom';
import { Layout, Spin } from 'antd';
import styles from './index.less';
import {
Header, Sider, Footer, ErrorBoundary,
} from 'components';
const { Content } = Layout;
const Skuunit = lazy(() => import(/* webpackChunkName: "home" */ './Skuunit'));
const PrimaryLayout = (props) => {
const [collapsed, setCollapsed] = useState(false);
const toggleCollapse = () => {
setCollapsed(!collapsed);
};
return (
}>
nav2} />
);
};
PrimaryLayout.propTypes = {
history: PropTypes.objectOf(PropTypes.any),
location: PropTypes.objectOf(PropTypes.any),
};
export default PrimaryLayout;
================================================
FILE: src/routers/Skuunit/columns.jsx
================================================
import React from 'react';
const getColumns = ({
handleDelete,
}) => {
return [
{
title: '名称',
dataIndex: 'skuUnitName',
width: 230,
key: 'skuUnitName',
},
{
title: '集合名称',
dataIndex: 'skuSetName',
key: 'skuSetName',
},
{
title: '操作',
key: 'action',
width: 100,
render: (text, record) => (
handleDelete(record.skuUnitId, record.skuUnitName)}>删除
),
},
];
};
export default getColumns;
================================================
FILE: src/routers/Skuunit/index.jsx
================================================
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
Table, Modal,
} from 'antd';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Pages } from 'components';
import getColumns from './columns';
import { PAGE_DATA } from 'constants/constant';
import {
showSkuunitList,
deleteSkuUnit,
} from 'reduxDir/skuunit/skuunit';
const confirm = (msg, onOk, onCancel) => {
Modal.confirm({
title: msg,
onOk() {
onOk && onOk();
},
onCancel() {
onCancel && onCancel();
},
});
};
class Skuunit extends Component {
static propTypes = {
data: PropTypes.shape({
dataList: PropTypes.arrayOf(PropTypes.any),
isLoading: PropTypes.bool,
totalNumber: PropTypes.number,
}),
showSkuunitList: PropTypes.func,
deleteSkuUnit: PropTypes.func,
};
state = {
pageNo: PAGE_DATA.pageNo,
pageSize: PAGE_DATA.pageSize,
};
componentDidMount() {
this.loadTable();
}
getFetchListParams = () => {
const {
page, pageSize,
} = this.state;
return {
page,
pageSize,
};
}
/**
* @resetPage: 表示是否重置Pages组件
* @pageInfo: Pages组件的参数
*/
loadTable = (resetPage = true, pageInfo) => {
let pageNo;
let pageSize;
if (!resetPage) {
pageNo = pageInfo ? pageInfo.page : this.state.pageNo;
pageSize = pageInfo ? pageInfo.pageSize : this.state.pageSize;
} else {
pageNo = PAGE_DATA.pageNo;
pageSize = PAGE_DATA.pageSize;
}
this.setState({
pageNo,
pageSize,
}, () => this.props.showSkuunitList(this.getFetchListParams()));
}
deleteSingleUnit = (unitId, unitName) => {
confirm(`删除单元:${unitName} ?`, () => {
this.props.deleteSkuUnit([unitId]);
});
}
render() {
const { dataList, isLoading, totalNumber } = this.props.data;
const {
pageNo, pageSize,
} = this.state;
const columns = getColumns({
handleDelete: this.deleteSingleUnit,
});
return (
);
}
}
export default connect(
state => ({
data: state.skuunit,
}),
dispatch => bindActionCreators({
showSkuunitList,
deleteSkuUnit,
}, dispatch),
)(Skuunit);
================================================
FILE: src/routers/index.jsx
================================================
/*
* 路由主入口:可以在该页面进行登录权限控制
*/
import React from 'react';
import {
Route, Switch, withRouter,
} from 'react-router-dom';
import { hot } from 'react-hot-loader';
import { LocaleProvider } from 'antd';
import zhCN from 'antd/lib/locale-provider/zh_CN';
// UI示范,如果不需要目前的UI框架,直接替换该组件即可
import PrimaryLayout from './PrimaryLayout';
const Root = (props) => {
// const { location, history, match } = props;
return (
} />
);
};
export default hot(module)(withRouter(Root));
================================================
FILE: src/routers/index.less
================================================
.main-content {
margin: 24px 16px;
padding: 24px;
background: #fff;
min-height: 280;
}
================================================
FILE: template.html
================================================
PC端React+redux脚手架
================================================
FILE: webpack.config.js
================================================
const merge = require('webpack-merge');
const Env = require('./build/env');
const baseConfig = require('./build/webpack.base.conf');
const prodConfig = require('./build/webpack.prod.conf');
const devConfig = require('./build/webpack.dev.conf');
let config = devConfig;
if (Env.isQA() || Env.isProduction()) {
config = prodConfig;
}
module.exports = merge(baseConfig, config);