[release]4.1.0 (#211)
							
								
								
									
										12
									
								
								.babelrc
									
									
									
									
									
								
							
							
						
						@@ -1,12 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "presets": [
 | 
					 | 
				
			||||||
    ["env", {
 | 
					 | 
				
			||||||
      "modules": false,
 | 
					 | 
				
			||||||
      "targets": {
 | 
					 | 
				
			||||||
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }],
 | 
					 | 
				
			||||||
    "stage-2"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "plugins":["transform-vue-jsx", "transform-runtime"]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								.env.development
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -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
									
								
								.env.production
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					# just a flag
 | 
				
			||||||
 | 
					ENV = 'production'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# base api
 | 
				
			||||||
 | 
					VUE_APP_BASE_API = '/prod-api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								.env.staging
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					NODE_ENV = production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# just a flag
 | 
				
			||||||
 | 
					ENV = 'staging'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# base api
 | 
				
			||||||
 | 
					VUE_APP_BASE_API = '/stage-api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
build/*.js
 | 
					build/*.js
 | 
				
			||||||
config/*.js
 | 
					 | 
				
			||||||
src/assets
 | 
					src/assets
 | 
				
			||||||
 | 
					public
 | 
				
			||||||
 | 
					dist
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,10 @@ module.exports = {
 | 
				
			|||||||
        "allowFirstLine": false
 | 
					        "allowFirstLine": false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }],
 | 
					    }],
 | 
				
			||||||
 | 
					    "vue/singleline-html-element-content-newline": "off",
 | 
				
			||||||
 | 
					    "vue/multiline-html-element-content-newline":"off",
 | 
				
			||||||
    "vue/name-property-casing": ["error", "PascalCase"],
 | 
					    "vue/name-property-casing": ["error", "PascalCase"],
 | 
				
			||||||
 | 
					    "vue/no-v-html": "off",
 | 
				
			||||||
    'accessor-pairs': 2,
 | 
					    'accessor-pairs': 2,
 | 
				
			||||||
    'arrow-spacing': [2, {
 | 
					    'arrow-spacing': [2, {
 | 
				
			||||||
      'before': true,
 | 
					      'before': true,
 | 
				
			||||||
@@ -44,7 +47,7 @@ module.exports = {
 | 
				
			|||||||
    'curly': [2, 'multi-line'],
 | 
					    'curly': [2, 'multi-line'],
 | 
				
			||||||
    'dot-location': [2, 'property'],
 | 
					    'dot-location': [2, 'property'],
 | 
				
			||||||
    'eol-last': 2,
 | 
					    'eol-last': 2,
 | 
				
			||||||
    'eqeqeq': [2, 'allow-null'],
 | 
					    'eqeqeq': ["error", "always", {"null": "ignore"}],
 | 
				
			||||||
    'generator-star-spacing': [2, {
 | 
					    'generator-star-spacing': [2, {
 | 
				
			||||||
      'before': true,
 | 
					      'before': true,
 | 
				
			||||||
      'after': true
 | 
					      'after': true
 | 
				
			||||||
@@ -73,7 +76,7 @@ module.exports = {
 | 
				
			|||||||
    'no-class-assign': 2,
 | 
					    'no-class-assign': 2,
 | 
				
			||||||
    'no-cond-assign': 2,
 | 
					    'no-cond-assign': 2,
 | 
				
			||||||
    'no-const-assign': 2,
 | 
					    'no-const-assign': 2,
 | 
				
			||||||
    'no-control-regex': 2,
 | 
					    'no-control-regex': 0,
 | 
				
			||||||
    'no-delete-var': 2,
 | 
					    'no-delete-var': 2,
 | 
				
			||||||
    'no-dupe-args': 2,
 | 
					    'no-dupe-args': 2,
 | 
				
			||||||
    'no-dupe-class-members': 2,
 | 
					    'no-dupe-class-members': 2,
 | 
				
			||||||
@@ -193,4 +196,3 @@ module.exports = {
 | 
				
			|||||||
    'array-bracket-spacing': [2, 'never']
 | 
					    'array-bracket-spacing': [2, 'never']
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -5,6 +5,7 @@ npm-debug.log*
 | 
				
			|||||||
yarn-debug.log*
 | 
					yarn-debug.log*
 | 
				
			||||||
yarn-error.log*
 | 
					yarn-error.log*
 | 
				
			||||||
package-lock.json
 | 
					package-lock.json
 | 
				
			||||||
 | 
					tests/**/coverage/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Editor directories and files
 | 
					# Editor directories and files
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,8 @@
 | 
				
			|||||||
// https://github.com/michael-ciniawsky/postcss-load-config
 | 
					// https://github.com/michael-ciniawsky/postcss-load-config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
  "plugins": {
 | 
					  'plugins': {
 | 
				
			||||||
    "postcss-import": {},
 | 
					 | 
				
			||||||
    "postcss-url": {},
 | 
					 | 
				
			||||||
    // to edit target browsers: use "browserslist" field in package.json
 | 
					    // to edit target browsers: use "browserslist" field in package.json
 | 
				
			||||||
    "autoprefixer": {}
 | 
					    'autoprefixer': {}
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								README-zh.md
									
									
									
									
									
								
							
							
						
						@@ -1,19 +1,17 @@
 | 
				
			|||||||
# vue-admin-template
 | 
					# vue-admin-template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> 这是一个 极简的 vue admin 管理后台 它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
 | 
					> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[线上地址](http://panjiachen.github.io/vue-admin-template)
 | 
					[线上地址](http://panjiachen.github.io/vue-admin-template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[国内访问](https://panjiachen.gitee.io/vue-admin-template)
 | 
					[国内访问](https://panjiachen.gitee.io/vue-admin-template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若发现问题,欢迎提 issue。若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Extra
 | 
					## Extra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
 | 
					如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
本项目基于`webpack4`开发,若还想使用`webpack3`开发,请使用该分支[webpack3](https://github.com/PanJiaChen/vue-admin-template/tree/webpack3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
如果你想使用基于 vue + typescript 的管理后台, 可以看看这个项目: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 相关项目
 | 
					## 相关项目
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
 | 
					[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
 | 
				
			||||||
@@ -33,54 +31,56 @@
 | 
				
			|||||||
## Build Setup
 | 
					## Build Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Clone project
 | 
					# 克隆项目
 | 
				
			||||||
git clone https://github.com/PanJiaChen/vue-admin-template.git
 | 
					git clone https://github.com/PanJiaChen/vue-admin-template.git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Install dependencies
 | 
					# 进入项目目录
 | 
				
			||||||
 | 
					cd vue-admin-template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 安装依赖
 | 
				
			||||||
npm install
 | 
					npm install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 建议不要用cnpm  安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题
 | 
					# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
 | 
				
			||||||
npm install --registry=https://registry.npm.taobao.org
 | 
					npm install --registry=https://registry.npm.taobao.org
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Serve with hot reload at localhost:9528
 | 
					# 启动服务
 | 
				
			||||||
npm run dev
 | 
					npm run dev
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build for production with minification
 | 
					 | 
				
			||||||
npm run build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build for production and view the bundle analyzer report
 | 
					 | 
				
			||||||
npm run build --report
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					浏览器访问 [http://localhost:9528](http://localhost:9528)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 发布
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# 构建测试环境
 | 
				
			||||||
 | 
					npm run build:stage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 构建生产环境
 | 
				
			||||||
 | 
					npm run build:prod
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 其它
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# 预览发布环境效果
 | 
				
			||||||
 | 
					npm run preview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 预览发布环境效果 + 静态资源分析
 | 
				
			||||||
 | 
					npm run preview -- --report
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 代码格式检查
 | 
				
			||||||
 | 
					npm run lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 代码格式检查并自动修复
 | 
				
			||||||
 | 
					npm run lint -- --fix
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Demo
 | 
					## Demo
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Element-Ui 使用 cdn 教程
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
首先找到 `index.html` ([根目录下](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/index.html))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
引入 Element 的 css 和 js ,并且引入 vue 。因为 Element-Ui 是依赖 vue 的,所以必须在它之前引入 vue 。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
之后找到 [webpack.base.conf.js](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/build/webpack.base.conf.js) 加入 `externals` 让 webpack 不打包 vue 和 element
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
externals: {
 | 
					 | 
				
			||||||
  vue: 'Vue',
 | 
					 | 
				
			||||||
  'element-ui':'ELEMENT'
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
之后还有一个小细节是如果你用了全局对象方式引入 vue,就不需要 手动 `Vue.use(Vuex)` ,它会自动挂载,具体见 [issue](https://github.com/vuejs/vuex/issues/731)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
最终你可以使用 `npm run build --report` 查看效果
 | 
					 | 
				
			||||||
如图:
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**[具体代码](https://github.com/PanJiaChen/vue-admin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**[对应分支](https://github.com/PanJiaChen/vue-admin-template/tree/element-ui-cdn)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Browsers support
 | 
					## Browsers support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Modern browsers and Internet Explorer 10+.
 | 
					Modern browsers and Internet Explorer 10+.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										85
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,30 +1,61 @@
 | 
				
			|||||||
# vue-admin-template
 | 
					# vue-admin-template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					English | [简体中文](./README.zh-CN.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
 | 
					> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Live demo:** http://panjiachen.github.io/vue-admin-template
 | 
					**Live demo:** http://panjiachen.github.io/vue-admin-template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[中文文档](https://github.com/PanJiaChen/vue-admin-template/blob/master/README-zh.md)
 | 
					
 | 
				
			||||||
 | 
					**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli'**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Build Setup
 | 
					## Build Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
# Clone project
 | 
					 | 
				
			||||||
git clone https://github.com/PanJiaChen/vue-admin-template.git
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Install dependencies
 | 
					```bash
 | 
				
			||||||
 | 
					# 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
 | 
					npm install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Serve with hot reload at localhost:9528
 | 
					# develop
 | 
				
			||||||
npm run dev
 | 
					npm run dev
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build for production with minification
 | 
					 | 
				
			||||||
npm run build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build for production and view the bundle analyzer report
 | 
					 | 
				
			||||||
npm run build --report
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This will automatically open http://localhost:9527
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# build for test environment
 | 
				
			||||||
 | 
					npm run build:stage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# build for production environment
 | 
				
			||||||
 | 
					npm run build:prod
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Advanced
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# preview the release environment effect
 | 
				
			||||||
 | 
					npm run preview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# preview the release environment effect + static resource analysis
 | 
				
			||||||
 | 
					npm run preview -- --report
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# code format check
 | 
				
			||||||
 | 
					npm run lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# code format check and auto fix
 | 
				
			||||||
 | 
					npm run lint -- --fix
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Demo
 | 
					## Demo
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
@@ -33,8 +64,6 @@ npm run build --report
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
 | 
					If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This project is based on `webpack4` development. If you want to use `webpack3` development, please use this branch [webpack3](https://github.com/PanJiaChen/vue-admin-template/tree/webpack3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
 | 
					For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Related Project
 | 
					## Related Project
 | 
				
			||||||
@@ -45,34 +74,6 @@ For `typescript` version, you can use [vue-typescript-admin-template](https://gi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
 | 
					[vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Element-Ui using cdn tutorial
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
First find `index.html`([root directory](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/index.html))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Import css and js of `Element`, and then import vue. Because `Element` is vue-dependent, vue must be import before it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Then find [webpack.base.conf.js](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/build/webpack.base.conf.js)
 | 
					 | 
				
			||||||
Add `externals` to make webpack not package vue and element.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
externals: {
 | 
					 | 
				
			||||||
  vue: 'Vue',
 | 
					 | 
				
			||||||
  'element-ui':'ELEMENT'
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Finally there is a small detail to pay attention to that if you import vue in global, you don't need to manually `Vue.use(Vuex)`, it will be automatically mounted, see
 | 
					 | 
				
			||||||
[issue](https://github.com/vuejs/vuex/issues/731)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
And you can use `npm run build --report` to see the effect
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Pictured:
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**[Detailed code](https://github.com/PanJiaChen/vue-admin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**[Branch](https://github.com/PanJiaChen/vue-admin-template/tree/element-ui-cdn)**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Browsers support
 | 
					## Browsers support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Modern browsers and Internet Explorer 10+.
 | 
					Modern browsers and Internet Explorer 10+.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								babel.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  presets: [
 | 
				
			||||||
 | 
					    '@vue/app'
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,45 +0,0 @@
 | 
				
			|||||||
'use strict'
 | 
					 | 
				
			||||||
require('./check-versions')()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
process.env.NODE_ENV = 'production'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const spinner = ora('building for production...')
 | 
					 | 
				
			||||||
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"
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -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
									
								
								build/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -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
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 6.7 KiB  | 
							
								
								
									
										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')
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
'use strict'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  //You can set the vue-loader configuration by yourself.
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,108 +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',
 | 
					 | 
				
			||||||
        include: [
 | 
					 | 
				
			||||||
          resolve('src'),
 | 
					 | 
				
			||||||
          resolve('test'),
 | 
					 | 
				
			||||||
          resolve('mock'),
 | 
					 | 
				
			||||||
          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'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,95 +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-admin-template'
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -1,177 +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/prod.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-admin-template',
 | 
					 | 
				
			||||||
      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[\\/]/
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
'use strict'
 | 
					 | 
				
			||||||
const merge = require('webpack-merge')
 | 
					 | 
				
			||||||
const prodEnv = require('./prod.env')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = merge(prodEnv, {
 | 
					 | 
				
			||||||
  NODE_ENV: '"development"',
 | 
					 | 
				
			||||||
  BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -1,86 +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
 | 
					 | 
				
			||||||
    host: 'localhost', // can be overwritten by process.env.HOST
 | 
					 | 
				
			||||||
    port: 9528, // 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 --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
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
'use strict'
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  NODE_ENV: '"production"',
 | 
					 | 
				
			||||||
  BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								index.html
									
									
									
									
									
								
							
							
						
						@@ -1,12 +0,0 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <meta charset="utf-8">
 | 
					 | 
				
			||||||
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
 | 
					 | 
				
			||||||
    <title>vue-admin-template</title>
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body>
 | 
					 | 
				
			||||||
    <div id="app"></div>
 | 
					 | 
				
			||||||
    <!-- built files will be auto injected -->
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										24
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
 | 
				
			||||||
 | 
					  transform: {
 | 
				
			||||||
 | 
					    '^.+\\.vue$': 'vue-jest',
 | 
				
			||||||
 | 
					    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
 | 
				
			||||||
 | 
					      'jest-transform-stub',
 | 
				
			||||||
 | 
					    '^.+\\.jsx?$': 'babel-jest'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  moduleNameMapper: {
 | 
				
			||||||
 | 
					    '^@/(.*)$': '<rootDir>/src/$1'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  snapshotSerializers: ['jest-serializer-vue'],
 | 
				
			||||||
 | 
					  testMatch: [
 | 
				
			||||||
 | 
					    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  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/'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,26 +1,66 @@
 | 
				
			|||||||
import Mock from 'mockjs'
 | 
					import Mock from 'mockjs'
 | 
				
			||||||
import userAPI from './user'
 | 
					import { param2Obj } from '../src/utils'
 | 
				
			||||||
import tableAPI from './table'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Fix an issue with setting withCredentials = true, cross-domain request lost cookies
 | 
					import user from './user'
 | 
				
			||||||
// https://github.com/nuysoft/Mock/issues/300
 | 
					import table from './table'
 | 
				
			||||||
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
 | 
					
 | 
				
			||||||
Mock.XHR.prototype.send = function() {
 | 
					const mocks = [
 | 
				
			||||||
 | 
					  ...user,
 | 
				
			||||||
 | 
					  ...table
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// for front mock
 | 
				
			||||||
 | 
					// please use it cautiously, it will redefine XMLHttpRequest,
 | 
				
			||||||
 | 
					// which will cause many of your third-party libraries to be invalidated(like progress event).
 | 
				
			||||||
 | 
					export function mockXHR() {
 | 
				
			||||||
 | 
					  // mock patch
 | 
				
			||||||
 | 
					  // 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) {
 | 
					    if (this.custom.xhr) {
 | 
				
			||||||
      this.custom.xhr.withCredentials = this.withCredentials || false
 | 
					      this.custom.xhr.withCredentials = this.withCredentials || false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.responseType) {
 | 
				
			||||||
 | 
					        this.custom.xhr.responseType = this.responseType
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.proxy_send(...arguments)
 | 
					    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))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
// Mock.setup({
 | 
					 | 
				
			||||||
//   timeout: '350-600'
 | 
					 | 
				
			||||||
// })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// User
 | 
					// for mock server
 | 
				
			||||||
Mock.mock(/\/user\/login/, 'post', userAPI.login)
 | 
					const responseFake = (url, type, respond) => {
 | 
				
			||||||
Mock.mock(/\/user\/info/, 'get', userAPI.getInfo)
 | 
					  return {
 | 
				
			||||||
Mock.mock(/\/user\/logout/, 'post', userAPI.logout)
 | 
					    url: new RegExp(`/mock${url}`),
 | 
				
			||||||
 | 
					    type: type || 'get',
 | 
				
			||||||
 | 
					    response(req, res) {
 | 
				
			||||||
 | 
					      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Table
 | 
					export default mocks.map(route => {
 | 
				
			||||||
Mock.mock(/\/table\/list/, 'get', tableAPI.list)
 | 
					  return responseFake(route.url, route.type, route.response)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
export default Mock
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										64
									
								
								mock/mock-server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					const chokidar = require('chokidar')
 | 
				
			||||||
 | 
					const bodyParser = require('body-parser')
 | 
				
			||||||
 | 
					const chalk = require('chalk')
 | 
				
			||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mockDir = path.join(process.cwd(), 'mock')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function registerRoutes(app) {
 | 
				
			||||||
 | 
					  let mockLastIndex
 | 
				
			||||||
 | 
					  const { default: mocks } = require('./index.js')
 | 
				
			||||||
 | 
					  for (const mock of mocks) {
 | 
				
			||||||
 | 
					    app[mock.type](mock.url, mock.response)
 | 
				
			||||||
 | 
					    mockLastIndex = app._router.stack.length
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const mockRoutesLength = Object.keys(mocks).length
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    mockRoutesLength: mockRoutesLength,
 | 
				
			||||||
 | 
					    mockStartIndex: mockLastIndex - mockRoutesLength
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function unregisterRoutes() {
 | 
				
			||||||
 | 
					  Object.keys(require.cache).forEach(i => {
 | 
				
			||||||
 | 
					    if (i.includes(mockDir)) {
 | 
				
			||||||
 | 
					      delete require.cache[require.resolve(i)]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = app => {
 | 
				
			||||||
 | 
					  // es6 polyfill
 | 
				
			||||||
 | 
					  require('@babel/register')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // parse app.body
 | 
				
			||||||
 | 
					  // https://expressjs.com/en/4x/api.html#req.body
 | 
				
			||||||
 | 
					  app.use(bodyParser.json())
 | 
				
			||||||
 | 
					  app.use(bodyParser.urlencoded({
 | 
				
			||||||
 | 
					    extended: true
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mockRoutes = registerRoutes(app)
 | 
				
			||||||
 | 
					  var mockRoutesLength = mockRoutes.mockRoutesLength
 | 
				
			||||||
 | 
					  var mockStartIndex = mockRoutes.mockStartIndex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // watch files, hot reload mock server
 | 
				
			||||||
 | 
					  chokidar.watch(mockDir, {
 | 
				
			||||||
 | 
					    ignored: /mock-server/,
 | 
				
			||||||
 | 
					    ignoreInitial: true
 | 
				
			||||||
 | 
					  }).on('all', (event, path) => {
 | 
				
			||||||
 | 
					    if (event === 'change' || event === 'add') {
 | 
				
			||||||
 | 
					      // remove mock routes stack
 | 
				
			||||||
 | 
					      app._router.stack.splice(mockStartIndex, mockRoutesLength)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // clear routes cache
 | 
				
			||||||
 | 
					      unregisterRoutes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const mockRoutes = registerRoutes(app)
 | 
				
			||||||
 | 
					      mockRoutesLength = mockRoutes.mockRoutesLength
 | 
				
			||||||
 | 
					      mockStartIndex = mockRoutes.mockStartIndex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
import Mock from 'mockjs'
 | 
					import Mock from 'mockjs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					const data = Mock.mock({
 | 
				
			||||||
  list: () => {
 | 
					 | 
				
			||||||
    const items = Mock.mock({
 | 
					 | 
				
			||||||
  'items|30': [{
 | 
					  'items|30': [{
 | 
				
			||||||
    id: '@id',
 | 
					    id: '@id',
 | 
				
			||||||
    title: '@sentence(10, 20)',
 | 
					    title: '@sentence(10, 20)',
 | 
				
			||||||
@@ -11,10 +9,21 @@ export default {
 | 
				
			|||||||
    display_time: '@datetime',
 | 
					    display_time: '@datetime',
 | 
				
			||||||
    pageviews: '@integer(300, 5000)'
 | 
					    pageviews: '@integer(300, 5000)'
 | 
				
			||||||
  }]
 | 
					  }]
 | 
				
			||||||
    })
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    url: '/table/list',
 | 
				
			||||||
 | 
					    type: 'get',
 | 
				
			||||||
 | 
					    response: config => {
 | 
				
			||||||
 | 
					      const items = data.items
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        code: 20000,
 | 
					        code: 20000,
 | 
				
			||||||
      data: items
 | 
					        data: {
 | 
				
			||||||
 | 
					          total: items.length,
 | 
				
			||||||
 | 
					          items: items
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
}
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								mock/user.js
									
									
									
									
									
								
							
							
						
						@@ -1,4 +1,3 @@
 | 
				
			|||||||
import { param2Obj } from './utils'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tokens = {
 | 
					const tokens = {
 | 
				
			||||||
  admin: {
 | 
					  admin: {
 | 
				
			||||||
@@ -24,41 +23,62 @@ const users = {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default [
 | 
				
			||||||
  login: res => {
 | 
					  // user login
 | 
				
			||||||
    const { username } = JSON.parse(res.body)
 | 
					  {
 | 
				
			||||||
    const data = tokens[username]
 | 
					    url: '/user/login',
 | 
				
			||||||
 | 
					    type: 'post',
 | 
				
			||||||
 | 
					    response: config => {
 | 
				
			||||||
 | 
					      const { username } = config.body
 | 
				
			||||||
 | 
					      const token = tokens[username]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (data) {
 | 
					      // mock error
 | 
				
			||||||
      return {
 | 
					      if (!token) {
 | 
				
			||||||
        code: 20000,
 | 
					 | 
				
			||||||
        data
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          code: 60204,
 | 
					          code: 60204,
 | 
				
			||||||
          message: 'Account and password are incorrect.'
 | 
					          message: 'Account and password are incorrect.'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        code: 20000,
 | 
				
			||||||
 | 
					        data: token
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  getInfo: res => {
 | 
					
 | 
				
			||||||
    const { token } = param2Obj(res.url)
 | 
					  // get user info
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    url: '/user/info\.*',
 | 
				
			||||||
 | 
					    type: 'get',
 | 
				
			||||||
 | 
					    response: config => {
 | 
				
			||||||
 | 
					      const { token } = config.query
 | 
				
			||||||
      const info = users[token]
 | 
					      const info = users[token]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (info) {
 | 
					      // mock error
 | 
				
			||||||
 | 
					      if (!info) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          code: 50008,
 | 
				
			||||||
 | 
					          message: 'Login failed, unable to get user details.'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        code: 20000,
 | 
					        code: 20000,
 | 
				
			||||||
        data: info
 | 
					        data: info
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      code: 50008,
 | 
					 | 
				
			||||||
      message: 'Login failed, unable to get user details.'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  logout: () => {
 | 
					
 | 
				
			||||||
 | 
					  // user logout
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    url: '/user/logout',
 | 
				
			||||||
 | 
					    type: 'post',
 | 
				
			||||||
 | 
					    response: _ => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        code: 20000,
 | 
					        code: 20000,
 | 
				
			||||||
        data: 'success'
 | 
					        data: 'success'
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
export function param2Obj(url) {
 | 
					 | 
				
			||||||
  const search = url.split('?')[1]
 | 
					 | 
				
			||||||
  if (!search) {
 | 
					 | 
				
			||||||
    return {}
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return JSON.parse(
 | 
					 | 
				
			||||||
    '{"' +
 | 
					 | 
				
			||||||
      decodeURIComponent(search)
 | 
					 | 
				
			||||||
        .replace(/"/g, '\\"')
 | 
					 | 
				
			||||||
        .replace(/&/g, '","')
 | 
					 | 
				
			||||||
        .replace(/=/g, '":"') +
 | 
					 | 
				
			||||||
      '"}'
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										101
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,82 +1,59 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "vue-admin-template",
 | 
					  "name": "vue-admin-template",
 | 
				
			||||||
  "version": "3.9.0",
 | 
					  "version": "4.1.0",
 | 
				
			||||||
  "license": "MIT",
 | 
					 | 
				
			||||||
  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
 | 
					  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
 | 
				
			||||||
  "author": "Pan <panfree23@gmail.com>",
 | 
					  "author": "Pan <panfree23@gmail.com>",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
 | 
					    "dev": "vue-cli-service serve",
 | 
				
			||||||
    "start": "npm run dev",
 | 
					    "build:prod": "vue-cli-service build",
 | 
				
			||||||
    "build": "node build/build.js",
 | 
					    "build:stage": "vue-cli-service build --mode staging",
 | 
				
			||||||
    "build:report": "npm_config_report=true npm run build",
 | 
					    "preview": "node build/index.js --preview",
 | 
				
			||||||
    "lint": "eslint --ext .js,.vue src",
 | 
					    "lint": "eslint --ext .js,.vue src",
 | 
				
			||||||
    "test": "npm run lint",
 | 
					    "test:unit": "jest --clearCache && 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"
 | 
					    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "axios": "0.18.0",
 | 
					    "axios": "0.18.0",
 | 
				
			||||||
    "element-ui": "2.4.6",
 | 
					    "element-ui": "2.7.2",
 | 
				
			||||||
    "js-cookie": "2.2.0",
 | 
					    "js-cookie": "2.2.0",
 | 
				
			||||||
    "mockjs": "1.0.1-beta3",
 | 
					 | 
				
			||||||
    "normalize.css": "7.0.0",
 | 
					    "normalize.css": "7.0.0",
 | 
				
			||||||
    "nprogress": "0.2.0",
 | 
					    "nprogress": "0.2.0",
 | 
				
			||||||
    "vue": "2.5.17",
 | 
					    "path-to-regexp": "2.4.0",
 | 
				
			||||||
    "vue-router": "3.0.1",
 | 
					    "vue": "2.6.10",
 | 
				
			||||||
    "vuex": "3.0.1"
 | 
					    "vue-router": "3.0.6",
 | 
				
			||||||
 | 
					    "vuex": "3.1.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "autoprefixer": "8.5.0",
 | 
					    "@babel/core": "7.0.0",
 | 
				
			||||||
    "babel-core": "6.26.0",
 | 
					    "@babel/register": "7.0.0",
 | 
				
			||||||
    "babel-eslint": "8.2.6",
 | 
					    "@vue/cli-plugin-babel": "3.6.0",
 | 
				
			||||||
    "babel-helper-vue-jsx-merge-props": "2.0.3",
 | 
					    "@vue/cli-plugin-eslint": "3.6.0",
 | 
				
			||||||
    "babel-loader": "7.1.5",
 | 
					    "@vue/cli-plugin-unit-jest": "3.6.3",
 | 
				
			||||||
    "babel-plugin-syntax-jsx": "6.18.0",
 | 
					    "@vue/cli-service": "3.6.0",
 | 
				
			||||||
    "babel-plugin-transform-runtime": "6.23.0",
 | 
					    "@vue/test-utils": "1.0.0-beta.29",
 | 
				
			||||||
    "babel-plugin-transform-vue-jsx": "3.7.0",
 | 
					    "babel-core": "7.0.0-bridge.0",
 | 
				
			||||||
    "babel-preset-env": "1.7.0",
 | 
					    "babel-eslint": "10.0.1",
 | 
				
			||||||
    "babel-preset-stage-2": "6.24.1",
 | 
					    "babel-jest": "23.6.0",
 | 
				
			||||||
    "chalk": "2.4.1",
 | 
					    "chalk": "2.4.2",
 | 
				
			||||||
    "compression-webpack-plugin": "2.0.0",
 | 
					    "connect": "3.6.6",
 | 
				
			||||||
    "copy-webpack-plugin": "4.5.2",
 | 
					    "eslint": "5.15.3",
 | 
				
			||||||
    "css-loader": "1.0.0",
 | 
					    "eslint-plugin-vue": "5.2.2",
 | 
				
			||||||
    "eslint": "4.19.1",
 | 
					    "html-webpack-plugin": "3.2.0",
 | 
				
			||||||
    "eslint-friendly-formatter": "4.0.1",
 | 
					    "mockjs": "1.0.1-beta3",
 | 
				
			||||||
    "eslint-loader": "2.0.0",
 | 
					    "node-sass": "^4.9.0",
 | 
				
			||||||
    "eslint-plugin-vue": "4.7.1",
 | 
					    "runjs": "^4.3.2",
 | 
				
			||||||
    "eventsource-polyfill": "0.9.6",
 | 
					    "sass-loader": "^7.1.0",
 | 
				
			||||||
    "file-loader": "1.1.11",
 | 
					    "script-ext-html-webpack-plugin": "2.1.3",
 | 
				
			||||||
    "friendly-errors-webpack-plugin": "1.7.0",
 | 
					    "script-loader": "0.7.2",
 | 
				
			||||||
    "html-webpack-plugin": "4.0.0-alpha",
 | 
					    "serve-static": "^1.13.2",
 | 
				
			||||||
    "mini-css-extract-plugin": "0.4.1",
 | 
					    "svg-sprite-loader": "4.1.3",
 | 
				
			||||||
    "node-notifier": "5.2.1",
 | 
					    "svgo": "1.2.2",
 | 
				
			||||||
    "node-sass": "^4.7.2",
 | 
					    "vue-template-compiler": "2.6.10"
 | 
				
			||||||
    "optimize-css-assets-webpack-plugin": "5.0.0",
 | 
					 | 
				
			||||||
    "ora": "3.0.0",
 | 
					 | 
				
			||||||
    "path-to-regexp": "2.4.0",
 | 
					 | 
				
			||||||
    "portfinder": "1.0.16",
 | 
					 | 
				
			||||||
    "postcss-import": "12.0.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",
 | 
					 | 
				
			||||||
    "semver": "5.5.0",
 | 
					 | 
				
			||||||
    "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"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "engines": {
 | 
					  "engines": {
 | 
				
			||||||
    "node": ">= 6.0.0",
 | 
					    "node": ">=8.9",
 | 
				
			||||||
    "npm": ">= 3.0.0"
 | 
					    "npm": ">= 3.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "browserslist": [
 | 
					  "browserslist": [
 | 
				
			||||||
 
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB  | 
							
								
								
									
										17
									
								
								public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
 | 
				
			||||||
 | 
					    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
 | 
				
			||||||
 | 
					    <title><%= webpackConfig.name %></title>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <noscript>
 | 
				
			||||||
 | 
					      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
 | 
				
			||||||
 | 
					    </noscript>
 | 
				
			||||||
 | 
					    <div id="app"></div>
 | 
				
			||||||
 | 
					    <!-- built files will be auto injected -->
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div id="app">
 | 
					  <div id="app">
 | 
				
			||||||
    <router-view/>
 | 
					    <router-view />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,10 @@
 | 
				
			|||||||
import request from '@/utils/request'
 | 
					import request from '@/utils/request'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function login(username, password) {
 | 
					export function login(data) {
 | 
				
			||||||
  return request({
 | 
					  return request({
 | 
				
			||||||
    url: '/user/login',
 | 
					    url: '/user/login',
 | 
				
			||||||
    method: 'post',
 | 
					    method: 'post',
 | 
				
			||||||
    data: {
 | 
					    data
 | 
				
			||||||
      username,
 | 
					 | 
				
			||||||
      password
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
  <el-breadcrumb class="app-breadcrumb" separator="/">
 | 
					  <el-breadcrumb class="app-breadcrumb" separator="/">
 | 
				
			||||||
    <transition-group name="breadcrumb">
 | 
					    <transition-group name="breadcrumb">
 | 
				
			||||||
      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
 | 
					      <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">{{ item.meta.title }}</span>
 | 
					        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
 | 
				
			||||||
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
 | 
					        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
 | 
				
			||||||
      </el-breadcrumb-item>
 | 
					      </el-breadcrumb-item>
 | 
				
			||||||
    </transition-group>
 | 
					    </transition-group>
 | 
				
			||||||
@@ -28,15 +28,23 @@ export default {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    getBreadcrumb() {
 | 
					    getBreadcrumb() {
 | 
				
			||||||
      let matched = this.$route.matched.filter(item => item.name)
 | 
					      // only show routes with meta.title
 | 
				
			||||||
 | 
					      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
 | 
				
			||||||
      const first = matched[0]
 | 
					      const first = matched[0]
 | 
				
			||||||
      if (first && first.name !== 'dashboard') {
 | 
					
 | 
				
			||||||
 | 
					      if (!this.isDashboard(first)) {
 | 
				
			||||||
        matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
 | 
					        matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
 | 
					      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    isDashboard(route) {
 | 
				
			||||||
 | 
					      const name = route && route.name
 | 
				
			||||||
 | 
					      if (!name) {
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    pathCompile(path) {
 | 
					    pathCompile(path) {
 | 
				
			||||||
      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
 | 
					      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
 | 
				
			||||||
      const { params } = this.$route
 | 
					      const { params } = this.$route
 | 
				
			||||||
@@ -55,15 +63,16 @@ export default {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
  .app-breadcrumb.el-breadcrumb {
 | 
					.app-breadcrumb.el-breadcrumb {
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
  font-size: 14px;
 | 
					  font-size: 14px;
 | 
				
			||||||
  line-height: 50px;
 | 
					  line-height: 50px;
 | 
				
			||||||
    margin-left: 10px;
 | 
					  margin-left: 8px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .no-redirect {
 | 
					  .no-redirect {
 | 
				
			||||||
    color: #97a8be;
 | 
					    color: #97a8be;
 | 
				
			||||||
    cursor: text;
 | 
					    cursor: text;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div style="padding: 0 15px;" @click="toggleClick">
 | 
				
			||||||
    <svg
 | 
					    <svg
 | 
				
			||||||
      :class="{'is-active':isActive}"
 | 
					      :class="{'is-active':isActive}"
 | 
				
			||||||
      class="hamburger"
 | 
					      class="hamburger"
 | 
				
			||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
      xmlns="http://www.w3.org/2000/svg"
 | 
					      xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
      width="64"
 | 
					      width="64"
 | 
				
			||||||
      height="64"
 | 
					      height="64"
 | 
				
			||||||
      @click="toggleClick">
 | 
					    >
 | 
				
			||||||
      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
 | 
					      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
 | 
				
			||||||
    </svg>
 | 
					    </svg>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@@ -20,10 +20,11 @@ export default {
 | 
				
			|||||||
    isActive: {
 | 
					    isActive: {
 | 
				
			||||||
      type: Boolean,
 | 
					      type: Boolean,
 | 
				
			||||||
      default: false
 | 
					      default: false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    toggleClick: {
 | 
					  methods: {
 | 
				
			||||||
      type: Function,
 | 
					    toggleClick() {
 | 
				
			||||||
      default: null
 | 
					      this.$emit('toggleClick')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -32,10 +33,11 @@ export default {
 | 
				
			|||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
.hamburger {
 | 
					.hamburger {
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
  cursor: pointer;
 | 
					  vertical-align: middle;
 | 
				
			||||||
  width: 20px;
 | 
					  width: 20px;
 | 
				
			||||||
  height: 20px;
 | 
					  height: 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.hamburger.is-active {
 | 
					.hamburger.is-active {
 | 
				
			||||||
  transform: rotate(180deg);
 | 
					  transform: rotate(180deg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <svg :class="svgClass" aria-hidden="true" v-on="$listeners">
 | 
					  <svg :class="svgClass" aria-hidden="true" v-on="$listeners">
 | 
				
			||||||
    <use :xlink:href="iconName"/>
 | 
					    <use :xlink:href="iconName" />
 | 
				
			||||||
  </svg>
 | 
					  </svg>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import Vue from 'vue'
 | 
					import Vue from 'vue'
 | 
				
			||||||
import SvgIcon from '@/components/SvgIcon' // svg组件
 | 
					import SvgIcon from '@/components/SvgIcon'// svg component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// register globally
 | 
					// register globally
 | 
				
			||||||
Vue.component('svg-icon', SvgIcon)
 | 
					Vue.component('svg-icon', SvgIcon)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const requireAll = requireContext => requireContext.keys().map(requireContext)
 | 
					 | 
				
			||||||
const req = require.context('./svg', false, /\.svg$/)
 | 
					const req = require.context('./svg', false, /\.svg$/)
 | 
				
			||||||
 | 
					const requireAll = requireContext => requireContext.keys().map(requireContext)
 | 
				
			||||||
requireAll(req)
 | 
					requireAll(req)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/icons/svg/dashboard.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
@@ -1 +1 @@
 | 
				
			|||||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></g></svg>
 | 
					<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 285 B  | 
@@ -1 +1 @@
 | 
				
			|||||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></g></svg>
 | 
					<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 597 B  | 
@@ -1,9 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <section class="app-main">
 | 
					  <section class="app-main">
 | 
				
			||||||
    <transition name="fade-transform" mode="out-in">
 | 
					    <transition name="fade-transform" mode="out-in">
 | 
				
			||||||
      <!-- or name="fade" -->
 | 
					      <router-view :key="key" />
 | 
				
			||||||
      <!-- <router-view :key="key"></router-view> -->
 | 
					 | 
				
			||||||
      <router-view/>
 | 
					 | 
				
			||||||
    </transition>
 | 
					    </transition>
 | 
				
			||||||
  </section>
 | 
					  </section>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -12,9 +10,9 @@
 | 
				
			|||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: 'AppMain',
 | 
					  name: 'AppMain',
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
    // key() {
 | 
					    key() {
 | 
				
			||||||
    //   return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
 | 
					      return this.$route.fullPath
 | 
				
			||||||
    // }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@@ -23,7 +21,11 @@ export default {
 | 
				
			|||||||
.app-main {
 | 
					.app-main {
 | 
				
			||||||
  /*50 = navbar  */
 | 
					  /*50 = navbar  */
 | 
				
			||||||
  min-height: calc(100vh - 50px);
 | 
					  min-height: calc(100vh - 50px);
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
  position: relative;
 | 
					  position: relative;
 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.fixed-header+.app-main {
 | 
				
			||||||
 | 
					  padding-top: 50px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										139
									
								
								src/layout/components/Navbar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="navbar">
 | 
				
			||||||
 | 
					    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <breadcrumb class="breadcrumb-container" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="right-menu">
 | 
				
			||||||
 | 
					      <el-dropdown class="avatar-container" trigger="click">
 | 
				
			||||||
 | 
					        <div class="avatar-wrapper">
 | 
				
			||||||
 | 
					          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
 | 
				
			||||||
 | 
					          <i class="el-icon-caret-bottom" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <el-dropdown-menu slot="dropdown" class="user-dropdown">
 | 
				
			||||||
 | 
					          <router-link to="/">
 | 
				
			||||||
 | 
					            <el-dropdown-item>
 | 
				
			||||||
 | 
					              Home
 | 
				
			||||||
 | 
					            </el-dropdown-item>
 | 
				
			||||||
 | 
					          </router-link>
 | 
				
			||||||
 | 
					          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
 | 
				
			||||||
 | 
					            <el-dropdown-item>Github</el-dropdown-item>
 | 
				
			||||||
 | 
					          </a>
 | 
				
			||||||
 | 
					          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
 | 
				
			||||||
 | 
					            <el-dropdown-item>Docs</el-dropdown-item>
 | 
				
			||||||
 | 
					          </a>
 | 
				
			||||||
 | 
					          <el-dropdown-item divided>
 | 
				
			||||||
 | 
					            <span style="display:block;" @click="logout">Log Out</span>
 | 
				
			||||||
 | 
					          </el-dropdown-item>
 | 
				
			||||||
 | 
					        </el-dropdown-menu>
 | 
				
			||||||
 | 
					      </el-dropdown>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex'
 | 
				
			||||||
 | 
					import Breadcrumb from '@/components/Breadcrumb'
 | 
				
			||||||
 | 
					import Hamburger from '@/components/Hamburger'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    Breadcrumb,
 | 
				
			||||||
 | 
					    Hamburger
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    ...mapGetters([
 | 
				
			||||||
 | 
					      'sidebar',
 | 
				
			||||||
 | 
					      'avatar'
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    toggleSideBar() {
 | 
				
			||||||
 | 
					      this.$store.dispatch('app/toggleSideBar')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async logout() {
 | 
				
			||||||
 | 
					      await this.$store.dispatch('user/logout')
 | 
				
			||||||
 | 
					      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.navbar {
 | 
				
			||||||
 | 
					  height: 50px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  background: #fff;
 | 
				
			||||||
 | 
					  box-shadow: 0 1px 4px rgba(0,21,41,.08);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .hamburger-container {
 | 
				
			||||||
 | 
					    line-height: 46px;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    transition: background .3s;
 | 
				
			||||||
 | 
					    -webkit-tap-highlight-color:transparent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      background: rgba(0, 0, 0, .025)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .breadcrumb-container {
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .right-menu {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    line-height: 50px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:focus {
 | 
				
			||||||
 | 
					      outline: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .right-menu-item {
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      padding: 0 8px;
 | 
				
			||||||
 | 
					      height: 100%;
 | 
				
			||||||
 | 
					      font-size: 18px;
 | 
				
			||||||
 | 
					      color: #5a5e66;
 | 
				
			||||||
 | 
					      vertical-align: text-bottom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.hover-effect {
 | 
				
			||||||
 | 
					        cursor: pointer;
 | 
				
			||||||
 | 
					        transition: background .3s;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &:hover {
 | 
				
			||||||
 | 
					          background: rgba(0, 0, 0, .025)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .avatar-container {
 | 
				
			||||||
 | 
					      margin-right: 30px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .avatar-wrapper {
 | 
				
			||||||
 | 
					        margin-top: 5px;
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .user-avatar {
 | 
				
			||||||
 | 
					          cursor: pointer;
 | 
				
			||||||
 | 
					          width: 40px;
 | 
				
			||||||
 | 
					          height: 40px;
 | 
				
			||||||
 | 
					          border-radius: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .el-icon-caret-bottom {
 | 
				
			||||||
 | 
					          cursor: pointer;
 | 
				
			||||||
 | 
					          position: absolute;
 | 
				
			||||||
 | 
					          right: -20px;
 | 
				
			||||||
 | 
					          top: 25px;
 | 
				
			||||||
 | 
					          font-size: 12px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/layout/components/Sidebar/FixiOSBug.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    device() {
 | 
				
			||||||
 | 
					      return this.$store.state.app.device
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
 | 
				
			||||||
 | 
					    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
 | 
				
			||||||
 | 
					    this.fixBugIniOS()
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    fixBugIniOS() {
 | 
				
			||||||
 | 
					      const $subMenu = this.$refs.subMenu
 | 
				
			||||||
 | 
					      if ($subMenu) {
 | 
				
			||||||
 | 
					        const handleMouseleave = $subMenu.handleMouseleave
 | 
				
			||||||
 | 
					        $subMenu.handleMouseleave = (e) => {
 | 
				
			||||||
 | 
					          if (this.device === 'mobile') {
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          handleMouseleave(e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,18 +3,17 @@ export default {
 | 
				
			|||||||
  name: 'MenuItem',
 | 
					  name: 'MenuItem',
 | 
				
			||||||
  functional: true,
 | 
					  functional: true,
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    meta: {
 | 
					    icon: {
 | 
				
			||||||
      type: Object,
 | 
					      type: String,
 | 
				
			||||||
      default: () => {
 | 
					      default: ''
 | 
				
			||||||
        return {
 | 
					    },
 | 
				
			||||||
          title: '',
 | 
					    title: {
 | 
				
			||||||
          icon: ''
 | 
					      type: String,
 | 
				
			||||||
        }
 | 
					      default: ''
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  render(h, context) {
 | 
					  render(h, context) {
 | 
				
			||||||
    const { icon, title } = context.props.meta
 | 
					    const { icon, title } = context.props
 | 
				
			||||||
    const vnodes = []
 | 
					    const vnodes = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (icon) {
 | 
					    if (icon) {
 | 
				
			||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <!-- eslint-disable vue/require-component-is -->
 | 
					  <!-- eslint-disable vue/require-component-is -->
 | 
				
			||||||
  <component v-bind="linkProps(to)">
 | 
					  <component v-bind="linkProps(to)">
 | 
				
			||||||
    <slot/>
 | 
					    <slot />
 | 
				
			||||||
  </component>
 | 
					  </component>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/layout/components/Sidebar/Logo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
 | 
				
			||||||
 | 
					    <transition name="sidebarLogoFade">
 | 
				
			||||||
 | 
					      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
 | 
				
			||||||
 | 
					        <img v-if="logo" :src="logo" class="sidebar-logo">
 | 
				
			||||||
 | 
					        <h1 v-else class="sidebar-title">{{ title }} </h1>
 | 
				
			||||||
 | 
					      </router-link>
 | 
				
			||||||
 | 
					      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
 | 
				
			||||||
 | 
					        <img v-if="logo" :src="logo" class="sidebar-logo">
 | 
				
			||||||
 | 
					        <h1 class="sidebar-title">{{ title }} </h1>
 | 
				
			||||||
 | 
					      </router-link>
 | 
				
			||||||
 | 
					    </transition>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  name: 'SidebarLogo',
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    collapse: {
 | 
				
			||||||
 | 
					      type: Boolean,
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      title: 'Vue Admin Template',
 | 
				
			||||||
 | 
					      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.sidebarLogoFade-enter-active {
 | 
				
			||||||
 | 
					  transition: opacity 1.5s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sidebarLogoFade-enter,
 | 
				
			||||||
 | 
					.sidebarLogoFade-leave-to {
 | 
				
			||||||
 | 
					  opacity: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sidebar-logo-container {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 50px;
 | 
				
			||||||
 | 
					  line-height: 50px;
 | 
				
			||||||
 | 
					  background: #2b2f3a;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  & .sidebar-logo-link {
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    & .sidebar-logo {
 | 
				
			||||||
 | 
					      width: 32px;
 | 
				
			||||||
 | 
					      height: 32px;
 | 
				
			||||||
 | 
					      vertical-align: middle;
 | 
				
			||||||
 | 
					      margin-right: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    & .sidebar-title {
 | 
				
			||||||
 | 
					      display: inline-block;
 | 
				
			||||||
 | 
					      margin: 0;
 | 
				
			||||||
 | 
					      color: #fff;
 | 
				
			||||||
 | 
					      font-weight: 600;
 | 
				
			||||||
 | 
					      line-height: 50px;
 | 
				
			||||||
 | 
					      font-size: 14px;
 | 
				
			||||||
 | 
					      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
 | 
				
			||||||
 | 
					      vertical-align: middle;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &.collapse {
 | 
				
			||||||
 | 
					    .sidebar-logo {
 | 
				
			||||||
 | 
					      margin-right: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,27 +1,26 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div v-if="!item.hidden" class="menu-wrapper">
 | 
					  <div v-if="!item.hidden" class="menu-wrapper">
 | 
				
			||||||
 | 
					 | 
				
			||||||
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
 | 
					    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
 | 
				
			||||||
      <app-link :to="resolvePath(onlyOneChild.path)">
 | 
					      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
 | 
				
			||||||
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
 | 
					        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
 | 
				
			||||||
          <item :meta="Object.assign({},item.meta,onlyOneChild.meta)" />
 | 
					          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
 | 
				
			||||||
        </el-menu-item>
 | 
					        </el-menu-item>
 | 
				
			||||||
      </app-link>
 | 
					      </app-link>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
 | 
					    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
 | 
				
			||||||
      <template slot="title">
 | 
					      <template slot="title">
 | 
				
			||||||
        <item :meta="item.meta" />
 | 
					        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
      <sidebar-item
 | 
					      <sidebar-item
 | 
				
			||||||
        v-for="child in item.children"
 | 
					        v-for="child in item.children"
 | 
				
			||||||
 | 
					        :key="child.path"
 | 
				
			||||||
        :is-nest="true"
 | 
					        :is-nest="true"
 | 
				
			||||||
        :item="child"
 | 
					        :item="child"
 | 
				
			||||||
        :key="child.path"
 | 
					 | 
				
			||||||
        :base-path="resolvePath(child.path)"
 | 
					        :base-path="resolvePath(child.path)"
 | 
				
			||||||
        class="nest-menu" />
 | 
					        class="nest-menu"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
    </el-submenu>
 | 
					    </el-submenu>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,10 +29,12 @@ import path from 'path'
 | 
				
			|||||||
import { isExternal } from '@/utils/validate'
 | 
					import { isExternal } from '@/utils/validate'
 | 
				
			||||||
import Item from './Item'
 | 
					import Item from './Item'
 | 
				
			||||||
import AppLink from './Link'
 | 
					import AppLink from './Link'
 | 
				
			||||||
 | 
					import FixiOSBug from './FixiOSBug'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: 'SidebarItem',
 | 
					  name: 'SidebarItem',
 | 
				
			||||||
  components: { Item, AppLink },
 | 
					  components: { Item, AppLink },
 | 
				
			||||||
 | 
					  mixins: [FixiOSBug],
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    // route object
 | 
					    // route object
 | 
				
			||||||
    item: {
 | 
					    item: {
 | 
				
			||||||
@@ -84,6 +85,9 @@ export default {
 | 
				
			|||||||
      if (isExternal(routePath)) {
 | 
					      if (isExternal(routePath)) {
 | 
				
			||||||
        return routePath
 | 
					        return routePath
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      if (isExternal(this.basePath)) {
 | 
				
			||||||
 | 
					        return this.basePath
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      return path.resolve(this.basePath, routePath)
 | 
					      return path.resolve(this.basePath, routePath)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/layout/components/Sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div :class="{'has-logo':showLogo}">
 | 
				
			||||||
 | 
					    <logo v-if="showLogo" :collapse="isCollapse" />
 | 
				
			||||||
 | 
					    <el-scrollbar wrap-class="scrollbar-wrapper">
 | 
				
			||||||
 | 
					      <el-menu
 | 
				
			||||||
 | 
					        :default-active="activeMenu"
 | 
				
			||||||
 | 
					        :collapse="isCollapse"
 | 
				
			||||||
 | 
					        :background-color="variables.menuBg"
 | 
				
			||||||
 | 
					        :text-color="variables.menuText"
 | 
				
			||||||
 | 
					        :unique-opened="false"
 | 
				
			||||||
 | 
					        :active-text-color="variables.menuActiveText"
 | 
				
			||||||
 | 
					        :collapse-transition="false"
 | 
				
			||||||
 | 
					        mode="vertical"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
 | 
				
			||||||
 | 
					      </el-menu>
 | 
				
			||||||
 | 
					    </el-scrollbar>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex'
 | 
				
			||||||
 | 
					import Logo from './Logo'
 | 
				
			||||||
 | 
					import SidebarItem from './SidebarItem'
 | 
				
			||||||
 | 
					import variables from '@/styles/variables.scss'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  components: { SidebarItem, Logo },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    ...mapGetters([
 | 
				
			||||||
 | 
					      'permission_routes',
 | 
				
			||||||
 | 
					      'sidebar'
 | 
				
			||||||
 | 
					    ]),
 | 
				
			||||||
 | 
					    activeMenu() {
 | 
				
			||||||
 | 
					      const route = this.$route
 | 
				
			||||||
 | 
					      const { meta, path } = route
 | 
				
			||||||
 | 
					      // if set path, the sidebar will highlight the path you set
 | 
				
			||||||
 | 
					      if (meta.activeMenu) {
 | 
				
			||||||
 | 
					        return meta.activeMenu
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return path
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    showLogo() {
 | 
				
			||||||
 | 
					      return this.$store.state.settings.sidebarLogo
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    variables() {
 | 
				
			||||||
 | 
					      return variables
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    isCollapse() {
 | 
				
			||||||
 | 
					      return !this.sidebar.opened
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -1,10 +1,12 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div :class="classObj" class="app-wrapper">
 | 
					  <div :class="classObj" class="app-wrapper">
 | 
				
			||||||
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
 | 
					    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
 | 
				
			||||||
    <sidebar class="sidebar-container"/>
 | 
					    <sidebar class="sidebar-container" />
 | 
				
			||||||
    <div class="main-container">
 | 
					    <div class="main-container">
 | 
				
			||||||
      <navbar/>
 | 
					      <div :class="{'fixed-header':fixedHeader}">
 | 
				
			||||||
      <app-main/>
 | 
					        <navbar />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <app-main />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -28,6 +30,9 @@ export default {
 | 
				
			|||||||
    device() {
 | 
					    device() {
 | 
				
			||||||
      return this.$store.state.app.device
 | 
					      return this.$store.state.app.device
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    fixedHeader() {
 | 
				
			||||||
 | 
					      return this.$store.state.settings.fixedHeader
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    classObj() {
 | 
					    classObj() {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        hideSidebar: !this.sidebar.opened,
 | 
					        hideSidebar: !this.sidebar.opened,
 | 
				
			||||||
@@ -45,8 +50,10 @@ export default {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
  @import "src/styles/mixin.scss";
 | 
					  @import "~@/styles/mixin.scss";
 | 
				
			||||||
 | 
					  @import "~@/styles/variables.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .app-wrapper {
 | 
					  .app-wrapper {
 | 
				
			||||||
    @include clearfix;
 | 
					    @include clearfix;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
@@ -66,4 +73,21 @@ export default {
 | 
				
			|||||||
    position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
    z-index: 999;
 | 
					    z-index: 999;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .fixed-header {
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    z-index: 9;
 | 
				
			||||||
 | 
					    width: calc(100% - #{$sideBarWidth});
 | 
				
			||||||
 | 
					    transition: width 0.28s;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .hideSidebar .fixed-header {
 | 
				
			||||||
 | 
					    width: calc(100% - 54px)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .mobile .fixed-header {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/layout/mixin/ResizeHandler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import store from '@/store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { body } = document
 | 
				
			||||||
 | 
					const WIDTH = 992 // refer to Bootstrap's responsive design
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  watch: {
 | 
				
			||||||
 | 
					    $route(route) {
 | 
				
			||||||
 | 
					      if (this.device === 'mobile' && this.sidebar.opened) {
 | 
				
			||||||
 | 
					        store.dispatch('app/closeSideBar', { withoutAnimation: false })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  beforeMount() {
 | 
				
			||||||
 | 
					    window.addEventListener('resize', this.$_resizeHandler)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  beforeDestroy() {
 | 
				
			||||||
 | 
					    window.removeEventListener('resize', this.$_resizeHandler)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    const isMobile = this.$_isMobile()
 | 
				
			||||||
 | 
					    if (isMobile) {
 | 
				
			||||||
 | 
					      store.dispatch('app/toggleDevice', 'mobile')
 | 
				
			||||||
 | 
					      store.dispatch('app/closeSideBar', { withoutAnimation: true })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    // use $_ for mixins properties
 | 
				
			||||||
 | 
					    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
 | 
				
			||||||
 | 
					    $_isMobile() {
 | 
				
			||||||
 | 
					      const rect = body.getBoundingClientRect()
 | 
				
			||||||
 | 
					      return rect.width - 1 < WIDTH
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    $_resizeHandler() {
 | 
				
			||||||
 | 
					      if (!document.hidden) {
 | 
				
			||||||
 | 
					        const isMobile = this.$_isMobile()
 | 
				
			||||||
 | 
					        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isMobile) {
 | 
				
			||||||
 | 
					          store.dispatch('app/closeSideBar', { withoutAnimation: true })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						@@ -16,15 +16,15 @@ import '@/icons' // icon
 | 
				
			|||||||
import '@/permission' // permission control
 | 
					import '@/permission' // permission control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This project originally used easy-mock to simulate data,
 | 
					 * If you don't want to use mock-server
 | 
				
			||||||
 * but its official service is very unstable,
 | 
					 * you want to use mockjs for request interception
 | 
				
			||||||
 * and you can build your own service if you need it.
 | 
					 * you can execute:
 | 
				
			||||||
 * So here I use Mock.js for local emulation,
 | 
					 *
 | 
				
			||||||
 * it will intercept your request, so you won't see the request in the network.
 | 
					 * import { mockXHR } from '../mock'
 | 
				
			||||||
 * If you remove `../mock` it will automatically request easy-mock data.
 | 
					 * mockXHR()
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
import '../mock' // simulation data
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// set ElementUI lang to EN
 | 
				
			||||||
Vue.use(ElementUI, { locale })
 | 
					Vue.use(ElementUI, { locale })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.config.productionTip = false
 | 
					Vue.config.productionTip = false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,43 +1,74 @@
 | 
				
			|||||||
import router from './router'
 | 
					import router from './router'
 | 
				
			||||||
import store from './store'
 | 
					import store from './store'
 | 
				
			||||||
 | 
					import { Message } from 'element-ui'
 | 
				
			||||||
import NProgress from 'nprogress' // progress bar
 | 
					import NProgress from 'nprogress' // progress bar
 | 
				
			||||||
import 'nprogress/nprogress.css' // progress bar style
 | 
					import 'nprogress/nprogress.css' // progress bar style
 | 
				
			||||||
import { Message } from 'element-ui'
 | 
					import { getToken } from '@/utils/auth' // get token from cookie
 | 
				
			||||||
import { getToken } from '@/utils/auth' // getToken from cookie
 | 
					import getPageTitle from '@/utils/get-page-title'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NProgress.configure({ showSpinner: false })// NProgress configuration
 | 
					NProgress.configure({ showSpinner: false }) // NProgress Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const whiteList = ['/login'] // 不重定向白名单
 | 
					const whiteList = ['/login'] // no redirect whitelist
 | 
				
			||||||
router.beforeEach((to, from, next) => {
 | 
					
 | 
				
			||||||
 | 
					router.beforeEach(async(to, from, next) => {
 | 
				
			||||||
 | 
					  // start progress bar
 | 
				
			||||||
  NProgress.start()
 | 
					  NProgress.start()
 | 
				
			||||||
  if (getToken()) {
 | 
					
 | 
				
			||||||
 | 
					  // set page title
 | 
				
			||||||
 | 
					  document.title = getPageTitle(to.meta.title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // determine whether the user has logged in
 | 
				
			||||||
 | 
					  const hasToken = getToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (hasToken) {
 | 
				
			||||||
    if (to.path === '/login') {
 | 
					    if (to.path === '/login') {
 | 
				
			||||||
 | 
					      // if is logged in, redirect to the home page
 | 
				
			||||||
      next({ path: '/' })
 | 
					      next({ path: '/' })
 | 
				
			||||||
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
 | 
					      NProgress.done()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (store.getters.roles.length === 0) {
 | 
					      // determine whether the user has obtained his permission roles through getInfo
 | 
				
			||||||
        store.dispatch('GetInfo').then(res => { // 拉取用户信息
 | 
					      const hasRoles = store.getters.roles && store.getters.roles.length > 0
 | 
				
			||||||
 | 
					      if (hasRoles) {
 | 
				
			||||||
        next()
 | 
					        next()
 | 
				
			||||||
        }).catch((err) => {
 | 
					 | 
				
			||||||
          store.dispatch('FedLogOut').then(() => {
 | 
					 | 
				
			||||||
            Message.error(err || 'Verification failed, please login again')
 | 
					 | 
				
			||||||
            next({ path: '/' })
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        next()
 | 
					        try {
 | 
				
			||||||
 | 
					          // get user info
 | 
				
			||||||
 | 
					          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
 | 
				
			||||||
 | 
					          const { roles } = await store.dispatch('user/getInfo')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // generate accessible routes map based on roles
 | 
				
			||||||
 | 
					          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // dynamically add accessible routes
 | 
				
			||||||
 | 
					          router.addRoutes(accessRoutes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // hack method to ensure that addRoutes is complete
 | 
				
			||||||
 | 
					          // set the replace: true, so the navigation will not leave a history record
 | 
				
			||||||
 | 
					          next({ ...to, replace: true })
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          // remove token and go to login page to re-login
 | 
				
			||||||
 | 
					          await store.dispatch('user/resetToken')
 | 
				
			||||||
 | 
					          Message.error(error || 'Has Error')
 | 
				
			||||||
 | 
					          next(`/login?redirect=${to.path}`)
 | 
				
			||||||
 | 
					          NProgress.done()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					    /* has no token*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (whiteList.indexOf(to.path) !== -1) {
 | 
					    if (whiteList.indexOf(to.path) !== -1) {
 | 
				
			||||||
 | 
					      // in the free login whitelist, go directly
 | 
				
			||||||
      next()
 | 
					      next()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
 | 
					      // other pages that do not have permission to access are redirected to the login page.
 | 
				
			||||||
 | 
					      next(`/login?redirect=${to.path}`)
 | 
				
			||||||
      NProgress.done()
 | 
					      NProgress.done()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.afterEach(() => {
 | 
					router.afterEach(() => {
 | 
				
			||||||
  NProgress.done() // 结束Progress
 | 
					  // finish progress bar
 | 
				
			||||||
 | 
					  NProgress.done()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,40 +1,57 @@
 | 
				
			|||||||
import Vue from 'vue'
 | 
					import Vue from 'vue'
 | 
				
			||||||
import Router from 'vue-router'
 | 
					import Router from 'vue-router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
 | 
					 | 
				
			||||||
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Vue.use(Router)
 | 
					Vue.use(Router)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Layout */
 | 
					/* Layout */
 | 
				
			||||||
import Layout from '../views/layout/Layout'
 | 
					import Layout from '@/layout'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
 | 
					 * Note: sub-menu only appear when route children.length >= 1
 | 
				
			||||||
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
 | 
					 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 | 
				
			||||||
*                                if not set alwaysShow, only more than one route under the children
 | 
					 *
 | 
				
			||||||
*                                it will becomes nested mode, otherwise not show the root menu
 | 
					 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 | 
				
			||||||
* redirect: noredirect           if `redirect:noredirect` will no redirect in the breadcrumb
 | 
					 * alwaysShow: true               if set true, will always show the root menu
 | 
				
			||||||
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
 | 
					 *                                if not set alwaysShow, when item has more than one children route,
 | 
				
			||||||
* meta : {
 | 
					 *                                it will becomes nested mode, otherwise not show the root menu
 | 
				
			||||||
    title: 'title'               the name show in subMenu and breadcrumb (recommend set)
 | 
					 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 | 
				
			||||||
 | 
					 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 | 
				
			||||||
 | 
					 * meta : {
 | 
				
			||||||
 | 
					    roles: ['admin','editor']    control the page roles (you can set multiple roles)
 | 
				
			||||||
 | 
					    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
 | 
				
			||||||
    icon: 'svg-name'             the icon show in the sidebar
 | 
					    icon: 'svg-name'             the icon show in the sidebar
 | 
				
			||||||
    breadcrumb: false            if false, the item will hidden in breadcrumb(default is true)
 | 
					    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
 | 
				
			||||||
 | 
					    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
**/
 | 
					 */
 | 
				
			||||||
export const constantRouterMap = [
 | 
					
 | 
				
			||||||
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
 | 
					/**
 | 
				
			||||||
  { path: '/404', component: () => import('@/views/404'), hidden: true },
 | 
					 * constantRoutes
 | 
				
			||||||
 | 
					 * a base page that does not have permission requirements
 | 
				
			||||||
 | 
					 * all roles can be accessed
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const constantRoutes = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/login',
 | 
				
			||||||
 | 
					    component: () => import('@/views/login/index'),
 | 
				
			||||||
 | 
					    hidden: true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/404',
 | 
				
			||||||
 | 
					    component: () => import('@/views/404'),
 | 
				
			||||||
 | 
					    hidden: true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    path: '/',
 | 
					    path: '/',
 | 
				
			||||||
    component: Layout,
 | 
					    component: Layout,
 | 
				
			||||||
    redirect: '/dashboard',
 | 
					    redirect: '/dashboard',
 | 
				
			||||||
    name: 'Dashboard',
 | 
					 | 
				
			||||||
    hidden: true,
 | 
					 | 
				
			||||||
    children: [{
 | 
					    children: [{
 | 
				
			||||||
      path: 'dashboard',
 | 
					      path: 'dashboard',
 | 
				
			||||||
      component: () => import('@/views/dashboard/index')
 | 
					      name: 'Dashboard',
 | 
				
			||||||
 | 
					      component: () => import('@/views/dashboard/index'),
 | 
				
			||||||
 | 
					      meta: { title: 'Dashboard', icon: 'dashboard' }
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,8 +88,14 @@ export const constantRouterMap = [
 | 
				
			|||||||
        meta: { title: 'Form', icon: 'form' }
 | 
					        meta: { title: 'Form', icon: 'form' }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * asyncRoutes
 | 
				
			||||||
 | 
					 * the routes that need to be dynamically loaded based on user roles
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const asyncRoutes = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    path: '/nested',
 | 
					    path: '/nested',
 | 
				
			||||||
    component: Layout,
 | 
					    component: Layout,
 | 
				
			||||||
@@ -142,11 +165,22 @@ export const constantRouterMap = [
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 404 page must be placed at the end !!!
 | 
				
			||||||
  { path: '*', redirect: '/404', hidden: true }
 | 
					  { path: '*', redirect: '/404', hidden: true }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new Router({
 | 
					const createRouter = () => new Router({
 | 
				
			||||||
  // mode: 'history', //后端支持可开
 | 
					  // mode: 'history', // require service support
 | 
				
			||||||
  scrollBehavior: () => ({ y: 0 }),
 | 
					  scrollBehavior: () => ({ y: 0 }),
 | 
				
			||||||
  routes: constantRouterMap
 | 
					  routes: constantRoutes
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = createRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
 | 
				
			||||||
 | 
					export function resetRouter() {
 | 
				
			||||||
 | 
					  const newRouter = createRouter()
 | 
				
			||||||
 | 
					  router.matcher = newRouter.matcher // reset router
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default router
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  title: 'Vue Admin Template',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {boolean} true | false
 | 
				
			||||||
 | 
					   * @description Whether fix the header
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  fixedHeader: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {boolean} true | false
 | 
				
			||||||
 | 
					   * @description Whether show the logo in sidebar
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  sidebarLogo: false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,7 @@ const getters = {
 | 
				
			|||||||
  token: state => state.user.token,
 | 
					  token: state => state.user.token,
 | 
				
			||||||
  avatar: state => state.user.avatar,
 | 
					  avatar: state => state.user.avatar,
 | 
				
			||||||
  name: state => state.user.name,
 | 
					  name: state => state.user.name,
 | 
				
			||||||
  roles: state => state.user.roles
 | 
					  roles: state => state.user.roles,
 | 
				
			||||||
 | 
					  permission_routes: state => state.permission.routes
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export default getters
 | 
					export default getters
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,18 @@
 | 
				
			|||||||
import Vue from 'vue'
 | 
					import Vue from 'vue'
 | 
				
			||||||
import Vuex from 'vuex'
 | 
					import Vuex from 'vuex'
 | 
				
			||||||
import app from './modules/app'
 | 
					 | 
				
			||||||
import user from './modules/user'
 | 
					 | 
				
			||||||
import getters from './getters'
 | 
					import getters from './getters'
 | 
				
			||||||
 | 
					import app from './modules/app'
 | 
				
			||||||
 | 
					import permission from './modules/permission'
 | 
				
			||||||
 | 
					import settings from './modules/settings'
 | 
				
			||||||
 | 
					import user from './modules/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.use(Vuex)
 | 
					Vue.use(Vuex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const store = new Vuex.Store({
 | 
					const store = new Vuex.Store({
 | 
				
			||||||
  modules: {
 | 
					  modules: {
 | 
				
			||||||
    app,
 | 
					    app,
 | 
				
			||||||
 | 
					    permission,
 | 
				
			||||||
 | 
					    settings,
 | 
				
			||||||
    user
 | 
					    user
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  getters
 | 
					  getters
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,43 +1,48 @@
 | 
				
			|||||||
import Cookies from 'js-cookie'
 | 
					import Cookies from 'js-cookie'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const app = {
 | 
					const state = {
 | 
				
			||||||
  state: {
 | 
					 | 
				
			||||||
  sidebar: {
 | 
					  sidebar: {
 | 
				
			||||||
      opened: !+Cookies.get('sidebarStatus'),
 | 
					    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
 | 
				
			||||||
    withoutAnimation: false
 | 
					    withoutAnimation: false
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  device: 'desktop'
 | 
					  device: 'desktop'
 | 
				
			||||||
  },
 | 
					}
 | 
				
			||||||
  mutations: {
 | 
					
 | 
				
			||||||
 | 
					const mutations = {
 | 
				
			||||||
  TOGGLE_SIDEBAR: state => {
 | 
					  TOGGLE_SIDEBAR: state => {
 | 
				
			||||||
 | 
					    state.sidebar.opened = !state.sidebar.opened
 | 
				
			||||||
 | 
					    state.sidebar.withoutAnimation = false
 | 
				
			||||||
    if (state.sidebar.opened) {
 | 
					    if (state.sidebar.opened) {
 | 
				
			||||||
      Cookies.set('sidebarStatus', 1)
 | 
					      Cookies.set('sidebarStatus', 1)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      Cookies.set('sidebarStatus', 0)
 | 
					      Cookies.set('sidebarStatus', 0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      state.sidebar.opened = !state.sidebar.opened
 | 
					 | 
				
			||||||
      state.sidebar.withoutAnimation = false
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  CLOSE_SIDEBAR: (state, withoutAnimation) => {
 | 
					  CLOSE_SIDEBAR: (state, withoutAnimation) => {
 | 
				
			||||||
      Cookies.set('sidebarStatus', 1)
 | 
					    Cookies.set('sidebarStatus', 0)
 | 
				
			||||||
    state.sidebar.opened = false
 | 
					    state.sidebar.opened = false
 | 
				
			||||||
    state.sidebar.withoutAnimation = withoutAnimation
 | 
					    state.sidebar.withoutAnimation = withoutAnimation
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  TOGGLE_DEVICE: (state, device) => {
 | 
					  TOGGLE_DEVICE: (state, device) => {
 | 
				
			||||||
    state.device = device
 | 
					    state.device = device
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  },
 | 
					}
 | 
				
			||||||
  actions: {
 | 
					
 | 
				
			||||||
    ToggleSideBar: ({ commit }) => {
 | 
					const actions = {
 | 
				
			||||||
 | 
					  toggleSideBar({ commit }) {
 | 
				
			||||||
    commit('TOGGLE_SIDEBAR')
 | 
					    commit('TOGGLE_SIDEBAR')
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    CloseSideBar({ commit }, { withoutAnimation }) {
 | 
					  closeSideBar({ commit }, { withoutAnimation }) {
 | 
				
			||||||
    commit('CLOSE_SIDEBAR', withoutAnimation)
 | 
					    commit('CLOSE_SIDEBAR', withoutAnimation)
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
    ToggleDevice({ commit }, device) {
 | 
					  toggleDevice({ commit }, device) {
 | 
				
			||||||
    commit('TOGGLE_DEVICE', device)
 | 
					    commit('TOGGLE_DEVICE', device)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default app
 | 
					export default {
 | 
				
			||||||
 | 
					  namespaced: true,
 | 
				
			||||||
 | 
					  state,
 | 
				
			||||||
 | 
					  mutations,
 | 
				
			||||||
 | 
					  actions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										69
									
								
								src/store/modules/permission.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					import { asyncRoutes, constantRoutes } from '@/router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Use meta.role to determine if the current user has permission
 | 
				
			||||||
 | 
					 * @param roles
 | 
				
			||||||
 | 
					 * @param route
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function hasPermission(roles, route) {
 | 
				
			||||||
 | 
					  if (route.meta && route.meta.roles) {
 | 
				
			||||||
 | 
					    return roles.some(role => route.meta.roles.includes(role))
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Filter asynchronous routing tables by recursion
 | 
				
			||||||
 | 
					 * @param routes asyncRoutes
 | 
				
			||||||
 | 
					 * @param roles
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function filterAsyncRoutes(routes, roles) {
 | 
				
			||||||
 | 
					  const res = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  routes.forEach(route => {
 | 
				
			||||||
 | 
					    const tmp = { ...route }
 | 
				
			||||||
 | 
					    if (hasPermission(roles, tmp)) {
 | 
				
			||||||
 | 
					      if (tmp.children) {
 | 
				
			||||||
 | 
					        tmp.children = filterAsyncRoutes(tmp.children, roles)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.push(tmp)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = {
 | 
				
			||||||
 | 
					  routes: [],
 | 
				
			||||||
 | 
					  addRoutes: []
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mutations = {
 | 
				
			||||||
 | 
					  SET_ROUTES: (state, routes) => {
 | 
				
			||||||
 | 
					    state.addRoutes = routes
 | 
				
			||||||
 | 
					    state.routes = constantRoutes.concat(routes)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const actions = {
 | 
				
			||||||
 | 
					  generateRoutes({ commit }, roles) {
 | 
				
			||||||
 | 
					    return new Promise(resolve => {
 | 
				
			||||||
 | 
					      let accessedRoutes
 | 
				
			||||||
 | 
					      if (roles.includes('admin')) {
 | 
				
			||||||
 | 
					        accessedRoutes = asyncRoutes || []
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      commit('SET_ROUTES', accessedRoutes)
 | 
				
			||||||
 | 
					      resolve(accessedRoutes)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  namespaced: true,
 | 
				
			||||||
 | 
					  state,
 | 
				
			||||||
 | 
					  mutations,
 | 
				
			||||||
 | 
					  actions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/store/modules/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import defaultSettings from '@/settings'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const state = {
 | 
				
			||||||
 | 
					  showSettings: showSettings,
 | 
				
			||||||
 | 
					  fixedHeader: fixedHeader,
 | 
				
			||||||
 | 
					  sidebarLogo: sidebarLogo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mutations = {
 | 
				
			||||||
 | 
					  CHANGE_SETTING: (state, { key, value }) => {
 | 
				
			||||||
 | 
					    if (state.hasOwnProperty(key)) {
 | 
				
			||||||
 | 
					      state[key] = value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const actions = {
 | 
				
			||||||
 | 
					  changeSetting({ commit }, data) {
 | 
				
			||||||
 | 
					    commit('CHANGE_SETTING', data)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  namespaced: true,
 | 
				
			||||||
 | 
					  state,
 | 
				
			||||||
 | 
					  mutations,
 | 
				
			||||||
 | 
					  actions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
import { login, logout, getInfo } from '@/api/login'
 | 
					import { login, logout, getInfo } from '@/api/user'
 | 
				
			||||||
import { getToken, setToken, removeToken } from '@/utils/auth'
 | 
					import { getToken, setToken, removeToken } from '@/utils/auth'
 | 
				
			||||||
 | 
					import { resetRouter } from '@/router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const user = {
 | 
					const state = {
 | 
				
			||||||
  state: {
 | 
					 | 
				
			||||||
  token: getToken(),
 | 
					  token: getToken(),
 | 
				
			||||||
  name: '',
 | 
					  name: '',
 | 
				
			||||||
  avatar: '',
 | 
					  avatar: '',
 | 
				
			||||||
  roles: []
 | 
					  roles: []
 | 
				
			||||||
  },
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mutations: {
 | 
					const mutations = {
 | 
				
			||||||
  SET_TOKEN: (state, token) => {
 | 
					  SET_TOKEN: (state, token) => {
 | 
				
			||||||
    state.token = token
 | 
					    state.token = token
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -22,17 +22,17 @@ const user = {
 | 
				
			|||||||
  SET_ROLES: (state, roles) => {
 | 
					  SET_ROLES: (state, roles) => {
 | 
				
			||||||
    state.roles = roles
 | 
					    state.roles = roles
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  },
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  actions: {
 | 
					const actions = {
 | 
				
			||||||
    // 登录
 | 
					  // user login
 | 
				
			||||||
    Login({ commit }, userInfo) {
 | 
					  login({ commit }, userInfo) {
 | 
				
			||||||
      const username = userInfo.username.trim()
 | 
					    const { username, password } = userInfo
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        login(username, userInfo.password).then(response => {
 | 
					      login({ username: username.trim(), password: password }).then(response => {
 | 
				
			||||||
          const data = response.data
 | 
					        const { data } = response
 | 
				
			||||||
          setToken(data.token)
 | 
					 | 
				
			||||||
        commit('SET_TOKEN', data.token)
 | 
					        commit('SET_TOKEN', data.token)
 | 
				
			||||||
 | 
					        setToken(data.token)
 | 
				
			||||||
        resolve()
 | 
					        resolve()
 | 
				
			||||||
      }).catch(error => {
 | 
					      }).catch(error => {
 | 
				
			||||||
        reject(error)
 | 
					        reject(error)
 | 
				
			||||||
@@ -40,32 +40,41 @@ const user = {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 获取用户信息
 | 
					  // get user info
 | 
				
			||||||
    GetInfo({ commit, state }) {
 | 
					  getInfo({ commit, state }) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      getInfo(state.token).then(response => {
 | 
					      getInfo(state.token).then(response => {
 | 
				
			||||||
          const data = response.data
 | 
					        const { data } = response
 | 
				
			||||||
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
 | 
					
 | 
				
			||||||
            commit('SET_ROLES', data.roles)
 | 
					        if (!data) {
 | 
				
			||||||
          } else {
 | 
					          reject('Verification failed, please Login again.')
 | 
				
			||||||
            reject('getInfo: roles must be a non-null array !')
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
          commit('SET_NAME', data.name)
 | 
					
 | 
				
			||||||
          commit('SET_AVATAR', data.avatar)
 | 
					        const { roles, name, avatar } = data
 | 
				
			||||||
          resolve(response)
 | 
					
 | 
				
			||||||
 | 
					        // roles must be a non-empty array
 | 
				
			||||||
 | 
					        if (!roles || roles.length <= 0) {
 | 
				
			||||||
 | 
					          reject('getInfo: roles must be a non-null array!')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        commit('SET_ROLES', roles)
 | 
				
			||||||
 | 
					        commit('SET_NAME', name)
 | 
				
			||||||
 | 
					        commit('SET_AVATAR', avatar)
 | 
				
			||||||
 | 
					        resolve(data)
 | 
				
			||||||
      }).catch(error => {
 | 
					      }).catch(error => {
 | 
				
			||||||
        reject(error)
 | 
					        reject(error)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 登出
 | 
					  // user logout
 | 
				
			||||||
    LogOut({ commit, state }) {
 | 
					  logout({ commit, state }) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      logout(state.token).then(() => {
 | 
					      logout(state.token).then(() => {
 | 
				
			||||||
        commit('SET_TOKEN', '')
 | 
					        commit('SET_TOKEN', '')
 | 
				
			||||||
        commit('SET_ROLES', [])
 | 
					        commit('SET_ROLES', [])
 | 
				
			||||||
        removeToken()
 | 
					        removeToken()
 | 
				
			||||||
 | 
					        resetRouter()
 | 
				
			||||||
        resolve()
 | 
					        resolve()
 | 
				
			||||||
      }).catch(error => {
 | 
					      }).catch(error => {
 | 
				
			||||||
        reject(error)
 | 
					        reject(error)
 | 
				
			||||||
@@ -73,15 +82,21 @@ const user = {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 前端 登出
 | 
					  // remove token
 | 
				
			||||||
    FedLogOut({ commit }) {
 | 
					  resetToken({ commit }) {
 | 
				
			||||||
    return new Promise(resolve => {
 | 
					    return new Promise(resolve => {
 | 
				
			||||||
      commit('SET_TOKEN', '')
 | 
					      commit('SET_TOKEN', '')
 | 
				
			||||||
 | 
					      commit('SET_ROLES', [])
 | 
				
			||||||
      removeToken()
 | 
					      removeToken()
 | 
				
			||||||
      resolve()
 | 
					      resolve()
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default user
 | 
					export default {
 | 
				
			||||||
 | 
					  namespaced: true,
 | 
				
			||||||
 | 
					  state,
 | 
				
			||||||
 | 
					  mutations,
 | 
				
			||||||
 | 
					  actions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,10 @@
 | 
				
			|||||||
//to reset element-ui default css
 | 
					// cover some element-ui styles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.el-breadcrumb__inner,
 | 
				
			||||||
 | 
					.el-breadcrumb__inner a {
 | 
				
			||||||
 | 
					  font-weight: 400 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.el-upload {
 | 
					.el-upload {
 | 
				
			||||||
  input[type="file"] {
 | 
					  input[type="file"] {
 | 
				
			||||||
    display: none !important;
 | 
					    display: none !important;
 | 
				
			||||||
@@ -9,7 +15,8 @@
 | 
				
			|||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
 | 
					
 | 
				
			||||||
 | 
					// to fixed https://github.com/ElemeFE/element/issues/2461
 | 
				
			||||||
.el-dialog {
 | 
					.el-dialog {
 | 
				
			||||||
  transform: none;
 | 
					  transform: none;
 | 
				
			||||||
  left: 0;
 | 
					  left: 0;
 | 
				
			||||||
@@ -17,7 +24,7 @@
 | 
				
			|||||||
  margin: 0 auto;
 | 
					  margin: 0 auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//element ui upload
 | 
					// refine element ui upload
 | 
				
			||||||
.upload-container {
 | 
					.upload-container {
 | 
				
			||||||
  .el-upload {
 | 
					  .el-upload {
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
@@ -28,3 +35,10 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dropdown
 | 
				
			||||||
 | 
					.el-dropdown-menu {
 | 
				
			||||||
 | 
					  a {
 | 
				
			||||||
 | 
					    display: block
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,19 +31,6 @@ html {
 | 
				
			|||||||
  box-sizing: inherit;
 | 
					  box-sizing: inherit;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a,
 | 
					 | 
				
			||||||
a:focus,
 | 
					 | 
				
			||||||
a:hover {
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
  color: inherit;
 | 
					 | 
				
			||||||
  outline: none;
 | 
					 | 
				
			||||||
  text-decoration: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div:focus {
 | 
					 | 
				
			||||||
  outline: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
a:focus,
 | 
					a:focus,
 | 
				
			||||||
a:active {
 | 
					a:active {
 | 
				
			||||||
  outline: none;
 | 
					  outline: none;
 | 
				
			||||||
@@ -57,6 +44,10 @@ a:hover {
 | 
				
			|||||||
  text-decoration: none;
 | 
					  text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div:focus {
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.clearfix {
 | 
					.clearfix {
 | 
				
			||||||
  &:after {
 | 
					  &:after {
 | 
				
			||||||
    visibility: hidden;
 | 
					    visibility: hidden;
 | 
				
			||||||
@@ -68,11 +59,7 @@ a:hover {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//main-container全局样式
 | 
					// main-container global css
 | 
				
			||||||
.app-main {
 | 
					 | 
				
			||||||
  min-height: 100%
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.app-container {
 | 
					.app-container {
 | 
				
			||||||
  padding: 20px;
 | 
					  padding: 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
#app {
 | 
					#app {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 主体区域 Main container
 | 
					 | 
				
			||||||
  .main-container {
 | 
					  .main-container {
 | 
				
			||||||
    min-height: 100%;
 | 
					    min-height: 100%;
 | 
				
			||||||
    transition: margin-left .28s;
 | 
					    transition: margin-left .28s;
 | 
				
			||||||
@@ -8,10 +7,10 @@
 | 
				
			|||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 侧边栏 Sidebar container
 | 
					 | 
				
			||||||
  .sidebar-container {
 | 
					  .sidebar-container {
 | 
				
			||||||
    transition: width 0.28s;
 | 
					    transition: width 0.28s;
 | 
				
			||||||
    width: $sideBarWidth !important;
 | 
					    width: $sideBarWidth !important;
 | 
				
			||||||
 | 
					    background-color: $menuBg;
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    font-size: 0px;
 | 
					    font-size: 0px;
 | 
				
			||||||
@@ -21,23 +20,29 @@
 | 
				
			|||||||
    z-index: 1001;
 | 
					    z-index: 1001;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //reset element-ui css
 | 
					    // reset element-ui css
 | 
				
			||||||
    .horizontal-collapse-transition {
 | 
					    .horizontal-collapse-transition {
 | 
				
			||||||
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
 | 
					      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .scrollbar-wrapper {
 | 
					    .scrollbar-wrapper {
 | 
				
			||||||
      overflow-x: hidden !important;
 | 
					      overflow-x: hidden !important;
 | 
				
			||||||
 | 
					 | 
				
			||||||
      .el-scrollbar__view {
 | 
					 | 
				
			||||||
        height: 100%;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .el-scrollbar__bar.is-vertical {
 | 
					    .el-scrollbar__bar.is-vertical {
 | 
				
			||||||
      right: 0px;
 | 
					      right: 0px;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .el-scrollbar {
 | 
				
			||||||
 | 
					      height: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.has-logo {
 | 
				
			||||||
 | 
					      .el-scrollbar {
 | 
				
			||||||
 | 
					        height: calc(100% - 50px);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .is-horizontal {
 | 
					    .is-horizontal {
 | 
				
			||||||
      display: none;
 | 
					      display: none;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -100,6 +105,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      .el-tooltip {
 | 
					      .el-tooltip {
 | 
				
			||||||
        padding: 0 !important;
 | 
					        padding: 0 !important;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .svg-icon {
 | 
					        .svg-icon {
 | 
				
			||||||
          margin-left: 20px;
 | 
					          margin-left: 20px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -111,6 +117,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      &>.el-submenu__title {
 | 
					      &>.el-submenu__title {
 | 
				
			||||||
        padding: 0 !important;
 | 
					        padding: 0 !important;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .svg-icon {
 | 
					        .svg-icon {
 | 
				
			||||||
          margin-left: 20px;
 | 
					          margin-left: 20px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -140,7 +147,7 @@
 | 
				
			|||||||
    min-width: $sideBarWidth !important;
 | 
					    min-width: $sideBarWidth !important;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 适配移动端, Mobile responsive
 | 
					  // mobile responsive
 | 
				
			||||||
  .mobile {
 | 
					  .mobile {
 | 
				
			||||||
    .main-container {
 | 
					    .main-container {
 | 
				
			||||||
      margin-left: 0px;
 | 
					      margin-left: 0px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
//globl transition css
 | 
					// global transition css
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*fade*/
 | 
					/* fade */
 | 
				
			||||||
.fade-enter-active,
 | 
					.fade-enter-active,
 | 
				
			||||||
.fade-leave-active {
 | 
					.fade-leave-active {
 | 
				
			||||||
  transition: opacity 0.28s;
 | 
					  transition: opacity 0.28s;
 | 
				
			||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
  opacity: 0;
 | 
					  opacity: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*fade-transform*/
 | 
					/* fade-transform */
 | 
				
			||||||
.fade-transform-leave-active,
 | 
					.fade-transform-leave-active,
 | 
				
			||||||
.fade-transform-enter-active {
 | 
					.fade-transform-enter-active {
 | 
				
			||||||
  transition: all .5s;
 | 
					  transition: all .5s;
 | 
				
			||||||
@@ -27,7 +27,7 @@
 | 
				
			|||||||
  transform: translateX(30px);
 | 
					  transform: translateX(30px);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*fade*/
 | 
					/* breadcrumb transition */
 | 
				
			||||||
.breadcrumb-enter-active,
 | 
					.breadcrumb-enter-active,
 | 
				
			||||||
.breadcrumb-leave-active {
 | 
					.breadcrumb-leave-active {
 | 
				
			||||||
  transition: all .5s;
 | 
					  transition: all .5s;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
//sidebar
 | 
					// sidebar
 | 
				
			||||||
$menuText:#bfcbd9;
 | 
					$menuText:#bfcbd9;
 | 
				
			||||||
$menuActiveText:#409EFF;
 | 
					$menuActiveText:#409EFF;
 | 
				
			||||||
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
 | 
					$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/utils/get-page-title.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import defaultSettings from '@/settings'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const title = defaultSettings.title || 'Vue Admin Template'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getPageTitle(pageTitle) {
 | 
				
			||||||
 | 
					  if (pageTitle) {
 | 
				
			||||||
 | 
					    return `${pageTitle} - ${title}`
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return `${title}`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Created by PanJiaChen on 16/11/18.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parse the time to string
 | 
				
			||||||
 | 
					 * @param {(Object|string|number)} time
 | 
				
			||||||
 | 
					 * @param {string} cFormat
 | 
				
			||||||
 | 
					 * @returns {string}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function parseTime(time, cFormat) {
 | 
				
			||||||
 | 
					  if (arguments.length === 0) {
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
 | 
				
			||||||
 | 
					  let date
 | 
				
			||||||
 | 
					  if (typeof time === 'object') {
 | 
				
			||||||
 | 
					    date = time
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
 | 
				
			||||||
 | 
					      time = parseInt(time)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if ((typeof time === 'number') && (time.toString().length === 10)) {
 | 
				
			||||||
 | 
					      time = time * 1000
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    date = new Date(time)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const formatObj = {
 | 
				
			||||||
 | 
					    y: date.getFullYear(),
 | 
				
			||||||
 | 
					    m: date.getMonth() + 1,
 | 
				
			||||||
 | 
					    d: date.getDate(),
 | 
				
			||||||
 | 
					    h: date.getHours(),
 | 
				
			||||||
 | 
					    i: date.getMinutes(),
 | 
				
			||||||
 | 
					    s: date.getSeconds(),
 | 
				
			||||||
 | 
					    a: date.getDay()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
 | 
				
			||||||
 | 
					    let value = formatObj[key]
 | 
				
			||||||
 | 
					    // Note: getDay() returns 0 on Sunday
 | 
				
			||||||
 | 
					    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
 | 
				
			||||||
 | 
					    if (result.length > 0 && value < 10) {
 | 
				
			||||||
 | 
					      value = '0' + value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return value || 0
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  return time_str
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {number} time
 | 
				
			||||||
 | 
					 * @param {string} option
 | 
				
			||||||
 | 
					 * @returns {string}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function formatTime(time, option) {
 | 
				
			||||||
 | 
					  if (('' + time).length === 10) {
 | 
				
			||||||
 | 
					    time = parseInt(time) * 1000
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    time = +time
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const d = new Date(time)
 | 
				
			||||||
 | 
					  const now = Date.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const diff = (now - d) / 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff < 30) {
 | 
				
			||||||
 | 
					    return '刚刚'
 | 
				
			||||||
 | 
					  } else if (diff < 3600) {
 | 
				
			||||||
 | 
					    // less 1 hour
 | 
				
			||||||
 | 
					    return Math.ceil(diff / 60) + '分钟前'
 | 
				
			||||||
 | 
					  } else if (diff < 3600 * 24) {
 | 
				
			||||||
 | 
					    return Math.ceil(diff / 3600) + '小时前'
 | 
				
			||||||
 | 
					  } else if (diff < 3600 * 24 * 2) {
 | 
				
			||||||
 | 
					    return '1天前'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (option) {
 | 
				
			||||||
 | 
					    return parseTime(time, option)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      d.getMonth() +
 | 
				
			||||||
 | 
					      1 +
 | 
				
			||||||
 | 
					      '月' +
 | 
				
			||||||
 | 
					      d.getDate() +
 | 
				
			||||||
 | 
					      '日' +
 | 
				
			||||||
 | 
					      d.getHours() +
 | 
				
			||||||
 | 
					      '时' +
 | 
				
			||||||
 | 
					      d.getMinutes() +
 | 
				
			||||||
 | 
					      '分'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,62 +1,74 @@
 | 
				
			|||||||
import axios from 'axios'
 | 
					import axios from 'axios'
 | 
				
			||||||
import { Message, MessageBox } from 'element-ui'
 | 
					import { MessageBox, Message } from 'element-ui'
 | 
				
			||||||
import store from '../store'
 | 
					import store from '@/store'
 | 
				
			||||||
import { getToken } from '@/utils/auth'
 | 
					import { getToken } from '@/utils/auth'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 创建axios实例
 | 
					// create an axios instance
 | 
				
			||||||
const service = axios.create({
 | 
					const service = axios.create({
 | 
				
			||||||
  baseURL: process.env.BASE_API, // api 的 base_url
 | 
					  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
 | 
				
			||||||
  timeout: 5000 // 请求超时时间
 | 
					  withCredentials: true, // send cookies when cross-domain requests
 | 
				
			||||||
 | 
					  timeout: 5000 // request timeout
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// request拦截器
 | 
					// request interceptor
 | 
				
			||||||
service.interceptors.request.use(
 | 
					service.interceptors.request.use(
 | 
				
			||||||
  config => {
 | 
					  config => {
 | 
				
			||||||
 | 
					    // do something before request is sent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (store.getters.token) {
 | 
					    if (store.getters.token) {
 | 
				
			||||||
      config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
 | 
					      // let each request carry token
 | 
				
			||||||
 | 
					      // ['X-Token'] is a custom headers key
 | 
				
			||||||
 | 
					      // please modify it according to the actual situation
 | 
				
			||||||
 | 
					      config.headers['X-Token'] = getToken()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return config
 | 
					    return config
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  error => {
 | 
					  error => {
 | 
				
			||||||
    // Do something with request error
 | 
					    // do something with request error
 | 
				
			||||||
    console.log(error) // for debug
 | 
					    console.log(error) // for debug
 | 
				
			||||||
    Promise.reject(error)
 | 
					    return Promise.reject(error)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// response 拦截器
 | 
					// response interceptor
 | 
				
			||||||
service.interceptors.response.use(
 | 
					service.interceptors.response.use(
 | 
				
			||||||
  response => {
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
     * code为非20000是抛错 可结合自己业务进行修改
 | 
					   * If you want to get http information such as headers or status
 | 
				
			||||||
 | 
					   * Please return  response => response
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Determine the request status by custom code
 | 
				
			||||||
 | 
					   * Here is just an example
 | 
				
			||||||
 | 
					   * You can also judge the status by HTTP Status Code
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  response => {
 | 
				
			||||||
    const res = response.data
 | 
					    const res = response.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // if the custom code is not 20000, it is judged as an error.
 | 
				
			||||||
    if (res.code !== 20000) {
 | 
					    if (res.code !== 20000) {
 | 
				
			||||||
      Message({
 | 
					      Message({
 | 
				
			||||||
        message: res.message,
 | 
					        message: res.message || 'error',
 | 
				
			||||||
        type: 'error',
 | 
					        type: 'error',
 | 
				
			||||||
        duration: 5 * 1000
 | 
					        duration: 5 * 1000
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
 | 
					      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
 | 
				
			||||||
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
 | 
					      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
 | 
				
			||||||
        MessageBox.confirm(
 | 
					        // to re-login
 | 
				
			||||||
          '你已被登出,可以取消继续留在该页面,或者重新登录',
 | 
					        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
 | 
				
			||||||
          '确定登出',
 | 
					          confirmButtonText: 'Re-Login',
 | 
				
			||||||
          {
 | 
					          cancelButtonText: 'Cancel',
 | 
				
			||||||
            confirmButtonText: '重新登录',
 | 
					 | 
				
			||||||
            cancelButtonText: '取消',
 | 
					 | 
				
			||||||
          type: 'warning'
 | 
					          type: 'warning'
 | 
				
			||||||
          }
 | 
					        }).then(() => {
 | 
				
			||||||
        ).then(() => {
 | 
					          store.dispatch('user/resetToken').then(() => {
 | 
				
			||||||
          store.dispatch('FedLogOut').then(() => {
 | 
					            location.reload()
 | 
				
			||||||
            location.reload() // 为了重新实例化vue-router对象 避免bug
 | 
					 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return Promise.reject('error')
 | 
					      return Promise.reject(res.message || 'error')
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return response.data
 | 
					      return res
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  error => {
 | 
					  error => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,20 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Created by jiachenpan on 16/11/18.
 | 
					 * Created by PanJiaChen on 16/11/18.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isvalidUsername(str) {
 | 
					/**
 | 
				
			||||||
  const valid_map = ['admin', 'editor']
 | 
					 * @param {string} path
 | 
				
			||||||
  return valid_map.indexOf(str.trim()) >= 0
 | 
					 * @returns {Boolean}
 | 
				
			||||||
}
 | 
					 */
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function isExternal(path) {
 | 
					export function isExternal(path) {
 | 
				
			||||||
  return /^(https?:|mailto:|tel:)/.test(path)
 | 
					  return /^(https?:|mailto:|tel:)/.test(path)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {string} str
 | 
				
			||||||
 | 
					 * @returns {Boolean}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function validUsername(str) {
 | 
				
			||||||
 | 
					  const valid_map = ['admin', 'editor']
 | 
				
			||||||
 | 
					  return valid_map.indexOf(str.trim()) >= 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,12 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="bullshit">
 | 
					      <div class="bullshit">
 | 
				
			||||||
        <div class="bullshit__oops">OOPS!</div>
 | 
					        <div class="bullshit__oops">OOPS!</div>
 | 
				
			||||||
        <div class="bullshit__info">版权所有
 | 
					        <div class="bullshit__info">All rights reserved
 | 
				
			||||||
          <a class="link-type" href="https://wallstreetcn.com" target="_blank">华尔街见闻</a>
 | 
					          <a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="bullshit__headline">{{ message }}</div>
 | 
					        <div class="bullshit__headline">{{ message }}</div>
 | 
				
			||||||
        <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
 | 
					        <div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
 | 
				
			||||||
        <a href="" class="bullshit__return-home">返回首页</a>
 | 
					        <a href="" class="bullshit__return-home">Back to home</a>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@@ -26,13 +26,13 @@ export default {
 | 
				
			|||||||
  name: 'Page404',
 | 
					  name: 'Page404',
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
    message() {
 | 
					    message() {
 | 
				
			||||||
      return '网管说这个页面你不能进......'
 | 
					      return 'The webmaster said that you can not enter this page...'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.wscn-http404-container{
 | 
					.wscn-http404-container{
 | 
				
			||||||
  transform: translate(-50%,-50%);
 | 
					  transform: translate(-50%,-50%);
 | 
				
			||||||
  position: absolute;
 | 
					  position: absolute;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="dashboard-container">
 | 
					  <div class="dashboard-container">
 | 
				
			||||||
    <div class="dashboard-text">name:{{ name }}</div>
 | 
					    <div class="dashboard-text">name: {{ name }}</div>
 | 
				
			||||||
    <div class="dashboard-text">roles:<span v-for="role in roles" :key="role">{{ role }}</span></div>
 | 
					    <div class="dashboard-text">roles: <span v-for="role in roles" :key="role">{{ role }}</span></div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,7 +19,7 @@ export default {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.dashboard {
 | 
					.dashboard {
 | 
				
			||||||
  &-container {
 | 
					  &-container {
 | 
				
			||||||
    margin: 30px;
 | 
					    margin: 30px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,42 +2,42 @@
 | 
				
			|||||||
  <div class="app-container">
 | 
					  <div class="app-container">
 | 
				
			||||||
    <el-form ref="form" :model="form" label-width="120px">
 | 
					    <el-form ref="form" :model="form" label-width="120px">
 | 
				
			||||||
      <el-form-item label="Activity name">
 | 
					      <el-form-item label="Activity name">
 | 
				
			||||||
        <el-input v-model="form.name"/>
 | 
					        <el-input v-model="form.name" />
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Activity zone">
 | 
					      <el-form-item label="Activity zone">
 | 
				
			||||||
        <el-select v-model="form.region" placeholder="please select your zone">
 | 
					        <el-select v-model="form.region" placeholder="please select your zone">
 | 
				
			||||||
          <el-option label="Zone one" value="shanghai"/>
 | 
					          <el-option label="Zone one" value="shanghai" />
 | 
				
			||||||
          <el-option label="Zone two" value="beijing"/>
 | 
					          <el-option label="Zone two" value="beijing" />
 | 
				
			||||||
        </el-select>
 | 
					        </el-select>
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Activity time">
 | 
					      <el-form-item label="Activity time">
 | 
				
			||||||
        <el-col :span="11">
 | 
					        <el-col :span="11">
 | 
				
			||||||
          <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;"/>
 | 
					          <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;" />
 | 
				
			||||||
        </el-col>
 | 
					        </el-col>
 | 
				
			||||||
        <el-col :span="2" class="line">-</el-col>
 | 
					        <el-col :span="2" class="line">-</el-col>
 | 
				
			||||||
        <el-col :span="11">
 | 
					        <el-col :span="11">
 | 
				
			||||||
          <el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;"/>
 | 
					          <el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;" />
 | 
				
			||||||
        </el-col>
 | 
					        </el-col>
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Instant delivery">
 | 
					      <el-form-item label="Instant delivery">
 | 
				
			||||||
        <el-switch v-model="form.delivery"/>
 | 
					        <el-switch v-model="form.delivery" />
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Activity type">
 | 
					      <el-form-item label="Activity type">
 | 
				
			||||||
        <el-checkbox-group v-model="form.type">
 | 
					        <el-checkbox-group v-model="form.type">
 | 
				
			||||||
          <el-checkbox label="Online activities" name="type"/>
 | 
					          <el-checkbox label="Online activities" name="type" />
 | 
				
			||||||
          <el-checkbox label="Promotion activities" name="type"/>
 | 
					          <el-checkbox label="Promotion activities" name="type" />
 | 
				
			||||||
          <el-checkbox label="Offline activities" name="type"/>
 | 
					          <el-checkbox label="Offline activities" name="type" />
 | 
				
			||||||
          <el-checkbox label="Simple brand exposure" name="type"/>
 | 
					          <el-checkbox label="Simple brand exposure" name="type" />
 | 
				
			||||||
        </el-checkbox-group>
 | 
					        </el-checkbox-group>
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Resources">
 | 
					      <el-form-item label="Resources">
 | 
				
			||||||
        <el-radio-group v-model="form.resource">
 | 
					        <el-radio-group v-model="form.resource">
 | 
				
			||||||
          <el-radio label="Sponsor"/>
 | 
					          <el-radio label="Sponsor" />
 | 
				
			||||||
          <el-radio label="Venue"/>
 | 
					          <el-radio label="Venue" />
 | 
				
			||||||
        </el-radio-group>
 | 
					        </el-radio-group>
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item label="Activity form">
 | 
					      <el-form-item label="Activity form">
 | 
				
			||||||
        <el-input v-model="form.desc" type="textarea"/>
 | 
					        <el-input v-model="form.desc" type="textarea" />
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item>
 | 
					      <el-form-item>
 | 
				
			||||||
        <el-button type="primary" @click="onSubmit">Create</el-button>
 | 
					        <el-button type="primary" @click="onSubmit">Create</el-button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,95 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="navbar">
 | 
					 | 
				
			||||||
    <hamburger :toggle-click="toggleSideBar" :is-active="sidebar.opened" class="hamburger-container"/>
 | 
					 | 
				
			||||||
    <breadcrumb />
 | 
					 | 
				
			||||||
    <el-dropdown class="avatar-container" trigger="click">
 | 
					 | 
				
			||||||
      <div class="avatar-wrapper">
 | 
					 | 
				
			||||||
        <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
 | 
					 | 
				
			||||||
        <i class="el-icon-caret-bottom"/>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <el-dropdown-menu slot="dropdown" class="user-dropdown">
 | 
					 | 
				
			||||||
        <router-link class="inlineBlock" to="/">
 | 
					 | 
				
			||||||
          <el-dropdown-item>
 | 
					 | 
				
			||||||
            Home
 | 
					 | 
				
			||||||
          </el-dropdown-item>
 | 
					 | 
				
			||||||
        </router-link>
 | 
					 | 
				
			||||||
        <el-dropdown-item divided>
 | 
					 | 
				
			||||||
          <span style="display:block;" @click="logout">LogOut</span>
 | 
					 | 
				
			||||||
        </el-dropdown-item>
 | 
					 | 
				
			||||||
      </el-dropdown-menu>
 | 
					 | 
				
			||||||
    </el-dropdown>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
import { mapGetters } from 'vuex'
 | 
					 | 
				
			||||||
import Breadcrumb from '@/components/Breadcrumb'
 | 
					 | 
				
			||||||
import Hamburger from '@/components/Hamburger'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  components: {
 | 
					 | 
				
			||||||
    Breadcrumb,
 | 
					 | 
				
			||||||
    Hamburger
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  computed: {
 | 
					 | 
				
			||||||
    ...mapGetters([
 | 
					 | 
				
			||||||
      'sidebar',
 | 
					 | 
				
			||||||
      'avatar'
 | 
					 | 
				
			||||||
    ])
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    toggleSideBar() {
 | 
					 | 
				
			||||||
      this.$store.dispatch('ToggleSideBar')
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    logout() {
 | 
					 | 
				
			||||||
      this.$store.dispatch('LogOut').then(() => {
 | 
					 | 
				
			||||||
        location.reload() // 为了重新实例化vue-router对象 避免bug
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					 | 
				
			||||||
.navbar {
 | 
					 | 
				
			||||||
  height: 50px;
 | 
					 | 
				
			||||||
  line-height: 50px;
 | 
					 | 
				
			||||||
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.12), 0 0 3px 0 rgba(0,0,0,.04);
 | 
					 | 
				
			||||||
  .hamburger-container {
 | 
					 | 
				
			||||||
    line-height: 58px;
 | 
					 | 
				
			||||||
    height: 50px;
 | 
					 | 
				
			||||||
    float: left;
 | 
					 | 
				
			||||||
    padding: 0 10px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .screenfull {
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    right: 90px;
 | 
					 | 
				
			||||||
    top: 16px;
 | 
					 | 
				
			||||||
    color: red;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .avatar-container {
 | 
					 | 
				
			||||||
    height: 50px;
 | 
					 | 
				
			||||||
    display: inline-block;
 | 
					 | 
				
			||||||
    position: absolute;
 | 
					 | 
				
			||||||
    right: 35px;
 | 
					 | 
				
			||||||
    .avatar-wrapper {
 | 
					 | 
				
			||||||
      cursor: pointer;
 | 
					 | 
				
			||||||
      margin-top: 5px;
 | 
					 | 
				
			||||||
      position: relative;
 | 
					 | 
				
			||||||
      line-height: initial;
 | 
					 | 
				
			||||||
      .user-avatar {
 | 
					 | 
				
			||||||
        width: 40px;
 | 
					 | 
				
			||||||
        height: 40px;
 | 
					 | 
				
			||||||
        border-radius: 10px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      .el-icon-caret-bottom {
 | 
					 | 
				
			||||||
        position: absolute;
 | 
					 | 
				
			||||||
        right: -20px;
 | 
					 | 
				
			||||||
        top: 25px;
 | 
					 | 
				
			||||||
        font-size: 12px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <el-scrollbar wrap-class="scrollbar-wrapper">
 | 
					 | 
				
			||||||
    <el-menu
 | 
					 | 
				
			||||||
      :default-active="$route.path"
 | 
					 | 
				
			||||||
      :collapse="isCollapse"
 | 
					 | 
				
			||||||
      :background-color="variables.menuBg"
 | 
					 | 
				
			||||||
      :text-color="variables.menuText"
 | 
					 | 
				
			||||||
      :active-text-color="variables.menuActiveText"
 | 
					 | 
				
			||||||
      :collapse-transition="false"
 | 
					 | 
				
			||||||
      mode="vertical"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path"/>
 | 
					 | 
				
			||||||
    </el-menu>
 | 
					 | 
				
			||||||
  </el-scrollbar>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
import { mapGetters } from 'vuex'
 | 
					 | 
				
			||||||
import variables from '@/styles/variables.scss'
 | 
					 | 
				
			||||||
import SidebarItem from './SidebarItem'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  components: { SidebarItem },
 | 
					 | 
				
			||||||
  computed: {
 | 
					 | 
				
			||||||
    ...mapGetters([
 | 
					 | 
				
			||||||
      'sidebar'
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    routes() {
 | 
					 | 
				
			||||||
      return this.$router.options.routes
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    variables() {
 | 
					 | 
				
			||||||
      return variables
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    isCollapse() {
 | 
					 | 
				
			||||||
      return !this.sidebar.opened
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
import store from '@/store'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { body } = document
 | 
					 | 
				
			||||||
const WIDTH = 992 // refer to Bootstrap's responsive design
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
  watch: {
 | 
					 | 
				
			||||||
    $route(route) {
 | 
					 | 
				
			||||||
      if (this.device === 'mobile' && this.sidebar.opened) {
 | 
					 | 
				
			||||||
        store.dispatch('CloseSideBar', { withoutAnimation: false })
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  beforeMount() {
 | 
					 | 
				
			||||||
    window.addEventListener('resize', this.resizeHandler)
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  mounted() {
 | 
					 | 
				
			||||||
    const isMobile = this.isMobile()
 | 
					 | 
				
			||||||
    if (isMobile) {
 | 
					 | 
				
			||||||
      store.dispatch('ToggleDevice', 'mobile')
 | 
					 | 
				
			||||||
      store.dispatch('CloseSideBar', { withoutAnimation: true })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    isMobile() {
 | 
					 | 
				
			||||||
      const rect = body.getBoundingClientRect()
 | 
					 | 
				
			||||||
      return rect.width - 1 < WIDTH
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    resizeHandler() {
 | 
					 | 
				
			||||||
      if (!document.hidden) {
 | 
					 | 
				
			||||||
        const isMobile = this.isMobile()
 | 
					 | 
				
			||||||
        store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (isMobile) {
 | 
					 | 
				
			||||||
          store.dispatch('CloseSideBar', { withoutAnimation: true })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,57 +1,73 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="login-container">
 | 
					  <div class="login-container">
 | 
				
			||||||
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
 | 
					    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
 | 
				
			||||||
      <h3 class="title">vue-admin-template</h3>
 | 
					
 | 
				
			||||||
 | 
					      <div class="title-container">
 | 
				
			||||||
 | 
					        <h3 class="title">Login Form</h3>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <el-form-item prop="username">
 | 
					      <el-form-item prop="username">
 | 
				
			||||||
        <span class="svg-container">
 | 
					        <span class="svg-container">
 | 
				
			||||||
          <svg-icon icon-class="user" />
 | 
					          <svg-icon icon-class="user" />
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
        <el-input v-model="loginForm.username" name="username" type="text" auto-complete="on" placeholder="username" />
 | 
					        <el-input
 | 
				
			||||||
 | 
					          ref="username"
 | 
				
			||||||
 | 
					          v-model="loginForm.username"
 | 
				
			||||||
 | 
					          placeholder="Username"
 | 
				
			||||||
 | 
					          name="username"
 | 
				
			||||||
 | 
					          type="text"
 | 
				
			||||||
 | 
					          tabindex="1"
 | 
				
			||||||
 | 
					          auto-complete="on"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <el-form-item prop="password">
 | 
					      <el-form-item prop="password">
 | 
				
			||||||
        <span class="svg-container">
 | 
					        <span class="svg-container">
 | 
				
			||||||
          <svg-icon icon-class="password" />
 | 
					          <svg-icon icon-class="password" />
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
        <el-input
 | 
					        <el-input
 | 
				
			||||||
          :type="pwdType"
 | 
					          :key="passwordType"
 | 
				
			||||||
 | 
					          ref="password"
 | 
				
			||||||
          v-model="loginForm.password"
 | 
					          v-model="loginForm.password"
 | 
				
			||||||
 | 
					          :type="passwordType"
 | 
				
			||||||
 | 
					          placeholder="Password"
 | 
				
			||||||
          name="password"
 | 
					          name="password"
 | 
				
			||||||
 | 
					          tabindex="2"
 | 
				
			||||||
          auto-complete="on"
 | 
					          auto-complete="on"
 | 
				
			||||||
          placeholder="password"
 | 
					          @keyup.enter.native="handleLogin"
 | 
				
			||||||
          @keyup.enter.native="handleLogin" />
 | 
					        />
 | 
				
			||||||
        <span class="show-pwd" @click="showPwd">
 | 
					        <span class="show-pwd" @click="showPwd">
 | 
				
			||||||
          <svg-icon :icon-class="pwdType === 'password' ? 'eye' : 'eye-open'" />
 | 
					          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </el-form-item>
 | 
					      </el-form-item>
 | 
				
			||||||
      <el-form-item>
 | 
					
 | 
				
			||||||
        <el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
 | 
					      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
 | 
				
			||||||
          Sign in
 | 
					
 | 
				
			||||||
        </el-button>
 | 
					 | 
				
			||||||
      </el-form-item>
 | 
					 | 
				
			||||||
      <div class="tips">
 | 
					      <div class="tips">
 | 
				
			||||||
        <span style="margin-right:20px;">username: admin</span>
 | 
					        <span style="margin-right:20px;">username: admin</span>
 | 
				
			||||||
        <span> password: admin</span>
 | 
					        <span> password: any</span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </el-form>
 | 
					    </el-form>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import { isvalidUsername } from '@/utils/validate'
 | 
					import { validUsername } from '@/utils/validate'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: 'Login',
 | 
					  name: 'Login',
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
    const validateUsername = (rule, value, callback) => {
 | 
					    const validateUsername = (rule, value, callback) => {
 | 
				
			||||||
      if (!isvalidUsername(value)) {
 | 
					      if (!validUsername(value)) {
 | 
				
			||||||
        callback(new Error('请输入正确的用户名'))
 | 
					        callback(new Error('Please enter the correct user name'))
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        callback()
 | 
					        callback()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const validatePass = (rule, value, callback) => {
 | 
					    const validatePassword = (rule, value, callback) => {
 | 
				
			||||||
      if (value.length < 5) {
 | 
					      if (value.length < 6) {
 | 
				
			||||||
        callback(new Error('密码不能小于5位'))
 | 
					        callback(new Error('The password can not be less than 6 digits'))
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        callback()
 | 
					        callback()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -59,14 +75,14 @@ export default {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      loginForm: {
 | 
					      loginForm: {
 | 
				
			||||||
        username: 'admin',
 | 
					        username: 'admin',
 | 
				
			||||||
        password: 'admin'
 | 
					        password: '111111'
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      loginRules: {
 | 
					      loginRules: {
 | 
				
			||||||
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
 | 
					        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
 | 
				
			||||||
        password: [{ required: true, trigger: 'blur', validator: validatePass }]
 | 
					        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      loading: false,
 | 
					      loading: false,
 | 
				
			||||||
      pwdType: 'password',
 | 
					      passwordType: 'password',
 | 
				
			||||||
      redirect: undefined
 | 
					      redirect: undefined
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -80,19 +96,22 @@ export default {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    showPwd() {
 | 
					    showPwd() {
 | 
				
			||||||
      if (this.pwdType === 'password') {
 | 
					      if (this.passwordType === 'password') {
 | 
				
			||||||
        this.pwdType = ''
 | 
					        this.passwordType = ''
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.pwdType = 'password'
 | 
					        this.passwordType = 'password'
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      this.$nextTick(() => {
 | 
				
			||||||
 | 
					        this.$refs.password.focus()
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    handleLogin() {
 | 
					    handleLogin() {
 | 
				
			||||||
      this.$refs.loginForm.validate(valid => {
 | 
					      this.$refs.loginForm.validate(valid => {
 | 
				
			||||||
        if (valid) {
 | 
					        if (valid) {
 | 
				
			||||||
          this.loading = true
 | 
					          this.loading = true
 | 
				
			||||||
          this.$store.dispatch('Login', this.loginForm).then(() => {
 | 
					          this.$store.dispatch('user/login', this.loginForm).then(() => {
 | 
				
			||||||
            this.loading = false
 | 
					 | 
				
			||||||
            this.$router.push({ path: this.redirect || '/' })
 | 
					            this.$router.push({ path: this.redirect || '/' })
 | 
				
			||||||
 | 
					            this.loading = false
 | 
				
			||||||
          }).catch(() => {
 | 
					          }).catch(() => {
 | 
				
			||||||
            this.loading = false
 | 
					            this.loading = false
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
@@ -106,9 +125,19 @@ export default {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss">
 | 
					<style lang="scss">
 | 
				
			||||||
$bg:#2d3a4b;
 | 
					/* 修复input 背景不协调 和光标变色 */
 | 
				
			||||||
$light_gray:#eee;
 | 
					/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$bg:#283443;
 | 
				
			||||||
 | 
					$light_gray:#fff;
 | 
				
			||||||
 | 
					$cursor: #fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
 | 
				
			||||||
 | 
					  .login-container .el-input input {
 | 
				
			||||||
 | 
					    color: $cursor;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* reset element-ui css */
 | 
					/* reset element-ui css */
 | 
				
			||||||
.login-container {
 | 
					.login-container {
 | 
				
			||||||
@@ -116,6 +145,7 @@ $light_gray:#eee;
 | 
				
			|||||||
    display: inline-block;
 | 
					    display: inline-block;
 | 
				
			||||||
    height: 47px;
 | 
					    height: 47px;
 | 
				
			||||||
    width: 85%;
 | 
					    width: 85%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    input {
 | 
					    input {
 | 
				
			||||||
      background: transparent;
 | 
					      background: transparent;
 | 
				
			||||||
      border: 0px;
 | 
					      border: 0px;
 | 
				
			||||||
@@ -124,12 +154,15 @@ $light_gray:#eee;
 | 
				
			|||||||
      padding: 12px 5px 12px 15px;
 | 
					      padding: 12px 5px 12px 15px;
 | 
				
			||||||
      color: $light_gray;
 | 
					      color: $light_gray;
 | 
				
			||||||
      height: 47px;
 | 
					      height: 47px;
 | 
				
			||||||
 | 
					      caret-color: $cursor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      &:-webkit-autofill {
 | 
					      &:-webkit-autofill {
 | 
				
			||||||
        -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
 | 
					        box-shadow: 0 0 0px 1000px $bg inset !important;
 | 
				
			||||||
        -webkit-text-fill-color: #fff !important;
 | 
					        -webkit-text-fill-color: $cursor !important;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .el-form-item {
 | 
					  .el-form-item {
 | 
				
			||||||
    border: 1px solid rgba(255, 255, 255, 0.1);
 | 
					    border: 1px solid rgba(255, 255, 255, 0.1);
 | 
				
			||||||
    background: rgba(0, 0, 0, 0.1);
 | 
					    background: rgba(0, 0, 0, 0.1);
 | 
				
			||||||
@@ -137,37 +170,40 @@ $light_gray:#eee;
 | 
				
			|||||||
    color: #454545;
 | 
					    color: #454545;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style rel="stylesheet/scss" lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
$bg:#2d3a4b;
 | 
					$bg:#2d3a4b;
 | 
				
			||||||
$dark_gray:#889aa4;
 | 
					$dark_gray:#889aa4;
 | 
				
			||||||
$light_gray:#eee;
 | 
					$light_gray:#eee;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.login-container {
 | 
					.login-container {
 | 
				
			||||||
  position: fixed;
 | 
					  min-height: 100%;
 | 
				
			||||||
  height: 100%;
 | 
					 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  background-color: $bg;
 | 
					  background-color: $bg;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .login-form {
 | 
					  .login-form {
 | 
				
			||||||
    position: absolute;
 | 
					    position: relative;
 | 
				
			||||||
    left: 0;
 | 
					 | 
				
			||||||
    right: 0;
 | 
					 | 
				
			||||||
    width: 520px;
 | 
					    width: 520px;
 | 
				
			||||||
    max-width: 100%;
 | 
					    max-width: 100%;
 | 
				
			||||||
    padding: 35px 35px 15px 35px;
 | 
					    padding: 160px 35px 0;
 | 
				
			||||||
    margin: 120px auto;
 | 
					    margin: 0 auto;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .tips {
 | 
					  .tips {
 | 
				
			||||||
    font-size: 14px;
 | 
					    font-size: 14px;
 | 
				
			||||||
    color: #fff;
 | 
					    color: #fff;
 | 
				
			||||||
    margin-bottom: 10px;
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    span {
 | 
					    span {
 | 
				
			||||||
      &:first-of-type {
 | 
					      &:first-of-type {
 | 
				
			||||||
        margin-right: 16px;
 | 
					        margin-right: 16px;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .svg-container {
 | 
					  .svg-container {
 | 
				
			||||||
    padding: 6px 5px 6px 15px;
 | 
					    padding: 6px 5px 6px 15px;
 | 
				
			||||||
    color: $dark_gray;
 | 
					    color: $dark_gray;
 | 
				
			||||||
@@ -175,14 +211,19 @@ $light_gray:#eee;
 | 
				
			|||||||
    width: 30px;
 | 
					    width: 30px;
 | 
				
			||||||
    display: inline-block;
 | 
					    display: inline-block;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .title-container {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .title {
 | 
					    .title {
 | 
				
			||||||
      font-size: 26px;
 | 
					      font-size: 26px;
 | 
				
			||||||
    font-weight: 400;
 | 
					 | 
				
			||||||
      color: $light_gray;
 | 
					      color: $light_gray;
 | 
				
			||||||
      margin: 0px auto 40px auto;
 | 
					      margin: 0px auto 40px auto;
 | 
				
			||||||
      text-align: center;
 | 
					      text-align: center;
 | 
				
			||||||
      font-weight: bold;
 | 
					      font-weight: bold;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .show-pwd {
 | 
					  .show-pwd {
 | 
				
			||||||
    position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
    right: 10px;
 | 
					    right: 10px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
<template >
 | 
					<template>
 | 
				
			||||||
  <div style="padding:30px;">
 | 
					  <div style="padding:30px;">
 | 
				
			||||||
    <el-alert :closable="false" title="menu 1">
 | 
					    <el-alert :closable="false" title="menu 1">
 | 
				
			||||||
      <router-view />
 | 
					      <router-view />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
<template >
 | 
					<template>
 | 
				
			||||||
  <div style="padding:30px;">
 | 
					  <div style="padding:30px;">
 | 
				
			||||||
    <el-alert :closable="false" title="menu 1-1" type="success">
 | 
					    <el-alert :closable="false" title="menu 1-1" type="success">
 | 
				
			||||||
      <router-view />
 | 
					      <router-view />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,8 @@
 | 
				
			|||||||
      element-loading-text="Loading"
 | 
					      element-loading-text="Loading"
 | 
				
			||||||
      border
 | 
					      border
 | 
				
			||||||
      fit
 | 
					      fit
 | 
				
			||||||
      highlight-current-row>
 | 
					      highlight-current-row
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      <el-table-column align="center" label="ID" width="95">
 | 
					      <el-table-column align="center" label="ID" width="95">
 | 
				
			||||||
        <template slot-scope="scope">
 | 
					        <template slot-scope="scope">
 | 
				
			||||||
          {{ scope.$index }}
 | 
					          {{ scope.$index }}
 | 
				
			||||||
@@ -34,7 +35,7 @@
 | 
				
			|||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
 | 
					      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
 | 
				
			||||||
        <template slot-scope="scope">
 | 
					        <template slot-scope="scope">
 | 
				
			||||||
          <i class="el-icon-time"/>
 | 
					          <i class="el-icon-time" />
 | 
				
			||||||
          <span>{{ scope.row.display_time }}</span>
 | 
					          <span>{{ scope.row.display_time }}</span>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
      </el-table-column>
 | 
					      </el-table-column>
 | 
				
			||||||
@@ -68,7 +69,7 @@ export default {
 | 
				
			|||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    fetchData() {
 | 
					    fetchData() {
 | 
				
			||||||
      this.listLoading = true
 | 
					      this.listLoading = true
 | 
				
			||||||
      getList(this.listQuery).then(response => {
 | 
					      getList().then(response => {
 | 
				
			||||||
        this.list = response.data.items
 | 
					        this.list = response.data.items
 | 
				
			||||||
        this.listLoading = false
 | 
					        this.listLoading = false
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								tests/unit/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  env: {
 | 
				
			||||||
 | 
					    jest: true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								tests/unit/components/Breadcrumb.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import { mount, createLocalVue } from '@vue/test-utils'
 | 
				
			||||||
 | 
					import VueRouter from 'vue-router'
 | 
				
			||||||
 | 
					import ElementUI from 'element-ui'
 | 
				
			||||||
 | 
					import Breadcrumb from '@/components/Breadcrumb/index.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const localVue = createLocalVue()
 | 
				
			||||||
 | 
					localVue.use(VueRouter)
 | 
				
			||||||
 | 
					localVue.use(ElementUI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/',
 | 
				
			||||||
 | 
					    name: 'home',
 | 
				
			||||||
 | 
					    children: [{
 | 
				
			||||||
 | 
					      path: 'dashboard',
 | 
				
			||||||
 | 
					      name: 'dashboard'
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/menu',
 | 
				
			||||||
 | 
					    name: 'menu',
 | 
				
			||||||
 | 
					    children: [{
 | 
				
			||||||
 | 
					      path: 'menu1',
 | 
				
			||||||
 | 
					      name: 'menu1',
 | 
				
			||||||
 | 
					      meta: { title: 'menu1' },
 | 
				
			||||||
 | 
					      children: [{
 | 
				
			||||||
 | 
					        path: 'menu1-1',
 | 
				
			||||||
 | 
					        name: 'menu1-1',
 | 
				
			||||||
 | 
					        meta: { title: 'menu1-1' }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        path: 'menu1-2',
 | 
				
			||||||
 | 
					        name: 'menu1-2',
 | 
				
			||||||
 | 
					        redirect: 'noredirect',
 | 
				
			||||||
 | 
					        meta: { title: 'menu1-2' },
 | 
				
			||||||
 | 
					        children: [{
 | 
				
			||||||
 | 
					          path: 'menu1-2-1',
 | 
				
			||||||
 | 
					          name: 'menu1-2-1',
 | 
				
			||||||
 | 
					          meta: { title: 'menu1-2-1' }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          path: 'menu1-2-2',
 | 
				
			||||||
 | 
					          name: 'menu1-2-2'
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					      }]
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					  }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = new VueRouter({
 | 
				
			||||||
 | 
					  routes
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Breadcrumb.vue', () => {
 | 
				
			||||||
 | 
					  const wrapper = mount(Breadcrumb, {
 | 
				
			||||||
 | 
					    localVue,
 | 
				
			||||||
 | 
					    router
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('dashboard', () => {
 | 
				
			||||||
 | 
					    router.push('/dashboard')
 | 
				
			||||||
 | 
					    const len = wrapper.findAll('.el-breadcrumb__inner').length
 | 
				
			||||||
 | 
					    expect(len).toBe(1)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('normal route', () => {
 | 
				
			||||||
 | 
					    router.push('/menu/menu1')
 | 
				
			||||||
 | 
					    const len = wrapper.findAll('.el-breadcrumb__inner').length
 | 
				
			||||||
 | 
					    expect(len).toBe(2)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('nested route', () => {
 | 
				
			||||||
 | 
					    router.push('/menu/menu1/menu1-2/menu1-2-1')
 | 
				
			||||||
 | 
					    const len = wrapper.findAll('.el-breadcrumb__inner').length
 | 
				
			||||||
 | 
					    expect(len).toBe(4)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('no meta.title', () => {
 | 
				
			||||||
 | 
					    router.push('/menu/menu1/menu1-2/menu1-2-2')
 | 
				
			||||||
 | 
					    const len = wrapper.findAll('.el-breadcrumb__inner').length
 | 
				
			||||||
 | 
					    expect(len).toBe(3)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  // it('click link', () => {
 | 
				
			||||||
 | 
					  //   router.push('/menu/menu1/menu1-2/menu1-2-2')
 | 
				
			||||||
 | 
					  //   const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
 | 
				
			||||||
 | 
					  //   const second = breadcrumbArray.at(1)
 | 
				
			||||||
 | 
					  //   console.log(breadcrumbArray)
 | 
				
			||||||
 | 
					  //   const href = second.find('a').attributes().href
 | 
				
			||||||
 | 
					  //   expect(href).toBe('#/menu/menu1')
 | 
				
			||||||
 | 
					  // })
 | 
				
			||||||
 | 
					  // it('noRedirect', () => {
 | 
				
			||||||
 | 
					  //   router.push('/menu/menu1/menu1-2/menu1-2-1')
 | 
				
			||||||
 | 
					  //   const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
 | 
				
			||||||
 | 
					  //   const redirectBreadcrumb = breadcrumbArray.at(2)
 | 
				
			||||||
 | 
					  //   expect(redirectBreadcrumb.contains('a')).toBe(false)
 | 
				
			||||||
 | 
					  // })
 | 
				
			||||||
 | 
					  it('last breadcrumb', () => {
 | 
				
			||||||
 | 
					    router.push('/menu/menu1/menu1-2/menu1-2-1')
 | 
				
			||||||
 | 
					    const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
 | 
				
			||||||
 | 
					    const redirectBreadcrumb = breadcrumbArray.at(3)
 | 
				
			||||||
 | 
					    expect(redirectBreadcrumb.contains('a')).toBe(false)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										18
									
								
								tests/unit/components/Hamburger.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { shallowMount } from '@vue/test-utils'
 | 
				
			||||||
 | 
					import Hamburger from '@/components/Hamburger/index.vue'
 | 
				
			||||||
 | 
					describe('Hamburger.vue', () => {
 | 
				
			||||||
 | 
					  it('toggle click', () => {
 | 
				
			||||||
 | 
					    const wrapper = shallowMount(Hamburger)
 | 
				
			||||||
 | 
					    const mockFn = jest.fn()
 | 
				
			||||||
 | 
					    wrapper.vm.$on('toggleClick', mockFn)
 | 
				
			||||||
 | 
					    wrapper.find('.hamburger').trigger('click')
 | 
				
			||||||
 | 
					    expect(mockFn).toBeCalled()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('prop isActive', () => {
 | 
				
			||||||
 | 
					    const wrapper = shallowMount(Hamburger)
 | 
				
			||||||
 | 
					    wrapper.setProps({ isActive: true })
 | 
				
			||||||
 | 
					    expect(wrapper.contains('.is-active')).toBe(true)
 | 
				
			||||||
 | 
					    wrapper.setProps({ isActive: false })
 | 
				
			||||||
 | 
					    expect(wrapper.contains('.is-active')).toBe(false)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										22
									
								
								tests/unit/components/SvgIcon.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { shallowMount } from '@vue/test-utils'
 | 
				
			||||||
 | 
					import SvgIcon from '@/components/SvgIcon/index.vue'
 | 
				
			||||||
 | 
					describe('SvgIcon.vue', () => {
 | 
				
			||||||
 | 
					  it('iconClass', () => {
 | 
				
			||||||
 | 
					    const wrapper = shallowMount(SvgIcon, {
 | 
				
			||||||
 | 
					      propsData: {
 | 
				
			||||||
 | 
					        iconClass: 'test'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    expect(wrapper.find('use').attributes().href).toBe('#icon-test')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('className', () => {
 | 
				
			||||||
 | 
					    const wrapper = shallowMount(SvgIcon, {
 | 
				
			||||||
 | 
					      propsData: {
 | 
				
			||||||
 | 
					        iconClass: 'test'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    expect(wrapper.classes().length).toBe(1)
 | 
				
			||||||
 | 
					    wrapper.setProps({ className: 'test' })
 | 
				
			||||||
 | 
					    expect(wrapper.classes().includes('test')).toBe(true)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										30
									
								
								tests/unit/utils/formatTime.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import { formatTime } from '@/utils/index.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Utils:formatTime', () => {
 | 
				
			||||||
 | 
					  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
 | 
				
			||||||
 | 
					  const retrofit = 5 * 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('ten digits timestamp', () => {
 | 
				
			||||||
 | 
					    expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('test now', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(+new Date() - 1)).toBe('刚刚')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('less two minute', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('less two hour', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('less one day', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('more than one day', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(d)).toBe('7月13日17时54分')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('format', () => {
 | 
				
			||||||
 | 
					    expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
 | 
				
			||||||
 | 
					    expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
 | 
				
			||||||
 | 
					    expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										28
									
								
								tests/unit/utils/parseTime.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import { parseTime } from '@/utils/index.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Utils:parseTime', () => {
 | 
				
			||||||
 | 
					  const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
 | 
				
			||||||
 | 
					  it('timestamp', () => {
 | 
				
			||||||
 | 
					    expect(parseTime(d)).toBe('2018-07-13 17:54:01')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('ten digits timestamp', () => {
 | 
				
			||||||
 | 
					    expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('new Date', () => {
 | 
				
			||||||
 | 
					    expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('format', () => {
 | 
				
			||||||
 | 
					    expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
 | 
				
			||||||
 | 
					    expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
 | 
				
			||||||
 | 
					    expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('get the day of the week', () => {
 | 
				
			||||||
 | 
					    expect(parseTime(d, '{a}')).toBe('五') // 星期五
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('get the day of the week', () => {
 | 
				
			||||||
 | 
					    expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('empty argument', () => {
 | 
				
			||||||
 | 
					    expect(parseTime()).toBeNull()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										17
									
								
								tests/unit/utils/validate.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { validUsername, isExternal } from '@/utils/validate.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Utils:validate', () => {
 | 
				
			||||||
 | 
					  it('validUsername', () => {
 | 
				
			||||||
 | 
					    expect(validUsername('admin')).toBe(true)
 | 
				
			||||||
 | 
					    expect(validUsername('editor')).toBe(true)
 | 
				
			||||||
 | 
					    expect(validUsername('xxxx')).toBe(false)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  it('isExternal', () => {
 | 
				
			||||||
 | 
					    expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
 | 
				
			||||||
 | 
					    expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
 | 
				
			||||||
 | 
					    expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false)
 | 
				
			||||||
 | 
					    expect(isExternal('/dashboard')).toBe(false)
 | 
				
			||||||
 | 
					    expect(isExternal('./dashboard')).toBe(false)
 | 
				
			||||||
 | 
					    expect(isExternal('dashboard')).toBe(false)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										133
									
								
								vue.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					'use strict'
 | 
				
			||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					const defaultSettings = require('./src/settings.js')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resolve(dir) {
 | 
				
			||||||
 | 
					  return path.join(__dirname, dir)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const name = defaultSettings.title || 'vue Admin Template' // page title
 | 
				
			||||||
 | 
					const port = 9528 // dev port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// All configuration item explanations can be find in https://cli.vuejs.org/config/
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * You will need to set publicPath 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 publicPath should be set to "/bar/".
 | 
				
			||||||
 | 
					   * In most cases please use '/' !!!
 | 
				
			||||||
 | 
					   * Detail: https://cli.vuejs.org/config/#publicpath
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  publicPath: '/',
 | 
				
			||||||
 | 
					  outputDir: 'dist',
 | 
				
			||||||
 | 
					  assetsDir: 'static',
 | 
				
			||||||
 | 
					  lintOnSave: process.env.NODE_ENV === 'development',
 | 
				
			||||||
 | 
					  productionSourceMap: false,
 | 
				
			||||||
 | 
					  devServer: {
 | 
				
			||||||
 | 
					    port: port,
 | 
				
			||||||
 | 
					    open: true,
 | 
				
			||||||
 | 
					    overlay: {
 | 
				
			||||||
 | 
					      warnings: false,
 | 
				
			||||||
 | 
					      errors: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    proxy: {
 | 
				
			||||||
 | 
					      // change xxx-api/login => mock/login
 | 
				
			||||||
 | 
					      // detail: https://cli.vuejs.org/config/#devserver-proxy
 | 
				
			||||||
 | 
					      [process.env.VUE_APP_BASE_API]: {
 | 
				
			||||||
 | 
					        target: `http://localhost:${port}/mock`,
 | 
				
			||||||
 | 
					        changeOrigin: true,
 | 
				
			||||||
 | 
					        pathRewrite: {
 | 
				
			||||||
 | 
					          ['^' + process.env.VUE_APP_BASE_API]: ''
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    after: require('./mock/mock-server.js')
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  configureWebpack: {
 | 
				
			||||||
 | 
					    // provide the app's title in webpack's name field, so that
 | 
				
			||||||
 | 
					    // it can be accessed in index.html to inject the correct title.
 | 
				
			||||||
 | 
					    name: name,
 | 
				
			||||||
 | 
					    resolve: {
 | 
				
			||||||
 | 
					      alias: {
 | 
				
			||||||
 | 
					        '@': resolve('src')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  chainWebpack(config) {
 | 
				
			||||||
 | 
					    config.plugins.delete('preload') // TODO: need test
 | 
				
			||||||
 | 
					    config.plugins.delete('prefetch') // TODO: need test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // set svg-sprite-loader
 | 
				
			||||||
 | 
					    config.module
 | 
				
			||||||
 | 
					      .rule('svg')
 | 
				
			||||||
 | 
					      .exclude.add(resolve('src/icons'))
 | 
				
			||||||
 | 
					      .end()
 | 
				
			||||||
 | 
					    config.module
 | 
				
			||||||
 | 
					      .rule('icons')
 | 
				
			||||||
 | 
					      .test(/\.svg$/)
 | 
				
			||||||
 | 
					      .include.add(resolve('src/icons'))
 | 
				
			||||||
 | 
					      .end()
 | 
				
			||||||
 | 
					      .use('svg-sprite-loader')
 | 
				
			||||||
 | 
					      .loader('svg-sprite-loader')
 | 
				
			||||||
 | 
					      .options({
 | 
				
			||||||
 | 
					        symbolId: 'icon-[name]'
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .end()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // set preserveWhitespace
 | 
				
			||||||
 | 
					    config.module
 | 
				
			||||||
 | 
					      .rule('vue')
 | 
				
			||||||
 | 
					      .use('vue-loader')
 | 
				
			||||||
 | 
					      .loader('vue-loader')
 | 
				
			||||||
 | 
					      .tap(options => {
 | 
				
			||||||
 | 
					        options.compilerOptions.preserveWhitespace = true
 | 
				
			||||||
 | 
					        return options
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .end()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config
 | 
				
			||||||
 | 
					    // https://webpack.js.org/configuration/devtool/#development
 | 
				
			||||||
 | 
					      .when(process.env.NODE_ENV === 'development',
 | 
				
			||||||
 | 
					        config => config.devtool('cheap-source-map')
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config
 | 
				
			||||||
 | 
					      .when(process.env.NODE_ENV !== 'development',
 | 
				
			||||||
 | 
					        config => {
 | 
				
			||||||
 | 
					          config
 | 
				
			||||||
 | 
					            .plugin('ScriptExtHtmlWebpackPlugin')
 | 
				
			||||||
 | 
					            .after('html')
 | 
				
			||||||
 | 
					            .use('script-ext-html-webpack-plugin', [{
 | 
				
			||||||
 | 
					            // `runtime` must same as runtimeChunk name. default is `runtime`
 | 
				
			||||||
 | 
					              inline: /runtime\..*\.js$/
 | 
				
			||||||
 | 
					            }])
 | 
				
			||||||
 | 
					            .end()
 | 
				
			||||||
 | 
					          config
 | 
				
			||||||
 | 
					            .optimization.splitChunks({
 | 
				
			||||||
 | 
					              chunks: 'all',
 | 
				
			||||||
 | 
					              cacheGroups: {
 | 
				
			||||||
 | 
					                libs: {
 | 
				
			||||||
 | 
					                  name: 'chunk-libs',
 | 
				
			||||||
 | 
					                  test: /[\\/]node_modules[\\/]/,
 | 
				
			||||||
 | 
					                  priority: 10,
 | 
				
			||||||
 | 
					                  chunks: 'initial' // only package third parties that are initially dependent
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                elementUI: {
 | 
				
			||||||
 | 
					                  name: 'chunk-elementUI', // split elementUI into a single package
 | 
				
			||||||
 | 
					                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
 | 
				
			||||||
 | 
					                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                commons: {
 | 
				
			||||||
 | 
					                  name: 'chunk-commons',
 | 
				
			||||||
 | 
					                  test: resolve('src/components'), // can customize your rules
 | 
				
			||||||
 | 
					                  minChunks: 3, //  minimum common number
 | 
				
			||||||
 | 
					                  priority: 5,
 | 
				
			||||||
 | 
					                  reuseExistingChunk: true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          config.optimization.runtimeChunk('single')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||