Browse Source

[release] 4.0.0 (#1291)

* fix[ExternalLink]: fixed bug when url include chinese #1182

* feature: support  Spanish(#1196)

* fix[MockJS]: fix bug with withCredentials after using mockjs  (#1194)

* 修复 Mock 导致请求丢失 Cookie 的问题

修复 Mock 导致 Cookie 丢失的问题,只有在 XHR.open() 周期时,自定义的 withCredentials 会被挂载,此时检查是否是未被拦截的 xhr,并挂载自定义的 withCredentials ,无则默认为 false

* update readme

* perf[tagsView]: refactor the moveToTarget function (#1195)

* fix[tagsView]:fixed visited view move to currentTag

* edit the scroll regular friendly

* tweak

* fix[tagsView]: fixed moveToCurrentTag bug

* feature: add pagination component (#1213)

* fix[TagsView]: fixed update tags title demo bug (#1223)

* chore: temporary hack cssnano bug #1222

* [release] 3.9.2

* chore: restore the hack of cssnano bug

https://github.com/cssnano/cssnano/issues/643

* add an example of sort data by table  (#1236)

* feature: add drag select component (#1249)

* feat: perfect migrate to @vue/cli-service, upgrade vue babel version (#1267)

* feat: perfect migrate to @vue/cli-service, upgrade vue babel version

1. update to @vue/cli-service@3.0.5, @babel/core@7.0.0
2. use vue-cli service replace config file in build/ and config/
3. upgrade vue and babel configuration
4. solve the svg-sprite config problem #980

refs: #932 #1087  #980 #1056

* fix: fix breadcrumb dependency

* fix: fix index template and static assets load with vue-cli 3

* fix: fix import driver.js in guide page

* refactor(mock): mak mock api compatible with both web-view and webpack server

1. 把 Mockjs 功能移到 server 端中间件,同时也兼容前端直接劫持 XHR
2. dev 环境下默认作为 express 中间件通过 webpack server 提供 mock api
3. prod 构建时,默认在前端用 Mockjs 劫持 XHR

benefits:
  - dev 开发调试时能直接看到 XHR 请求,方便调试网络,能和后端对接联调
  - 避开在开发时因为 Mockjs 引起的网络 bug
  - prod 构建时劫持 XHR,保证本项目的 Github Pages preview 能正常显示 (逻辑和 error-log 一样)
  - 前后台使用的 mock 是同一份代码,不会增加维护负担

ref: [#562](https://github.com/PanJiaChen/vue-element-admin/issues/562#issuecomment-378116233)

* update requires the lowest version of node

* add favicon

* fix(TreeTable): fix `Array.prototype.concat` on custom-tree-table page

* update

* add test

* fix bug

* fix[Charts]: fixed charts resize mixins bug  #1285 (#1290)

* perf[Tinymce]: add searchreplace plugin

* perf[avatar]:minimize the selected area of avatar on the mobile phone when user clicked avatar (#1304)

* refine css

* fix[DragSelect]: fixed querySelectorAll bug

* perf[DragSelect]: add $listeners

* fix link

* fix[Breadcurmb]: fixed pathCompile bug

* fix[Breadcurmb]: fixed router-link bug

* perf[style]: use webpack alias instead of hard code src path (#1338)

* perf[style]: use webpack alias instead of hard code src path

* add sponsors

* fix import path bug

* update vue-router to fixed url path for non ascii urls #1362

* fix[Pagination]: apply PageSizes property to el-pagination  (#1355)

Apply PageSizes property to el-pagination

* update dependence

* add tui.editor (#1374)

* tweak

* add preview

* fix return back bug

* update guide page

* fix[Tinymce]: fixed fullScreen bug #1400

* feat[Breadcrumb]: add hide Breadcrumb option #1442

* perf: use WeChat 7.0 new version icon color

* refactor[login]: refactor login page style

* perf[ScrollPane]: refine moveToTarget code (#1460)

* feature[PDF]: add PDF demo (#1469)

* perf[v-permission]: refine v-permission demo

* perf[Sidebar]: refine sidebar store #1473 (#1474)

* refine: GetUserInfo error message

* fix typo (#1505)

* perf: add sidebar width to variables.scss (#1494)

* tweak

* fix[ThemePicker]: fixed bug when oldVal is null (#1517)

* update README.md

* fix[Breadcrumb]: fixed eslint error (#1521)

* fix[DndList]: fixed drag bug (#1527)

https://github.com/PanJiaChen/vue-element-admin/issues/1524

* pref[Hamburger]: refactor Hamburger component (#1528)

* 美化侧栏菜单切换按钮

* tweak

* perf[Login Form]: optimize eye icon style (#1545)

* optimiz: eye icon style for login form

* change eye-open svg

* perf[Sticky]: export reset method (#1550)

* perf[Sticky]: refine width default value

* perf[utils]: refine parseTime function (#1546)

* 优化 parseTime

修复传入的时间戳是字符串类型,不能转换时间的问题
例:parseTime("1548221490638")

* Update index.js

* perf[UploadExcel]: optimized code (#1552)

* perf: adjust the import order to make it more elegant #1537

* perf[Sidebar]: use sass variables in vue template

* perf[Style]: optimize the sidebar style to make it better to set (#1568)

* perf[SizeSelect]: add default size option (#1566)

* fix[SIdebar]: fixed bug in mobile #1567 (#1569)

* perf: fixed eslint errors

* perf[Lang]: make up for miss keywords

* perf: optimize some code

* perf[Navbar]: refactor navbar style

* perf[Login]: refine css

* feature[Navbar]: add header-search component(#1591)

* fix[Screenfull]: fix screenfull click bug

* perf[Screenfull]: refactor screenfull component

* fix[Screenfull]: fix screenfull bug (#1603)

* fix typo

* fearure[TagsView]: add affix option (#1577)

* perf[utils]: optimize code

* perf[utils]: optimizate variable name

* perf[Navbar]: add scroll bar when the subMenu is too long (#1619)

* perf[ThemePicker]: refine updateStyle function (#554)

* theme replacing should cut tons of irrelevant css

* perf[ResizeHandler]: optimized the judgment of isMobile (#1633)

perf[ResizeHandler]: optimized the judgment of isMobile

*  fix[Sidebar]: fixed infinite loop bug(#1333)

* fixed infinite loop Bug when in hasOneShowingChild Edit the onlyOneChild

* tweak

* fix[Sidebar]: data should return a object

* perf[Sidebar]: optimize code logic (#1349)

* fix[TagsView]: fixed refresh affixed-tag bug (#1653)

* perf[utils.js]: refactor byteLength function (#1650)

* perf[TagsView]: refine code

* perf[TagsView]: set the scrollPane as a business component (#1660)

* fix[DragTable]: support multiple drag-table (#1666)

* perf[Tree-Table]: refactor tree-table

* perf[Tree-Table]: organize the structure and add documentation (#1673)

* fix[Sidebar]: fixed nested router hover bug

* update version

* set preserveWhitespace

* lint code

* fix jest test case

* update config

* bump

* remove empty file

* docs: add link

* fix[Sidebar]: fixed collapse animation problem (#1690)

* fix[Tree-Table]: fixed update item data bug (#1692)

* fix[Waves-Directive]: fixed v-waves does not support update (#1705)

* update husky

* rm cli-plugin-eslint

* add settings (#1707)

* refine settings

* fix[utils]: fixed param2Obj not decoding plus sign (#1712)

* feature[Directive]: add auto-height table directive (#1702)

* fix bug

* feature[Permission]: add role permission management page (#1605)

* feature[Excel]: support export merged header export (#1718)

* feature[Excel]: add export merge header excel demo

* lint

* refine theme color

* add role mock

* tweak mock

* fix[Excel]: fixed export merge-header excel bug

* refine code

* add ThemePicker to setting

* fix[HeaderSearch]: fixed bug in vue2.6+ (#1733)

* fix[Sticky]: fixed bug when set stickyTop

* perf[Sticky]: refine demo

* refine code

* tweak mock

* vuex add namespaced

* fix[Excel]: fixed export bug (#1736)

* rm

* refactor permission

* perf[ThemePicker]: add predefine (#1743)

* fix[Utils]: fixed deepClone error msg (#1748)

* feature: add fixedHeader settings

* fix style in mobile

* fix chore

* perf[Eslint]: update eslint rules

* feature: add create template (#1762)

* add comment

* update vue.config.js

* feature: add sidebar logo (#1767)

* rm

* perf settings

* bump

* refine script and css

* update

* refine settings

* refine config

* update docs

* refine

* rm

* fix jest

* add theme setting

* dump vue-cli

* perf: remove redundant code

* update element-ui

* fix sticky demo bug

* docs

* fixed password input  bug

* refine login form css

* remove tree-table

* update version

* mock error

* refine layout name

* refine
花裤衩 6 years ago
parent
commit
b94e69be6f
100 changed files with 1654 additions and 1260 deletions
  1. 0 17
      .babelrc
  2. 14 0
      .env.development
  3. 6 0
      .env.production
  4. 8 0
      .env.staging
  5. 2 1
      .eslintignore
  6. 2 1
      .eslintrc.js
  7. 4 2
      .gitignore
  8. 0 2
      .postcssrc.js
  9. 22 29
      README.md
  10. 30 33
      README.zh-CN.md
  11. 5 0
      babel.config.js
  12. 0 67
      build/build.js
  13. 0 64
      build/check-versions.js
  14. 35 0
      build/index.js
  15. BIN
      build/logo.png
  16. 0 108
      build/utils.js
  17. 0 5
      build/vue-loader.conf.js
  18. 0 107
      build/webpack.base.conf.js
  19. 0 98
      build/webpack.dev.conf.js
  20. 0 187
      build/webpack.prod.conf.js
  21. 0 5
      config/dev.env.js
  22. 0 88
      config/index.js
  23. 0 5
      config/prod.env.js
  24. 0 5
      config/sit.env.js
  25. 27 0
      jest.config.js
  26. 116 0
      mock/article.js
  27. 54 0
      mock/index.js
  28. 12 0
      mock/mocks.js
  29. 51 0
      mock/remoteSearch.js
  30. 98 0
      mock/role/index.js
  31. 525 0
      mock/role/routes.js
  32. 84 0
      mock/user.js
  33. 64 84
      package.json
  34. 26 0
      plop-templates/component/index.hbs
  35. 55 0
      plop-templates/component/prompt.js
  36. 9 0
      plop-templates/utils.js
  37. 26 0
      plop-templates/view/index.hbs
  38. 55 0
      plop-templates/view/prompt.js
  39. 7 0
      plopfile.js
  40. 0 0
      public/favicon.ico
  41. 3 2
      index.html
  42. 0 0
      public/static/tinymce4.7.5/langs/zh_CN.js
  43. 0 0
      public/static/tinymce4.7.5/plugins/codesample/css/prism.css
  44. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif
  45. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif
  46. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif
  47. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif
  48. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif
  49. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif
  50. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif
  51. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif
  52. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif
  53. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif
  54. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif
  55. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif
  56. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif
  57. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif
  58. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif
  59. 0 0
      public/static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif
  60. 0 0
      public/static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css
  61. 0 0
      public/static/tinymce4.7.5/skins/lightgray/content.inline.min.css
  62. 0 0
      public/static/tinymce4.7.5/skins/lightgray/content.min.css
  63. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff
  64. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot
  65. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg
  66. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf
  67. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff
  68. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot
  69. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg
  70. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf
  71. 0 0
      public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff
  72. 0 0
      public/static/tinymce4.7.5/skins/lightgray/img/anchor.gif
  73. 0 0
      public/static/tinymce4.7.5/skins/lightgray/img/loader.gif
  74. 0 0
      public/static/tinymce4.7.5/skins/lightgray/img/object.gif
  75. 0 0
      public/static/tinymce4.7.5/skins/lightgray/img/trans.gif
  76. 0 0
      public/static/tinymce4.7.5/skins/lightgray/skin.min.css
  77. 0 0
      public/static/tinymce4.7.5/skins/lightgray/skin.min.css.map
  78. 0 0
      public/static/tinymce4.7.5/tinymce.min.js
  79. 9 1
      src/api/remoteSearch.js
  80. 10 10
      src/api/role.js
  81. 0 9
      src/api/transaction.js
  82. 9 13
      src/api/login.js
  83. 21 21
      src/components/BackToTop/index.vue
  84. 13 13
      src/components/Breadcrumb/index.vue
  85. 9 5
      src/components/DndList/index.vue
  86. 1 1
      src/components/Dropzone/index.vue
  87. 9 4
      src/components/ErrorLog/index.vue
  88. 5 4
      src/components/Hamburger/index.vue
  89. 2 0
      src/components/HeaderSearch/index.vue
  90. 1 1
      src/components/Kanban/index.vue
  91. 1 1
      src/components/LangSelect/index.vue
  92. 1 1
      src/components/MDinput/index.vue
  93. 152 0
      src/components/RightPanel/index.vue
  94. 12 3
      src/components/Screenfull/index.vue
  95. 1 1
      src/components/Share/dropdownMenu.vue
  96. 4 3
      src/components/SizeSelect/index.vue
  97. 44 35
      src/components/ThemePicker/index.vue
  98. 10 4
      src/components/Tinymce/components/editorImage.vue
  99. 0 220
      src/components/TreeTable/README.md
  100. 0 0
      src/components/TreeTable/eval.js

+ 0 - 17
.babelrc

@@ -1,17 +0,0 @@
-{
-  "presets": [
-    ["env", {
-      "modules": false,
-      "targets": {
-        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
-      }
-    }],
-    "stage-2"
-  ],
-  "plugins": ["transform-vue-jsx", "transform-runtime"],
-  "env": {
-    "development":{
-      "plugins": ["dynamic-import-node"]
-    }
-  }
-}

+ 14 - 0
.env.development

@@ -0,0 +1,14 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'
+
+# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
+# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
+# It only does one thing by converting all import() to require().
+# This configuration can significantly increase the speed of hot updates,
+# when you have a large number of pages.
+# Detail:  https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
+
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 6 - 0
.env.production

@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/prod-api'
+

+ 8 - 0
.env.staging

@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+

+ 2 - 1
.eslintignore

@@ -1,3 +1,4 @@
 build/*.js
-config/*.js
 src/assets
+public
+dist

+ 2 - 1
.eslintrc.js

@@ -24,6 +24,7 @@ module.exports = {
     "vue/singleline-html-element-content-newline": "off",
     "vue/multiline-html-element-content-newline":"off",
     "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
     'accessor-pairs': 2,
     'arrow-spacing': [2, {
       'before': true,
@@ -46,7 +47,7 @@ module.exports = {
     'curly': [2, 'multi-line'],
     'dot-location': [2, 'property'],
     'eol-last': 2,
-    'eqeqeq': [2, 'allow-null'],
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
     'generator-star-spacing': [2, {
       'before': true,
       'after': true

+ 4 - 2
.gitignore

@@ -6,8 +6,8 @@ yarn-debug.log*
 yarn-error.log*
 **/*.log
 
-test/unit/coverage
-test/e2e/reports
+tests/**/coverage/
+tests/e2e/reports
 selenium-debug.log
 
 # Editor directories and files
@@ -17,5 +17,7 @@ selenium-debug.log
 *.ntvs*
 *.njsproj
 *.sln
+*.local
 
 package-lock.json
+yarn.lock

+ 0 - 2
.postcssrc.js

@@ -2,8 +2,6 @@
 
 module.exports = {
   "plugins": {
-    "postcss-import": {},
-    "postcss-url": {},
     // to edit target browsers: use "browserslist" field in package.json
     "autoprefixer": {}
   }

+ 22 - 29
README.md

@@ -4,10 +4,10 @@
 
 <p align="center">
   <a href="https://github.com/vuejs/vue">
-    <img src="https://img.shields.io/badge/vue-2.5.17-brightgreen.svg" alt="vue">
+    <img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
   </a>
   <a href="https://github.com/ElemeFE/element">
-    <img src="https://img.shields.io/badge/element--ui-2.4.11-brightgreen.svg" alt="element-ui">
+    <img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
   </a>
   <a href="https://travis-ci.org/PanJiaChen/vue-element-admin" rel="nofollow">
     <img src="https://travis-ci.org/PanJiaChen/vue-element-admin.svg?branch=master" alt="Build Status">
@@ -30,48 +30,41 @@ English | [简体中文](./README.zh-CN.md)
 
 ## Introduction
 
-[vue-element-admin](http://panjiachen.github.io/vue-element-admin) is a front-end management background integration solution. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element](https://github.com/ElemeFE/element).
+[vue-element-admin](http://panjiachen.github.io/vue-element-admin) is a production-ready front-end solution for admin interfaces. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element-ui](https://github.com/ElemeFE/element).
 
 It is a magical vue admin based on the newest development stack of vue, built-in i18n solution, typical templates for enterprise applications, lots of awesome features. It helps you build a large complex Single-Page Applications. I believe whatever your needs are, this project will help you.
 
-**[v4.0](https://github.com/PanJiaChen/vue-element-admin/tree/v4.0) has in beta. It built on vue-cli@3, optimized a lot of code and added a lot of new features. Welcome to use and make suggestions.**
-
 - [Preview](http://panjiachen.github.io/vue-element-admin)
 
 - [Documentation](https://panjiachen.github.io/vue-element-admin-site/)
 
 - [Gitter](https://gitter.im/vue-element-admin/discuss)
 
-- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
-
 - [Donate](https://panjiachen.github.io/vue-element-admin-site/donate/)
 
-- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览
+- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
 
-**This project is positioned as a background integration solution and is not suitable for secondary development as a basic template.**
+- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览
 
 - Base template recommends using: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
 - Desktop: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
 - Typescript: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
 
-**This project does not support low version browsers (e.g. IE). Please add polyfill yourself if you need them.**
-
-**Note: This project uses element-ui@2.3.0+ version, so the minimum compatible vue@2.5.0+**
+**The current version is `4.0-beta`. If you find a problem, please put [issue](https://github.com/PanJiaChen/vue-element-admin/issues/new). If you want to use the old version - stable version, you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0)**
 
-**Start using `webpack4` from `v3.8.0`. If you still want to continue using `webpack3`, please use this branch [webpack3](https://github.com/PanJiaChen/vue-element-admin/tree/webpack3)**
+**This project does not support low version browsers (e.g. IE). Please add polyfill by yourself.**
 
 ## Preparation
 
-You need to install [node](http://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](http://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock).
+You need to install [node](http://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](http://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock).
 Understanding and learning this knowledge in advance will greatly help the use of this project.
 
----
-
  <p align="center">
   <img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
 </p>
 
 ## Sponsors
+
 Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
 
 <a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
@@ -84,6 +77,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
 - Permission Authentication
   - Page permission
   - Directive permission
+  - Permission configuration page
   - Two-step login
 
 - Multi-environment build
@@ -107,14 +101,13 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
 
 - Excel
   - Export Excel
-  - Export zip
   - Upload Excel
   - Visualization Excel
+  - Export zip
 
 - Table
   - Dynamic Table
   - Drag And Drop Table
-  - Tree Table
   - Inline Edit Table
 
 - Error Page
@@ -148,6 +141,9 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
 # clone the project
 git clone https://github.com/PanJiaChen/vue-element-admin.git
 
+# enter the project directory
+cd vue-element-admin
+
 # install dependency
 npm install
 
@@ -155,13 +151,13 @@ npm install
 npm run dev
 ```
 
-This will automatically open http://localhost:9527.
+This will automatically open http://localhost:9527
 
 ## Build
 
 ```bash
 # build for test environment
-npm run build:sit
+npm run build:stage
 
 # build for production environment
 npm run build:prod
@@ -170,19 +166,16 @@ npm run build:prod
 ## Advanced
 
 ```bash
-# --report to build with bundle size analytics
-npm run build:prod --report
-
-# --generate a bundle size analytics. default: bundle-report.html
-npm run build:prod --generate_report
+# preview the release environment effect
+npm run preview
 
-# --preview to start a server in local to preview
-npm run build:prod --preview
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
 
-# lint code
+# code format check
 npm run lint
 
-# auto fix
+# code format check and auto fix
 npm run lint -- --fix
 ```
 

File diff suppressed because it is too large
+ 30 - 33
README.zh-CN.md


+ 5 - 0
babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/app'
+  ]
+}

+ 0 - 67
build/build.js

@@ -1,67 +0,0 @@
-'use strict'
-require('./check-versions')()
-
-const ora = require('ora')
-const rm = require('rimraf')
-const path = require('path')
-const chalk = require('chalk')
-const webpack = require('webpack')
-const config = require('../config')
-const webpackConfig = require('./webpack.prod.conf')
-var connect = require('connect')
-var serveStatic = require('serve-static')
-
-const spinner = ora(
-  'building for ' + process.env.env_config + ' environment...'
-)
-spinner.start()
-
-rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
-  if (err) throw err
-  webpack(webpackConfig, (err, stats) => {
-    spinner.stop()
-    if (err) throw err
-    process.stdout.write(
-      stats.toString({
-        colors: true,
-        modules: false,
-        children: false,
-        chunks: false,
-        chunkModules: false
-      }) + '\n\n'
-    )
-
-    if (stats.hasErrors()) {
-      console.log(chalk.red(' Build failed with errors.\n'))
-      process.exit(1)
-    }
-
-    console.log(chalk.cyan(' Build complete.\n'))
-    console.log(
-      chalk.yellow(
-        ' Tip: built files are meant to be served over an HTTP server.\n' +
-          " Opening index.html over file:// won't work.\n"
-      )
-    )
-
-    if (process.env.npm_config_preview) {
-      const port = 9526
-      const host = 'http://localhost:' + port
-      const basePath = config.build.assetsPublicPath
-      const app = connect()
-
-      app.use(
-        basePath,
-        serveStatic('./dist', {
-          index: ['index.html', '/']
-        })
-      )
-
-      app.listen(port, function() {
-        console.log(
-          chalk.green(`> Listening at  http://localhost:${port}${basePath}`)
-        )
-      })
-    }
-  })
-})

+ 0 - 64
build/check-versions.js

@@ -1,64 +0,0 @@
-'use strict'
-const chalk = require('chalk')
-const semver = require('semver')
-const packageConfig = require('../package.json')
-const shell = require('shelljs')
-
-function exec(cmd) {
-  return require('child_process')
-    .execSync(cmd)
-    .toString()
-    .trim()
-}
-
-const versionRequirements = [
-  {
-    name: 'node',
-    currentVersion: semver.clean(process.version),
-    versionRequirement: packageConfig.engines.node
-  }
-]
-
-if (shell.which('npm')) {
-  versionRequirements.push({
-    name: 'npm',
-    currentVersion: exec('npm --version'),
-    versionRequirement: packageConfig.engines.npm
-  })
-}
-
-module.exports = function() {
-  const warnings = []
-
-  for (let i = 0; i < versionRequirements.length; i++) {
-    const mod = versionRequirements[i]
-
-    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
-      warnings.push(
-        mod.name +
-          ': ' +
-          chalk.red(mod.currentVersion) +
-          ' should be ' +
-          chalk.green(mod.versionRequirement)
-      )
-    }
-  }
-
-  if (warnings.length) {
-    console.log('')
-    console.log(
-      chalk.yellow(
-        'To use this template, you must update following to modules:'
-      )
-    )
-    console.log()
-
-    for (let i = 0; i < warnings.length; i++) {
-      const warning = warnings[i]
-      console.log('  ' + warning)
-    }
-
-    console.log()
-    process.exit(1)
-  }
-}

+ 35 - 0
build/index.js

@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}/report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}

BIN
build/logo.png


+ 0 - 108
build/utils.js

@@ -1,108 +0,0 @@
-'use strict'
-const path = require('path')
-const config = require('../config')
-const MiniCssExtractPlugin = require('mini-css-extract-plugin')
-const packageConfig = require('../package.json')
-
-exports.assetsPath = function(_path) {
-  const assetsSubDirectory =
-    process.env.NODE_ENV === 'production'
-      ? config.build.assetsSubDirectory
-      : config.dev.assetsSubDirectory
-
-  return path.posix.join(assetsSubDirectory, _path)
-}
-
-exports.cssLoaders = function(options) {
-  options = options || {}
-
-  const cssLoader = {
-    loader: 'css-loader',
-    options: {
-      sourceMap: options.sourceMap
-    }
-  }
-
-  const postcssLoader = {
-    loader: 'postcss-loader',
-    options: {
-      sourceMap: options.sourceMap
-    }
-  }
-
-  // generate loader string to be used with extract text plugin
-  function generateLoaders(loader, loaderOptions) {
-    const loaders = []
-
-    // Extract CSS when that option is specified
-    // (which is the case during production build)
-    if (options.extract) {
-      loaders.push(MiniCssExtractPlugin.loader)
-    } else {
-      loaders.push('vue-style-loader')
-    }
-
-    loaders.push(cssLoader)
-
-    if (options.usePostCSS) {
-      loaders.push(postcssLoader)
-    }
-
-    if (loader) {
-      loaders.push({
-        loader: loader + '-loader',
-        options: Object.assign({}, loaderOptions, {
-          sourceMap: options.sourceMap
-        })
-      })
-    }
-
-    return loaders
-  }
-  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
-  return {
-    css: generateLoaders(),
-    postcss: generateLoaders(),
-    less: generateLoaders('less'),
-    sass: generateLoaders('sass', {
-      indentedSyntax: true
-    }),
-    scss: generateLoaders('sass'),
-    stylus: generateLoaders('stylus'),
-    styl: generateLoaders('stylus')
-  }
-}
-
-// Generate loaders for standalone style files (outside of .vue)
-exports.styleLoaders = function(options) {
-  const output = []
-  const loaders = exports.cssLoaders(options)
-
-  for (const extension in loaders) {
-    const loader = loaders[extension]
-    output.push({
-      test: new RegExp('\\.' + extension + '$'),
-      use: loader
-    })
-  }
-
-  return output
-}
-
-exports.createNotifierCallback = () => {
-  const notifier = require('node-notifier')
-
-  return (severity, errors) => {
-    if (severity !== 'error') return
-
-    const error = errors[0]
-    const filename = error.file && error.file.split('!').pop()
-
-    notifier.notify({
-      title: packageConfig.name,
-      message: severity + ': ' + error.name,
-      subtitle: filename || '',
-      icon: path.join(__dirname, 'logo.png')
-    })
-  }
-}

+ 0 - 5
build/vue-loader.conf.js

@@ -1,5 +0,0 @@
-'use strict'
-
-module.exports = {
-  //You can set the vue-loader configuration by yourself.
-}

+ 0 - 107
build/webpack.base.conf.js

@@ -1,107 +0,0 @@
-'use strict'
-const path = require('path')
-const utils = require('./utils')
-const config = require('../config')
-const { VueLoaderPlugin } = require('vue-loader')
-const vueLoaderConfig = require('./vue-loader.conf')
-
-function resolve(dir) {
-  return path.join(__dirname, '..', dir)
-}
-
-const createLintingRule = () => ({
-  test: /\.(js|vue)$/,
-  loader: 'eslint-loader',
-  enforce: 'pre',
-  include: [resolve('src'), resolve('test')],
-  options: {
-    formatter: require('eslint-friendly-formatter'),
-    emitWarning: !config.dev.showEslintErrorsInOverlay
-  }
-})
-
-module.exports = {
-  context: path.resolve(__dirname, '../'),
-  entry: {
-    app: './src/main.js'
-  },
-  output: {
-    path: config.build.assetsRoot,
-    filename: '[name].js',
-    publicPath:
-      process.env.NODE_ENV === 'production'
-        ? config.build.assetsPublicPath
-        : config.dev.assetsPublicPath
-  },
-  resolve: {
-    extensions: ['.js', '.vue', '.json'],
-    alias: {
-      '@': resolve('src')
-    }
-  },
-  module: {
-    rules: [
-      ...(config.dev.useEslint ? [createLintingRule()] : []),
-      {
-        test: /\.vue$/,
-        loader: 'vue-loader',
-        options: vueLoaderConfig
-      },
-      {
-        test: /\.js$/,
-        loader: 'babel-loader?cacheDirectory',
-        include: [
-          resolve('src'),
-          resolve('test'),
-          resolve('node_modules/webpack-dev-server/client')
-        ]
-      },
-      {
-        test: /\.svg$/,
-        loader: 'svg-sprite-loader',
-        include: [resolve('src/icons')],
-        options: {
-          symbolId: 'icon-[name]'
-        }
-      },
-      {
-        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
-        loader: 'url-loader',
-        exclude: [resolve('src/icons')],
-        options: {
-          limit: 10000,
-          name: utils.assetsPath('img/[name].[hash:7].[ext]')
-        }
-      },
-      {
-        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
-        loader: 'url-loader',
-        options: {
-          limit: 10000,
-          name: utils.assetsPath('media/[name].[hash:7].[ext]')
-        }
-      },
-      {
-        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
-        loader: 'url-loader',
-        options: {
-          limit: 10000,
-          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
-        }
-      }
-    ]
-  },
-  plugins: [new VueLoaderPlugin()],
-  node: {
-    // prevent webpack from injecting useless setImmediate polyfill because Vue
-    // source contains it (although only uses it if it's native).
-    setImmediate: false,
-    // prevent webpack from injecting mocks to Node native modules
-    // that does not make sense for the client
-    dgram: 'empty',
-    fs: 'empty',
-    net: 'empty',
-    tls: 'empty',
-    child_process: 'empty'
-  }
-}

+ 0 - 98
build/webpack.dev.conf.js

@@ -1,98 +0,0 @@
-'use strict'
-const path = require('path')
-const utils = require('./utils')
-const webpack = require('webpack')
-const config = require('../config')
-const merge = require('webpack-merge')
-const baseWebpackConfig = require('./webpack.base.conf')
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
-const portfinder = require('portfinder')
-
-function resolve(dir) {
-  return path.join(__dirname, '..', dir)
-}
-
-const HOST = process.env.HOST
-const PORT = process.env.PORT && Number(process.env.PORT)
-
-const devWebpackConfig = merge(baseWebpackConfig, {
-  mode: 'development',
-  module: {
-    rules: utils.styleLoaders({
-      sourceMap: config.dev.cssSourceMap,
-      usePostCSS: true
-    })
-  },
-  // cheap-module-eval-source-map is faster for development
-  devtool: config.dev.devtool,
-
-  // these devServer options should be customized in /config/index.js
-  devServer: {
-    clientLogLevel: 'warning',
-    historyApiFallback: true,
-    hot: true,
-    compress: true,
-    host: HOST || config.dev.host,
-    port: PORT || config.dev.port,
-    open: config.dev.autoOpenBrowser,
-    overlay: config.dev.errorOverlay
-      ? { warnings: false, errors: true }
-      : false,
-    publicPath: config.dev.assetsPublicPath,
-    proxy: config.dev.proxyTable,
-    quiet: true, // necessary for FriendlyErrorsPlugin
-    watchOptions: {
-      poll: config.dev.poll
-    }
-  },
-  plugins: [
-    new webpack.DefinePlugin({
-      'process.env': require('../config/dev.env')
-    }),
-    new webpack.HotModuleReplacementPlugin(),
-    // https://github.com/ampedandwired/html-webpack-plugin
-    new HtmlWebpackPlugin({
-      filename: 'index.html',
-      template: 'index.html',
-      inject: true,
-      favicon: resolve('favicon.ico'),
-      title: 'vue-element-admin',
-      templateParameters: {
-        BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory,
-      },
-    }),
-  ]
-})
-
-module.exports = new Promise((resolve, reject) => {
-  portfinder.basePort = process.env.PORT || config.dev.port
-  portfinder.getPort((err, port) => {
-    if (err) {
-      reject(err)
-    } else {
-      // publish the new Port, necessary for e2e tests
-      process.env.PORT = port
-      // add port to devServer config
-      devWebpackConfig.devServer.port = port
-
-      // Add FriendlyErrorsPlugin
-      devWebpackConfig.plugins.push(
-        new FriendlyErrorsPlugin({
-          compilationSuccessInfo: {
-            messages: [
-              `Your application is running here: http://${
-                devWebpackConfig.devServer.host
-              }:${port}`
-            ]
-          },
-          onErrors: config.dev.notifyOnErrors
-            ? utils.createNotifierCallback()
-            : undefined
-        })
-      )
-
-      resolve(devWebpackConfig)
-    }
-  })
-})

+ 0 - 187
build/webpack.prod.conf.js

@@ -1,187 +0,0 @@
-'use strict'
-const path = require('path')
-const utils = require('./utils')
-const webpack = require('webpack')
-const config = require('../config')
-const merge = require('webpack-merge')
-const baseWebpackConfig = require('./webpack.base.conf')
-const CopyWebpackPlugin = require('copy-webpack-plugin')
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
-const MiniCssExtractPlugin = require('mini-css-extract-plugin')
-const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
-const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
-
-function resolve(dir) {
-  return path.join(__dirname, '..', dir)
-}
-
-const env = require('../config/' + process.env.env_config + '.env')
-
-// For NamedChunksPlugin
-const seen = new Set()
-const nameLength = 4
-
-const webpackConfig = merge(baseWebpackConfig, {
-  mode: 'production',
-  module: {
-    rules: utils.styleLoaders({
-      sourceMap: config.build.productionSourceMap,
-      extract: true,
-      usePostCSS: true
-    })
-  },
-  devtool: config.build.productionSourceMap ? config.build.devtool : false,
-  output: {
-    path: config.build.assetsRoot,
-    filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
-    chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
-  },
-  plugins: [
-    // http://vuejs.github.io/vue-loader/en/workflow/production.html
-    new webpack.DefinePlugin({
-      'process.env': env
-    }),
-    // extract css into its own file
-    new MiniCssExtractPlugin({
-      filename: utils.assetsPath('css/[name].[contenthash:8].css'),
-      chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
-    }),
-    // generate dist index.html with correct asset hash for caching.
-    // you can customize output by editing /index.html
-    // see https://github.com/ampedandwired/html-webpack-plugin
-    new HtmlWebpackPlugin({
-      filename: config.build.index,
-      template: 'index.html',
-      inject: true,
-      favicon: resolve('favicon.ico'),
-      title: 'vue-element-admin',
-      templateParameters: {
-        BASE_URL: config.build.assetsPublicPath + config.build.assetsSubDirectory,
-      },
-      minify: {
-        removeComments: true,
-        collapseWhitespace: true,
-        removeAttributeQuotes: true
-        // more options:
-        // https://github.com/kangax/html-minifier#options-quick-reference
-      }
-      // default sort mode uses toposort which cannot handle cyclic deps
-      // in certain cases, and in webpack 4, chunk order in HTML doesn't
-      // matter anyway
-    }),
-    new ScriptExtHtmlWebpackPlugin({
-      //`runtime` must same as runtimeChunk name. default is `runtime`
-      inline: /runtime\..*\.js$/
-    }),
-    // keep chunk.id stable when chunk has no name
-    new webpack.NamedChunksPlugin(chunk => {
-      if (chunk.name) {
-        return chunk.name
-      }
-      const modules = Array.from(chunk.modulesIterable)
-      if (modules.length > 1) {
-        const hash = require('hash-sum')
-        const joinedHash = hash(modules.map(m => m.id).join('_'))
-        let len = nameLength
-        while (seen.has(joinedHash.substr(0, len))) len++
-        seen.add(joinedHash.substr(0, len))
-        return `chunk-${joinedHash.substr(0, len)}`
-      } else {
-        return modules[0].id
-      }
-    }),
-    // keep module.id stable when vender modules does not change
-    new webpack.HashedModuleIdsPlugin(),
-    // copy custom static assets
-    new CopyWebpackPlugin([
-      {
-        from: path.resolve(__dirname, '../static'),
-        to: config.build.assetsSubDirectory,
-        ignore: ['.*']
-      }
-    ])
-  ],
-  optimization: {
-    splitChunks: {
-      chunks: 'all',
-      cacheGroups: {
-        libs: {
-          name: 'chunk-libs',
-          test: /[\\/]node_modules[\\/]/,
-          priority: 10,
-          chunks: 'initial' // 只打包初始时依赖的第三方
-        },
-        elementUI: {
-          name: 'chunk-elementUI', // 单独将 elementUI 拆包
-          priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
-          test: /[\\/]node_modules[\\/]element-ui[\\/]/
-        },
-        commons: {
-          name: 'chunk-commons',
-          test: resolve('src/components'), // 可自定义拓展你的规则
-          minChunks: 3, // 最小公用次数
-          priority: 5,
-          reuseExistingChunk: true
-        }
-      }
-    },
-    runtimeChunk: 'single',
-    minimizer: [
-      new UglifyJsPlugin({
-        uglifyOptions: {
-          mangle: {
-            safari10: true
-          }
-        },
-        sourceMap: config.build.productionSourceMap,
-        cache: true,
-        parallel: true
-      }),
-      // Compress extracted CSS. We are using this plugin so that possible
-      // duplicated CSS from different components can be deduped.
-      new OptimizeCSSAssetsPlugin()
-    ]
-  }
-})
-
-if (config.build.productionGzip) {
-  const CompressionWebpackPlugin = require('compression-webpack-plugin')
-
-  webpackConfig.plugins.push(
-    new CompressionWebpackPlugin({
-      algorithm: 'gzip',
-      test: new RegExp(
-        '\\.(' + config.build.productionGzipExtensions.join('|') + ')$'
-      ),
-      threshold: 10240,
-      minRatio: 0.8
-    })
-  )
-}
-
-if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) {
-  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
-    .BundleAnalyzerPlugin
-
-  if (config.build.bundleAnalyzerReport) {
-    webpackConfig.plugins.push(
-      new BundleAnalyzerPlugin({
-        analyzerPort: 8080,
-        generateStatsFile: false
-      })
-    )
-  }
-
-  if (config.build.generateAnalyzerReport) {
-    webpackConfig.plugins.push(
-      new BundleAnalyzerPlugin({
-        analyzerMode: 'static',
-        reportFilename: 'bundle-report.html',
-        openAnalyzer: false
-      })
-    )
-  }
-}
-
-module.exports = webpackConfig

+ 0 - 5
config/dev.env.js

@@ -1,5 +0,0 @@
-module.exports = {
-  NODE_ENV: '"development"',
-  ENV_CONFIG: '"dev"',
-  BASE_API: '"https://api-dev"'
-}

+ 0 - 88
config/index.js

@@ -1,88 +0,0 @@
-'use strict'
-// Template version: 1.2.6
-// see http://vuejs-templates.github.io/webpack for documentation.
-
-const path = require('path')
-
-module.exports = {
-  dev: {
-    // Paths
-    assetsSubDirectory: 'static',
-    assetsPublicPath: '/',
-    proxyTable: {},
-
-    // Various Dev Server settings
-
-    // can be overwritten by process.env.HOST
-    // if you want dev by ip, please set host: '0.0.0.0'
-    host: 'localhost',
-    port: 9527, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
-    autoOpenBrowser: true,
-    errorOverlay: true,
-    notifyOnErrors: false,
-    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
-
-    // Use Eslint Loader?
-    // If true, your code will be linted during bundling and
-    // linting errors and warnings will be shown in the console.
-    useEslint: true,
-    // If true, eslint errors and warnings will also be shown in the error overlay
-    // in the browser.
-    showEslintErrorsInOverlay: false,
-
-    /**
-     * Source Maps
-     */
-
-    // https://webpack.js.org/configuration/devtool/#development
-    devtool: 'cheap-source-map',
-
-    // CSS Sourcemaps off by default because relative paths are "buggy"
-    // with this option, according to the CSS-Loader README
-    // (https://github.com/webpack/css-loader#sourcemaps)
-    // In our experience, they generally work as expected,
-    // just be aware of this issue when enabling this option.
-    cssSourceMap: false
-  },
-
-  build: {
-    // Template for index.html
-    index: path.resolve(__dirname, '../dist/index.html'),
-
-    // Paths
-    assetsRoot: path.resolve(__dirname, '../dist'),
-    assetsSubDirectory: 'static',
-
-    /**
-     * You can set by youself according to actual condition
-     * You will need to set this if you plan to deploy your site under a sub path,
-     * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
-     * then assetsPublicPath should be set to "/bar/".
-     * In most cases please use '/' !!!
-     */
-    assetsPublicPath: '/',
-
-    /**
-     * Source Maps
-     */
-    productionSourceMap: false,
-    // https://webpack.js.org/configuration/devtool/#production
-    devtool: 'source-map',
-
-    // Gzip off by default as many popular static hosts such as
-    // Surge or Netlify already gzip all static assets for you.
-    // Before setting to `true`, make sure to:
-    // npm install --save-dev compression-webpack-plugin
-    productionGzip: false,
-    productionGzipExtensions: ['js', 'css'],
-
-    // Run the build command with an extra argument to
-    // View the bundle analyzer report after build finishes:
-    // `npm run build:prod --report`
-    // Set to `true` or `false` to always turn it on or off
-    bundleAnalyzerReport: process.env.npm_config_report || false,
-
-    // `npm run build:prod --generate_report`
-    generateAnalyzerReport: process.env.npm_config_generate_report || false
-  }
-}

+ 0 - 5
config/prod.env.js

@@ -1,5 +0,0 @@
-module.exports = {
-  NODE_ENV: '"production"',
-  ENV_CONFIG: '"prod"',
-  BASE_API: '"https://api-prod"'
-}

+ 0 - 5
config/sit.env.js

@@ -1,5 +0,0 @@
-module.exports = {
-  NODE_ENV: '"production"',
-  ENV_CONFIG: '"sit"',
-  BASE_API: '"https://api-sit"'
-}

+ 27 - 0
jest.config.js

@@ -0,0 +1,27 @@
+module.exports = {
+  verbose: true,
+  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+  transformIgnorePatterns: [
+    'node_modules/(?!(babel-jest|jest-vue-preprocessor)/)'
+  ],
+  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)'
+  ],
+  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+  coverageDirectory: '<rootDir>/tests/unit/coverage',
+  // 'collectCoverage': true,
+  'coverageReporters': [
+    'lcov',
+    'text-summary'
+  ],
+  testURL: 'http://localhost/'
+}

+ 116 - 0
mock/article.js

@@ -0,0 +1,116 @@
+import Mock from 'mockjs'
+
+const List = []
+const count = 100
+
+const baseContent = '<p>我是测试数据我是测试数据</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
+const image_uri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3'
+
+for (let i = 0; i < count; i++) {
+  List.push(Mock.mock({
+    id: '@increment',
+    timestamp: +Mock.Random.date('T'),
+    author: '@first',
+    reviewer: '@first',
+    title: '@title(5, 10)',
+    content_short: 'mock data',
+    content: baseContent,
+    forecast: '@float(0, 100, 2, 2)',
+    importance: '@integer(1, 3)',
+    'type|1': ['CN', 'US', 'JP', 'EU'],
+    'status|1': ['published', 'draft', 'deleted'],
+    display_time: '@datetime',
+    comment_disabled: true,
+    pageviews: '@integer(300, 5000)',
+    image_uri,
+    platforms: ['a-platform']
+  }))
+}
+
+export default [
+  {
+    url: '/article/list',
+    type: 'get',
+    response: config => {
+      const { importance, type, title, page = 1, limit = 20, sort } = config.query
+
+      let mockList = List.filter(item => {
+        if (importance && item.importance !== +importance) return false
+        if (type && item.type !== type) return false
+        if (title && item.title.indexOf(title) < 0) return false
+        return true
+      })
+
+      if (sort === '-id') {
+        mockList = mockList.reverse()
+      }
+
+      const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
+
+      return {
+        code: 20000,
+        data: {
+          total: mockList.length,
+          items: pageList
+        }
+      }
+    }
+  },
+
+  {
+    url: '/article/detail',
+    type: 'get',
+    response: config => {
+      const { id } = config.query
+      for (const article of List) {
+        if (article.id === +id) {
+          return {
+            code: 20000,
+            data: article
+          }
+        }
+      }
+    }
+  },
+
+  {
+    url: '/article/pv',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: {
+          pvData: [
+            { key: 'PC', pv: 1024 },
+            { key: 'mobile', pv: 1024 },
+            { key: 'ios', pv: 1024 },
+            { key: 'android', pv: 1024 }
+          ]
+        }
+      }
+    }
+  },
+
+  {
+    url: '/article/create',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  },
+
+  {
+    url: '/article/update',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]
+

+ 54 - 0
mock/index.js

@@ -0,0 +1,54 @@
+import Mock from 'mockjs'
+import mocks from './mocks'
+import { param2Obj } from '../src/utils'
+
+const MOCK_API_BASE = '/mock'
+
+export function mockXHR() {
+  // 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
+  // https://github.com/nuysoft/Mock/issues/300
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function() {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+      this.custom.xhr.responseType = this.responseType
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHR2ExpressReqWrap(respond) {
+    return function(options) {
+      let result = null
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        // https://expressjs.com/en/4x/api.html#req
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: param2Obj(url)
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  for (const i of mocks) {
+    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+  }
+}
+
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${MOCK_API_BASE}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+export default mocks.map(route => {
+  return responseFake(route.url, route.type, route.response)
+})

+ 12 - 0
mock/mocks.js

@@ -0,0 +1,12 @@
+import user from './user'
+import role from './role'
+import article from './article'
+import search from './remoteSearch'
+
+export default [
+  ...user,
+  ...role,
+  ...article,
+  ...search
+]
+

+ 51 - 0
mock/remoteSearch.js

@@ -0,0 +1,51 @@
+import Mock from 'mockjs'
+
+const NameList = []
+const count = 100
+
+for (let i = 0; i < count; i++) {
+  NameList.push(Mock.mock({
+    name: '@first'
+  }))
+}
+NameList.push({ name: 'mock-Pan' })
+
+export default [
+  // username search
+  {
+    url: '/search/user',
+    type: 'get',
+    response: config => {
+      const { name } = config.query
+      const mockNameList = NameList.filter(item => {
+        const lowerCaseName = item.name.toLowerCase()
+        return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0)
+      })
+      return {
+        code: 20000,
+        data: { items: mockNameList }
+      }
+    }
+  },
+
+  // transaction list
+  {
+    url: '/transaction/list',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: {
+          total: 20,
+          'items|20': [{
+            order_no: '@guid()',
+            timestamp: +Mock.Random.date('T'),
+            username: '@name()',
+            price: '@float(1000, 15000, 0, 2)',
+            'status|1': ['success', 'pending']
+          }]
+        }
+      }
+    }
+  }
+]

+ 98 - 0
mock/role/index.js

@@ -0,0 +1,98 @@
+import Mock from 'mockjs'
+import { deepClone } from '../../src/utils/index.js'
+import { asyncRoutes, constantRoutes } from './routes.js'
+
+const routes = deepClone([...constantRoutes, ...asyncRoutes])
+
+const roles = [
+  {
+    key: 'admin',
+    name: 'admin',
+    description: 'Super Administrator. Have access to view all pages.',
+    routes: routes
+  },
+  {
+    key: 'editor',
+    name: 'editor',
+    description: 'Normal Editor. Can see all pages except permission page',
+    routes: routes.filter(i => i.path !== '/permission')// just a mock
+  },
+  {
+    key: 'visitor',
+    name: 'visitor',
+    description: 'Just a visitor. Can only see the home page and the document page',
+    routes: [{
+      path: '',
+      redirect: 'dashboard',
+      children: [
+        {
+          path: 'dashboard',
+          name: 'Dashboard',
+          meta: { title: 'dashboard', icon: 'dashboard' }
+        }
+      ]
+    }]
+  }
+]
+
+export default [
+  // mock get all routes form server
+  {
+    url: '/routes',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: routes
+      }
+    }
+  },
+
+  // mock get all roles form server
+  {
+    url: '/roles',
+    type: 'get',
+    response: _ => {
+      return {
+        code: 20000,
+        data: roles
+      }
+    }
+  },
+
+  // add role
+  {
+    url: '/role',
+    type: 'post',
+    response: {
+      code: 20000,
+      data: {
+        key: Mock.mock('@integer(300, 5000)')
+      }
+    }
+  },
+
+  // update role
+  {
+    url: '/role/[A-Za-z0-9]',
+    type: 'put',
+    response: {
+      code: 20000,
+      data: {
+        status: 'success'
+      }
+    }
+  },
+
+  // delete role
+  {
+    url: '/role/[A-Za-z0-9]',
+    type: 'delete',
+    response: {
+      code: 20000,
+      data: {
+        status: 'success'
+      }
+    }
+  }
+]

+ 525 - 0
mock/role/routes.js

@@ -0,0 +1,525 @@
+// Just a mock data
+
+export const constantRoutes = [
+  {
+    path: '/redirect',
+    component: 'layout/Layout',
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path*',
+        component: 'views/redirect/index'
+      }
+    ]
+  },
+  {
+    path: '/login',
+    component: 'views/login/index',
+    hidden: true
+  },
+  {
+    path: '/auth-redirect',
+    component: 'views/login/authredirect',
+    hidden: true
+  },
+  {
+    path: '/404',
+    component: 'views/errorPage/404',
+    hidden: true
+  },
+  {
+    path: '/401',
+    component: 'views/errorPage/401',
+    hidden: true
+  },
+  {
+    path: '',
+    component: 'layout/Layout',
+    redirect: 'dashboard',
+    children: [
+      {
+        path: 'dashboard',
+        component: 'views/dashboard/index',
+        name: 'Dashboard',
+        meta: { title: 'dashboard', icon: 'dashboard', noCache: true, affix: true }
+      }
+    ]
+  },
+  {
+    path: '/documentation',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/documentation/index',
+        name: 'Documentation',
+        meta: { title: 'documentation', icon: 'documentation', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/guide',
+    component: 'layout/Layout',
+    redirect: '/guide/index',
+    children: [
+      {
+        path: 'index',
+        component: 'views/guide/index',
+        name: 'Guide',
+        meta: { title: 'guide', icon: 'guide', noCache: true }
+      }
+    ]
+  }
+]
+
+export const asyncRoutes = [
+  {
+    path: '/permission',
+    component: 'layout/Layout',
+    redirect: '/permission/index',
+    alwaysShow: true,
+    meta: {
+      title: 'permission',
+      icon: 'lock',
+      roles: ['admin', 'editor']
+    },
+    children: [
+      {
+        path: 'page',
+        component: 'views/permission/page',
+        name: 'PagePermission',
+        meta: {
+          title: 'pagePermission',
+          roles: ['admin']
+        }
+      },
+      {
+        path: 'directive',
+        component: 'views/permission/directive',
+        name: 'DirectivePermission',
+        meta: {
+          title: 'directivePermission'
+        }
+      },
+      {
+        path: 'role',
+        component: 'views/permission/role',
+        name: 'RolePermission',
+        meta: {
+          title: 'rolePermission',
+          roles: ['admin']
+        }
+      }
+    ]
+  },
+
+  {
+    path: '/icon',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/svg-icons/index',
+        name: 'Icons',
+        meta: { title: 'icons', icon: 'icon', noCache: true }
+      }
+    ]
+  },
+
+  {
+    path: '/components',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    name: 'ComponentDemo',
+    meta: {
+      title: 'components',
+      icon: 'component'
+    },
+    children: [
+      {
+        path: 'tinymce',
+        component: 'views/components-demo/tinymce',
+        name: 'TinymceDemo',
+        meta: { title: 'tinymce' }
+      },
+      {
+        path: 'markdown',
+        component: 'views/components-demo/markdown',
+        name: 'MarkdownDemo',
+        meta: { title: 'markdown' }
+      },
+      {
+        path: 'json-editor',
+        component: 'views/components-demo/jsonEditor',
+        name: 'JsonEditorDemo',
+        meta: { title: 'jsonEditor' }
+      },
+      {
+        path: 'splitpane',
+        component: 'views/components-demo/splitpane',
+        name: 'SplitpaneDemo',
+        meta: { title: 'splitPane' }
+      },
+      {
+        path: 'avatar-upload',
+        component: 'views/components-demo/avatarUpload',
+        name: 'AvatarUploadDemo',
+        meta: { title: 'avatarUpload' }
+      },
+      {
+        path: 'dropzone',
+        component: 'views/components-demo/dropzone',
+        name: 'DropzoneDemo',
+        meta: { title: 'dropzone' }
+      },
+      {
+        path: 'sticky',
+        component: 'views/components-demo/sticky',
+        name: 'StickyDemo',
+        meta: { title: 'sticky' }
+      },
+      {
+        path: 'count-to',
+        component: 'views/components-demo/countTo',
+        name: 'CountToDemo',
+        meta: { title: 'countTo' }
+      },
+      {
+        path: 'mixin',
+        component: 'views/components-demo/mixin',
+        name: 'ComponentMixinDemo',
+        meta: { title: 'componentMixin' }
+      },
+      {
+        path: 'back-to-top',
+        component: 'views/components-demo/backToTop',
+        name: 'BackToTopDemo',
+        meta: { title: 'backToTop' }
+      },
+      {
+        path: 'drag-dialog',
+        component: 'views/components-demo/dragDialog',
+        name: 'DragDialogDemo',
+        meta: { title: 'dragDialog' }
+      },
+      {
+        path: 'drag-select',
+        component: 'views/components-demo/dragSelect',
+        name: 'DragSelectDemo',
+        meta: { title: 'dragSelect' }
+      },
+      {
+        path: 'dnd-list',
+        component: 'views/components-demo/dndList',
+        name: 'DndListDemo',
+        meta: { title: 'dndList' }
+      },
+      {
+        path: 'drag-kanban',
+        component: 'views/components-demo/dragKanban',
+        name: 'DragKanbanDemo',
+        meta: { title: 'dragKanban' }
+      }
+    ]
+  },
+  {
+    path: '/charts',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    name: 'Charts',
+    meta: {
+      title: 'charts',
+      icon: 'chart'
+    },
+    children: [
+      {
+        path: 'keyboard',
+        component: 'views/charts/keyboard',
+        name: 'KeyboardChart',
+        meta: { title: 'keyboardChart', noCache: true }
+      },
+      {
+        path: 'line',
+        component: 'views/charts/line',
+        name: 'LineChart',
+        meta: { title: 'lineChart', noCache: true }
+      },
+      {
+        path: 'mixchart',
+        component: 'views/charts/mixChart',
+        name: 'MixChart',
+        meta: { title: 'mixChart', noCache: true }
+      }
+    ]
+  },
+  {
+    path: '/nested',
+    component: 'layout/Layout',
+    redirect: '/nested/menu1/menu1-1',
+    name: 'Nested',
+    meta: {
+      title: 'nested',
+      icon: 'nested'
+    },
+    children: [
+      {
+        path: 'menu1',
+        component: 'views/nested/menu1/index',
+        name: 'Menu1',
+        meta: { title: 'menu1' },
+        redirect: '/nested/menu1/menu1-1',
+        children: [
+          {
+            path: 'menu1-1',
+            component: 'views/nested/menu1/menu1-1',
+            name: 'Menu1-1',
+            meta: { title: 'menu1-1' }
+          },
+          {
+            path: 'menu1-2',
+            component: 'views/nested/menu1/menu1-2',
+            name: 'Menu1-2',
+            redirect: '/nested/menu1/menu1-2/menu1-2-1',
+            meta: { title: 'menu1-2' },
+            children: [
+              {
+                path: 'menu1-2-1',
+                component: 'views/nested/menu1/menu1-2/menu1-2-1',
+                name: 'Menu1-2-1',
+                meta: { title: 'menu1-2-1' }
+              },
+              {
+                path: 'menu1-2-2',
+                component: 'views/nested/menu1/menu1-2/menu1-2-2',
+                name: 'Menu1-2-2',
+                meta: { title: 'menu1-2-2' }
+              }
+            ]
+          },
+          {
+            path: 'menu1-3',
+            component: 'views/nested/menu1/menu1-3',
+            name: 'Menu1-3',
+            meta: { title: 'menu1-3' }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2',
+        component: 'views/nested/menu2/index',
+        meta: { title: 'menu2' }
+      }
+    ]
+  },
+
+  {
+    path: '/example',
+    component: 'layout/Layout',
+    redirect: '/example/list',
+    name: 'Example',
+    meta: {
+      title: 'example',
+      icon: 'example'
+    },
+    children: [
+      {
+        path: 'create',
+        component: 'views/example/create',
+        name: 'CreateArticle',
+        meta: { title: 'createArticle', icon: 'edit' }
+      },
+      {
+        path: 'edit/:id(\\d+)',
+        component: 'views/example/edit',
+        name: 'EditArticle',
+        meta: { title: 'editArticle', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'list',
+        component: 'views/example/list',
+        name: 'ArticleList',
+        meta: { title: 'articleList', icon: 'list' }
+      }
+    ]
+  },
+
+  {
+    path: '/tab',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/tab/index',
+        name: 'Tab',
+        meta: { title: 'tab', icon: 'tab' }
+      }
+    ]
+  },
+
+  {
+    path: '/error',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    name: 'ErrorPages',
+    meta: {
+      title: 'errorPages',
+      icon: '404'
+    },
+    children: [
+      {
+        path: '401',
+        component: 'views/errorPage/401',
+        name: 'Page401',
+        meta: { title: 'page401', noCache: true }
+      },
+      {
+        path: '404',
+        component: 'views/errorPage/404',
+        name: 'Page404',
+        meta: { title: 'page404', noCache: true }
+      }
+    ]
+  },
+
+  {
+    path: '/error-log',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'log',
+        component: 'views/errorLog/index',
+        name: 'ErrorLog',
+        meta: { title: 'errorLog', icon: 'bug' }
+      }
+    ]
+  },
+
+  {
+    path: '/excel',
+    component: 'layout/Layout',
+    redirect: '/excel/export-excel',
+    name: 'Excel',
+    meta: {
+      title: 'excel',
+      icon: 'excel'
+    },
+    children: [
+      {
+        path: 'export-excel',
+        component: 'views/excel/exportExcel',
+        name: 'ExportExcel',
+        meta: { title: 'exportExcel' }
+      },
+      {
+        path: 'export-selected-excel',
+        component: 'views/excel/selectExcel',
+        name: 'SelectExcel',
+        meta: { title: 'selectExcel' }
+      },
+      {
+        path: 'export-merge-header',
+        component: 'views/excel/mergeHeader',
+        name: 'MergeHeader',
+        meta: { title: 'mergeHeader' }
+      },
+      {
+        path: 'upload-excel',
+        component: 'views/excel/uploadExcel',
+        name: 'UploadExcel',
+        meta: { title: 'uploadExcel' }
+      }
+    ]
+  },
+
+  {
+    path: '/zip',
+    component: 'layout/Layout',
+    redirect: '/zip/download',
+    alwaysShow: true,
+    meta: { title: 'zip', icon: 'zip' },
+    children: [
+      {
+        path: 'download',
+        component: 'views/zip/index',
+        name: 'ExportZip',
+        meta: { title: 'exportZip' }
+      }
+    ]
+  },
+
+  {
+    path: '/pdf',
+    component: 'layout/Layout',
+    redirect: '/pdf/index',
+    children: [
+      {
+        path: 'index',
+        component: 'views/pdf/index',
+        name: 'PDF',
+        meta: { title: 'pdf', icon: 'pdf' }
+      }
+    ]
+  },
+  {
+    path: '/pdf/download',
+    component: 'views/pdf/download',
+    hidden: true
+  },
+
+  {
+    path: '/theme',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'index',
+        component: 'views/theme/index',
+        name: 'Theme',
+        meta: { title: 'theme', icon: 'theme' }
+      }
+    ]
+  },
+
+  {
+    path: '/clipboard',
+    component: 'layout/Layout',
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'index',
+        component: 'views/clipboard/index',
+        name: 'ClipboardDemo',
+        meta: { title: 'clipboardDemo', icon: 'clipboard' }
+      }
+    ]
+  },
+
+  {
+    path: '/i18n',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'index',
+        component: 'views/i18n-demo/index',
+        name: 'I18n',
+        meta: { title: 'i18n', icon: 'international' }
+      }
+    ]
+  },
+
+  {
+    path: 'external-link',
+    component: 'layout/Layout',
+    children: [
+      {
+        path: 'https://github.com/PanJiaChen/vue-element-admin',
+        meta: { title: 'externalLink', icon: 'link' }
+      }
+    ]
+  },
+
+  { path: '*', redirect: '/404', hidden: true }
+]

+ 84 - 0
mock/user.js

@@ -0,0 +1,84 @@
+
+const tokens = {
+  admin: {
+    token: 'admin-token'
+  },
+  editor: {
+    token: 'editor-token'
+  }
+}
+
+const users = {
+  'admin-token': {
+    roles: ['admin'],
+    introduction: 'I am a super administrator',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Super Admin'
+  },
+  'editor-token': {
+    roles: ['editor'],
+    introduction: 'I am an editor',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Normal Editor'
+  }
+}
+
+export default [
+  // user login
+  {
+    url: '/user/login',
+    type: 'post',
+    response: config => {
+      const { username } = config.body
+      const token = tokens[username]
+
+      // mock error
+      if (!token) {
+        return {
+          code: 60204,
+          message: 'Account and password are incorrect.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: token
+      }
+    }
+  },
+
+  // get user info
+  {
+    url: '/user/info\.*',
+    type: 'get',
+    response: config => {
+      const { token } = config.query
+      const info = users[token]
+
+      // mock error
+      if (!info) {
+        return {
+          code: 50008,
+          message: 'Login failed, unable to get user details.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: info
+      }
+    }
+  },
+
+  // user logout
+  {
+    url: '/user/logout',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]

+ 64 - 84
package.json

@@ -1,17 +1,24 @@
 {
   "name": "vue-element-admin",
-  "version": "3.11.0",
-  "description": "A magical vue admin. Typical templates for enterprise applications. Newest development stack of vue. Lots of awesome features",
+  "version": "4.0.0",
+  "description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
   "author": "Pan <panfree23@gmail.com>",
   "license": "MIT",
   "scripts": {
-    "dev": "cross-env BABEL_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
-    "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
-    "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
     "lint": "eslint --ext .js,.vue src",
-    "test": "npm run lint",
-    "precommit": "lint-staged",
-    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
+    "test:unit": "vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "new": "plop"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
   },
   "lint-staged": {
     "src/**/*.{js,vue}": [
@@ -21,10 +28,12 @@
   },
   "keywords": [
     "vue",
-    "element-ui",
     "admin",
-    "management-system",
-    "admin-template"
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
   ],
   "repository": {
     "type": "git",
@@ -35,93 +44,64 @@
   },
   "dependencies": {
     "axios": "0.18.0",
-    "clipboard": "1.7.1",
-    "codemirror": "5.39.2",
-    "driver.js": "0.8.1",
-    "dropzone": "5.2.0",
-    "echarts": "4.1.0",
-    "element-ui": "2.4.11",
-    "file-saver": "1.3.8",
-    "fuse.js": "3.4.2",
+    "clipboard": "2.0.4",
+    "codemirror": "5.45.0",
+    "driver.js": "0.9.5",
+    "dropzone": "5.5.1",
+    "echarts": "4.2.1",
+    "element-ui": "2.7.0",
+    "file-saver": "2.0.1",
+    "fuse.js": "3.4.4",
     "js-cookie": "2.2.0",
     "jsonlint": "1.6.3",
-    "jszip": "3.1.5",
-    "mockjs": "1.0.1-beta3",
+    "jszip": "3.2.1",
     "normalize.css": "7.0.0",
     "nprogress": "0.2.0",
-    "screenfull": "4.0.0",
-    "showdown": "1.8.6",
-    "sortablejs": "1.7.0",
-    "tui-editor": "1.2.7",
-    "vue": "2.5.17",
+    "path-to-regexp": "2.4.0",
+    "screenfull": "4.2.0",
+    "showdown": "1.9.0",
+    "sortablejs": "1.8.4",
+    "tui-editor": "1.3.3",
+    "vue": "2.6.10",
     "vue-count-to": "1.0.13",
     "vue-i18n": "7.3.2",
     "vue-router": "3.0.2",
-    "vue-splitpane": "1.0.2",
-    "vuedraggable": "^2.16.0",
-    "vuex": "3.0.1",
-    "xlsx": "^0.11.16"
+    "vue-splitpane": "1.0.4",
+    "vuedraggable": "2.20.0",
+    "vuex": "3.1.0",
+    "xlsx": "0.14.1"
   },
   "devDependencies": {
-    "autoprefixer": "8.5.0",
-    "babel-core": "6.26.3",
-    "babel-eslint": "8.2.6",
-    "babel-helper-vue-jsx-merge-props": "2.0.3",
-    "babel-loader": "7.1.5",
-    "babel-plugin-dynamic-import-node": "2.0.0",
-    "babel-plugin-syntax-jsx": "6.18.0",
-    "babel-plugin-transform-runtime": "6.23.0",
-    "babel-plugin-transform-vue-jsx": "3.7.0",
-    "babel-preset-env": "1.7.0",
-    "babel-preset-stage-2": "6.24.1",
-    "chalk": "2.4.1",
-    "compression-webpack-plugin": "2.0.0",
+    "@babel/core": "7.0.0",
+    "@babel/register": "7.0.0",
+    "@vue/cli-plugin-babel": "3.5.3",
+    "@vue/cli-plugin-unit-jest": "3.5.3",
+    "@vue/cli-service": "3.5.3",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "babel-core": "7.0.0-bridge.0",
+    "babel-eslint": "10.0.1",
+    "babel-jest": "23.6.0",
+    "chalk": "2.4.2",
     "connect": "3.6.6",
-    "copy-webpack-plugin": "4.5.2",
-    "cross-env": "5.2.0",
-    "css-loader": "1.0.0",
-    "eslint": "5.15.2",
-    "eslint-friendly-formatter": "4.0.1",
-    "eslint-loader": "2.1.2",
+    "eslint": "5.15.3",
     "eslint-plugin-vue": "5.2.2",
-    "file-loader": "1.1.11",
-    "friendly-errors-webpack-plugin": "1.7.0",
-    "hash-sum": "1.0.2",
-    "html-webpack-plugin": "4.0.0-alpha",
-    "husky": "0.14.3",
-    "lint-staged": "7.2.2",
-    "mini-css-extract-plugin": "0.4.1",
-    "node-notifier": "5.2.1",
-    "node-sass": "^4.7.2",
-    "optimize-css-assets-webpack-plugin": "5.0.0",
-    "ora": "3.0.0",
-    "path-to-regexp": "2.4.0",
-    "portfinder": "1.0.13",
-    "postcss-import": "11.1.0",
-    "postcss-loader": "2.1.6",
-    "postcss-url": "7.3.2",
-    "rimraf": "2.6.2",
-    "sass-loader": "7.0.3",
-    "script-ext-html-webpack-plugin": "2.0.1",
+    "html-webpack-plugin": "3.2.0",
+    "husky": "1.3.1",
+    "lint-staged": "8.1.5",
+    "mockjs": "1.0.1-beta3",
+    "node-sass": "^4.9.0",
+    "plop": "2.3.0",
+    "runjs": "^4.3.2",
+    "sass-loader": "^7.1.0",
+    "script-ext-html-webpack-plugin": "2.1.3",
     "script-loader": "0.7.2",
-    "semver": "5.5.0",
-    "serve-static": "1.13.2",
-    "shelljs": "0.8.2",
-    "svg-sprite-loader": "3.8.0",
-    "svgo": "1.0.5",
-    "uglifyjs-webpack-plugin": "1.2.7",
-    "url-loader": "1.0.1",
-    "vue-loader": "15.3.0",
-    "vue-style-loader": "4.1.2",
-    "vue-template-compiler": "2.5.17",
-    "webpack": "4.16.5",
-    "webpack-bundle-analyzer": "2.13.1",
-    "webpack-cli": "3.1.0",
-    "webpack-dev-server": "3.1.14",
-    "webpack-merge": "4.1.4"
+    "serve-static": "^1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.0",
+    "vue-template-compiler": "2.6.10"
   },
   "engines": {
-    "node": ">= 6.0.0",
+    "node": ">=8.9",
     "npm": ">= 3.0.0"
   },
   "browserslist": [

+ 26 - 0
plop-templates/component/index.hbs

@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}

+ 55 - 0
plop-templates/component/prompt.js

@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate vue component',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'component name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'Components require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{properCase name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/components/${name}/index.vue`,
+      templateFile: 'plop-templates/component/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}

+ 9 - 0
plop-templates/utils.js

@@ -0,0 +1,9 @@
+exports.notEmpty = name => {
+  return v => {
+    if (!v || v.trim === '') {
+      return `${name} is required`
+    } else {
+      return true
+    }
+  }
+}

+ 26 - 0
plop-templates/view/index.hbs

@@ -0,0 +1,26 @@
+{{#if template}}
+<template>
+  <div />
+</template>
+{{/if}}
+
+{{#if script}}
+<script>
+export default {
+  name: '{{ properCase name }}',
+  props: {},
+  data() {
+    return {}
+  },
+  created() {},
+  mounted() {},
+  methods: {}
+}
+</script>
+{{/if}}
+
+{{#if style}}
+<style lang="scss" scoped>
+
+</style>
+{{/if}}

+ 55 - 0
plop-templates/view/prompt.js

@@ -0,0 +1,55 @@
+const { notEmpty } = require('../utils.js')
+
+module.exports = {
+  description: 'generate a view',
+  prompts: [{
+    type: 'input',
+    name: 'name',
+    message: 'view name please',
+    validate: notEmpty('name')
+  },
+  {
+    type: 'checkbox',
+    name: 'blocks',
+    message: 'Blocks:',
+    choices: [{
+      name: '<template>',
+      value: 'template',
+      checked: true
+    },
+    {
+      name: '<script>',
+      value: 'script',
+      checked: true
+    },
+    {
+      name: 'style',
+      value: 'style',
+      checked: true
+    }
+    ],
+    validate(value) {
+      if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
+        return 'View require at least a <script> or <template> tag.'
+      }
+      return true
+    }
+  }
+  ],
+  actions: data => {
+    const name = '{{name}}'
+    const actions = [{
+      type: 'add',
+      path: `src/views/${name}/index.vue`,
+      templateFile: 'plop-templates/view/index.hbs',
+      data: {
+        name: name,
+        template: data.blocks.includes('template'),
+        script: data.blocks.includes('script'),
+        style: data.blocks.includes('style')
+      }
+    }]
+
+    return actions
+  }
+}

+ 7 - 0
plopfile.js

@@ -0,0 +1,7 @@
+const viewGenerator = require('./plop-templates/view/prompt')
+const componentGenerator = require('./plop-templates/component/prompt')
+
+module.exports = function(plop) {
+  plop.setGenerator('view', viewGenerator)
+  plop.setGenerator('component', componentGenerator)
+}

favicon.ico → public/favicon.ico


+ 3 - 2
index.html

@@ -5,10 +5,11 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
     <meta name="renderer" content="webkit">
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
-    <title>vue-element-admin</title>
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
   </head>
   <body>
-    <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
+    <script src="<%= BASE_URL %>static/tinymce4.7.5/tinymce.min.js"></script>
     <div id="app"></div>
     <!-- built files will be auto injected -->
   </body>

static/tinymce4.7.5/langs/zh_CN.js → public/static/tinymce4.7.5/langs/zh_CN.js


static/tinymce4.7.5/plugins/codesample/css/prism.css → public/static/tinymce4.7.5/plugins/codesample/css/prism.css


static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif


static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif → public/static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif


static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css → public/static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css


static/tinymce4.7.5/skins/lightgray/content.inline.min.css → public/static/tinymce4.7.5/skins/lightgray/content.inline.min.css


static/tinymce4.7.5/skins/lightgray/content.min.css → public/static/tinymce4.7.5/skins/lightgray/content.min.css


static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff


static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot


static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg


static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf


static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff


static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot


static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg


static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf


static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff → public/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff


static/tinymce4.7.5/skins/lightgray/img/anchor.gif → public/static/tinymce4.7.5/skins/lightgray/img/anchor.gif


static/tinymce4.7.5/skins/lightgray/img/loader.gif → public/static/tinymce4.7.5/skins/lightgray/img/loader.gif


static/tinymce4.7.5/skins/lightgray/img/object.gif → public/static/tinymce4.7.5/skins/lightgray/img/object.gif


static/tinymce4.7.5/skins/lightgray/img/trans.gif → public/static/tinymce4.7.5/skins/lightgray/img/trans.gif


static/tinymce4.7.5/skins/lightgray/skin.min.css → public/static/tinymce4.7.5/skins/lightgray/skin.min.css


static/tinymce4.7.5/skins/lightgray/skin.min.css.map → public/static/tinymce4.7.5/skins/lightgray/skin.min.css.map


static/tinymce4.7.5/tinymce.min.js → public/static/tinymce4.7.5/tinymce.min.js


+ 9 - 1
src/api/remoteSearch.js

@@ -1,9 +1,17 @@
 import request from '@/utils/request'
 
-export function userSearch(name) {
+export function searchUser(name) {
   return request({
     url: '/search/user',
     method: 'get',
     params: { name }
   })
 }
+
+export function transactionList(query) {
+  return request({
+    url: '/transaction/list',
+    method: 'get',
+    params: query
+  })
+}

+ 10 - 10
src/api/role.js

@@ -14,25 +14,25 @@ export function getRoles() {
   })
 }
 
-export function deleteRole(id) {
-  return request({
-    url: `/roles/${id}`,
-    method: 'delete'
-  })
-}
-
 export function addRole(data) {
   return request({
-    url: '/roles',
+    url: '/role',
     method: 'post',
     data
   })
 }
 
-export function updateRole(key, data) {
+export function updateRole(id, data) {
   return request({
-    url: `/roles/${key}`,
+    url: `/role/${id}`,
     method: 'put',
     data
   })
 }
+
+export function deleteRole(id) {
+  return request({
+    url: `/role/${id}`,
+    method: 'delete'
+  })
+}

+ 0 - 9
src/api/transaction.js

@@ -1,9 +0,0 @@
-import request from '@/utils/request'
-
-export function fetchList(query) {
-  return request({
-    url: '/transaction/list',
-    method: 'get',
-    params: query
-  })
-}

+ 9 - 13
src/api/login.js

@@ -1,29 +1,25 @@
 import request from '@/utils/request'
 
-export function loginByUsername(username, password) {
-  const data = {
-    username,
-    password
-  }
+export function login(data) {
   return request({
-    url: '/login/login',
+    url: '/user/login',
     method: 'post',
     data
   })
 }
 
-export function logout() {
+export function getInfo(token) {
   return request({
-    url: '/login/logout',
-    method: 'post'
+    url: '/user/info',
+    method: 'get',
+    params: { token }
   })
 }
 
-export function getUserInfo(token) {
+export function logout() {
   return request({
-    url: '/user/info',
-    method: 'get',
-    params: { token }
+    url: '/user/logout',
+    method: 'post'
   })
 }
 

+ 21 - 21
src/components/BackToTop/index.vue

@@ -88,29 +88,29 @@ export default {
 </script>
 
 <style scoped>
-  .back-to-ceiling {
-    position: fixed;
-    display: inline-block;
-    text-align: center;
-    cursor: pointer;
-  }
+.back-to-ceiling {
+  position: fixed;
+  display: inline-block;
+  text-align: center;
+  cursor: pointer;
+}
 
-  .back-to-ceiling:hover {
-    background: #d5dbe7;
-  }
+.back-to-ceiling:hover {
+  background: #d5dbe7;
+}
 
-  .fade-enter-active,
-  .fade-leave-active {
-    transition: opacity .5s;
-  }
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity .5s;
+}
 
-  .fade-enter,
-  .fade-leave-to {
-    opacity: 0
-  }
+.fade-enter,
+.fade-leave-to {
+  opacity: 0
+}
 
-  .back-to-ceiling .Icon {
-    fill: #9aaabf;
-    background: none;
-  }
+.back-to-ceiling .Icon {
+  fill: #9aaabf;
+  background: none;
+}
 </style>

+ 13 - 13
src/components/Breadcrumb/index.vue

@@ -2,9 +2,8 @@
   <el-breadcrumb class="app-breadcrumb" separator="/">
     <transition-group name="breadcrumb">
       <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
-        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">
-          {{ generateTitle(item.meta.title) }}
-        </span>
+        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{
+          generateTitle(item.meta.title) }}</span>
         <a v-else @click.prevent="handleLink(item)">{{ generateTitle(item.meta.title) }}</a>
       </el-breadcrumb-item>
     </transition-group>
@@ -59,15 +58,16 @@ export default {
 }
 </script>
 
-<style rel="stylesheet/scss" lang="scss" scoped>
-  .app-breadcrumb.el-breadcrumb {
-    display: inline-block;
-    font-size: 14px;
-    line-height: 50px;
-    margin-left: 8px;
-    .no-redirect {
-      color: #97a8be;
-      cursor: text;
-    }
+<style lang="scss" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
   }
+}
 </style>

+ 9 - 5
src/components/DndList/index.vue

@@ -2,9 +2,11 @@
   <div class="dndList">
     <div :style="{width:width1}" class="dndList-list">
       <h3>{{ list1Title }}</h3>
-      <draggable :list="list1" :options="{group:'article'}" class="dragArea">
+      <draggable :list="list1" group="article" class="dragArea">
         <div v-for="element in list1" :key="element.id" class="list-complete-item">
-          <div class="list-complete-item-handle">{{ element.id }}[{{ element.author }}] {{ element.title }}</div>
+          <div class="list-complete-item-handle">
+            {{ element.id }}[{{ element.author }}] {{ element.title }}
+          </div>
           <div style="position:absolute;right:0px;">
             <span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
               <i style="color:#ff4949" class="el-icon-delete" />
@@ -15,9 +17,11 @@
     </div>
     <div :style="{width:width2}" class="dndList-list">
       <h3>{{ list2Title }}</h3>
-      <draggable :list="list2" :options="{group:'article'}" class="dragArea">
+      <draggable :list="list2" group="article" class="dragArea">
         <div v-for="element in list2" :key="element.id" class="list-complete-item">
-          <div class="list-complete-item-handle2" @click="pushEle(element)">{{ element.id }} [{{ element.author }}] {{ element.title }}</div>
+          <div class="list-complete-item-handle2" @click="pushEle(element)">
+            {{ element.id }} [{{ element.author }}] {{ element.title }}
+          </div>
         </div>
       </draggable>
     </div>
@@ -95,7 +99,7 @@ export default {
 }
 </script>
 
-<style rel="stylesheet/scss" lang="scss" scoped>
+<style lang="scss" scoped>
 .dndList {
   background: #fff;
   padding-bottom: 40px;

+ 1 - 1
src/components/Dropzone/index.vue

@@ -237,7 +237,7 @@ export default {
 
     .dropzone .dz-preview:hover .dz-image img {
         transform: none;
-        -webkit-filter: none;
+        filter: none;
         width: 100%;
         height: 100%;
     }

+ 9 - 4
src/components/ErrorLog/index.vue

@@ -12,17 +12,23 @@
           <template slot-scope="scope">
             <div>
               <span class="message-title">Msg:</span>
-              <el-tag type="danger">{{ scope.row.err.message }}</el-tag>
+              <el-tag type="danger">
+                {{ scope.row.err.message }}
+              </el-tag>
             </div>
             <br>
             <div>
               <span class="message-title" style="padding-right: 10px;">Info: </span>
-              <el-tag type="warning">{{ scope.row.vm.$vnode.tag }} error in {{ scope.row.info }}</el-tag>
+              <el-tag type="warning">
+                {{ scope.row.vm.$vnode.tag }} error in {{ scope.row.info }}
+              </el-tag>
             </div>
             <br>
             <div>
               <span class="message-title" style="padding-right: 16px;">Url: </span>
-              <el-tag type="success">{{ scope.row.url }}</el-tag>
+              <el-tag type="success">
+                {{ scope.row.url }}
+              </el-tag>
             </div>
           </template>
         </el-table-column>
@@ -33,7 +39,6 @@
         </el-table-column>
       </el-table>
     </el-dialog>
-
   </div>
 </template>
 

+ 5 - 4
src/components/Hamburger/index.vue

@@ -20,10 +20,11 @@ export default {
     isActive: {
       type: Boolean,
       default: false
-    },
-    toggleClick: {
-      type: Function,
-      default: null
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
     }
   }
 }

+ 2 - 0
src/components/HeaderSearch/index.vue

@@ -18,6 +18,8 @@
 </template>
 
 <script>
+// fuse is a lightweight fuzzy-search module
+// make search results more in line with expectations
 import Fuse from 'fuse.js'
 import path from 'path'
 import i18n from '@/lang'

+ 1 - 1
src/components/Kanban/index.vue

@@ -5,7 +5,7 @@
     </div>
     <draggable
       :list="list"
-      :options="options"
+      v-bind="$attrs"
       class="board-column-content"
     >
       <div v-for="element in list" :key="element.id" class="board-item">

+ 1 - 1
src/components/LangSelect/index.vue

@@ -27,7 +27,7 @@ export default {
   methods: {
     handleSetLanguage(lang) {
       this.$i18n.locale = lang
-      this.$store.dispatch('setLanguage', lang)
+      this.$store.dispatch('app/setLanguage', lang)
       this.$message({
         message: 'Switch Language Success',
         type: 'success'

+ 1 - 1
src/components/MDinput/index.vue

@@ -197,7 +197,7 @@ export default {
 }
 </script>
 
-<style rel="stylesheet/scss" lang="scss" scoped>
+<style lang="scss" scoped>
   // Fonts:
   $font-size-base: 16px;
   $font-size-small: 18px;

+ 152 - 0
src/components/RightPanel/index.vue

@@ -0,0 +1,152 @@
+<template>
+  <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+    <div class="rightPanel-background" />
+    <div class="rightPanel">
+      <div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
+        <i :class="show?'el-icon-close':'el-icon-setting'" />
+      </div>
+      <div class="rightPanel-items">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { addClass, removeClass } from '@/utils'
+
+export default {
+  name: 'RightPanel',
+  props: {
+    clickNotClose: {
+      default: false,
+      type: Boolean
+    },
+    buttonTop: {
+      default: 250,
+      type: Number
+    }
+  },
+  data() {
+    return {
+      show: false
+    }
+  },
+  computed: {
+    theme() {
+      return this.$store.state.settings.theme
+    }
+  },
+  watch: {
+    show(value) {
+      if (value && !this.clickNotClose) {
+        this.addEventClick()
+      }
+      if (value) {
+        addClass(document.body, 'showRightPanel')
+      } else {
+        removeClass(document.body, 'showRightPanel')
+      }
+    }
+  },
+  mounted() {
+    this.insertToBody()
+  },
+  beforeDestroy() {
+    const elx = this.$refs.rightPanel
+    elx.remove()
+  },
+  methods: {
+    addEventClick() {
+      window.addEventListener('click', this.closeSidebar)
+    },
+    closeSidebar(evt) {
+      const parent = evt.target.closest('.rightPanel')
+      if (!parent) {
+        this.show = false
+        window.removeEventListener('click', this.closeSidebar)
+      }
+    },
+    insertToBody() {
+      const elx = this.$refs.rightPanel
+      const body = document.querySelector('body')
+      body.insertBefore(elx, body.firstChild)
+    }
+  }
+}
+</script>
+
+<style>
+.showRightPanel {
+  overflow: hidden;
+  position: relative;
+  width: calc(100% - 15px);
+}
+</style>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+  opacity: 0;
+  transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+  background: rgba(0, 0, 0, .2);
+  width: 0;
+  height: 0;
+  top: 0;
+  left: 0;
+  position: fixed;
+  z-index: -1;
+}
+
+.rightPanel {
+  background: #fff;
+  z-index: 3000;
+  position: fixed;
+  height: 100vh;
+  width: 100%;
+  max-width: 260px;
+  top: 0px;
+  left: 0px;
+  box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+  transition: all .25s cubic-bezier(.7, .3, .1, 1);
+  transform: translate(100%);
+  z-index: 40000;
+  left: auto;
+  right: 0px;
+}
+
+.show {
+  transition: all .3s cubic-bezier(.7, .3, .1, 1);
+
+  .rightPanel-background {
+    z-index: 20000;
+    opacity: 1;
+    width: 100%;
+    height: 100%;
+  }
+
+  .rightPanel {
+    transform: translate(0);
+  }
+}
+
+.handle-button {
+  position: absolute;
+  left: -48px;
+  border-radius: 6px 0 0 6px !important;
+  width: 48px;
+  height: 48px;
+  pointer-events: auto;
+  z-index: 0;
+  cursor: pointer;
+  pointer-events: auto;
+  font-size: 24px;
+  text-align: center;
+  color: #fff;
+  line-height: 48px;
+
+  i {
+    font-size: 24px;
+    line-height: 48px;
+  }
+}
+</style>

+ 12 - 3
src/components/Screenfull/index.vue

@@ -17,6 +17,9 @@ export default {
   mounted() {
     this.init()
   },
+  beforeDestroy() {
+    this.destroy()
+  },
   methods: {
     click() {
       if (!screenfull.enabled) {
@@ -28,11 +31,17 @@ export default {
       }
       screenfull.toggle()
     },
+    change() {
+      this.isFullscreen = screenfull.isFullscreen
+    },
     init() {
       if (screenfull.enabled) {
-        screenfull.on('change', () => {
-          this.isFullscreen = screenfull.isFullscreen
-        })
+        screenfull.on('change', this.change)
+      }
+    },
+    destroy() {
+      if (screenfull.enabled) {
+        screenfull.off('change', this.change)
       }
     }
   }

+ 1 - 1
src/components/Share/dropdownMenu.vue

@@ -37,7 +37,7 @@ export default {
 }
 </script>
 
-<style rel="stylesheet/scss" lang="scss" >
+<style lang="scss" >
 $n: 8; //和items.length 相同
 $t: .1s;
 .share-dropdown-menu {

+ 4 - 3
src/components/SizeSelect/index.vue

@@ -5,7 +5,8 @@
     </div>
     <el-dropdown-menu slot="dropdown">
       <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
-        {{ item.label }}
+        {{
+          item.label }}
       </el-dropdown-item>
     </el-dropdown-menu>
   </el-dropdown>
@@ -31,7 +32,7 @@ export default {
   methods: {
     handleSetSize(size) {
       this.$ELEMENT.size = size
-      this.$store.dispatch('setSize', size)
+      this.$store.dispatch('app/setSize', size)
       this.refreshView()
       this.$message({
         message: 'Switch Size Success',
@@ -40,7 +41,7 @@ export default {
     },
     refreshView() {
       // In order to make the cached page re-rendered
-      this.$store.dispatch('delAllCachedViews', this.$route)
+      this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
 
       const { fullPath } = this.$route
 

+ 44 - 35
src/components/ThemePicker/index.vue

@@ -1,7 +1,7 @@
 <template>
   <el-color-picker
     v-model="theme"
-    :predefine="['#409EFF', '#11a983', '#13c2c2', '#6959CD', '#f5222d', '#eb2f96', '#DB7093', '#e6a23c', '#8B8989', '#212121']"
+    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
     class="theme-picker"
     popper-class="theme-picker-dropdown"
   />
@@ -11,21 +11,31 @@
 
 const version = require('element-ui/package.json').version // element-ui version from node_modules
 const ORIGINAL_THEME = '#409EFF' // default color
+import defaultSettings from '@/settings'
 
 export default {
   data() {
     return {
       chalk: '', // content of theme-chalk css
-      theme: ORIGINAL_THEME
+      theme: defaultSettings.theme
     }
   },
   watch: {
-    theme(val) {
-      const oldVal = this.theme
+    async theme(val) {
+      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
       if (typeof val !== 'string') return
       const themeCluster = this.getThemeCluster(val.replace('#', ''))
       const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
       console.log(themeCluster, originalCluster)
+
+      const $message = this.$message({
+        message: '  Compiling the theme',
+        customClass: 'theme-message',
+        type: 'success',
+        duration: 0,
+        iconClass: 'el-icon-loading'
+      })
+
       const getHandler = (variable, id) => {
         return () => {
           const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
@@ -41,15 +51,15 @@ export default {
         }
       }
 
-      const chalkHandler = getHandler('chalk', 'chalk-style')
-
       if (!this.chalk) {
         const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
-        this.getCSSString(url, chalkHandler, 'chalk')
-      } else {
-        chalkHandler()
+        await this.getCSSString(url, 'chalk')
       }
 
+      const chalkHandler = getHandler('chalk', 'chalk-style')
+
+      chalkHandler()
+
       const styles = [].slice.call(document.querySelectorAll('style'))
         .filter(style => {
           const text = style.innerText
@@ -60,39 +70,34 @@ export default {
         if (typeof innerText !== 'string') return
         style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
       })
-      this.$message({
-        message: '换肤成功',
-        type: 'success'
-      })
+
+      this.$emit('change', val)
+
+      $message.close()
     }
   },
 
   methods: {
     updateStyle(style, oldCluster, newCluster) {
-      const colorOverrides = [] // only capture color overides
+      let newStyle = style
       oldCluster.forEach((color, index) => {
-        const value = newCluster[index]
-        const color_plain = color.replace(/([()])/g, '\\$1')
-        const repl = new RegExp(`(^|})([^{]+{[^{}]+)${color_plain}\\b([^}]*)(?=})`, 'gi')
-        const nestRepl = new RegExp(color_plain, 'ig') // for greed matching before the 'color'
-        let v
-        while ((v = repl.exec(style))) {
-          colorOverrides.push(v[2].replace(nestRepl, value) + value + v[3] + '}') // '}' not captured in the regexp repl to reserve it as locator-boundary
-        }
+        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
       })
-      return colorOverrides.join('')
+      return newStyle
     },
 
-    getCSSString(url, callback, variable) {
-      const xhr = new XMLHttpRequest()
-      xhr.onreadystatechange = () => {
-        if (xhr.readyState === 4 && xhr.status === 200) {
-          this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
-          callback()
+    getCSSString(url, variable) {
+      return new Promise(resolve => {
+        const xhr = new XMLHttpRequest()
+        xhr.onreadystatechange = () => {
+          if (xhr.readyState === 4 && xhr.status === 200) {
+            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+            resolve()
+          }
         }
-      }
-      xhr.open('GET', url)
-      xhr.send()
+        xhr.open('GET', url)
+        xhr.send()
+      })
     },
 
     getThemeCluster(theme) {
@@ -144,10 +149,14 @@ export default {
 </script>
 
 <style>
+.theme-message,
+.theme-picker-dropdown {
+  z-index: 99999 !important;
+}
+
 .theme-picker .el-color-picker__trigger {
-  margin-top: 12px;
-  height: 26px!important;
-  width: 26px!important;
+  height: 26px !important;
+  width: 26px !important;
   padding: 2px;
 }
 

+ 10 - 4
src/components/Tinymce/components/editorImage.vue

@@ -15,10 +15,16 @@
         action="https://httpbin.org/post"
         list-type="picture-card"
       >
-        <el-button size="small" type="primary">点击上传</el-button>
+        <el-button size="small" type="primary">
+          点击上传
+        </el-button>
       </el-upload>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-      <el-button type="primary" @click="handleSubmit">确 定</el-button>
+      <el-button @click="dialogVisible = false">
+        取 消
+      </el-button>
+      <el-button type="primary" @click="handleSubmit">
+        确 定
+      </el-button>
     </el-dialog>
   </div>
 </template>
@@ -95,7 +101,7 @@ export default {
 }
 </script>
 
-<style rel="stylesheet/scss" lang="scss" scoped>
+<style lang="scss" scoped>
 .editor-slide-upload {
   margin-bottom: 20px;
   /deep/ .el-upload--picture-card {

+ 0 - 220
src/components/TreeTable/README.md

@@ -1,220 +0,0 @@
-
-- [Enlgish](#Brief)
-
-# 中文
-
-## 写在前面
-
-此组件仅提供一个创建 `TreeTable` 的解决思路。它基于`element-ui`的 table 组件实现,通过`el-table`的`row-style`方法,在里面判断元素是否需要隐藏或者显示,从而实现`TreeTable`的展开与收起。
-
-并且本组件充分利用 `vue` 插槽的特性来方便用户自定义。
-
-`evel.js` 里面,`addAttrs` 方法会给数据添加几个属性,`treeTotable` 会对数组扁平化。这些操作都不会破坏源数据,只是会新增属性。
-
-## Props 说明
-
-|    Attribute     | Description                        |  Type   | Default  |
-| :--------------: | :--------------------------------- | :-----: | :------: |
-|       data       | 原始展示数据                       |  Array  |    []    |
-|     columns      | 列属性                             |  Array  |    []    |
-| defaultExpandAll | 默认是否全部展开                   | Boolean |  false   |
-| defaultChildren  | 指定子树为节点对象的某个属性值     | String  | children |  |
-|      indent      | 相邻级节点间的水平缩进,单位为像素 | Number  |    50    |
-
-> 任何 `el-table` 的属性都支持,例如`border`、`fit`、`size`或者`@select`、`@cell-click`等方法。详情属性见`el-table`文档。
-
----
-
-### 代码示例
-
-```html
-<tree-table :data="data" :columns="columns" border>
-```
-
-#### data(**必填**)
-
-```js
-const data = [
-  {
-    name:'1'
-    children: [
-      {
-        name: '1-1'
-      },
-      {
-        name: '1-2'
-      }
-    ]
-  },
-  {
-    name: `2`
-  }
-]
-```
-
-#### columns(**必填**)
-
-- label: 显示在表头的文字
-- key: 对应 data 的 key。treeTable 将显示相应的 value
-- expand: `true` or `false`。若为 true,则在该列显示展开收起图标
-- checkbox: `true` or `false`。若为 true,则在该列显示`checkbox`
-- width: 每列的宽度,为一个数字(可选)。例如`200`
-- align: 对齐方式 `left/center/right`
-- header-align: 表头对齐方式 `left/center/right`
-
-```javascript
-const columns = [
-  {
-    label: 'Checkbox',
-    checkbox: true
-  },
-  {
-    label: '',
-    key: 'id',
-    expand: true
-  },
-  {
-    label: 'Event',
-    key: 'event',
-    width: 200,
-    align: 'left'
-  },
-  {
-    label: 'Scope',
-    key: 'scope'
-  }
-]
-```
-
-> 树表组件将会根据 columns 的 key 属性生成具名插槽,如果你需要对列数据进行自定义,通过插槽即可实现
-
-```html
-<template slot="your key" slot-scope="{scope}">
-  <el-tag>level: {{ scope.row._level }}</el-tag>
-  <el-tag>expand: {{ scope.row._expand }}</el-tag>
-  <el-tag>select: {{ scope.row._select }}</el-tag>
-</template>
-```
-
-## Events
-
-目前提供了几个方法,不过只是`beta`版本,之后很可能会修改。
-
-```js
-this.$refs.TreeTable.addChild(row, data) //添加子元素
-this.$refs.TreeTable.addBrother(row, data) //添加兄弟元素
-this.$refs.TreeTable.delete(row) //删除该元素
-```
-
-## 其他
-
-如果有其他的需求,请参考[el-table](http://element-cn.eleme.io/#/en-US/component/table)的 api 自行修改 index.vue
-
-# English
-
-## Brief
-
-This component only provides a solution for creating `TreeTable`. It is based on the `element-ui` table component. It uses the `row-style` method of `el-table` to determine whether the element needs to be hidden or displayed.
-
-And this component makes full use of the features of the `vue` slot to make it user-friendly.
-
-In `evel.js`, the `addAttrs` method adds several properties to the data, and `treeTotable` flattens the array. None of these operations will destroy the source data, just add properties.
-
-## Props
-
-|    Attribute     | Description                                                  |  Type   | Default  |
-| :--------------: | :----------------------------------------------------------- | :-----: | :------: |
-|       data       | original display data                                        |  Array  |    []    |
-|     columns      | column attribute                                             |  Array  |    []    |
-| defaultExpandAll | whether to expand all nodes by default                       | Boolean |  false   |
-| defaultChildren  | specify which node object is used as the node's subtree      | String  | children |  |
-|      indent      | horizontal indentation of nodes in adjacent levels in pixels | Number  |    50    |
-
-> Any of the `el-table` properties are supported, such as `border`, `fit`, `size` or `@select`, `@cell-click`. See the ʻel-table` documentation for details.
-
----
-
-### Example
-
-```html
-<tree-table :data="data" :columns="columns" border>
-```
-
-#### data(**Required**)
-
-```js
-const data = [
-  {
-    name:'1'
-    children: [
-      {
-        name: '1-1'
-      },
-      {
-        name: '1-2'
-      }
-    ]
-  },
-  {
-    name: `2`
-  }
-]
-```
-
-#### columns(**Required**)
-
-- label: text displayed in the header
-- key: data.key will show in column
-- expand: `true` or `false`
-- checkbox: `true` or `false`
-- width: column width 。such as `200`
-- align: alignment `left/center/right`
-- header-align: alignment of the table header `left/center/right`
-
-```javascript
-const columns = [
-  {
-    label: 'Checkbox',
-    checkbox: true
-  },
-  {
-    label: '',
-    key: 'id',
-    expand: true
-  },
-  {
-    label: 'Event',
-    key: 'event',
-    width: 200,
-    align: 'left'
-  },
-  {
-    label: 'Scope',
-    key: 'scope'
-  }
-]
-```
-
-> The tree table component will generate a named slot based on the key property of columns. If you need to customize the column data, you can do it through the slot.
-
-```html
-<template slot="your key" slot-scope="{scope}">
-  <el-tag>level: {{ scope.row._level }}</el-tag>
-  <el-tag>expand: {{ scope.row._expand }}</el-tag>
-  <el-tag>select: {{ scope.row._select }}</el-tag>
-</template>
-```
-
-## Events
-
-Several methods are currently available, but only the `beta` version, which is likely to be modified later.
-
-```js
-this.$refs.TreeTable.addChild(row, data) //Add child elements
-this.$refs.TreeTable.addBrother(row, data) //Add a sibling element
-this.$refs.TreeTable.delete(row) //Delete the element
-```
-
-## Other
-
-If you have other requirements, please refer to the [el-table](http://element-cn.eleme.io/#/en-US/component/table) api to modify the index.vue

+ 0 - 0
src/components/TreeTable/eval.js


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