vue-router
# 单页面应用
单页面应用 > 一个项目就一个页面
点击导航标签,页面会变换,看似是多个页面,实则是页面公共区域的替换(即加载不同的组件) -- 用Vue Router来实现!
2
# 快速使用
dengchuan@MacBook-Air Desktop % npm create vue@latest
Vue.js - The Progressive JavaScript Framework
✔ 请输入项目名称: … vue04
✔ 是否使用 TypeScript 语法? … 否 / 是
✔ 是否启用 JSX 支持? … 否 / 是
✔ 是否引入 Vue Router 进行单页面应用开发? … 否 / 是 # 选择是,其余的都选否
✔ 是否引入 Pinia 用于状态管理? … 否 / 是
✔ 是否引入 Vitest 用于单元测试? … 否 / 是
✔ 是否要引入一款端到端(End to End)测试工具? › 不需要
✔ 是否引入 ESLint 用于代码质量检测? › 否
正在初始化项目 /Users/dengchuan/Desktop/vue04...
项目初始化完成,可执行以下命令:
cd vue04
npm install
npm run dev
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
完整代码如下:
注 - <RouterLink>
的本质就是 a标签, 在浏览器上右键可观察到..
# router组件 - 参数
<RouterLink>
的本质就是 a标签
携带参数(url参数、动态路由参数), 同一组件之间跳转的情况,
导航中一般不会出现, 在详情页里, 有其他推荐, 点击推荐, 就是同组件跳转.. 要用onBeforeRouteUpdate 钩子函数来解决!!
同组件跳转 ==>
http://localhost:5173/about?v1=123&v2=888 跳转到 http://localhost:5173/about?v1=123&v2=999
http://localhost:5173/about/1 跳转到 http://localhost:5173/about/2
在onBeforeRouteUpdate中获取到参数后,就可以通过axios重新发送请求来加载页面!!
2
3
4
5
# 路由无参数
/src/router/index.js
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
2
3
4
5
/src/App.vue
// 以下的三种写法皆可对应上面这个index.js中配置的about路由!任选其一即可.
<RouterLink to="/about">About</RouterLink>
<RouterLink :to="{name:'about'}">About</RouterLink>
<RouterLink :to="{path:'/about'}">About</RouterLink>
<RouterLink></RouterLink> 也可写成 <router-link></router-link>
<RouterView/> 也可写成 <router-view/>
2
3
4
5
6
7
# url参数的携带与接收
http://localhost:5173/about?v1=123&v2=888
# 基本实现
/src/router/index.js
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
}
2
3
4
5
/src/App.vue -- router-link
标签携带url参数..
// 以下两种携带方式任选其一即可
<router-link to="/about?v1=123&v2=888">About</router-link>
<router-link :to="{name:'about', query:{v1:123,v2:888}}">About</router-link>
2
3
/src/views/AboutView.vue -- 在/about路由对应的 AboutView.vue组件里进行接收
import {useRoute} from "vue-router"
const route = useRoute()
console.log(route.query) // {v1: '123', v2: '888'}
2
3
# 同组件跳转不重新接收参数
问题: 同一组件(无参数到有参数; 有参数到有参数,但参数不同).. 此时若当前组件跳转到当前组件, 不会重新接收参数.页面不会变化..
解决: 用onBeforeRouteUpdate来解决..
简单来说,当路由仅是路径参数、query、hash 发生改变, 而不是 path 发生改变时, 便会触发 onBeforeRouteUpdate.
onBeforeRouteUpdate 钩子函数可接收三个参数:
to: 即将进入的目标路由对象
from: 当前导航正要离开的路由对象
next: 必须调用该函数来 resolve 这个钩子函数,可以传递一个参数来指示路由的行为
导航中, About、About1、About2属于同一个路由, 只是携带的url参数不同(没携带、携带的v2值时888、携带的v2值时999)
若我不使用onBeforeRouteUpdate,
从Home到任一About, 页面都可正确变化; 若是About组件之间跳转, 浏览器的url地址会变, 但页面不会变化..
gif中是配置了onBeforeRouteUpdate后, 我们想要得到的效果..
/src/App.vue
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">Home</RouterLink> |
<RouterLink to="/about">About</RouterLink> |
<router-link to="/about?v1=123&v2=888">About1</router-link> |
<router-link :to="{name:'about', query:{v1:123,v2:999}}">About2</router-link>
</nav>
</div>
</header>
<RouterView/>
</template>
<script setup></script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/src/views/AboutView.vue
<template>
<h2>About - {{ query_dict }}</h2>
</template>
<style></style>
<script setup>
import {ref} from "vue";
import {useRoute, onBeforeRouteUpdate} from "vue-router"
const route = useRoute()
const query_dict = ref(route.query)
// 捕获到同一组件之间跳转时,路由的url参数等信息!!
onBeforeRouteUpdate(function (to, from) {
// console.log(to, from)
query_dict.value = to.query
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 动态路由参数的携带与接收
它也存在类似的问题 - 同组件跳转不重新接收动态路由参数.. 同样的,可用onBeforeRouteUpdate来解决.
- 此次不是用
to.query
来取, 而是用to.params
来取.. {name:'about',params:{nid:1}}
+path: '/about/:nid'
这里的nid是对应的.
/src/App.vue
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">Home</RouterLink>
|
<RouterLink :to="{name:'about',params:{nid:1}}">About1</RouterLink>
|
<RouterLink :to="{name:'about',params:{nid:2}}">About2</RouterLink>
</nav>
</div>
</header>
<RouterView/>
</template>
<script setup>
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about/:nid',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/src/views/AboutView.vue
<template>
<h2>About - {{ param_dict }}</h2>
</template>
<style></style>
<script setup>
import {ref} from "vue";
import {useRoute, onBeforeRouteUpdate} from "vue-router"
const route = useRoute()
const param_dict = ref(route.params)
onBeforeRouteUpdate(function (to, from) {
// console.log(to, from)
param_dict.value = to.params
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# router组件-嵌套
关键点在于: 父路由对应的组件中定义
<router-view/>
, 用于显示子路由对应组件的内容.
需求: (点击父路由,会默认展示它的某个子路由)
首页按钮 http://localhost:5173
关于按钮 http://localhost:5173/about
> ★ 默认展示的是关于里的info按钮的内容!!写个子路由,用path: "", + component来实现.
关于里的info按钮 http://localhost:5173/about/info
关于里的blog按钮 http://localhost:5173/about/blog
2
3
4
5
在App.vue中, home、about这些一级路由都展示在下方代码的 <RouterView/>
的位置.
下方关于 关于
按钮的代码, 任选其一即可..
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">首页</RouterLink> <!-- http://localhost:5173 -->
|
<!--<RouterLink :to="{name:'about_'}">关于</RouterLink>-->
<RouterLink to="/about">关于</RouterLink> <!-- http://localhost:5173/about -->
</nav>
</div>
</header>
<RouterView/>
</template>
<script setup></script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
假设about这个一级路由下有三个子路由..(其中一个是空子路由)
Ps: 其实about下的空子路由, 还可以 redirect: {name: "info"},
但这种写法, url会有所不同.. 可以自我实验进行验证..
这种写法, 相当于点击 关于 按钮, http://localhost:5173/about
自动跳转到 http://localhost:5173/about/info
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'), // http://localhost:5173/about
children: [
{
path: "",
name: "about_",
component: () => import('../views/Info.vue'),
},
{
path: 'info',
name: 'info',
component: () => import('../views/Info.vue'), // http://localhost:5173/about/info
},
{
path: 'blog',
name: 'blog',
component: () => import('../views/blog.vue'), // http://localhost:5173/about/blog
},
]
},
],
})
export default router
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
30
31
32
33
34
35
36
37
那么about这个一级路由下的两个子路由info和blog展示在下方代码的<RouterView/>
的位置..
<template>
<RouterLink :to="{name:'info'}">info</RouterLink>
|
<RouterLink :to="{name:'blog'}">blog</RouterLink>
<RouterView/>
</template>
<style></style>
<script setup></script>
2
3
4
5
6
7
8
9
10
# 编程式导航/js代码跳转
在前面, 我们都是通过
<RouterLink>
标签实现路由的跳转..
其实, 使用RouterLink的地方我们都可以使用js代码实现路由的跳转..(这种方式就叫作编程导航)编程式导航的应用场景: 登陆成功后跳转首页 -- 按钮触发了一个事件, 跳转到另一个路由.
# 关键语法
实现编程式导航的关键语法: (push跳转到指定路由
import {useRouter} from "vue-router"
const router = useRouter()
router.push({path:'/about/blog'}) 等同于 router.push({name: "blog"})
// 若路由配置有参数的话 动态路由参数、url的get参数
router.push({name: "blog", params: {nid: 100}, query: {page: 100}})
router.replace 与 router.push
- 使用都是一模一样的
- 当前历史记录 = [A,B,C,D]
push > [A,B,C,D,blog] -- 新增
replace > [A,B,C,blog] -- 覆盖最新的
2
3
4
5
6
7
8
9
10
11
12
我们将上面 router组件-嵌套
小节示例中 的 blog路由相关的 RouterLink标签 给注释掉, 改用编程式导航实现相同的效果..
更改的代码如下:
<template>
<RouterLink :to="{name:'info'}">info</RouterLink>
|
<!--<RouterLink :to="{name:'blog'}">blog</RouterLink>-->
<span @click="doClick">blog</span> <!--我试了 a、span、button标签都可以-->
<RouterView/>
</template>
<style></style>
<script setup>
import {useRouter} from "vue-router";
const router = useRouter()
function doClick() {
router.push({name: 'blog'})
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
下面我们通过登陆跳转来好好体会下编程式导航..
登陆跳转是否含顶部, 本质就在于 /src/router/index.js
里的路由嵌套不一样
# 登陆跳转(含顶部)
应用场景: 博客、公司官网、xx在线平台 不登陆即可看到一些内容 -- 首页、关于、登陆 都在导航条上面..
需求: 在未登录时, 就可以看 首页、关于 的内容.. 登陆成功后 跳转首页..
/src/App.vue
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">首页</RouterLink>
|
<RouterLink to="/about">关于</RouterLink>
|
<RouterLink to="/login">登陆</RouterLink>
</nav>
</div>
</header>
<RouterView/>
</template>
<script setup></script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue'),
},
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/src/views/LoginView.vue
<template>
<div>
<input type="text" placeholder="用户名" v-model="username">
<input type="text" placeholder="密码" v-model="pwd">
<input type="button" @click="doLogin" value="登陆">
</div>
</template>
<script setup>
import {ref} from "vue";
import {useRouter} from "vue-router";
const router = useRouter()
const username = ref("")
const pwd = ref("")
function doLogin() {
// 1.获取数据 Todo-携带数据向后端发送请求进行验证
console.log(username.value, pwd.value)
// 2.假设登陆成功,本地存储用户信息(登录凭证 应该用后端返回的token,此处简单化用的用户名)
// 其实token 不仅本地缓存持久化 + 我们还会将其放到 vuex、pinia 中,因为是存在内存中,读取更快
localStorage.setItem("name", username.value)
// 3.跳转到首页
router.push({name: "home"})
}
</script>
<style scoped></style>
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
# 登陆跳转(不含顶部)
应用场景: 某平台的后台管理.. (没有登陆,就不能展现导航菜单) -- 登陆不在导航条上.. ★ 将后台跟登陆的页面区分开.
需求: 登陆页面是单独的, 登陆成功后, 跳转后台页面.
简单分析下:
vue是单应用/单页面开发, 可以简单理解就是App.vue组件!! 项目不管访问什么地址一直都在展示App.vue!!
既然展示的都是App.vue,那么为啥不同地址呈现的App.vue不一样呢?是因为路由的配置.
根据路由index.js的配置,配置中一级路由对应的组件通通都会渲染到 App.vue的<router-view>处!!
若一级路由有children属性,那么该属性下配置的二级路由都会渲染到当前一级路由对应组件的<router-view>处!!
App.vue组件里啥也没有,就剩下一个<router-view>!!
意味着,每次往App.vue的<router-view>加载的组件就是该组件原本的内容.
所以,该登陆跳转的页面就不含顶部啦!!!
2
3
4
5
6
7
8
不含顶部和含顶部相比,/src/views/LoginView.vue
的内容不用改.
需要新增一个一级路由admin,将原本写在App.vue中的写在了该组件里 + 改一下App.vue和路由配置文件index.js.
/src/App.vue
<template>
<RouterView/>
</template>
<script setup></script>
<style scoped></style>
2
3
4
5
6
/src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue'),
},
{
path: '/',
name: 'admin',
component: () => import('../views/AdminView.vue'),
children: [
{
path: '',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
]
},
],
})
export default router
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
30
31
32
/src/views/AdminView.vue
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">首页</RouterLink>
|
<RouterLink to="/about">关于</RouterLink>
</nav>
</div>
</header>
<RouterView/>
</template>
<script setup>
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 导航守卫
有些路由需要登陆后才能访问, 此时就需要导航守卫! - 就是Django中的中间件,其他框架里的拦截器. - 拦截请求进行处理
vue-router作拦截, 本地缓存中有token才表明已登陆, 否则登陆未成功就跳转登陆页面..
提一嘴, 后续还要实现axios拦截器, 携带token进行访问, 会验证token是否有效.. (待学习)
# 浏览器的本地存储
- cookie, 可设置有效期
- localStorage 可长期保存
localStorage.setItem(key,value)
localStorage.getItem(key)
localStorage.removeItem(key)
localStorage.clear()
- sessionStorage 一般不会用它,有个弊端:关闭浏览器,自动消失
sessionStorage.setItem(key,value)
sessionStorage.getItem(key)
sessionStorage.removeItem(key)
sessionStorage.clear()
> localStorage.clear()
undefined
> localStorage.setItem('name','dc')
undefined
> localStorage.getItem('name')
'dc'
> localStorage.getItem('xxx')
null
> if(null){}else{console.log(123)}
123
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 登陆才能访问+登出
需求: 不登陆就没法访问 首页和关于; 登陆后跳转首页; 退出登陆后,清空本地缓存,跳转登陆页面. 在登陆跳转(不含顶部) 这个示例上, 进行一点代码的修改, 即可实现该需求, 完整代码如下:
/src/App.vue
<template>
<RouterView/>
</template>
<script setup></script>
<style scoped></style>
2
3
4
5
6
/src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue'),
},
{
path: '/',
name: 'admin',
component: () => import('../views/AdminView.vue'),
children: [
{
path: '',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
]
},
],
})
// ★ 导航守卫!!
router.beforeEach((to, from, next) => {
/*
* to 即将访问的的路由对象
* from 当前要离开的路由对象
* next() 继续向后执行,去to的页面
* next(false) 不跳转,还在当前页面
* next("/xxx") next({name:"xxx"}) next({path:"/xxx"}) 跳转指定的页面
* */
//1.白名单 eg:访问登录页面,该页面不需要登录就可以直接去查看
if (to.name === "login" || to.name === "Index") {
next()
return
}
//2.检查用户登录状态(登录成功,继续往后走next(); 未登录,跳转到登录页面)
let username = localStorage.getItem("name")
if (username) {
next()
} else{
next({name: "login"})
}
})
export default router
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/src/views/LoginView.vue
<template>
<div>
<input type="text" placeholder="用户名" v-model="username">
<input type="text" placeholder="密码" v-model="pwd">
<input type="button" @click="doLogin" value="登陆">
</div>
</template>
<script setup>
import {ref} from "vue";
import {useRouter} from "vue-router";
const router = useRouter()
const username = ref("")
const pwd = ref("")
function doLogin() {
// 1.获取数据 Todo-携带数据向后端发送请求进行验证
console.log(username.value, pwd.value)
localStorage.setItem("name", username.value) // 应存后端返回的token,此处为了方便,存的是name
// 3.跳转到首页
router.push({name: "home"})
}
</script>
<style scoped></style>
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
/src/views/AdminView.vue
<template>
<RouterLink to="/">首页</RouterLink>
|
<RouterLink to="/about">关于</RouterLink>
--
<span @click="doLogout" style="cursor: pointer;">退出</span>
<RouterView/>
</template>
<script setup>
import {useRouter} from "vue-router";
let router = useRouter()
function doLogout() {
// 清除localStorage并跳转登陆页.
localStorage.clear()
router.push({name: 'login'})
}
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21