Browse Source

门户前端工程提交

chenzhfa 2 years ago
parent
commit
f705248590
100 changed files with 35148 additions and 0 deletions
  1. 16 0
      .editorconfig
  2. 6 0
      .eslintignore
  3. 119 0
      .eslintrc
  4. 3 0
      .ga
  5. 20 0
      .gitignore
  6. 25 0
      .stylelintrc
  7. 46 0
      CODE_OF_CONDUCT.md
  8. 21 0
      LICENSE
  9. 91 0
      README.us-EN.md
  10. 132 0
      config/config.js
  11. 200 0
      config/router.config.js
  12. 0 0
      mock/.gitkeep
  13. 450 0
      mock/IndividuationConfig.js
  14. 1012 0
      mock/api.js
  15. 197 0
      mock/chart.js
  16. 85 0
      mock/notices.js
  17. 158 0
      mock/profile.js
  18. 131 0
      mock/rule.js
  19. 45 0
      mock/utils.js
  20. 28012 0
      package-lock.json
  21. 69 0
      package.json
  22. BIN
      public/favicon.ico
  23. BIN
      src/assets/bannerError.png
  24. BIN
      src/assets/imgError.png
  25. BIN
      src/assets/login-bg.png
  26. BIN
      src/assets/logo.png
  27. 63 0
      src/common/nav.js
  28. 62 0
      src/components/AmountFun/index.js
  29. 46 0
      src/components/AmountFun/index.less
  30. 103 0
      src/components/BreadcrumbPackage/index.js
  31. 23 0
      src/components/BreadcrumbPackage/index.less
  32. 446 0
      src/components/CartListComp/index.js
  33. 334 0
      src/components/CartListComp/index.less
  34. 24 0
      src/components/CountDown/demo/simple.md
  35. 9 0
      src/components/CountDown/index.d.ts
  36. 15 0
      src/components/CountDown/index.en-US.md
  37. 105 0
      src/components/CountDown/index.js
  38. 16 0
      src/components/CountDown/index.zh-CN.md
  39. 13 0
      src/components/CustIcon/README.md
  40. 22 0
      src/components/CustIcon/demo.js
  41. 43 0
      src/components/CustIcon/index.js
  42. 17 0
      src/components/CustIcon/index.less
  43. 17 0
      src/components/DescriptionList/Description.js
  44. 21 0
      src/components/DescriptionList/DescriptionList.js
  45. 35 0
      src/components/DescriptionList/demo/basic.md
  46. 35 0
      src/components/DescriptionList/demo/vertical.md
  47. 22 0
      src/components/DescriptionList/index.d.ts
  48. 5 0
      src/components/DescriptionList/index.js
  49. 75 0
      src/components/DescriptionList/index.less
  50. 39 0
      src/components/DescriptionList/index.md
  51. 6 0
      src/components/DescriptionList/responsive.js
  52. 53 0
      src/components/DetailList/index.js
  53. 21 0
      src/components/DetailList/index.less
  54. 50 0
      src/components/DetailTable/index.js
  55. 1 0
      src/components/DetailTable/index.less
  56. 0 0
      src/components/Drawers/index.d.ts
  57. 54 0
      src/components/EditableItem/index.js
  58. 25 0
      src/components/EditableItem/index.less
  59. 44 0
      src/components/EditableLinkGroup/index.js
  60. 16 0
      src/components/EditableLinkGroup/index.less
  61. 20 0
      src/components/Ellipsis/demo/line.md
  62. 20 0
      src/components/Ellipsis/demo/number.md
  63. 11 0
      src/components/Ellipsis/index.d.ts
  64. 210 0
      src/components/Ellipsis/index.js
  65. 25 0
      src/components/Ellipsis/index.less
  66. 18 0
      src/components/Ellipsis/index.md
  67. 12 0
      src/components/EmptyData/index.js
  68. 21 0
      src/components/Exception/demo/403.md
  69. 14 0
      src/components/Exception/demo/404.md
  70. 14 0
      src/components/Exception/demo/500.md
  71. 11 0
      src/components/Exception/index.d.ts
  72. 33 0
      src/components/Exception/index.js
  73. 88 0
      src/components/Exception/index.less
  74. 21 0
      src/components/Exception/index.md
  75. 19 0
      src/components/Exception/typeConfig.js
  76. 36 0
      src/components/FooterToolbar/demo/basic.md
  77. 9 0
      src/components/FooterToolbar/index.d.ts
  78. 17 0
      src/components/FooterToolbar/index.js
  79. 33 0
      src/components/FooterToolbar/index.less
  80. 21 0
      src/components/FooterToolbar/index.md
  81. 322 0
      src/components/Form/index.js
  82. 75 0
      src/components/Form/index.less
  83. 33 0
      src/components/GlobalFooter/demo/basic.md
  84. 14 0
      src/components/GlobalFooter/index.d.ts
  85. 27 0
      src/components/GlobalFooter/index.js
  86. 29 0
      src/components/GlobalFooter/index.less
  87. 17 0
      src/components/GlobalFooter/index.md
  88. BIN
      src/components/GoodsBig/goodsHot.png
  89. 88 0
      src/components/GoodsBig/index.js
  90. 114 0
      src/components/GoodsBig/index.less
  91. 86 0
      src/components/GoodsBuyIpt/index.js
  92. 23 0
      src/components/GoodsBuyIpt/index.less
  93. BIN
      src/components/GoodsCommon/goodsHot.png
  94. 251 0
      src/components/GoodsCommon/index.js
  95. 184 0
      src/components/GoodsCommon/index.less
  96. 48 0
      src/components/GoodsHistory/index.js
  97. 51 0
      src/components/GoodsHistory/index.less
  98. 243 0
      src/components/GoodsHot/index.js
  99. 192 0
      src/components/GoodsHot/index.less
  100. 0 0
      src/components/GoodsLevel/data.json

+ 16 - 0
.editorconfig

@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 6 - 0
.eslintignore

@@ -0,0 +1,6 @@
+# /node_modules/* and /bower_components/* ignored by default
+
+# Ignore built files except build/index.js
+build/*
+!build/index.js
+../occ-portal-static/*

+ 119 - 0
.eslintrc

@@ -0,0 +1,119 @@
+{
+  "parser": "babel-eslint",
+  "extends": "airbnb",
+  "plugins": ["compat"],
+  "env": {
+    "browser": true,
+    "node": true,
+    "es6": true,
+    "mocha": true,
+    "jest": true,
+    "jasmine": true
+  },
+  "rules": {
+    "linebreak-style": [0, "windows"],
+    "class-methods-use-this": [0, { "exceptMethods": [<...exceptions>] }],
+    "default-case": [0, { "commentPattern": "^skip\\sdefault" }],
+    "quotes": [0, "double"],
+    "no-plusplus": [0, { "allowForLoopAfterthoughts": true }],
+    "no-console": [0, { allow: ["warn", "error"] }],
+    "no-useless-constructor": [0],
+    "no-unused-vars": [0, { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
+    "arrow-parens": [0, "always"],
+    "eqeqeq": [0, "always"],
+    "import/newline-after-import": [0, { "count": 2 }],
+    "react/self-closing-comp": [0, {
+      "component": true,
+      "html": true
+    }],
+    "jsx-a11y/anchor-has-content": [0, {
+        "components": [ "Anchor" ],
+    }],
+    "wrap-iife": [0],
+    "no-sequences": [0],
+    "space-before-function-paren": [0],
+    "func-names": [0],
+    "no-var": [0],
+    "no-continue": [0],
+    "vars-on-top": [0],
+    "one-var": [0],
+    "no-shadow": [0],
+    "one-var-declaration-per-line": [0],
+    "no-underscore-dangle": [0],
+    "no-unused-expressions": [0],
+    "no-multi-spaces": [0],
+    "radix": [0],
+    "react/jsx-indent": [0],
+    "object-shorthand": [0],
+    "react/no-unescaped-entities": [0],
+    "no-undef": [0],
+    "no-unneeded-ternary": [0],
+    "guard-for-in": [0],
+    "prefer-destructuring": [0, {"object": true, "array": false}],
+    "dot-notation": [0],
+    "no-confusing-arrow": [0],
+    "no-return-assign": [0],
+    "no-param-reassign": [0],
+    "no-dupe-keys": [0],
+    "no-mixed-operators": [0],
+    "max-len": [0, { "code": 80 }],
+    "no-debugger": [0],
+    "no-case-declarations": [0],
+    "indent": [0],
+    "generator-star-spacing": [0],
+    "consistent-return": [0],
+    "react/forbid-prop-types": [0],
+    "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
+    "global-require": [1],
+    "import/prefer-default-export": [0],
+    "react/jsx-no-bind": [0],
+    "react/prop-types": [0],
+    "react/prefer-stateless-function": [0],
+    "no-else-return": [0],
+    "no-restricted-syntax": [0],
+    "import/no-extraneous-dependencies": [0],
+    "no-use-before-define": [0],
+    "jsx-a11y/no-static-element-interactions": [0],
+    "jsx-a11y/no-noninteractive-element-interactions": [0],
+    "jsx-a11y/click-events-have-key-events": [0],
+    "jsx-a11y/anchor-is-valid": [0],
+    "no-nested-ternary": [0],
+    "arrow-body-style": [0],
+    "import/extensions": [0],
+    "no-bitwise": [0],
+    "no-cond-assign": [0],
+    "import/no-unresolved": [0],
+    "import/first": [0],
+    "comma-dangle": [0, {
+      "arrays": "always-multiline",
+      "objects": "always-multiline",
+      "imports": "always-multiline",
+      "exports": "always-multiline",
+      "functions": "ignore"
+    }],
+    "object-curly-newline": [0],
+    "function-paren-newline": [0],
+    "no-restricted-globals": [0],
+    "require-yield": [1],
+    "lines-between-class-members": [0],
+    "implicit-arrow-linebreak": [1],
+    "react/destructuring-assignment": [1],
+    "react/jsx-one-expression-per-line": [1],
+    "react/jsx-props-no-multi-spaces": [1],
+    "operator-linebreak": [1],
+    "import/order": [1],
+    "react/jsx-tag-spacing": [1],
+    "react/no-access-state-in-setstate": [1],
+    "no-extra-boolean-cast": [1],
+    "no-lonely-if": [0],
+    "prefer-template": [1]
+  },
+  "parserOptions": {
+    "ecmaFeatures": {
+      "experimentalObjectRestSpread": true
+    }
+  },
+  "settings": {
+    "polyfills": ["fetch"]
+  }
+}

+ 3 - 0
.ga

@@ -0,0 +1,3 @@
+{
+    "code":"UA-72788897-6"
+} 

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+# roadhog-api-doc ignore
+/src/utils/request-temp.js
+_roadhog-api-doc
+
+# production
+/dist
+.umi
+.umi-production
+
+# misc
+.DS_Store
+npm-debug.log*
+
+/coverage
+
+shop/

+ 25 - 0
.stylelintrc

@@ -0,0 +1,25 @@
+{
+  "extends": "stylelint-config-standard",
+  "rules": {
+    "selector-pseudo-class-no-unknown": null,
+    "shorthand-property-no-redundant-values": null,
+    "at-rule-empty-line-before": null,
+    "at-rule-name-space-after": null,
+    "comment-empty-line-before": null,
+    "declaration-bang-space-before": null,
+    "declaration-empty-line-before": null,
+    "function-comma-newline-after": null,
+    "function-name-case": null,
+    "function-parentheses-newline-inside": null,
+    "function-max-empty-lines": null,
+    "function-whitespace-after": null,
+    "number-leading-zero": null,
+    "number-no-trailing-zeros": null,
+    "rule-empty-line-before": null,
+    "selector-combinator-space-after": null,
+    "selector-list-comma-newline-after": null,
+    "selector-pseudo-element-colon-notation": null,
+    "unit-no-unknown": null,
+    "value-list-max-empty-lines": null
+  }
+}

+ 46 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at afc163@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Alipay.inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 91 - 0
README.us-EN.md

@@ -0,0 +1,91 @@
+English | [简体中文](./README.zh-CN.md)
+
+# ChannelCloud Frontend
+
+An out-of-box UI solution for enterprise applications as a React boilerplate.
+
+![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
+
+- Preview: http://preview.pro.ant.design
+- Home Page: http://pro.ant.design
+- Documentation: http://pro.ant.design/docs/getting-started
+- FAQ: http://pro.ant.design/docs/faq
+
+## Translation Recruitment :loudspeaker:
+
+We need your help: https://github.com/ant-design/ant-design-pro/issues/120
+
+## Features
+
+- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
+- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
+- :rocket: **State of The Art Development**: Newest development stack of React/dva/antd
+- :iphone: **Responsive**: Designed for varies of screen size
+- :art: **Themeing**: Customizable theme with simple config
+- :globe_with_meridians: **International**: Built-in i18n solution
+- :gear: **Best Practice**: Solid workflow make your code health
+- :1234: **Mock development**: Easy to use mock development solution
+- :white_check_mark: **UI Test**: Fly safely with unit test and e2e test
+
+## Templates
+
+```
+- Dashboard
+  - Analytic
+  - Monitor
+  - Workspace
+- Form
+  - Basic Form
+  - Step Form
+  - Advanced From
+- List
+  - Standard Table
+  - Standard List
+  - Card List
+  - Search List (Project/Applications/Article)
+- Profile
+  - Simple Profile
+  - Advanced Profile
+- Result
+  - Success
+  - Failed
+- Exception
+  - 403
+  - 404
+  - 500
+- User
+  - Login
+  - Register
+  - Register Result
+```
+
+## Usage
+
+```bash
+$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
+$ cd ant-design-pro
+$ npm install
+$ npm start         # visit http://localhost:8000
+```
+
+Or you can use the command tool: [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli)
+
+```bash
+$ npm install ant-design-pro-cli -g
+$ mkdir pro-demo && cd pro-demo
+$ pro new
+```
+
+More instruction at [documentation](http://pro.ant.design/docs/getting-started)。
+
+## Compatibility
+
+Modern browsers and IE11.
+
+## Contributing
+
+Any Contribution of following ways will be welcome:
+
+- Use Ant Design Pro in your daily work.
+- Submit [issue](http://github.com/ant-design/ant-design-pro/issues) to report bug or ask questions.
+- Propose [pull request](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.

+ 132 - 0
config/config.js

@@ -0,0 +1,132 @@
+import os from 'os';
+import pageRoutes from './router.config';
+
+const isDev = process.env.NODE_ENV === "development";
+const publicPath = isDev ? '/' :  './';
+export default {
+  targets: { // 配置浏览器最低版本  
+    ie: 9,
+  },
+  // exportStatic: {}, // 导出全部路由为静态页面
+  plugins: [
+    [
+      'umi-plugin-react',
+      {
+        antd: true,
+        dva: {
+          hmr: true
+        },
+        targets: {
+          ie: 11,
+        },
+        locale: {
+          enable: true, // default false
+          default: 'zh-CN', // default zh-CN
+          baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
+        },
+        // dynamicImport: true,  
+        ...(!process.env.TEST && os.platform() === 'darwin'
+          ? {
+              dll: {
+                include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
+                exclude: ['@babel/runtime'],
+              },
+              hardSource: false,
+              pwa: false
+            }
+          : {}),
+      },
+    ],
+    [
+      'umi-plugin-ga',
+      {
+        code: 'UA-72788897-6',
+        judge: () => process.env.APP_TYPE === 'site',
+      },
+    ],
+  ],
+  externals: {
+    '@antv/data-set': 'DataSet',
+  },
+  ignoreMomentLocale: true,
+  lessLoaderOptions: {
+    javascriptEnabled: true,
+  },
+  disableRedirectHoist: true,
+  cssLoaderOptions: {
+    modules: true,
+    getLocalIdent: (context, localIdentName, localName) => {
+      if (
+        context.resourcePath.includes('node_modules') ||
+        context.resourcePath.includes('ant.design.pro.less') ||
+        context.resourcePath.includes('global.less')
+      ) {
+        return localName;
+      }
+      const match = context.resourcePath.match(/src(.*)/);
+      if (match && match[1]) {
+        const antdProPath = match[1].replace('.less', '');
+        const arr = antdProPath
+          .split('/')
+          .map(a => a.replace(/([A-Z])/g, '-$1'))
+          .map(a => a.toLowerCase());
+        return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
+      }
+      return localName;
+    },
+  },
+  theme: {
+    'primary-color': '#E14C46',
+    'icon-normal-color': '#333',
+    'icon-disable-color': '#CCC',
+    "card-actions-background": "#f5f8fa",
+    "user-login-header-background": "rgb(248, 228, 228)"
+  },
+  // 路由配置
+  history: 'hash',
+  routes: pageRoutes,
+  publicPath: publicPath,
+  hash: !isDev,
+  devtool: isDev? "cheap-module-eval-source-map":"cheap-module-source-map", 
+  proxy: {
+    "/wbalone/open/getEncryptParam": {
+  	  target: "http://zt.maydos.com.cn/wbalone/open/getEncryptParam", 
+      changeOrigin: true,
+      pathRewrite: { "^/wbalone/open/getEncryptParam" : "" }
+    },
+    "/wbalone/account/login": {
+  	  target: "http://zt.maydos.com.cn/wbalone/account/login", 
+      changeOrigin: true,
+      pathRewrite: { "^/wbalone/account/login" : "" }
+    },
+    "/occ-cmpt": {
+  	  target: "http://zt.maydos.com.cn/occ-cmpt", 
+      changeOrigin: true,
+      pathRewrite: { "^/occ-cmpt" : "" }
+    },
+    "/g1": {
+  	  target: "http://zt.maydos.com.cn/g1",
+      changeOrigin: true,
+      pathRewrite: { "^/g1" : "" }
+    },
+    "/occ-": {
+  	  target: "http://zt.maydos.com.cn",
+      changeOrigin: true,
+      pathRewrite: { "^/" : "/" }
+  	},
+  },
+  manifest: {
+    name: 'CHANNEL CLOUD',
+    background_color: '#FFF',
+    description: 'An out-of-box UI solution for enterprise applications as a React boilerplate.',
+    display: 'standalone',
+    start_url: '/index.html',
+    icons: [
+      {
+        src: '/favicon.png',
+        sizes: '48x48',
+        type: 'image/png',
+      },
+    ],
+  },
+};

+ 200 - 0
config/router.config.js

@@ -0,0 +1,200 @@
+module.exports = [
+  {
+    path: '/gotoLogin',
+    name: '登录',
+    component: 'Login/Login.js'
+  },
+  {
+    path: '/404',
+    name: '404',
+    component: '404.js'
+  },
+  {
+    path: '/500',
+    name: '500',
+    component: '500.js'
+  },
+  {
+    path: '/403',
+    name: '403',
+    component: '403.js'
+  },
+  {
+    path: '/',
+    name: '要货',
+    component: '../layouts/buyer/HomeLayout.js',
+    icon: 'shopping-cart',
+    routes: [
+      {
+        path: '/',
+        component: 'Buyer/home/index.js'
+      },
+      {
+        path: '/buyer',
+        name: 'buyer',
+        routes: [
+          {
+            path: '/buyer/noticeList',
+            name: 'noticeList',
+            component: 'Buyer/noticeList/index.js',
+          },
+          {
+            path: '/buyer/orderListDetail',
+            name: 'orderListDetail',
+            component: 'Buyer/orderListDetail/index.js',
+          },
+          {
+            path: '/buyer/cartList',
+            name: 'cartList',
+            component: 'Buyer/cartList/index.js',
+          },
+          {
+            path: '/buyer/commodityDetails',
+            name: 'commodityDetails',
+            component: 'Buyer/commodityDetails/index.js',
+          },
+          {
+            path: '/buyer/orderList/:orderStatus?',
+            name: 'orderList',
+            component: 'Buyer/orderList/index.js',
+          },
+          {
+            path: '/buyer/orderSign',
+            name: 'orderSign',
+            component: 'Buyer/orderSign/index.js',
+          },
+          {
+            path: '/buyer/orderEdit',
+            name: 'orderEdit',
+            component: 'Buyer/orderEdit/index.js',
+          },
+          {
+            path: '/buyer/goodsPurchase',
+            name: 'goodsPurchase',
+            component: 'Buyer/goodsPurchase/index.js',
+          },
+          {
+            path: '/buyer/replenishment/:label?',
+            name: 'replenishment',
+            component: 'Buyer/replenishment/index.js',
+          },
+          {
+            path: '/buyer/announcement',
+            name: 'announcement',
+            component: 'Buyer/announcement/index.js',
+          },
+          {
+            path: '/buyer/orderSignListDetail',
+            name: 'orderSignListDetail',
+            component: 'Buyer/orderSignListDetail/index.js',
+          },
+          {
+            path: '/buyer/goodsSupplyList',
+            name: 'goodsSupplyList',
+            component: 'Buyer/goodsSupplyList/index.js',
+          },
+          {
+            path: '/buyer/goodsSupplyDetail',
+            name: 'goodsSupplyDetail',
+            component: 'Buyer/goodsSupplyDetail/index.js',
+          },
+          {
+            path: '/buyer/noticeDetail',
+            name: 'noticeDetail',
+            component: 'Buyer/noticeDetail/index.js',
+          },
+          {
+            path: '/buyer/credit',
+            name: 'credit',
+            component: 'Buyer/credit/index.js',
+          },
+          {
+            path: '/buyer/replenishmentParameter',
+            name: 'replenishmentParameter',
+            component: 'Buyer/replenishmentParameter/index.js',
+          },
+          {
+            path: '/buyer/goods/GoodsDetail',
+            name: 'GoodsDetail',
+            component: 'Buyer/goods/goodsDetail/index.js',
+          },
+          {
+            path: '/buyer/returnOrderList',
+            name: 'returnOrderList',
+            component: 'Buyer/returnOrderList/index.js',
+          },
+          {
+            path: '/buyer/returnOrderListDetail',
+            name: 'returnOrderListDetail',
+            component: 'Buyer/returnOrderListDetail/index.js',
+          },
+          {
+            path: '/buyer/returnOrderEdit',
+            name: 'returnOrderEdit',
+            component: 'Buyer/returnOrderEdit/index.js',
+          },
+          {
+            path: '/buyer/myAccount/cost',
+            name: 'cost',
+            component: 'Buyer/myAccount/cost/index.js',
+          },
+          {
+            path: '/buyer/myAccount/accountBalance/:tabIndex?',
+            name: 'accountBalance',
+            component: 'Buyer/myAccount/accountBalance/index.js',
+          },
+          {
+            path: '/buyer/myAccount/businessInfo',
+            name: 'businessInfo',
+            component: 'Buyer/myAccount/businessInfo/index.js',
+          },
+          {
+            path: '/buyer/myAccount/supplier',
+            name: 'supplier',
+            component: 'Buyer/myAccount/supplier/index.js',
+          },
+          {
+            path: '/buyer/reconciliations/costReconcile',
+            name: 'costReconcile',
+            component: 'Buyer/reconciliations/costReconcile/index.js',
+          },
+          {
+            path: '/buyer/reconciliations/dealReconcile',
+            name: 'dealReconcile',
+            component: 'Buyer/reconciliations/dealReconcile/index.js',
+          },
+          {
+            path: '/buyer/updatePassword',
+            name: 'updatePassword',
+            component: 'Buyer/updatePassword/index.js',
+          },
+          {
+            path: '/buyer/paymentNoticeEdit',
+            name: 'paymentNoticeEdit',
+            component: 'Buyer/paymentNoticeEdit/index.js',
+          },
+          {
+            path: '/buyer/paymentNoticeList',
+            name: 'paymentNoticeList',
+            component: 'Buyer/paymentNoticeList/index.js',
+          },
+          {
+            path: '/buyer/onlinePayEdit',
+            name: 'onlinePayEdit',
+            component: 'Buyer/onlinePayEdit/index.js',
+          },
+          {
+            path: '/buyer/onlinePayList',
+            name: 'onlinePayList',
+            component: 'Buyer/onlinePayList/index.js',
+          },
+          {
+            path: '/buyer/onlinePayList',
+            name: 'onlinePayList',
+            component: 'Buyer/onlinePayList/index.js',
+          }
+        ],
+      }
+    ],
+  },
+];

+ 0 - 0
mock/.gitkeep


+ 450 - 0
mock/IndividuationConfig.js

@@ -0,0 +1,450 @@
+export default {
+  orderListConfig: [{
+    field: 'poTypeName',
+    displayName: '订单类型',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单类型',
+      en_US: 'Type',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderCode',
+    displayName: '编码',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '编码',
+      en_US: 'Code',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'billStatusName',
+    displayName: '订单状态',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单状态',
+      en_US: 'Status',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalQuantity',
+    displayName: '订单数量',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单数量',
+      en_US: 'Quantity',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalAmount',
+    displayName: '订单金额',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单金额',
+      en_US: 'Amount',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderDate',
+    displayName: '订单日期',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单日期',
+      en_US: 'Date',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'remark',
+    displayName: '备注',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '备注',
+      en_US: 'remark',
+    }],
+    index: 0,
+    search: true,
+  }],
+  goodsListConfig: [{
+    field: 'poTypeName',
+    displayName: '订单类型',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单类型',
+      en_US: 'Type',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderCode',
+    displayName: '编码',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '编码',
+      en_US: 'Code',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'billStatusName',
+    displayName: '订单状态',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单状态',
+      en_US: 'Status',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalQuantity',
+    displayName: '订单数量',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单数量',
+      en_US: 'Quantity',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalAmount',
+    displayName: '订单金额',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单金额',
+      en_US: 'Amount',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderDate',
+    displayName: '订单日期',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单日期',
+      en_US: 'Date',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'remark',
+    displayName: '备注',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '备注',
+      en_US: 'remark',
+    }],
+    index: 0,
+    search: true,
+  }],
+  returnTreasuryListConfig: [{
+    field: 'poTypeName',
+    displayName: '订单类型',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单类型',
+      en_US: 'Type',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderCode',
+    displayName: '编码',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '编码',
+      en_US: 'Code',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'billStatusName',
+    displayName: '订单状态',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单状态',
+      en_US: 'Status',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalQuantity',
+    displayName: '订单数量',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单数量',
+      en_US: 'Quantity',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalAmount',
+    displayName: '订单金额',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单金额',
+      en_US: 'Amount',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderDate',
+    displayName: '订单日期',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单日期',
+      en_US: 'Date',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'remark',
+    displayName: '备注',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '备注',
+      en_US: 'remark',
+    }],
+    index: 0,
+    search: true,
+  }],
+  allocatingStorageListConfig: [{
+    field: 'poTypeName',
+    displayName: '订单类型',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单类型',
+      en_US: 'Type',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderCode',
+    displayName: '编码',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '编码',
+      en_US: 'Code',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'billStatusName',
+    displayName: '订单状态',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单状态',
+      en_US: 'Status',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalQuantity',
+    displayName: '订单数量',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单数量',
+      en_US: 'Quantity',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'totalAmount',
+    displayName: '订单金额',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单金额',
+      en_US: 'Amount',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'orderDate',
+    displayName: '订单日期',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '订单日期',
+      en_US: 'Date',
+    }],
+    index: 0,
+    search: true,
+  },
+  {
+    field: 'remark',
+    displayName: '备注',
+    value: '',
+    required: false,
+    isShow: true,
+    fieldType: 'string',
+    ref: '',
+    readonly: false,
+    nameList: [{
+      zh_CN: '备注',
+      en_US: 'remark',
+    }],
+    index: 0,
+    search: true,
+  }],
+};

File diff suppressed because it is too large
+ 1012 - 0
mock/api.js


+ 197 - 0
mock/chart.js

@@ -0,0 +1,197 @@
+import moment from 'moment';
+
+// mock data
+const visitData = [];
+const beginDay = new Date().getTime();
+
+const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
+for (let i = 0; i < fakeY.length; i += 1) {
+  visitData.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: fakeY[i],
+  });
+}
+
+const visitData2 = [];
+const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
+for (let i = 0; i < fakeY2.length; i += 1) {
+  visitData2.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: fakeY2[i],
+  });
+}
+
+const salesData = [];
+for (let i = 0; i < 12; i += 1) {
+  salesData.push({
+    x: `${i + 1}月`,
+    y: Math.floor(Math.random() * 1000) + 200,
+  });
+}
+const searchData = [];
+for (let i = 0; i < 50; i += 1) {
+  searchData.push({
+    index: i + 1,
+    keyword: `搜索关键词-${i}`,
+    count: Math.floor(Math.random() * 1000),
+    range: Math.floor(Math.random() * 100),
+    status: Math.floor((Math.random() * 10) % 2),
+  });
+}
+const salesTypeData = [
+  {
+    x: '家用电器',
+    y: 4544,
+  },
+  {
+    x: '食用酒水',
+    y: 3321,
+  },
+  {
+    x: '个护健康',
+    y: 3113,
+  },
+  {
+    x: '服饰箱包',
+    y: 2341,
+  },
+  {
+    x: '母婴产品',
+    y: 1231,
+  },
+  {
+    x: '其他',
+    y: 1231,
+  },
+];
+
+const salesTypeDataOnline = [
+  {
+    x: '家用电器',
+    y: 244,
+  },
+  {
+    x: '食用酒水',
+    y: 321,
+  },
+  {
+    x: '个护健康',
+    y: 311,
+  },
+  {
+    x: '服饰箱包',
+    y: 41,
+  },
+  {
+    x: '母婴产品',
+    y: 121,
+  },
+  {
+    x: '其他',
+    y: 111,
+  },
+];
+
+const salesTypeDataOffline = [
+  {
+    x: '家用电器',
+    y: 99,
+  },
+  {
+    x: '个护健康',
+    y: 188,
+  },
+  {
+    x: '服饰箱包',
+    y: 344,
+  },
+  {
+    x: '母婴产品',
+    y: 255,
+  },
+  {
+    x: '其他',
+    y: 65,
+  },
+];
+
+const offlineData = [];
+for (let i = 0; i < 10; i += 1) {
+  offlineData.push({
+    name: `门店${i}`,
+    cvr: Math.ceil(Math.random() * 9) / 10,
+  });
+}
+const offlineChartData = [];
+for (let i = 0; i < 20; i += 1) {
+  offlineChartData.push({
+    x: (new Date().getTime()) + (1000 * 60 * 30 * i),
+    y1: Math.floor(Math.random() * 100) + 10,
+    y2: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+const radarOriginData = [
+  {
+    name: '个人',
+    ref: 10,
+    koubei: 8,
+    output: 4,
+    contribute: 5,
+    hot: 7,
+  },
+  {
+    name: '团队',
+    ref: 3,
+    koubei: 9,
+    output: 6,
+    contribute: 3,
+    hot: 1,
+  },
+  {
+    name: '部门',
+    ref: 4,
+    koubei: 1,
+    output: 6,
+    contribute: 5,
+    hot: 7,
+  },
+];
+
+//
+const radarData = [];
+const radarTitleMap = {
+  ref: '引用',
+  koubei: '口碑',
+  output: '产量',
+  contribute: '贡献',
+  hot: '热度',
+};
+radarOriginData.forEach((item) => {
+  Object.keys(item).forEach((key) => {
+    if (key !== 'name') {
+      radarData.push({
+        name: item.name,
+        label: radarTitleMap[key],
+        value: item[key],
+      });
+    }
+  });
+});
+
+export const getFakeChartData = {
+  visitData,
+  visitData2,
+  salesData,
+  searchData,
+  offlineData,
+  offlineChartData,
+  salesTypeData,
+  salesTypeDataOnline,
+  salesTypeDataOffline,
+  radarData,
+};
+
+export default {
+  getFakeChartData,
+};

+ 85 - 0
mock/notices.js

@@ -0,0 +1,85 @@
+export default {
+  getNotices(req, res) {
+    res.json([{
+      id: '000000001',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '你收到了 14 份新周报',
+      datetime: '2017-08-09',
+      type: '通知',
+    }, {
+      id: '000000002',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
+      datetime: '2017-08-08',
+      type: '通知',
+    }, {
+      id: '000000003',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
+      title: '这种模板可以区分多种通知类型',
+      datetime: '2017-08-07',
+      read: true,
+      type: '通知',
+    }, {
+      id: '000000004',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+      title: '左侧图标用于区分不同的类型',
+      datetime: '2017-08-07',
+      type: '通知',
+    }, {
+      id: '000000005',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '内容不要超过两行字,超出时自动截断',
+      datetime: '2017-08-07',
+      type: '通知',
+    }, {
+      id: '000000006',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '曲丽丽 评论了你',
+      description: '描述信息描述信息描述信息',
+      datetime: '2017-08-07',
+      type: '消息',
+    }, {
+      id: '000000007',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '朱偏右 回复了你',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: '消息',
+    }, {
+      id: '000000008',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '标题',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: '消息',
+    }, {
+      id: '000000009',
+      title: '任务名称',
+      description: '任务需要在 2017-01-12 20:00 前启动',
+      extra: '未开始',
+      status: 'todo',
+      type: '待办',
+    }, {
+      id: '000000010',
+      title: '第三方紧急代码变更',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '马上到期',
+      status: 'urgent',
+      type: '待办',
+    }, {
+      id: '000000011',
+      title: '信息安全考试',
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
+      extra: '已耗时 8 天',
+      status: 'doing',
+      type: '待办',
+    }, {
+      id: '000000012',
+      title: 'ABCD 版本发布',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '进行中',
+      status: 'processing',
+      type: '待办',
+    }]);
+  },
+};

+ 158 - 0
mock/profile.js

@@ -0,0 +1,158 @@
+const basicGoods = [
+  {
+    id: '1234561',
+    name: '矿泉水 550ml',
+    barcode: '12421432143214321',
+    price: '2.00',
+    num: '1',
+    amount: '2.00',
+  },
+  {
+    id: '1234562',
+    name: '凉茶 300ml',
+    barcode: '12421432143214322',
+    price: '3.00',
+    num: '2',
+    amount: '6.00',
+  },
+  {
+    id: '1234563',
+    name: '好吃的薯片',
+    barcode: '12421432143214323',
+    price: '7.00',
+    num: '4',
+    amount: '28.00',
+  },
+  {
+    id: '1234564',
+    name: '特别好吃的蛋卷',
+    barcode: '12421432143214324',
+    price: '8.50',
+    num: '3',
+    amount: '25.50',
+  },
+];
+
+const basicProgress = [
+  {
+    key: '1',
+    time: '2017-10-01 14:10',
+    rate: '联系客户',
+    status: 'processing',
+    operator: '取货员 ID1234',
+    cost: '5mins',
+  },
+  {
+    key: '2',
+    time: '2017-10-01 14:05',
+    rate: '取货员出发',
+    status: 'success',
+    operator: '取货员 ID1234',
+    cost: '1h',
+  },
+  {
+    key: '3',
+    time: '2017-10-01 13:05',
+    rate: '取货员接单',
+    status: 'success',
+    operator: '取货员 ID1234',
+    cost: '5mins',
+  },
+  {
+    key: '4',
+    time: '2017-10-01 13:00',
+    rate: '申请审批通过',
+    status: 'success',
+    operator: '系统',
+    cost: '1h',
+  },
+  {
+    key: '5',
+    time: '2017-10-01 12:00',
+    rate: '发起退货申请',
+    status: 'success',
+    operator: '用户',
+    cost: '5mins',
+  },
+];
+
+const advancedOperation1 = [
+  {
+    key: 'op1',
+    type: '订购关系生效',
+    name: '曲丽丽',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+  {
+    key: 'op2',
+    type: '财务复审',
+    name: '付小小',
+    status: 'reject',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '不通过原因',
+  },
+  {
+    key: 'op3',
+    type: '部门初审',
+    name: '周毛毛',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+  {
+    key: 'op4',
+    type: '提交订单',
+    name: '林东东',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '很棒',
+  },
+  {
+    key: 'op5',
+    type: '创建订单',
+    name: '汗牙牙',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+const advancedOperation2 = [
+  {
+    key: 'op1',
+    type: '订购关系生效',
+    name: '曲丽丽',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+const advancedOperation3 = [
+  {
+    key: 'op1',
+    type: '创建订单',
+    name: '汗牙牙',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+export const getProfileBasicData = {
+  basicGoods,
+  basicProgress,
+};
+
+export const getProfileAdvancedData = {
+  advancedOperation1,
+  advancedOperation2,
+  advancedOperation3,
+};
+
+export default {
+  getProfileBasicData,
+  getProfileAdvancedData,
+};

+ 131 - 0
mock/rule.js

@@ -0,0 +1,131 @@
+import { getUrlParams } from './utils';
+
+// mock tableListDataSource
+let tableListDataSource = [];
+for (let i = 0; i < 46; i += 1) {
+  tableListDataSource.push({
+    key: i,
+    disabled: ((i % 6) === 0),
+    href: 'https://ant.design',
+    avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2],
+    no: `TradeCode ${i}`,
+    title: `一个任务名称 ${i}`,
+    owner: '曲丽丽',
+    description: '这是一段描述',
+    callNo: Math.floor(Math.random() * 1000),
+    status: Math.floor(Math.random() * 10) % 4,
+    updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
+    createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
+    progress: Math.ceil(Math.random() * 100),
+  });
+}
+
+export function getRule(req, res, u) {
+  let url = u;
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
+    url = req.url; // eslint-disable-line
+  }
+
+  const params = getUrlParams(url);
+
+  let dataSource = [...tableListDataSource];
+
+  if (params.sorter) {
+    const s = params.sorter.split('_');
+    dataSource = dataSource.sort((prev, next) => {
+      if (s[1] === 'descend') {
+        return next[s[0]] - prev[s[0]];
+      }
+      return prev[s[0]] - next[s[0]];
+    });
+  }
+
+  if (params.status) {
+    const status = params.status.split(',');
+    let filterDataSource = [];
+    status.forEach((s) => {
+      filterDataSource = filterDataSource.concat(
+        [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
+      );
+    });
+    dataSource = filterDataSource;
+  }
+
+  if (params.no) {
+    dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1);
+  }
+
+  let pageSize = 10;
+  if (params.pageSize) {
+    pageSize = params.pageSize * 1;
+  }
+
+  const result = {
+    list: dataSource,
+    pagination: {
+      total: dataSource.length,
+      pageSize,
+      current: parseInt(params.currentPage, 10) || 1,
+    },
+  };
+
+  if (res && res.json) {
+    res.json(result);
+  } else {
+    return result;
+  }
+}
+
+export function postRule(req, res, u, b) {
+  let url = u;
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
+    url = req.url; // eslint-disable-line
+  }
+
+  const body = (b && b.body) || req.body;
+  const { method, no, description } = body;
+
+  switch (method) {
+    /* eslint no-case-declarations:0 */
+    case 'delete':
+      tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1);
+      break;
+    case 'post':
+      const i = Math.ceil(Math.random() * 10000);
+      tableListDataSource.unshift({
+        key: i,
+        href: 'https://ant.design',
+        avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2],
+        no: `TradeCode ${i}`,
+        title: `一个任务名称 ${i}`,
+        owner: '曲丽丽',
+        description,
+        callNo: Math.floor(Math.random() * 1000),
+        status: Math.floor(Math.random() * 10) % 2,
+        updatedAt: new Date(),
+        createdAt: new Date(),
+        progress: Math.ceil(Math.random() * 100),
+      });
+      break;
+    default:
+      break;
+  }
+
+  const result = {
+    list: tableListDataSource,
+    pagination: {
+      total: tableListDataSource.length,
+    },
+  };
+
+  if (res && res.json) {
+    res.json(result);
+  } else {
+    return result;
+  }
+}
+
+export default {
+  getRule,
+  postRule,
+};

+ 45 - 0
mock/utils.js

@@ -0,0 +1,45 @@
+export const imgMap = {
+  user: 'https://gw.alipayobjects.com/zos/rmsportal/UjusLxePxWGkttaqqmUI.png',
+  a: 'https://gw.alipayobjects.com/zos/rmsportal/ZrkcSjizAKNWwJTwcadT.png',
+  b: 'https://gw.alipayobjects.com/zos/rmsportal/KYlwHMeomKQbhJDRUVvt.png',
+  c: 'https://gw.alipayobjects.com/zos/rmsportal/gabvleTstEvzkbQRfjxu.png',
+  d: 'https://gw.alipayobjects.com/zos/rmsportal/jvpNzacxUYLlNsHTtrAD.png',
+};
+
+// refers: https://www.sitepoint.com/get-url-parameters-with-javascript/
+export function getUrlParams(url) {
+  const d = decodeURIComponent;
+  let queryString = url ? url.split('?')[1] : window.location.search.slice(1);
+  const obj = {};
+  if (queryString) {
+    queryString = queryString.split('#')[0]; // eslint-disable-line
+    const arr = queryString.split('&');
+    for (let i = 0; i < arr.length; i += 1) {
+      const a = arr[i].split('=');
+      let paramNum;
+      const paramName = a[0].replace(/\[\d*\]/, (v) => {
+        paramNum = v.slice(1, -1);
+        return '';
+      });
+      const paramValue = typeof (a[1]) === 'undefined' ? true : a[1];
+      if (obj[paramName]) {
+        if (typeof obj[paramName] === 'string') {
+          obj[paramName] = d([obj[paramName]]);
+        }
+        if (typeof paramNum === 'undefined') {
+          obj[paramName].push(d(paramValue));
+        } else {
+          obj[paramName][paramNum] = d(paramValue);
+        }
+      } else {
+        obj[paramName] = d(paramValue);
+      }
+    }
+  }
+  return obj;
+}
+
+export default {
+  getUrlParams,
+  imgMap,
+};

File diff suppressed because it is too large
+ 28012 - 0
package-lock.json


+ 69 - 0
package.json

@@ -0,0 +1,69 @@
+{
+  "name": "occ-portal-shop",
+  "version": "2.0.0",
+  "description": "An out-of-box UI solution for enterprise applications",
+  "private": true,
+  "scripts": {
+    "start": "cross-env APP_TYPE=site umi dev",
+    "start:no-proxy": "cross-env NO_PROXY=true ESLINT=none umi dev",
+    "build": "umi build",
+    "analyze": "cross-env ANALYZE=true umi build",
+    "lint:style": "stylelint \"src/**/*.less\" --syntax less",
+    "lint": "eslint --ext .js src mock tests && npm run lint:style",
+    "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
+    "lint-staged": "lint-staged",
+    "lint-staged:js": "eslint --ext .js",
+    "test": "umi test",
+    "test:component": "umi test ./src/components",
+    "test:all": "node ./tests/run-tests.js",
+    "prettier": "prettier --write ./src/**/**/**/*"
+  },
+  "dependencies": {
+    "classnames": "^2.2.5",
+    "js-cookie": "^2.2.0",
+    "less": "2.7.2",
+    "qrcode-react": "0.1.16"
+  },
+  "devDependencies": {
+    "cross-env": "^5.1.1",
+    "cross-port-killer": "^1.0.1",
+    "enzyme": "^3.1.0",
+    "eslint": "^4.19.1",
+    "eslint-config-airbnb": "^17.0.0",
+    "eslint-config-prettier": "^3.0.1",
+    "eslint-plugin-babel": "^5.1.0",
+    "eslint-plugin-compat": "^2.6.2",
+    "eslint-plugin-import": "^2.8.0",
+    "eslint-plugin-jsx-a11y": "^6.1.2",
+    "eslint-plugin-markdown": "^1.0.0-beta.6",
+    "eslint-plugin-react": "^7.11.1",
+    "lint-staged": "^7.2.0",
+    "merge-umi-mock-data": "^0.0.3",
+    "mockjs": "^1.0.1-beta3",
+    "stylelint": "^8.4.0",
+    "stylelint-config-prettier": "^3.0.4",
+    "stylelint-config-standard": "^18.0.0",
+    "umi": "^2.1.2",
+    "umi-plugin-ga": "^1.1.3",
+    "umi-plugin-react": "^1.1.1"
+  },
+  "optionalDependencies": {
+    "puppeteer": "^1.9.0"
+  },
+  "lint-staged": {
+    "**/*.{js,jsx,less}": [
+      "prettier --write",
+      "git add"
+    ],
+    "**/*.{js,jsx}": "lint-staged:js",
+    "**/*.less": "stylelint --syntax less"
+  },
+  "engines": {
+    "node": ">=8.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ]
+}

BIN
public/favicon.ico


BIN
src/assets/bannerError.png


BIN
src/assets/imgError.png


BIN
src/assets/login-bg.png


BIN
src/assets/logo.png


+ 63 - 0
src/common/nav.js

@@ -0,0 +1,63 @@
+import dynamic from "dva/dynamic";
+import orderSignList from "../models/orderSignList";
+import noticeDetail from "../models/noticeDetail";
+import credit from "../models/credit";
+// import BasicModules from "@/pages/BasicModules";
+
+// wrapper of dynamic
+const dynamicWrapper = (app, models, component) =>
+  dynamic({
+    app,
+    // models: () => models.map(function(m) {
+    //   const model = import(`../models/${m}.js`);
+    //   console.log(m);
+    //   return model;
+    // }),
+    models: () => models.map(m => import(`../models/${m}.js`)),
+    // component:
+    // models.length > 0 ? () => new BasicModules(component(), app) : component
+    component
+  });
+
+// nav data
+export const getNavData = app => [
+  {
+    name: "要货",
+    path: "buyer",
+    layout: "BuyerHomeLayout",
+    icon: "shopping-cart",
+    component: dynamicWrapper(
+      app,
+      [
+        "homeLayout",
+        "home",
+        "goodsLevel",
+        "goodsCommon",
+        "goodsHot",
+        "orderEdit",
+        "goodsPurchase",
+        "orderList",
+        "cartList",
+        "commodityDetails",
+        "orderListDetail",
+        "orderSignList",
+        "announcement",
+        "orderSignListDetail",
+        "goodsSupplyList",
+        "goodsSupplyDetail",
+        "noticeDetail",
+        "credit",
+        "replenishmentParameter",
+        "returnOrderList",
+        "returnOrderListDetail",
+        "returnOrderEdit",
+        "myAccount",
+        "public",
+        "paymentNotice",
+        "goodsOpts"
+      ],
+      () => import("../layouts/buyer/HomeLayout")
+    ),
+    children: []
+  }
+];

+ 62 - 0
src/components/AmountFun/index.js

@@ -0,0 +1,62 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import styles from "./index.less";
+
+class GoodsBuyIpt extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {
+      val: this.props.num
+    };
+  }
+  numChange(e) {
+    e.preventDefault();
+    const value = e.target.value == "" ? 0 : e.target.value;
+    this.setState({
+      val: value
+    });
+    this.props.onChange(value);
+  }
+  lessHandle() {
+    let { val } = this.state;
+    if (val < 1) {
+      val = 0;
+    } else {
+      val--;
+    }
+    this.setState({
+      val: val
+    });
+    this.props.onChange(val);
+  }
+  addHandle() {
+    let { val } = this.state;
+    if (val > 99) {
+      val = 99;
+    } else {
+      val++;
+    }
+    this.setState({
+      val: val
+    });
+    this.props.onChange(val);
+  }
+  render() {
+    return (
+      <div className={styles.goodsBuyIpt}>
+        <span className={styles.subBtn} onClick={this.lessHandle.bind(this)}>
+          -
+        </span>
+        <span className={styles.addBtn} onClick={this.addHandle.bind(this)}>
+          +
+        </span>
+        <input
+          className={styles.ipt}
+          value={this.state.val}
+          onChange={this.numChange.bind(this)}
+        />
+      </div>
+    );
+  }
+}
+export default GoodsBuyIpt;

+ 46 - 0
src/components/AmountFun/index.less

@@ -0,0 +1,46 @@
+@import "~antd/lib/style/themes/default.less";
+
+.goodsBuyIpt {
+  clear: both;
+  height: 30px;
+  width: 100%;
+  background: #fff;
+  border: 1px solid #ddd;
+  border-radius: 2px;
+  ::after{
+    content:".";display:block;visibility:hidden;height:0;clear:both;
+  }
+}
+.subBtn {
+  float: left;
+  width: 30px;
+  height: 30px;
+  line-height: 20px;
+  text-align: center;
+  color: #333;
+  cursor: pointer;
+  font-size: 30px;
+  border-right:1px solid #ddd;
+}
+.addBtn {
+  float: right;
+  width: 30px;
+  height: 30px;
+  line-height: 22px;
+  text-align: center;
+  color: #333;
+  cursor: pointer;
+  font-size: 20px;
+  border-left:1px solid #ddd;
+}
+.ipt {
+  display: block;
+  margin: 0;padding: 0;
+  border: none;
+  outline: none;
+  background: none;
+  width: 90px;
+  text-align: center;
+  line-height: 30px;
+  font-size: 12px;
+}

+ 103 - 0
src/components/BreadcrumbPackage/index.js

@@ -0,0 +1,103 @@
+import React, { PureComponent } from "react";
+import { Breadcrumb, Icon } from "antd";
+import { connect } from "dva";
+import { Link, routerRedux } from "dva/router";
+import styles from "./index.less";
+import MenuDatas from "../../layouts/buyer/menuDatas.json";
+
+class BreadcrumbPackage extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+  matchMenuPath(menuPath) {
+    const { searchParam } = this.props;
+    const items = [];
+    for (let i = 0; i < MenuDatas.length; i++) {
+      const firstMenu = MenuDatas[i];
+      let secondMenu = null;
+      let currMenu = null;
+      if (firstMenu.children) {
+        const secondMenus =  firstMenu.children;
+        currMenu = secondMenus.find(
+          item => (item.path == menuPath || item.path == (menuPath + searchParam))
+        );
+        if (currMenu && currMenu.key) {
+          items.push(firstMenu);
+          items.push(currMenu);
+          return items;
+        } else {
+          for (let j = 0; j < secondMenus.length; j++) {
+            secondMenu = secondMenus[j];
+            if (secondMenu.children) {
+              currMenu = secondMenu.children.find(
+                item => (item.path == menuPath || item.path == (menuPath + searchParam))
+              );
+              if (currMenu && currMenu.key) {
+                items.push(firstMenu);
+                items.push(secondMenu);
+                items.push(currMenu);
+                return items;
+              }
+            }
+          }
+        }
+      }
+    }
+    return [];
+  }
+  render() {
+    const { dispatch, menuItemPath, additems } = this.props;
+    let menuPath = menuItemPath;
+    if (!menuPath) {
+      menuPath = localStorage.getItem("menuItemPath");
+    }
+    const getBreadcrumbHtml = () => {
+      if (!menuPath && !(additems && additems.length > 0)) {
+        dispatch(
+          routerRedux.push({
+            pathname: "/"
+          })
+        );
+      }
+      let items = [];
+      if (menuPath) {
+        const length = menuPath.split("/").length;
+        for (let i = length - 1; i >= 2; i--) {
+          items = this.matchMenuPath(menuPath);
+          if (items && items.length > 0) break;
+          menuPath = menuPath.substring(0, menuPath.lastIndexOf("/"));
+        }
+      }
+      if (additems) {
+        items = items.concat(additems);
+      }
+      const length = items.length - 1;
+      return items.map((item, index) => {
+        if (item.path && index < length) {
+          return (
+            <Breadcrumb.Item key={item.key}>
+              <Link to={item.path}>{item.title}</Link>
+            </Breadcrumb.Item>
+          );
+        } else {
+          return <Breadcrumb.Item key={item.key}>{item.title}</Breadcrumb.Item>;
+        }
+      });
+    };
+    return (
+      <div className={styles.classHead}>
+        <Breadcrumb separator={<Icon type="right" />}>
+          <Breadcrumb.Item>
+            <Link to="/">首页</Link>
+          </Breadcrumb.Item>
+          {getBreadcrumbHtml()}
+        </Breadcrumb>
+      </div>
+    );
+  }
+}
+export default connect(state => ({
+  menuItemPath: state.global.menuItem.menuItemPath,
+  searchParam: state.global.menuItem.searchParam || "",
+  additems: state.global.additems
+}))(BreadcrumbPackage);

+ 23 - 0
src/components/BreadcrumbPackage/index.less

@@ -0,0 +1,23 @@
+.classHead {
+  width: 100%;
+  height: 36px;
+  background: #f5f5f5;
+  padding: 10px 0 10px 20px;
+}
+:global {
+  .ant-breadcrumb {
+    font-size: 12px;
+    > span {
+      color: #666;
+      > .ant-breadcrumb-link > a {
+        color: #666;
+      }
+      &:last-child {
+        color: #151515;
+      }
+    }
+    .ant-breadcrumb-separator {
+      margin: 0 6px 0 13px;
+    }
+  }
+}

+ 446 - 0
src/components/CartListComp/index.js

@@ -0,0 +1,446 @@
+import React, { Component } from "react";
+import _ from "lodash";
+import { connect } from "dva";
+import { routerRedux } from "dva/router";
+import {
+  Menu,
+  Icon,
+  Pagination,
+  Checkbox,
+  Button,
+  message,
+  Popconfirm
+} from "antd";
+import LazyLoad from "react-lazy-load";
+import styles from "./index.less";
+import FooterToolbar from "../FooterToolbar";
+import GoodsBuyIpt from "../GoodsBuyIpt/index.js";
+import CustIcon, { STATUS as IconStatus } from "../CustIcon";
+import { amountPrecision } from '@/utils/utils';
+import erroImg  from "@/assets/imgError.png";
+import { goodsVersion } from '@/utils/common';
+
+/**
+ * 购物车-商品显示-列表显示
+ * @param cartCheckStatus: {saleOrgId+goodsId: Boolean};用来标示购物车商品的选中状态
+ */
+class CartListComp extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      allPrice: 0, // 总价
+      selectNum: 0 // 已选个数
+    };
+    this.state.checkAllStatus = this.props.checkAllStatus;
+  }
+  componentWillMount() {
+    const { dispatch } = this.props;
+    dispatch({
+      type: "cartList/getCartListData"
+    });
+  }
+  // 获取已经选择的数据
+  getCheckedData(cartCheckStatus) {
+    const { cartListData = {}, dispatch } = this.props;
+    const ids = [];
+    let checkData;
+    const childDatas = [];
+    for (let i = 0; i < cartListData.length; i++) {
+      const data = cartListData[i];
+      for (const j in cartCheckStatus) {
+        if (cartCheckStatus[j] !== true) continue;
+        if (j == data.saleOrgId) {
+          checkData = _.cloneDeep(data);
+          break;
+        } else if (j.startsWith(data.saleOrgId)) {
+          for (let m = 0; m < cartListData[i].cartlist.length; m++) {
+            const childData = cartListData[i].cartlist[m];
+            if (j == `${data.saleOrgId}${childData.id}`) {
+              childDatas.push(childData);
+            }
+          }
+          checkData = _.cloneDeep(data);
+        }
+      }
+    }
+    if (childDatas && childDatas.length > 0) {
+      checkData.cartlist = childDatas;
+    }
+    console.log(checkData)
+    return checkData;
+  }
+  // 总价
+  allPrice(cartCheckStatus) {
+    const checkData = this.getCheckedData(
+      cartCheckStatus || this.state.cartCheckStatus
+    );
+    let selectNum = 0;
+    let allPrice = 0;
+    if (checkData) {
+      for (const cart of checkData.cartlist) {
+        allPrice += parseFloat(cart.salePrice) * parseFloat(cart.orderNum) * parseFloat(cart.conversionRate);
+        selectNum += parseFloat(cart.orderNum);
+      }
+    }
+    this.setState({
+      allPrice,
+      selectNum,
+      cartCheckStatus
+    });
+  }
+  // 去下单
+  goBuyBtn() {
+    const { dispatch } = this.props;
+    const { cartCheckStatus } = this.state;
+    const checkData = this.getCheckedData(cartCheckStatus);
+    if (!checkData) {
+      message.warning("请选择商品!");
+      return;
+    }
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/orderEdit",
+        cartData: checkData
+      })
+    );
+  }
+  // 购买数量改变
+  orderNumChange(index, cartGroupItem, num) {
+    const { dispatch } = this.props;
+    let { cartListData } = this.props;
+    cartListData = cartListData.concat([]);
+    let newCartlistData = '';
+    for (let i = 0; i < cartListData.length; i++) {
+        if (cartListData[i].saleOrgCode == cartGroupItem.saleOrgCode) {
+            newCartlistData = cartListData[i].cartlist;
+            const conversionRate = parseFloat(newCartlistData[index][`conversionRate`] || "1");
+            newCartlistData[index][`orderNum`] = num;
+            newCartlistData[index][`mainNum`] = num * conversionRate;
+            cartListData[i].cartlist[index][`orderNum`] = num;
+            cartListData[i].cartlist[index][`mainNum`] = num * conversionRate;
+        }
+    }
+    dispatch({
+      type: "cartList/updateCart",
+      payload: newCartlistData[index]
+    }).finally(() => {
+      dispatch({
+        type: "cartList/cartListData",
+        cartListData: cartListData
+      });
+    });
+  }
+  // checkAll全选
+  checkAllClick(cartGroupItem) {
+    const cartCheckStatus =
+      this.state.cartCheckStatus || this.props.cartCheckStatus;
+
+    if (!cartCheckStatus[cartGroupItem.saleOrgId]) {
+      // 勾选供货方则自动选择所有子元素
+      cartCheckStatus[`checkedSize${cartGroupItem.saleOrgId}`] =
+        cartCheckStatus[`childSize${cartGroupItem.saleOrgId}`];
+      for (const i in cartCheckStatus) {
+        if (i.startsWith(cartGroupItem.saleOrgId)) {
+          cartCheckStatus[i] = true;
+        } else if (
+          i.startsWith("checkedSize") &&
+          !i.startsWith(`checkedSize${cartGroupItem.saleOrgId}`)
+        ) {
+          cartCheckStatus[i] = 0;
+        } else if (!i.startsWith("childSize")) {
+          cartCheckStatus[i] = false;
+        }
+      }
+    } else {
+      // 反选供货方则反选其所有子元素
+      cartCheckStatus[`checkedSize${cartGroupItem.saleOrgId}`] = 0;
+      for (const i in cartCheckStatus) {
+        if (i.startsWith(cartGroupItem.saleOrgId)) {
+          cartCheckStatus[i] = false;
+        }
+      }
+    }
+    this.allPrice(cartCheckStatus);
+  }
+  // 选中商品,改变状态
+  goodsCheckChange(groupId, cartId) {
+    const { dispatch } = this.props;
+    const cartCheckStatus =
+      this.state.cartCheckStatus || this.props.cartCheckStatus;
+    const key = `${groupId}${cartId}`;
+    cartCheckStatus[key] = !cartCheckStatus[key];
+    // 供货方分组计数
+    if (cartCheckStatus[key]) {
+      cartCheckStatus[`checkedSize${groupId}`] += 1;
+    } else {
+      cartCheckStatus[`checkedSize${groupId}`] -= 1;
+    }
+    // 所有子元素被选择则自动勾选供货方分组
+    if (
+      cartCheckStatus[`checkedSize${groupId}`] ===
+      cartCheckStatus[`childSize${groupId}`]
+    ) {
+      cartCheckStatus[groupId] = true;
+    } else {
+      cartCheckStatus[groupId] = false;
+    }
+    // 反选其他供货方分组数据
+    for (const i in cartCheckStatus) {
+      if (
+        !i.startsWith(groupId) &&
+        !i.startsWith("childSize") &&
+        !i.startsWith(`checkedSize${groupId}`)
+      ) {
+        cartCheckStatus[i] = false;
+        if (i.startsWith("checkedSize")) {
+          cartCheckStatus[i] = 0;
+        }
+      }
+    }
+    this.allPrice(cartCheckStatus);
+  }
+  // 商品单项删除
+  deleteGoods(cartItem) {
+    const { dispatch } = this.props;
+    // 删除后台
+    dispatch({
+      type: "cartList/deleteCartListData",
+      payload: { id: cartItem.id }
+    });
+  }
+  // 删除选中商品
+  selectDelete() {
+    const { cartCheckStatus } = this.state;
+    const { cartListData, dispatch } = this.props;
+
+    const checkData = this.getCheckedData(cartCheckStatus);
+    if (!checkData || checkData.lenght == 0) {
+      message.warning("请选择商品!");
+    }
+    const ids = [];
+    for (const data of checkData.cartlist) {
+      ids.push(data.id);
+    }
+    // 删除后台
+    dispatch({
+      type: "cartList/deleteCartListData",
+      payload: { id: ids.join(",") }
+    });
+  }
+
+  // 删除选中商品
+  deleteAll() {
+    // const { cartCheckStatus } = this.state;
+    // const { cartListData, dispatch } = this.props;
+
+    // const checkData = this.getCheckedData(cartCheckStatus);
+    // if (!checkData || checkData.lenght == 0) {
+    //   message.warning("请选择商品!");
+    // }
+    // const ids = [];
+    // for (const data of checkData.cartlist) {
+    //   ids.push(data.id);
+    // }
+    // 删除后台
+    const { dispatch } = this.props;
+    dispatch({
+      type: "cartList/deleteAllCartListData"
+    });
+  }
+   // 图片加载失败
+   imgErro(e) {
+    e.target.src = erroImg;
+  }
+  // 跳转详情
+  toDetailHandle(data, e) {
+    e.preventDefault();
+    const { dispatch } = this.props;
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/goods/GoodsDetail",
+        query: {
+          code: data.code,
+          id: data.goodsId,
+          saleOrgId: data.saleOrgId ? data.saleOrgId : data.supplier,
+          productId: data.productId
+        }
+      })
+    );
+    localStorage.setItem("goodsSku", data.code);
+    localStorage.setItem("goodsDetailsId", data.goodsId);  // 商品Id
+    localStorage.setItem("goodsDetailSsaleOrgId", data.saleOrgId ? data.saleOrgId : data.supplier);  // 供应商
+    localStorage.setItem("goodsDetailProductId", data.productId);  // 产品id
+  }
+  render() {
+    const { cartListData } = this.props;
+    const { allPrice, selectNum, cartCheckStatus } = this.state;
+    // 返回是否全选状态
+    const checkAllStatus = cartGroupItem => {
+      if (!cartCheckStatus) return false;
+      return cartCheckStatus[cartGroupItem.saleOrgId];
+    };
+    // 商品列表-子表
+    const goodsListChildHtml = cartGroupItem => {
+      if (!cartGroupItem.cartlist) return null;
+      const html = cartGroupItem.cartlist.map((item, index) => (
+        <div
+          className={styles.goodsChildList}
+          key={`${cartGroupItem.saleOrgId}${item.goodsId}`}
+        >
+          <div className={styles.goodsNameBox}>
+            <div className={styles.goodsCheck}>
+              <Checkbox
+                onChange={this.goodsCheckChange.bind(
+                  this,
+                  cartGroupItem.saleOrgId,
+                  item.id
+                )}
+                checked={
+                  cartCheckStatus
+                    ? cartCheckStatus[`${cartGroupItem.saleOrgId}${item.id}`]
+                    : false
+                }
+              />
+            </div>
+            <div className={styles.imgBox}>
+              <LazyLoad height={60} offsetVertical={60}>
+                <img src={item.goodsImg ? item.goodsImg : erroImg} alt="" onError={this.imgErro.bind(this)}  style={{ width: '60px', height: '60px', border: '1px solid #eee', cursor: 'pointer' }} onClick={this.toDetailHandle.bind(this, item)} />
+              </LazyLoad>
+            </div>
+            <div className={styles.goodsInfo}>
+              <p className={styles.goodsTitleName}>{item.goodsDisplayName.length > 20 ? `${item.goodsDisplayName.substr(0, 20)}...` : item.goodsDisplayName}</p>
+              <p style={{ color: '#999','font-size': '12px', margin: '0px'}}>
+                <span>编码:{item.goodsCode}</span>
+                <span style={{'margin-left': '20px'}}>型号:{item.model}</span>
+              </p>
+              <p style={{ color: '#999','font-size': '12px', margin: '0px'}}>
+                <span>规格:{item.specification}{item.specification.indexOf("/") >= 0 ? "" : item.mainNumUnitName}</span>
+              </p>
+            </div>
+          </div>
+          <div className={styles.goodsColorBox}>
+            <span className={styles.color}>
+              {item.baseGoodsOptValue}
+            </span>
+              <em className={styles.cell}></em>
+          </div>
+          <div className={styles.goodsPriceBox}>
+            <span className={styles.price}>
+              {amountPrecision(item.salePrice)}{" "}
+            </span>
+            <em className={styles.cell}>/{item.mainNumUnitName}</em>
+          </div>
+          <div className={styles.goodsNumBox}>
+            <div className={styles.numIptBox}>
+              <GoodsBuyIpt
+                num={item.orderNum}
+                onChange={this.orderNumChange.bind(this, index, item)}
+              />
+            </div>
+            <span className={styles.cell}>{item.orderNumUnitName}</span>
+          </div>
+          <div className={styles.goodsCartBox}>
+            {amountPrecision(parseFloat(item.salePrice) * parseFloat(item.orderNum) * parseFloat(item.conversionRate), 'amount')}
+          </div>
+          <div className={styles.goodsFocusBox}>
+            <CustIcon
+              type="icon-icon-heart"
+              style={{ fontSize: 26 }}
+            />
+            <Popconfirm
+              title="确定要删除此行?"
+              onConfirm={this.deleteGoods.bind(this, item)}
+            >
+              <CustIcon
+                type="icon-icon-delete"
+                style={{ fontSize: 26, marginLeft: 15 }}
+              />
+            </Popconfirm>
+          </div>
+        </div>
+      ));
+      return html;
+    };
+    // 商品;列表
+    const goodsListHtml = () => {
+      const html = [];
+      if (!cartListData || cartListData.length == 0) {
+        return (
+          <div className={styles.noData}>暂无数据</div>
+        );
+      }
+      for (let i = 0; i < cartListData.length; i++) {
+        const item = cartListData[i];
+        html.push(
+          <div className={styles.goodsItem} key={item.saleOrgId}>
+            {/* 商品经销商 */}
+            <div className={styles.goodsHead}>
+              <Checkbox
+                checked={checkAllStatus(item)}
+                onChange={this.checkAllClick.bind(this, item)}
+              />
+              <span className={styles.name}>{item.saleOrgName || item.supplierName}</span>
+              <span className={styles.sortItem}>
+                编码排序<Icon type="arrow-up" className={styles.icon} />
+              </span>
+              <span className={styles.sortItem}>
+                时间排序<Icon type="arrow-up" className={styles.icon} />
+              </span>
+            </div>
+            {/* 商品经销商--end */}
+            {goodsListChildHtml(item, i)}
+          </div>
+        );
+      }
+      return html;
+    };
+    return (
+      <div className={styles.cartListBox}>
+        <div className={styles.goodsListHead}>
+          <div className={styles.goodsDisplayName}>商品</div>
+          <div className={styles.goodsColor}>颜色</div>
+          <div className={styles.goodsPrice}>单价</div>
+          <div className={styles.goodsNum}>订货量</div>
+          {/* <div className={styles.goodsMainNum}>主数量</div>
+          <div className={styles.goodsInventory}>库存</div> */}
+          <div className={styles.goodsCart}>金额小计</div>
+          <div className={styles.goodsFocus}>操作</div>
+        </div>
+        <div className={styles.goodsList}>{goodsListHtml()}</div>
+        <div className={styles.settleBox}>
+            <Popconfirm
+              title="确定要删除已选行?"
+              onConfirm={this.selectDelete.bind(this)}
+            >
+              <span className={styles.deleteBtn}>删除选中商品</span>
+            </Popconfirm>
+            <Popconfirm
+              title="确定要删除清除购物车?"
+              onConfirm={this.deleteAll.bind(this)}
+            >
+              <span className={styles.deleteBtn}>一键清除</span>
+            </Popconfirm>
+            {/* <span className={styles.focusBtn}>移入关注</span> */}
+            <Button
+              className={styles.goBuyBtn}
+              onClick={this.goBuyBtn.bind(this)}
+            >
+              去下单
+            </Button>
+            <span className={styles.allPrice} style={{ paddingRight: 20 }}>
+              <em className={styles.price}>{amountPrecision(allPrice, 'amount')}</em>
+            </span>
+            <span className={styles.allPrice} style={{ marginTop: 5 }}>总价:</span>
+            <span className={styles.selectNum}>
+              已选择<em className={styles.num}> {selectNum} </em>件商品
+            </span>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default connect(state => ({
+  cartListData: state.cartList.cartListData,
+  cartCheckStatus: state.cartList.cartCheckStatus
+}))(CartListComp);

+ 334 - 0
src/components/CartListComp/index.less

@@ -0,0 +1,334 @@
+@import "~antd/lib/style/themes/default.less";
+
+.goodsListHead {
+  width: 100%;
+  height: 40px;
+  background: #fff;
+  clear: both;
+  line-height: 40px;
+  color: #333;
+  font-size: 14px;
+  font-weight: bold;
+  & div {
+    float: left;
+  }
+  & .goodsDisplayName {
+    width: 340px;
+    text-align: center;
+  }
+  & .goodsColor {
+    width: 230px;
+    text-align: center;
+  }
+  & .goodsCode {
+    width: 150px;
+    text-align: center;
+  }
+  & .goodsPrice {
+    width: 160px;
+    text-align: center;
+  }
+  & .goodsInventory {
+    width: 120px;
+    text-align: left;
+    padding-left: 60px;
+  }
+  & .goodsNum {
+    width: 200px;
+    padding-left: 70px;
+    text-align: left;
+  }
+  & .goodsMainNum {
+    width: 110px;
+    text-align: left;
+    padding-left: 40px;
+  }
+  & .goodsCart {
+    width: 160px;
+    text-align: center;
+  }
+  & .goodsFocus {
+    width: 130px;
+    text-align: center;
+  }
+}
+// 列表
+.goodsItem {
+  width: 100%;
+  clear: both;
+  background: rgba(255, 255, 255, 0);
+  box-shadow: 0 0 0 0 #e6e6e6;
+
+  & .goodsChildList {
+    border-bottom: 1px solid #e6e6e6;
+    height: 82px;
+    width: 100%;
+  }
+  & .goodsHead {
+    width: 1200px;
+    height: 40px;
+    background: rgba(245, 245, 245, 0.5);
+    padding: 10px 20px 10px 10px;
+    & .name {
+      vertical-align: middle;
+      font-size: 12px;
+      color: #333;
+      font-weight: bold;
+    }
+    & .sortItem {
+      float: right;
+      color: #333;
+      font-size: 12px;
+      cursor: pointer;
+      &:last-child {
+        margin-right: 20px;
+      }
+      & .icon {
+        color: #e14c46;
+      }
+    }
+  }
+  & .goodsNameBox {
+    width: 340px;
+    float: left;
+    height: 80px;
+    & .goodsCheck {
+      float: left;
+      width: 50px;
+      line-height: 80px;
+      padding-left: 20px;
+    }
+  }
+  & .goodsColorBox {
+    width: 230px;
+    text-align: right;
+    line-height: 80px;
+    float: left;
+    height: 80px;
+    & .color {
+      font-size: 14px;
+    }
+  }
+  
+  & .goodsCodeBox {
+    width: 150px;
+    text-align: center;
+    float: left;
+    line-height: 80px;
+    color: #999;
+  }
+  & .goodsPriceBox {
+    width: 160px;
+    text-align: right;
+    line-height: 80px;
+    float: left;
+    height: 80px;
+    & .price {
+      font-size: 16px;
+      color: #e14c46;
+    }
+    & .cell {
+      font-size: 12px;
+      min-width: 40px;
+      text-align: left;
+      display: inline-block;
+      color: #999;
+    }
+  }
+  & .goodsInventoryBox {
+    width: 120px;
+    text-align: right;
+    line-height: 80px;
+    float: left;
+    height: 80px;
+    & .InventoryNum {
+      font-size: 14px;
+      color: #333;
+    }
+    & .cell {
+      font-size: 12px;
+      min-width: 40px;
+      text-align: left;
+      display: inline-block;
+      color: #999;
+    }
+  }
+  & .goodsNumBox {
+    width: 200px;
+    padding-left: 30px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    float: left;
+    height: 80px;
+    & .numIptBox {
+      // flex: 0 0 100px;
+      height: 30px;
+    }
+    & .cell {
+      font-size: 12px;
+      color: #999;
+      min-width: 40px;
+      text-align: left;
+      display: inline-block;
+      margin-left: 10px;
+    }
+  }
+  & .goodsMainNumBox {
+    width: 110px;
+    float: left;
+    height: 80px;
+    line-height: 80px;
+    text-align: right;
+    & .MainNum {
+      font-size: 14px;
+      color: #333;
+    }
+    & .cell {
+      font-size: 12px;
+      min-width: 40px;
+      text-align: left;
+      display: inline-block;
+      color: #999;
+    }
+  }
+  & .goodsCartBox {
+    width: 160px;
+    float: left;
+    height: 80px;
+    line-height: 80px;
+    text-align: center;
+    color: #e14c46;
+    font-size: 16px;
+  }
+  & .goodsFocusBox {
+    width: 130px;
+    float: left;
+    height: 80px;
+    line-height: 80px;
+    text-align: center;
+    & .heartIcon {
+      color: #333;
+      font-size: 20px;
+      cursor: pointer;
+      padding-right: 10px;
+    }
+    & .delete {
+      color: #333;
+      font-size: 20px;
+      cursor: pointer;
+    }
+  }
+}
+.imgBox {
+  float: left;
+  width: 60px;
+  height: 60px;
+  margin: 10px 10px 10px 0;
+  & img {
+    height: 60px;
+    width: 60px;
+    border: 1px solid #eee;
+  }
+}
+.goodsInfo {
+  float: left;
+  height: 80px;
+  width: 220px;
+  padding-top: 10px;
+}
+.goodsTitleName {
+  color: #333;
+  font-size: 12px;
+  height: 20px;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+  margin: 0;
+}
+.goodsStatus {
+  display: block;
+  width: 16px;
+  height: 16px;
+  background: #59c152;
+  border-radius: 2px;
+  color: #fff;
+  text-align: center;
+  line-height: 16px;
+}
+.goodsStatusTxt {
+  font-size: 10px;
+  transform: scale(0.8);
+  margin: 0;
+}
+.settleBox {
+  height: 50px;
+  width: 100%;
+  margin-top: 80px;
+  background: #fafafa;
+  margin-bottom: 300px;
+  & .deleteBtn {
+    display: inline-block;
+    line-height: 50px;
+    padding-left: 18px;
+    font-size: 12px;
+    color: #333;
+    cursor: pointer;
+  }
+  & .focusBtn {
+    display: inline-block;
+    line-height: 50px;
+    padding-left: 20px;
+    font-size: 12px;
+    color: #333;
+    cursor: pointer;
+  }
+  & .goBuyBtn {
+    float: right;
+    width: 120px;
+    height: 50px;
+    line-height: 50px;
+    text-align: center;
+    font-size: 16px;
+    color: #fff;
+    background: #e14c46;
+    border: none;
+    border-radius: 0;
+  }
+  & .allPrice {
+    float: right;
+    vertical-align: middle;
+    line-height: 45px;
+    padding-right: 10px;
+    color: #333;
+    font-size: 16px;
+    vertical-align: middle;
+    & .price {
+      font-size: 24px;
+      color: #e14c46;
+      vertical-align: middle;
+    }
+  }
+  & .selectNum {
+    float: right;
+    line-height: 50px;
+    font-size: 14px;
+    margin-top: 3px;
+    margin-right: 40px;
+    color: #333;
+    vertical-align: middle;
+    & .num {
+      font-size: 14px;
+      color: #000b0d;
+    }
+  }
+}
+// 无数据列表
+.noData{
+  height: 50px;
+  line-height: 50px;
+  background: #fafafa;
+  color: #666;
+  text-align: center;
+}

+ 24 - 0
src/components/CountDown/demo/simple.md

@@ -0,0 +1,24 @@
+---
+order: 0
+title:
+  zh-CN: 基本
+  en-US: Basic
+---
+
+## zh-CN
+
+简单的倒计时组件使用。
+
+## en-US
+
+The simplest usage.
+
+````jsx
+import CountDown from 'ant-design-pro/lib/CountDown';
+
+const targetTime = new Date().getTime() + 3900000;
+
+ReactDOM.render(
+  <CountDown style={{ fontSize: 20 }} target={targetTime} />
+, mountNode);
+````

+ 9 - 0
src/components/CountDown/index.d.ts

@@ -0,0 +1,9 @@
+import * as React from "react";
+export interface CountDownProps {
+  format?: (time: number) => void;
+  target: Date | number;
+  onEnd?: () => void;
+  style?: React.CSSProperties;
+}
+
+export default class CountDown extends React.Component<CountDownProps, any> {}

+ 15 - 0
src/components/CountDown/index.en-US.md

@@ -0,0 +1,15 @@
+---
+title: CountDown
+cols: 1
+order: 3
+---
+
+Simple CountDown Component.
+
+## API
+
+| Property | Description | Type | Default |
+|----------|------------------------------------------|-------------|-------|
+| format | Formatter of time | Function(time) |  |
+| target | Target time | Date | - |
+| onEnd |  Countdown to the end callback | funtion | -|

+ 105 - 0
src/components/CountDown/index.js

@@ -0,0 +1,105 @@
+import React, { Component } from 'react';
+
+function fixedZero(val) {
+  return val * 1 < 10 ? `0${val}` : val;
+}
+
+class CountDown extends Component {
+  constructor(props) {
+    super(props);
+
+    const { lastTime } = this.initTime(props);
+
+    this.state = {
+      lastTime,
+    };
+  }
+
+  componentDidMount() {
+    this.tick();
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.target !== nextProps.target) {
+      const { lastTime } = this.initTime(nextProps);
+      this.setState({
+        lastTime,
+      });
+    }
+  }
+
+  componentWillUnmount() {
+    clearTimeout(this.timer);
+  }
+
+  timer = 0;
+  interval = 1000;
+  initTime = (props) => {
+    let lastTime = 0;
+    let targetTime = 0;
+    try {
+      if (Object.prototype.toString.call(props.target) === '[object Date]') {
+        targetTime = props.target.getTime();
+      } else {
+        targetTime = new Date(props.target).getTime();
+      }
+    } catch (e) {
+      throw new Error('invalid target prop', e);
+    }
+
+    lastTime = targetTime - new Date().getTime();
+
+    return {
+      lastTime,
+    };
+  }
+  // defaultFormat = time => (
+  //  <span>{moment(time).format('hh:mm:ss')}</span>
+  // );
+  defaultFormat = (time) => {
+    const hours = 60 * 60 * 1000;
+    const minutes = 60 * 1000;
+
+    const h = fixedZero(Math.floor(time / hours));
+    const m = fixedZero(Math.floor((time - (h * hours)) / minutes));
+    const s = fixedZero(Math.floor((time - (h * hours) - (m * minutes)) / 1000));
+    return (
+      <span>{h}:{m}:{s}</span>
+    );
+  }
+  tick = () => {
+    const { onEnd } = this.props;
+    let { lastTime } = this.state;
+
+    this.timer = setTimeout(() => {
+      if (lastTime < this.interval) {
+        clearTimeout(this.timer);
+        this.setState({
+          lastTime: 0,
+        });
+
+        if (onEnd) {
+          onEnd();
+        }
+      } else {
+        lastTime -= this.interval;
+        this.setState({
+          lastTime,
+        });
+
+        this.tick();
+      }
+    }, this.interval);
+  }
+
+  render() {
+    const { format = this.defaultFormat, ...rest } = this.props;
+    const { lastTime } = this.state;
+
+    const result = format(lastTime);
+
+    return (<span {...rest}>{result}</span>);
+  }
+}
+
+export default CountDown;

+ 16 - 0
src/components/CountDown/index.zh-CN.md

@@ -0,0 +1,16 @@
+---
+title: CountDown 
+subtitle: 倒计时
+cols: 1
+order: 3
+---
+
+倒计时组件。
+
+## API
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| format | 时间格式化显示 | Function(time) |  |
+| target | 目标时间 | Date | - |
+| onEnd |  倒计时结束回调 | funtion | -|

+ 13 - 0
src/components/CustIcon/README.md

@@ -0,0 +1,13 @@
+# 自定义图标
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | ---- | --- | --- |
+| type | icon 样式 | string | |
+| status | icon 状态【-1:禁用;0:默认;1: 激活】 | integer |  0 |
+| onClick | click 事件 | function | |
+
+```
+  <CustIcon type="icon-gouwuche" status={0} onClick={() => message.info("添加购物车")} />
+  <CustIcon type="icon-gouwuche" status={1} onClick={() => message.info("取消添加购物车")} />
+  <CustIcon type="icon-gouwuche" status={-1} onClick={() => message.warning("已禁用")}  />
+```

+ 22 - 0
src/components/CustIcon/demo.js

@@ -0,0 +1,22 @@
+import React, { PureComponent } from 'react';
+import ReactDOM from 'react-dom';
+import { message } from 'antd';
+
+import CustIcon from './index.js';
+
+export default class DemoCustIcon extends PureComponent {
+  render() {
+    function Icons() {
+      return (
+        <div>
+          默认:<CustIcon type="icon-gouwuche" status={0} onClick={() => message.info("添加购物车")} /><br />
+          激活:<CustIcon type="icon-gouwuche" status={1} onClick={() => message.info("取消添加购物车")} /><br />
+          禁用:<CustIcon type="icon-gouwuche" status={-1} onClick={() => message.warning("已禁用")}  /><br />
+        </div>
+      );
+    }
+    return (
+      <Icons />
+    );
+  }
+}

+ 43 - 0
src/components/CustIcon/index.js

@@ -0,0 +1,43 @@
+import React, { PureComponent } from "react";
+import classnames from "classnames";
+import iconfont from "../../iconfont/iconfont.css";
+import styles from "./index.less";
+
+/**
+ * 自定义 icon 展示
+ * @description 用法:<CustIcon type="icon-qiuguanzhu" status={item.inFav} onClick={this.ifClickAlert.bind(this)} />
+ */
+export default class CustIcon extends PureComponent {
+  render() {
+    const { type, status, onClick, ...props } = this.props;
+    const _status = status ? status : STATUS.NORMAL;
+    return (
+      <i
+        className={classnames(
+          iconfont[NAMESPACE],
+          styles.iconStyle,
+          _status == STATUS.ACTIVITY ? iconfont[`${type}-i`] : iconfont[type],
+          {
+            "icon-disable": _status == STATUS.DISABLE,
+            "icon-activity": _status == STATUS.ACTIVITY,
+            "icon-normal": _status == STATUS.NORMAL
+          }
+        )}
+        {...props}
+        onClick={
+          onClick ||
+          function() {
+            return true;
+          }
+        }
+      />
+    );
+  }
+}
+
+export const STATUS = Object.freeze({
+  DISABLE: -1, // 禁用
+  NORMAL: 0, // 正常
+  ACTIVITY: 1 // 激活
+});
+export const NAMESPACE = "buyer-iconfont";

+ 17 - 0
src/components/CustIcon/index.less

@@ -0,0 +1,17 @@
+.iconStyle {
+    &:global(.icon-disable) {
+      color: @icon-disable-color;
+      font-size: 24px;
+      cursor: not-allowed;
+    }
+    &:global(.icon-activity) {
+      color: @primary-color !important;
+      font-size: 24px;
+      cursor: pointer;
+    }
+    &:global(.icon-normal) {
+      color: @icon-normal-color;
+      font-size: 24px;
+      cursor: pointer;
+    }
+}

+ 17 - 0
src/components/DescriptionList/Description.js

@@ -0,0 +1,17 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Col } from 'antd';
+import styles from './index.less';
+import responsive from './responsive';
+
+const Description = ({ term, column, className, children, ...restProps }) => {
+  const clsString = classNames(styles.description, className);
+  return (
+    <Col className={clsString} {...responsive[column]} {...restProps}>
+      {term && <div className={styles.term}>{term}</div>}
+      {children && <div className={styles.detail}>{children}</div>}
+    </Col>
+  );
+};
+
+export default Description;

+ 21 - 0
src/components/DescriptionList/DescriptionList.js

@@ -0,0 +1,21 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Row } from 'antd';
+import styles from './index.less';
+
+export default ({ className, title, col = 3, layout = 'horizontal', gutter = 32,
+  children, size, ...restProps }) => {
+  const clsString = classNames(styles.descriptionList, styles[layout], className, {
+    [styles.descriptionListSmall]: size === 'small',
+    [styles.descriptionListLarge]: size === 'large',
+  });
+  const column = col > 4 ? 4 : col;
+  return (
+    <div className={clsString} {...restProps}>
+      {title ? <div className={styles.title}>{title}</div> : null}
+      <Row gutter={gutter}>
+        {React.Children.map(children, child => React.cloneElement(child, { column }))}
+      </Row>
+    </div>
+  );
+};

+ 35 - 0
src/components/DescriptionList/demo/basic.md

@@ -0,0 +1,35 @@
+---
+order: 0
+title: Basic
+---
+
+基本描述列表。
+
+````jsx
+import DescriptionList from 'ant-design-pro/lib/DescriptionList';
+
+const { Description } = DescriptionList;
+
+ReactDOM.render(
+  <DescriptionList size="large" title="title">
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+  </DescriptionList>
+, mountNode);
+````

+ 35 - 0
src/components/DescriptionList/demo/vertical.md

@@ -0,0 +1,35 @@
+---
+order: 1
+title: Vertical
+---
+
+垂直布局。
+
+````jsx
+import DescriptionList from 'ant-design-pro/lib/DescriptionList';
+
+const { Description } = DescriptionList;
+
+ReactDOM.render(
+  <DescriptionList size="large" title="title" layout="vertical">
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+    <Description term="Firefox">
+      A free, open source, cross-platform,
+      graphical web browser developed by the
+      Mozilla Corporation and hundreds of
+      volunteers.
+    </Description>
+  </DescriptionList>
+, mountNode);
+````

+ 22 - 0
src/components/DescriptionList/index.d.ts

@@ -0,0 +1,22 @@
+import * as React from "react";
+export interface DescriptionListProps {
+  layout?: "horizontal" | "vertical";
+  col?: number;
+  title: React.ReactNode;
+  gutter?: number;
+  size?: "large" | "small";
+}
+
+declare class Description extends React.Component<
+  {
+    term: React.ReactNode;
+  },
+  any
+> {}
+
+export default class DescriptionList extends React.Component<
+  DescriptionListProps,
+  any
+> {
+  static Description: typeof Description;
+}

+ 5 - 0
src/components/DescriptionList/index.js

@@ -0,0 +1,5 @@
+import DescriptionList from './DescriptionList';
+import Description from './Description';
+
+DescriptionList.Description = Description;
+export default DescriptionList;

+ 75 - 0
src/components/DescriptionList/index.less

@@ -0,0 +1,75 @@
+@import "~antd/lib/style/themes/default.less";
+
+.descriptionList {
+  // offset the padding-bottom of last row
+  :global {
+    .ant-row {
+      margin-bottom: -16px;
+      overflow: hidden;
+    }
+  }
+
+  .title {
+    font-size: 14px;
+    color: @heading-color;
+    font-weight: 500;
+    margin-bottom: 16px;
+  }
+
+  .term {
+    line-height: 22px;
+    padding-bottom: 16px;
+    margin-right: 8px;
+    color: @heading-color;
+    white-space: nowrap;
+    display: table-cell;
+
+    &:after {
+      content: ":";
+      margin: 0 8px 0 2px;
+      position: relative;
+      top: -.5px;
+    }
+  }
+
+  .detail {
+    line-height: 22px;
+    width: 100%;
+    padding-bottom: 16px;
+    color: @text-color;
+    display: table-cell;
+  }
+
+  &.vertical {
+
+    .term {
+      padding-bottom: 8px;
+      display: block;
+    }
+
+    .detail {
+      display: block;
+    }
+  }
+}
+
+.descriptionListSmall {
+  // offset the padding-bottom of last row
+  :global {
+    .ant-row {
+      margin-bottom: -8px;
+    }
+  }
+  .title {
+    margin-bottom: 12px;
+    color: @text-color;
+  }
+  .term, .detail {
+    padding-bottom: 8px;
+  }
+}
+.descriptionListLarge {
+  .title {
+    font-size: 16px;
+  }
+}

+ 39 - 0
src/components/DescriptionList/index.md

@@ -0,0 +1,39 @@
+---
+title:
+  en-US: DescriptionList
+  zh-CN: DescriptionList
+subtitle: 描述列表
+cols: 1
+order: 4
+---
+
+成组展示多个只读字段,常见于详情页的信息展示。
+
+## API
+
+### DescriptionList
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| layout    | 布局方式                                 | Enum{'horizontal', 'vertical'}  | 'horizontal' |
+| col       | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定          | number(0 < col <= 4)  | 3 |
+| title     | 列表标题                                 | ReactNode  | - |
+| gutter    | 列表项间距,单位为 `px`                    | number  | 32 |
+| size     | 列表型号,可以设置为 `large` `small`        | Enum{'large', 'small'}  | - |
+
+#### 响应式规则
+
+| 窗口宽度             | 展示列数                                      | 
+|---------------------|---------------------------------------------|
+| `≥768px`           |  `col`                                       |
+| `≥576px`           |  `col < 2 ? col : 2`                         |
+| `<576px`           |  `1`                                         |
+
+### DescriptionList.Description
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| term     | 列表项标题                                 | ReactNode  | - |
+
+
+

+ 6 - 0
src/components/DescriptionList/responsive.js

@@ -0,0 +1,6 @@
+export default {
+  1: { xs: 24 },
+  2: { xs: 24, sm: 12 },
+  3: { xs: 24, sm: 12, md: 8 },
+  4: { xs: 24, sm: 12, md: 6 },
+};

+ 53 - 0
src/components/DetailList/index.js

@@ -0,0 +1,53 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { Button, Form, Col, Row, Input } from "antd";
+import styles from "./index.less";
+
+const FormItem = Form.Item;
+
+@Form.create()
+class DetailList extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+  // 生成组件
+  getFields() {
+    const { getFieldDecorator } = this.props.form;
+    const { fieldList, detailList } = this.props;
+    const children = [];
+    for (let i = 0; i < fieldList.length; i++) {
+      children.push(
+        <Col key={i} className={styles.searchFormCol}>
+          <FormItem label={fieldList[i].label}>
+            {getFieldDecorator(fieldList[i].field, {
+              initialValue: detailList[fieldList[i].field]
+            })(this.parseEdit(fieldList[i].isEdit))}
+          </FormItem>
+        </Col>
+      );
+    }
+    return children;
+  }
+  // 解析详情项是否可编辑
+  parseEdit(isEdit) {
+    if (isEdit) {
+      return <Input />;
+    } else {
+      return <Input className={styles.detailIpt} disabled />;
+    }
+  }
+  render() {
+    const detailHtml = this.getFields();
+    return (
+      <div>
+        <div>
+          <Form layout="inline">
+            <Row>{detailHtml}</Row>
+          </Form>
+        </div>
+      </div>
+    );
+  }
+}
+// export default connect(() => ({}))(DetailList);
+export default DetailList;

+ 21 - 0
src/components/DetailList/index.less

@@ -0,0 +1,21 @@
+@import "~antd/lib/style/themes/default.less";
+
+.searchFormCol {
+  float: left;
+  width: 400px;
+
+  :global(.ant-form-item-label) {
+    width: 120px;
+  }
+}
+.detailIpt {
+  border: none;
+  outline: none;
+  cursor: auto;
+  background: #fff;
+  color: #333;
+  padding-left: 0;
+}
+:global(.ant-input) {
+  height: 28px;
+}

+ 50 - 0
src/components/DetailTable/index.js

@@ -0,0 +1,50 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { Table } from "antd";
+import styles from "./index.less";
+
+class DetailTable extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+  render() {
+    const columns = [
+      {
+        title: "序号",
+        dataIndex: "key",
+        key: "key",
+        render: (text, record, index) => {
+          return index + 1;
+        }
+      },
+      {
+        title: "商品名称",
+        dataIndex: "commidityId",
+        key: "commidityId"
+      },
+      // {
+      //   title: "账面调拨数量",
+      //   dataIndex: "quantity",
+      //   key: "quantity"
+      // },
+      {
+        title: "调拨数量",
+        dataIndex: "actualQuantity",
+        key: "actualQuantity"
+      }
+    ];
+    const data = [];
+    return (
+      <div>
+        <Table
+          columns={columns}
+          dataSource={this.props.data}
+          rowKey={record => record.id}
+          pagination={false}
+        />
+      </div>
+    );
+  }
+}
+// export default connect(() => ({}))(DetailTable);
+export default DetailTable;

+ 1 - 0
src/components/DetailTable/index.less

@@ -0,0 +1 @@
+@import "~antd/lib/style/themes/default.less";

+ 0 - 0
src/components/Drawers/index.d.ts


+ 54 - 0
src/components/EditableItem/index.js

@@ -0,0 +1,54 @@
+import React, { PureComponent } from 'react';
+import { Input, Icon } from 'antd';
+import styles from './index.less';
+
+export default class EditableItem extends PureComponent {
+  state = {
+    value: this.props.value,
+    editable: false,
+  };
+  handleChange = (e) => {
+    const { value } = e.target;
+    this.setState({ value });
+  }
+  check = () => {
+    this.setState({ editable: false });
+    if (this.props.onChange) {
+      this.props.onChange(this.state.value);
+    }
+  }
+  edit = () => {
+    this.setState({ editable: true });
+  }
+  render() {
+    const { value, editable } = this.state;
+    return (
+      <div className={styles.editableItem}>
+        {
+          editable ?
+            <div className={styles.wrapper}>
+              <Input
+                value={value}
+                onChange={this.handleChange}
+                onPressEnter={this.check}
+              />
+              <Icon
+                type="check"
+                className={styles.icon}
+                onClick={this.check}
+              />
+            </div>
+            :
+            <div className={styles.wrapper}>
+              <span>{value || ' '}</span>
+              <Icon
+                type="edit"
+                className={styles.icon}
+                onClick={this.edit}
+              />
+            </div>
+        }
+      </div>
+    );
+  }
+}

+ 25 - 0
src/components/EditableItem/index.less

@@ -0,0 +1,25 @@
+@import "~antd/lib/style/themes/default.less";
+
+.editableItem {
+  line-height: @input-height-base;
+  display: table;
+  width: 100%;
+  margin-top: (@font-size-base * @line-height-base - @input-height-base) / 2;
+
+  .wrapper {
+    display: table-row;
+
+    & > * {
+      display: table-cell;
+    }
+
+    & > *:first-child {
+      width: 85%;
+    }
+
+    .icon {
+      cursor: pointer;
+      text-align: right;
+    }
+  }
+}

+ 44 - 0
src/components/EditableLinkGroup/index.js

@@ -0,0 +1,44 @@
+import React, { PureComponent, createElement } from 'react';
+import PropTypes from 'prop-types';
+import { Button } from 'antd';
+import styles from './index.less';
+
+// TODO: 添加逻辑
+
+class EditableLinkGroup extends PureComponent {
+  static defaultProps = {
+    links: [],
+    onAdd: () => {},
+    linkElement: 'a',
+  };
+
+  static propTypes = {
+    links: PropTypes.array,
+    onAdd: PropTypes.func,
+    linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
+  };
+
+  render() {
+    const { links, linkElement, onAdd } = this.props;
+    return (
+      <div className={styles.linkGroup}>
+        {
+          links.map(link => (
+            createElement(linkElement, {
+              key: `linkGroup-item-${link.id || link.title}`,
+              to: link.href,
+              href: link.href,
+            }, link.title)
+          ))
+        }
+        {
+          <Button size="small" type="primary" ghost onClick={onAdd} icon="plus">
+            添加
+          </Button>
+        }
+      </div>
+    );
+  }
+}
+
+export default EditableLinkGroup;

+ 16 - 0
src/components/EditableLinkGroup/index.less

@@ -0,0 +1,16 @@
+@import "~antd/lib/style/themes/default.less";
+
+.linkGroup {
+  padding: 20px 0 8px 24px;
+  font-size: 0;
+  & > a {
+    color: @text-color;
+    display: inline-block;
+    font-size: @font-size-base;
+    margin-bottom: 13px;
+    width: 25%;
+    &:hover {
+      color: @primary-color;
+    }
+  }
+}

+ 20 - 0
src/components/Ellipsis/demo/line.md

@@ -0,0 +1,20 @@
+---
+order: 1
+title: 按照行数省略
+---
+
+通过设置 `lines` 属性指定最大行数,如果超过这个行数的文本会自动截取。但是在这种模式下所有 `children` 将会被转换成纯文本。
+
+并且注意在这种模式下,外容器需要有指定的宽度(或设置自身宽度)。
+
+````jsx
+import Ellipsis from 'ant-design-pro/lib/Ellipsis';
+
+const article = <p>There were injuries alleged in three <a href="#cover">cases in 2015</a>, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.</p>;
+
+ReactDOM.render(
+  <div style={{ width: 200 }}>
+    <Ellipsis tooltip lines={3}>{article}</Ellipsis>
+  </div>
+, mountNode);
+````

+ 20 - 0
src/components/Ellipsis/demo/number.md

@@ -0,0 +1,20 @@
+---
+order: 0
+title: 按照字符数省略 
+---
+
+通过设置 `length` 属性指定文本最长长度,如果超过这个长度会自动截取。
+
+````jsx
+import Ellipsis from 'ant-design-pro/lib/Ellipsis';
+
+const article = 'There were injuries alleged in three cases in 2015, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.';
+
+ReactDOM.render(
+  <div>
+    <Ellipsis length={100}>{article}</Ellipsis>
+    <h4 style={{ marginTop: 24 }}>Show Tooltip</h4>
+    <Ellipsis length={100} tooltip>{article}</Ellipsis>
+  </div>
+, mountNode);
+````

+ 11 - 0
src/components/Ellipsis/index.d.ts

@@ -0,0 +1,11 @@
+import * as React from "react";
+export interface EllipsisProps {
+  tooltip?: boolean;
+  length?: number;
+  lines?: number;
+}
+
+export default class Ellipsis extends React.Component<
+  EllipsisProps,
+  any
+> {}

+ 210 - 0
src/components/Ellipsis/index.js

@@ -0,0 +1,210 @@
+import React, { Component } from 'react';
+import { Tooltip } from 'antd';
+import classNames from 'classnames';
+import styles from './index.less';
+
+/* eslint react/no-did-mount-set-state: 0 */
+/* eslint no-param-reassign: 0 */
+
+const isSupportLineClamp = (document.body.style.webkitLineClamp !== undefined);
+
+const EllipsisText = ({ text, length, tooltip, ...other }) => {
+  if (typeof text !== 'string') {
+    throw new Error('Ellipsis children must be string.');
+  }
+  if (text.length <= length || length < 0) {
+    return <span {...other}>{text}</span>;
+  }
+  const tail = '...';
+  let displayText;
+  if (length - tail.length <= 0) {
+    displayText = '';
+  } else {
+    displayText = text.slice(0, (length - tail.length));
+  }
+
+  if (tooltip) {
+    return <Tooltip title={text}><span>{displayText}{tail}</span></Tooltip>;
+  }
+
+  return (
+    <span {...other}>
+      {displayText}{tail}
+    </span>
+  );
+};
+
+export default class Ellipsis extends Component {
+  state = {
+    text: '',
+    targetCount: 0,
+  }
+
+  componentDidMount() {
+    if (this.node) {
+      this.computeLine();
+    }
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.lines !== nextProps.lines) {
+      this.computeLine();
+    }
+  }
+
+  computeLine = () => {
+    const { lines } = this.props;
+    if (lines && !isSupportLineClamp) {
+      const text = this.shadowChildren.innerText;
+      const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10);
+      const targetHeight = lines * lineHeight;
+      this.content.style.height = `${targetHeight}px`;
+      const totalHeight = this.shadowChildren.offsetHeight;
+      const shadowNode = this.shadow.firstChild;
+
+      if (totalHeight <= targetHeight) {
+        this.setState({
+          text,
+          targetCount: text.length,
+        });
+        return;
+      }
+
+      // bisection
+      const len = text.length;
+      const mid = Math.floor(len / 2);
+
+      const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode);
+
+      this.setState({
+        text,
+        targetCount: count,
+      });
+    }
+  }
+
+  bisection = (th, m, b, e, text, shadowNode) => {
+    const suffix = '...';
+    let mid = m;
+    let end = e;
+    let begin = b;
+    shadowNode.innerHTML = text.substring(0, mid) + suffix;
+    let sh = shadowNode.offsetHeight;
+
+    if (sh <= th) {
+      shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
+      sh = shadowNode.offsetHeight;
+      if (sh > th) {
+        return mid;
+      } else {
+        begin = mid;
+        mid = Math.floor((end - begin) / 2) + begin;
+        return this.bisection(th, mid, begin, end, text, shadowNode);
+      }
+    } else {
+      if (mid - 1 < 0) {
+        return mid;
+      }
+      shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
+      sh = shadowNode.offsetHeight;
+      if (sh <= th) {
+        return mid - 1;
+      } else {
+        end = mid;
+        mid = Math.floor((end - begin) / 2) + begin;
+        return this.bisection(th, mid, begin, end, text, shadowNode);
+      }
+    }
+  }
+
+  handleRoot = (n) => {
+    this.root = n;
+  }
+
+  handleContent = (n) => {
+    this.content = n;
+  }
+
+  handleNode = (n) => {
+    this.node = n;
+  }
+
+  handleShadow = (n) => {
+    this.shadow = n;
+  }
+
+  handleShadowChildren = (n) => {
+    this.shadowChildren = n;
+  }
+
+  render() {
+    const { text, targetCount } = this.state;
+    const {
+      children,
+      lines,
+      length,
+      className,
+      tooltip,
+      ...restProps
+    } = this.props;
+
+    const cls = classNames(styles.ellipsis, className, {
+      [styles.lines]: (lines && !isSupportLineClamp),
+      [styles.lineClamp]: (lines && isSupportLineClamp),
+    });
+
+    if (!lines && !length) {
+      return (<span className={cls} {...restProps}>{children}</span>);
+    }
+
+    // length
+    if (!lines) {
+      return (<EllipsisText className={cls} length={length} text={children || ''} tooltip={tooltip} {...restProps} />);
+    }
+
+    const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
+
+    // support document.body.style.webkitLineClamp
+    if (isSupportLineClamp) {
+      const style = `#${id}{-webkit-line-clamp:${lines};}`;
+      return (
+        <div id={id} className={cls} {...restProps}>
+          <style>{style}</style>
+          {
+            tooltip ? (<Tooltip title={text}>{children}</Tooltip>) : children
+          }
+        </div>);
+    }
+
+    const childNode = (
+      <span ref={this.handleNode}>
+        {
+          (targetCount > 0) && text.substring(0, targetCount)
+        }
+        {
+          (targetCount > 0) && (targetCount < text.length) && '...'
+        }
+      </span>
+    );
+
+    return (
+      <div
+        {...restProps}
+        ref={this.handleRoot}
+        className={cls}
+      >
+        <div
+          ref={this.handleContent}
+        >
+          {
+            tooltip ? (
+              <Tooltip title={text}>{childNode}</Tooltip>
+            ) : childNode
+          }
+          <div className={styles.shadow} ref={this.handleShadowChildren}>{children}</div>
+          <div className={styles.shadow} ref={this.handleShadow}><span>{text}</span></div>
+        </div>
+      </div>
+    );
+  }
+}

+ 25 - 0
src/components/Ellipsis/index.less

@@ -0,0 +1,25 @@
+.ellipsis {
+  overflow: hidden;
+  display: inline-block;
+  word-break: break-all;
+  width: 100%;
+}
+
+.lines {
+  position: relative;
+  .shadow {
+    display: block;
+    position: relative;
+    color: transparent;
+    opacity: 0;
+    z-index: -999;
+  }
+}
+
+.lineClamp {
+  position: relative;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+}

+ 18 - 0
src/components/Ellipsis/index.md

@@ -0,0 +1,18 @@
+---
+title:
+  en-US: Ellipsis 
+  zh-CN: Ellipsis
+subtitle: 文本自动省略号
+cols: 1
+order: 10
+---
+
+文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+tooltip | 移动到文本展示完整内容的提示 | boolean | -
+length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -
+lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1`

+ 12 - 0
src/components/EmptyData/index.js

@@ -0,0 +1,12 @@
+import React, { Component } from "react";
+
+class EmptyData extends Component {
+  constructor(props) {
+    super(props);
+  }
+  render() {
+    return <div>没有符合您条件的数据</div>;
+  }
+}
+
+export default EmptyData;

+ 21 - 0
src/components/Exception/demo/403.md

@@ -0,0 +1,21 @@
+---
+order: 2
+title: 403
+---
+
+403 页面,配合自定义操作。
+
+````jsx
+import Exception from 'ant-design-pro/lib/Exception';
+import { Button } from 'antd';
+
+const actions = (
+  <div>
+    <Button type="primary">回到首页</Button>
+    <Button>查看详情</Button>
+  </div>
+);
+ReactDOM.render(
+  <Exception type="403" actions={actions} />
+, mountNode);
+````

+ 14 - 0
src/components/Exception/demo/404.md

@@ -0,0 +1,14 @@
+---
+order: 0
+title: 404
+---
+
+404 页面。
+
+````jsx
+import Exception from 'ant-design-pro/lib/Exception';
+
+ReactDOM.render(
+  <Exception type="404" />
+, mountNode);
+````

+ 14 - 0
src/components/Exception/demo/500.md

@@ -0,0 +1,14 @@
+---
+order: 1
+title: 500
+---
+
+500 页面。
+
+````jsx
+import Exception from 'ant-design-pro/lib/Exception';
+
+ReactDOM.render(
+  <Exception type="500" />
+, mountNode);
+````

+ 11 - 0
src/components/Exception/index.d.ts

@@ -0,0 +1,11 @@
+import * as React from "react";
+export interface ExceptionProps {
+  type?: "403" | "404" | "500";
+  title?: React.ReactNode;
+  desc?: React.ReactNode;
+  img?: string;
+  actions?: React.ReactNode;
+  linkElement?: React.ReactNode;
+}
+
+export default class Exception extends React.Component<ExceptionProps, any> {}

+ 33 - 0
src/components/Exception/index.js

@@ -0,0 +1,33 @@
+import React, { createElement } from 'react';
+import classNames from 'classnames';
+import { Button } from 'antd';
+import config from './typeConfig';
+import styles from './index.less';
+
+export default ({ className, linkElement = 'a', type, title, desc, img, actions, ...rest }) => {
+  const pageType = type in config ? type : '404';
+  const clsString = classNames(styles.exception, className);
+  return (
+    <div className={clsString} {...rest}>
+      <div className={styles.imgBlock}>
+        <div
+          className={styles.imgEle}
+          style={{ backgroundImage: `url(${img || config[pageType].img})` }}
+        />
+      </div>
+      <div className={styles.content}>
+        <h1>{title || config[pageType].title}</h1>
+        <div className={styles.desc}>{desc || config[pageType].desc}</div>
+        <div className={styles.actions}>
+          {
+            actions ||
+              createElement(linkElement, {
+                to: '/',
+                href: '/',
+              }, <Button type="primary">返回首页</Button>)
+          }
+        </div>
+      </div>
+    </div>
+  );
+};

+ 88 - 0
src/components/Exception/index.less

@@ -0,0 +1,88 @@
+@import "~antd/lib/style/themes/default.less";
+
+.exception {
+  display: flex;
+  align-items: center;
+  height: 100%;
+
+  .imgBlock {
+    flex: 0 0 62.5%;
+    width: 62.5%;
+    padding-right: 152px;
+    zoom: 1;
+    &:before,
+    &:after {
+      content: " ";
+      display: table;
+    }
+    &:after {
+      clear: both;
+      visibility: hidden;
+      font-size: 0;
+      height: 0;
+    }
+  }
+
+  .imgEle {
+    height: 360px;
+    width: 100%;
+    max-width: 430px;
+    float: right;
+    background-repeat: no-repeat;
+    background-position: 50% 50%;
+    background-size: contain;
+  }
+
+  .content {
+    flex: auto;
+
+    h1 {
+      color: #434e59;
+      font-size: 72px;
+      font-weight: 600;
+      line-height: 72px;
+      margin-bottom: 24px;
+    }
+
+    .desc {
+      color: @text-color-secondary;
+      font-size: 20px;
+      line-height: 28px;
+      margin-bottom: 16px;
+    }
+
+    .actions {
+      button:not(:last-child) {
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: @screen-xl) {
+  .exception {
+    .imgBlock {
+      padding-right: 88px;
+    }
+  }
+}
+
+@media screen and (max-width: @screen-sm) {
+  .exception {
+    display: block;
+    text-align: center;
+    .imgBlock {
+      padding-right: 0;
+      margin: 0 auto 24px;
+    }
+  }
+}
+
+@media screen and (max-width: @screen-xs) {
+  .exception {
+    .imgBlock {
+      margin-bottom: -24px;
+      overflow: hidden;
+    }
+  }
+}

+ 21 - 0
src/components/Exception/index.md

@@ -0,0 +1,21 @@
+---
+title:
+  en-US: Exception
+  zh-CN: Exception
+subtitle: 异常
+cols: 1
+order: 5
+---
+
+异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。
+
+## API
+
+| 参数         | 说明                                      | 类型         | 默认值 |
+|-------------|------------------------------------------|-------------|-------|
+| type        | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - |
+| title       | 标题     | ReactNode  | -    |
+| desc        | 补充描述    | ReactNode  | -    |
+| img         | 背景图片地址     | string  | -    |
+| actions     | 建议操作,配置此属性时默认的『返回首页』按钮不生效    | ReactNode  | -    |
+| linkElement | 定义链接的元素,默认为 `a` | string\|ReactElement | - |

+ 19 - 0
src/components/Exception/typeConfig.js

@@ -0,0 +1,19 @@
+const config = {
+  403: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
+    title: '403',
+    desc: '抱歉,你无权访问该页面',
+  },
+  404: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
+    title: '404',
+    desc: '抱歉,你访问的页面不存在',
+  },
+  500: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
+    title: '500',
+    desc: '抱歉,服务器出错了',
+  },
+};
+
+export default config;

+ 36 - 0
src/components/FooterToolbar/demo/basic.md

@@ -0,0 +1,36 @@
+---
+order: 0
+title: 演示
+iframe: 400
+---
+
+浮动固定页脚。
+
+````jsx
+import FooterToolbar from 'ant-design-pro/lib/FooterToolbar';
+import { Button } from 'antd';
+
+ReactDOM.render(
+  <div style={{ background: '#f7f7f7', padding: 24 }}>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <p>页面内容 页面内容 页面内容 页面内容</p>
+    <FooterToolbar extra="提示信息">
+      <Button>取消</Button>
+      <Button type="primary">提交</Button>
+    </FooterToolbar>
+  </div>
+, mountNode);
+````

+ 9 - 0
src/components/FooterToolbar/index.d.ts

@@ -0,0 +1,9 @@
+import * as React from "react";
+export interface FooterToolbarProps {
+  extra: React.ReactNode;
+}
+
+export default class FooterToolbar extends React.Component<
+  FooterToolbarProps,
+  any
+> {}

+ 17 - 0
src/components/FooterToolbar/index.js

@@ -0,0 +1,17 @@
+import React, { Component } from "react";
+import classNames from "classnames";
+import styles from "./index.less";
+
+export default class FooterToolbar extends Component {
+  render() {
+    const { children, className, extra, rightStyle, ...restProps } = this.props;
+    return (
+      <div className={classNames(className, styles.toolbar)} {...restProps}>
+        <div className={styles.left}>{extra}</div>
+        <div className={styles.right} style={rightStyle}>
+          {children}
+        </div>
+      </div>
+    );
+  }
+}

+ 33 - 0
src/components/FooterToolbar/index.less

@@ -0,0 +1,33 @@
+@import "~antd/lib/style/themes/default.less";
+
+.toolbar {
+  position: fixed;
+  width: 100%;
+  bottom: 0;
+  right: 0;
+  height: 56px;
+  line-height: 56px;
+  box-shadow: 0 -1px 2px rgba(0, 0, 0, .03);
+  background: #fff;
+  border-top: 1px solid @border-color-split;
+  padding: 0 24px;
+  z-index: 9;
+
+  &:after {
+    content: "";
+    display: block;
+    clear: both;
+  }
+
+  .left {
+    float: left;
+  }
+
+  .right {
+    float: right;
+  }
+
+  button + button {
+    margin-left: 8px;
+  }
+}

+ 21 - 0
src/components/FooterToolbar/index.md

@@ -0,0 +1,21 @@
+---
+title:
+  en-US: FooterToolbar
+  zh-CN: FooterToolbar
+subtitle: 底部工具栏
+cols: 1
+order: 6
+---
+
+固定在底部的工具栏。
+
+## 何时使用
+
+固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+children | 工具栏内容,向右对齐 | ReactNode | -
+extra | 额外信息,向左对齐 | ReactNode | -

+ 322 - 0
src/components/Form/index.js

@@ -0,0 +1,322 @@
+import React, { PureComponent } from "react";
+import { Input, DatePicker, Radio, Select } from "antd";
+import moment from "moment";
+import styles from "./index.less";
+const RadioGroup = Radio.Group;
+const OPERATOR = {
+  EQ: "EQ",
+  LIKE: "LIKE",
+  GT: "GT",
+  LT: "LT",
+  GTE: "GTE",
+  LTE: "LTE",
+  NOTEQ: "NOTEQ",
+  IN: "IN",
+  NOTIN: "NOTIN",
+  LIKEORIN: "LIKEORIN",
+  DELIMIT: ":"
+};
+
+class Form extends PureComponent {
+  constructor(props) {
+    super(props);
+    const { searchAttrs } = this.props;
+    this.state = {};
+    searchAttrs.forEach(attr => {
+      if (attr.type == "comb" && attr.children) {
+        attr.children.forEach(item => {
+          this.getDefalutState(item);
+        });
+      } else {
+        this.getDefalutState(attr);
+      }
+    });
+  }
+  componentDidMount() {
+    this.search();
+  }
+  getDefalutState(option) {
+    switch (option.type) {
+      case "text":
+        this.state[`${option.key}`] = option.defaultValue || "";
+        break;
+      case "select":
+        this.state[`${option.key}`] = option.defaultValue || "";
+        break;
+      case "dropSelect":
+        this.state[`${option.key}`] = option.defaultValue || "";
+        break;
+      case "radio":
+        this.state[`${option.key}`] = option.defaultValue || "";
+        break;
+      case "daterange":
+        this.state[`${option.key}Start`] = option.startValue || "";
+        this.state[`${option.key}End`] = option.endValue || "";
+        break;
+      case "inputSearch":
+        this.state[`${option.key}`] = option.defaultValue || "";
+        break;
+      default:
+        break;
+    }
+  }
+  getSearchParams(searchParams, option) {
+    switch (option.type) {
+      case "text":
+        if (this.state[`${option.key}`]) {
+          searchParams[`search_${OPERATOR.LIKE}_${option.key}`] = `%${this.state[`${option.key}`]}%`;
+        }
+        break;
+      case "dropSelect":
+        searchParams[`search_${OPERATOR.EQ}_${option.key}`] = this.state[`${option.key}`] || "";
+        break;
+      case "select":
+        searchParams[`search_${OPERATOR.EQ}_${option.key}`] = this.state[`${option.key}`] || "";
+        break;
+      case "radio":
+        searchParams[`search_${OPERATOR.EQ}_${option.key}`] = this.state[`${option.key}`] || "";
+        break;
+      case "daterange":
+        searchParams[`search_${OPERATOR.GTE}_${option.key}_date`] = this.state[`${option.key}Start`];
+        searchParams[`search_${OPERATOR.LT}_${option.key}_date`] = this.state[`${option.key}End`];
+        break;
+      case "inputSearch":
+        if (this.state[`${option.key}`]) {
+          searchParams[`search_${OPERATOR.LIKE}_${option.key}`] = `%${this.state[`${option.key}`]}%`;
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  search(noSeach) {
+    const { searchAttrs, setSearchParams  } = this.props;
+    const searchParams = {};
+    searchAttrs.forEach(attr => {
+      if (attr.type == "comb" && attr.children) {
+        attr.children.forEach(item => {
+          this.getSearchParams(searchParams, item);
+        });
+      } else {
+        this.getSearchParams(searchParams, attr);
+      }
+    });
+    setSearchParams(searchParams, !noSeach);
+  }
+  // select 值改变
+  statusChange(field, status) {
+    this.setState(
+      {
+        [`${field}`]: status
+      },
+      () => {
+        this.search();
+      }
+    );
+  }
+  // rangeDate 值改变
+  rangePickerChange(field, dates, dateStrings) {
+    if (!dates[0]) return;
+    this.setState({
+      [`${field}`]: dateStrings,
+      [`${field}Start`]: Date.parse(dates[0].format()),
+      [`${field}End`]: Date.parse(dates[1].format())
+    });
+  }
+  // input,inputSearch, radio 值改变
+  fieldChange(field, e) {
+    e.preventDefault();
+    this.setState({
+      [`${field}`]: e.target.value
+    });
+  }
+  selectChange(field, value) {
+    this.setState({
+      [`${field}`]: value
+    });
+  }
+  // 搜索
+  searchHandle(field, value) {
+    this.setState(
+      {
+        [`${field}`]: value
+      },
+      () => {
+        this.search();
+      }
+    );
+  }
+  render() {
+    const { searchAttrs } = this.props;
+    const styleSelectOption = {
+      background: "#E14C46",
+      borderRadius: 2,
+      color: "#fff"
+    };
+    // Select渲染
+    const selectOptionHtml = (option) => {
+      if (!(option && option.sourceData && option.sourceData.length > 0)) return null;
+      const html = option.sourceData.map(item => {
+        const keyValue = option.refId ? item.id : item.code;
+        return (
+          <span
+            className={styles.btn}
+            key={item.name}
+            onClick={this.statusChange.bind(this, option.key, keyValue)}
+            style={this.state[`${option.key}`] == keyValue ? styleSelectOption : null}
+          >
+            {item.name}
+          </span>
+        );
+      });
+      html.unshift(
+        <span
+          className={styles.btn}
+          onClick={this.statusChange.bind(this,  option.key, "")}
+          style={this.state[`${option.key}`] == "" ? styleSelectOption : null}
+          key={1}
+        >
+          全部
+        </span>
+      );
+      return html;
+    };
+    // 拼接下拉框选项
+    const dropSelectOptionHtml = option => {
+      if (!(option && option.sourceData && option.sourceData.length > 0)) return null;
+      const optionHtmls = option.sourceData.map(item => {
+        const keyValue = option.refId ? item.id : item.code;
+        let keyName = item.name;
+        if (option.refName && option.refName.length > 0) {
+          keyName = option.refName.reduce((total, val) => total + item[`${val}`], "");
+        }
+        return (
+          <Select.Option key={keyValue} value={keyValue}>
+            {keyName}
+          </Select.Option>
+        );
+      });
+      return optionHtmls;
+    };
+    const dropSelectHtml = (option) => {
+      return (
+        <div className={styles.dropSelectBox}>
+          <span className={styles.textLabel}>{option.label ? `${option.label}${OPERATOR.DELIMIT}` : ""}</span>
+          <Select
+            style={{ width: option.width ? `${option.width}px` : "200px" }}
+            value={this.state[`${option.key}`]}
+            onChange={this.selectChange.bind(this, option.key)}
+          >
+            {dropSelectOptionHtml(option)}
+          </Select>
+        </div>
+      );
+    };
+    const radioOptionHtml = (option) => {
+      if (!(option && option.sourceData && option.sourceData.length > 0)) return null;
+      const html = option.sourceData.map(item => {
+        const keyValue = option.refId ? item.id : item.code;
+        return (
+          <Radio value={keyValue}>{item.name}</Radio>
+        );
+      });
+      return html;
+    };
+    const appendFormOptionHtml = (option) => {
+      switch (option.type) {
+        case "text":
+          return (
+            <div className={styles.textBox}>
+              <span className={styles.textLabel}>{option.label ? `${option.label}${OPERATOR.DELIMIT}` : ""}</span>
+              <div className={styles.textVal}>
+                <Input
+                  placeholder={option.placeholder}
+                  style={{ width: option.width ? option.width : "200" }}
+                  onChange={this.fieldChange.bind(this, option.key)}
+                  value={this.state[`${option.key}`]}
+                />
+              </div>
+            </div>
+          );
+        case "select":
+          return (
+            <div>{selectOptionHtml(option)}</div>
+          );
+        case "dropSelect":
+          return (
+            dropSelectHtml(option)
+          );
+        case "radio":
+          const radioValue = this.state[`${option.key}`] || "";
+          return (
+            <RadioGroup onChange={this.fieldChange.bind(this, option.key)} value={radioValue}>
+              {radioOptionHtml(option)}
+            </RadioGroup>
+          );
+        case "daterange":
+          const startDate = this.state[`${option.key}Start`] || "";
+          const endDate = this.state[`${option.key}End`] || "";
+          return (
+            <DatePicker.RangePicker
+              defaultValue={startDate && [moment(startDate), endDate && moment(endDate)]}
+              className={styles.searchData}
+              onChange={this.rangePickerChange.bind(this, option.key)}
+            />
+          );
+        case "inputSearch":
+          return (
+            <div className={styles.searchBox}>
+              <Input.Search
+                placeholder={option.placeholder}
+                enterButton={option.enterButton}
+                style={{ width: 280 }}
+                onChange={this.fieldChange.bind(this, option.key)}
+                value={this.state[`${option.key}`]}
+                onSearch={this.searchHandle.bind(this, option.key)}
+              />
+            </div>
+          );
+        default:
+          return null;
+      }
+    };
+    const appendFormHtml = (searchAttrs) => {
+      const optionHtmls = searchAttrs.map(attr => {
+        const style = {};
+        if (attr.labelWidth) {
+          style.width = attr.labelWidth;
+        }
+        if (attr.type == "comb" && attr.children) {
+          return (
+            <div className={styles.searchItem}>
+              <span className={styles.searchName} style={style}>{attr.label}{OPERATOR.DELIMIT}</span>
+              <div className={styles.searchVal}>
+                {
+                  attr.children.map(option => {
+                    return appendFormOptionHtml(option);
+                  })
+                }
+              </div>
+            </div>
+          );
+        } else {
+          return (
+            <div className={styles.searchItem}>
+              <span className={styles.searchName} style={style}>{attr.label}{OPERATOR.DELIMIT}</span>
+              <div className={styles.searchVal}>
+                {appendFormOptionHtml(attr)}
+              </div>
+            </div>
+          );
+        }
+      });
+      return optionHtmls;
+    };
+    return (
+      <div className={styles.FromBox}>
+        {appendFormHtml(searchAttrs)}
+      </div>
+    );
+  }
+}
+export default Form;

+ 75 - 0
src/components/Form/index.less

@@ -0,0 +1,75 @@
+.FromBox {
+  .searchItem {
+    width: 100%;
+    padding: 0 10px;
+    height: 30px;
+    font-size: 12px;
+    color: #333;
+    margin-bottom: 10px;
+    & .searchName {
+      display: inline-block;
+      line-height: 30px;
+      font-weight: bold;
+      min-width: 60px;
+    }
+    & .searchVal {
+      display: inline-block;
+      height: 30px;
+      margin-left: 15px;
+      & .btn {
+        display: inline-block;
+        min-width: 90px;
+        cursor: pointer;
+        text-align: center;
+        line-height: 30px;
+      }
+      & .searchData {
+        width: 220px;
+        height: 30px;
+        :global {
+          & .ant-input {
+          height: 30px;
+          }
+        }
+      }
+      & .searchBox {
+        display: inline-block;
+        margin-left: 20px;
+      }
+      & .dropSelectBox {
+        display: inline-block;
+      }
+      & .textBox {
+        display: inline-block;
+      }
+      .textLabel {
+        display: inline-block;
+        font-weight: bold;
+      }
+      .textVal {
+        display: inline-block;
+        margin-right: 20px;
+      }
+      :global {
+        .ant-radio-wrapper {
+          font-size: 12px;
+        }
+      }
+    }
+    & .addGoodsBtn {
+      float: right;
+      // background: #e56968;
+      border-radius: 3px;
+      width: 100px;
+      height: 32px;
+      line-height: 32px;
+      text-align: center;
+      color: #fff;
+      font-size: 12px;
+      cursor: pointer;
+      & .icon {
+        font-size: 16px;
+      }
+    }
+  }
+}

+ 33 - 0
src/components/GlobalFooter/demo/basic.md

@@ -0,0 +1,33 @@
+---
+order: 0
+title: 演示
+iframe: 400
+---
+
+基本页脚。
+
+````jsx
+import GlobalFooter from 'ant-design-pro/lib/GlobalFooter';
+import { Icon } from 'antd';
+
+const links = [{
+  title: '帮助',
+  href: '',
+}, {
+  title: '隐私',
+  href: '',
+}, {
+  title: '条款',
+  href: '',
+  blankTarget: true,
+}];
+
+const copyright = <div>Copyright <Icon type="copyright" /> 2017 蚂蚁金服体验技术部出品</div>;
+
+ReactDOM.render(
+  <div style={{ background: '#f5f5f5', overflow: 'hidden' }}>
+    <div style={{ height: 280 }} />
+    <GlobalFooter links={links} copyright={copyright} />
+  </div>
+, mountNode);
+````

+ 14 - 0
src/components/GlobalFooter/index.d.ts

@@ -0,0 +1,14 @@
+import * as React from "react";
+export interface GlobalFooterProps {
+  links: Array<{
+    title: React.ReactNode;
+    href: string;
+    blankTarget?: boolean;
+  }>;
+  copyright: React.ReactNode;
+}
+
+export default class GlobalFooter extends React.Component<
+  GlobalFooterProps,
+  any
+> {}

+ 27 - 0
src/components/GlobalFooter/index.js

@@ -0,0 +1,27 @@
+import React from 'react';
+import classNames from 'classnames';
+import styles from './index.less';
+
+export default ({ className, links, copyright }) => {
+  const clsString = classNames(styles.globalFooter, className);
+  return (
+    <div className={clsString}>
+      {
+        links && (
+          <div className={styles.links}>
+            {links.map(link => (
+              <a
+                key={link.title}
+                target={link.blankTarget ? '_blank' : '_self'}
+                href={link.href}
+              >
+                {link.title}
+              </a>
+            ))}
+          </div>
+        )
+      }
+      {copyright && <div className={styles.copyright}>{copyright}</div>}
+    </div>
+  );
+};

+ 29 - 0
src/components/GlobalFooter/index.less

@@ -0,0 +1,29 @@
+@import "~antd/lib/style/themes/default.less";
+
+.globalFooter {
+  padding: 0 16px;
+  margin: 48px 0 24px 0;
+  text-align: center;
+
+  .links {
+    margin-bottom: 8px;
+
+    a {
+      color: @text-color-secondary;
+      transition: all .3s;
+
+      &:not(:last-child) {
+        margin-right: 40px;
+      }
+
+      &:hover {
+        color: @text-color;
+      }
+    }
+  }
+
+  .copyright {
+    color: @text-color-secondary;
+    font-size: @font-size-base;
+  }
+}

+ 17 - 0
src/components/GlobalFooter/index.md

@@ -0,0 +1,17 @@
+---
+title:
+  en-US: GlobalFooter
+  zh-CN: GlobalFooter
+subtitle: 全局页脚
+cols: 1
+order: 7
+---
+
+页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | -
+copyright | 版权信息 | ReactNode | -

BIN
src/components/GoodsBig/goodsHot.png


+ 88 - 0
src/components/GoodsBig/index.js

@@ -0,0 +1,88 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { Menu, Icon } from "antd";
+import styles from "./index.less";
+import imgsrc from "./goodsHot.png";
+import GoodsBuyIpt from "../GoodsBuyIpt/index.js";
+
+class GoodsHot extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+  // 购买数量改变
+  buyNumChange(key, num) {
+    const { data } = this.props;
+    orderCartData[key][`buyNum`] = num;
+    this.props.dispatch({
+      type: "orderEdit/orderCartData",
+      orderCartData: orderCartData
+    });
+  }
+  // 增加购物车
+  addCartHandle(data) {
+    console.log(data);
+    const { dispatch } = this.props;
+    const postData = {
+      buyNum: "",
+      goodsSku: data.code
+    };
+    dispatch({
+      type: "cartList/addCart",
+      payload: postData
+    });
+  }
+  render() {
+    const { data, index } = this.props;
+    if (!data) return null;
+    return (
+      <div className={styles.goodsHot}>
+        <div className={styles.imgBox}>
+          <img src={data.pictureUrl} alt={{}} className={styles.img} />
+        </div>
+        {/* 商品信息 */}
+        <div className={styles.goodsHotInfobox}>
+          <div className={styles.goodsHotTitlebox}>
+            <span className={styles.goodsTitle}>{data.name}</span>
+            <span className={styles.goodsHotPrice}>¥{data.salePrice}</span>
+          </div>
+          <div className={styles.goodsHotCodeBox}>
+            <div className={styles.goodsHotCode}>
+              <span className={styles.goodsCode}>商品编码: </span>
+              <span className={styles.codeInfo}>{data.code}</span>
+            </div>
+            <div className={styles.goodsNumBox}>
+              <span className={styles.goodsNum}>库存: </span>
+              <span className={styles.num}>
+                {data.stockNum} {data.unitName}
+              </span>
+            </div>
+            <span className={styles.oldPrice}>¥{data.marketPrice}</span>
+          </div>
+        </div>
+        {/* 商品信息 --结束 */}
+        {/* 鼠标移入 */}
+        <div className={styles.goodsHotHoverBox}>
+          <div className={styles.goodsCollect}>
+            <Icon type="heart-o" className={styles.collectIcon} />
+            <span className={styles.collectTxt}>关注商品</span>
+          </div>
+          <div className={styles.iptBox}>
+            <GoodsBuyIpt
+              onChange={this.buyNumChange.bind(this, index)}
+              num={0}
+            />
+          </div>
+          <div
+            className={styles.addCart}
+            onClick={this.addCartHandle.bind(this, data)}
+          >
+            <Icon type="shopping-cart" className={styles.cartIcon} />
+            <span className={styles.cartTxt}>加入购物车</span>
+          </div>
+        </div>
+        {/* 鼠标移入--结束 */}
+      </div>
+    );
+  }
+}
+export default GoodsHot;

+ 114 - 0
src/components/GoodsBig/index.less

@@ -0,0 +1,114 @@
+@import "~antd/lib/style/themes/default.less";
+
+.goodsHot {
+  width: 388px;
+  height: 300px;
+  padding: 0;
+  margin: 0;
+  border: 1px solid #f0f0f0;
+  // cursor: pointer;
+  position: relative;
+}
+.imgBox {
+  height: 223px;
+}
+.img {
+  width: 100%;
+  height: 213px;
+}
+.goodsHotInfobox {
+  height: 77px;
+  background: #f9f9f9;
+  width: 100%;
+  padding: 16px 7px;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  z-index: 2;
+}
+.goodsHotTitlebox {
+  clear: both;
+  margin-bottom: 10px;
+}
+.goodsTitle {
+  font-size: 14px;
+  color: #333;
+  font-weight: bold;
+}
+.goodsHotPrice {
+  float: right;
+  font-size: 16px;
+  color: #d81719;
+}
+.goodsHotCodeBox {
+  clear: both;
+}
+.goodsHotCode {
+  float: left;
+  width: 165px;
+  font-size: 12px;
+  color: #666;
+}
+// .goodsCode {
+// }
+// .codeInfo {
+// }
+.goodsNumBox {
+  float: left;
+}
+// .goodsNum {
+// }
+.oldPrice {
+  float: right;
+  font-size: 12px;
+  color: #999;
+  text-decoration: line-through;
+}
+.goodsHot:hover {
+  .goodsHotHoverBox {
+    bottom: 77px;
+  }
+}
+// 鼠标移入
+.goodsHotHoverBox {
+  position: absolute;
+  bottom: 47px;
+  left: 0;
+  height: 40px;
+  width: 100%;
+  background: rgba(255, 255, 255, 0.8);
+  padding: 10px 10px 0 20px;
+  clear: both;
+  transition: all 0.2s;
+}
+.goodsCollect {
+  float: left;
+  cursor: pointer;
+}
+.collectIcon {
+  color: #333;
+  font-size: 16px;
+}
+.collectTxt {
+  color: #333;
+  font-size: 14px;
+  padding-left: 10px;
+}
+.iptBox {
+  float: left;
+  width: 80px;
+  margin-left: 80px;
+}
+.addCart {
+  float: right;
+  cursor: pointer;
+}
+.cartIcon {
+  color: #333;
+  padding-right: 10px;
+  font-size: 16px;
+}
+.cartTxt {
+  color: #333;
+  font-size: 14px;
+}

+ 86 - 0
src/components/GoodsBuyIpt/index.js

@@ -0,0 +1,86 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { Input, Icon } from "antd";
+import styles from "./index.less";
+
+class GoodsBuyIpt extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {
+      val: this.props.num
+    };
+  }
+  componentWillReceiveProps(nextProps) {
+    this.setState({
+      val: nextProps.num
+    });
+  }
+  numChange(e) {
+    e.preventDefault();
+    let value = e.target.value == "" ? 0 : e.target.value;
+    const { max } = this.props;
+    if (max) {
+      if (value >= max) {
+        value = max;
+      }
+    }
+    this.setState({
+      val: value
+    });
+    this.props.onChange(value);
+  }
+  lessHandle(e) {
+    e.preventDefault();
+    let { val } = this.state;
+    let { step } = this.props;
+    val = parseFloat(val || "0");
+    step = parseFloat(step || 1);
+    if (val < step) {
+      val = 0;
+    } else {
+      val -= step;
+    }
+    this.setState({
+      val: val
+    });
+    this.props.onChange(val);
+  }
+  addHandle(e) {
+    e.preventDefault();
+    let { val } = this.state;
+    let { max, step } = this.props;
+    val = parseFloat(val || "0");
+    step = parseFloat(step || 1);
+    max = parseInt(max || "9999");
+    if (val >= max - step) {
+      val = max;
+    } else {
+      val += step;
+    }
+    this.setState({
+      val: val
+    });
+    this.props.onChange(val);
+  }
+  render() {
+    const { step, disabled } = this.props;
+    const prefixHtml = () => (
+      <Icon onClick={this.lessHandle.bind(this)} type="minus" />
+    );
+    const suffixHtml = () => (
+      <Icon onClick={this.addHandle.bind(this)} type="plus" />
+    );
+    return (
+      <div className={styles.goodsBuyIpt}>
+        <Input
+          disabled={disabled ? true : false}
+          value={this.state.val}
+          onChange={this.numChange.bind(this)}
+          prefix={prefixHtml()}
+          suffix={suffixHtml()}
+        />
+      </div>
+    );
+  }
+}
+export default GoodsBuyIpt;

+ 23 - 0
src/components/GoodsBuyIpt/index.less

@@ -0,0 +1,23 @@
+@import "~antd/lib/style/themes/default.less";
+
+.goodsBuyIpt {
+  clear: both;
+  height: 30px;
+  width: 92px;
+  background: #fff;
+  border: 1px solid #ccc;
+  border-radius: 2px;
+  :global {
+    .ant-input-affix-wrapper {
+      input {
+        border: none;
+        height: 28px;
+        text-align: center;
+      }
+      .ant-input-prefix,
+      .ant-input-suffix {
+        cursor: pointer;
+      }
+    }
+  }
+}

BIN
src/components/GoodsCommon/goodsHot.png


+ 251 - 0
src/components/GoodsCommon/index.js

@@ -0,0 +1,251 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { routerRedux } from "dva/router";
+import { Menu, Icon, message } from "antd";
+import iconfontStyles from "../../iconfont/iconfont.css";
+import styles from "./index.less";
+// import GoodsSmall from "../GoodsSmall/index.js";
+import imgsrc from "./goodsHot.png";
+import GoodsBuyIpt from "../GoodsBuyIpt/index.js";
+
+import CustIcon, { STATUS as IconStatus } from "../CustIcon";
+import erroImg  from "@/assets/imgError.png";
+import { amountPrecision } from '@/utils/utils';
+import { getCustomer } from '@/utils/request';
+
+class GoodsCommon extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {
+        refresh: false
+    };
+  }
+  componentWillMount() {
+    const { dispatch } = this.props;
+    dispatch({
+      type: "goodsCommon/getGoodsCommon",
+      payload: null
+    });
+    dispatch({
+      type: "cartList/getCartListData"
+  });
+  }
+  // 购买数量改变
+  buyNumChange(key, num) {
+    const { goodsAllData } = this.props;
+    goodsAllData[key][`orderNum`] = num;
+    this.props.dispatch({
+      type: "goodsCommon/goodsAllData",
+      goodsAllData: goodsAllData
+    });
+  }
+  // 增加购物车
+  addCartHandle(item, e) {
+    e.preventDefault();
+    const { dispatch } = this.props;
+    if (!item.baseDiscountPrice || !item.stock || item.baseDiscountPrice <= 0 || item.stock <= 0) {
+      return false;
+    }
+    if (item.orderNum < 1) {
+      message.warning("添加数量不能为0!");
+      return;
+    }
+    const mainNum = parseFloat(item.orderNum || 1) * parseFloat(item.conversionRate || 1);
+    const postData = {
+      orderNum: item.orderNum || 1,
+        mainNum: mainNum,
+        goodsId: item.id,
+        goodsDisplayName: item.displayName,
+        goodsName: item.name,
+        goodsCode: item.code,
+        goodsImg: item.mainPictureFileUrl,
+        orderNumUnitId: item.assistUnitId,
+        orderNumUnitCode: item.assistUnitCode,
+        orderNumUnitName: item.assistUnitName,
+        orderNumUnitScale: item.orderNumUnitScale || 0,
+        mainNumUnitId: item.basicUnitId,
+        mainNumUnitCode: item.basicUnitCode,
+        mainNumUnitName: item.basicUnitName,
+        mainNumUnitScale: item.basicUnitPrecision || 0,
+        weight: item.grossWeight, // 默认取毛重
+        ...item
+    };
+    dispatch({
+      type: "cartList/addCart",
+      payload: postData
+    }).finally(() => {
+      message.warning('已加入购物车');
+    });
+  }
+  // 关注商品 & 取消商品
+  addAndDelStore(item, index) {
+    const { dispatch } = this.props;
+    const postData = !item.inFav ? {
+        customerId: getCustomer().id,
+        goodsId: item.id,
+        goodsImg: item.mainPictureFileUrl,
+        isPrimaryChannel: 1,
+        saleOrgId: item.saleOrgId,
+        supplierId: null
+    } : {
+        customer: getCustomer().id,
+        id: item.id
+    };
+    const url = item.inFav ? "public/getDelStore" : "public/getAddStore";
+    dispatch({
+      type: url,
+      payload: postData
+    }).finally(() => {
+        const { goodsAllData } = this.props;
+        goodsAllData[index].inFav = !goodsAllData[index].inFav;
+        const message = item.inFav ? "已关注" : "已取消关注";
+        message.warning(message);
+        dispatch({
+            type: "goodsCommon/goodsAllData",
+            goodsAllData: goodsAllData
+        });
+        this.setState({ refresh: !this.state.refresh });
+    });
+  }
+  // 跳转详情
+  toDetailHandle(data, e) {
+    e.preventDefault();
+    const { dispatch } = this.props;
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/goods/GoodsDetail",
+        query: {
+          code: data.code,
+          id: data.id,
+          saleOrgId: data.saleOrgId ? data.saleOrgId : data.supplier,
+          productId: data.productId
+        }
+      })
+    );
+    localStorage.setItem("goodsSku", data.code);
+    localStorage.setItem("goodsDetailsId", data.id);  // 商品Id
+    localStorage.setItem("goodsDetailSsaleOrgId", data.saleOrgId ? data.saleOrgId : data.supplier);  // 供应商
+    localStorage.setItem("goodsDetailProductId", data.productId);  // 产品id
+  }
+  // 跳转商品列表页
+  toGoodsPurchase() {
+    const { dispatch } = this.props;
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/goodsPurchase"
+      })
+    );
+  }
+  // 图片加载失败
+  imgErro(e) {
+    e.target.src = erroImg;
+  }
+  render() {
+    const { goodsAllData, cartListData } = this.props;
+    if (!goodsAllData || goodsAllData.length === 0) {
+      return null;
+    }
+    if (cartListData && goodsAllData) {
+      for (const cartGroup of cartListData) {
+          if (cartGroup && cartGroup.cartlist) {
+              const cartList = cartGroup.cartlist;
+              for (const cart of cartList) {
+                  for (const goods of goodsAllData) {
+                      if (cart.goodsId === goods.id) {
+                          goods.inCart = true;
+                      }
+                  }
+              }
+          }
+      }
+  }
+    const goodsHotListHtml = () => {
+      const html = goodsAllData.map((item, index) => (
+        <li className={styles.goodsHotItem} key={item.id} style={index % 4 == 0 && index != 0 ? { marginRight: 0 } : { marginRight: 10 }}>
+          <div className={styles.goodsHot}>
+            <div
+              className={styles.imgBox}
+              onClick={this.toDetailHandle.bind(this, item)}
+            >
+              <img src={item.mainPictureFileUrl ? item.mainPictureFileUrl : erroImg} alt="" onError={this.imgErro.bind(this)} className={styles.img} />
+            </div>
+            {/* 商品信息 */}
+            <div className={styles.goodsHotInfobox}>
+              <div className={styles.goodsHotTitlebox}>
+                <span className={styles.goodsTitle}>{item.displayName}</span>
+              </div>
+              <div className={styles.codeBox}>
+                <div>
+                  <span>编码: {item.code}</span>
+                  <span className={styles.cell} title={`${item.model}`}>型号:{item.model}</span>
+                </div>
+                <div>
+                  <span>规格: {item.specification}</span>
+                </div>
+              </div>
+              <div className={styles.goodsHotCodeBox}>
+                <span className={styles.goodsHotPrice}>{item.baseDiscountPrice ? amountPrecision(item.baseDiscountPrice) : '0.00'}</span>
+              </div>
+            </div>
+            {/* 商品信息 --结束 */}
+            {/* 鼠标移入 */}
+            <div className={styles.goodsHotHoverBox}>
+              <div className={styles.goodsCollect} onClick={this.addAndDelStore.bind(this, item, index)}>
+                <CustIcon
+                  type={item.inFav ? "icon-icon-heart-i" : "icon-icon-heart"}
+                  style={{ fontSize: 20, color: item.inFav ? '#E14C46' : '#333' }}
+                />
+              </div>
+              <div className={styles.iptBox}>
+                <GoodsBuyIpt
+                  onChange={this.buyNumChange.bind(this, index)}
+                  num={1}
+                />
+              </div>
+              <div
+                className={styles.addCart}
+                onClick={this.addCartHandle.bind(this, item)}
+              >
+                  <CustIcon
+                    type="icon-icon-shoppingcart"
+                    status={(item.baseDiscountPrice && item.stock && item.baseDiscountPrice > 0 && item.stock > 0) ? item.inCart : IconStatus.DISABLE}
+                    style={{ fontSize: 20, color: '#333' }}
+                  />
+              </div>
+            </div>
+            {/* 鼠标移入--结束 */}
+            {/* 促销 状态 *
+            <div className={styles.goodsStatusBox}>
+              <i className={styles.goodsStatusNew}>新品</i>
+              <i className={styles.goodsStatusSale}>促销</i>
+            </div>
+            {/* 促销 状态 --结束 */}
+          </div>
+        </li>
+      ));
+      return html;
+    };
+    return (
+      <div className={styles.allGoodsBox}>
+        <div className={styles.heardbox}>
+          <span className={styles.title}>全部商品</span>
+          <span
+            className={styles.more}
+            onClick={this.toGoodsPurchase.bind(this)}
+          >
+            更多 <CustIcon type="icon-icon-right" style={{ fontSize: 16 }} />
+          </span>
+        </div>
+        <div className={styles.goodsHotBox}>
+          <ul className={styles.goodsHotList} key="goods_ul">
+            {goodsHotListHtml()}
+          </ul>
+        </div>
+      </div>
+    );
+  }
+}
+export default connect(state => ({
+  goodsAllData: state.goodsCommon.goodsAllData,
+  cartListData: state.cartList.cartListData,
+}))(GoodsCommon);

+ 184 - 0
src/components/GoodsCommon/index.less

@@ -0,0 +1,184 @@
+@import "~antd/lib/style/themes/default.less";
+
+.heardbox {
+  padding: 20px 0;
+}
+.title {
+  font-size: 20px;
+  color: #333;
+  font-weight: bold;
+}
+.more {
+  float: right;
+  font-size: 16px;
+  color: #333;
+  cursor: pointer;
+}
+.goodsHotBox {
+  // position: relative;
+  min-height: 330px;
+  width: 100%;
+}
+.codeBox {
+  font-size:12px;
+}
+.cell{
+  float: right; 
+  padding-right: 9px;
+}
+.goodsHotList {
+  list-style-type: none;
+  width: 100%;
+  display: flex;
+  justify-content: flex-start;
+  padding: 0;
+  margin: 0;
+  flex-wrap: wrap;
+}
+.goodsHotItem {
+  flex: 0 0 232px;
+  height: 330px;
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+  margin-bottom: 10px;
+}
+.goodsHot {
+  width: 230px;
+  height: 330px;
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+  border: 1px solid #f0f0f0;
+  position: relative;
+  margin-bottom: 10px;
+}
+.imgBox {
+  cursor: pointer;
+  height: 220px;
+}
+.img {
+  width: 100%;
+  height: 220px;
+}
+.goodsHotInfobox {
+  height: 100px;
+  background: #f9f9f9;
+  width: 100%;
+  position: absolute;
+  padding: 10px;
+  bottom: 0;
+  left: 0;
+  z-index: 3;
+}
+.goodsHotTitlebox {
+  clear: both;
+  height: 28px;
+  line-height: 14px;
+}
+.goodsTitle {
+  font-size: 14px;
+  color: #333;
+  font-weight: bold;
+}
+.goodsHotPrice {
+  font-size: 16px;
+  color: #d81719;
+}
+.goodsHotCodeBox {
+  margin-top: 0px;
+  // text-align: center;
+}
+.goodsNumBox {
+  float: left;
+}
+// .goodsNum{
+// }
+.goodsHot:hover {
+  .goodsHotHoverBox {
+    bottom: 74px;
+  }
+}
+// 鼠标移入
+.goodsHotHoverBox {
+  position: absolute;
+  bottom: 35px;
+  left: 0;
+  height: 40px;
+  width: 100%;
+  background: rgba(255, 255, 255, 0.8);
+  padding: 0 10px;
+  clear: both;
+  transition: all 0.2s;
+}
+.goodsCollect {
+  float: left;
+  cursor: pointer;
+  margin-top: -15px;
+}
+.collectIcon {
+  color: #333;
+  font-size: 16px;
+  height: 40px;
+  line-height: 40px;
+  margin-top: -17px;
+}
+.iptBox {
+  float: left;
+  width: 80px;
+  margin-left: 39px;
+  margin-top: -17px;
+  // div[class^="goodsBuyIpt"]{
+  //   width: 80px;
+  //   height: 24px;
+  //   border-radius: 0; 
+  //   background: rgba(255, 255, 255, 0.8);
+  //   border: 1px solid #333;
+  //   input{
+  //     height: 22px;
+  //     font-size: 12px;
+  //     color: #333;
+  //   }
+  // }
+}
+.addCart {
+  float: right;
+  height: 40px;
+  line-height: 40px;
+  cursor: pointer;
+  margin-top: -20px;
+}
+.cartIcon {
+  color: #333;
+  font-size: 16px;
+}
+
+// 促销
+.goodsStatusBox {
+  position: absolute;
+  left: 0;
+  top: 16px;
+}
+.goodsStatusNew {
+  display: block;
+  width: 50px;
+  height: 24px;
+  border-radius: 0 10px 10px 0;
+  background: #59c152;
+  text-align: center;
+  line-height: 24px;
+  color: #fff;
+  font-size: 12px;
+}
+.goodsStatusSale {
+  margin-top: 10px;
+  display: block;
+  width: 50px;
+  height: 24px;
+  border-radius: 0 10px 10px 0;
+  background: #e14c46;
+  text-align: center;
+  line-height: 24px;
+  color: #fff;
+  font-size: 12px;
+}

+ 48 - 0
src/components/GoodsHistory/index.js

@@ -0,0 +1,48 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { Menu, Icon } from "antd";
+import styles from "./index.less";
+import GoodsSmall from "../GoodsSmall/index.js";
+
+class GoodsHistory extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+  render() {
+    // const classname1 = classnames
+    return (
+      // <div className={styles.allGoodsBox}>
+      //   <div className={styles.heardbox}>
+      //     <span className={styles.title}>最近浏览</span>
+      //     <div className={styles.btnBox}>
+      //       <i className={styles.btn} />
+      //       <i className={styles.btn} />
+      //       <i className={styles.btn} />
+      //       <i className={styles.btn} />
+      //     </div>
+      //   </div>
+      //   <div className={styles.goodsHotBox}>
+      //     <ul className={styles.goodsHotList}>
+      //       <li className={styles.goodsHotItem}>
+      //         <GoodsSmall />
+      //       </li>
+      //       <li className={styles.goodsHotItem}>
+      //         <GoodsSmall />
+      //       </li>
+      //       <li className={styles.goodsHotItem}>
+      //         <GoodsSmall />
+      //       </li>
+      //       <li className={styles.goodsHotItem}>
+      //         <GoodsSmall />
+      //       </li>
+      //       <li className={styles.goodsHotItem}>
+      //         <GoodsSmall />
+      //       </li>
+      //     </ul>
+      //   </div>
+      // </div>
+      null
+    );
+  }
+}
+export default GoodsHistory;

+ 51 - 0
src/components/GoodsHistory/index.less

@@ -0,0 +1,51 @@
+@import "~antd/lib/style/themes/default.less";
+
+.heardbox {
+  padding: 20px 0;
+}
+.title {
+  font-size: 20px;
+  color: #333;
+  font-weight: bold;
+}
+.btnBox {
+  float: right;
+}
+.btn {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background: #d8d8d8;
+  margin-left: 10px;
+  cursor: pointer;
+  &.active {
+    width: 14px;
+    height: 14px;
+    background: #e14c46;
+  }
+}
+.goodsHotBox {
+  position: relative;
+  min-height: 310px;
+  width: 100%;
+}
+.goodsHotList {
+  list-style-type: none;
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  padding: 0;
+  margin: 0;
+  position: absolute;
+  left: 0;
+  top: 0;
+  overflow: hidden;
+}
+.goodsHotItem {
+  flex: 0 0 230px;
+  height: 310px;
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+}

+ 243 - 0
src/components/GoodsHot/index.js

@@ -0,0 +1,243 @@
+import React, { PureComponent } from "react";
+import { connect } from "dva";
+import { routerRedux } from "dva/router";
+import { Menu, Icon, message } from "antd";
+import iconfontStyles from "../../iconfont/iconfont.css";
+import styles from "./index.less";
+// import GoodsBig from "../GoodsBig/index.js";
+import GoodsBuyIpt from "../GoodsBuyIpt/index.js";
+
+import CustIcon, { STATUS as IconStatus } from "../CustIcon";
+import erroImg  from "@/assets/imgError.png";
+import { getCustomer } from '@/utils/request';
+import { amountPrecision } from '@/utils/utils';
+
+class GoodsHot extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {
+      refresh: false
+    };
+  }
+  componentWillMount() {
+    const { dispatch } = this.props;
+    dispatch({
+      type: "goodsHot/getGoodsHotData"
+    });
+    dispatch({
+      type: "cartList/getCartListData"
+    });
+  }
+  // 购买数量改变
+  buyNumChange(key, num) {
+    const { goodsHotData } = this.props;
+    goodsHotData[key][`orderNum`] = num;
+    this.props.dispatch({
+      type: "goodsHot/goodsHotData",
+      goodsHotData: goodsHotData
+    });
+  }
+  // 增加购物车
+  addCartHandle(item) {
+    const { goodsHotData, dispatch } = this.props;
+    if (!item.baseDiscountPrice || !item.stock || item.baseDiscountPrice <= 0 || item.stock <= 0) {
+      return false;
+    }
+    if (item.orderNum < 1) {
+      message.warning("添加数量不能为0!");
+      return;
+    }
+    const mainNum = parseFloat(item.orderNum || 1) * parseFloat(item.conversionRate || 1);
+    const postData = {
+      orderNum: item.orderNum || 1,
+        mainNum: mainNum,
+        goodsId: item.id,
+        goodsDisplayName: item.displayName,
+        goodsName: item.name,
+        goodsCode: item.code,
+        goodsImg: item.mainPictureFileUrl,
+        orderNumUnitId: item.assistUnitId,
+        orderNumUnitCode: item.assistUnitCode,
+        orderNumUnitName: item.assistUnitName,
+        orderNumUnitScale: item.orderNumUnitScale || 0,
+        mainNumUnitId: item.basicUnitId,
+        mainNumUnitCode: item.basicUnitCode,
+        mainNumUnitName: item.basicUnitName,
+        mainNumUnitScale: item.basicUnitPrecision || 0,
+        weight: item.grossWeight, // 默认取毛重
+        ...item
+    };
+    dispatch({
+      type: "cartList/addCart",
+      payload: postData
+    }).finally(() => {
+        message.warning('已加入购物车');
+    });
+  }
+  // 关注商品 & 取消商品
+  addAndDelStore(item, index) {
+    const { goodsHotData, dispatch } = this.props;
+    const postData = !item.inFav ? {
+      customerId: getCustomer().id,
+      goodsId: item.id,
+      goodsImg: item.mainPictureFileUrl,
+      isPrimaryChannel: 1,
+      saleOrgId: item.saleOrgId,
+      supplierId: null
+    } : {
+      customer: getCustomer().id,
+      id: item.id
+    };
+    const url = item.inFav ? "public/getDelStore" : "public/getAddStore";
+    dispatch({
+      type: url,
+      payload: postData
+    }).finally(() => {
+      const { goodsHotData } = this.props;
+      goodsHotData[index].inFav = !goodsHotData[index].inFav;
+      const message = item.inFav ? "已关注" : "已取消关注";
+      message.warning(message);
+      dispatch({
+        type: "goodsHot/goodsHotData",
+        goodsHotData: goodsHotData
+      });
+      this.setState({ refresh: !this.state.refresh });
+    });
+  }
+  // 跳转详情
+  toDetailHandle(data) {
+    const { dispatch } = this.props;
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/goods/GoodsDetail",
+        query: {
+          code: data.code,
+          id: data.id,
+          saleOrgId: data.saleOrgId ? data.saleOrgId : data.supplier,
+          productId: data.productId
+        }
+      })
+    );
+    localStorage.setItem("goodsSku", data.code);
+    localStorage.setItem("goodsDetailsId", data.id);  // 商品Id
+    localStorage.setItem("goodsDetailSsaleOrgId", data.saleOrgId ? data.saleOrgId : data.supplier);  // 供应商
+    localStorage.setItem("goodsDetailProductId", data.productId);  // 产品id
+  }
+  // 跳转商品列表页
+  toGoodsPurchase() {
+    const { dispatch } = this.props;
+    dispatch(
+      routerRedux.push({
+        pathname: "/buyer/replenishment/4"
+      })
+    );
+  }
+  // 图片加载失败
+  imgErro(e) {
+    e.target.src = erroImg;
+  }
+  render() {
+    const { goodsHotData, cartListData } = this.props;
+    if (cartListData && goodsHotData) {
+      for (const cartGroup of cartListData) {
+          if (cartGroup && cartGroup.cartlist) {
+              const cartList = cartGroup.cartlist;
+              for (const cart of cartList) {
+                  for (const goods of goodsHotData) {
+                      if (cart.goodsId === goods.id) {
+                          goods.inCart = true;
+                      }
+                  }
+              }
+          }
+      }
+    }
+    const goodsHotListHtml = () => {
+      const goodsHotListData = goodsHotData && !goodsHotData.status ? goodsHotData : [];
+      if (goodsHotListData.length == 0) return (<div className={styles.noGoodsHot}>暂无热销产品</div>);
+      const html = goodsHotListData.map((item, index) => (
+        <li className={styles.goodsHotItem} key={item.id} style={index == 0 ? { marginLeft: 0 } : { marginLeft: 13 }}>
+          <div className={styles.goodsHot}>
+            <div
+              className={styles.imgBox}
+              onClick={this.toDetailHandle.bind(this, item)}
+            >
+              <img src={item.mainPictureFileUrl ? item.mainPictureFileUrl : erroImg} alt="" onError={this.imgErro.bind(this)} className={styles.img} />
+            </div>
+            {/* 商品信息 */}
+            <div className={styles.goodsHotInfobox}>
+              <div className={styles.goodsHotTitlebox}>
+                <span className={styles.goodsTitle}>{item.displayName.length > 19 ? `${item.displayName.substr(0, 18)}...` : item.displayName}</span>
+                <span className={styles.goodsColorMask}></span>
+              </div>
+              <div className={styles.codeBox}>
+                <div>
+                  <span>编码: {item.code}</span>
+                  <span className={styles.cell} title={`${item.model}`}>型号:{item.model}</span>
+                </div>
+                <div>
+                  <span>规格: {item.specification}</span>
+                </div>
+              </div>
+              <div className={styles.goodsHotCodeBox}>
+                <div className={styles.goodsHotCode}>
+                <span className={styles.goodsHotPrice}>{item.baseDiscountPrice ? amountPrecision(item.baseDiscountPrice) : '0.00'}</span>
+                </div>
+                <span className={styles.oldPrice}>{item.basePrice ? amountPrecision(item.basePrice) : '0.00'}</span>
+              </div>
+            </div>
+            {/* 商品信息 --结束 */}
+            {/* 鼠标移入 */}
+            <div className={styles.goodsHotHoverBox}>
+              <div className={styles.goodsCollect} onClick={this.addAndDelStore.bind(this, item)}>
+                <CustIcon
+                  type={item.inFav ? "icon-icon-heart-i" : "icon-icon-heart"}
+                  style={{ fontSize: 26, color: item.inFav ? '#E14C46' : '#333' }}
+                />
+              </div>
+              <div className={styles.iptBox}>
+                <GoodsBuyIpt
+                  onChange={this.buyNumChange.bind(this, index)}
+                  num={1}
+                />
+              </div>
+              <div
+                className={styles.addCart}
+                onClick={this.addCartHandle.bind(this, item)}
+              >
+                <CustIcon
+                  type="icon-icon-shoppingcart"
+                  status={(item.baseDiscountPrice && item.stock && item.baseDiscountPrice > 0 && item.stock > 0) ? item.inCart : IconStatus.DISABLE}
+                  style={{ fontSize: 26, color: '#333' }}
+                />
+              </div>
+            </div>
+            {/* 鼠标移入--结束 */}
+          </div>
+        </li>
+      ));
+      return html;
+    };
+    return (
+      <div className={styles.allGoodsBox}>
+        <div className={styles.heardbox}>
+          <span className={styles.title}>热卖促销</span>
+          <span
+            className={styles.more}
+            onClick={this.toGoodsPurchase.bind(this)}
+            style={goodsHotData && goodsHotData.length > 4 ? {} : { display: 'none' }}
+          >
+            更多 <CustIcon type="icon-icon-right" style={{ fontSize: 16 }} />
+          </span>
+        </div>
+        <div className={styles.goodsHotBox}>
+          <ul className={styles.goodsHotList}>{goodsHotListHtml()}</ul>
+        </div>
+      </div>
+    );
+  }
+}
+export default connect(state => ({
+  goodsHotData: state.goodsHot.goodsHotData,
+  cartListData: state.cartList.cartListData,
+}))(GoodsHot);

+ 192 - 0
src/components/GoodsHot/index.less

@@ -0,0 +1,192 @@
+@import "~antd/lib/style/themes/default.less";
+
+.heardbox {
+  padding: 20px 0;
+}
+.title {
+  font-size: 20px;
+  color: #333;
+  font-weight: bold;
+}
+.more {
+  float: right;
+  font-size: 16px;
+  color: #333;
+  cursor: pointer;
+}
+.goodsHotBox {
+  position: relative;
+  height: 380px;
+  width: 100%;
+}
+.goodsHotList {
+  position: absolute;
+  top: 0;
+  left: 0;
+  list-style-type: none;
+  width: 100%;
+  display: flex;
+  justify-content:  flex-start;
+  padding: 0;
+  margin: 0;
+  overflow: hidden;
+}
+.goodsHotItem {
+  flex: 0 0 290px;
+  height: 380px;
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+}
+.goodsHot {
+  width: 290px;
+  height: 380px;
+  padding: 0;
+  margin: 0;
+  border: 1px solid #f0f0f0;
+  // cursor: pointer;
+  position: relative;
+}
+.imgBox {
+  height: 290px;
+  cursor: pointer;
+}
+.img {
+  width: 100%;
+  height: 290px;
+}
+.goodsHotInfobox {
+  height: 100px;
+  background: #f9f9f9;
+  width: 100%;
+  padding: 8px;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  z-index: 3;
+}
+.goodsHotTitlebox {
+  clear: both;
+  margin-bottom: 5px;
+}
+.goodsTitle {
+  font-size: 14px;
+  color: #333;
+}
+.goodsColorMask {
+  background-color: #f85300;
+  color: #fff;
+  margin-left: 2px;
+}
+.goodsHotPrice {
+  font-size: 16px;
+  color: #d81719;
+}
+.goodsHotCodeBox {
+  clear: both;
+}
+.goodsHotCode {
+  float: left;
+  font-size: 12px;
+  color: #666;
+}
+// .goodsCode {
+// }
+// .codeInfo {
+// }
+.goodsNumBox {
+  float: left;
+  font-size: 12px;
+  color: #666;
+}
+// .goodsNum {
+// }
+.oldPrice {
+  float: right;
+  font-size: 12px;
+  padding-right: 9px;
+  color: #999;
+  text-decoration: line-through;
+}
+.goodsHot:hover {
+  .goodsHotHoverBox {
+    bottom: 90px;
+  }
+}
+// 鼠标移入
+.goodsHotHoverBox {
+  position: absolute;
+  bottom: 50px;
+  left: 0;
+  height: 40px;
+  width: 100%;
+  background: rgba(255, 255, 255, 0.8);
+  padding: 0 10px;
+  clear: both;
+  transition: all 0.2s;
+}
+.codeBox {
+  font-size:12px;
+}
+.cell{
+  float: right; 
+  padding-right: 9px;
+}
+.goodsCollect {
+  height: 40px;
+  line-height: 45px;
+  float: left;
+  cursor: pointer;
+  margin-top: -10px;
+}
+.collectIcon {
+  color: #333;
+  font-size: 16px;
+}
+.collectTxt {
+  color: #333;
+  font-size: 14px;
+  padding-left: 10px;
+}
+.iptBox {
+  float: left;
+  width: 80px;
+  height: 40px;
+  margin-top: -10px;
+  margin-left: 62px;
+  /* & div[class^="goodsBuyIpt"]{
+    width: 80px;
+    height: 24px;
+    border-radius: 0; 
+    background: rgba(255, 255, 255, 0.8);
+    border: 1px solid #333;
+    input{
+      height: 22px;
+      font-size: 12px;
+      color: #333;
+    }
+  } */
+}
+.addCart {
+  float: right;
+  margin-top: -10px;
+  cursor: pointer;
+}
+.cartIcon {
+  color: #333;
+  padding-right: 10px;
+  font-size: 16px;
+}
+.cartTxt {
+  color: #333;
+  font-size: 14px;
+}
+.noGoodsHot{
+  width: 100%;
+  height: 100px;
+  line-height: 100px;
+  text-align: center;
+  font-size: 16px;
+  color: #666;
+  border: 1px solid #eee;
+}

+ 0 - 0
src/components/GoodsLevel/data.json


Some files were not shown because too many files changed in this diff