前言
基于Vue2.x + Vue-cli4.x的后台管理系统,主要包含路由自动挂载、主题换肤、多语言国际化、表格操作、图表展示、全屏操作、字体图标、权限控制、路由嵌套和数据mock等功能。部分内容学习自vue-element-admin等优秀开源作品,个人做了部分取舍和优化。以下内容为后面总结的,可能存在部分错误和不足,欢迎指出!
- 演示地址:juetan.github.io/management
- 演示账号:管理账号:admin-admin;普通账号:juetan-juetan
技术栈
基于NodeJS、VS Code和vue-cli开发,技术栈主要以vue、axios、vuex、
vue-router和element-ui为主。此外,还包含以下依赖或包:
- 主题修改基于webpack-theme-color-replacer
- css预处理基于scss
- 国际化基于vue-i18n
- 数据mock基于mockjs
- 图表展示基于echarts
- markdown基于mavon-editor
- 全屏展示基于screenfull
- 计数动效基于countTo
- 进度加载基于nprogress
- 版本控制基于Git
- 静态部署基于Github Pages和Github Action
主要功能
主题换肤
Vue-element-admin里提供了两种修改主题的思路,但是只支持element-ui的颜色,不支持vue组件内的变量颜色。恰好遇到一个webpack插件-webpack-theme-color-replacer,可以完美支持这些功能且体积更小,原理主要是在打包时将涉及到的颜色样式单独提取出来,更换主题时进行正则替换。
语言国际
安装完vue-i18n
差不多就能上手使用,但这里需要注意的是与第三方框架的语言同步,如element-ui、echarts和mavon-editor等,好在这些开源包都有提供国际化的功能,这里注意下加入的内容即可。
CSS预处理
这里采用scss,一是scss功能和使用都广泛些,二是element-ui也是采用的scss,修改主题的时候比较方便。关于css文件和样式文件的规划,我在知乎看到一篇不错的文章,如果你感兴趣可以看一下。
状态管理
在这里使用vuex我觉得是很有必要的,不单是vue组件间的状态共享,还涉及到第三方依赖对状态的修改和读取。vuex采用模块化封装,读取统一从state读取,更新从actions中操作。
网络请求
由于是纯前端项目,数据MOCK多少是个问题,之前有尝试过Faskmock,但有时候感觉不是很稳定,最后我还是选择使用纯MOCKJS是生成数据。网络请求使用axios进行封装,以模块化的方式进行调用,这里与vuex结合得比较深。
按需加载
项目中用到不少大型的库,完全加载的话有些浪费,因此有必要进行按需加载。项目中涉及的大型库有element-ui、lodash和echarts等。
静态部署
个人开源项目一般都有部署到Github pages的需求,除了创建<username>.github.io
仓库这样的方式,其实还可以通过在普通仓库创建gh-pages
分支来挂载静态页面(上传后通过<username>.github.io/<repository>
访问)。再结合Github Action,可以帮助我们进行CI构建和部署。
项目启动
开始前需要安装好NodeJS和vue-cli(4.x版本),代码编辑器我使用的是vs code。我曾尝试过Vue3+TS的方式,但使用过程感觉不太顺畅(主要是太菜),这里依旧使用2.x版本的vue,但cli是4.x的。
Vue-cli4.x有2种方式创建项目,一种是vue ui
这样的GUI界面,另一种是vue create
这样的CLI界面。这里我用的是CLI方式,毕竟在vs code终端里操作还是比较方便的。
// management 为项目名称([Vue 2] babel, eslint)
vue create management
题外话:Vue-cli使用的是
npm run serve
而不是npm run dev
。之前有点奇怪为什么不是server
而是serve
,后来才知道serve
是个动词,而server
是个名词(~ ̄▽ ̄)~。
项目结构
Vue-cli为我们提供一个项目脚手架,但目录结构什么的仍需要根据我们根据需要自己需要设置。在这里,我将vue全家桶的封装,统一放在/src
目录下(已经是种默认规则),目前有如下:
vuex # /src/store
axios # /src/api
vue-router # /src/router
vue-i18n # /src/lang
对于其他第三方库,我并不是直接import
引用,而是统一放置在/src/plugins
目录下,进行二次封装后再引用,目前有如下:
element-ui # /src/plugins/element-ui.js
driver.js # /src/plugins/driver.js
nprogress # /src/plugins/nprogress.js
vue-count-to # /src/plugins/count-to.js
webpack-theme-color-replacer # /src/plugins/theme-replacer.js
最终项目结构如下:
├── .github # Github Action的配置目录
├── public # 公共目录
│ │── favicon.ico # 网站图标
│ └── index.html # 首页模板
├── /src # 源代码目录(/src#source)
│ ├── api # 网络请求
│ ├── assets # 静态资源(图片、字体和样式)
│ ├── components # 公共组件
│ ├── config # 配置文件(角色配置、主题配置和vue指令等)
│ ├── helper # 助手函数
│ ├── lang # 语言国际(lang#language)
│ ├── pages # 路由页面
│ ├── plugins # 第三方依赖封装(element-ui、nprogress、driver等二次封装)
│ ├── router # 路由封装
│ ├── store # 状态管理
│ └── main.js # 入口文件
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .gitignore # git配置
├── babel.config.j # babel配置
├── package-lock.json # package锁定配置
├── package.json # package配置
├── README.md # readme
└── vue.config.js # vue-cli的配置文件
项目配置
vue-cli提供有vue.config.js
项目配置和.env
环境变量配置功能,在开发之前有必要对项目做一下基本的配置。
配置vue-cli
首先,在项目根目录下,创建vue.config.js
文件,写入一些基本的配置(更多配置可以参考这里)。
需要注意的是,导出方式需为commonJS的module.exports
而不是ES module的export default
,这是因为Node默认只支持commojs模块,而webpack才对es模块进行支持。
<!-- filename: /vue.config.js -->
module.exports = {
// 因为要部署到GitHub pages上,所以将生产环境的值改成仓库名称
publicPath: process.env.NODE_ENV === "production" ? "/management/" : "/",
// 减少生产环境下的文件体积
productionSourceMap: false,
// 链式配置webpack
chainWebpack: (config) => {
// 初始化public/index.html中的title值
config.plugin("html").tap((args) => {
// 该值在.env.production和.env.development中配置
args[0].title = `${process.env.VUE_APP_NAME} - ${process.env.VUE_APP_DESCRIPTION}`;
return args;
});
},
};
配置环境变量
这是Vue-cli提供的环境变量功能,创建.env.development
和.env.production
文件分别作用于开发环境和生产环境。关于env的更多信息,可以点击这里。
<!-- filename: /.env.development -->
# 系统名称
VUE_APP_NAME = '绝弹开发系统'
# 系统描述
VUE_APP_DESCRIPTION = '遇到路途上不知道的风景'
# 开发环境的API接口
VUE_APP_API_BASE_URL = ''
# 开发环境下的路由模式
VUE_APP_ROUTER_MODE = 'history'
<!-- filename: /.env.production -->
# 系统名称
VUE_APP_NAME = '绝弹管理系统'
# 系统描述
VUE_APP_DESCRIPTION = '遇到你不知道的风景'
# 开发环境的API接口
VUE_APP_API_BASE_URL = 'https://www.fastmock.site/mock/7522085e7af7a7b98bc42530bd1ddfb7/management'
# 开发环境下的路由模式
VUE_APP_ROUTER_MODE = 'hash'
网站图标
这个步骤当前可做可不做,主要看个人喜好,但我喜欢换个icon这样看起来顺眼些。将/public
目录下的favicon.ico
文件替换掉即可。
本地域名
这一步主要是将诸如localhost:8080/#/home
这样的URL,换为www.w.com/home
这样的URL,这步同样可做可不做,主要看个人习惯。
配置本地hosts
我使用的是windows系统,这里就以这个为例,打开C:\Windows\System32\drivers\etc
目录下的hosts
文件,在文件的末尾添加一个本地域名映射。推荐使用单数字或单字母(非QXZ).com
域名,因为这些域名尚未启用,暂无冲突危险。
<!-- filename: C:\Windows\System32\drivers\etc\hosts -->
# IP和域名之间以空格分开
127.0.0.1 w.com
配置vue.config.js
以上配置好之后,在浏览器访问w.com
会直接指向本地IP127.0.0.1
。但vue-cli会默认使用端口8080,如果不想以w.com:8080
的形式访问,则需要配置一下vue.config.js
。同时,还需要将disableHostCheck
设为true
,否则在访问的时候回出现Invalid Host header
的情况。
<!-- filename: vue.config.js -->
module.exports = {
configureWebpack: {
devServer: {
// 在hosts文件中添加127.0.0.1 www.w.com记录,再配合以下两个参数设置可使用www.w.com代替localhost:8080访问
host: "localhost",
// 端口设为80,方便使用本地域名开发
port: "80",
// 因为使用本地域名,此值需设为true,避免出现Invalid Host header的情况
disableHostCheck: true,
},
}
}
配置vue-router
以上已经可以正常使用,但还可以再尝试优化一下:在本地开发环境下使用history
路由提升浏览体验,在实际生产环境下使用hash
路由。
<!-- filename: /.env.development -->
# 开发环境下的路由模式
VUE_APP_ROUTER_MODE = 'history'
<!-- filename: /.env.production -->
# 生产环境下的路由模式
VUE_APP_ROUTER_MODE = 'hash'
这里使用.env
进行环境变量配置,修改和配置都方便一些,不过要注意文件名不要以.local
结尾,这些文件会被git忽略。
自动构建
版本控制是必不可少的,什么协作开发等暂且不谈。在这个项目里,主要是要发布到Github,配合Github Action以及Github Pages进行持续构建及自动部署。
创建仓库
直接3连操作,主要是先把之前改造的目录结构给记录下来。
git init
git add .
git commit -m "首次提交"
关联Github仓库
这里我用的的是github
不一定非得是origin
,这只是个别名而已,另外我这里用的是ssh链接。
git remote add github git@github.com:juetan/management.git
刚开始时,我用的是https链接,但由于某股神秘力量会遇到下面的错误:
// 提示问题
OpenSSL SSL_read: Connection was reset, errno 10054
谷歌一搜也有解决办法:
// 解决办法
git config --global http.sslVerify "false"
但实际效果并不是很理想,偶尔还是有这个问题,后面我想到可以用SSH连接,于是切换到这种方式,目前没有再遇到过这个问题。
CI构建
个人开源项目一般都有部署到Github pages的需求,除了创建<username>.github.io
仓库这样的方式,其实还可以通过在普通仓库创建gh-pages
分支来挂载静态页面(上传后通过<username>.github.io/<repository>
访问)。再结合Github Action,可以帮助我们进行CI构建和部署。
本地配置
GitHub Action使用.yml
文件作为配置文件,需要在项目根目录下创建.github/workflows
目录,文件名可任意。网上有些教程可能会说要设置secret密钥什么的,我翻阅了一下文档,是可以直接使用secrets.GITHUB_TOKEN
的,所以这一步可以省略。
<!-- filename: /.github/workflows/main.yml -->
# 工作流名称,可自定义
name: 通过GithubAction自动部署到GithubPages
# 事件监听,决定什么触发该工作流内的任务
on:
# 在master分支推动到github时触发
push:
branches: [ master ]
# 任务集合,可包含多个任务
jobs:
# 任务名称
build:
# 运行的操作系统
runs-on: ubuntu-latest
# 步骤集合,可包含多个步骤
steps:
# 单个步骤,没有名称,直接使用一个action
- uses: actions/checkout@v2
# 单个步骤,带有名称,带有参数
- name: build and deploy
run: |
npm install
npm run build
cd dist
# 用户名自定义修改
git config --global user.name "juetan"
# 邮箱自定义修改
git config --global user.email "810335188@qq.com"
git init
git add -A
git commit -m "通过Github Action自动部署"
git push -f "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" master:gh-pages
除此之外,还要配置下vue.config.js
,将公共目录指向仓库名。
<!-- filename: /vue.config.js -->
module.exports = {
// 公共路径,因为要部署到GitHub pages上,所以将生产环境的值改成仓库名称
publicPath: process.env.NODE_ENV === "production" ? "/management/" : "/",
}
远程配置
在GitHub仓库的setting
中,将Pages
中的分支切换为gh-pages
,然后就可以看到下图中绿色背景框中的发布地址。
提交到github
本地和远程都配置好之后,就可以push到GitHub仓库了。
git push github master
然后在仓库中的Actions
页面,可以看到我们的CI任务,点击进去可以看到详情。等待构建完成后(几十秒到几分钟不等),就可以通过<username>.github.io/<repository>
访问了。此外,域名是可以用自己域名的而且支持https,如果是个人博客可以考虑用域名,这里不做细说。最后,贴一张成功的图。
数据模拟
对于数据模拟,如果有后端尽量使用后端提供的API,没有后端配合的话可以考虑使用以下2种途径来获得数据:
- 使用Easy-Mock或Fast-Mock这样的在线MOCK平台
- 本地环境使用Mock.js来实现。
我一开始使用的是fast-mock,但有时候不是特稳定,最后转向了mock.js。对于mock.js的话,其实也有2种使用方式:
- 直接在
src\main.js
中引用,这会重写XHR并可能与其他库冲突,同时在浏览器的network
面板中看不到网络请求; - 通过webpack的
devServer
配置,将匹配的请求导向mock.js。
目前使用的是方式1,因为本项目只是个纯前端项目,同时涉及的请求并不多。
MockJS安装
在终端里安装,如果使用方式1保存为dependencies
,使用方式2保存为devDependencies
。
// 如果是方式2,保存为--save-dev
npm install mockjs --save
MockJS配置
mock文件夹一般放置在根目录下,我想这样做的原因是:方便数据源的切换,无论使用上面的哪种方式,都可以做出很好的处理。目前只是我个人猜测,这里遵循这种默认规则。关于MockJS的配置和语法这里不做介绍,详情可点击官网查看。
<!-- filename: /mock/server.js -->
import Mock from "mockjs";
// MOCKJS配置
Mock.setup({
// 延迟返回数据
timeout: 800,
});
// 表格数据
Mock.mock("/table", "get", {
code: 2000,
message: "数据返回成功",
"data|80": [
{
"id|+1": 1,
name: "@cname",
number: "equip-@first",
spec: "ws-@last",
type: "@natural(1,3)",
status: "@natural(1,3)",
manufacturer: "@cname",
buyDate: "@date",
mark: "无",
},
],
});
文件引入
直接在main.js中引入,即可使用axios等网络请求库进行数据请求。
<!-- filename: /src/main.js -->
import "/mock/server.js";
状态管理
关于vuex的使用,官方文档已经给出很好的实践,直接按照模块的方式进行封装即可。
vuex安装
直接在命令行里安装即可;
npm install vuex --save
实例创建
在/src/store
目录下,创建index.js
文件;该文件的作用在于,自动引入./modules
目录下的所有.js
文件,并导出实例化的vuex。
<!-- filename: /src/store/index.js -->
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 使用require.context自动引入modules目录下的所有文件
const moduleFiles = require.context("./modules", true, /\.js$/);
// moduleFiles既是函数也是对象,此处作对象调用
const modules = moduleFiles.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
// 此处作函数调用,类似于import
const module = moduleFiles(modulePath);
// 将所有模块放在一个数组内
modules[moduleName] = module.default;
return modules;
}, {});
// 创建vuex实例
const store = new Vuex.Store({
modules
});
export default store;
模块创建
模块放在/src/store
下的modules
目录,每个模块命名一个.js
文件,例如user.js
;
<!-- /src/store/modules/user.js -->:
const state = {
// 用户名
username: '',
};
const mutations = {
// 设置用户名
set_username: (state, name) => {
state.username = name;
},
};
export default {
namespaced: true,
state,
mutations,
};
实例引入
在/src/main.js
中引入即可;
<!-- filename: /src/main.js -->
import store from '@/store'
new Vue({
store
}).$mount('#app');
使用
vue组件内使用:
<!-- filetype: .vue -->
export default {
methods: {
xxx() {
this.$store.commit('user/set_name',name)
}
}
}
非vue组件内使用:
<!-- filetype: .js -->
import store from '@/store'
cosnt title = store.state.default.title;
网络请求
axios有2种封装的思路是不错的。一种是偏函数的封装,每个请求模块导出一个个请求函数,使用的时候按需引用就行;另一种是偏对象的封装,每个模块统一导出,汇总到index文件,最后挂在到Vue.prototype
上。这里采用第2种,主要是和vuex配合进行状态管理,同时挂载$request
方法到vue原型上方便调用。
axios安装
直接在命令行里安装axios即可;
npm install axios --save
实例创建
在src/api
目录下,新建index.js
文件,配置基本参数、interceptors拦截器并实例化axios。具体代码不贴了可以去看源码,这里贴一下主代码并说下2个点:
baseURL
:通过process.env.NODE_ENV === 'development' ? 'xx' : 'xx'
配置是可以的,不过利用vue-cli提供的环境变量也是不错的。- loading: 这里封装了一个loading并加了一些处理逻辑,对于数据请求来说,知道数据在请求还是比较有体验的。
<!-- filename: /src/api/request.js -->
// 引入axios
import axios from "axios";
// 创建axio例
const request = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 3000,
});
// 设置请求拦截器
request.interceptors.request.use(
(config) => {
// ...
},
(error) => {
// ...
}
);
// 设置相应拦截器
request.interceptors.response.use(
(response) => {
// ...
},
(error) => {
// ...
}
);
export default request;
模块创建
在src/api/modules
,新建user.js
(每个.js
文件表示一个模块的网络请求)。这里可以对请求URL单独做一个单独的配置,不过我感觉目前没必要就暂时不做。
<!-- filename: /src/plugins/api/modules/user.js -->
import request from "../index";
// 登录用户
export function login_user(data) {
return request.post("/login", data);
},
// 获取用户信息
export function get_userinfo() {
return request.post("/userinfo");
},
实例挂载
在src\main.js
文件中,引入axios实例并挂载在vue原型上;
<!-- filename: /src/main.js -->
import Vue from "vue";
// 网络请求
import request from "@/api";
Vue.prototype.$request = request;
请求使用
在这里,一个axios的模块对应一个vue组件或vuex模块,因而存在vue组件和vuex模块2种使用场景。
在vue组件内使用:
<!-- filename: /src/pages/app/table/index.vue -->
import { getTableData } from '@/api/modules/table';
export default {
created() {
getTableData().then((data)=>{
...
}
}
}
在vuex模块内使用:
<!-- filename: /src/store/modules/user.js -->
import { login_user } from "@/api/modules/user";
const actions = {
// 登录用户
login_user(context, userData) {
return login_user(userData).then((data) => {
...
});
},
}
语言国际
首先说下国际化中的i18n
和l10n
,这是两个单词。
- i18n,即internationalization(国际化),开头的
i
和结尾的n
隔着18个字母,简称i18n
; - l10n,即localization(本地化),开头的
l
和结尾的n
隔着10个字母,简称l10n
。
我最早了解到这个概念,是在接触wordpress主题的时候。在wordpress,国际化使用
__
、_x
之类的函数进行不同语言的翻译输出,功能在于提供多种语言的切换;而本地化则使用插件、POEDITOR之类的工具,将首选语言翻译成不同的语言,如将英语翻译成中文,每种语言代表一个.po
文件。
在wordpress里,国际化和本地化是分开的,但在vue-i18n里两者集成在一起,这是非常方便的。但在前端项目里,有个问题需要考虑,就是组件内的语言和外部依赖中的语言能否协调一致。
但好在一般的大型开源项目都会提供国际化的功能,这里以vue-i18n和element-ui的集成为例,记录下如何集成。
vue-i18n安装
直接在终端里安装即可。
npm install vue-i18n --save
实例创建
在/src/lang
目录下创建index.js
,这里主要是导入./modules
目录下的所有.js
语言包文件,创建并导出vue-i18n实例。对于第三方库的语言,目前涉及3种情况:
- 有的第三方库提供修改语言的方法,如mavon-editor;
- 有的第三方库内置语言包(需要自己配置),如element-ui;
- 有的第三方库没有提供语言接口或需要根据API返回数据修改语言,如echarts。
对于情况1,直接在组件内进行调用即可;对于情况2,可根据文档提示操作;对于情况3,目前将语言包放置在./modules
下,数据返回时重新更新组件。
<!-- filename: src/lang/index.js-->
import Vue from "vue";
import VueI18n from "vue-i18n";
// element-ui的语言包
import locale_element_en from "element-ui/lib/locale/lang/en";
import locale_element_zh from "element-ui/lib/locale/lang/zh-CN";
// 本项目的语言包
import locale_zh from "./modules/zh";
import locale_en from "./modules/en";
Vue.use(VueI18n);
// 语言包合并
const messages = {
zh: {
...locale_element_zh,
...locale_zh,
},
en: {
...locale_element_en,
...locale_en,
},
};
// 创建实例
const lang = new VueI18n({
locale: localStorage.getItem("language") || "zh",
messages,
});
export default lang;
模块创建
在/src/lang/modules
目录,新建zh.js
文件(每个.js
文件代表一种语言)。该模块返回一个对象,建议使用2级结构:即1级表示vue组件或系统模块,2级表示具体的内容。示例如下,login表示vue登录组件,该对象下的属性表示具体内容,实际使用的时候可通过$t('login.title')
方式调用。
<!-- filename: /src/lang/modules/zh.js -->
export default {
login: {
title: "账号登录",
description: "Hello!,欢迎您登录后台管理系统!",
userplaceholder: "请输入用户名",
passplaceholder: "请输入密码",
uservalidator: "用户名不能为空,请检查用户名!",
passvalidator: "密码不能为空,请检查面慢",
rememberme: "记住密码",
forget: "忘记密码?",
submit: "提交",
ways: "其他登录方式",
bymobile: "手机登录",
byemail: "邮箱登录",
loginedtip: "登录成功,即将跳转首页",
},
};
引入和安装
element-ui的语言包除了在vue-i18n引入外,element-ui也需要做一些处理。因为我对element-ui做了单独封装,目前使用的是下面的方式1,如果你不想做单独封装的话,直接使用方式2即可。
方式一(目前使用):
<!-- filename: /src/plugins/element-ui.js -->
import Vue from "vue";
import i18n from "@/lang";
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value),
});
<!-- filename: /src/main.js -->
import i18n from "@/lang";
new Vue({
i18n,
})
方式二(常规方法):
<!-- filename: /src/main.js -->
import i18n from "@/lang";
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value),
});
new Vue({
i18n,
})
注意:无论使用哪种方法,都需要在new Vue
的选项里传递i18n
(vue-i18n实例)。vue-i18n需要读取这个参数,将$i18n
属性和$t
方法挂载到vue上面。
使用和修改语言
完成以上步骤后,就可以在vue里面正常使用了。虽然调用API是相同的,在vue组件里和在非vue组件里的调用姿势略有差别:
- 在
.vue
文件中,语言翻译使用this.$t()
方法,切换语言修改this.$i18n.locale
属性。 - 在其他文件中,语言翻译使用
i18n.t()
方法,切换语言修改i18n.locale
属性
姿势一(在vue组件里使用):
<template>
<div :title="$t('login.title')">
// 在template里使用
{{ $t('login.title') }}
</div>
</template>
<script>
export default {
methods: {
xxx(lang) {
// 在methods里使用
this.$i18n.locale = lang
}
}
};
</script>
姿势二(在非vue组件里使用):
<!-- Filename: /src/store/modules/user.js -->
import i18n from "@/lang";
const actions = {
// 切换语言
switch_language(context, language) {
context.commit("set_language", language);
i18n.locale = language;
return Promise.resolve();
},
}
路由管理
普通路由不怎么需要封装,但涉及权限控制及菜单自动生成等方面,就需要对路由进行一番改造。关于路由权限和菜单自动生成,这里暂且不谈,后面会单独说明。
vue-router安装
如果项目内还没有自动安装vue-router,需要先安装下。
npm install vue-router --save
实例创建
按照习惯,直接在/src/router
下新建index.js
文件,这一块涉及的内容较多,略去权限部分的逻辑,贴出主要逻辑代码。其中,主要涉及以下几个方面:
- 使用
.env
变量配置路由模式; - 使用nprogress进行路由过渡;
- 在后置守卫中更新网站标题;
import Vue from "vue";
import vueRouter from "vue-router";
import normalRoutes from "./modules/normal-routes";
import Nprogress from "@/plugins/nprogress";
import store from "@/store";
import { message } from "@/plugins/element-ui";
import i18n from "@/lang";
Vue.use(vueRouter);
// 创建实例
const router = new vueRouter({
// 开发环境使用history模式,生产环境使用hash模式
mode: process.env.VUE_APP_ROUTER_MODE,
// 权限路由是嵌套在普通路由中的
routes: normalRoutes,
});
// 全局前置守卫
router.beforeEach(function(to, from, next) {
// 开始进度条
Nprogress.start();
// 权限处理代码...
});
// 全局后置守卫
router.afterEach((to) => {
// 结束进度条
Nprogress.done();
// 如果有标题则更新标题
if (to.meta && to.meta.title) {
// 更新title
document.title =
i18n.t("router." + to.meta.title) + " - " + i18n.t("system.description");
} else {
document.title =
i18n.t("system.title") + " - " + i18n.t("system.description");
}
});
export default router;
模块创建
在src/router/modules
目录下,创建2个文件:normal-routes.js
和authed-routes.js
文件,前者保存不需要访问权限的路由,后者保存需要权限的路由。
<!-- filename: /src/router/modules/normal-routes.js -->
export default [
{
path: "/login",
name: "login",
meta: { title: "login", icon: "xxx" },
component: () =>
import(/* webpackChunkName: "chunk-login" */ "@/pages/login"),
},
// ...
]
这里需要注意下import()
,这是webpack提供的异步加载函数,如果将所有代码都打包到app chunk里会造成首屏渲染问题;同时还需要注意该函数内的注释,这是webpack提供的异步加载chunk注释,默认情况下每个异步加载的文件会独立打包成一个独立chunk,使用该功能,可将多个异步加载的文件打包到同一个chunk里面,该注释可以指定chunk的名字,这样不仅达到请求优化的目的,还便于文件识别和分析。
实例引入
在/src/main.js
中引入router。这里删除/src/app.vue
,直接使用rendrer
方法生成路由视图组件,这是1级路由视图,主要用于渲染app
、login
、404
等组件。
<!-- filename: /src/main.js -->
impot router from '@/router'
new Vue({
router,
render: (h) => h("div", { attrs: { id: "app" } }, [h("router-view")], 1),
})
路由视图
除了/src/main.js
中的1级路由,/src/pages/layout/index.vue
中的路由视图是承载整个应用的2级路由,包括home
、table
、markdown
和admin-page
等组件。
<!-- filename: /src/pages/layout/index.vue -->
<template>
...
<!-- 渐变效果,用以配合进度条 -->
<transition name="fade-transform" mode="out-in">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
...
</template>
上面使用了2个内置组件:<transition>
和<keep-alive>
,前者用于配合nprogess
插件进行视图过渡;后者用于缓存组件,目前组件不多暂不做限制,两者目的都在于提升用户体验。
<!-- filename: /src/router/index.js -->
import Nprogress from "@/plugins/nprogress";
// 全局前置守卫
router.beforeEach(function(to, from, next) {
// 开始进度条
Nprogress.start();
}
// 全局后置守卫
router.afterEach((to) => {
// 结束进度条
Nprogress.done();
}
静态资源
这里的静态资源包括图片、字体和样式文件等,目前我将所有的静态资源统一放置在/src/assets
目录下。对于图片文件,我个人习惯放置于对应组件的目录下。这里主要说说样式的处理,以及图标字体的引入和更新等问题。
样式处理
CSS三大流行预处理器(less、scss和stylus),各有各的好处,这里不作讨论。这里说下我在这里使用scss的2个主要原因:
- scss与css比较贴合,配合emmet体验良好(stylus偶尔遇过问题);
- 方便修改element-ui的颜色变量(element-ui使用scss)
sass和scss是2代不同的版本,前者使用缩进语法,后者使用CSS语法,早前的sass是Ruby写的,后来移植到了nodejs。在nodejs,sass和scss是在同一个package里面的,通过.sass
和.scss
后缀判断其版本。而这个package之前使用的是node-sass
,这需要安装ruby。后面使用sass
这个package,这是使用纯nodejs书写的package。
关于CSS样式和目录结构的处理,可以参考下这篇文章。
安装scss
这里需要注意的是scss和scss-loader之间的版本关系,最新的scss和scss-loader不一定匹配,强行安装会报错。目前,我是使用的scss为v1.26.2
版本,sass-loader为v8.0.2
版本,
npm install sass@1.26.2 sass-loader@8.0.2 --save-dev
样式类名
对于CSS样式类名的命名,目前在非完全地使用BEM命名规则。对于模块化,可以使用BEM配合文件分类,也可以使用官方给出的moduled
和scoped
任意1种方式。这里使用的是scoped
方式,使用方式也比较简单,在.vue
组件里的style
标签里加上scoped
属性即可。
<!-- filetype: .vue -->
<style scoped>
...
</style>
目录结构
除去vue组件内的样式,我们还需要写一些其他的样式,涉及样式重置、通用样式、第三方样式重写等情况。以下是一个示例,更多可根据自己需要添加。
<!-- directory: /src/assets/styles -->
├── base-normalize.scss # normalize.css库
├── base-typegraphy.scss # 排版样式
├── base-variables # 颜色变量
├── index.scss # 主文件
├── layout-app.scss # 布局样式
├── layout-scrollbar.scss # 滚动条样式
├── vendor-driver.scss # driver.js库样式重写
├── vendor-element.scss # element-ui库样式重写
└── vendor-nprogress.scss # nprogress库样式重写
关于第三方样式,这里重写的主要原因是,配合webpack-theme-color-replacer
进行主题换肤,尽量保证页面样式的一致性。
全局引入
对于颜色变量和重置等样式,需要提前全局插入,这里可以利用vue-cli提供的vue.config.js
进行提前注入。需要注意的是,下面scss对象内的的属性分2种情况(官方文档):
- sass-loader<v8,使用
additionalData
属性名; - sass-loader>v8,使用
prependData
属性名(当前使用版本:v8.0.2)。
<!-- filename: /vue.config.js -->
module.exports = {
// css相关配置
css: {
loaderOptions: {
scss: {
prependData: `@import "~@/assets/styles/index.scss";`,
},
},
},
}
图标字体
element-ui内置不少图标字体,但不一定满足实际需求,这时可以使用iconfont、fontawesome等字体库提供的图标。
引入iconfotn图标
对于iconfont上面的图标,可以下载到本地,也可以在线引用。这里直接在线引用,因为项目一开始需要的图标尚未确定,后面更新用这种方式非常方便。方式也非常简单,直接在/public/index.html
文件中进行引入即可。
<!-- filename: public/index.html -->
<html lang="">
<head>
<!-- 注意这里 -->
<link rel="stylesheet" href="//at.alicdn.com/t/font_2449987_nge8aeefn5.css">
</head>
</html>
提前处理
不知道有没有人注意到,element-ui和iconfont的图标调用姿势是略有差别的:
- 在element-ui里,通常以
el-icon-xxx
(1个类名)的形式调用图标; - 在iconfont里,通常以
iconfont icon-xxx
(2个类名)的形式调用图标。
为啥不一样?这里其实是element-ui提前做了处理,使用属性选择器来巧妙地规避多写这个问题,下面直接贴改造好的代码。
<!-- filename: src/assets/fonts/iconfont.scss -->
[class*=" icon-"], [class^=icon-] {
font-family: iconfont!important;
font-style: normal;
font-weight: 400;
font-variant: normal;
text-transform: none;
line-height: 1;
vertical-align: baseline;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
注意:这里使用了2个选择器,主要是应对<element class="icon-xx a-class">
和<element class="a-class icon-xx">
这2种情况。
主题切换
在vue-element-admin中,提到有2种方案对element-ui的主题色进行切换:
- 使用颜色选择器,对品牌色进行正则替换;
- 使用theme-chalk生成多套主题,切换主题时动态加载。
不过,以上2种方式功能都有限,且均未涉及vue组件内颜色的替换。经过一番探索,发现一款插件-webpack-theme-color-replacer非常好用:不仅可以满足vue组件的颜色替换,还可以满足第三方库的颜色替换(包括element-ui),同时还为element-ui提供相关支持。
这个插件的另一个优点在于,只抽离涉及特定颜色的样式,意味着导出体积很小。完整的element-ui样式有200k左右,而使用插件只有几十K。关于插件原理,可以点击这里查看。
插件安装
这里不需要安装在生产依赖下,直接安装在开发依赖下即可;
npm i webpack-theme-color-replacer --save-dev
主题配置
本项目在/src/assets/styles/base-variables.scss
中已经预设一套颜色变量,配置文件中将以它作为默认主题进行配置。
<!-- /src/config/theme.config.js -->
const forElementUI = require("webpack-theme-color-replacer/forElementUI");
// 两个主题色 这里使用commonjs语法,因为在vue.config.js不能使用es module语法
module.exports = {
default: [
// element-ui主题色
...forElementUI.getElementUISeries("#10c599"),
// element-ui功能色-成功色
...forElementUI.getElementUISeries("#3c9"),
// element-ui功能色-警报色
...forElementUI.getElementUISeries("#f90"),
// element-ui功能色-危险色
...forElementUI.getElementUISeries("#f66"),
// element-ui功能色-信息色
...forElementUI.getElementUISeries("#959595"),
// 侧边栏背景色
"#324554",
],
skyblue: [
// element-ui主题色
...forElementUI.getElementUISeries("#409eff"),
// element-ui功能色-成功色
...forElementUI.getElementUISeries("#67C23A"),
// element-ui功能色-警报色
...forElementUI.getElementUISeries("#E6A23C"),
// element-ui功能色-危险色
...forElementUI.getElementUISeries("#F56C6C"),
// element-ui功能色-信息色
...forElementUI.getElementUISeries("#909399"),
// 侧边栏背景色
"#001529",
],
};
注意:颜色的16进制能简写尽量简写,例如#3c9
不要写成#33cc99
,否则会造成开发环境和生产环境下,样式不一致的问题。
功能重写
在实际使用过程中,我发现element-ui的功能色是不正常的。F12一番后发现是插件changeSelector函数的问题,于是对其重新封装,问题解决,但目前尚不明确带来的副作用。
<!-- filename: /src/helper/change-selector.js -->
function changeSelector(selector, util) {
switch (selector) {
case ".el-button:active":
case ".el-button:focus,.el-button:hover":
return util.changeEach(selector,".el-button--default:not(.is-plainnot(.el-button--primary)" );
case ".el-button.is-active,.el-button.is-plain:active":
// 注意这里
return "useless";
case ".el-button.is-plain:active":
case ".el-button.is-plain:focus,.el-button.is-plain:hover":
return util.changeEach(selector, ".el-button--default");
case ".el-pagination button:hover":
return selector + ":not(:disabled)";
case ".el-pagination.is-background .el-pager li:not(.disabled):hover":
return selector + ":not(.active)";
case ".el-tag":
return selector + ":not(.el-tag--dark)";
default:
return selector;
}
}
module.exports = changeSelector;
vue配置
因为使用的是vue-cli4.x版本,所以直接在vue.config.js中配置到plugins即可。注意,vue.config.js不能使用import语句,需要使用require语句导入插件。此外,该插件对element-ui主题色提供了增强功能
<!-- filename: /vue.config.js -->
const webpackThemeColorRplacer = require("webpack-theme-color-replacer");
const themeConfig = require("./src/config/theme.config.js");
const changeSelector = require("./src/helper/change-selector");
module.exports = {
// 这里我用的是链式配置,使用configureWebpack也是可以的
chainWebpack: (config) => {
config.plugin("webpackThemeColorRplacer").use(webpackThemeColorRplacer, [
{
matchColors: themeConfig["default"],
fileName: "css/chunk-theme-[contenthash:8].css",
// 使用element-ui必备,否则plain和普通按钮样式会有bug
changeSelector: changeSelector,
injectCss: false,
isJsUgly: true,
},
]);
}
}
功能封装
以上完成css颜色样式的功能,接下来写颜色主题切换的函数。
<!-- filename: src/plugins/theme-replacer.js -->
import client from "webpack-theme-color-replacer/client";
import store from "@/store";
const themeConfig = require("@/config/theme.config.js");
// 替换主题色
export function replaceThemeColors(color) {
// 选项
const options = { newColors: themeConfig[color] };
return client.changer.changeColor(options, Promise);
}
// 初始化主题色
export function initThemeColor() {
// 获取当前主题
const currentTheme = store.state.default.theme;
if (currentTheme) {
document.body.style.display = "none";
// 替换颜色
replaceThemeColors(currentTheme).finally(() => {
document.body.style.display = "";
});
}
}
初始化和使用
首先在/src/main.js
进行每次初始化,当前主题会保存在localStorage
中,初始化时会读取该值进行初始化。
<!-- filename: src/main.js -->
import { initThemeColor } from "@/plugins/theme-replacer";
// 初始化主题
initThemeColor();
函数replaceThemeColors
一般在vue组件内调用,但考虑到其他因素,这里将其与vuex进行结合,可根据个人习惯调用。
<!-- filename: src/store/modules/default.js -->
import { replaceThemeColors } from '@/plugins/theme-replacer';
const actions = {
// 切换主题
switch_theme(context, theme) {
// 切换主题的主函数,传入的theme为string类型
return replaceThemeColors(theme).then(() => {
// 处理代码...
});
},
}
权限控制
项目里的权限控制,并不是基于角色(role)的访问控制,而是基于权限(capability或permission)。主要是对权限的颗粒控制更友好,且对于页面内的按钮控制同样适用。
具体做法是,在/src/config/role.config.js
里保存所有权限配置,在vue-router的meta
字段里指定capability
(或permission
)属性,
权限配置
在/src/config/role.config.js
会保存角色(role)及其权限(capability)配置,后续会根据这个进行权限的判断。
<!-- filename: /src/config/role.config.js -->
// 用户权限配置
export default {
admin: {
// 路由权限
visitAdminPage: true,
visitEditorPage: false,
// 指令权限
publishPost: true,
editPost: true,
deletePost: true,
readPost: true,
},
editor: {
// 路由权限
visitAdminPage: false,
visitEditorPage: true,
// 指令权限
publishPost: false,
editPost: true,
deletePost: false,
readPost: true,
},
};
权限函数
用户登陆后会将角色保存在vuex
和sessionStorage
中,通过读取role.config.js
中对应的权限配置,与访问目标需要的权限对比,这样就实现了权限判断。这里将其单独封装成1个助手函数,方便后续调用。
import store from "@/store";
import roleConfig from "@/config/role.config.js";
function hasCapability(capability) {
// 是否有权限(boolean)
let isShowedInMenu = false;
// 如果需要权限则判断一下权限
if (capability) {
// 遍历当前用户角色
store.state.user.role.forEach((userRole) => {
// 获取角色的权限表
const roleCapabilities = roleConfig[userRole];
// 如果对应的权限在用户权限表上为true则返回true
if (
roleCapabilities[capability] &&
roleCapabilities[capability] === true
) {
isShowedInMenu = true;
}
});
} else {
// 没有声明权限的话默认为公共权限,任何角色都可访问
isShowedInMenu = true;
}
return isShowedInMenu;
}
export default hasCapability;
页面权限
在src/router/modules
目录下,创建2个文件:normal-routes.js
和authed-routes.js
文件,前者保存不需要访问权限的路由,后者保存需要权限的路由。
<!-- filename: /src/router/modules/authed-routes.js -->
export default [
{
path: "/admin-page",
name: "adminPage",
meta: {
title: "adminCapability",
icon: "icon-cap",
capability: "visitAdminPage",
},
component: () => import("@/pages/app/capability"),
},
}
与普通路由相比,权限路由在meta
属性多了capability
属性,表示访问该路由需要的权限。在生成菜单时,判断权限决定是否渲染。
<!-- filename: /src/pages/layout/components/nav-item/index.vue -->
<template>
...
<!-- 情况[1]:单个路由 -->
<el-menu-item v-if="!route.children && !route.hidden && !route.meta.link && isCapabilited(route.meta.capability)" :key="route.path" :index="route.path" class="ment-item">
<!-- 路由图标 -->
<i :class="route.meta.icon"></i>
<!-- 路由标题 -->
<span slot="title">{{ $t('router.'+route.meta.title) }}</span>
</el-menu-item>
...
</template>
<script>
export default {
methods: {
// 判断当前路由是否需要权限,无权限则不显示在菜单上
isCapabilited(capability) {
return hasCapability(capability)
}
}
}
</script>
对于页面路由,需要在全局前置守卫进行处理。
<!-- filename: /src/router/index.js -->
// 全局前置守卫
router.beforeEach(function(to, from, next) {
// 获取登录令牌(token)
let token = store.state.user.token;
// 用户已登录
if (token) {
// 如果访问的是login,重定向回首页
if (to.path === "/login") {
// 弹窗提示已登录并返回首页
message({
message: i18n.t("router.loginedinfo"),
type: "success",
duration: 2000,
onClose() {
next({ path: "/" });
},
});
} else {
// 检查是否已获取用户角色等信息
const roled = store.state.user.role && store.state.user.role.length !== 0;
if (!roled) {
// 获取用户信息并跳转路由
store
.dispatch("user/get_userinfo")
.then(() => {
next();
})
.catch(() => {
// 获取失败的话返回登录页
store.commit("user/remove_token");
Nprogress.done();
next("/login");
});
} else {
// 判断是否有访问该页面的权限
if (hasCapability(to.meta.capability)) {
next();
} else {
message({
type: "error",
message: i18n.t("router.hasNoCapability"),
duration: 2000,
onClose() {
next("/");
},
});
}
}
}
} else {
// 是否在白名单内
if (whiteList.includes(to.path)) {
next();
} else {
next({ path: "/login" });
}
}
});
指令权限
对于页面内的按钮权限,统一通过vue指令进行判断,这里封装1个权限指令。
<!-- filename: /src/config/v-cap.js -->
import hasCapability from "@/helper/hasCapability";
const cap = {
inserted(el, bind) {
// 获取传入的权限值
const capability = bind.value;
// 如果为空直接返回
if (!capability) return;
const capabilitied = hasCapability(capability);
// 如果没有权限,直接移除
if (!capabilitied) {
el.parentNode && el.parentNode.removeChild(el);
}
},
};
export default cap;
然后在vue组件内,传入相对应的权限即可根据权限决定是否渲染元素。
<!-- filename: /src/pages/app/capability/index.vue -->
<template>
...
<el-button type="primary" v-cap="'publishPost'">{{ $t('capability.publishPost') }}</el-button>
...
</template>
导航菜单
这里的导航菜单,指将route
自动挂载到el-menu
中,避免手动挂载。这里的route
可分为3种情况:
- 普通路由组件,即
path
指向.vue
组件; - 嵌套路由,即当前路由存在
children
子路由; - 外部链接,即
path
指向外部链接(例如https://www.juetan.cn
)。
路由容器
这里的实现思路是,使用<el-menu>
作为外部容器,定义<vue-navitem>
组件作为递归组件,处理上面的情况。这里主要做了以下2件事:
- 折叠变量
collapsed
使用vuex
,这是因为这个值会在其他组件中用到(如nav-top
组件)。 - 添加主题切换的相关支持,这里用的是
<el-menu>
提供的属性接口进行处理,用纯css也是可以的,不过比较麻烦。
<!-- filename: /src/pages/layout/components/nav-menu.vue -->
<template>
<el-scrollbar id="navmenu">
<!-- 如需路由正确工作,需添加router属性 -->
<el-menu
router
:default-active="$route.path"
:collapse="collapsed"
:unique-opened="false"
:collapse-transition="false"
text-color="rgba(255,255,255,.65)"
:active-text-color="activeColor"
:background-color="backgroundColor"
class="aside"
>
<!-- 多少层routes就多少层router-view -->
<vue-navitem :data="routes"></vue-navitem>
</el-menu>
</el-scrollbar>
</template>
<script>
import vueNavitem from '../nav-item'
import routes from '@/router/modules/normal-routes'
export default {
name: "navmenu",
data() {
return {
routes: null
}
},
computed: {
collapsed() {
return this.$store.state.default.collapsed
},
backgroundColor() {
return this.$store.state.default.theme==='skyblue'?'#001529':'#324554'
},
activeColor() {
return this.$store.state.default.theme==='skyblue'?'#fff':'#ffd04b'
}
},
created() {
this.routes = routes[0].children
},
components: {
vueNavitem
}
}
</script>
<style lang="scss" scoped>
.aside.el-menu {
border-right: none;
}
::v-deep .el-menu-item:not(.is-active):hover {
color: $--color-white!important;
i {
color: $--color-white!important;
}
}
</style>
上面,部分内容为主题切换的功能,对于element-ui组件的样式渗透需要使用::v-deep
作前缀。
路由组件
这是个递归组件,遇到子路由的情况下,会调用自身组件,因此需要指定name
属性。此外,这里使用hasCapability
进行权限判断,实现路由的可视和挂载,这里只是个简单的实现,没有考虑更多复杂的情况。
<!-- filename: /src/pages/layout/components/nav-item.vue -->
<template>
<div class="navitem">
<!-- 因为用的是template,所以key放在里面 -->
<template v-for="route in data">
<!-- 情况[1]:单个路由 -->
<el-menu-item v-if="!route.children && !route.hidden && !route.meta.link && isCapabilited(route.meta.capability)" :key="route.path" :index="route.path" class="ment-item">
<i :class="route.meta.icon"></i>
<span slot="title">{{ $t('router.'+route.meta.title) }}</span>
</el-menu-item>
<!-- 情况[2]: 外部链接 -->
<a v-else-if="!route.children && !route.hidden && route.meta.link" :href="route.path" :key="route.path" target="_blank">
<el-menu-item class="ment-item">
<i :class="route.meta.icon"></i>
<span slot="title">{{ $t('router.'+route.meta.title) }}</span>
</el-menu-item>
</a>
<!-- 情况[3]: 嵌套路由 -->
<el-submenu v-else-if="route.children" ref="subMenu" :key="route.path" :index="route.path" popper-append-to-body>
<template slot="title">
<i :class="route.meta.icon"></i>
<span slot="title">{{ $t('router.'+route.meta.title) }}</span>
</template>
<nav-item :data="route.children"/>
</el-submenu>
</template>
</div>
</template>
<script>
import hasCapability from '@/helper/hasCapability';
export default {
name: "navItem",
props:{
data: {
type: Array,
required: true
}
},
methods: {
// 判断当前路由是否需要权限,无权限则不显示在菜单上
isCapabilited(capability) {
return hasCapability(capability)
}
}
}
</script>
<style lang="scss">
// 样式处理
</style>
以上,逻辑基本写完,上面css样式没贴出来,主要是想说2个样式问题:
- 使用非element-ui的图标,需要做一点样式处理(如下)。注意:上面
<style>
标签没有加scoped
属性,也可以使用::v-deep
进行样式渗透。
.el-menu-item [class^=icon-],.el-submenu [class^=icon-] {
margin-right: 5px;
width: 24px;
text-align: center;
font-size: 18px;
vertical-align: middle;
}
- 嵌套路由在折叠的时候,会将标题显露出来,这是由于el-menu子组件应为el-submenu、el-menu-item或el-menu-item-group。我的子组件最外层为div,有路由嵌套时会出现样式问题,此处添加处理样式。
// 隐藏标题
.el-menu--collapse .el-submenu__title span{
display: none;
}
// 隐藏小箭头
.el-menu--collapse .el-submenu__title .el-submenu__icon-arrow{
display: none;
}
体积优化
对于某些库来说(如element-ui、echarts等),完全打包进去是没必要的,此时可以通过按需加载进行体积优化。
体积分析
analyzer安装
安装到开发依赖下即可;
npm install analyzer-webpack-plugin --save-dev
webpack配置
该包为webpack插件,需要在webpack下配置,对于vue-cli4,可以在/vue.config.js
中进行配置。这里需要注意一点:如果有使用Github Action之类的需求,需要将该插件取消掉,否则会导致构建挂起。
<!-- filename: /vue.config.js -->
module.exports = {
// 链式配置webpack
chainWebpack: (config) => {
config.plugin("BundleAnalyzerPlugin")
.use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin, [
{
// 在默认浏览器中自动打开
openAnalyzer: false,
},
]);
}
}
Gzip压缩
关于Gzip压缩,这里分2种:静态Gzip和动态Gzip压缩。静态Gzip压缩,指Nginx服务器直接读取网站目录下的.gz
文件,然后返回给浏览器;动态Gzip压缩,指Nginx服务器读取网站目录下的.js
、.css
和.html
文件,将其压缩成.gz
文件,再返回给浏览器。
对于Github Pages,使用的是动态Gzip压缩,即使将文件压缩成.gz
文件也不会读取。但如果要部署到自己服务器,使用静态Gzip压缩还是很有必要的,毕竟牺牲一点内存换返回速度还是很划算的。
compression安装
如果安装最新版本出错,可以尝试回退到之前的某个版本。
// 安装最新的7.1.2版本会报错
npm i compression-webpack-plugin@5.0.1 -D
webpack配置
一般在生产环境下使用,
<!-- filename: /vue.config.js -->
module.exports = {
// 链式配置webpack
config.plugin("compression").use(ComporessionPlugin, [
{
// 正则匹配文件后缀
test: /.js$|.html$|.css$/,
// 对超过10KB的文件进行压缩
threshold: 1,
// 不删除源文件,主要是兼容不支持gzip的服务器
deleteOriginalAssets: false,
},
]);
}
异步加载
一次性加载完所有异步组件是没必要的,此时可以通过import()
函数来进行异步加载:它会将其单独打包成一个chunk,在使用的时候进行请求。
在路由中使用
注释中的webpackChunkName
,指打包后的Chunk名字。如果有多个小的路由组件,又不想每个都单独请求,此时可以通过指定webpackChunkName
,将多个路由组件打包到同一个chunk中。
component: () => import(/* webpackChunkName: "chunk-home" */ "@/pages/app/home"),
最后
以上内容为后面总结,可能存在错误和不足,欢迎指出!如果你觉得有用的话可以帮忙点个star(项目地址),另外借此求职一份前端工作,本人19年普通本科毕业,在一家外企子公司做了一年半(非前端,目前已离职),目前想转前端。非零基础,从大学就开始接触,技术栈vue(毕设就用的vue),基础还可以,具备一定的英语读写能力和自学能力,详情可看我博客:www.juetan.cn/65