├── .env.development ├── .env.production ├── .env.stage ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── list.js │ └── login.js ├── assets │ ├── css │ │ ├── global.styl │ │ ├── index.styl │ │ └── resetElement.styl │ ├── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── icons │ │ ├── index.js │ │ └── svg │ │ │ ├── 404.svg │ │ │ ├── backtop.svg │ │ │ ├── bug.svg │ │ │ ├── chart.svg │ │ │ ├── clipboard.svg │ │ │ ├── component.svg │ │ │ ├── dashboard.svg │ │ │ ├── documentation.svg │ │ │ ├── editor.svg │ │ │ ├── excel.svg │ │ │ ├── exit-fullscreen.svg │ │ │ ├── flex-left.svg │ │ │ ├── flex-right.svg │ │ │ ├── fullscreen.svg │ │ │ ├── github.svg │ │ │ ├── global.svg │ │ │ ├── guide.svg │ │ │ ├── hot.svg │ │ │ ├── icons.svg │ │ │ ├── issue.svg │ │ │ ├── language.svg │ │ │ ├── link.svg │ │ │ ├── lock.svg │ │ │ ├── markdown.svg │ │ │ ├── message.svg │ │ │ ├── move.svg │ │ │ ├── number.svg │ │ │ ├── pdf.svg │ │ │ ├── progressbar.svg │ │ │ ├── qq.svg │ │ │ ├── spinner.svg │ │ │ ├── thumbtack.svg │ │ │ ├── user.svg │ │ │ ├── users.svg │ │ │ ├── view.svg │ │ │ └── zip.svg │ ├── img │ │ ├── 404.gif │ │ ├── logo.png │ │ └── people.png │ └── index.js ├── components │ ├── Base │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Cola │ │ │ └── index.vue │ │ ├── ErrorLog │ │ │ └── index.vue │ │ └── SvgIcon │ │ │ └── index.vue │ └── index.js ├── directive │ ├── dragDialog │ │ ├── drag.js │ │ └── index.js │ ├── index.js │ └── permission │ │ ├── index.js │ │ └── permission.js ├── filters │ └── index.js ├── layout │ ├── NavBar │ │ ├── UserSelect │ │ │ ├── index.vue │ │ │ ├── infoDialog.vue │ │ │ └── passwordDialog.vue │ │ └── index.vue │ ├── PageView │ │ └── index.vue │ ├── Sidebar │ │ ├── index.vue │ │ ├── sideItem.vue │ │ ├── sideLink.vue │ │ └── sideValue.vue │ └── TagsView │ │ ├── index.vue │ │ └── scrollPane.vue ├── main.js ├── mock │ └── index.js ├── pages │ ├── index │ │ ├── children │ │ │ ├── dashboard │ │ │ │ └── index.vue │ │ │ └── tableExample │ │ │ │ ├── dialog.vue │ │ │ │ └── index.vue │ │ └── index.vue │ ├── login │ │ └── login.vue │ └── other │ │ ├── page401.vue │ │ ├── page404.vue │ │ └── redirect.vue ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── login.js │ │ ├── routes.js │ │ └── tagsView.js └── utils │ ├── cache.js │ ├── errorLog.js │ ├── permission.js │ ├── request.js │ └── validate.js └── vue.config.js /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_ENV_CONFIG = 'dev' 2 | VUE_APP_BASE_API = 'http://www.exapmple.com/dev' -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_ENV_CONFIG = 'prod' 2 | VUE_APP_BASE_API = 'http://www.exapmple.com/prod' -------------------------------------------------------------------------------- /.env.stage: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | VUE_APP_ENV_CONFIG = 'stage' 3 | VUE_APP_BASE_API = 'http://www.exapmple.com/stage' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | package-lock.json 5 | /public/CNAME 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vueBlog-template 2 | 3 | ![image](https://img.shields.io/badge/vue-2.6.8-green.svg) 4 | ![image](https://img.shields.io/badge/vue--router-3.0.2-green.svg) 5 | ![image](https://img.shields.io/badge/vuex-3.1.0-green.svg) 6 | ![image](https://img.shields.io/badge/element--ui-2.10.0-blue.svg) 7 | 8 | 9 | > 这是一个极简的管理后台模板,它只包含了搭建管理后台的一些必要功能 10 | 11 | ##### 注:master分支基于 vue-cli-3.x,vue-cli-2.x请移步到[v1.0分支](https://github.com/uncleLian/vueBlog-template/tree/v1.0) 12 | 13 | - [在线演示](http://template.liansixin.win) 14 | - [使用文档](https://unclelian.github.io/vue-blog-docs/) 15 | 16 | 17 | 18 | ## 功能 19 | - [x] 登录/注销 20 | - [x] 权限验证(页面级) 21 | - [x] 动态侧边栏 22 | - [x] 动态面包屑 23 | - [x] 导航标签 24 | - [x] 401、404、全局错误捕捉 25 | - [x] 多环境(dev、sit、prod) 26 | - [x] svg icon / iconfont 27 | - [x] 进度条 28 | - [x] element-ui 29 | - [x] axios封装(统一处理请求、拦截、报错等) 30 | - [x] cache封装 31 | 32 | 33 | ## 开发和发布 34 | ```bash 35 | # 克隆项目 36 | git clone https://github.com/uncleLian/vueBlog-template.git 37 | 38 | # 安装依赖 39 | npm install 40 | 41 | # 启动服务 42 | npm run dev 43 | 44 | # 发布 45 | npm run build 46 | 47 | # 测试环境 48 | npm run build:stage 49 | 50 | # 报告 51 | npm run build:report 52 | ``` 53 | 54 | ## 交流 55 | 欢迎热爱学习、忠于分享的朋友一起来交流 56 | - Vue交流群:338241465 57 | 58 | ## License 59 | [MIT](http://opensource.org/licenses/MIT) 60 | 61 | Copyright (c) 2018-present,uncleLian 62 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app', 4 | '@vue/babel-preset-jsx' 5 | ], 6 | plugins: [ 7 | [ 8 | 'component', 9 | { 10 | 'libraryName': 'element-ui', 11 | 'styleLibraryName': 'theme-chalk' 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-blog-template", 3 | "version": "2.4.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "build:stage": "vue-cli-service build --mode stage", 9 | "build:report": "vue-cli-service build --report", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "element-ui": "^2.12.0", 15 | "js-cookie": "^2.2.0", 16 | "mockjs": "^1.0.1-beta3", 17 | "normalize.css": "^8.0.1", 18 | "vue": "^2.6.8", 19 | "vue-progressbar": "^0.7.5", 20 | "vue-router": "^3.0.2", 21 | "vuex": "^3.1.0" 22 | }, 23 | "devDependencies": { 24 | "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0-beta.2", 25 | "@vue/babel-preset-jsx": "^1.0.0-beta.2", 26 | "@vue/cli-plugin-babel": "^3.5.0", 27 | "@vue/cli-plugin-eslint": "^3.5.0", 28 | "@vue/cli-service": "^3.5.0", 29 | "@vue/eslint-config-standard": "^4.0.0", 30 | "babel-eslint": "^10.0.1", 31 | "babel-plugin-component": "^1.1.1", 32 | "eslint": "^5.8.0", 33 | "eslint-plugin-vue": "^5.0.0", 34 | "stylus": "^0.54.5", 35 | "stylus-loader": "^3.0.2", 36 | "svg-sprite-loader": "^4.1.3", 37 | "vue-template-compiler": "^2.6.8" 38 | }, 39 | "eslintConfig": { 40 | "root": true, 41 | "env": { 42 | "node": true 43 | }, 44 | "extends": [ 45 | "plugin:vue/essential", 46 | "@vue/standard" 47 | ], 48 | "rules": { 49 | "no-undef": 0, 50 | "indent": 0, 51 | "camelcase": 0, 52 | "space-before-function-paren": 0, 53 | "vue/require-component-is": 0 54 | }, 55 | "parserOptions": { 56 | "parser": "babel-eslint" 57 | } 58 | }, 59 | "postcss": { 60 | "plugins": { 61 | "autoprefixer": {} 62 | } 63 | }, 64 | "browserslist": [ 65 | "> 1%", 66 | "last 2 versions", 67 | "not ie <= 8" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= webpackConfig.name %> 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 27 | 33 | -------------------------------------------------------------------------------- /src/api/list.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request' 2 | 3 | // 列表 4 | export function getList() { 5 | let res = request('/api/list', 'GET') 6 | return res 7 | } 8 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request' 2 | 3 | // 登录 4 | export function getLogin(form) { 5 | let res = request('/api/login', 'POST', form) 6 | return res 7 | } 8 | // 用户信息 9 | export function getUser(token) { 10 | let res = request('/api/user', 'POST', token) 11 | return res 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/css/global.styl: -------------------------------------------------------------------------------- 1 | @import './index'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | html, body { 7 | width: 100%; 8 | height: 100%; 9 | min-width: 1024px; 10 | min-height: 100%; 11 | background: #f4f5f6; 12 | } 13 | body { 14 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; 15 | font-weight: 400; 16 | font-size: 14px; 17 | color: #333; 18 | overflow-x: hidden; 19 | overflow-y: auto; 20 | } 21 | a { 22 | color: $appColor; 23 | text-decoration: none; 24 | outline: none; 25 | } 26 | // hover 27 | .ishover { 28 | transition: background 0.3s; 29 | &:hover { 30 | background: rgba(0, 0, 0, 0.025); 31 | } 32 | } 33 | // 过滤容器 34 | .filter-container { 35 | display: flex; 36 | align-items: center; 37 | margin-bottom: 20px; 38 | .left { 39 | display: flex; 40 | align-items: center; 41 | } 42 | .right { 43 | display: flex; 44 | align-items: center; 45 | margin-left: auto; 46 | } 47 | .filter-item { 48 | margin-right: 5px; 49 | } 50 | } 51 | // 分页容器 52 | .pagination-container { 53 | display: flex; 54 | background: #fff; 55 | padding: 10px 15px; 56 | margin-top: 20px; 57 | } 58 | -------------------------------------------------------------------------------- /src/assets/css/index.styl: -------------------------------------------------------------------------------- 1 | // 主色调 2 | $appColor = #409EFF; 3 | $appColorRGB = 64,158,255; 4 | // 水平垂直居中 5 | flex-center() { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | // 单行溢出省略号 11 | text-ellipsis() { 12 | white-space: nowrap; 13 | text-overflow: ellipsis; 14 | overflow: hidden; 15 | } 16 | :export { 17 | appColor: $appColor; 18 | appColorRGB: $appColorRGB; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/css/resetElement.styl: -------------------------------------------------------------------------------- 1 | .el-dialog { 2 | .el-dialog__body { 3 | padding: 20px; 4 | .el-form-item { 5 | display: inline-block; 6 | width: 50%; 7 | padding: 0 2%; 8 | vertical-align: top; 9 | &.textarea { 10 | width: 100%; 11 | } 12 | .el-form-item__content { 13 | & > .el-input, .el-select, .el-textarea, .el-cascader, .el-input-number { 14 | width: 100%; 15 | } 16 | .el-textarea { 17 | .el-textarea__inner { 18 | padding-bottom: 32px; 19 | } 20 | } 21 | .el-input-number { 22 | .el-input__inner { 23 | text-align: left; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | .el-form--label-top .el-form-item__label { 30 | padding: 0; 31 | } 32 | .el-form-item--small.el-form-item { 33 | margin-bottom: 15px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "element-icons"; 2 | src: url('iconfont.eot?t=1554275721438'); /* IE9 */ 3 | src: url('iconfont.eot?t=1554275721438#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1554275721438') format('woff'), 6 | url('iconfont.ttf?t=1554275721438') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1554275721438#element-icons') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .element-icons { 11 | font-family: "element-icons" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .el-icon-my-thumbtack:before { 19 | content: "\e944"; 20 | } 21 | 22 | .el-icon-my-chart:before { 23 | content: "\e96d"; 24 | } 25 | 26 | .el-icon-my-editor:before { 27 | content: "\e915"; 28 | } 29 | 30 | .el-icon-my-message:before { 31 | content: "\e9ab"; 32 | } 33 | 34 | .el-icon-my-qq:before { 35 | content: "\e982"; 36 | } 37 | 38 | .el-icon-my-users:before { 39 | content: "\e94f"; 40 | } 41 | 42 | .el-icon-my-link:before { 43 | content: "\e91c"; 44 | } 45 | 46 | .el-icon-my-clipboard:before { 47 | content: "\e996"; 48 | } 49 | 50 | .el-icon-my-hot:before { 51 | content: "\e98e"; 52 | } 53 | 54 | .el-icon-my-markdown:before { 55 | content: "\e99c"; 56 | } 57 | 58 | .el-icon-my-user:before { 59 | content: "\e97f"; 60 | } 61 | 62 | .el-icon-my-spinner:before { 63 | content: "\e999"; 64 | } 65 | 66 | .el-icon-my-github:before { 67 | content: "\e928"; 68 | } 69 | 70 | .el-icon-my-i18n:before { 71 | content: "\e963"; 72 | } 73 | 74 | .el-icon-my-move:before { 75 | content: "\e9ff"; 76 | } 77 | 78 | .el-icon-my-progressbar:before { 79 | content: "\e972"; 80 | } 81 | 82 | .el-icon-my-number:before { 83 | content: "\e922"; 84 | } 85 | 86 | .el-icon-my-excel:before { 87 | content: "\e9e8"; 88 | } 89 | 90 | .el-icon-my-view:before { 91 | content: "\e929"; 92 | } 93 | 94 | .el-icon-my-backtop:before { 95 | content: "\e914"; 96 | } 97 | 98 | .el-icon-my-flex-right:before { 99 | content: "\e908"; 100 | } 101 | 102 | .el-icon-my-flex-left:before { 103 | content: "\e907"; 104 | } 105 | 106 | .el-icon-my-component:before { 107 | content: "\e919"; 108 | } 109 | 110 | .el-icon-my-documentation:before { 111 | content: "\e91a"; 112 | } 113 | 114 | .el-icon-my-fullscreen:before { 115 | content: "\e91d"; 116 | } 117 | 118 | .el-icon-my-dashboard:before { 119 | content: "\e91e"; 120 | } 121 | 122 | .el-icon-my-lock:before { 123 | content: "\e924"; 124 | } 125 | 126 | .el-icon-my-icons:before { 127 | content: "\e925"; 128 | } 129 | 130 | .el-icon-my-zip:before { 131 | content: "\e920"; 132 | } 133 | 134 | .el-icon-my-exit-fullscreen:before { 135 | content: "\e956"; 136 | } 137 | 138 | .el-icon-my-language:before { 139 | content: "\e9225"; 140 | } 141 | 142 | .el-icon-my-pdf:before { 143 | content: "\e949"; 144 | } 145 | 146 | .el-icon-my-bug:before { 147 | content: "\e926"; 148 | } 149 | 150 | .el-icon-my-404:before { 151 | content: "\e927"; 152 | } 153 | 154 | .el-icon-my-guide:before { 155 | content: "\e918"; 156 | } 157 | 158 | .el-icon-my-issue:before { 159 | content: "\e94d"; 160 | } 161 | 162 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/Base/SvgIcon' 3 | 4 | Vue.component('svg-icon', SvgIcon) 5 | 6 | const req = require.context('./svg', false, /\.svg$/) 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | requireAll(req) 9 | -------------------------------------------------------------------------------- /src/assets/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/backtop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/editor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/exit-fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/flex-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/flex-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/global.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/hot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/issue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/markdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/move.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/number.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/progressbar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/spinner.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/thumbtack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/users.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/view.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/img/404.gif -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uncleLian/vue-blog-template/eff786041241fd6dea58c9051b007ad992b57313/src/assets/img/people.png -------------------------------------------------------------------------------- /src/assets/index.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css' // 重置样式 2 | import '@/assets/iconfont/iconfont.css' // iconfont 3 | import '@/assets/icons' // svg icon 4 | import '@/assets/css/resetElement.styl' // 重置element样式 5 | import '@/assets/css/global.styl' // 全局样式 6 | -------------------------------------------------------------------------------- /src/components/Base/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | 43 | -------------------------------------------------------------------------------- /src/components/Base/Cola/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 21 | 26 | -------------------------------------------------------------------------------- /src/components/Base/ErrorLog/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 55 | -------------------------------------------------------------------------------- /src/components/Base/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | 28 | 37 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import PageView from '@/layout/PageView' 3 | 4 | const components = { 5 | 'app-pageView': PageView 6 | } 7 | 8 | // 注册全局组件 9 | Object.keys(components).forEach(key => { 10 | Vue.component(key, components[key]) 11 | }) 12 | -------------------------------------------------------------------------------- /src/directive/dragDialog/drag.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind(el, binding, vnode) { 3 | const dialogHeaderEl = el.querySelector('.el-dialog__header') 4 | const dragDom = el.querySelector('.el-dialog') 5 | dialogHeaderEl.style.cssText += ';cursor:move;' 6 | dragDom.style.cssText += ';top:0px;' 7 | 8 | // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); 9 | const getStyle = (function () { 10 | if (window.document.currentStyle) { 11 | return (dom, attr) => dom.currentStyle[attr] 12 | } else { 13 | return (dom, attr) => getComputedStyle(dom, false)[attr] 14 | } 15 | })() 16 | 17 | dialogHeaderEl.onmousedown = (e) => { 18 | // 鼠标按下,计算当前元素距离可视区的距离 19 | const disX = e.clientX - dialogHeaderEl.offsetLeft 20 | const disY = e.clientY - dialogHeaderEl.offsetTop 21 | 22 | const dragDomWidth = dragDom.offsetWidth 23 | const dragDomHeight = dragDom.offsetHeight 24 | 25 | const screenWidth = document.body.clientWidth 26 | const screenHeight = document.body.clientHeight 27 | 28 | const minDragDomLeft = dragDom.offsetLeft 29 | const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth 30 | 31 | const minDragDomTop = dragDom.offsetTop 32 | let maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight 33 | maxDragDomTop = maxDragDomTop >= 0 ? maxDragDomTop : 'unLimit' // // 修复弹框高度大于可视区域的问题 34 | 35 | // 获取到的值带px 正则匹配替换 36 | let styL = getStyle(dragDom, 'left') 37 | let styT = getStyle(dragDom, 'top') 38 | 39 | if (styL.includes('%')) { 40 | styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100) 41 | styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100) 42 | } else { 43 | styL = +styL.replace(/px/g, '') 44 | styT = +styT.replace(/px/g, '') 45 | } 46 | 47 | document.onmousemove = function (e) { 48 | // 通过事件委托,计算移动的距离 49 | let left = e.clientX - disX 50 | let top = e.clientY - disY 51 | 52 | // 边界处理 53 | if (-(left) > minDragDomLeft) { 54 | left = -minDragDomLeft 55 | } else if (left > maxDragDomLeft) { 56 | left = maxDragDomLeft 57 | } 58 | 59 | if (-(top) > minDragDomTop) { 60 | top = -minDragDomTop 61 | } else if (maxDragDomTop !== 'unLimit') { // 修复弹框高度大于可视区域的问题 62 | if (top > maxDragDomTop) { 63 | top = maxDragDomTop 64 | } 65 | } 66 | 67 | // 移动当前元素 68 | dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;` 69 | 70 | // emit onDrag event 71 | vnode.child.$emit('moving') 72 | } 73 | 74 | document.onmouseup = function (e) { 75 | document.onmousemove = null 76 | document.onmouseup = null 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/directive/dragDialog/index.js: -------------------------------------------------------------------------------- 1 | import drag from './drag' 2 | 3 | drag.install = function (Vue) { 4 | // 注册全局指令 5 | Vue.directive('drag', drag) 6 | } 7 | // 供局部注册指令使用 8 | export default drag 9 | -------------------------------------------------------------------------------- /src/directive/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import permission from './permission' 3 | import dragDialog from './dragDialog' 4 | 5 | const directive = [ 6 | permission, 7 | dragDialog 8 | ] 9 | 10 | // 注册全局指令 11 | directive.forEach(item => { 12 | Vue.use(item) 13 | }) 14 | -------------------------------------------------------------------------------- /src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission, { checkPermission } from './permission' 2 | 3 | export default { 4 | install: function (Vue) { 5 | Vue.directive('permission', permission) // 注册全局指令 6 | Vue.prototype.$checkPermission = checkPermission // 挂在到全局,供动态渲染dom调用 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/directive/permission/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | export default { 4 | inserted(el, binding, vnode) { 5 | const permissionRole = binding.value 6 | const hasPermission = checkPermission(permissionRole) 7 | if (!hasPermission) { 8 | el.parentNode && el.parentNode.removeChild(el) 9 | } 10 | } 11 | } 12 | 13 | export function checkPermission(permissionRole) { 14 | // 不能作为通用方案,判断权限的方法需自行实现 15 | // 当前采用登录角色是否包含被验证的permissionRole 16 | let currentRole = (store.state.login.user && store.state.login.user.roles) || 'visitor' 17 | if (typeof currentRole === 'string') { 18 | currentRole = [currentRole] 19 | } 20 | if (permissionRole && Array.isArray(permissionRole)) { 21 | const hasPermission = currentRole.some(role => { 22 | return permissionRole.includes(role) 23 | }) 24 | return hasPermission 25 | } else { 26 | throw new Error(`Error! Please Enter Array Type"`) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const filters = { 4 | // 时间格式化 5 | formatTime: function (time, formatType) { 6 | if (arguments.length === 0) { 7 | return null 8 | } 9 | const format = formatType || '{y}-{m}-{d} {h}:{i}:{s}' 10 | let date 11 | if (typeof time === 'object') { 12 | date = time 13 | } else { 14 | if (('' + time).length === 10) time = parseInt(time) * 1000 15 | date = new Date(time) 16 | } 17 | const formatObj = { 18 | y: date.getFullYear(), 19 | m: date.getMonth() + 1, 20 | d: date.getDate(), 21 | h: date.getHours(), 22 | i: date.getMinutes(), 23 | s: date.getSeconds(), 24 | a: date.getDay() 25 | } 26 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 27 | let value = formatObj[key] 28 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 29 | if (result.length > 0 && value < 10) { 30 | value = '0' + value 31 | } 32 | return value || 0 33 | }) 34 | return time_str 35 | } 36 | } 37 | 38 | // 注册全局过滤 39 | Object.keys(filters).forEach(key => { 40 | Vue.filter(key, filters[key]) 41 | }) 42 | // 挂在到全局 43 | Vue.prototype.$filter = filters 44 | -------------------------------------------------------------------------------- /src/layout/NavBar/UserSelect/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 79 | 98 | -------------------------------------------------------------------------------- /src/layout/NavBar/UserSelect/infoDialog.vue: -------------------------------------------------------------------------------- 1 | 17 | 57 | -------------------------------------------------------------------------------- /src/layout/NavBar/UserSelect/passwordDialog.vue: -------------------------------------------------------------------------------- 1 | 20 | 67 | -------------------------------------------------------------------------------- /src/layout/NavBar/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 31 | 64 | -------------------------------------------------------------------------------- /src/layout/PageView/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | -------------------------------------------------------------------------------- /src/layout/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 67 | 75 | 123 | -------------------------------------------------------------------------------- /src/layout/Sidebar/sideItem.vue: -------------------------------------------------------------------------------- 1 | 29 | 56 | 65 | -------------------------------------------------------------------------------- /src/layout/Sidebar/sideLink.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/layout/Sidebar/sideValue.vue: -------------------------------------------------------------------------------- 1 | 30 | 41 | -------------------------------------------------------------------------------- /src/layout/TagsView/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 126 | 207 | -------------------------------------------------------------------------------- /src/layout/TagsView/scrollPane.vue: -------------------------------------------------------------------------------- 1 | 6 | 57 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from '@/App' 3 | import router from '@/router' 4 | import store from '@/store' 5 | 6 | // 第三方 7 | import ElementUI from 'element-ui' 8 | import 'element-ui/lib/theme-chalk/index.css' // element-ui 默认颜色 9 | import VueProgressBar from 'vue-progressbar' 10 | 11 | // 自定义 12 | import '@/assets' // 字体、样式等资源 13 | import '@/components' // 全局组件 14 | import '@/directive' // 全局指令 15 | import '@/filters' // 全局过滤 16 | import '@/utils/permission' // 权限验证 17 | import '@/utils/errorLog' // 错误捕捉 18 | import '@/utils/cache' // 缓存 19 | 20 | // mock数据 21 | import '@/mock' 22 | 23 | Vue.config.productionTip = false 24 | 25 | // 第三方 26 | Vue.use(ElementUI, { size: 'small' }) 27 | Vue.use(VueProgressBar) 28 | 29 | new Vue({ 30 | router, 31 | store, 32 | render: h => h(App) 33 | }).$mount('#app') 34 | -------------------------------------------------------------------------------- /src/pages/index/children/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | 23 | -------------------------------------------------------------------------------- /src/pages/index/children/tableExample/dialog.vue: -------------------------------------------------------------------------------- 1 | 14 | 50 | -------------------------------------------------------------------------------- /src/pages/index/children/tableExample/index.vue: -------------------------------------------------------------------------------- 1 | 81 | 162 | 166 | -------------------------------------------------------------------------------- /src/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 30 | 59 | -------------------------------------------------------------------------------- /src/pages/login/login.vue: -------------------------------------------------------------------------------- 1 | 26 | 67 | 162 | -------------------------------------------------------------------------------- /src/pages/other/page401.vue: -------------------------------------------------------------------------------- 1 | 16 | 21 | 87 | -------------------------------------------------------------------------------- /src/pages/other/page404.vue: -------------------------------------------------------------------------------- 1 | 15 | 20 | 53 | -------------------------------------------------------------------------------- /src/pages/other/redirect.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | // 视图组件 5 | // const PageView = () => import('@/layout/PageView') 6 | 7 | Vue.use(Router) 8 | 9 | /* Routes Config 10 | * @meta 11 | * icon: '' 菜单图标(支持svg-icon、el-icon) 12 | * title: '' 菜单标题 13 | * login: false 是否需要登录 14 | * roles: 'admin' || ['admin'] 是否需要权限 15 | * keep: false 是否需要缓存(需要name才能生效) 16 | * hidden: false 是否显示在菜单 17 | * open: false 是否展开菜单(有子菜单前提下) 18 | * redirectIndex: 0 重定向到第index位子菜单(有子菜单前提下) 19 | * affix: false 是否常驻在tagView组件上(外链无效) 20 | */ 21 | 22 | // 异步路由 23 | export const asyncRoutes = [ 24 | { 25 | name: 'dashboard', 26 | path: 'dashboard', 27 | component: () => import('@/pages/index/children/dashboard'), 28 | meta: { 29 | icon: 'dashboard', 30 | title: '主页', 31 | affix: true 32 | } 33 | }, 34 | { 35 | name: 'tableExample', 36 | path: 'tableExample', 37 | component: () => import('@/pages/index/children/tableExample'), 38 | meta: { 39 | icon: 'el-icon-s-grid', 40 | title: '表格示例' 41 | } 42 | } 43 | ] 44 | 45 | // 本地路由 46 | export const localRoutes = [ 47 | { 48 | path: '', 49 | redirect: '/login' 50 | }, 51 | { 52 | path: '/login', 53 | component: () => import('@/pages/login/login') 54 | }, 55 | { 56 | path: '/page401', 57 | component: () => import('@/pages/other/page401') 58 | }, 59 | { 60 | path: '/page404', 61 | component: () => import('@/pages/other/page404') 62 | } 63 | ] 64 | 65 | const createRouter = () => new Router({ 66 | // mode: 'history', 67 | routes: localRoutes, 68 | scrollBehavior: () => ({ y: 0 }) 69 | }) 70 | 71 | const router = createRouter() 72 | 73 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 74 | export function resetRouter() { 75 | const newRouter = createRouter() 76 | router.matcher = newRouter.matcher // reset router 77 | } 78 | 79 | export default router 80 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import cache from '@/utils/cache' 4 | 5 | Vue.use(Vuex) 6 | 7 | const state = { 8 | logs: [], 9 | sidebarStatus: cache.getCookie('sidebarStatus') !== 'false' 10 | } 11 | const getters = { 12 | } 13 | const mutations = { 14 | SET_LOGS(state, error) { 15 | state.logs.unshift(error) 16 | }, 17 | SET_SIDEBAR_STATUS(state) { 18 | let status = !state.sidebarStatus 19 | state.sidebarStatus = status 20 | cache.setCookie('sidebarStatus', status) 21 | } 22 | } 23 | const actions = { 24 | } 25 | 26 | // 自动引入和注册modules下的文件 27 | const modulesFiles = require.context('./modules', false, /\.js$/) 28 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 29 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 30 | const value = modulesFiles(modulePath) 31 | modules[moduleName] = value.default 32 | return modules 33 | }, {}) 34 | 35 | export default new Vuex.Store({ 36 | state, 37 | getters, 38 | mutations, 39 | actions, 40 | modules 41 | }) 42 | -------------------------------------------------------------------------------- /src/store/modules/login.js: -------------------------------------------------------------------------------- 1 | import { getLogin, getUser } from '@/api/login' 2 | import router, { resetRouter } from '@/router' 3 | import cache from '@/utils/cache' 4 | 5 | export default { 6 | namespaced: true, 7 | state: { 8 | user: '' 9 | }, 10 | mutations: { 11 | SET_USER(state, val) { 12 | state.user = val 13 | } 14 | }, 15 | actions: { 16 | // 获取登录数据 17 | async getLoginToken({ commit }, params) { 18 | return new Promise((resolve, reject) => { 19 | getLogin(params).then(res => { 20 | // console.log('login', res) 21 | if (res && res.token) { 22 | cache.setToken(res.token) 23 | resolve(res) 24 | } else { 25 | reject(new Error('nothing login data')) 26 | } 27 | }).catch(err => { 28 | reject(err) 29 | }) 30 | }) 31 | }, 32 | // 获取用户数据 33 | async getUserData({ commit }) { 34 | return new Promise((resolve, reject) => { 35 | let token = cache.getToken() 36 | getUser(token).then(res => { 37 | // console.log('user', res) 38 | if (res.data) { 39 | commit('SET_USER', res.data) 40 | resolve(res.data) 41 | } else { 42 | reject(new Error('nothing user data')) 43 | } 44 | }).catch(err => { 45 | reject(err) 46 | }) 47 | }) 48 | }, 49 | logout({ commit }) { 50 | return new Promise((resolve, reject) => { 51 | // 删除本地token 52 | cache.removeToken() 53 | // 重置路由 54 | resetRouter() 55 | // 删除用户信息 56 | commit('SET_USER', '') 57 | // 跳转到登录页 58 | router.push('/login') 59 | resolve() 60 | }) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/store/modules/routes.js: -------------------------------------------------------------------------------- 1 | import { localRoutes, asyncRoutes } from '@/router' 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | allRoutes: [], // 全部路由 7 | sideRoutes: [] // 侧边栏路由 8 | }, 9 | mutations: { 10 | SET_ALL_ROUTES: (state, routes) => { 11 | state.allRoutes = [...routes] 12 | }, 13 | SET_SIDE_ROUTES: (state, routes) => { 14 | state.sideRoutes = [...routes] 15 | } 16 | }, 17 | actions: { 18 | generateRoutes({ commit }) { 19 | return new Promise(resolve => { 20 | // 这里可以根据项目需求自行对asyncRoutes进行过滤得到finalAsyncRoutes 21 | const finalAsyncRoutes = [...asyncRoutes] 22 | let finalAsyncParentRoutes = { ...asyncParentRoutes } 23 | finalAsyncParentRoutes.children = [...finalAsyncRoutes, extraPanentRoutes] 24 | const sideRoutes = setRedirect([finalAsyncParentRoutes]) 25 | const addRoutes = [...sideRoutes, extraGlobalRoutes] // 实际动态添加的路由 26 | const allRoutes = [...localRoutes, ...addRoutes] // 所有路由 27 | commit('SET_SIDE_ROUTES', sideRoutes) 28 | commit('SET_ALL_ROUTES', allRoutes) 29 | resolve(addRoutes) 30 | }) 31 | } 32 | } 33 | } 34 | 35 | const asyncParentRoutes = { 36 | name: 'index', 37 | path: '/index', 38 | component: () => import('@/pages/index/index'), 39 | meta: { 40 | login: true, 41 | title: '首页' 42 | }, 43 | children: [] 44 | } 45 | const extraPanentRoutes = { 46 | path: 'redirect/:path*', 47 | component: () => import('@/pages/other/redirect'), 48 | meta: { 49 | hidden: true 50 | } 51 | } 52 | const extraGlobalRoutes = { 53 | path: '*', 54 | redirect: '/page404' 55 | } 56 | 57 | // 自动设置路由的重定向(有子路由前提下) 58 | function setRedirect(routes, redirect = '') { 59 | routes.forEach(route => { 60 | if (route.children && route.children.length > 0) { 61 | if (!route.redirect) { 62 | let defaultRedirectRoute = route.children.filter(item => !item.meta || !item.meta.hidden)[0] 63 | let redirectIndex = route.meta && route.meta.redirectIndex 64 | if (redirectIndex) { 65 | defaultRedirectRoute = route.children[redirectIndex] 66 | } 67 | let redirectName = defaultRedirectRoute.name 68 | route.redirect = `${redirect}/${route.name}/${redirectName}` 69 | } 70 | let fatherDir = route.redirect && `${redirect}/${route.name}` 71 | route.children = setRedirect(route.children, fatherDir) 72 | } 73 | }) 74 | return routes 75 | } 76 | -------------------------------------------------------------------------------- /src/store/modules/tagsView.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { isExternal } from '@/utils/validate' 3 | 4 | export default { 5 | namespaced: true, 6 | state: { 7 | tagsView: [] 8 | }, 9 | mutations: { 10 | SET_TAGS_VIEW(state, views) { 11 | state.tagsView = [...views] 12 | }, 13 | ADD_TAGS_VIEW(state, view) { 14 | if (view.name && view.meta && !view.meta.hidden) { 15 | const isHas = state.tagsView.some((v, index) => { 16 | if (v.path === view.path) { 17 | // 存在时进行替换(场景:参数变化) 18 | state.tagsView.splice(index, 1, { ...view }) 19 | return true 20 | } 21 | }) 22 | if (isHas) return 23 | state.tagsView.push({ ...view }) 24 | } 25 | }, 26 | CLOSE_TAGS_VIEW(state, view) { 27 | let index = state.tagsView.findIndex(v => v.path === view.path) 28 | state.tagsView.splice(index, 1) 29 | }, 30 | CLOSE_OTHER_TAGS_VIEW(state, view) { 31 | state.tagsView = state.tagsView.filter(v => v.meta.affix || v.path === view.path) 32 | }, 33 | CLEAR_TAGS_VIEW(state) { 34 | state.tagsView = state.tagsView.filter(v => v.meta.affix) 35 | } 36 | }, 37 | actions: { 38 | initTagsView({ commit, rootState }) { 39 | let affixTags = filterAffixTags(rootState.routes.sideRoutes[0].children) 40 | commit('SET_TAGS_VIEW', affixTags) 41 | function filterAffixTags(routes, basePath = '/index') { 42 | let tags = [] 43 | routes.forEach(route => { 44 | // 过滤外链 45 | if (!isExternal(route.path)) { 46 | let tagPath = path.resolve(basePath, route.path) 47 | if (route.meta && route.meta.affix) { 48 | tags.push({ 49 | fullPath: tagPath, 50 | path: tagPath, 51 | name: route.name, 52 | meta: { ...route.meta } 53 | }) 54 | } 55 | if (route.children) { 56 | const tempTags = filterAffixTags(route.children, tagPath) 57 | if (tempTags.length >= 1) { 58 | tags = [...tags, ...tempTags] 59 | } 60 | } 61 | } 62 | }) 63 | return tags 64 | } 65 | }, 66 | closeTagsView({ state, commit }, view) { 67 | return new Promise(resolve => { 68 | commit('CLOSE_TAGS_VIEW', view) 69 | resolve([...state.tagsView]) 70 | }) 71 | }, 72 | clearTagsView({ state, commit }, view) { 73 | return new Promise(resolve => { 74 | commit('CLEAR_TAGS_VIEW', view) 75 | resolve([...state.tagsView]) 76 | }) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/cache.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Cookies from 'js-cookie' 3 | 4 | const TokenKey = 'vue-bolg-template-token' // Token键值 5 | const cookieTime = 7 // cookie过期时间 6 | 7 | const cache = { 8 | // token 9 | getToken: function () { 10 | return Cookies.get(TokenKey) 11 | }, 12 | setToken: function (token) { 13 | return Cookies.set(TokenKey, token, { expires: cookieTime }) 14 | }, 15 | removeToken: function () { 16 | return Cookies.remove(TokenKey) 17 | }, 18 | // cookie 19 | getCookie: function (name) { 20 | return Cookies.get(name) 21 | }, 22 | setCookie: function (name, val) { 23 | return Cookies.set(name, val, { expires: cookieTime }) 24 | }, 25 | removeCookie: function (name) { 26 | return Cookies.remove(name) 27 | }, 28 | // seesion 29 | getSession: function (name) { 30 | if (!name) return 31 | return window.sessionStorage.getItem(name) 32 | }, 33 | setSession: function (name, content) { 34 | if (!name) return 35 | if (typeof content !== 'string') { 36 | content = JSON.stringify(content) 37 | } 38 | window.sessionStorage.setItem(name, content) 39 | }, 40 | removeSession: function (name) { 41 | if (!name) return 42 | window.sessionStorage.removeItem(name) 43 | }, 44 | getLocal: function (name) { 45 | if (!name) return 46 | return window.localStorage.getItem(name) 47 | }, 48 | // local 49 | setLocal: function (name, content) { 50 | if (!name) return 51 | if (typeof content !== 'string') { 52 | content = JSON.stringify(content) 53 | } 54 | window.localStorage.setItem(name, content) 55 | }, 56 | removeLocal: function (name) { 57 | if (!name) return 58 | window.localStorage.removeItem(name) 59 | } 60 | } 61 | 62 | Vue.prototype.$cache = cache 63 | 64 | export default cache 65 | -------------------------------------------------------------------------------- /src/utils/errorLog.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | 4 | // you can set only in production env show the error-log 5 | if (process.env.NODE_ENV === 'production') { 6 | Vue.config.errorHandler = function (error, vm, info) { 7 | store.commit('SET_LOGS', { 8 | error, 9 | vm, 10 | info, 11 | url: window.location.href, 12 | time: new Date() 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import router from '@/router' 2 | import store from '@/store' 3 | import cache from '@/utils/cache' 4 | 5 | // 登录验证,权限验证 6 | router.beforeEach((to, from, next) => { 7 | // 是否需要登录 8 | if (cache.getToken()) { 9 | if (to.path === '/login') { 10 | next('/index') 11 | } else { 12 | // 是否已有用户信息 13 | let userInfo = store.state.login.user 14 | if (userInfo) { 15 | assessPermission(userInfo.roles, to.meta.roles, next) 16 | } else { 17 | store.dispatch('login/getUserData').then(res => { 18 | store.dispatch('routes/generateRoutes').then(addRoutes => { 19 | router.addRoutes(addRoutes) 20 | next({ ...to, replace: true }) 21 | }) 22 | }).catch(err => { 23 | console.log(err) 24 | // 可根据错误信息,做相应需求,这里默认token值失效 25 | window.alert('登录已失效,请重新登录') 26 | store.dispatch('login/logout').then(() => { 27 | next({ path: '/login', query: { redirect: to.fullPath } }) 28 | }) 29 | }) 30 | } 31 | } 32 | } else { 33 | if (to.path === '/login') { 34 | next() 35 | } else { 36 | next({ path: '/login', query: { redirect: to.fullPath } }) 37 | } 38 | } 39 | }) 40 | 41 | // 验证权限(页面级) 42 | function assessPermission(userRole, pageRole, next) { 43 | let pass = false 44 | // 页面无需权限 || 用户是管理员 45 | if (!pageRole || userRole.indexOf('admin') > -1) pass = true 46 | // 符合页面的其中一种权限(支持String和Array写法) 47 | if (typeof pageRole === 'string') { 48 | if (userRole.indexOf(pageRole) > -1) pass = true 49 | } else if (Array.isArray(pageRole)) { 50 | if (pageRole.some(role => userRole.indexOf(role) > -1)) pass = true 51 | } 52 | pass ? next() : next('/page401') 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message } from 'element-ui' 3 | import cache from '@/utils/cache' 4 | 5 | export const instance = axios.create({ 6 | baseURL: process.env.VUE_APP_BASE_API, 7 | timeout: 20 * 1000 8 | }) 9 | 10 | // request 11 | instance.interceptors.request.use(config => { 12 | if (cache.getToken()) { 13 | config.headers['Authorization'] = `Bearer ${cache.getToken()}` 14 | } 15 | return config 16 | }, error => { 17 | Promise.reject(error) 18 | }) 19 | 20 | // response 21 | instance.interceptors.response.use(response => { 22 | const res = response.data 23 | // 自定义报错规则 24 | if (res && res.errorMessage) { 25 | return Promise.reject(res.errorMessage) 26 | } 27 | return Promise.resolve(res) 28 | }, error => { 29 | // 请求直接报错 30 | const status = (error.response && error.response.status) || '' 31 | if (status === 401) { 32 | store.dispatch('login/logout') 33 | Message.error('登录已失效,请重新登录') 34 | } else { 35 | let message = error.message || error 36 | Message.error(message) 37 | } 38 | return Promise.reject(error) 39 | }) 40 | 41 | /* 42 | * request方法(统一axios请求方法的格式) 43 | * url 请求URL 44 | * type 请求类型 45 | * data 参数 46 | * isForm 是否表单数据 47 | */ 48 | export const request = async (url = '', type = 'GET', data = {}, isForm = false) => { 49 | let result 50 | type = type.toUpperCase() 51 | let requestOptions = { 52 | method: type, 53 | url: url 54 | } 55 | if (isForm) { 56 | let form = new FormData() 57 | Object.keys(data).forEach(key => { 58 | let value = data[key] 59 | if (Array.isArray(value)) { 60 | value.forEach(item => { 61 | form.append(key, item) 62 | }) 63 | } else { 64 | form.append(key, data[key]) 65 | } 66 | }) 67 | data = form 68 | } 69 | requestOptions['headers'] = { 70 | 'Content-type': isForm ? 'multipart/form-data' : 'application/json' 71 | } 72 | if (type === 'GET') { 73 | requestOptions['params'] = data 74 | } else { 75 | requestOptions['data'] = data 76 | } 77 | await instance(requestOptions).then(res => { 78 | result = res 79 | }) 80 | return result 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {String} path 3 | * @returns {Boolean} 4 | */ 5 | export function isExternal(path) { 6 | return /^(https?:|mailto:|tel:)/.test(path) 7 | } 8 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | function resolve(dir) { 3 | return path.join(__dirname, dir) 4 | } 5 | const name = require('./package.json').name 6 | 7 | module.exports = { 8 | publicPath: '/', 9 | outputDir: 'dist', 10 | lintOnSave: true, 11 | productionSourceMap: false, 12 | css: { 13 | sourceMap: false, 14 | modules: false, 15 | loaderOptions: { 16 | stylus: { 17 | import: [resolve('./src/assets/css/index.styl')] 18 | } 19 | } 20 | }, 21 | devServer: { 22 | port: 8003, 23 | open: true, 24 | // proxy: { 25 | // '/api': { 26 | // target: '', 27 | // changeOrigin: true, 28 | // pathRewrite: { 29 | // '^/api': '' 30 | // } 31 | // } 32 | // } 33 | }, 34 | configureWebpack: { 35 | name: name, 36 | resolve: { 37 | alias: { 38 | '@': resolve('src') 39 | } 40 | } 41 | }, 42 | chainWebpack: config => { 43 | config.module 44 | .rule('svg') 45 | .exclude.add(resolve('src/assets/icons')) 46 | .end() 47 | config.module 48 | .rule('icons') 49 | .test(/\.svg$/) 50 | .include.add(resolve('src/assets/icons')) 51 | .end() 52 | .use('svg-sprite-loader') 53 | .loader('svg-sprite-loader') 54 | .options({ 55 | symbolId: 'icon-[name]' 56 | }) 57 | .end() 58 | config.when(process.env.NODE_ENV !== 'development', 59 | config => { 60 | config.optimization.splitChunks({ 61 | chunks: 'all', 62 | cacheGroups: { 63 | libs: { 64 | name: 'chunk-libs', 65 | test: /[\\/]node_modules[\\/]/, 66 | priority: 10, 67 | chunks: 'initial' // 只打包初始时依赖的第三方 68 | }, 69 | elementUI: { 70 | name: 'chunk-elementUI', // 单独将 elementUI 拆包 71 | priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app 72 | test: /[\\/]node_modules[\\/]element-ui[\\/]/ 73 | }, 74 | commons: { 75 | name: 'chunk-commons', 76 | test: resolve('src/components'), // 可自定义拓展你的规则 77 | minChunks: 3, // 最小公用次数 78 | priority: 5, 79 | reuseExistingChunk: true 80 | } 81 | } 82 | }) 83 | config.optimization.runtimeChunk('single') 84 | } 85 | ) 86 | } 87 | } 88 | --------------------------------------------------------------------------------