├── .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 | 
4 | 
5 | 
6 | 
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 |
2 |
6 |
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 |
2 |
3 | {{(item.meta.title || item.name)}}
4 |
5 |
6 |
28 |
43 |
--------------------------------------------------------------------------------
/src/components/Base/Cola/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
21 |
26 |
--------------------------------------------------------------------------------
/src/components/Base/ErrorLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{scope.$index + 1}}
14 |
15 |
16 |
17 |
18 | {{scope.row.time | formatTime('{y}-{m}-{d} {h}:{i}')}}
19 |
20 |
21 |
22 |
23 | Msg:{{scope.row.error.message}}
24 |
25 | Info:{{scope.row.info}}
26 |
27 | URL:{{scope.row.url}}
28 |
29 |
30 |
31 |
32 |
33 | {{scope.row.error.stack}}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
55 |
--------------------------------------------------------------------------------
/src/components/Base/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
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 |
2 |
3 |
4 |

5 |
{{user.nickname}}
6 |
7 |
8 | {{version}}
9 | 个人信息
10 | 修改密码
11 | 退出登录
12 |
13 |
14 |
15 |
16 |
17 |
18 |
79 |
98 |
--------------------------------------------------------------------------------
/src/layout/NavBar/UserSelect/infoDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{form.roles}}
9 |
10 |
11 |
15 |
16 |
17 |
57 |
--------------------------------------------------------------------------------
/src/layout/NavBar/UserSelect/passwordDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
67 |
--------------------------------------------------------------------------------
/src/layout/NavBar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
31 |
64 |
--------------------------------------------------------------------------------
/src/layout/PageView/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
29 |
--------------------------------------------------------------------------------
/src/layout/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
67 |
75 |
123 |
--------------------------------------------------------------------------------
/src/layout/Sidebar/sideItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
56 |
65 |
--------------------------------------------------------------------------------
/src/layout/Sidebar/sideLink.vue:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/src/layout/Sidebar/sideValue.vue:
--------------------------------------------------------------------------------
1 |
30 |
41 |
--------------------------------------------------------------------------------
/src/layout/TagsView/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{tag.meta.title? tag.meta.title : tag.name}}
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
126 |
207 |
--------------------------------------------------------------------------------
/src/layout/TagsView/scrollPane.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
2 |
3 | Dashboard
4 |
5 |
6 |
19 |
23 |
--------------------------------------------------------------------------------
/src/pages/index/children/tableExample/dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
50 |
--------------------------------------------------------------------------------
/src/pages/index/children/tableExample/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{scope.$index + 1}}
24 |
25 |
26 |
27 |
28 | {{scope.row.sound.id}}
29 |
30 |
31 |
32 |
33 |
34 |
35 | {{scope.row.sound.song_info.name.type}}: {{ scope.row.sound.song_info.name.name}}
36 | {{scope.row.sound.song_info.author.type}}: {{ scope.row.sound.song_info.author.name}}
37 | {{scope.row.sound.song_info.album_name.type}}: {{ scope.row.sound.song_info.album_name.name}}
38 |
39 |
40 | {{scope.row.sound.name}}
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{scope.row.sound.channel.name}}
48 |
49 |
50 |
51 |
52 | {{scope.row.sound.user.name}}
53 |
54 |
55 |
56 |
57 |
58 | {{scope.row.sound.update_time | formatTime('{y}-{m}-{d} {h}:{i}')}}
59 |
60 |
61 |
62 |
63 | 编辑
64 | 删除
65 |
66 |
67 |
68 |
69 |
70 |
76 |
77 |
78 |
79 |
80 |
81 |
162 |
166 |
--------------------------------------------------------------------------------
/src/pages/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
30 |
59 |
--------------------------------------------------------------------------------
/src/pages/login/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
8 |
9 |
10 | 系统登录
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
67 |
162 |
--------------------------------------------------------------------------------
/src/pages/other/page401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
401
8 |
Mom says you don't have enough age to see these things.
9 |
You have no permission
10 |
11 | Go Back
12 |
13 |
14 |
15 |
16 |
21 |
87 |
--------------------------------------------------------------------------------
/src/pages/other/page404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
The page you are looking for is either stolen by aliens or never existed.
6 |
7 | Go Home
8 |
9 |
10 | Issues me
11 |
12 |
13 |
14 |
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 |
--------------------------------------------------------------------------------