Browse Source

fix: bug 修复打包后报错 TypeError: le.addRoute is not a function

原因是本地开发阶段引用的是npm包中的vue-router,与打包后externals中CDN载入的vue-router版本不一致导致的。
sam 3 years ago
commit
47df691132
100 changed files with 4772 additions and 0 deletions
  1. 3 0
      .browserslistrc
  2. 39 0
      .editorconfig
  3. 3 0
      .env
  4. 3 0
      .env.development
  5. 3 0
      .env.preview
  6. 75 0
      .eslintrc.js
  7. 5 0
      .eslintrc.json
  8. 1 0
      .gitattributes
  9. 54 0
      .github/ISSUE_TEMPLATE/bug_report.md
  10. 20 0
      .github/ISSUE_TEMPLATE/feature_request.md
  11. 17 0
      .github/ISSUE_TEMPLATE/need-help-issue.md
  12. 48 0
      .github/pull_request_template.md
  13. 22 0
      .gitignore
  14. 6 0
      .prettierrc
  15. 7 0
      .travis.yml
  16. 6 0
      Dockerfile
  17. 21 0
      LICENSE
  18. 102 0
      README.md
  19. 110 0
      README.zh-CN.md
  20. 30 0
      babel.config.js
  21. 49 0
      config/plugin.config.js
  22. 115 0
      config/themePluginConfig.js
  23. 9 0
      deploy/caddy.conf
  24. 24 0
      deploy/nginx.conf
  25. 31 0
      docs/add-page-loading-animate.md
  26. 40 0
      docs/webpack-bundle-analyzer.md
  27. 23 0
      jest.config.js
  28. 11 0
      jsconfig.json
  29. 62 0
      package.json
  30. 5 0
      postcss.config.js
  31. BIN
      public/avatar2.jpg
  32. 34 0
      public/index.html
  33. BIN
      public/logo.png
  34. 28 0
      src/App.vue
  35. 80 0
      src/api/login.js
  36. 70 0
      src/api/manage.js
  37. 69 0
      src/assets/background.svg
  38. 1 0
      src/assets/icons/bx-analyse.svg
  39. 29 0
      src/assets/logo.svg
  40. 89 0
      src/components/ArticleListContent/ArticleListContent.vue
  41. 3 0
      src/components/ArticleListContent/index.js
  42. 25 0
      src/components/AvatarList/Item.jsx
  43. 72 0
      src/components/AvatarList/List.jsx
  44. 9 0
      src/components/AvatarList/index.js
  45. 60 0
      src/components/AvatarList/index.less
  46. 64 0
      src/components/AvatarList/index.md
  47. 62 0
      src/components/Charts/Bar.vue
  48. 120 0
      src/components/Charts/ChartCard.vue
  49. 67 0
      src/components/Charts/Liquid.vue
  50. 56 0
      src/components/Charts/MiniArea.vue
  51. 57 0
      src/components/Charts/MiniBar.vue
  52. 75 0
      src/components/Charts/MiniProgress.vue
  53. 40 0
      src/components/Charts/MiniSmoothArea.vue
  54. 68 0
      src/components/Charts/Radar.vue
  55. 77 0
      src/components/Charts/RankList.vue
  56. 113 0
      src/components/Charts/TagCloud.vue
  57. 64 0
      src/components/Charts/TransferBar.vue
  58. 82 0
      src/components/Charts/Trend.vue
  59. 13 0
      src/components/Charts/chart.less
  60. 14 0
      src/components/Charts/smooth.area.less
  61. 113 0
      src/components/Dialog.js
  62. 83 0
      src/components/Editor/QuillEditor.vue
  63. 57 0
      src/components/Editor/WangEditor.vue
  64. 64 0
      src/components/Ellipsis/Ellipsis.vue
  65. 3 0
      src/components/Ellipsis/index.js
  66. 38 0
      src/components/Ellipsis/index.md
  67. 47 0
      src/components/FooterToolbar/FooterToolBar.vue
  68. 4 0
      src/components/FooterToolbar/index.js
  69. 23 0
      src/components/FooterToolbar/index.less
  70. 48 0
      src/components/FooterToolbar/index.md
  71. 23 0
      src/components/GlobalFooter/index.vue
  72. 80 0
      src/components/GlobalHeader/AvatarDropdown.vue
  73. 58 0
      src/components/GlobalHeader/RightContent.vue
  74. 86 0
      src/components/IconSelector/IconSelector.vue
  75. 48 0
      src/components/IconSelector/README.md
  76. 36 0
      src/components/IconSelector/icons.js
  77. 2 0
      src/components/IconSelector/index.js
  78. 162 0
      src/components/MultiTab/MultiTab.vue
  79. 2 0
      src/components/MultiTab/events.js
  80. 40 0
      src/components/MultiTab/index.js
  81. 25 0
      src/components/MultiTab/index.less
  82. 76 0
      src/components/NProgress/nprogress.less
  83. 90 0
      src/components/NoticeIcon/NoticeIcon.vue
  84. 2 0
      src/components/NoticeIcon/index.js
  85. 54 0
      src/components/NumberInfo/NumberInfo.vue
  86. 3 0
      src/components/NumberInfo/index.js
  87. 55 0
      src/components/NumberInfo/index.less
  88. 43 0
      src/components/NumberInfo/index.md
  89. 62 0
      src/components/Other/CarbonAds.vue
  90. 106 0
      src/components/PageLoading/index.jsx
  91. 63 0
      src/components/Search/GlobalSearch.jsx
  92. 25 0
      src/components/Search/index.less
  93. 58 0
      src/components/SelectLang/index.jsx
  94. 31 0
      src/components/SelectLang/index.less
  95. 343 0
      src/components/SettingDrawer/SettingDrawer.vue
  96. 38 0
      src/components/SettingDrawer/SettingItem.vue
  97. 2 0
      src/components/SettingDrawer/index.js
  98. 48 0
      src/components/SettingDrawer/settingConfig.js
  99. 24 0
      src/components/SettingDrawer/themeColor.js
  100. 122 0
      src/components/StandardFormRow/StandardFormRow.vue

+ 3 - 0
.browserslistrc

@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not ie <= 10

+ 39 - 0
.editorconfig

@@ -0,0 +1,39 @@
+[*]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=false
+indent_style=space
+indent_size=2
+
+[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
+indent_style=space
+indent_size=2
+
+[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
+indent_style=space
+indent_size=2
+
+[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
+indent_style=space
+indent_size=2
+
+[*.svg]
+indent_style=space
+indent_size=2
+
+[*.js.map]
+indent_style=space
+indent_size=2
+
+[*.less]
+indent_style=space
+indent_size=2
+
+[*.vue]
+indent_style=space
+indent_size=2
+
+[{.analysis_options,*.yml,*.yaml}]
+indent_style=space
+indent_size=2
+

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+NODE_ENV=production
+VUE_APP_PREVIEW=false
+VUE_APP_API_BASE_URL=/api

+ 3 - 0
.env.development

@@ -0,0 +1,3 @@
+NODE_ENV=development
+VUE_APP_PREVIEW=true
+VUE_APP_API_BASE_URL=/api

+ 3 - 0
.env.preview

@@ -0,0 +1,3 @@
+NODE_ENV=production
+VUE_APP_PREVIEW=true
+VUE_APP_API_BASE_URL=/api

+ 75 - 0
.eslintrc.js

@@ -0,0 +1,75 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  'extends': [
+    'plugin:vue/strongly-recommended',
+    '@vue/standard'
+  ],
+  rules: {
+    'no-console': 'off',
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+    'generator-star-spacing': 'off',
+    'no-mixed-operators': 0,
+    'vue/max-attributes-per-line': [
+      2,
+      {
+        'singleline': 5,
+        'multiline': {
+          'max': 1,
+          'allowFirstLine': false
+        }
+      }
+    ],
+    'vue/attribute-hyphenation': 0,
+    'vue/html-self-closing': 0,
+    'vue/component-name-in-template-casing': 0,
+    'vue/html-closing-bracket-spacing': 0,
+    'vue/singleline-html-element-content-newline': 0,
+    'vue/no-unused-components': 0,
+    'vue/multiline-html-element-content-newline': 0,
+    'vue/no-use-v-if-with-v-for': 0,
+    'vue/html-closing-bracket-newline': 0,
+    'vue/no-parsing-error': 0,
+    'no-tabs': 0,
+    'quotes': [
+      2,
+      'single',
+      {
+        'avoidEscape': true,
+        'allowTemplateLiterals': true
+      }
+    ],
+    'semi': [
+      2,
+      'never',
+      {
+        'beforeStatementContinuationChars': 'never'
+      }
+    ],
+    'no-delete-var': 2,
+    'prefer-const': [
+      2,
+      {
+        'ignoreReadBeforeAssign': false
+      }
+    ],
+    'template-curly-spacing': 'off',
+    'indent': 'off'
+  },
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  overrides: [
+    {
+      files: [
+        '**/__tests__/*.{j,t}s?(x)',
+        '**/tests/unit/**/*.spec.{j,t}s?(x)'
+      ],
+      env: {
+        jest: true
+      }
+    }
+  ]
+}

+ 5 - 0
.eslintrc.json

@@ -0,0 +1,5 @@
+{
+  "rules": {
+    "space-before-function-paren": 0
+  }
+}

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+public/* linguist-vendored

+ 54 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,54 @@
+---
+name: Bug report
+about: Create a report to help us improve(Bug 反馈)
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug (描述 Bug)**
+
+A clear and concise description of what the bug is.
+
+
+
+**To Reproduce (重现步骤)**
+Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+
+
+**Expected behavior(你期待的是什么?)**
+A clear and concise description of what you expected to happen.
+
+
+
+**Screenshots(截图)**
+If applicable, add screenshots to help explain your problem.
+
+
+
+**Desktop (please complete the following information):**
+
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+
+
+**Smartphone (please complete the following information):**
+
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+
+
+**Additional context(附加信息)**
+Add any other context about the problem here.

+ 20 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.

+ 17 - 0
.github/ISSUE_TEMPLATE/need-help-issue.md

@@ -0,0 +1,17 @@
+---
+name: Need help issue
+about: Question for use(问题求助)
+title: ''
+labels: question
+assignees: ''
+
+---
+
+**Question (问题描述)**
+How to use component `s-table` paging
+
+**Describe the solution you'd like (你期待的是什么?)**
+A clear and concise description of what you want to happen.
+
+**Additional context(附加信息)**
+Add any other context or screenshots about the feature request here.

+ 48 - 0
.github/pull_request_template.md

@@ -0,0 +1,48 @@
+First of all, thank you for your contribution! 😄
+
+Pull request will be merged after one of collaborators approve.
+Please makes sure that these form are filled before submitting your pull request, thank you!
+
+
+### 这个变动的性质是
+
+- [ ] 新特性提交
+- [ ] 日常 bug 修复
+- [ ] 文档改进
+- [ ] 组件样式改进
+- [ ] 重构
+- [ ] 代码风格优化
+- [ ] 分支合并
+- [ ] 其他改动(是关于什么的改动?)
+
+### 需求背景
+
+> 1. 描述相关需求的来源。
+> 2. 要解决的问题。
+> 3. 相关的 issue 讨论链接。
+
+### 实现方案和 API(非新功能可选)
+
+> 1. 基本的解决思路和其他可选方案。
+> 2. 列出最终的 API 实现和用法。
+> 3. 涉及UI/交互变动需要有截图或 GIF。
+
+### 对用户的影响和可能的风险(非新功能可选)
+
+> 1. 这个改动对用户端是否有影响?影响的方面有哪些?
+> 2. 是否有可能隐含的 break change 和其他风险?
+
+### Changelog 描述(非新功能可选)
+
+> 1. 英文描述
+> 2. 中文描述(可选)
+
+### 请求合并前的自查清单
+
+- [ ] 文档已补充或无须补充
+- [ ] 代码演示已提供或无须提供
+- [ ] Changelog 已提供或无须提供
+
+### 后续计划(非新功能可选)
+
+> 如果这个提交后面还有相关的其他提交和跟进信息,可以写在这里。

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+package-lock.json

+ 6 - 0
.prettierrc

@@ -0,0 +1,6 @@
+{
+  "printWidth": 120,
+  "semi": false,
+  "singleQuote": true,
+  "prettier.spaceBeforeFunctionParen": true
+}

+ 7 - 0
.travis.yml

@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+  - 10.15.0
+cache: yarn
+script:
+  - yarn
+  - yarn run lint --no-fix && yarn run build

+ 6 - 0
Dockerfile

@@ -0,0 +1,6 @@
+FROM nginx
+
+RUN rm /etc/nginx/conf.d/default.conf
+
+ADD deploy/nginx.conf /etc/nginx/conf.d/default.conf
+COPY dist/ /usr/share/nginx/html/

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Anan Yang
+
+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.

+ 102 - 0
README.md

@@ -0,0 +1,102 @@
+English | [简体中文](./README.zh-CN.md)
+
+<h1 align="center">Ant Design Vue Pro</h1>
+<div align="center">
+An out-of-box UI solution for enterprise applications as a Vue boilerplate. based on  <a href="https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/" target="_blank">Ant Design of Vue</a>
+</div>
+
+<div align="center">
+
+[![License](https://img.shields.io/npm/l/package.json.svg?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/blob/master/LICENSE)
+[![Release](https://img.shields.io/github/release/vueComponent/ant-design-vue-pro.svg?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/releases/latest)
+[![Support Vue Version](https://img.shields.io/badge/Support-Vue2-green?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/releases/latest)
+[![Travis branch](https://travis-ci.org/vueComponent/ant-design-vue-pro.svg?branch=master)](https://travis-ci.org/vueComponent/ant-design-vue-pro)
+
+</div>
+
+- Preview: https://preview.pro.antdv.com
+- Home Page: https://pro.antdv.com
+- Documentation: https://pro.antdv.com/docs/getting-started
+- ChangeLog: https://pro.antdv.com/docs/changelog
+- FAQ: https://pro.antdv.com/docs/faq
+
+Overview
+----
+
+![dashboard](https://static-2.loacg.com/open/static/github/SP1.png)
+
+### Env and dependencies
+
+- node
+- yarn
+- webpack
+- eslint
+- @vue/cli
+- [ant-design-vue@1.x](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 
+- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - Picture edit
+- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - AntV G2
+- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation)  - Antv/G2 of Vue
+
+> Note:  [Yarn](https://yarnpkg.com/) package management is recommended, the exact same version loaded with the demo site of this project (yarn.lock) . but you can also use npm
+
+
+### Project setup
+
+- Clone repo
+```bash
+git clone https://github.com/vueComponent/ant-design-vue-pro.git
+cd ant-design-vue-pro
+```
+
+- Install dependencies
+```
+yarn install
+```
+
+- Compiles and hot-reloads for development
+```
+yarn run serve
+```
+
+- Compiles and minifies for production
+```
+yarn run build
+```
+
+- Lints and fixes files
+```
+yarn run lint
+```
+
+
+### Other
+
+- **IMPORTANT : About Issue feedback !! when opening Issue read [Issue / PR Contributing](https://github.com/vueComponent/ant-design-vue-pro/issues/90)**
+
+- [Vue-cli3](https://cli.vuejs.org/guide/) used by the project.
+
+- Disable Eslint (not recommended): remove `eslintConfig`  field in `package.json`  and `vue.config.js` field `lintOnSave: false`
+
+- Load on Demand `/src/main.js` L14, in `import './core/lazy_use'`, `import './core/use''`. more [load-on-demand.md](./docs/load-on-demand.md)
+
+- Customize Theme:  [Custom Theme Config (@kokoroli)](https://github.com/kokoroli/antd-awesome/blob/master/docs/Ant_Design_%E6%A0%B7%E5%BC%8F%E8%A6%86%E7%9B%96.md)
+
+- I18n: [locales (@musnow)](./src/locales/index.js)
+
+- Production env `mock` is disabled. use `src/mock/index.js`
+
+- pls use `release` version
+
+## Browsers support
+
+Modern browsers and IE10.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
+| --- | --- | --- | --- | --- |
+| IE10, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
+
+
+## Contributors
+
+This project exists thanks to all the people who contribute. 
+<a href="https://github.com/vueComponent/ant-design-vue-pro/graphs/contributors"><img src="https://opencollective.com/ant-design-pro-vue/contributors.svg?width=890&button=false" /></a>

+ 110 - 0
README.zh-CN.md

@@ -0,0 +1,110 @@
+[English](./README.md) | 简体中文
+
+<h1 align="center">Ant Design Vue Pro</h1>
+<div align="center">
+An out-of-box UI solution for enterprise applications as a Vue boilerplate. based on  <a href="https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/" target="_blank">Ant Design of Vue</a>
+</div>
+
+<div align="center">
+
+[![License](https://img.shields.io/npm/l/package.json.svg?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/blob/master/LICENSE)
+[![Release](https://img.shields.io/github/release/vueComponent/ant-design-vue-pro.svg?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/releases/latest)
+[![Support Vue Version](https://img.shields.io/badge/Support-Vue2-green?style=flat)](https://github.com/vueComponent/ant-design-vue-pro/releases/latest)
+[![Travis branch](https://travis-ci.org/vueComponent/ant-design-vue-pro.svg?branch=master)](https://travis-ci.org/vueComponent/ant-design-vue-pro)
+
+</div>
+
+- 预览: https://preview.pro.antdv.com
+- 首页: https://pro.antdv.com
+- 文档: https://pro.antdv.com/docs/getting-started
+- 更新日志: https://pro.antdv.com/docs/changelog
+- 常见问题: https://pro.antdv.com/docs/faq
+
+
+Overview
+----
+
+基于 [Ant Design of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 实现的 [Ant Design Pro](https://pro.ant.design/) 
+
+![dashboard](https://static-2.loacg.com/open/static/github/SP1.png)
+
+环境和依赖
+----
+
+- node
+- yarn
+- webpack
+- eslint
+- @vue/cli
+- [ant-design-vue@1.x](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
+- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
+- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
+- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation)  - antv/g2 封装实现
+
+> 请注意,我们强烈建议本项目使用 [Yarn](https://yarnpkg.com/) 包管理工具,这样可以与本项目演示站所加载完全相同的依赖版本 (yarn.lock) 。由于我们没有对依赖进行强制的版本控制,采用非 yarn 包管理进行引入时,可能由于 Pro 所依赖的库已经升级版本而引入了新版本所导致的问题。作者可能会由于时间问题无法及时排查而导致您采用本项目作为基项目而出现问题。
+
+
+
+项目下载和运行
+----
+
+- 拉取项目代码
+```bash
+git clone https://github.com/vueComponent/ant-design-vue-pro.git
+cd ant-design-vue-pro
+```
+
+- 安装依赖
+```
+yarn install
+```
+
+- 开发模式运行
+```
+yarn run serve
+```
+
+- 编译项目
+```
+yarn run build
+```
+
+- Lints and fixes files
+```
+yarn run lint
+```
+
+
+
+其他说明
+----
+
+- **关于 Issue 反馈 (重要!重要!重要!) 请在开 *Issue* 前,先阅读该内容:[Issue / PR 编写建议](https://github.com/vueComponent/ant-design-vue-pro/issues/90)** 
+
+- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请确保你所使用的 vue-cli 是新版,并且已经学习 cli 官方文档使用教程
+
+- 关闭 Eslint (不推荐) 移除 `package.json` 中 `eslintConfig` 整个节点代码, `vue.config.js` 下的 `lintOnSave` 值改为 `false`
+
+- 组件按需加载 `/src/main.js` L14 相关代码 `import './core/lazy_use'` / `import './core/use'` 
+
+- [修改 Ant Design 配色 (@kokoroli)](https://github.com/kokoroli/antd-awesome/blob/master/docs/Ant_Design_%E6%A0%B7%E5%BC%8F%E8%A6%86%E7%9B%96.md)
+
+- I18n: [多语言支持 (@musnow)](./src/locales/index.js)
+
+- 生产环境默认不加载 `mock`,更多详情请看 `src/mock/index.js`
+
+- **用于生产环境,请使用 `release` 版本代码,使用 master 代码出现的任何问题需要你自行解决**
+
+## 浏览器兼容
+
+Modern browsers and IE10.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
+| --- | --- | --- | --- | --- |
+| IE10, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
+
+
+## Contributors
+
+This project exists thanks to all the people who contribute. 
+<a href="https://github.com/vueComponent/ant-design-vue-pro/graphs/contributors"><img src="https://opencollective.com/ant-design-pro-vue/contributors.svg?width=890&button=false" /></a>

+ 30 - 0
babel.config.js

@@ -0,0 +1,30 @@
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
+const IS_PREVIEW = process.env.VUE_APP_PREVIEW === 'true'
+
+const plugins = []
+if (IS_PROD && !IS_PREVIEW) {
+  // 去除日志的插件,
+  plugins.push('transform-remove-console')
+}
+
+// lazy load ant-design-vue
+// if your use import on Demand, Use this code
+plugins.push(['import', {
+  'libraryName': 'ant-design-vue',
+  'libraryDirectory': 'es',
+  'style': true // `style: true` 会加载 less 文件
+}])
+
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset',
+    [
+      '@babel/preset-env',
+      {
+        'useBuiltIns': 'entry',
+        'corejs': 3
+      }
+    ]
+  ],
+  plugins
+}

+ 49 - 0
config/plugin.config.js

@@ -0,0 +1,49 @@
+const ThemeColorReplacer = require('webpack-theme-color-replacer')
+const generate = require('@ant-design/colors/lib/generate').default
+
+const getAntdSerials = (color) => {
+  // 淡化(即less的tint)
+  const lightens = new Array(9).fill().map((t, i) => {
+    return ThemeColorReplacer.varyColor.lighten(color, i / 10)
+  })
+  const colorPalettes = generate(color)
+  const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',')
+  return lightens.concat(colorPalettes).concat(rgb)
+}
+
+const themePluginOption = {
+  fileName: 'css/theme-colors-[contenthash:8].css',
+  matchColors: getAntdSerials('#1890ff'), // 主色系列
+  // 改变样式选择器,解决样式覆盖问题
+  changeSelector (selector) {
+    switch (selector) {
+      case '.ant-calendar-today .ant-calendar-date':
+        return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector
+      case '.ant-btn:focus,.ant-btn:hover':
+        return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)'
+      case '.ant-btn.active,.ant-btn:active':
+        return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)'
+      case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
+      case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon':
+        return ':not(.ant-steps-item-process)' + selector
+      // fixed https://github.com/vueComponent/ant-design-vue-pro/issues/876
+      case '.ant-steps-item-process .ant-steps-item-icon':
+        return ':not(.ant-steps-item-custom)' + selector
+      case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
+      case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
+        return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover'
+      case '.ant-menu-horizontal > .ant-menu-item-selected > a':
+      case '.ant-menu-horizontal>.ant-menu-item-selected>a':
+        return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a'
+      case '.ant-menu-horizontal > .ant-menu-item > a:hover':
+      case '.ant-menu-horizontal>.ant-menu-item>a:hover':
+        return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover'
+      default :
+        return selector
+    }
+  }
+}
+
+const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption)
+
+module.exports = createThemeColorReplacerPlugin

+ 115 - 0
config/themePluginConfig.js

@@ -0,0 +1,115 @@
+export default {
+  theme: [
+    {
+      key: 'dark',
+      fileName: 'dark.css',
+      theme: 'dark'
+    },
+    {
+      key: '#F5222D',
+      fileName: '#F5222D.css',
+      modifyVars: {
+        '@primary-color': '#F5222D'
+      }
+    },
+    {
+      key: '#FA541C',
+      fileName: '#FA541C.css',
+      modifyVars: {
+        '@primary-color': '#FA541C'
+      }
+    },
+    {
+      key: '#FAAD14',
+      fileName: '#FAAD14.css',
+      modifyVars: {
+        '@primary-color': '#FAAD14'
+      }
+    },
+    {
+      key: '#13C2C2',
+      fileName: '#13C2C2.css',
+      modifyVars: {
+        '@primary-color': '#13C2C2'
+      }
+    },
+    {
+      key: '#52C41A',
+      fileName: '#52C41A.css',
+      modifyVars: {
+        '@primary-color': '#52C41A'
+      }
+    },
+    {
+      key: '#2F54EB',
+      fileName: '#2F54EB.css',
+      modifyVars: {
+        '@primary-color': '#2F54EB'
+      }
+    },
+    {
+      key: '#722ED1',
+      fileName: '#722ED1.css',
+      modifyVars: {
+        '@primary-color': '#722ED1'
+      }
+    },
+
+    {
+      key: '#F5222D',
+      theme: 'dark',
+      fileName: 'dark-#F5222D.css',
+      modifyVars: {
+        '@primary-color': '#F5222D'
+      }
+    },
+    {
+      key: '#FA541C',
+      theme: 'dark',
+      fileName: 'dark-#FA541C.css',
+      modifyVars: {
+        '@primary-color': '#FA541C'
+      }
+    },
+    {
+      key: '#FAAD14',
+      theme: 'dark',
+      fileName: 'dark-#FAAD14.css',
+      modifyVars: {
+        '@primary-color': '#FAAD14'
+      }
+    },
+    {
+      key: '#13C2C2',
+      theme: 'dark',
+      fileName: 'dark-#13C2C2.css',
+      modifyVars: {
+        '@primary-color': '#13C2C2'
+      }
+    },
+    {
+      key: '#52C41A',
+      theme: 'dark',
+      fileName: 'dark-#52C41A.css',
+      modifyVars: {
+        '@primary-color': '#52C41A'
+      }
+    },
+    {
+      key: '#2F54EB',
+      theme: 'dark',
+      fileName: 'dark-#2F54EB.css',
+      modifyVars: {
+        '@primary-color': '#2F54EB'
+      }
+    },
+    {
+      key: '#722ED1',
+      theme: 'dark',
+      fileName: 'dark-#722ED1.css',
+      modifyVars: {
+        '@primary-color': '#722ED1'
+      }
+    }
+  ]
+}

+ 9 - 0
deploy/caddy.conf

@@ -0,0 +1,9 @@
+0.0.0.0:80 {
+  gzip
+  root /usr/share/nginx/html
+
+  rewrite {
+    r .*
+    to {path} /
+  }
+}

+ 24 - 0
deploy/nginx.conf

@@ -0,0 +1,24 @@
+server {
+    listen 80;
+    server_name  _;
+    # gzip config
+    gzip on;
+    gzip_min_length 1k;
+    gzip_comp_level 6;
+    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
+    gzip_vary on;
+    gzip_disable "MSIE [1-6]\.";
+
+    root /usr/share/nginx/html;
+    include /etc/nginx/mime.types;
+
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+
+#    location /api {
+#        proxy_pass https://preview.pro.antdv.com/api;
+#        proxy_set_header   X-Forwarded-Proto $scheme;
+#        proxy_set_header   X-Real-IP         $remote_addr;
+#    }
+}

+ 31 - 0
docs/add-page-loading-animate.md

@@ -0,0 +1,31 @@
+为首屏增加 加载动画
+====
+
+
+
+## 需求
+
+> 为了缓解用户第一次访问时,加载 JS 过大所导致用户等待白屏时间过长导致的用户体验不好,进行的一个优化动效。
+
+
+
+## 实现方案
+
+1. 将 动画加载 dom 元素放在 #app 内,Vue 生命周期开始时,会自动清掉 #app 下的所有元素。
+2. 将 动画加载 dom 元素放在 body 下,Vue 生命周期开始时 App.vue (created, mounted) 调用 `@/utils/utll` 下的 removeLoadingAnimate(#id, timeout) 则会移除加载动画
+
+最后一步:
+​	将样式插入到 `public/index.html` 文件的 `<head></head>` 最好写成内联 `<style>动画样式</style>` 
+
+
+
+----
+
+目前提供有两个样式,均在 `public/loading` 文件夹内。且 pro 已经默认使用了一套 loading 动画方案,可以直接参考 `public/index.html`
+
+
+## 写在最后
+
+目前 pro 有页面 overflow 显示出浏览器滚动条时,页面会抖动一下的问题。
+
+欢迎各位提供能解决的方案和实现 demo。如果在条件允许的情况下,建议请直接使用 pro 进行改造,也欢迎直接 PR 到 pro 的仓库

+ 40 - 0
docs/webpack-bundle-analyzer.md

@@ -0,0 +1,40 @@
+先增加依赖
+
+```bash
+// npm
+$ npm install --save-dev webpack-bundle-analyzer
+
+// or yarn
+$ yarn add webpack-bundle-analyzer -D
+```
+
+配置文件 `vue.config.js` 增加 `configureWebpack.plugins` 参数
+
+```
+const path = require('path')
+const webpack = require('webpack')
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+
+function resolve (dir) {
+  return path.join(__dirname, dir)
+}
+
+// vue.config.js
+module.exports = {
+  configureWebpack: {
+    plugins: [
+      // Ignore all locale files of moment.js
+      new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
+      // 依赖大小分析工具
+      new BundleAnalyzerPlugin(),
+    ]
+  },
+  
+  
+  ...
+}
+```
+
+
+
+启动 `cli` 的 `build` 命令进行项目编译,编译完成时,会自动运行一个 http://localhost:8888 的地址,完整显示了支持库依赖

+ 23 - 0
jest.config.js

@@ -0,0 +1,23 @@
+module.exports = {
+  moduleFileExtensions: [
+    'js',
+    'jsx',
+    'json',
+    'vue'
+  ],
+  transform: {
+    '^.+\\.vue$': 'vue-jest',
+    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
+    '^.+\\.jsx?$': 'babel-jest'
+  },
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  snapshotSerializers: [
+    'jest-serializer-vue'
+  ],
+  testMatch: [
+    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+  ],
+  testURL: 'http://localhost/'
+}

+ 11 - 0
jsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "target": "es6",
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"],
+  "include": ["src/**/*"]
+}

+ 62 - 0
package.json

@@ -0,0 +1,62 @@
+{
+  "name": "vue-antd-pro",
+  "version": "3.0.2",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "test:unit": "vue-cli-service test:unit",
+    "lint": "vue-cli-service lint",
+    "build:preview": "vue-cli-service build --mode preview",
+    "lint:nofix": "vue-cli-service lint --no-fix"
+  },
+  "dependencies": {
+    "@ant-design-vue/pro-layout": "^1.0.8",
+    "@antv/data-set": "^0.10.2",
+    "ant-design-vue": "^1.7.6",
+    "axios": ">=0.21.1",
+    "core-js": "^3.1.2",
+    "enquire.js": "^2.1.6",
+    "lodash.clonedeep": "^4.5.0",
+    "lodash.get": "^4.4.2",
+    "lodash.pick": "^4.4.0",
+    "md5": "^2.2.1",
+    "mockjs2": "1.0.8",
+    "moment": "^2.24.0",
+    "nprogress": "^0.2.0",
+    "store": "^2.0.12",
+    "viser-vue": "^2.4.6",
+    "vue": "^2.6.14",
+    "vue-clipboard2": "^0.2.1",
+    "vue-cropper": "0.4.9",
+    "vue-i18n": "^8.17.4",
+    "vue-quill-editor": "^3.0.6",
+    "vue-router": "^3.5.2",
+    "vue-svg-component-runtime": "^1.0.1",
+    "vuex": "^3.1.1",
+    "wangeditor": "^3.1.1"
+  },
+  "devDependencies": {
+    "@ant-design/colors": "^3.2.1",
+    "@vue/cli-plugin-babel": "^4.0.4",
+    "@vue/cli-plugin-eslint": "^4.0.4",
+    "@vue/cli-plugin-router": "^4.0.4",
+    "@vue/cli-plugin-unit-jest": "^4.0.4",
+    "@vue/cli-plugin-vuex": "^4.0.4",
+    "@vue/cli-service": "^4.0.4",
+    "@vue/eslint-config-standard": "^4.0.0",
+    "@vue/test-utils": "^1.0.0-beta.29",
+    "babel-eslint": "^10.0.1",
+    "babel-plugin-import": "^1.12.2",
+    "babel-plugin-transform-remove-console": "^6.9.4",
+    "eslint": "^5.16.0",
+    "eslint-plugin-html": "^5.0.0",
+    "eslint-plugin-vue": "^5.2.3",
+    "git-revision-webpack-plugin": "^3.0.6",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "vue-svg-icon-loader": "^2.1.1",
+    "vue-template-compiler": "^2.6.14",
+    "webpack-theme-color-replacer": "^1.3.12"
+  }
+}

+ 5 - 0
postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {}
+  }
+}

BIN
public/avatar2.jpg


+ 34 - 0
public/index.html

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>logo.png">
+    <title>Ant Design Pro</title>
+    <style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
+    <!-- require cdn assets css -->
+    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
+    <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
+    <% } %>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app">
+      <div class="first-loading-wrp">
+        <h1>Pro</h1>
+        <div class="loading-wrp">
+          <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
+        </div>
+        <div style="display: flex; justify-content: center; align-items: center;">Ant Design</div>
+      </div>
+    </div>
+    <!-- require cdn assets js -->
+    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
+    <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
+    <% } %>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

BIN
public/logo.png


+ 28 - 0
src/App.vue

@@ -0,0 +1,28 @@
+<template>
+  <a-config-provider :locale="locale">
+    <div id="app">
+      <router-view/>
+    </div>
+  </a-config-provider>
+</template>
+
+<script>
+import { domTitle, setDocumentTitle } from '@/utils/domUtil'
+import { i18nRender } from '@/locales'
+
+export default {
+  data () {
+    return {
+    }
+  },
+  computed: {
+    locale () {
+      // 只是为了切换语言时,更新标题
+      const { title } = this.$route.meta
+      title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`))
+
+      return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale
+    }
+  }
+}
+</script>

+ 80 - 0
src/api/login.js

@@ -0,0 +1,80 @@
+import request from '@/utils/request'
+
+const userApi = {
+  Login: '/auth/login',
+  Logout: '/auth/logout',
+  ForgePassword: '/auth/forge-password',
+  Register: '/auth/register',
+  twoStepCode: '/auth/2step-code',
+  SendSms: '/account/sms',
+  SendSmsErr: '/account/sms_err',
+  // get my info
+  UserInfo: '/user/info',
+  UserMenu: '/user/nav'
+}
+
+/**
+ * login func
+ * parameter: {
+ *     username: '',
+ *     password: '',
+ *     remember_me: true,
+ *     captcha: '12345'
+ * }
+ * @param parameter
+ * @returns {*}
+ */
+export function login (parameter) {
+  return request({
+    url: userApi.Login,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getSmsCaptcha (parameter) {
+  return request({
+    url: userApi.SendSms,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getInfo () {
+  return request({
+    url: userApi.UserInfo,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+export function getCurrentUserNav () {
+  return request({
+    url: userApi.UserMenu,
+    method: 'get'
+  })
+}
+
+export function logout () {
+  return request({
+    url: userApi.Logout,
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
+ * get user 2step code open?
+ * @param parameter {*}
+ */
+export function get2step (parameter) {
+  return request({
+    url: userApi.twoStepCode,
+    method: 'post',
+    data: parameter
+  })
+}

+ 70 - 0
src/api/manage.js

@@ -0,0 +1,70 @@
+import request from '@/utils/request'
+
+const api = {
+  user: '/user',
+  role: '/role',
+  service: '/service',
+  permission: '/permission',
+  permissionNoPager: '/permission/no-pager',
+  orgTree: '/org/tree'
+}
+
+export default api
+
+export function getUserList (parameter) {
+  return request({
+    url: api.user,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getRoleList (parameter) {
+  return request({
+    url: api.role,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getServiceList (parameter) {
+  return request({
+    url: api.service,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getPermissions (parameter) {
+  return request({
+    url: api.permissionNoPager,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getOrgTree (parameter) {
+  return request({
+    url: api.orgTree,
+    method: 'get',
+    params: parameter
+  })
+}
+
+// id == 0 add     post
+// id != 0 update  put
+export function saveService (parameter) {
+  return request({
+    url: api.service,
+    method: parameter.id === 0 ? 'post' : 'put',
+    data: parameter
+  })
+}
+
+export function saveSub (sub) {
+  return request({
+    url: '/sub',
+    method: sub.id === 0 ? 'post' : 'put',
+    data: sub
+  })
+}

+ 69 - 0
src/assets/background.svg

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 21</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
+            <g id="Group-21" transform="translate(77.000000, 73.000000)">
+                <g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
+                    <ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
+                    <ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
+                    <path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
+                    <path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
+                    <path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+                    <path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
+                    <g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
+                        <ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
+                        <path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
+                    </g>
+                </g>
+                <g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
+                    <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
+                    <ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
+                    <ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
+                    <ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
+                    <path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
+                    <g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
+                        <ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
+                        <path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
+                    </g>
+                    <ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
+                    <ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
+                    <ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
+                    <path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
+                </g>
+                <g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
+                    <ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
+                    <g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
+                        <ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
+                        <path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
+                    </g>
+                    <path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
+                    <ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
+                    <ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
+                    <path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
+                </g>
+                <g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
+                    <g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
+                        <circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
+                        <path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
+                    </g>
+                    <circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
+                    <path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
+                    <path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
+                    <path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
+                    <path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
+                    <circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
+                    <circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
+                    <circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
+                    <circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
+                    <circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 1 - 0
src/assets/icons/bx-analyse.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1551058675966" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M85.333333 512h85.333334a340.736 340.736 0 0 1 99.712-241.621333 337.493333 337.493333 0 0 1 108.458666-72.96 346.453333 346.453333 0 0 1 261.546667-1.749334A106.154667 106.154667 0 0 0 746.666667 298.666667C805.802667 298.666667 853.333333 251.136 853.333333 192S805.802667 85.333333 746.666667 85.333333c-29.397333 0-55.978667 11.776-75.221334 30.933334-103.722667-41.514667-222.848-40.874667-325.76 2.517333a423.594667 423.594667 0 0 0-135.68 91.264 423.253333 423.253333 0 0 0-91.306666 135.637333A426.88 426.88 0 0 0 85.333333 512z m741.248 133.205333c-17.109333 40.618667-41.685333 77.141333-72.96 108.416s-67.797333 55.850667-108.458666 72.96a346.453333 346.453333 0 0 1-261.546667 1.749334A106.154667 106.154667 0 0 0 277.333333 725.333333C218.197333 725.333333 170.666667 772.864 170.666667 832S218.197333 938.666667 277.333333 938.666667c29.397333 0 55.978667-11.776 75.221334-30.933334A425.173333 425.173333 0 0 0 512 938.666667a425.941333 425.941333 0 0 0 393.258667-260.352A426.325333 426.325333 0 0 0 938.666667 512h-85.333334a341.034667 341.034667 0 0 1-26.752 133.205333z" p-id="7873"></path><path d="M512 318.378667c-106.752 0-193.621333 86.869333-193.621333 193.621333S405.248 705.621333 512 705.621333s193.621333-86.869333 193.621333-193.621333S618.752 318.378667 512 318.378667z m0 301.909333c-59.690667 0-108.288-48.597333-108.288-108.288S452.309333 403.712 512 403.712s108.288 48.597333 108.288 108.288-48.597333 108.288-108.288 108.288z" p-id="7874"></path></svg>

+ 29 - 0
src/assets/logo.svg

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
+  <title>Vue</title>
+  <desc>Created with Sketch.</desc>
+  <defs>
+    <linearGradient x1="69.644116%" y1="0%" x2="69.644116%" y2="100%" id="linearGradient-1">
+      <stop stop-color="#29CDFF" offset="0%"></stop>
+      <stop stop-color="#148EFF" offset="37.8600687%"></stop>
+      <stop stop-color="#0A60FF" offset="100%"></stop>
+    </linearGradient>
+    <linearGradient x1="-19.8191553%" y1="-36.7931464%" x2="138.57919%" y2="157.637507%" id="linearGradient-2">
+      <stop stop-color="#29CDFF" offset="0%"></stop>
+      <stop stop-color="#0F78FF" offset="100%"></stop>
+    </linearGradient>
+    <linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-3">
+      <stop stop-color="#FA8E7D" offset="0%"></stop>
+      <stop stop-color="#F74A5C" offset="51.2635191%"></stop>
+      <stop stop-color="#F51D2C" offset="100%"></stop>
+    </linearGradient>
+  </defs>
+  <g id="Vue" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="Group" transform="translate(19.000000, 9.000000)">
+      <path d="M89.96,90.48 C78.58,93.48 68.33,83.36 67.62,82.48 L46.6604487,62.2292258 C45.5023849,61.1103236 44.8426845,59.5728835 44.8296987,57.9626396 L44.5035564,17.5209948 C44.4948861,16.4458744 44.0537714,15.4195095 43.2796864,14.6733517 L29.6459999,1.53153737 C28.055475,-0.00160504005 25.5232423,0.0449126588 23.9900999,1.63543756 C23.2715121,2.38092066 22.87,3.37600834 22.87,4.41143746 L22.87,64.3864751 C22.87,67.0807891 23.9572233,69.6611067 25.885409,71.5429748 L63.6004615,108.352061 C65.9466323,110.641873 69.6963584,110.624605 72.0213403,108.313281" id="Path-Copy" fill="url(#linearGradient-1)" fill-rule="nonzero" transform="translate(56.415000, 54.831157) scale(-1, 1) translate(-56.415000, -54.831157) "></path>
+      <path d="M68,90.1163122 C56.62,93.1163122 45.46,83.36 44.75,82.48 L23.7904487,62.2292258 C22.6323849,61.1103236 21.9726845,59.5728835 21.9596987,57.9626396 L21.6335564,17.5209948 C21.6248861,16.4458744 21.1837714,15.4195095 20.4096864,14.6733517 L6.7759999,1.53153737 C5.185475,-0.00160504005 2.65324232,0.0449126588 1.12009991,1.63543756 C0.401512125,2.38092066 3.90211878e-13,3.37600834 3.90798505e-13,4.41143746 L3.94351218e-13,64.3864751 C3.94681177e-13,67.0807891 1.08722326,69.6611067 3.01540903,71.5429748 L40.7807092,108.401101 C43.1069304,110.671444 46.8180151,110.676525 49.1504445,108.412561" id="Path" fill="url(#linearGradient-2)" fill-rule="nonzero"></path>
+      <path d="M43.2983488,19.0991931 L27.5566079,3.88246244 C26.7624281,3.11476967 26.7409561,1.84862177 27.5086488,1.05444194 C27.8854826,0.664606611 28.4044438,0.444472651 28.9466386,0.444472651 L60.3925021,0.444472651 C61.4970716,0.444472651 62.3925021,1.33990315 62.3925021,2.44447265 C62.3925021,2.9858375 62.1730396,3.50407742 61.7842512,3.88079942 L46.0801285,19.0975301 C45.3051579,19.8484488 44.0742167,19.8491847 43.2983488,19.0991931 Z" id="Path" fill="url(#linearGradient-3)"></path>
+    </g>
+  </g>
+</svg>

+ 89 - 0
src/components/ArticleListContent/ArticleListContent.vue

@@ -0,0 +1,89 @@
+<template>
+  <div class="antd-pro-components-article-list-content-index-listContent">
+    <div class="description">
+      <slot>
+        {{ description }}
+      </slot>
+    </div>
+    <div class="extra">
+      <a-avatar :src="avatar" size="small" />
+      <a :href="href">{{ owner }}</a> 发布在 <a :href="href">{{ href }}</a>
+      <em>{{ updateAt | moment }}</em>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ArticleListContent',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'antd-pro-components-article-list-content-index-listContent'
+    },
+    description: {
+      type: String,
+      default: ''
+    },
+    owner: {
+      type: String,
+      required: true
+    },
+    avatar: {
+      type: String,
+      required: true
+    },
+    href: {
+      type: String,
+      required: true
+    },
+    updateAt: {
+      type: String,
+      required: true
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+@import '../index.less';
+
+.antd-pro-components-article-list-content-index-listContent {
+  .description {
+    max-width: 720px;
+    line-height: 22px;
+  }
+  .extra {
+    margin-top: 16px;
+    color: @text-color-secondary;
+    line-height: 22px;
+
+    & /deep/ .ant-avatar {
+      position: relative;
+      top: 1px;
+      width: 20px;
+      height: 20px;
+      margin-right: 8px;
+      vertical-align: top;
+    }
+
+    & > em {
+      margin-left: 16px;
+      color: @disabled-color;
+      font-style: normal;
+    }
+  }
+}
+
+@media screen and (max-width: @screen-xs) {
+  .antd-pro-components-article-list-content-index-listContent {
+    .extra {
+      & > em {
+        display: block;
+        margin-top: 8px;
+        margin-left: 0;
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
src/components/ArticleListContent/index.js

@@ -0,0 +1,3 @@
+import ArticleListContent from './ArticleListContent'
+
+export default ArticleListContent

+ 25 - 0
src/components/AvatarList/Item.jsx

@@ -0,0 +1,25 @@
+import PropTypes from 'ant-design-vue/es/_util/vue-types'
+import { Tooltip, Avatar } from 'ant-design-vue'
+import { getSlotOptions } from 'ant-design-vue/lib/_util/props-util'
+import { warning } from 'ant-design-vue/lib/vc-util/warning'
+
+export const AvatarListItemProps = {
+  tips: PropTypes.string,
+  src: PropTypes.string.def('')
+}
+
+const Item = {
+  __ANT_AVATAR_CHILDREN: true,
+  name: 'AvatarListItem',
+  props: AvatarListItemProps,
+  created () {
+    warning(getSlotOptions(this.$parent).__ANT_AVATAR_LIST, 'AvatarListItem must be a subcomponent of AvatarList')
+  },
+  render () {
+    const size = this.$parent.size === 'mini' ? 'small' : this.$parent.size
+    const AvatarDom = <Avatar size={size || 'small'} src={this.src} />
+    return (this.tips && <Tooltip title={this.tips}>{AvatarDom}</Tooltip>) || <AvatarDom />
+  }
+}
+
+export default Item

+ 72 - 0
src/components/AvatarList/List.jsx

@@ -0,0 +1,72 @@
+import './index.less'
+
+import PropTypes from 'ant-design-vue/es/_util/vue-types'
+import Avatar from 'ant-design-vue/es/avatar'
+import Item from './Item.jsx'
+import { filterEmpty } from '@/components/_util/util'
+
+/**
+ * size: `number`、 `large`、`small`、`default` 默认值: default
+ * maxLength: number
+ * excessItemsStyle: CSSProperties
+ */
+const AvatarListProps = {
+  prefixCls: PropTypes.string.def('ant-pro-avatar-list'),
+  size: {
+    validator: val => {
+      return typeof val === 'number' || ['small', 'large', 'default'].includes(val)
+    },
+    default: 'default'
+  },
+  maxLength: PropTypes.number.def(0),
+  excessItemsStyle: PropTypes.object.def({
+    color: '#f56a00',
+    backgroundColor: '#fde3cf'
+  })
+}
+
+const AvatarList = {
+  __ANT_AVATAR_LIST: true,
+  Item,
+  name: 'AvatarList',
+  props: AvatarListProps,
+  render (h) {
+    const { prefixCls, size } = this.$props
+    const className = {
+      [`${prefixCls}`]: true,
+      [`${size}`]: true
+    }
+
+    const items = filterEmpty(this.$slots.default)
+    const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{this.getItems(items)}</ul> : null
+    return (
+      <div class={className}>
+        {itemsDom}
+      </div>
+    )
+  },
+  methods: {
+    getItems (items) {
+      const className = {
+        [`${this.prefixCls}-item`]: true,
+        [`${this.size}`]: true
+      }
+      const totalSize = items.length
+
+      if (this.maxLength > 0) {
+        items = items.slice(0, this.maxLength)
+        items.push((<Avatar size={this.size === 'mini' ? 'small' : this.size} style={this.excessItemsStyle}>{`+${totalSize - this.maxLength}`}</Avatar>))
+      }
+      return items.map((item) => (
+        <li class={className}>{item}</li>
+      ))
+    }
+  }
+}
+
+AvatarList.install = function (Vue) {
+  Vue.component(AvatarList.name, AvatarList)
+  Vue.component(AvatarList.Item.name, AvatarList.Item)
+}
+
+export default AvatarList

+ 9 - 0
src/components/AvatarList/index.js

@@ -0,0 +1,9 @@
+import AvatarList from './List'
+import Item from './Item'
+
+export {
+  AvatarList,
+  Item as AvatarListItem
+}
+
+export default AvatarList

+ 60 - 0
src/components/AvatarList/index.less

@@ -0,0 +1,60 @@
+@import "../index";
+
+@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
+@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
+
+.@{avatar-list-prefix-cls} {
+  display: inline-block;
+
+  ul {
+    list-style: none;
+    display: inline-block;
+    padding: 0;
+    margin: 0 0 0 8px;
+    font-size: 0;
+  }
+}
+
+.@{avatar-list-item-prefix-cls} {
+  display: inline-block;
+  font-size: @font-size-base;
+  margin-left: -8px;
+  width: @avatar-size-base;
+  height: @avatar-size-base;
+
+  :global {
+    .ant-avatar {
+      border: 1px solid #fff;
+      cursor: pointer;
+    }
+  }
+
+  &.large {
+    width: @avatar-size-lg;
+    height: @avatar-size-lg;
+  }
+
+  &.small {
+    width: @avatar-size-sm;
+    height: @avatar-size-sm;
+  }
+
+  &.mini {
+    width: 20px;
+    height: 20px;
+
+    :global {
+      .ant-avatar {
+        width: 20px;
+        height: 20px;
+        line-height: 20px;
+
+        .ant-avatar-string {
+          font-size: 12px;
+          line-height: 18px;
+        }
+      }
+    }
+  }
+}
+

+ 64 - 0
src/components/AvatarList/index.md

@@ -0,0 +1,64 @@
+# AvatarList 用户头像列表
+
+
+一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
+
+
+
+引用方式:
+
+```javascript
+import AvatarList from '@/components/AvatarList'
+const AvatarListItem = AvatarList.Item
+
+export default {
+    components: {
+        AvatarList,
+        AvatarListItem
+    }
+}
+```
+
+
+
+## 代码演示  [demo](https://pro.loacg.com/test/home)
+
+```html
+<avatar-list size="mini">
+    <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
+    <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+</avatar-list>
+```
+或
+```html
+<avatar-list :max-length="3">
+    <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
+    <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+    <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+</avatar-list>
+```
+
+
+
+## API
+
+### AvatarList
+
+| 参数               | 说明       | 类型                                 | 默认值       |
+| ---------------- | -------- | ---------------------------------- | --------- |
+| size             | 头像大小     | `large`、`small` 、`mini`, `default` | `default` |
+| maxLength        | 要显示的最大项目 | number                             | -         |
+| excessItemsStyle | 多余的项目风格  | CSSProperties                      | -         |
+
+### AvatarList.Item
+
+| 参数   | 说明     | 类型        | 默认值 |
+| ---- | ------ | --------- | --- |
+| tips | 头像展示文案 | string | -   |
+| src  | 头像图片连接 | string    | -   |
+

+ 62 - 0
src/components/Charts/Bar.vue

@@ -0,0 +1,62 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart
+      height="254"
+      :data="data"
+      :forceFit="true"
+      :padding="['auto', 'auto', '40', '50']">
+      <v-tooltip />
+      <v-axis />
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Bar',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    data: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    scale: {
+      type: Array,
+      default: () => {
+        return [{
+          dataKey: 'x',
+          min: 2
+        }, {
+          dataKey: 'y',
+          title: '时间',
+          min: 1,
+          max: 22
+        }]
+      }
+    },
+    tooltip: {
+      type: Array,
+      default: () => {
+        return [
+          'x*y',
+          (x, y) => ({
+            name: x,
+            value: y
+          })
+        ]
+      }
+    }
+  },
+  data () {
+    return {
+    }
+  }
+}
+</script>

+ 120 - 0
src/components/Charts/ChartCard.vue

@@ -0,0 +1,120 @@
+<template>
+  <a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
+    <div class="chart-card-header">
+      <div class="meta">
+        <span class="chart-card-title">
+          <slot name="title">
+            {{ title }}
+          </slot>
+        </span>
+        <span class="chart-card-action">
+          <slot name="action"></slot>
+        </span>
+      </div>
+      <div class="total">
+        <slot name="total">
+          <span>{{ typeof total === 'function' && total() || total }}</span>
+        </slot>
+      </div>
+    </div>
+    <div class="chart-card-content">
+      <div class="content-fix">
+        <slot></slot>
+      </div>
+    </div>
+    <div class="chart-card-footer">
+      <div class="field">
+        <slot name="footer"></slot>
+      </div>
+    </div>
+  </a-card>
+</template>
+
+<script>
+export default {
+  name: 'ChartCard',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    total: {
+      type: [Function, Number, String],
+      required: false,
+      default: null
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  .chart-card-header {
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+
+    .meta {
+      position: relative;
+      overflow: hidden;
+      width: 100%;
+      color: rgba(0, 0, 0, .45);
+      font-size: 14px;
+      line-height: 22px;
+    }
+  }
+
+  .chart-card-action {
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    right: 0;
+  }
+
+  .chart-card-footer {
+    border-top: 1px solid #e8e8e8;
+    padding-top: 9px;
+    margin-top: 8px;
+
+    > * {
+      position: relative;
+    }
+
+    .field {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin: 0;
+    }
+  }
+
+  .chart-card-content {
+    margin-bottom: 12px;
+    position: relative;
+    height: 46px;
+    width: 100%;
+
+    .content-fix {
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      width: 100%;
+    }
+  }
+
+  .total {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+    color: #000;
+    margin-top: 4px;
+    margin-bottom: 0;
+    font-size: 30px;
+    line-height: 38px;
+    height: 38px;
+  }
+</style>

+ 67 - 0
src/components/Charts/Liquid.vue

@@ -0,0 +1,67 @@
+<template>
+  <div>
+    <v-chart
+      :forceFit="true"
+      :height="height"
+      :width="width"
+      :data="data"
+      :scale="scale"
+      :padding="0">
+      <v-tooltip />
+      <v-interval
+        :shape="['liquid-fill-gauge']"
+        position="transfer*value"
+        color=""
+        :v-style="{
+          lineWidth: 10,
+          opacity: 0.75
+        }"
+        :tooltip="[
+          'transfer*value',
+          (transfer, value) => {
+            return {
+              name: transfer,
+              value,
+            };
+          },
+        ]"
+      ></v-interval>
+      <v-guide
+        v-for="(row, index) in data"
+        :key="index"
+        type="text"
+        :top="true"
+        :position="{
+          gender: row.transfer,
+          value: 45
+        }"
+        :content="row.value + '%'"
+        :v-style="{
+          fontSize: 100,
+          textAlign: 'center',
+          opacity: 0.75,
+        }"
+      />
+    </v-chart>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Liquid',
+  props: {
+    height: {
+      type: Number,
+      default: 0
+    },
+    width: {
+      type: Number,
+      default: 0
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 56 - 0
src/components/Charts/MiniArea.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="antv-chart-mini">
+    <div class="chart-wrapper" :style="{ height: 46 }">
+      <v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 0, 18, 0]">
+        <v-tooltip />
+        <v-smooth-area position="x*y" />
+      </v-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+import moment from 'moment'
+const data = []
+const beginDay = new Date().getTime()
+
+for (let i = 0; i < 10; i++) {
+  data.push({
+    x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+    y: Math.round(Math.random() * 10)
+  })
+}
+
+const tooltip = [
+  'x*y',
+  (x, y) => ({
+    name: x,
+    value: y
+  })
+]
+const scale = [{
+  dataKey: 'x',
+  min: 2
+}, {
+  dataKey: 'y',
+  title: '时间',
+  min: 1,
+  max: 22
+}]
+
+export default {
+  name: 'MiniArea',
+  data () {
+    return {
+      data,
+      tooltip,
+      scale,
+      height: 100
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  @import "chart";
+</style>

+ 57 - 0
src/components/Charts/MiniBar.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="antv-chart-mini">
+    <div class="chart-wrapper" :style="{ height: 46 }">
+      <v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]">
+        <v-tooltip />
+        <v-bar position="x*y" />
+      </v-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+import moment from 'moment'
+const data = []
+const beginDay = new Date().getTime()
+
+for (let i = 0; i < 10; i++) {
+  data.push({
+    x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+    y: Math.round(Math.random() * 10)
+  })
+}
+
+const tooltip = [
+  'x*y',
+  (x, y) => ({
+    name: x,
+    value: y
+  })
+]
+
+const scale = [{
+  dataKey: 'x',
+  min: 2
+}, {
+  dataKey: 'y',
+  title: '时间',
+  min: 1,
+  max: 30
+}]
+
+export default {
+  name: 'MiniBar',
+  data () {
+    return {
+      data,
+      tooltip,
+      scale,
+      height: 100
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  @import "chart";
+</style>

+ 75 - 0
src/components/Charts/MiniProgress.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="chart-mini-progress">
+    <div class="target" :style="{ left: target + '%'}">
+      <span :style="{ backgroundColor: color }" />
+      <span :style="{ backgroundColor: color }"/>
+    </div>
+    <div class="progress-wrapper">
+      <div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height }"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MiniProgress',
+  props: {
+    target: {
+      type: Number,
+      default: 0
+    },
+    height: {
+      type: String,
+      default: '10px'
+    },
+    color: {
+      type: String,
+      default: '#13C2C2'
+    },
+    percentage: {
+      type: Number,
+      default: 0
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  .chart-mini-progress {
+    padding: 5px 0;
+    position: relative;
+    width: 100%;
+
+    .target {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+
+      span {
+        border-radius: 100px;
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 4px;
+        width: 2px;
+
+        &:last-child {
+          top: auto;
+          bottom: 0;
+        }
+      }
+    }
+    .progress-wrapper {
+      background-color: #f5f5f5;
+      position: relative;
+
+      .progress {
+        transition: all .4s cubic-bezier(.08,.82,.17,1) 0s;
+        border-radius: 1px 0 0 1px;
+        background-color: #1890ff;
+        width: 0;
+        height: 100%;
+      }
+    }
+  }
+</style>

+ 40 - 0
src/components/Charts/MiniSmoothArea.vue

@@ -0,0 +1,40 @@
+<template>
+  <div :class="prefixCls">
+    <div class="chart-wrapper" :style="{ height: 46 }">
+      <v-chart :force-fit="true" :height="100" :data="dataSource" :scale="scale" :padding="[36, 0, 18, 0]">
+        <v-tooltip />
+        <v-smooth-line position="x*y" :size="2" />
+        <v-smooth-area position="x*y" />
+      </v-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MiniSmoothArea',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-smooth-area'
+    },
+    scale: {
+      type: [Object, Array],
+      required: true
+    },
+    dataSource: {
+      type: Array,
+      required: true
+    }
+  },
+  data () {
+    return {
+      height: 100
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  @import "smooth.area.less";
+</style>

+ 68 - 0
src/components/Charts/Radar.vue

@@ -0,0 +1,68 @@
+<template>
+  <v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
+    <v-tooltip></v-tooltip>
+    <v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" />
+    <v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" />
+    <v-legend dataKey="user" marker="circle" :offset="30" />
+    <v-coord type="polar" radius="0.8" />
+    <v-line position="item*score" color="user" :size="2" />
+    <v-point position="item*score" color="user" :size="4" shape="circle" />
+  </v-chart>
+</template>
+
+<script>
+const axis1Opts = {
+  dataKey: 'item',
+  line: null,
+  tickLine: null,
+  grid: {
+    lineStyle: {
+      lineDash: null
+    },
+    hideFirstLine: false
+  }
+}
+const axis2Opts = {
+  dataKey: 'score',
+  line: null,
+  tickLine: null,
+  grid: {
+    type: 'polygon',
+    lineStyle: {
+      lineDash: null
+    }
+  }
+}
+
+const scale = [
+  {
+    dataKey: 'score',
+    min: 0,
+    max: 80
+  }, {
+    dataKey: 'user',
+    alias: '类型'
+  }
+]
+
+export default {
+  name: 'Radar',
+  props: {
+    data: {
+      type: Array,
+      default: null
+    }
+  },
+  data () {
+    return {
+      axis1Opts,
+      axis2Opts,
+      scale
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 77 - 0
src/components/Charts/RankList.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="rank">
+    <h4 class="title">{{ title }}</h4>
+    <ul class="list">
+      <li :key="index" v-for="(item, index) in list">
+        <span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
+        <span>{{ item.name }}</span>
+        <span>{{ item.total }}</span>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RankList',
+  // ['title', 'list']
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    list: {
+      type: Array,
+      default: null
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+  .rank {
+    padding: 0 32px 32px 72px;
+
+    .list {
+      margin: 25px 0 0;
+      padding: 0;
+      list-style: none;
+
+      li {
+        margin-top: 16px;
+
+        span {
+          color: rgba(0, 0, 0, .65);
+          font-size: 14px;
+          line-height: 22px;
+
+          &:first-child {
+            background-color: #f5f5f5;
+            border-radius: 20px;
+            display: inline-block;
+            font-size: 12px;
+            font-weight: 600;
+            margin-right: 24px;
+            height: 20px;
+            line-height: 20px;
+            width: 20px;
+            text-align: center;
+          }
+          &.active {
+            background-color: #314659;
+            color: #fff;
+          }
+          &:last-child {
+            float: right;
+          }
+        }
+      }
+    }
+  }
+
+  .mobile .rank {
+    padding: 0 32px 32px 32px;
+  }
+
+</style>

+ 113 - 0
src/components/Charts/TagCloud.vue

@@ -0,0 +1,113 @@
+<template>
+  <v-chart :width="width" :height="height" :padding="[0]" :data="data" :scale="scale">
+    <v-tooltip :show-title="false" />
+    <v-coord type="rect" direction="TL" />
+    <v-point position="x*y" color="category" shape="cloud" tooltip="value*category" />
+  </v-chart>
+</template>
+
+<script>
+import { registerShape } from 'viser-vue'
+const DataSet = require('@antv/data-set')
+
+const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'
+
+const scale = [
+  { dataKey: 'x', nice: false },
+  { dataKey: 'y', nice: false }
+]
+
+registerShape('point', 'cloud', {
+  draw (cfg, container) {
+    return container.addShape('text', {
+      attrs: {
+        fillOpacity: cfg.opacity,
+        fontSize: cfg.origin._origin.size,
+        rotate: cfg.origin._origin.rotate,
+        text: cfg.origin._origin.text,
+        textAlign: 'center',
+        fontFamily: cfg.origin._origin.font,
+        fill: cfg.color,
+        textBaseline: 'Alphabetic',
+        ...cfg.style,
+        x: cfg.x,
+        y: cfg.y
+      }
+    })
+  }
+})
+
+export default {
+  name: 'TagCloud',
+  props: {
+    tagList: {
+      type: Array,
+      required: true
+    },
+    height: {
+      type: Number,
+      default: 400
+    },
+    width: {
+      type: Number,
+      default: 640
+    }
+  },
+  data () {
+    return {
+      data: [],
+      scale
+    }
+  },
+  watch: {
+    tagList: function (val) {
+      if (val.length > 0) {
+        this.initTagCloud(val)
+      }
+    }
+  },
+  mounted () {
+    if (this.tagList.length > 0) {
+      this.initTagCloud(this.tagList)
+    }
+  },
+  methods: {
+    initTagCloud (dataSource) {
+      const { height, width } = this
+
+      const dv = new DataSet.View().source(dataSource)
+      const range = dv.range('value')
+      const min = range[0]
+      const max = range[1]
+      const imageMask = new Image()
+      imageMask.crossOrigin = ''
+      imageMask.src = imgUrl
+      imageMask.onload = () => {
+        dv.transform({
+          type: 'tag-cloud',
+          fields: ['name', 'value'],
+          size: [width, height],
+          imageMask,
+          font: 'Verdana',
+          padding: 0,
+          timeInterval: 5000, // max execute time
+          rotate () {
+            let random = ~~(Math.random() * 4) % 4
+            if (random === 2) {
+              random = 0
+            }
+            return random * 90 // 0, 90, 270
+          },
+          fontSize (d) {
+            if (d.value) {
+              return ((d.value - min) / (max - min)) * (32 - 8) + 8
+            }
+            return 0
+          }
+        })
+        this.data = dv.rows
+      }
+    }
+  }
+}
+</script>

+ 64 - 0
src/components/Charts/TransferBar.vue

@@ -0,0 +1,64 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart
+      height="254"
+      :data="data"
+      :scale="scale"
+      :forceFit="true"
+      :padding="['auto', 'auto', '40', '50']">
+      <v-tooltip />
+      <v-axis />
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+const tooltip = [
+  'x*y',
+  (x, y) => ({
+    name: x,
+    value: y
+  })
+]
+const scale = [{
+  dataKey: 'x',
+  title: '日期(天)',
+  alias: '日期(天)',
+  min: 2
+}, {
+  dataKey: 'y',
+  title: '流量(Gb)',
+  alias: '流量(Gb)',
+  min: 1
+}]
+
+export default {
+  name: 'Bar',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  data () {
+    return {
+      data: [],
+      scale,
+      tooltip
+    }
+  },
+  created () {
+    this.getMonthBar()
+  },
+  methods: {
+    getMonthBar () {
+      this.$http.get('/analysis/month-bar')
+        .then(res => {
+          this.data = res.result
+        })
+    }
+  }
+}
+</script>

+ 82 - 0
src/components/Charts/Trend.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="chart-trend">
+    {{ term }}
+    <span>{{ rate }}%</span>
+    <span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Trend',
+  props: {
+    term: {
+      type: String,
+      default: '',
+      required: true
+    },
+    percentage: {
+      type: Number,
+      default: null
+    },
+    type: {
+      type: Boolean,
+      default: null
+    },
+    target: {
+      type: Number,
+      default: 0
+    },
+    value: {
+      type: Number,
+      default: 0
+    },
+    fixed: {
+      type: Number,
+      default: 2
+    }
+  },
+  data () {
+    return {
+      trend: this.type && 'up' || 'down',
+      rate: this.percentage
+    }
+  },
+  created () {
+    const type = this.type === null ? this.value >= this.target : this.type
+    this.trend = type ? 'up' : 'down'
+    this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  .chart-trend {
+    display: inline-block;
+    font-size: 14px;
+    line-height: 22px;
+
+    .trend-icon {
+      font-size: 12px;
+
+      &.up, &.down {
+        margin-left: 4px;
+        position: relative;
+        top: 1px;
+
+        i {
+          font-size: 12px;
+          transform: scale(.83);
+        }
+      }
+
+      &.up {
+        color: #f5222d;
+      }
+      &.down {
+        color: #52c41a;
+        top: -1px;
+      }
+    }
+  }
+</style>

+ 13 - 0
src/components/Charts/chart.less

@@ -0,0 +1,13 @@
+.antv-chart-mini {
+  position: relative;
+  width: 100%;
+
+  .chart-wrapper {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+
+/*    margin: 0 -5px;
+    overflow: hidden;*/
+  }
+}

+ 14 - 0
src/components/Charts/smooth.area.less

@@ -0,0 +1,14 @@
+@import "../index";
+
+@smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area";
+
+.@{smoothArea-prefix-cls} {
+    position: relative;
+    width: 100%;
+
+    .chart-wrapper {
+        position: absolute;
+        bottom: -28px;
+        width: 100%;
+    }
+}

+ 113 - 0
src/components/Dialog.js

@@ -0,0 +1,113 @@
+import Modal from 'ant-design-vue/es/modal'
+export default (Vue) => {
+  function dialog (component, componentProps, modalProps) {
+    const _vm = this
+    modalProps = modalProps || {}
+    if (!_vm || !_vm._isVue) {
+      return
+    }
+    let dialogDiv = document.querySelector('body>div[type=dialog]')
+    if (!dialogDiv) {
+      dialogDiv = document.createElement('div')
+      dialogDiv.setAttribute('type', 'dialog')
+      document.body.appendChild(dialogDiv)
+    }
+
+    const handle = function (checkFunction, afterHandel) {
+      if (checkFunction instanceof Function) {
+        const res = checkFunction()
+        if (res instanceof Promise) {
+          res.then(c => {
+            c && afterHandel()
+          })
+        } else {
+          res && afterHandel()
+        }
+      } else {
+        // checkFunction && afterHandel()
+        checkFunction || afterHandel()
+      }
+    }
+
+    const dialogInstance = new Vue({
+      data () {
+        return {
+          visible: true
+        }
+      },
+      router: _vm.$router,
+      store: _vm.$store,
+      mounted () {
+        this.$on('close', (v) => {
+          this.handleClose()
+        })
+      },
+      methods: {
+        handleClose () {
+          handle(this.$refs._component.onCancel, () => {
+            this.visible = false
+            this.$refs._component.$emit('close')
+            this.$refs._component.$emit('cancel')
+            dialogInstance.$destroy()
+          })
+        },
+        handleOk () {
+          handle(this.$refs._component.onOK || this.$refs._component.onOk, () => {
+            this.visible = false
+            this.$refs._component.$emit('close')
+            this.$refs._component.$emit('ok')
+            dialogInstance.$destroy()
+          })
+        }
+      },
+      render: function (h) {
+        const that = this
+        const modalModel = modalProps && modalProps.model
+        if (modalModel) {
+          delete modalProps.model
+        }
+        const ModalProps = Object.assign({}, modalModel && { model: modalModel } || {}, {
+          attrs: Object.assign({}, {
+            ...(modalProps.attrs || modalProps)
+          }, {
+            visible: this.visible
+          }),
+          on: Object.assign({}, {
+            ...(modalProps.on || modalProps)
+          }, {
+            ok: () => {
+              that.handleOk()
+            },
+            cancel: () => {
+              that.handleClose()
+            }
+          })
+        })
+
+        const componentModel = componentProps && componentProps.model
+        if (componentModel) {
+          delete componentProps.model
+        }
+        const ComponentProps = Object.assign({}, componentModel && { model: componentModel } || {}, {
+          ref: '_component',
+          attrs: Object.assign({}, {
+            ...((componentProps && componentProps.attrs) || componentProps)
+          }),
+          on: Object.assign({}, {
+            ...((componentProps && componentProps.on) || componentProps)
+          })
+        })
+
+        return h(Modal, ModalProps, [h(component, ComponentProps)])
+      }
+    }).$mount(dialogDiv)
+  }
+
+  Object.defineProperty(Vue.prototype, '$dialog', {
+    get: () => {
+      return function () {
+        dialog.apply(this, arguments)
+      }
+    }
+  })
+}

+ 83 - 0
src/components/Editor/QuillEditor.vue

@@ -0,0 +1,83 @@
+<template>
+  <div :class="prefixCls">
+    <quill-editor
+      v-model="content"
+      ref="myQuillEditor"
+      :options="editorOption"
+      @blur="onEditorBlur($event)"
+      @focus="onEditorFocus($event)"
+      @ready="onEditorReady($event)"
+      @change="onEditorChange($event)">
+    </quill-editor>
+
+  </div>
+</template>
+
+<script>
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import 'quill/dist/quill.bubble.css'
+
+import { quillEditor } from 'vue-quill-editor'
+
+export default {
+  name: 'QuillEditor',
+  components: {
+    quillEditor
+  },
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-editor-quill'
+    },
+    // 表单校验用字段
+    // eslint-disable-next-line
+    value: {
+      type: String
+    }
+  },
+  data () {
+    return {
+      content: null,
+      editorOption: {
+        // some quill options
+      }
+    }
+  },
+  methods: {
+    onEditorBlur (quill) {
+      console.log('editor blur!', quill)
+    },
+    onEditorFocus (quill) {
+      console.log('editor focus!', quill)
+    },
+    onEditorReady (quill) {
+      console.log('editor ready!', quill)
+    },
+    onEditorChange ({ quill, html, text }) {
+      console.log('editor change!', quill, html, text)
+      this.$emit('change', html)
+    }
+  },
+  watch: {
+    value (val) {
+      this.content = val
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+@import url('../index.less');
+
+/* 覆盖 quill 默认边框圆角为 ant 默认圆角,用于统一 ant 组件风格 */
+.ant-editor-quill {
+  line-height: initial;
+  /deep/ .ql-toolbar.ql-snow {
+    border-radius: @border-radius-base @border-radius-base 0 0;
+  }
+  /deep/ .ql-container.ql-snow {
+    border-radius: 0 0 @border-radius-base @border-radius-base;
+  }
+}
+</style>

+ 57 - 0
src/components/Editor/WangEditor.vue

@@ -0,0 +1,57 @@
+<template>
+  <div :class="prefixCls">
+    <div ref="editor" class="editor-wrapper"></div>
+  </div>
+</template>
+
+<script>
+import WEditor from 'wangeditor'
+
+export default {
+  name: 'WangEditor',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-editor-wang'
+    },
+    // eslint-disable-next-line
+    value: {
+      type: String
+    }
+  },
+  data () {
+    return {
+      editor: null,
+      editorContent: null
+    }
+  },
+  watch: {
+    value (val) {
+      this.editorContent = val
+      this.editor.txt.html(val)
+    }
+  },
+  mounted () {
+    this.initEditor()
+  },
+  methods: {
+    initEditor () {
+      this.editor = new WEditor(this.$refs.editor)
+      // this.editor.onchangeTimeout = 200
+      this.editor.customConfig.onchange = (html) => {
+        this.editorContent = html
+        this.$emit('change', this.editorContent)
+      }
+      this.editor.create()
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.ant-editor-wang {
+  .editor-wrapper {
+    text-align: left;
+  }
+}
+</style>

+ 64 - 0
src/components/Ellipsis/Ellipsis.vue

@@ -0,0 +1,64 @@
+<script>
+import Tooltip from 'ant-design-vue/es/tooltip'
+import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
+/*
+    const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
+
+    const TooltipOverlayStyle = {
+      overflowWrap: 'break-word',
+      wordWrap: 'break-word',
+    };
+  */
+
+export default {
+  name: 'Ellipsis',
+  components: {
+    Tooltip
+  },
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-ellipsis'
+    },
+    tooltip: {
+      type: Boolean
+    },
+    length: {
+      type: Number,
+      required: true
+    },
+    lines: {
+      type: Number,
+      default: 1
+    },
+    fullWidthRecognition: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    getStrDom (str, fullLength) {
+      return (
+        <span>{ cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '') }</span>
+      )
+    },
+    getTooltip (fullStr, fullLength) {
+      return (
+        <Tooltip>
+          <template slot="title">{ fullStr }</template>
+          { this.getStrDom(fullStr, fullLength) }
+        </Tooltip>
+      )
+    }
+  },
+  render () {
+    const { tooltip, length } = this.$props
+    const str = this.$slots.default.map(vNode => vNode.text).join('')
+    const fullLength = getStrFullLength(str)
+    const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength)
+    return (
+      strDom
+    )
+  }
+}
+</script>

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

@@ -0,0 +1,3 @@
+import Ellipsis from './Ellipsis'
+
+export default Ellipsis

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

@@ -0,0 +1,38 @@
+# Ellipsis 文本自动省略号
+
+文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
+
+
+
+引用方式:
+
+```javascript
+import Ellipsis from '@/components/Ellipsis'
+
+export default {
+    components: {
+        Ellipsis
+    }
+}
+```
+
+
+
+## 代码演示  [demo](https://pro.loacg.com/test/home)
+
+```html
+<ellipsis :length="100" tooltip>
+        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.
+</ellipsis>
+```
+
+
+
+## API
+
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+tooltip | 移动到文本展示完整内容的提示 | boolean | -
+length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -

+ 47 - 0
src/components/FooterToolbar/FooterToolBar.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :class="prefixCls" :style="{ width: barWidth, transition: '0.3s all' }">
+    <div style="float: left">
+      <slot name="extra">{{ extra }}</slot>
+    </div>
+    <div style="float: right">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FooterToolBar',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-footer-toolbar'
+    },
+    collapsed: {
+      type: Boolean,
+      default: false
+    },
+    isMobile: {
+      type: Boolean,
+      default: false
+    },
+    siderWidth: {
+      type: Number,
+      default: undefined
+    },
+    extra: {
+      type: [String, Object],
+      default: ''
+    }
+  },
+  computed: {
+    barWidth () {
+      return this.isMobile ? undefined : `calc(100% - ${this.collapsed ? 80 : this.siderWidth || 256}px)`
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+</style>

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

@@ -0,0 +1,4 @@
+import FooterToolBar from './FooterToolBar'
+import './index.less'
+
+export default FooterToolBar

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

@@ -0,0 +1,23 @@
+@import "../index";
+
+@footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar";
+
+.@{footer-toolbar-prefix-cls} {
+  position: fixed;
+  width: 100%;
+  bottom: 0;
+  right: 0;
+  height: 56px;
+  line-height: 56px;
+  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
+  background: #fff;
+  border-top: 1px solid #e8e8e8;
+  padding: 0 24px;
+  z-index: 9;
+
+  &:after {
+    content: "";
+    display: block;
+    clear: both;
+  }
+}

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

@@ -0,0 +1,48 @@
+# FooterToolbar 底部工具栏
+
+固定在底部的工具栏。
+
+
+
+## 何时使用
+
+固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
+
+
+
+引用方式:
+
+```javascript
+import FooterToolBar from '@/components/FooterToolbar'
+
+export default {
+    components: {
+        FooterToolBar
+    }
+}
+```
+
+
+
+## 代码演示
+
+```html
+<footer-tool-bar>
+    <a-button type="primary" @click="validate" :loading="loading">提交</a-button>
+</footer-tool-bar>
+```
+或
+```html
+<footer-tool-bar extra="扩展信息提示">
+    <a-button type="primary" @click="validate" :loading="loading">提交</a-button>
+</footer-tool-bar>
+```
+
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+children (slot) | 工具栏内容,向右对齐 | - | -
+extra | 额外信息,向左对齐 | String, Object | -
+

+ 23 - 0
src/components/GlobalFooter/index.vue

@@ -0,0 +1,23 @@
+<template>
+  <global-footer class="footer custom-render">
+    <template v-slot:links>
+      <a href="https://www.github.com/vueComponent/pro-layout" target="_blank">Pro Layout</a>
+      <a href="https://www.github.com/vueComponent/ant-design-vue-pro" target="_blank">Github</a>
+      <a href="https://www.github.com/sendya/" target="_blank">@Sendya</a>
+    </template>
+    <template v-slot:copyright>
+      <a href="https://github.com/vueComponent" target="_blank">vueComponent</a>
+    </template>
+  </global-footer>
+</template>
+
+<script>
+import { GlobalFooter } from '@ant-design-vue/pro-layout'
+
+export default {
+  name: 'ProGlobalFooter',
+  components: {
+    GlobalFooter
+  }
+}
+</script>

+ 80 - 0
src/components/GlobalHeader/AvatarDropdown.vue

@@ -0,0 +1,80 @@
+<template>
+  <a-dropdown v-if="currentUser && currentUser.name" placement="bottomRight">
+    <span class="ant-pro-account-avatar">
+      <a-avatar size="small" src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png" class="antd-pro-global-header-index-avatar" />
+      <span>{{ currentUser.name }}</span>
+    </span>
+    <template v-slot:overlay>
+      <a-menu class="ant-pro-drop-down menu" :selected-keys="[]">
+        <a-menu-item v-if="menu" key="center" @click="handleToCenter">
+          <a-icon type="user" />
+          {{ $t('menu.account.center') }}
+        </a-menu-item>
+        <a-menu-item v-if="menu" key="settings" @click="handleToSettings">
+          <a-icon type="setting" />
+          {{ $t('menu.account.settings') }}
+        </a-menu-item>
+        <a-menu-divider v-if="menu" />
+        <a-menu-item key="logout" @click="handleLogout">
+          <a-icon type="logout" />
+          {{ $t('menu.account.logout') }}
+        </a-menu-item>
+      </a-menu>
+    </template>
+  </a-dropdown>
+  <span v-else>
+    <a-spin size="small" :style="{ marginLeft: 8, marginRight: 8 }" />
+  </span>
+</template>
+
+<script>
+import { Modal } from 'ant-design-vue'
+
+export default {
+  name: 'AvatarDropdown',
+  props: {
+    currentUser: {
+      type: Object,
+      default: () => null
+    },
+    menu: {
+      type: Boolean,
+      default: true
+    }
+  },
+  methods: {
+    handleToCenter () {
+      this.$router.push({ path: '/account/center' })
+    },
+    handleToSettings () {
+      this.$router.push({ path: '/account/settings' })
+    },
+    handleLogout (e) {
+      Modal.confirm({
+        title: this.$t('layouts.usermenu.dialog.title'),
+        content: this.$t('layouts.usermenu.dialog.content'),
+        onOk: () => {
+          // return new Promise((resolve, reject) => {
+          //   setTimeout(Math.random() > 0.5 ? resolve : reject, 1500)
+          // }).catch(() => console.log('Oops errors!'))
+          return this.$store.dispatch('Logout').then(() => {
+            this.$router.push({ name: 'login' })
+          })
+        },
+        onCancel () {}
+      })
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.ant-pro-drop-down {
+  /deep/ .action {
+    margin-right: 8px;
+  }
+  /deep/ .ant-dropdown-menu-item {
+    min-width: 160px;
+  }
+}
+</style>

+ 58 - 0
src/components/GlobalHeader/RightContent.vue

@@ -0,0 +1,58 @@
+<template>
+  <div :class="wrpCls">
+    <avatar-dropdown :menu="showMenu" :current-user="currentUser" :class="prefixCls" />
+    <select-lang :class="prefixCls" />
+  </div>
+</template>
+
+<script>
+import AvatarDropdown from './AvatarDropdown'
+import SelectLang from '@/components/SelectLang'
+
+export default {
+  name: 'RightContent',
+  components: {
+    AvatarDropdown,
+    SelectLang
+  },
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-global-header-index-action'
+    },
+    isMobile: {
+      type: Boolean,
+      default: () => false
+    },
+    topMenu: {
+      type: Boolean,
+      required: true
+    },
+    theme: {
+      type: String,
+      required: true
+    }
+  },
+  data () {
+    return {
+      showMenu: true,
+      currentUser: {}
+    }
+  },
+  computed: {
+    wrpCls () {
+      return {
+        'ant-pro-global-header-index-right': true,
+        [`ant-pro-global-header-index-${(this.isMobile || !this.topMenu) ? 'light' : this.theme}`]: true
+      }
+    }
+  },
+  mounted () {
+    setTimeout(() => {
+      this.currentUser = {
+        name: 'Serati Ma'
+      }
+    }, 1500)
+  }
+}
+</script>

+ 86 - 0
src/components/IconSelector/IconSelector.vue

@@ -0,0 +1,86 @@
+<template>
+  <div :class="prefixCls">
+    <a-tabs v-model="currentTab" @change="handleTabChange">
+      <a-tab-pane v-for="v in icons" :tab="v.title" :key="v.key">
+        <ul>
+          <li v-for="(icon, key) in v.icons" :key="`${v.key}-${key}`" :class="{ 'active': selectedIcon==icon }" @click="handleSelectedIcon(icon)" >
+            <a-icon :type="icon" :style="{ fontSize: '36px' }" />
+          </li>
+        </ul>
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script>
+import icons from './icons'
+
+export default {
+  name: 'IconSelect',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-icon-selector'
+    },
+    // eslint-disable-next-line
+    value: {
+      type: String
+    }
+  },
+  data () {
+    return {
+      selectedIcon: this.value || '',
+      currentTab: 'directional',
+      icons
+    }
+  },
+  watch: {
+    value (val) {
+      this.selectedIcon = val
+      this.autoSwitchTab()
+    }
+  },
+  created () {
+    if (this.value) {
+      this.autoSwitchTab()
+    }
+  },
+  methods: {
+    handleSelectedIcon (icon) {
+      this.selectedIcon = icon
+      this.$emit('change', icon)
+    },
+    handleTabChange (activeKey) {
+      this.currentTab = activeKey
+    },
+    autoSwitchTab () {
+      icons.some(item => item.icons.some(icon => icon === this.value) && (this.currentTab = item.key))
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  @import "../index.less";
+
+  ul{
+    list-style: none;
+    padding: 0;
+    overflow-y: scroll;
+    height: 250px;
+
+    li{
+      display: inline-block;
+      padding: @padding-sm;
+      margin: 3px 0;
+      border-radius: @border-radius-base;
+
+      &:hover, &.active{
+        // box-shadow: 0px 0px 5px 2px @primary-color;
+        cursor: pointer;
+        color: @white;
+        background-color: @primary-color;
+      }
+    }
+  }
+</style>

+ 48 - 0
src/components/IconSelector/README.md

@@ -0,0 +1,48 @@
+IconSelector
+====
+
+> 图标选择组件,常用于为某一个数据设定一个图标时使用
+> eg: 设定菜单列表时,为每个菜单设定一个图标
+
+该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装
+
+
+
+### 使用方式
+
+```vue
+<template>
+	<div>
+       <icon-selector @change="handleIconChange"/>
+    </div>
+</template>
+
+<script>
+import IconSelector from '@/components/IconSelector'
+
+export default {
+  name: 'YourView',
+  components: {
+    IconSelector
+  },
+  data () {
+    return {
+    }
+  },
+  methods: {
+    handleIconChange (icon) {
+      console.log('change Icon', icon)
+    }
+  }
+}
+</script>
+```
+
+
+
+### 事件
+
+
+| 名称   | 说明                       | 类型   | 默认值 |
+| ------ | -------------------------- | ------ | ------ |
+| change | 当改变了 `icon` 选中项触发 | String | -      |

+ 36 - 0
src/components/IconSelector/icons.js

@@ -0,0 +1,36 @@
+/**
+ * 增加新的图标时,请遵循以下数据结构
+ * Adding new icon please follow the data structure below
+ */
+export default [
+  {
+    key: 'directional',
+    title: '方向性图标',
+    icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit']
+  },
+  {
+    key: 'suggested',
+    title: '提示建议性图标',
+    icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
+  },
+  {
+    key: 'editor',
+    title: '编辑类图标',
+    icons: ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'colum-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
+  },
+  {
+    key: 'data',
+    title: '数据类图标',
+    icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
+  },
+  {
+    key: 'brand_logo',
+    title: '网站通用图标',
+    icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interation', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
+  },
+  {
+    key: 'application',
+    title: '品牌和标识',
+    icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
+  }
+]

+ 2 - 0
src/components/IconSelector/index.js

@@ -0,0 +1,2 @@
+import IconSelector from './IconSelector'
+export default IconSelector

+ 162 - 0
src/components/MultiTab/MultiTab.vue

@@ -0,0 +1,162 @@
+<script>
+import events from './events'
+
+export default {
+  name: 'MultiTab',
+  data () {
+    return {
+      fullPathList: [],
+      pages: [],
+      activeKey: '',
+      newTabIndex: 0
+    }
+  },
+  created () {
+    // bind event
+    events.$on('open', val => {
+      if (!val) {
+        throw new Error(`multi-tab: open tab ${val} err`)
+      }
+      this.activeKey = val
+    }).$on('close', val => {
+      if (!val) {
+        this.closeThat(this.activeKey)
+        return
+      }
+      this.closeThat(val)
+    }).$on('rename', ({ key, name }) => {
+      console.log('rename', key, name)
+      try {
+        const item = this.pages.find(item => item.path === key)
+        item.meta.customTitle = name
+        this.$forceUpdate()
+      } catch (e) {
+      }
+    })
+
+    this.pages.push(this.$route)
+    this.fullPathList.push(this.$route.fullPath)
+    this.selectedLastPath()
+  },
+  methods: {
+    onEdit (targetKey, action) {
+      this[action](targetKey)
+    },
+    remove (targetKey) {
+      this.pages = this.pages.filter(page => page.fullPath !== targetKey)
+      this.fullPathList = this.fullPathList.filter(path => path !== targetKey)
+      // 判断当前标签是否关闭,若关闭则跳转到最后一个还存在的标签页
+      if (!this.fullPathList.includes(this.activeKey)) {
+        this.selectedLastPath()
+      }
+    },
+    selectedLastPath () {
+      this.activeKey = this.fullPathList[this.fullPathList.length - 1]
+    },
+
+    // content menu
+    closeThat (e) {
+      // 判断是否为最后一个标签页,如果是最后一个,则无法被关闭
+      if (this.fullPathList.length > 1) {
+        this.remove(e)
+      } else {
+        this.$message.info('这是最后一个标签了, 无法被关闭')
+      }
+    },
+    closeLeft (e) {
+      const currentIndex = this.fullPathList.indexOf(e)
+      if (currentIndex > 0) {
+        this.fullPathList.forEach((item, index) => {
+          if (index < currentIndex) {
+            this.remove(item)
+          }
+        })
+      } else {
+        this.$message.info('左侧没有标签')
+      }
+    },
+    closeRight (e) {
+      const currentIndex = this.fullPathList.indexOf(e)
+      if (currentIndex < (this.fullPathList.length - 1)) {
+        this.fullPathList.forEach((item, index) => {
+          if (index > currentIndex) {
+            this.remove(item)
+          }
+        })
+      } else {
+        this.$message.info('右侧没有标签')
+      }
+    },
+    closeAll (e) {
+      const currentIndex = this.fullPathList.indexOf(e)
+      this.fullPathList.forEach((item, index) => {
+        if (index !== currentIndex) {
+          this.remove(item)
+        }
+      })
+    },
+    closeMenuClick (key, route) {
+      this[key](route)
+    },
+    renderTabPaneMenu (e) {
+      return (
+        <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}>
+          <a-menu-item key="closeThat">关闭当前标签</a-menu-item>
+          <a-menu-item key="closeRight">关闭右侧</a-menu-item>
+          <a-menu-item key="closeLeft">关闭左侧</a-menu-item>
+          <a-menu-item key="closeAll">关闭全部</a-menu-item>
+        </a-menu>
+      )
+    },
+    // render
+    renderTabPane (title, keyPath) {
+      const menu = this.renderTabPaneMenu(keyPath)
+
+      return (
+        <a-dropdown overlay={menu} trigger={['contextmenu']}>
+          <span style={{ userSelect: 'none' }}>{ title }</span>
+        </a-dropdown>
+      )
+    }
+  },
+  watch: {
+    '$route': function (newVal) {
+      this.activeKey = newVal.fullPath
+      if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
+        this.fullPathList.push(newVal.fullPath)
+        this.pages.push(newVal)
+      }
+    },
+    activeKey: function (newPathKey) {
+      this.$router.push({ path: newPathKey })
+    }
+  },
+  render () {
+    const { onEdit, $data: { pages } } = this
+    const panes = pages.map(page => {
+      return (
+        <a-tab-pane
+          style={{ height: 0 }}
+          tab={this.renderTabPane(page.meta.customTitle || page.meta.title, page.fullPath)}
+          key={page.fullPath} closable={pages.length > 1}
+        >
+        </a-tab-pane>)
+    })
+
+    return (
+      <div class="ant-pro-multi-tab">
+        <div class="ant-pro-multi-tab-wrapper">
+          <a-tabs
+            hideAdd
+            type={'editable-card'}
+            v-model={this.activeKey}
+            tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }}
+            {...{ on: { edit: onEdit } }}>
+            {panes}
+          </a-tabs>
+        </div>
+      </div>
+    )
+  }
+}
+</script>

+ 2 - 0
src/components/MultiTab/events.js

@@ -0,0 +1,2 @@
+import Vue from 'vue'
+export default new Vue()

+ 40 - 0
src/components/MultiTab/index.js

@@ -0,0 +1,40 @@
+import events from './events'
+import MultiTab from './MultiTab'
+import './index.less'
+
+const api = {
+  /**
+   * open new tab on route fullPath
+   * @param config
+   */
+  open: function (config) {
+    events.$emit('open', config)
+  },
+  rename: function (key, name) {
+    events.$emit('rename', { key: key, name: name })
+  },
+  /**
+   * close current page
+   */
+  closeCurrentPage: function () {
+    this.close()
+  },
+  /**
+   * close route fullPath tab
+   * @param config
+   */
+  close: function (config) {
+    events.$emit('close', config)
+  }
+}
+
+MultiTab.install = function (Vue) {
+  if (Vue.prototype.$multiTab) {
+    return
+  }
+  api.instance = events
+  Vue.prototype.$multiTab = api
+  Vue.component('multi-tab', MultiTab)
+}
+
+export default MultiTab

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

@@ -0,0 +1,25 @@
+@import '../index';
+
+@multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab";
+@multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper";
+
+/*
+.topmenu .@{multi-tab-prefix-cls} {
+  max-width: 1200px;
+  margin: -23px auto 24px auto;
+}
+*/
+.@{multi-tab-prefix-cls} {
+  margin: -23px -24px 24px -24px;
+  background: #fff;
+}
+
+.topmenu .@{multi-tab-wrapper-prefix-cls} {
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+.topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} {
+  max-width: 100%;
+  margin: 0 auto;
+}

+ 76 - 0
src/components/NProgress/nprogress.less

@@ -0,0 +1,76 @@
+@import url('../index.less');
+
+/* Make clicks pass-through */
+#nprogress {
+  pointer-events: none;
+}
+
+#nprogress .bar {
+  background: @primary-color;
+
+  position: fixed;
+  z-index: 1031;
+  top: 0;
+  left: 0;
+
+  width: 100%;
+  height: 2px;
+}
+
+/* Fancy blur effect */
+#nprogress .peg {
+  display: block;
+  position: absolute;
+  right: 0px;
+  width: 100px;
+  height: 100%;
+  box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
+  opacity: 1.0;
+
+  -webkit-transform: rotate(3deg) translate(0px, -4px);
+  -ms-transform: rotate(3deg) translate(0px, -4px);
+  transform: rotate(3deg) translate(0px, -4px);
+}
+
+/* Remove these to get rid of the spinner */
+#nprogress .spinner {
+  display: block;
+  position: fixed;
+  z-index: 1031;
+  top: 15px;
+  right: 15px;
+}
+
+#nprogress .spinner-icon {
+  width: 18px;
+  height: 18px;
+  box-sizing: border-box;
+
+  border: solid 2px transparent;
+  border-top-color: @primary-color;
+  border-left-color: @primary-color;
+  border-radius: 50%;
+
+  -webkit-animation: nprogress-spinner 400ms linear infinite;
+  animation: nprogress-spinner 400ms linear infinite;
+}
+
+.nprogress-custom-parent {
+  overflow: hidden;
+  position: relative;
+}
+
+.nprogress-custom-parent #nprogress .spinner,
+.nprogress-custom-parent #nprogress .bar {
+  position: absolute;
+}
+
+@-webkit-keyframes nprogress-spinner {
+  0%   { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(360deg); }
+}
+@keyframes nprogress-spinner {
+  0%   { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+

+ 90 - 0
src/components/NoticeIcon/NoticeIcon.vue

@@ -0,0 +1,90 @@
+<template>
+  <a-popover
+    v-model="visible"
+    trigger="click"
+    placement="bottomRight"
+    overlayClassName="header-notice-wrapper"
+    :getPopupContainer="() => $refs.noticeRef.parentElement"
+    :autoAdjustOverflow="true"
+    :arrowPointAtCenter="true"
+    :overlayStyle="{ width: '300px', top: '50px' }"
+  >
+    <template slot="content">
+      <a-spin :spinning="loading">
+        <a-tabs>
+          <a-tab-pane tab="通知" key="1">
+            <a-list>
+              <a-list-item>
+                <a-list-item-meta title="你收到了 14 份新周报" description="一年前">
+                  <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前">
+                  <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前">
+                  <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
+                </a-list-item-meta>
+              </a-list-item>
+            </a-list>
+          </a-tab-pane>
+          <a-tab-pane tab="消息" key="2">
+            123
+          </a-tab-pane>
+          <a-tab-pane tab="待办" key="3">
+            123
+          </a-tab-pane>
+        </a-tabs>
+      </a-spin>
+    </template>
+    <span @click="fetchNotice" class="header-notice" ref="noticeRef" style="padding: 0 18px">
+      <a-badge count="12">
+        <a-icon style="font-size: 16px; padding: 4px" type="bell" />
+      </a-badge>
+    </span>
+  </a-popover>
+</template>
+
+<script>
+export default {
+  name: 'HeaderNotice',
+  data () {
+    return {
+      loading: false,
+      visible: false
+    }
+  },
+  methods: {
+    fetchNotice () {
+      if (!this.visible) {
+        this.loading = true
+        setTimeout(() => {
+          this.loading = false
+        }, 2000)
+      } else {
+        this.loading = false
+      }
+      this.visible = !this.visible
+    }
+  }
+}
+</script>
+
+<style lang="css">
+  .header-notice-wrapper {
+    top: 50px !important;
+  }
+</style>
+<style lang="less" scoped>
+  .header-notice{
+    display: inline-block;
+    transition: all 0.3s;
+
+    span {
+      vertical-align: initial;
+    }
+  }
+</style>

+ 2 - 0
src/components/NoticeIcon/index.js

@@ -0,0 +1,2 @@
+import NoticeIcon from './NoticeIcon'
+export default NoticeIcon

+ 54 - 0
src/components/NumberInfo/NumberInfo.vue

@@ -0,0 +1,54 @@
+<template>
+  <div :class="[prefixCls]">
+    <slot name="subtitle">
+      <div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
+    </slot>
+    <div class="number-info-value">
+      <span>{{ total }}</span>
+      <span class="sub-total">
+        {{ subTotal }}
+        <icon :type="`caret-${status}`" />
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import Icon from 'ant-design-vue/es/icon'
+
+export default {
+  name: 'NumberInfo',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-number-info'
+    },
+    total: {
+      type: Number,
+      required: true
+    },
+    subTotal: {
+      type: Number,
+      required: true
+    },
+    subTitle: {
+      type: [String, Function],
+      default: ''
+    },
+    status: {
+      type: String,
+      default: 'up'
+    }
+  },
+  components: {
+    Icon
+  },
+  data () {
+    return {}
+  }
+}
+</script>
+
+<style lang="less" scoped>
+  @import "index";
+</style>

+ 3 - 0
src/components/NumberInfo/index.js

@@ -0,0 +1,3 @@
+import NumberInfo from './NumberInfo'
+
+export default NumberInfo

+ 55 - 0
src/components/NumberInfo/index.less

@@ -0,0 +1,55 @@
+@import "../index";
+
+@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
+
+.@{numberInfo-prefix-cls} {
+
+  .ant-pro-number-info-subtitle {
+    color: @text-color-secondary;
+    font-size: @font-size-base;
+    height: 22px;
+    line-height: 22px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+
+  .number-info-value {
+    margin-top: 4px;
+    font-size: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+
+    & > span {
+      color: @heading-color;
+      display: inline-block;
+      line-height: 32px;
+      height: 32px;
+      font-size: 24px;
+      margin-right: 32px;
+    }
+
+    .sub-total {
+      color: @text-color-secondary;
+      font-size: @font-size-lg;
+      vertical-align: top;
+      margin-right: 0;
+      i {
+        font-size: 12px;
+        transform: scale(0.82);
+        margin-left: 4px;
+      }
+      :global {
+        .anticon-caret-up {
+          color: @red-6;
+        }
+        .anticon-caret-down {
+          color: @green-6;
+        }
+      }
+    }
+  }
+}

+ 43 - 0
src/components/NumberInfo/index.md

@@ -0,0 +1,43 @@
+# NumberInfo 数据文本
+
+常用在数据卡片中,用于突出展示某个业务数据。
+
+
+
+引用方式:
+
+```javascript
+import NumberInfo from '@/components/NumberInfo'
+
+export default {
+    components: {
+        NumberInfo
+    }
+}
+```
+
+
+
+## 代码演示  [demo](https://pro.loacg.com/test/home)
+
+```html
+<number-info
+    :sub-title="() => { return 'Visits this week' }"
+    :total="12321"
+    status="up"
+    :sub-total="17.1"></number-info>
+```
+
+
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+title | 标题 | ReactNode\|string | -
+subTitle | 子标题 | ReactNode\|string | -
+total | 总量 | ReactNode\|string | -
+subTotal | 子总量 | ReactNode\|string | -
+status | 增加状态 | 'up \| down' | -
+theme | 状态样式 | string | 'light'
+gap | 设置数字和描述之间的间距(像素)| number | 8

+ 62 - 0
src/components/Other/CarbonAds.vue

@@ -0,0 +1,62 @@
+<script>
+const googleAdsUrl = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'
+export default {
+  props: {
+    isMobile: Boolean
+  },
+  // watch: {
+  //   $route (e, t) {
+  //     const adId = '#adsbygoogle'
+  //     // if(isGitee) {
+  //     //   adId = '#cf';
+  //     // }
+  //     if (e.path !== t.path && this.$el.querySelector(adId)) {
+  //       this.$el.innerHTML = ''
+  //       this.load()
+  //     }
+  //     this.adInterval && clearInterval(this.adInterval)
+  //     this.adInterval = setInterval(() => {
+  //       if (!this.$el.querySelector(adId)) {
+  //         this.$el.innerHTML = ''
+  //         this.load()
+  //       }
+  //     }, 20000)
+  //   }
+  // },
+  mounted () {
+    // this.load()
+  },
+  methods: {
+    load () {
+      if (googleAdsUrl) {
+        /* eslint-disable */
+        let adsbygoogle = []
+        const e = document.createElement('script')
+        e.id = '_adsbygoogle_js'
+        e.src = googleAdsUrl
+        this.$el.appendChild(e)
+        setTimeout(() => {
+          (adsbygoogle = window.adsbygoogle || []).push({})
+        }, 2000)
+      }
+    }
+  },
+  render () {
+    // return <ins class="adsbygoogle" style="display:inline-block;width:728px;height:90px" data-ad-client="ca-pub-4801326429087140" data-ad-slot="6929057621" />
+    return <div class="business-pro-ad"><a href="https://store.antdv.com/pro/" target="_blank">(推荐) 企业级商用版 Admin Pro 现已发售,采用 Vue3 + TS 欢迎购买。</a></div>;
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.business-pro-ad {
+  position: fixed;
+  background: rgba(255,255,255,0.25);
+  left: 0;
+  bottom: 0;
+  padding: 0 12px;
+  height: 48px;
+  width: 258px;
+  z-index: 99;
+}
+</style>

+ 106 - 0
src/components/PageLoading/index.jsx

@@ -0,0 +1,106 @@
+import { Spin } from 'ant-design-vue'
+
+export const PageLoading = {
+  name: 'PageLoading',
+  props: {
+    tip: {
+      type: String,
+      default: 'Loading..'
+    },
+    size: {
+      type: String,
+      default: 'large'
+    }
+  },
+  render () {
+    const style = {
+      textAlign: 'center',
+      background: 'rgba(0,0,0,0.6)',
+      position: 'fixed',
+      top: 0,
+      bottom: 0,
+      left: 0,
+      right: 0,
+      zIndex: 1100
+    }
+    const spinStyle = {
+      position: 'absolute',
+      left: '50%',
+      top: '40%',
+      transform: 'translate(-50%, -50%)'
+    }
+    return (<div style={style}>
+      <Spin size={this.size} style={spinStyle} tip={this.tip} />
+    </div>)
+  }
+}
+
+const version = '0.0.1'
+const loading = {}
+
+loading.newInstance = (Vue, options) => {
+  let loadingElement = document.querySelector('body>div[type=loading]')
+  if (!loadingElement) {
+    loadingElement = document.createElement('div')
+    loadingElement.setAttribute('type', 'loading')
+    loadingElement.setAttribute('class', 'ant-loading-wrapper')
+    document.body.appendChild(loadingElement)
+  }
+
+  const cdProps = Object.assign({ visible: false, size: 'large', tip: 'Loading...' }, options)
+
+  const instance = new Vue({
+    data () {
+      return {
+        ...cdProps
+      }
+    },
+    render () {
+      const { tip } = this
+      const props = {}
+      this.tip && (props.tip = tip)
+      if (this.visible) {
+        return <PageLoading { ...{ props } } />
+      }
+      return null
+    }
+  }).$mount(loadingElement)
+
+  function update (config) {
+    const { visible, size, tip } = { ...cdProps, ...config }
+    instance.$set(instance, 'visible', visible)
+    if (tip) {
+      instance.$set(instance, 'tip', tip)
+    }
+    if (size) {
+      instance.$set(instance, 'size', size)
+    }
+  }
+
+  return {
+    instance,
+    update
+  }
+}
+
+const api = {
+  show: function (options) {
+    this.instance.update({ ...options, visible: true })
+  },
+  hide: function () {
+    this.instance.update({ visible: false })
+  }
+}
+
+const install = function (Vue, options) {
+  if (Vue.prototype.$loading) {
+    return
+  }
+  api.instance = loading.newInstance(Vue, options)
+  Vue.prototype.$loading = api
+}
+
+export default {
+  version,
+  install
+}

+ 63 - 0
src/components/Search/GlobalSearch.jsx

@@ -0,0 +1,63 @@
+import { Select } from 'ant-design-vue'
+import './index.less'
+
+const GlobalSearch = {
+  name: 'GlobalSearch',
+  data () {
+    return {
+      visible: false
+    }
+  },
+  mounted () {
+    const keyboardHandle = (e) => {
+      e.preventDefault()
+      e.stopPropagation()
+      const { ctrlKey, shiftKey, altKey, keyCode } = e
+      console.log('keyCode:', e.keyCode, e)
+      // key is `K` and hold ctrl
+      if (keyCode === 75 && ctrlKey && !shiftKey && !altKey) {
+        this.visible = !this.visible
+      }
+    }
+    document.addEventListener('keydown', keyboardHandle)
+  },
+  render () {
+    const { visible } = this
+    const handleSearch = (e) => {
+      this.$emit('search', e)
+    }
+
+    const handleChange = (e) => {
+      this.$emit('change', e)
+    }
+    if (!visible) {
+      return null
+    }
+    return (
+      <div class={'global-search global-search-wrapper'}>
+        <div class={'global-search-box'}>
+          <Select
+            size={'large'}
+            showSearch
+            placeholder="Input search text.."
+            style={{ width: '100%' }}
+            defaultActiveFirstOption={false}
+            showArrow={false}
+            filterOption={false}
+            onSearch={handleSearch}
+            onChange={handleChange}
+            notFoundContent={null}
+          >
+          </Select>
+          <div class={'global-search-tips'}>Open with Ctrl/⌘ + K</div>
+        </div>
+      </div>
+    )
+  }
+}
+
+GlobalSearch.install = function (Vue) {
+  Vue.component(GlobalSearch.name, GlobalSearch)
+}
+
+export default GlobalSearch

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

@@ -0,0 +1,25 @@
+@import "~ant-design-vue/es/style/themes/default";
+
+.global-search-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: @zindex-modal-mask;
+  background: @modal-mask-bg;
+
+  .global-search-box {
+    position: absolute;
+    top: 20%;
+    left: 50%;
+    width: 450px;
+    transform: translate(-50%, -50%);
+
+    .global-search-tips {
+      color: @white;
+      font-size: @font-size-lg;
+      text-align: right;
+    }
+  }
+}

+ 58 - 0
src/components/SelectLang/index.jsx

@@ -0,0 +1,58 @@
+import './index.less'
+
+import { Icon, Menu, Dropdown } from 'ant-design-vue'
+import { i18nRender } from '@/locales'
+import i18nMixin from '@/store/i18n-mixin'
+
+const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']
+const languageLabels = {
+  'zh-CN': '简体中文',
+  'zh-TW': '繁体中文',
+  'en-US': 'English',
+  'pt-BR': 'Português'
+}
+// eslint-disable-next-line
+const languageIcons = {
+  'zh-CN': '🇨🇳',
+  'zh-TW': '🇭🇰',
+  'en-US': '🇺🇸',
+  'pt-BR': '🇧🇷'
+}
+
+const SelectLang = {
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'ant-pro-drop-down'
+    }
+  },
+  name: 'SelectLang',
+  mixins: [i18nMixin],
+  render () {
+    const { prefixCls } = this
+    const changeLang = ({ key }) => {
+      this.setLang(key)
+    }
+    const langMenu = (
+      <Menu class={['menu', 'ant-pro-header-menu']} selectedKeys={[this.currentLang]} onClick={changeLang}>
+        {locales.map(locale => (
+          <Menu.Item key={locale}>
+            <span role="img" aria-label={languageLabels[locale]}>
+              {languageIcons[locale]}
+            </span>{' '}
+            {languageLabels[locale]}
+          </Menu.Item>
+        ))}
+      </Menu>
+    )
+    return (
+      <Dropdown overlay={langMenu} placement="bottomRight">
+        <span class={prefixCls}>
+          <Icon type="global" title={i18nRender('navBar.lang')} />
+        </span>
+      </Dropdown>
+    )
+  }
+}
+
+export default SelectLang

+ 31 - 0
src/components/SelectLang/index.less

@@ -0,0 +1,31 @@
+@import "~ant-design-vue/es/style/themes/default";
+
+@header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu';
+@header-drop-down-prefix-cls: ~'@{ant-prefix}-pro-drop-down';
+
+.@{header-menu-prefix-cls} {
+
+  .anticon {
+    margin-right: 8px;
+  }
+  .ant-dropdown-menu-item {
+    min-width: 160px;
+  }
+}
+
+.@{header-drop-down-prefix-cls} {
+
+  line-height: @layout-header-height;
+  vertical-align: top;
+  cursor: pointer;
+
+  > i {
+    font-size: 16px !important;
+    transform: none !important;
+
+    svg {
+      position: relative;
+      top: -1px;
+    }
+  }
+}

+ 343 - 0
src/components/SettingDrawer/SettingDrawer.vue

@@ -0,0 +1,343 @@
+<template>
+  <div class="setting-drawer">
+    <a-drawer
+      width="300"
+      placement="right"
+      @close="onClose"
+      :closable="false"
+      :visible="visible"
+      :drawer-style="{ position: 'absolute' }"
+      style="position: absolute"
+    >
+      <div class="setting-drawer-index-content">
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">整体风格设置</h3>
+
+          <div class="setting-drawer-index-blockChecbox">
+            <a-tooltip>
+              <template slot="title">
+                暗色菜单风格
+              </template>
+              <div class="setting-drawer-index-item" @click="handleMenuTheme('dark')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark">
+                <div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                亮色菜单风格
+              </template>
+              <div class="setting-drawer-index-item" @click="handleMenuTheme('light')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light">
+                <div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+          </div>
+        </div>
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">主题色</h3>
+
+          <div style="height: 20px">
+            <a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
+              <template slot="title">
+                {{ item.key }}
+              </template>
+              <a-tag :color="item.color" @click="changeColor(item.color)">
+                <a-icon type="check" v-if="item.color === primaryColor"></a-icon>
+              </a-tag>
+            </a-tooltip>
+
+          </div>
+        </div>
+        <a-divider />
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">导航模式</h3>
+
+          <div class="setting-drawer-index-blockChecbox">
+            <a-tooltip>
+              <template slot="title">
+                侧边栏导航
+              </template>
+              <div class="setting-drawer-index-item" @click="handleLayout('sidemenu')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu">
+                <div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                顶部栏导航
+              </template>
+              <div class="setting-drawer-index-item" @click="handleLayout('topmenu')">
+                <img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu">
+                <div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'">
+                  <a-icon type="check"/>
+                </div>
+              </div>
+            </a-tooltip>
+          </div>
+          <div :style="{ marginTop: '24px' }">
+            <a-list :split="false">
+              <a-list-item>
+                <a-tooltip slot="actions">
+                  <template slot="title">
+                    该设定仅 [顶部栏导航] 时有效
+                  </template>
+                  <a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange">
+                    <a-select-option value="Fixed">固定</a-select-option>
+                    <a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option>
+                  </a-select>
+                </a-tooltip>
+                <a-list-item-meta>
+                  <div slot="title">内容区域宽度</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" />
+                <a-list-item-meta>
+                  <div slot="title">固定 Header</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" />
+                <a-list-item-meta>
+                  <a-tooltip slot="title" placement="left">
+                    <template slot="title">固定 Header 时可配置</template>
+                    <div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div>
+                  </a-tooltip>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item >
+                <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" />
+                <a-list-item-meta>
+                  <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div>
+                </a-list-item-meta>
+              </a-list-item>
+            </a-list>
+          </div>
+        </div>
+        <a-divider />
+
+        <div :style="{ marginBottom: '24px' }">
+          <h3 class="setting-drawer-index-title">其他设置</h3>
+          <div>
+            <a-list :split="false">
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" />
+                <a-list-item-meta>
+                  <div slot="title">色弱模式</div>
+                </a-list-item-meta>
+              </a-list-item>
+              <a-list-item>
+                <a-switch slot="actions" size="small" :defaultChecked="multiTab" @change="onMultiTab" />
+                <a-list-item-meta>
+                  <div slot="title">多页签模式</div>
+                </a-list-item-meta>
+              </a-list-item>
+            </a-list>
+          </div>
+        </div>
+        <a-divider />
+        <div :style="{ marginBottom: '24px' }">
+          <a-button
+            @click="doCopy"
+            icon="copy"
+            block
+          >拷贝设置</a-button>
+          <a-alert type="warning" :style="{ marginTop: '24px' }">
+            <span slot="message">
+              配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件。修改配置文件后,需要清空本地缓存和LocalStorage
+              <a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/defaultSettings.js" target="_blank">src/config/defaultSettings.js</a>
+            </span>
+          </a-alert>
+        </div>
+      </div>
+      <div class="setting-drawer-index-handle" @click="toggle" slot="handle">
+        <a-icon type="setting" v-if="!visible"/>
+        <a-icon type="close" v-else/>
+      </div>
+    </a-drawer>
+  </div>
+</template>
+
+<script>
+import SettingItem from './SettingItem'
+import config from '@/config/defaultSettings'
+import { updateTheme, updateColorWeak, colorList } from './settingConfig'
+
+export default {
+  components: {
+    SettingItem
+  },
+  mixins: [],
+  data () {
+    return {
+      visible: false,
+      colorList
+    }
+  },
+  watch: {
+
+  },
+  mounted () {
+    updateTheme(this.primaryColor)
+    if (this.colorWeak !== config.colorWeak) {
+      updateColorWeak(this.colorWeak)
+    }
+  },
+  methods: {
+    showDrawer () {
+      this.visible = true
+    },
+    onClose () {
+      this.visible = false
+    },
+    toggle () {
+      this.visible = !this.visible
+    },
+    onColorWeak (checked) {
+      this.$store.dispatch('ToggleWeak', checked)
+      updateColorWeak(checked)
+    },
+    onMultiTab (checked) {
+      this.$store.dispatch('ToggleMultiTab', checked)
+    },
+    handleMenuTheme (theme) {
+      this.$store.dispatch('ToggleTheme', theme)
+    },
+    doCopy () {
+      // get current settings from mixin or this.$store.state.app, pay attention to the property name
+      const text = `export default {
+  primaryColor: '${this.primaryColor}', // primary color of ant design
+  navTheme: '${this.navTheme}', // theme for nav menu
+  layout: '${this.layoutMode}', // nav menu position: sidemenu or topmenu
+  contentWidth: '${this.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu
+  fixedHeader: ${this.fixedHeader}, // sticky header
+  fixSiderbar: ${this.fixSiderbar}, // sticky siderbar
+  autoHideHeader: ${this.autoHideHeader}, //  auto hide header
+  colorWeak: ${this.colorWeak},
+  multiTab: ${this.multiTab},
+  production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true'
+}`
+      this.$copyText(text).then(message => {
+        console.log('copy', message)
+        this.$message.success('复制完毕')
+      }).catch(err => {
+        console.log('copy.err', err)
+        this.$message.error('复制失败')
+      })
+    },
+    handleLayout (mode) {
+      this.$store.dispatch('ToggleLayoutMode', mode)
+      // 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
+      this.handleFixSiderbar(false)
+    },
+    handleContentWidthChange (type) {
+      this.$store.dispatch('ToggleContentWidth', type)
+    },
+    changeColor (color) {
+      if (this.primaryColor !== color) {
+        this.$store.dispatch('ToggleColor', color)
+        updateTheme(color)
+      }
+    },
+    handleFixedHeader (fixed) {
+      this.$store.dispatch('ToggleFixedHeader', fixed)
+    },
+    handleFixedHeaderHidden (autoHidden) {
+      this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden)
+    },
+    handleFixSiderbar (fixed) {
+      if (this.layoutMode === 'topmenu') {
+        this.$store.dispatch('ToggleFixSiderbar', false)
+        return
+      }
+      this.$store.dispatch('ToggleFixSiderbar', fixed)
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+  .setting-drawer-index-content {
+
+    .setting-drawer-index-blockChecbox {
+      display: flex;
+
+      .setting-drawer-index-item {
+        margin-right: 16px;
+        position: relative;
+        border-radius: 4px;
+        cursor: pointer;
+
+        img {
+          width: 48px;
+        }
+
+        .setting-drawer-index-selectIcon {
+          position: absolute;
+          top: 0;
+          right: 0;
+          width: 100%;
+          padding-top: 15px;
+          padding-left: 24px;
+          height: 100%;
+          color: #1890ff;
+          font-size: 14px;
+          font-weight: 700;
+        }
+      }
+    }
+    .setting-drawer-theme-color-colorBlock {
+      width: 20px;
+      height: 20px;
+      border-radius: 2px;
+      float: left;
+      cursor: pointer;
+      margin-right: 8px;
+      padding-left: 0px;
+      padding-right: 0px;
+      text-align: center;
+      color: #fff;
+      font-weight: 700;
+
+      i {
+        font-size: 14px;
+      }
+    }
+  }
+
+  .setting-drawer-index-handle {
+    position: absolute;
+    top: 240px;
+    background: #1890ff;
+    width: 48px;
+    height: 48px;
+    right: 300px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+    pointer-events: auto;
+    z-index: 1001;
+    text-align: center;
+    font-size: 16px;
+    border-radius: 4px 0 0 4px;
+
+    i {
+      color: rgb(255, 255, 255);
+      font-size: 20px;
+    }
+  }
+</style>

+ 38 - 0
src/components/SettingDrawer/SettingItem.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="setting-drawer-index-item">
+    <h3 class="setting-drawer-index-title">{{ title }}</h3>
+    <slot></slot>
+    <a-divider v-if="divider"/>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SettingItem',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    divider: {
+      type: Boolean,
+      default: false
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+  .setting-drawer-index-item {
+    margin-bottom: 24px;
+
+    .setting-drawer-index-title {
+      font-size: 14px;
+      color: rgba(0, 0, 0, .85);
+      line-height: 22px;
+      margin-bottom: 12px;
+    }
+
+  }
+</style>

+ 2 - 0
src/components/SettingDrawer/index.js

@@ -0,0 +1,2 @@
+import SettingDrawer from './SettingDrawer'
+export default SettingDrawer

+ 48 - 0
src/components/SettingDrawer/settingConfig.js

@@ -0,0 +1,48 @@
+import message from 'ant-design-vue/es/message'
+// import defaultSettings from '../defaultSettings';
+import themeColor from './themeColor.js'
+
+// let lessNodesAppended
+const colorList = [
+  {
+    key: '薄暮', color: '#F5222D'
+  },
+  {
+    key: '火山', color: '#FA541C'
+  },
+  {
+    key: '日暮', color: '#FAAD14'
+  },
+  {
+    key: '明青', color: '#13C2C2'
+  },
+  {
+    key: '极光绿', color: '#52C41A'
+  },
+  {
+    key: '拂晓蓝(默认)', color: '#1890FF'
+  },
+  {
+    key: '极客蓝', color: '#2F54EB'
+  },
+  {
+    key: '酱紫', color: '#722ED1'
+  }
+]
+
+const updateTheme = newPrimaryColor => {
+  const hideMessage = message.loading('正在切换主题!', 0)
+  themeColor.changeColor(newPrimaryColor).finally(() => {
+    setTimeout(() => {
+      hideMessage()
+    }, 10)
+  })
+}
+
+const updateColorWeak = colorWeak => {
+  // document.body.className = colorWeak ? 'colorWeak' : '';
+  const app = document.body.querySelector('#app')
+  colorWeak ? app.classList.add('colorWeak') : app.classList.remove('colorWeak')
+}
+
+export { updateTheme, colorList, updateColorWeak }

+ 24 - 0
src/components/SettingDrawer/themeColor.js

@@ -0,0 +1,24 @@
+import client from 'webpack-theme-color-replacer/client'
+import generate from '@ant-design/colors/lib/generate'
+
+export default {
+  getAntdSerials (color) {
+    // 淡化(即less的tint)
+    const lightens = new Array(9).fill().map((t, i) => {
+      return client.varyColor.lighten(color, i / 10)
+    })
+    // colorPalette变换得到颜色值
+    const colorPalettes = generate(color)
+    const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',')
+    return lightens.concat(colorPalettes).concat(rgb)
+  },
+  changeColor (newColor) {
+    var options = {
+      newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
+      changeUrl (cssUrl) {
+        return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
+      }
+    }
+    return client.changer.changeColor(options, Promise)
+  }
+}

+ 122 - 0
src/components/StandardFormRow/StandardFormRow.vue

@@ -0,0 +1,122 @@
+<template>
+  <div :class="[prefixCls, lastCls, blockCls, gridCls]">
+    <div v-if="title" class="antd-pro-components-standard-form-row-index-label">
+      <span>{{ title }}</span>
+    </div>
+    <div class="antd-pro-components-standard-form-row-index-content">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+const classes = [
+  'antd-pro-components-standard-form-row-index-standardFormRowBlock',
+  'antd-pro-components-standard-form-row-index-standardFormRowGrid',
+  'antd-pro-components-standard-form-row-index-standardFormRowLast'
+]
+export default {
+  name: 'StandardFormRow',
+  props: {
+    prefixCls: {
+      type: String,
+      default: 'antd-pro-components-standard-form-row-index-standardFormRow'
+    },
+    title: {
+      type: String,
+      default: undefined
+    },
+    last: {
+      type: Boolean
+    },
+    block: {
+      type: Boolean
+    },
+    grid: {
+      type: Boolean
+    }
+  },
+  computed: {
+    lastCls () {
+      return this.last ? classes[2] : null
+    },
+    blockCls () {
+      return this.block ? classes[0] : null
+    },
+    gridCls () {
+      return this.grid ? classes[1] : null
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+@import '../index.less';
+
+.antd-pro-components-standard-form-row-index-standardFormRow {
+  display: flex;
+  margin-bottom: 16px;
+  padding-bottom: 16px;
+  border-bottom: 1px dashed @border-color-split;
+
+  /deep/ .ant-form-item {
+    margin-right: 24px;
+  }
+  /deep/ .ant-form-item-label label {
+    margin-right: 0;
+    color: @text-color;
+  }
+  /deep/ .ant-form-item-label,
+  .ant-form-item-control {
+    padding: 0;
+    line-height: 32px;
+  }
+
+  .antd-pro-components-standard-form-row-index-label {
+    flex: 0 0 auto;
+    margin-right: 24px;
+    color: @heading-color;
+    font-size: @font-size-base;
+    text-align: right;
+    & > span {
+      display: inline-block;
+      height: 32px;
+      line-height: 32px;
+      &::after {
+        content: ':';
+      }
+    }
+  }
+
+  .antd-pro-components-standard-form-row-index-content {
+    flex: 1 1 0;
+    /deep/ .ant-form-item:last-child {
+      margin-right: 0;
+    }
+  }
+
+  &.antd-pro-components-standard-form-row-index-standardFormRowLast {
+    margin-bottom: 0;
+    padding-bottom: 0;
+    border: none;
+  }
+
+  &.antd-pro-components-standard-form-row-index-standardFormRowBlock {
+    /deep/ .ant-form-item,
+    div.ant-form-item-control-wrapper {
+      display: block;
+    }
+  }
+
+  &.antd-pro-components-standard-form-row-index-standardFormRowGrid {
+      /deep/ .ant-form-item,
+      div.ant-form-item-control-wrapper {
+        display: block;
+      }
+      /deep/ .ant-form-item-label {
+        float: left;
+      }
+  }
+}
+
+</style>

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