Vue框架
Vue3路由基础— vue router
前面已经介绍过了组件的部分,包括动态组件、插槽slot等,其中的一个关键就是数据的传递,要么通过自定义事件,要么通过ref引用,还有一个重要的就是全局注册axios,在组件的声明周期函数created接收页面的初始数据;自定义指令也可以简化一些固定的操作;作用域插槽也可以向使用者传递数据
简单了解了vue中的组件使用之后,接下来就是vue-router路由的分享了
前端路由
路由就是对应关系;【后端中Servlet的路径绑定】,路由分为前端路由和后端路由;后端路由指的是: 请求方式,请求地址和function处理函数之间的对应关系
在node.js中,express路由的使用例子
const express = require('express')
const router = express.Router()
router.get('地址',function(){})
router.post('路径',funtion(){})
module.exports = router
相对应的,之前提过SPA指的是单页面应用程序;所有的组件的展示和切换都是在者唯一一个页面中完成,这个时候,在SPA项目中,不同的组件的切换需要通过前端路由来实现
前端路由就是 Hash地址与组件之间的对应关系; 比如一个简单的手机应用页面,点击下面的图标切换不同的内容,这就设计到组件的切换,需要使用路由【地址栏中以#开始的就是Hash地址】
前端路由的工作方式
- 用户点击了页面上的路由链接【就是各种图标按钮】
- 导致URL地址栏的Hash值发生了拜年话
- 前端路由监听Hash地址的变化
- 前端路由把当前Hash地址对应的组件渲染到浏览器中【Hash地址对应绑的是组件】
//除了#,还有其他的方式,比如通过参数直接从后端获取不同的页面
https://blog.csdn.net/a23452?type=lately ;比如CSDN这里就是通过type参数的不同值获取个人页面的不同页面
可以看到这里通过#/home绑定了home组件,当地址栏的地址变化,就会自动调用组件
实现简易的前端路由 created中window.onhashchange
export default {
name: 'App',
components: {
LifeCircle,
CosumKid,
SlotTest,
MyHome
},
- 之前分享动态组建的时候就提过,使用keep-alive避免销毁,同时component标签的is属性动态绑定名称;这里路由的实现就是依赖的component动态组件
<component :is='conName'></component>
- 在组件的结构中声明了几个a超链接,通过点击超链接,切换不同Hash值
<a href="#/lifeCircle">LfCir</a> <a href="#/counsumeKid">CosuKid</a>
这里就是一个相对路径,这是资源路径,就是当前的URL加上后面的部分就是绝对路径
- 在created生命周期函数中监听浏览器地址栏中Hash地址的变化,动态切换要展示的组件的名称
created() {
window.onhashchange = () => {
switch(location.hash) {
case "#/lifeCircle":
this.conName = 'LifeCircle'
break
case "#/counsumeKid":
this.conName = 'CosumKid'
}
}
}
所以前端路由就是基于动态组件加上Hash实现的,这里需要在Created函数中监听,使用的就是weindow.onhashchange,同时获取地址栏的Hash就是通过location.hash 【这里是原生的js的使劲按处理,hashchange就是一个地址的change事件,为onhanshchange属性绑定事件处理函数】
vue-router基本使用 router-link的hash地址不需要#
vue-router是vue.js给出的官方的路由解决方案,只能结合vue项目使用,简单管理SPA项目的组件的切换
【vue-router3.x只能结合vue2使用,vue4.x只能结合vue3使用】
使用命令npm i vue-router@next -S 【axios和vue-router都-S】 可以在package.json中查看版本号: “vue-router”: “^4.0.13”
就是定义几个需要切换的组件,这里就是用上面的例子中的LifeCirecle等组件
- 声明路由衔接和占位符
router-link标签的to属性绑定路径 ,router-view 直接放上就可以声明占位符
vue-router中就不建议再使用普通的超链接标签a,而是使用< router-link>标签来声明路由链接,【这里会自动帮助渲染一个#】
并且使用《 router-view>声明路由占位符【这两个标签都是router提供的】,这里就相当于是组件的填充的位置,和之前的手动的component的功能相同,并且不需要手动进行绑定了
<router-link to="/lifeCircle">LifCir</router-link>
<router-link to="/counsumeKid">ConKis</router-link>
<router-view></router-view>
-
创建路由模块; 创建路由模块需要按需导入并且需要有一个实例的路由对象
- 从vue-router按需导入两个方法
- 导入需要使用路由控制的组件
- 创建路由的实例对象
- 向外共享实例对象
- 再main.js导入挂载路由模块 【注意要导入组件,不然无法使用】 ==通过spa_app.use(router)==挂载 还有注意路由规则数组名称为routes,不是routers:happy: ----- 写错了就报错property undefined
import {createRouter,createWebHashHistory} from 'vue-router'
import LifeCircle from './components/life-circle.vue'
import CosumKid from './components/consum-kid.vue'
const router = createRouter({
history: createWebHashHistory(),
routes:[
{path:'/lifeCircle',component:LifeCircle},
{path: '/counsumeKid',componet:CosumKid}
]
})
export default router
-----------------在main.js中--------------------------
import router from './router.js'
spa_app.use(router)
-
导入并挂载路由模块
所以使用vue-router的关键: 首先就是使用router-link声明链接【用法和a一样,to属性,并且不需要#】,然后使用router-view占位【用处和component一样】,之后就是创建路由模块router.js — 主要就是创建一个router实例对象【对象中声明路由规则和路由模式】挂载到main.js中即可,use方法
vue-router的高级用法
路由重定向
【后端servlet的重定向和请求转发就是一次请求,访问多个,请求转发是内部,重定向相当于重新发起一次get请求】,路由重定向: 用户在访问地址A的时候,强制让用户跳转到地址C,从而展示特定的组件页面 — 直接通过路由规则的redirect属性,就可以指定路由地址,实现重定向
和这两个结点平级的就是redirect, redirct后面就是访问path重定向的地址
routes: [
{path: '/lifeCircle',component: LifeCircle},
{path: '/counsumeKid',component: CosumKid},
{path: '/',redirect: '/lifeCircle'}
]
这里就是如果访问相对路径为’/’,那么就会重定向到’/lifeCirecle’,也就是打开LifeCircle组件【在router-view位置】
路由高亮
router-link的链接如和设置高亮的效果,就是如果链接激活就是高亮的效果;可以通过两种方式将激活的路由链接进行高亮显示:
使用默认的高亮class类
被激活的路由链接,会默认应用一个router-link-active的类名,可以使用这个类名的选择器,为其设置高亮的效果
//在index.css中设置全局的样式,重写router-link-active的样式
.router-link-active{
background-color: red;
color: white;
font-weight: bold;
}
路由高亮就是要让用户清晰看到当前展示的是哪个地址的页面,就类似于QQ页面标签点击下面的变大的功能【也就是路由的高亮】
使用自定义的高亮class类
在创建路由实例对象的时候,可以基于linkActiveClass属性,自定义路由链接被激活时应用的类名
const router = createRouter({
history: createWebHashHistory(),
linkActiveClass: 'router-active',
routes: [
{path: '/lifeCircle',component: LifeCircle},
{path: '/counsumeKid',component: CosumKid},
{path: '/',redirect: '/lifeCircle'}
]
})
然后再在index.css中使用自定义的类名为激活的link添加样式
.router-active{
background-color: red;
color: white;
font-weight: bold;
}
嵌套路由
通过路由实现组件的嵌套展示;比如在App中通过link展示几个子组件,在子组件中也可以通过link在嵌套,并且再次通过router-view进行占位
比如这里上面的声明的两个子组件LifeCirecle和CounsumKid;这里在ConsumKid中嵌套声明两个子组件SlotTest和MyHome
//第一步就是在CounmKid中声明子路由链接和占位符
<template>
<div>
ConsumKid子组件<br>
X * 2 + 1的结果为 :<span ref='myspan' :style="{'background-color':color}">{{count * 2 + 1}}</span>
<hr>
<!-- 声明子路由链接 -->
<router-link to="/counsumeKid/slotTest">SlotT</router-link>
<router-link to="/counsumeKid/myHome">myHome</router-link>
<!-- 声明子路由占位符 -->
<router-view></router-view>
</div>
</template>
这个时候点击界面就可以看到两个链接,控制台会给出warning, [Vue Router warn]: No match found for location with path “/counsumeKid/myHome” 因为这个时候只有路由链接,没有路由规则
- 在父路由规则中,通过
children属性嵌套声明子路由规则
在router.js中,导入需要渲染的两个组件,使用属性声明子路由规则
const router = createRouter({
history: createWebHashHistory(),
linkActiveClass: 'router-active',
routes: [
{path: '/lifeCircle',component: LifeCircle},
{
path: '/counsumeKid',
component: CosumKid,
children: [
{path:'/counsumeKid/slotTest',component: SlotTest},
{path:'/counsumeKid/myHome',component: MyHome}
]
},
{path: '/',redirect: '/lifeCircle'}
]
})
这里子路径Hash地址,除了上面的写法,还可以使用资源路径的方式,比如直接slotTest;上面的是前台路径
嵌套路由的重定向
嵌套路由的重定向和之前的重定向没有太大的差别,就是把子规则中重新定义一个规则
routes: [
{path: '/lifeCircle',component: LifeCircle},
{
path: '/counsumeKid',
component: CosumKid,
children: [
{path:'/counsumeKid/slotTest',component: SlotTest},
{path:'/counsumeKid/myHome',component: MyHome},
{path:'/',redirect:'myhome'}
]
},
{path: '/',redirect: '/lifeCircle'}
]
动态路由匹配
比如像C站注册了很多博主,每一个博主对应一个页面,如果按照之前的普通的路由规则的写法,那这个工作量太大了,复用性差,不符合软件开发的思想;这个时候,就应该使用动态的路由匹配
动态路由指的是:把Hash地址中可变的部分定义为参数项,提高路由规则的复用性 ,在vue-router中使用冒号: 来定义参数项
{path:'/movie/1',component: Movie},
{path:'/movie/2',component: Movie},
{path:'/movie/3',component: Movie},
-----------合并为一条--------------
{path:'/movie/:id',component:Movie}
这里的id就是动态的,访问不同的页面就可以获得不同的id值 ------点击不同的路由链接匹配的都是Movie组件
{path:'/counsumeKid/myHome/:id',component: MyHome
{path:'/',redirect:'myhome/:id'}
<router-link to="/counsumeKid/myHome/1">myHome1</router-link>
<router-link to="/counsumeKid/myHome/2">myHome2</router-link>
动态路由匹配获取参数值id----- $route.params
访问的都是同类型的组件,但是还是会有差异性,就是通过这个id来区分,比如博主的id,获取之后查询数据库然后根据不同的数据来渲染页面
通过动态路由匹配的方式渲染的组件中,可以使用$route.params对象访问到动态匹配的参数值
$route.params.XXX --- 取得XXX参数值
这里可以直接在子组件中使用插值表达式
<p>MyHome组件 ---- {{count}} {{$route.params.id}}</p>
这样就获取到了参数的值,切换时就会动态改变
简化传参 — props接收路由参数
为了简化路由参数的获取形式,允许props传参
- 在定义路由规则时,加上结点props:true; 表示接收props传参,就可以在组件中通过props获取
- 直接使用props接收参数 参数名就是XXX
{path:'/counsumeKid/myHome/:id',component: MyHome,props:true},
直接使用props接收Hash地址的参数【名称一致】
export default {
name:'MyHome',
data() {
return {
count: 0,
}
},
props:['id'],
}
这样就可以便捷使用id这个prop, 比之前的$route.params方便许多
编程式导航
通过调用API实现导航的方式,叫做编程式导航,而通过点击链接实现导航的方式 — 声明式导航
普通网页中点击a链接,vue项目点击router-link都属于声明式导航
普通网页中点击location.href跳转到新页面的方式,属于编程式导航
vue-router编程式导航API
vue-router提供了很多编程式导航API,最常用的两个API分别是:
- this.$router.push(“hash地址”) ----- 跳转到指定的Hash地址,展示对应的组件【和之前的获取参数的¥route.id有区别】
<hr>
<button type="button" @click="gotoMyhome(2)">导航到myHome2界面</button><br>
methods:{
setColor() { //在子组件中定义了一个set方法,在另一个组件中点击按钮就可以执行该方法,那么就要使用组件的引用
this.$refs.myspan.style.backgroundColor = 'pink'
},
gotoMyhome(id) {
this.$router.push('/counsumeKid/myHome/' + id)
} //这里不能使用资源路径,必须使用/开始的相对路径
}
- this.$router.go(数值 n) ------ 实现导航历史的前进、后退
这里传递值和之前的原生JS的是相同的,-1就是回退到上一个界面
<button type="button" class="btn btn-danger" @click="goBack">后退</button>
methods:{
goBack() {
this.$router.go(-1)
}
}
这里go(-1)就是后退到上一个页面中
命名路由 name指定跳转的路由,params指定路由参数
通过name属性为路由规则定义名称 ----- 就是命名路由; 方式就是在路由规则对象中,使用name结点,和path,component,props、redirect等结点平级
routes: [
{path: '/lifeCircle',component: LifeCircle},
{
path: '/counsumeKid',
component: CosumKid,
children: [
{path:'/counsumeKid/slotTest',component: SlotTest},
{path:'/counsumeKid/myHome/:id',component: MyHome,props:true},
{path:'/',redirect:'myhome/:id'}
],
props: true,
name: 'conskid'
},
{path: '/',redirect: '/lifeCircle'}
]
需要注意的是,命名路由的name具有唯一性,不能重复
这样对路由规则进行命名之后,就可以在router-link中进行使用,这里直接在to属性绑定的对象中进行属性的绑定
<router-link to="/counsumeKid">ConKis</router-link>
routes: [
{path: '/lifeCircle',component: LifeCircle},
// {path: '/counsumeKid',component: CosumKid}, 这里使用children声明
{
path: '/counsumeKid',
之前这里就是通过的path来进行对应的,使用命名路由之后直接通过name进行匹配
--------------使用命名路由------------
<router-link :to="{name:'conskid',params:{id:3}}">ConKis</router-link>
这里携带的参数也是一个JSON对象; 因为这里需要识别后面的对象,所以这里属于属性绑定,需要将:to, 不然就会当作字符串处理
对于编程式导航,上面分析过,常用的就是this.$router.push(‘Hash地址’),或者使用this.¥router.go(); 对于编程式导航,也可以使用JSON对象使用命名路由;name为路由规则名,params为携带的参数
gotoMyhome(tid) {
console.log(1)
this.$router.push({name:'myHo',params:{id:tid}})
}
只要提供名称就可以匹配路由规则;和之前的非命名的路由的path匹配的效果是相同的;主要是可以避免path太长的情况
导航守卫
导航收尾可以控制路由的访问权限,防止一些恶意登录
从示意图可以看出来,导航守卫可以控制用户访问的界面
声明全局导航守卫 router.beforeEach(守卫方法)
这里的作用就和之前的SpringMvc的拦截器差不多,实际上,很多请求前台就应该拦截,不能放到后台增大工作量;全局导航守卫会拦截每个路由规则,对每一个路由进行访问权限的控制
调用路由实例对象的beforeEach函数,声明全局导航守卫
const router = createRouter({......})
router.beforeEach(fn)
这里在router.js中实现一下
router.beforeEach(() => {
console.log("进行了拦截处理")
})
export default router
这样每次进行组件的切换(路由的切换)就都会触发这个全局的导航守卫
守卫方法的形参to、from、next
全局导航守卫会拦截每一个每个路由规则,to参数代表的是目标路由对象,from代表的当前的导航栏中的路由对象;next代表一个函数,表示执行
router.beforeEach((to) => {
console.log("进行了拦截处理")
console.log(to)
})
---------------这里就会打印要访问的路由对象-------------
{fullPath: '/counsumeKid/slotTest', path: '/counsumeKid/slotTest', query: {…}, hash: '', name: undefined, …}
这里的路由对象有fullpath,path,还有query,hash,name等属性
router.beforeEach((to,from) => {
console.log("进行了拦截处理")
console.log(from)
})
----------------打印当前页面【要离开】的路由对象---------------
{path: '/', name: undefined, params: {…}, query: {…}, hash: '', …}
这里进去的时候就打印的不是/…, 而是/
对于next形参,如果守卫方法中不使用next形参,那么就会默认允许用户访问每一个路由,如果声明了next参数,就必须调用next()函数,不然不允许访问任何一个路由
router.beforeEach((to,from,next) => {
console.log("进行了拦截处理")
console.log(to)
})
比如这里就是点击链接,不会显示组件内容,但是导航守卫方法会执行
next函数3中调用方式
router.beforeEach((to,from,next) => {
if(to.path === '/lifeCircle') {
next()
}
else{
next()
}
})
这里就是不管访问的是哪一个页面,都会直接放行,拦截不起作用
- next(false) 拦截,强制停留在当前页面,不放行
router.beforeEach((to,from,next) => {
if(to.path === '/lifeCircle') {
alert("没有权限访问!")
next(false)
}
else{
next()
}
})
这里处理之后,访问Hash地址为/lifeCircle,就不能访问,其他的路径正常访问;
- next(‘hash地址’) 强制跳转到某个固定的页面
router.beforeEach((to,from,next) => {
if(to.path === '/lifeCircle') {
next('/counsumeKid/slotTest')
}
else{
next()
}
})
这里当点击/lifeCircle链接的时候,就会自动跳转到’/counsumeKid/slotTest’这个组件展示
结合token控制后台访问的权限
token和session有区别,当服务器不同的时候,session就不能正常使用了,但是原理都是一样的,都是令牌机制
router.beforeEach((to,from,next) => {
const token = localStorage.getItem('token')
if(to.path === '/lifeCircle' && !token) {
next('/counsumeKid/slotTest')
}
else{
next()
}
})
这里因为Application中的localstorage中没有token,所以会强制跳转到指定页面/counsumeKid/slotTest, 这里使用浏览器的在本地存储中手动添加token,就可以进行访问【手动添加token:xxx】,就可以正常跳转显示lifeCirecle组件
后台管理案例
这里就是简单的实现登录,然后呈现的页面就类似之前的SSM的后台系统这种简单的导航页面demo
首先就是初始化项目,使用那一连串的命令即可,同时安装axios和less还有vue-router
PS D:\Vueprogramm> npm init vite-app router-demo
Scaffolding project in D:\Vueprogramm\router-demo...
Done. Now run:
cd router-demo
npm install (or `yarn`)
npm run dev (or `yarn dev`)
PS D:\Vueprogramm> cd router-demo
PS D:\Vueprogramm\router-demo> npm i
added 285 packages in 7s
PS D:\Vueprogramm\router-demo> npm i less -D
added 17 packages in 1s
PS D:\Vueprogramm\router-demo> npm i vue-router@next -S
added 2 packages in 1s
PS D:\Vueprogramm\router-demo> npm i axios -S
added 1 package in 1s
PS D:\Vueprogramm\router-demo> npm run dev
> router-demo@0.0.0 dev
> vite
[vite] Optimizable dependencies detected:
axios, vue, vue-router
Dev server running at:
初始化项目
这里就删除项目初始的组件,同时删除相关的css的内容
- index.css 设置全局的字体的大小,同时设置全局的高度height
:root {
font-size: 13px;
}
html,
body,
#app {
height: 100%;
}
- main.js 导入bootstrap的css文件,同时要配置vue-router
import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/bootstrap.css'
import './index.css'
import router from './router.js'
const spa_app = createApp(App)
spa_app.use(router)
spa_app.mount('#app')
import { createRouter,createWebHashHistory } from "vue-router"
const router = createRouter({
history: createWebHashHistory(),
routes:[
]
})
router.beforeEach((to,from) =>{
})
export default router
封装MyLogin组件并进行路由配置
这个组件展示的是登录的界面,最开始登陆时显示的登录密码等【直接在bootstrap上取了部分样式】
<template>
<div class="login-container">
<div class="login-box">
<!-- 头像区域 -->
<div class="text-center avatar-box">
<img src="../assets/logoin.jpg" class="img-thumbnail avatar" alt="">
</div>
<!-- 表单区域 -->
<div class="form-login p-4">
<!-- 登录名称 -->
<div class="form-group form-inline">
<label for="username">登录名称</label>
<input type="text" class="form-control ml-2" id="username" placeholder="请输入登录名称" autocomplete="off">
</div>
<!-- 登录密码 -->
<div class="form-group form-inline">
<label for="password">登录密码</label>
<input type="password" class="form-control ml-2" id="password" placeholder="请输入登录密码">
</div>
<!-- 登录和重置按钮 -->
<div class="form-group form-inline d-flex justify-content-end">
<button type="button" class="btn btn-secondary mr-2">重置</button>
<button type="button" class="btn btn-primary">登录</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MyLogin',
}
</script>
<style lang="less" scoped>
.login-container {
background-color: #35495e;
height: 100%;
.login-box {
width: 400px;
height: 250px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
.form-login {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
}
}
}
.form-control {
flex: 1;
}
.avatar-box {
position: absolute;
width: 100%;
top: -65px;
left: 0;
.avatar {
width: 120px;
height: 120px;
border-radius: 50% !important;
box-shadow: 0 0 6px #efefef;
}
}
</style>
然后就是展示Login组件,在router.js中导入模块,并封装路由规则,这里使用重定向,当访问项目时默认跳转到该位置;类似之前的欢迎页面
import Login from './components/MyLogin.vue'
const router = createRouter({
history: createWebHashHistory(),
routes:[
{path:'/login',component: Login},
{path:'/',redirect:'/login'}
]
})
-------------App.vue占位符----------
<template>
<router-view></router-view> <!-- 这里就会自动展示为整个页面 -->
</template>
这样每次进入项目,相当于就是点击了/,然后自动重定向显示Login组件
这样就展示了登录页面,这个页面做的十分见到那,就几个input就没有了,美化的最主要的就是CSS
模拟实现登录功能
在MyLogin组件中声明data数据,username和password,然后将数据与上面的文本框进行双向的数据绑定
name: 'MyLogin',
data() {
return {
username: '',
password: '',
}
},
}
<div class="form-group form-inline">
<label for="username">登录名称</label>
<input type="text" class="form-control ml-2" id="username" placeholder="请输入登录名称" autocomplete="off" v-model.trim="username">
</div>
<!-- 登录密码 -->
<div class="form-group form-inline">
<label for="password">登录密码</label>
<input type="password" class="form-control ml-2" id="password" placeholder="请输入登录密码" v-model="password">
</div>
同时为登录按钮添加事件处理函数
methods:{
onLoginClick() {
//判断用户名和密码是否正确
if(this.username === 'Cfeng' && this.password === '000000') {
//登录成功,跳转到后台主页
this.$router.push('/home')
//模拟存储token
return localStorage.setItem('token','Bearer 89y')
}
//登录失败,就会清除token
localStorage.removeItem('token')
}
}
这里模拟了登录之后存取token的过程,登录成功就会获取到token存储到本地,否则移除本地的token【登录失败的时候会移除登录成功时的token】
封装MyHome组件并路由渲染
这个组件就是后台登录的页面,也就是#/home这个hash地址
<template>
<div class="home-container">
<!-- 头部组件 -->
<my-header></my-header>
<!-- 主体区域 -->
<div class="home-main-box">
<!-- 左边侧栏区域 -->
<my-aside></my-aside>
<!-- 右侧内容 -->
<div class="home-main-body">
</div>
</div>
</div>
</template>
<script>
// 头部区域组件
import MyHeader from './subcomponents/MyHeader.vue'
// 左侧边栏组件
import MyAside from './subcomponents/MyAside.vue'
export default {
name: 'MyHome',
// 注册组件
components: {
MyHeader,
MyAside,
},
}
</script>
<style lang="less" scoped>
.home-container {
height: 100%;
display: flex;
flex-direction: column;
.home-main-box {
height: 100%;
display: flex;
.home-main-body {
padding: 15px;
flex: 1;
}
}
}
</style>
后台的主页就是通过两个私有的子组件组成的
这个组件就是之前封装过多次,主要包含的就是logo和名称
<template>
<div class="layout-header-container d-flex justify-content-between align-items-center p-3">
<!-- 左侧 logo 和 标题区域 -->
<div class="layout-header-left d-flex align-items-center user-select-none">
<!-- logo -->
<img class="layout-header-left-img" src="../../assets/Cfeng.png" alt="">
<!-- 标题 -->
<h4 class="layout-header-left-title ml-3">Cfeng后台管理系统</h4>
</div>
<!-- 右侧按钮区域 -->
<div class="layout-header-right">
<button type="button" class="btn btn-light">退出登录</button>
</div>
</div>
</template>
<script>
export default {
name: 'MyHeader',
}
</script>
<style lang="less" scoped>
.layout-header-container {
height: 60px;
border-bottom: 1px solid #eaeaea;
}
.layout-header-left-img {
height: 50px;
}
</style>
这个组件封装的是左边的菜单栏部分; 就是一个简单的列表,但是最复杂的就是CSS的样式渲染:happy:
<template>
<div class="layout-aside-container">
<!-- 左侧边栏列表 -->
<ul class="user-select-none menu">
<li class="menu-item">用户管理</li>
<li class="menu-item">权限管理</li>
<li class="menu-item">商品管理</li>
<li class="menu-item">订单管理</li>
<li class="menu-item">系统设置</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyAside',
}
</script>
<style lang="less" scoped>
.layout-aside-container {
width: 250px;
height: 100%;
border-right: 1px solid #eaeaea;
}
.menu {
list-style-type: none;
padding: 0;
.menu-item {
line-height: 50px;
font-weight: bold;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
&:hover {
background-color: #efefef;
cursor: pointer;
}
a {
display: block;
color: black;
padding-left: 30px;
&:hover {
text-decoration: none;
}
}
}
}
// 设置路由高亮效果
.router-link-active {
background-color: #efefef;
box-sizing: border-box;
position: relative;
// 伪元素实现路由高亮效果
&::before {
content: ' ';
display: block;
width: 4px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: #42b983;
}
}
</style>
路由渲染的过程和之前是相同的,导入即可
渲染Home.vue的基本结构,将子组件渲染到页面上
实现退出登录
退出登录首先就是要回退到登录的界面,同时要清空token值,直接为按钮绑定一个函数onLoginOut了
export default {
name: 'MyHeader',
methods:{
onLoginOut() {
localStorage.removeItem('token')
this.$router.push('/login')
}
}
}
这个环节实现很简单
全局控制路由的访问权限
这里就是依靠全局的导航守卫
router.beforeEach((to,from,next) =>{
if(to.path === '/login') return next()
const token = localStorage.getItem('token')
if(!token) {
return next('/login')
}
next()
})
这样就不可以直接从地址栏访问到后台的页面
将home左侧的菜单改为路由链接
将菜单改为路由链接,这样点击链接就可以直接跳转,并且将对应的组件通过路由占位符显示到右侧
//之前的aside边框
<ul class="user-select-none menu">
<li class="menu-item">用户管理</li>
<li class="menu-item">权限管理</li>
<li class="menu-item">商品管理</li>
<li class="menu-item">订单管理</li>
<li class="menu-item">系统设置</li>
</ul>
--------将li中添加为路由链接---------------
<ul class="user-select-none menu">
<li class="menu-item">
<router-link to="/home/users">用户管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/rights">权限管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/goods">商品管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/orders">订单管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/settings">系统设置</router-link>
</li>
</ul>
在myHOme中进行占位
<!-- 右侧内容 -->
<div class="home-main-body">
<router-view></router-view>
</div>
之后在routers中绑定路由规则
const router = createRouter({
history: createWebHashHistory(),
routes:[
{path:'/login',component: Login},
{path:'/',redirect:'/login'},
{
path:'/home',
component: Home,
redirect:'/home/users',
children:[
{path:'users',component:Users},
{path:'rights',component:Rights},
{path:'goods',component:Goods},
{path:'settings',component:Settings},
{path:'orders',component:Orders}
]
},
]
})
绑定之后菜单栏就可以正常进行显示,这个easy,所以不阐释了
渲染用户列表数据
这里的用户管理的子组件还没有获取到任何的数据,这是需要请求后台从数据库中获取数据的; 这里直接在MyUsers组件中使用v-for命令进行渲染
<tbody>
<tr v-for="(item,index) in userList" :key="item.id">
<td>{{index + 1}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>{{item.position}}</td>
<td>详情</td>
</tr>
</tbody>
这里的数据直接使用的data定义,实际上是从数据库中获取的
这里点击详情需要达到的效果就是查看详细信息,跳转的组件和之前的页面平级 — 右侧主体区域
<td>
<router-link :to="'/home/users' + item.id">详情</router-link>
</td>
然后导入详情页面,将其在children结点下声明路由规则 {path:‘users/:id’,component:UserDetails}
就可以正常实现跳转
在router中开启props传参
{path:'users/:id',component:UserDetails,props:true}
在详情页的组件中,使用props接收参数
export default {
name: 'MyUserDetail',
props:['id']
}
这样就可以具体的渲染用户的详情
详情页后退
这里就直接给按钮绑定一个点击事件,并使用router的go方法就可以正常后退
methods:{
goBack() {
this.$router.go(-1)
}
}
这样就简单完成了功能, 🎉
|