目录
main.js入口文件
前端页面初始化
package.json配置文件
vue-cli:.env模式和环境变量配置
vue-cli三大核心构件
CLI 服务与npm scripts
vue-cli-service命令与.env模式配置文件
.env模式和环境变量配置
vue.config.js:项目启动-自动打开默认浏览器的两种配置方式
方式1:vue-cli-service命令参数--open
方式2:vue.config.js配置文件-devServer
vue.config.js配置文件与vue-cli
方式1:CommonJS模块导出
方式2:defineConfig 帮手函数
vue-router全局导航守卫配置
Vue-Router
Vue-Router:内置组件
router-view组件与初始页面渲染流程解析
vue-Router路由的配置信息
Vue-Router全局导航守卫配置
全局前置导航守卫的注册/配置
全局前置导航守卫的控制逻辑图解
若依前后端环境在本地部署完毕之后,成功启动前端Vue项目之后,会自动打开默认浏览器,并看到如下的初始登录页面,展示一个登录表单+验证码图像。
main.js入口文件
如果打开前端项目的main.js入口文件查看,会看到内部做了一些Scss全局变量导入、全局样式index.scss引入、自定义指令导入directive.js、自定义插件导入plugins.js、文件下载download等全局方法挂载、分页组件\富文本组件\图片上传组件等自定义组件的全局注册(Vue.component)、element-ui第三方UI组件库注册,以及Vue实例创建等诸多项初始化工作。
这是一个比较大的话题,可能需要花费不少时间去研究这些实现细节。在之后的篇章中将一点点去进行剖析。
前端页面初始化
以上谈到的关于main.js中的所做的初始化工作,是比较直观的。当然,若依的Vue前端项目也执行了一些较为隐蔽的、不易发觉的初始化工作,这些内容,即:后者是此刻我比较关心的、也是接下来要基于个人的粗浅理解、着重进行谈论的内容。
主要涉及的内容包括:vue.config.js文件配置、.env模式和环境变量配置、vue-router全局导航守卫配置、vue-router路由配置简介。由于整个前端项目是一个比较系统性的工程,各部分相互穿插,因此,以上涉及的内容可能不会特别深入到某个细节的知识点,而只是和当前篇章《前端页面初始化》相关的内容,至于余下的,将在之后的篇章中逐步深入。
package.json配置文件
当我拿到一个前端项目时,我会比较习惯性的先去查看整个目录结构,接着就是去看一看package.json配置文件,因为它至少可以告诉我们项目的环境依赖、启动/打包/测试等相关的npm脚本命令等基础信息。
package.json文件内容如下。当然,初次见面,我们还是优先关注scripts节点下的内容,
{
"name": "ruoyi",
"version": "3.8.4",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"repository": {
"type": "git",
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
},
"dependencies": {
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.24.0",
"clipboard": "2.0.8",
"core-js": "3.25.3",
"echarts": "5.4.0",
"element-ui": "2.15.10",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"quill": "1.3.7",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-meta": "2.4.0",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-plugin-eslint": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-eslint": "10.1.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "4.1.0",
"compression-webpack-plugin": "5.0.2",
"connect": "3.6.6",
"eslint": "7.15.0",
"eslint-plugin-vue": "7.2.0",
"lint-staged": "10.5.3",
"runjs": "4.4.2",
"sass": "1.32.13",
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-template-compiler": "2.6.12"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}
从中可以了解到如下内容,
# 项目开发环境下的启动命令
npm run dev
# 项目生产环境下的打包命令
npm run build:prod
# 项目Staging 模式下的打包命令
npm run build:stage
# 项目的打包预览命令-【即:先执行打包命令,然后在本地开启一个端口号,执行项目部署操作,通过localhost主机+端口号,可以看到项目打包之后的页面展示效果】
npm run preview
# ESlint自动修复错误的命令-【执行此命令后,在执行npm run dev启动项目,可防止ESLint检查报错】
npm run lint
vue-cli:.env模式和环境变量配置
以上,解析package.json文件,我们可以拿到项目在开发环境下的启动命令:`npm run dev`。那么,这个脚本命令是如何做到开发环境、生产环境的区分呢?
这就到聊到vue-cli脚手架中.env配置文件相关的内容了。按照传统惯例,我们要先使用如下命令进行本地的全局安装,才能进行后续的使用环节。
npm install -g @vue/cli
# OR
yarn global add @vue/cli
vue-cli三大核心构件
先来基于我自己的理解来聊一下vue-cli吧。
CLI-命令行界面构件
vue-cli,俗称“Vue脚手架”,通常,我们创建一个vue项目,很少会自己基于webpack和webpack-server去手动搭建项目框架,而是会使用如下命令获得一个vue项目模板。
vue create project_name
其实,这个命令就是vue-cli的CLI核心构件提供的终端里的 vue
命令,用于快速创建一个Vue项目模板。
CLI服务-命令行界面服务构件
基于vue-cli命令创建的vue项目,一般都会在项目的根目录下有一个node_modules文件夹,用来存放当前项目相关的所有依赖项或者说是第三方JavaScript开发库。我们注意到,若依的前端项目下就有node_modules文件夹。
其实,这个文件夹下还包括了@vue/cli-service插件依赖项,这个插件就是保证package.json文件中scripts节点下一些npm run XXX命令正常运行的关键。我们来看一下vue-cli官网的介绍,
细致看package.json中定义的一些命令,键值对的值,如前3个都包含vue-cli-service相关的命令,这些命令可以说都是依赖于@vue/cli-service插件才能生效的。
CLI插件-命令行界面插件构件
有关CLI插件,我们最常见的就是ESLint,当前还有一些其它的插件,但是无论如何,这些插件都是以@vue/cli-plugin开头的,我们可以在package.json文件中的devDependencies节点下看到若依前端框架中涉及到的CLI插件,分别是:babel和ESLint插件。
至于这些插件有什么用?插件可以修改 webpack 的内部配置,也可以向 vue-cli-service
注入命令。
CLI 服务与npm scripts
我们还是回归到“.env模式和环境变量配置”这个核心问题上,但是,关于环境区分,我们最直观的区分形式,是以npm scritps脚本命令的方式去进行区分的。那么,这其中的原理是什么呢?
我们还要继续聊一聊vue-cli三大核心构件之——CLI服务相关的内容。
以上内容中,我们谈到,在任何Vue项目根目录下的node_modules下,会自动安装一个叫做“@vue/cli-service”的第三方依赖,这个依赖项提供了名为 vue-cli-service
的命令,开发者可以在 npm scripts 中以 vue-cli-service
、或者从终端中以 ./node_modules/.bin/vue-cli-service
访问这个命令。
而在执行这个vue-cli-service命令时,还可以附加一些参数,例如:上图中的build:satge中就包含了一个mode参数,用于区分项目中预先配置好的环境变量。
以vue-cli-service serve启动项目的命令为例,详细的参数信息如下,
用法:vue-cli-service serve [options] [entry]
选项:
--open 在服务器启动时打开浏览器
--copy 在服务器启动时将 URL 复制到剪切版
--mode 指定环境模式 (默认值:development)
--host 指定 host (默认值:0.0.0.0)
--port 指定 port (默认值:8080)
--https 使用 https (默认值:false)
而vue-cli-service命令在执行时,会基于内部的webpack-dev-server,启动一个开发服务器并附带开箱即用的模块热重载 (Hot-Module-Replacement)。除了通过命令行参数,你也可以使用 vue.config.js
里的 devServer 字段配置开发服务器,当然,这是下一部分我们要讨论的内容。
再以vue-cli-service build打包项目的命令为例,详细的参数信息如下,
用法:vue-cli-service build [options] [entry|pattern]
选项:
--mode 指定环境模式 (默认值:production)
--dest 指定输出目录 (默认值:dist)
--modern 面向现代浏览器带自动回退地构建应用
--target app | lib | wc | wc-async (默认值:app)
--name 库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)
--no-clean 在构建项目之前不清除目标目录的内容
--report 生成 report.html 以帮助分析包内容
--report-json 生成 report.json 以帮助分析包内容
--watch 监听文件变化
vue-cli-service build
会在 dist/
目录产生一个可用于生产环境的包,带有 JS/CSS/HTML 的压缩,和为更好的缓存而做的自动的 vendor chunk splitting。它的 chunk manifest 会内联在 HTML 里。类似于vue-cli-service serve项目启动命令,项目打包的相关配置也可以在vue.config.js文件之中进行指定。
vue-cli-service命令与.env模式配置文件
注意到上述与项目启动、打包相关的 vue-cli-service
命令,都含有一个'--mode'参数,这个参数其实就是用来指定环境模式的,其中:vue-cli-service serve命令的--mode参数默认是开发环境,对应的值是:development,通常是在项目开发过程中使用的;当然,也还有生产环境production,通常是在项目打包/线上环境使用的。
具体的使用,例如:
上图的命令,默认等价于,
"scripts": {
"dev": "vue-cli-service serve --mode development",
"build:prod": "vue-cli-service build --mode production",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src"
},
.env模式和环境变量配置
进一步思考,这里的production、development是如何生效的呢?其实,仔细观察,会发现,若依前端项目根目录下,还包含了三个以.env开头的文件,例如:.env.development、.env.production、.env.staging,而上述命令中的--mode参数后面的值,就是与文件名中.env.xxx中的xxx部分相对应的,例如:执行npm run dev,以vue-cli-service serve --mode development命令启动项目时,在项目中的任何地方,待用process.env去查询环境变量时,匹配的就是.env.development文件中定义的信息。
例如,.env.development开发环境变量配置文件中定义的信息如下,那么在开发环境下,就可以通过process.env.VUE_APP_TITLE的方式,拿到页面标题的内容,其它的以此类推。
# 页面标题
VUE_APP_TITLE = 若依管理系统
# 开发环境配置
ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
.env文件中的信息称之为“环境变量”,通常地,配置一个.env.xxx文件的操作,也叫作:配置环境变量。但是,环境变量的名称定义并不是随意可写的,而是要遵循一定的规范。如下图所示,
但是,请注意:不要在你的应用程序中存储任何机密信息(例如私有 API 密钥)!环境变量会随着构建打包嵌入到输出代码,意味着任何人都有机会能够看到它。
请注意,只有 NODE_ENV
,BASE_URL
和以 VUE_APP_
开头的变量将通过 webpack.DefinePlugin
静态地嵌入到客户端侧的代码中。这意味着,只有遵循此规范的环境变量在一个Vue前端应用中才能生效。
vue.config.js:项目启动-自动打开默认浏览器的两种配置方式
接下来,我们终于可以执行'npm run dev'命令,间接驱动“vue-cli-service serve --mode development”命令,来启动若依前端项目了。
等待若干秒时长,项目被启动,并自动打开如下页面。
此刻我们当然应该高兴,因为这意味着若依的前后端项目我们至少已经在本地初步地部署成功了。但是,随之而来的是困惑:Vue项目是如何做到项目启动成功之后,自动打开默认浏览器,展示初始页面的呢?
方式1:vue-cli-service命令参数--open
还记得之前,我们在讨论vue-cli-service serve命令时,它包含着一个open参数吗?
好的,兄弟,我们来配置一下试一试,如下图所示,经过实际验证,是可以生效的。
方式2:vue.config.js配置文件-devServer
另一点内容,我们在谈论 vue-cli-service serve命令时,也提到,它的配置参数,也可以在vue.config.js配置文件中,对devServer字段进行配置,也是可行的。
当然,若依前端项目默认就是这样配置的。
vue.config.js配置文件与vue-cli
此刻,相比深入了解vue.config.js的具体配置项,我想你一定更想了解,这里的vue.config.js配置文件是如何生效的?
如果你了解过Java后端框架SpringBoot,那么,可以清晰地认定:项目启动时,会自动去加载resources目录下的application.yml配置文件。一样的道理,vue.config.js配置文件就是Vue前端项目的配置文件,在项目启动时会自动被加载、解析。
那么,它是如何生效的呢?还记得@vue/cli-service依赖项吗,这个项目根目录下的vue.config.js配置文件,在启动时,就是被@vue/cli-service依赖项自动加载的。当然,也可以在package.json文件中,新建一个vue结点,进行同类型的配置。但是,通常的我们更推荐在vue.config.js文件中进行配置。
那么,标准的vue.config.js配置文件该如何书写呢?
一般有两种书写方式,
方式1:CommonJS模块导出
示例文件内容如下,
// vue.config.js
/**
* @type {import('@vue/cli-service').ProjectOptions}
*/
module.exports = {
// 选项...
}
方式2:defineConfig
帮手函数
示例文件内容如下,
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// 选项
})
详细的配置项,可以查看Vue-cli官网-配置参考。
vue-router全局导航守卫配置
基本了解Vue项目的package.json、.env模式和环境变量区分、vue.config.js配置文件、vue-cli与项目基本启动/打包/规范化命令等内容之后,新的问题来了:若依的Vue前端项目启动之后,为什么是下面这个登录页面,这个路由控制是如何实现的呢?
结合若依的前端项目框架,这就要谈到Vue-Router路由控制相关、全局导航守卫相关的内容了 。
Vue-Router
Vue-Router的介绍,借用Vue Router官网的截图,总结为一句话就是:Vue Router为Vue前端项目提供了路由配置、路由跳转/导航控制相关的功能。
如果你了解过后端项目的开发流程,那么这里的Vue Router,也可以理解为类似于Java后端Controller控制器的页面重定向、请求拦截等一类操作。
Vue-Router官方网站提供了如下两种安装Vue Router依赖库的方式,
# npm安装命令
npm install vue-router@4
# yarn安装命令
yarn add vue-router@4
Vue-Router:内置组件
Vue-Router提供了两个强大的内置组件:<router-link/>、<router-view/>,其中:
①<router-link/>:类似于HTML中的<a></a>标签,但是,相比a标签,<router-link/>可以不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
②<router-view/>:router-view
将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。
router-view组件与初始页面渲染流程解析
我们可以查看若依前端项目中App.vue组件,在<template></template>内部,就包含了<router-view/>组件,
<template>
<div id="app">
<router-view />
<theme-picker />
</div>
</template>
这里的<router-view/>组件就是用于显示默认路由"/"或者""对应的组件的,我们可以通过vue-devTools开发工具查看页面组件的组织结构,注意到:在id为app的组件根节点下,<App/>父组件下的<router-viewer/>组件对应的就是Login.vue登录页面组件,因此,在执行npm run dev(vue-cli-service serve --mode development)项目启动之后,通过@vue/cli-service加载vue.config.js文件,解析到devServer.open=true,自动打开浏览器;然后通过路由匹配,将<App/>组价内部的<router-view/>组件替换为默认路由对应的Login.vue登录组件。
vue-Router路由的配置信息
接下来,我们可能会疑惑,这里的vue-Router路由的配置信息在哪里呢?或者说,Vue-Router的配置信息是如何生效的呢?
通常地,Vue项目在创建之后,如果选择了vue-Router依赖项,那么,就会自动在项目根目录下创建一个router文件夹,里面包含了一个index.js文件,这个index.js文件,就是用来对Vue-Router的路由规则进行配置的。
我们查看若依前端项目的这个文件,会发现最终该文件基于ES6的export的语法规则,导出了一个Router对象,而这个Router类是预先调用Vue.use()方法注册完成的。
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
...
export default new Router({
mode: 'history', // 去掉url中的#
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
注意到,这里的Router的routes参数就是关键,该参数的内容如下,
// 公共路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect')
}
]
},
{
path: '/login',
component: () => import('@/views/login'),
hidden: true
},
{
path: '/register',
component: () => import('@/views/register'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error/401'),
hidden: true
},
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
{
path: '/user',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'profile',
component: () => import('@/views/system/user/profile/index'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
}
]
}
]
仔细观察,我们找到了默认路由路径""对应的组件,竟然是:Index.vue主页组件,但是,实际上显示的是Login.vue登录组件,这又是如何实现的呢?
Vue-Router全局导航守卫配置
我们讲到:默认路由路径""对应的组件,竟然是:Index.vue主页组件,但是,实际上显示的是Login.vue登录组件,这种逻辑其实是通过Vue-Router的全局导航守卫实现的。
全局导航守卫,就类似于Java后端的Filter过滤器,例如:在用户还未登陆时,可以将一些未携带Token信息的HTTP请求在请求链中提前过滤掉,实现某种主页访问的权限控制。
这里基于Vue-Router的全局导航守卫,是一样的实现思路。由于全局导航守卫在任意的导航路由被触发时,都会先去执行导航守卫中预定义的逻辑,因此,也可以实现类似于后端基于过滤器的首页访问控制的效果。
全局前置导航守卫的注册/配置
可以使用 router.beforeEach
注册一个全局前置守卫:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
至于若依前端项目中的全局导航守卫配置,是在项目根目录下的permission.js文件中实现的,具体代码如下,
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
//路由白名单-用户未登录时可以随意访问的路由
const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
/**
* 全局前置守卫-router.beforeEach
* 判断用户是否有权限进入主页-获取存储在Cookie中的token值
* 有token-进入主页
* 无token-进入登录页重新登录
*/
router.beforeEach((to, from, next) => {
console.log(to)
console.log(from)
console.log(next)
debugger;
NProgress.start()
//从Cookie中获取Token-判断用户是否已经登录
if (getToken()) {
//如果已经登录
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token-用户未登录
if (whiteList.indexOf(to.path) !== -1) {
// 如果在免登录白名单,直接进入目标路由对应的页面
next()
} else {
//如果不在登录白名单中时,自动跳转到Login登录组件进行登录
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
//全局后置钩子-不会接受 next 函数也不会改变导航本身
router.afterEach(() => {
NProgress.done()
})
全局前置导航守卫的控制逻辑图解
下图为全局前置导航守卫的作用机制,
PS:有关获取Token成功之后,进行路由表动态生成的逻辑,在之后的篇章中再进行梳理。
在下一篇章中,将介绍Login.vue登录组件的初始化流程以及验证码图片的实现逻辑。