主题
布局容器与家庭档案(一)
本页对应 布局容器与家庭档案,共 7 段视频,约 1 小时 1 分。
学习目标
- 能够独立创建 Layout 及 Home、Article、Notify、User 五个组件,并在
router/index.ts中配置一级布局路由、四个嵌套二级路由与/到/home的重定向,通过浏览器地址逐项验证页面正确渲染。 - 能够使用 Vant Tabbar 的
route模式、to属性及icon插槽,结合插槽active状态动态切换激活/默认图标,完成底部导航的路由切换与样式调整。 - 能够编写全局前置守卫,结合用户仓库中的 token 与白名单实现未登录拦截并重定向到登录页、已登录放行,并能说明 Vue Router 4 中守卫返回值与
next用法的差异。 - 能够为路由配置
meta.title,通过全局后置守卫动态设置document.title,并扩展RouteMeta类型使title获得类型提示与默认值处理。 - 能够集成 NProgress,在前置守卫调用
start、后置守卫调用done,配置showSpinner: false并覆盖主题色,验证重定向场景下进度条能正常结束。 - 能够使用
Omit从User类型排除token并结合交叉类型定义UserInfo,调用获取用户信息 API,在组件挂载后渲染头像、数据统计与订单徽标,并实现数量为 0 时隐藏徽标。
知识点
本节目标:先准备容器,再拼出完整页面
为什么需要:登录成功后默认进入个人中心页,而个人中心页是二级路由组件。只渲染个人中心组件,页面并不完整,还需要外层布局容器承载二级路由出口和底部 Tabbar。
怎么想:把页面拆成“一级布局容器 + 四个二级页面”。一级路由是 Layout,二级路由分别是首页、健康百科、消息通知、个人中心。
怎么做:先在 views 下准备 5 个组件,再在 router/index.ts 配置嵌套路由和默认重定向。
易错点:不要把完整页面内容都塞进 Layout;Layout 只负责“壳”,二级页面通过 <router-view /> 显示。
准备五个组件:Layout 与四个二级页面
为什么需要:路由规则必须有对应组件,否则配置了路径也无法渲染。
怎么想:Layout 是容器,内部需要两部分:二级路由出口和 Tabbar 占位。四个二级页面先建空结构,后续再补内容。
怎么做:在 views 下建立:
views/layout/index.vueviews/home/index.vueviews/article/index.vue(转写稿中“arctic/artic”指健康百科,按语义纠正为 article)views/notify/index.vueviews/user/index.vue
Layout 先写成:
vue
<template>
<div>
<!-- 二级路由出口 -->
<router-view />
<!-- Tabbar 先占位,后续实现 -->
</div>
</template>
<script setup lang="ts">
// 布局容器组件
</script>易错点:router-view 不要拼错;Layout 中只放一个二级出口;四个二级页面目录名要与路由导入路径一致。
配置一级路由与嵌套路由
为什么需要:Layout 要和登录页处于同一级,四个页面作为 Layout 的子路由,这样切换二级页面时 Layout 不会被销毁。
怎么想:先写一级路由 path: '/'、component: Layout,再用 children 配置四个二级路由。
怎么做:在 router/index.ts 中按项目现有导入方式配置:
ts
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: '/home',
component: () => import('@/views/home/index.vue')
},
{
path: '/article',
component: () => import('@/views/article/index.vue')
},
{
path: '/notify',
component: () => import('@/views/notify/index.vue')
},
{
path: '/user',
component: () => import('@/views/user/index.vue')
}
]
}易错点:children 不要写成 childs;component 不要写成 components;二级路由地址要和 Tabbar 后续的 to 一致;Layout 导入路径要正确。
重定向与浏览器验证
为什么需要:访问 / 时如果只匹配到 Layout,页面只有壳,没有二级内容,看起来不完整。
怎么想:默认进入首页,所以访问 / 时重定向到 /home。
怎么做:在一级路由中加 redirect: '/home',然后到浏览器依次访问 /home、/article、/notify、/user。转写稿的调试过程就是:访问 /home 能看到 Tabbar 占位和 Home;访问 /article、/notify、/user 都能切换对应二级组件,说明布局容器与路由规则生效。
易错点:漏写 redirect 时访问 / 只看到 Layout;重定向地址写错会跳到不存在页面;修改路由后注意保存并刷新验证。
提交前检查清单
- 5 个组件文件是否都已创建;
- Layout 中是否有
<router-view />; - 一级路由是否与 login 同级;
- 四个二级路由是否都在
children中; /是否重定向到/home;- 浏览器访问四个地址是否都能显示 Layout + 对应页面。
本节笔记未提供图片素材,因此未插入图片;代码以上述路由与组件结构为准。
先搭好布局容器与路由出口
为什么需要:个人中心等页面是二级路由组件,必须有一个外层布局容器承接公共结构;否则每个页面都要重复写顶部内容和底部 Tabbar。
怎么想:布局容器只负责两件事——放二级路由出口、放底部 Tabbar。转写稿中先准备 layout/index.vue,里面用 router-view 作为二级路由出口,Tabbar 先占位。
怎么做:在布局容器中保留 <router-view />,再把占位文字替换成 Vant Tabbar。转写稿里的“w 的 table bar”即 Vant Tabbar,下面用 Vant 组件名示意;若项目组件前缀不同,按项目实际前缀替换。
vue
<template>
<div>
<router-view />
<van-tabbar route>
<!-- 四个 TabbarItem 在这里配置 -->
</van-tabbar>
</div>
</template>易错点:不要忘记 router-view,否则二级页面不显示;Tabbar 不要放到路由出口内部,否则切换页面时可能被重复渲染。
用路由模式完成页面切换
为什么需要:点击“消息通知”时,上方要变成消息通知页面,地址也要变化;这本质是路由切换,不是普通按钮切图。
怎么想:Tabbar 自己不会知道路由规则,所以要在 Tabbar 上开启路由模式,并让每个 item 通过 to 指向对应地址。转写稿中四个地址分别是 /home、/article、/notify、/user,其中“arctic/aric”应纠正为 article,“notify/naughtati”对应 notice/notify 路由,“游者/邮政”对应 user。
怎么做:给 van-tabbar 加 route,给每个 van-tabbar-item 加 to。
vue
<van-tabbar route>
<van-tabbar-item to="/home">首页</van-tabbar-item>
<van-tabbar-item to="/article">健康百科</van-tabbar-item>
<van-tabbar-item to="/notify">消息通知</van-tabbar-item>
<van-tabbar-item to="/user">我的</van-tabbar-item>
</van-tabbar>调试过程:转写稿中先在浏览器点击首页、健康百科、消息通知、我的,观察地址是否分别变为 /home、/article、/notify、/user,页面是否跟着切换。若地址变了但页面不变,先检查二级路由是否配置在布局容器的 children 中。
易错点:忘记 route 时点击只高亮不跳转;to 写错一个字符就会跳到空页面;四个 to 必须与 router/index.ts 中的路径一致。
用 icon 插槽完成图标切换
为什么需要:组件库默认图标不符合项目图标规范;同时激活项要显示绿色图标,失活项要显示灰色图标。
怎么想:TabbarItem 提供 icon 插槽,插槽作用域里有 active。激活时 active 为真,取 active 图标;失活时取 default 图标。转写稿中首页激活图标名为 home-index-active,失活为 default 版本。
怎么做:在 item 内写 #icon="{ active }",用 van-icon 动态绑定 name。
vue
<van-tabbar-item to="/home">
<template #icon="{ active }">
<van-icon
:name="active ? 'home-index-active' : 'home-index-default'"
/>
</template>
首页
</van-tabbar-item>其余三项按同样方式处理:健康百科使用 article 下的 active/default 图标,消息通知使用 notice 图标,我的使用 mine 图标。转写稿中先把首页图标从默认图标换成自定义图标,再在浏览器确认首页变绿;随后把 name 改成 default 版本,确认变灰;最后用 active 做三元判断,点击不同 Tab 验证绿色/灰色切换。
易错点:active 是插槽作用域数据,不要写成全局变量;图标名后缀 active/default 写反会导致激活状态显示灰色;四个 item 都要加插槽,否则只有首页能切换。
样式修正与浏览器验证
为什么需要:直接使用组件默认样式时,按钮和文字偏大、文字颜色偏深,和项目视觉不一致。
怎么想:功能正确后再做样式覆盖,避免先调样式导致结构问题难排查。转写稿中是先验证路由切换、图标切换,再把笔记里准备好的样式片段放到 style 中。
怎么做:把原笔记中的样式代码粘贴到布局容器组件的 style 区域,刷新浏览器观察按钮尺寸、文字大小和颜色是否变浅。
易错点:样式选择器要能命中 Tabbar 内部元素;若使用 scoped 仍不生效,按项目已有样式方案处理,不要随意删掉原样式。最终验收标准是:点击四个 Tab,地址和页面同步切换,激活图标为绿色、失活图标为灰色,样式与设计一致。
为什么需要访问权限控制
到目前为止,项目里的页面几乎都能直接访问:不登录、本地存储里没有 token,也可以打开个人中心、消息通知、健康百科和首页。转写稿里通过浏览器实际验证了这一点。这样显然不安全,因为需要登录后才能看的页面被直接暴露了。
怎么想: 页面切换本质上是路由跳转,所以权限控制应该放在路由跳转发生的位置。只要在每次路由跳转前判断“有没有 token、当前页面是否允许匿名访问”,就能决定放行还是重定向到登录页。
怎么做: 使用 Vue Router 的全局前置守卫 beforeEach 监听跳转;从用户仓库取 token;准备白名单;满足拦截条件时返回登录页地址。
易错点: 不要等页面渲染完再判断权限;不要在守卫里直接操作 DOM;白名单判断要用当前访问路径 to.path,不是整个路由对象。
导航守卫在 Vue3 中的变化
为什么需要: 访问权限控制依赖导航守卫。Vue3 项目里守卫写法和以前有变化,如果照旧写 next,容易混淆。
怎么想: 转写稿明确说,以前 beforeEach 有三个参数:to、from、next;next() 放行,next('/login') 重定向。到了 Vue3,不再需要 next,而是通过返回值表达结果。
怎么做:
- 返回
true或不返回:放行; - 返回
false:取消导航; - 返回路由地址,例如
'/login':重定向。
易错点: 不要同时使用“返回值”和 next;需要重定向时直接 return '/login',不要写成 next('/login')。转写稿中的“before each 曲”“铝碳”等是口误/同音,实际是 beforeEach 和 return。
实现思路:token 与白名单
为什么需要: 只判断 token 不够,因为登录页本身不能要求登录,否则会陷入“未登录访问登录页又被重定向到登录页”的循环。因此需要白名单。
怎么想: 拦截条件应为:没有 token,并且当前访问地址不在白名单里。只有这两个条件同时成立,才重定向到登录页;其他情况一律放行。
怎么做: 从用户仓库取 token;定义 whiteList,目前只放 '/login'。转写稿还说明,将来注册页、第三方绑定回调页等不需要登录的页面,也可以继续加入白名单。
易错点: 白名单不要漏掉登录页;不要写成“有 token 才放行”,否则登录页也会被拦截;判断白名单时用 whiteList.includes(to.path)。
在路由文件中编写全局前置守卫
为什么需要: 权限判断是全局行为,应该集中写在路由配置文件里,而不是散落在各个页面组件中。
怎么想: 在 router/index.ts 中,拿到 router 实例后调用 router.beforeEach;在回调里取用户仓库、取 token、判断白名单,最后返回重定向地址或放行。
怎么做: 转写稿中的核心代码可整理为:
ts
import { useUserStore } from '@/stores/user'
const whiteList = ['/login']
router.beforeEach((to) => {
const userStore = useUserStore()
// 转写稿中写作从 store 的 user 里取 token,
// 实际字段以项目用户仓库结构为准
if (!userStore.user?.token && !whiteList.includes(to.path)) {
return '/login'
}
// 其他情况默认放行
})如果项目里 token 直接挂在 store 上,则判断 !userStore.token。关键是:没有 token 且不在白名单时,return '/login'。
易错点: 不要忘记导入用户仓库;不要在守卫外层重复创建 store 导致状态不一致;返回地址必须是项目真实路由地址,转写稿中“罗钉/罗gan”实际是 /login。
调试与验证
为什么需要: 权限控制写完后必须验证“未登录被拦截、登录后能访问、删除 token 后再次被拦截”。
怎么想: 用浏览器直接访问受保护路由,观察是否重定向到登录页;登录后再访问同一批路由;最后删除本地 token 并刷新,确认再次拦截。
怎么做: 转写稿中的调试过程是:未登录时访问 /user,被重定向到 /login;访问消息通知、健康百科、首页也会被重定向。登录后访问 /user、/notify、/home 均正常。删除 token 后刷新,再次重定向到登录页,说明守卫生效。
易错点: 测试时先确认本地存储里是否真的没有 token;如果登录后仍被拦截,检查 token 存放位置和读取字段是否一致;如果登录页也被拦截,检查白名单是否包含 /login。
易错点汇总
- Vue3 守卫优先用返回值,不要习惯性调用
next。 - 拦截条件是“无 token 且不在白名单”,不是“无 token 就拦截”。
- 白名单判断用
to.path,不要用to。 - 重定向地址直接
return '/login'。 - 白名单目前只写登录页,后续注册页、第三方回调页再按需加入。
- 调试要覆盖未登录、登录后、删除 token 后三种情况。
代码示例
代码一:路由规则 + 访问权限 + 页面标题 + 加载进度(src/router/index.ts)
ts
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { useUserStore } from '@/stores/user'
// 白名单:不需要登录就能访问的页面(登录、注册、第三方回调等)
const whiteList = ['/login']
// 去掉 NProgress 默认右上角的环形转圈
NProgress.configure({ showSpinner: false })
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录' }
},
{
// 一级路由:布局容器
path: '/',
component: () => import('@/views/layout/index.vue'),
// 只访问 / 时页面不完整,重定向到首页
redirect: '/home',
// 二级路由:嵌套在布局容器里
children: [
{
path: '/home',
component: () => import('@/views/home/index.vue'),
meta: { title: '首页' }
},
{
path: '/article',
component: () => import('@/views/article/index.vue'),
meta: { title: '健康百科' }
},
{
path: '/notify',
component: () => import('@/views/notify/index.vue'),
meta: { title: '消息通知' }
},
{
path: '/user',
component: () => import('@/views/user/index.vue'),
meta: { title: '个人中心' }
}
]
}
]
})
// 全局前置守卫:开启进度条 + 访问权限控制
router.beforeEach((to) => {
NProgress.start()
const store = useUserStore()
// 没有 token,且访问的页面不在白名单里 → 重定向到登录页
if (!store.user?.token && !whiteList.includes(to.path)) {
return '/login'
}
// 其他情况不写 return,默认放行
})
// 全局后置守卫:结束进度条 + 动态设置页面标题
router.afterEach((to) => {
NProgress.done()
document.title = `优一问诊 - ${to.meta.title || ''}`
})
export default routerts
// src/types/vue-router.d.ts —— 扩展 RouteMeta 类型,让 to.meta.title 有提示
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
title?: string
}
}关键行与易错点
- 布局容器是一级路由(
path: '/'),首页/健康百科/消息通知/个人中心是它的children,这样才能拼出一个完整页面。 redirect: '/home'必须加,否则访问/时只渲染布局容器,页面不完整。- 权限判断:
!store.user?.token && !whiteList.includes(to.path)→return '/login'。Vue3 的守卫不再使用next,直接return路由地址即重定向,return true/什么都不返回即放行。 afterEach里to.meta.title || ''的空串兜底不能省,否则标题会拼成优一问诊undefined。- 标题放在后置守卫而不是前置守卫:要等路由解析、组件资源加载完再设置标题,否则网络慢时标题和页面不匹配。
- Vue3 中重定向也会走
afterEach,所以NProgress.done()不会漏掉,进度条不会卡住。 vue-router.d.ts扩展了RouteMeta,to.meta.title才有类型提示;不扩展则需手动断言。
代码二:布局容器 + Tabbar 切换效果(src/views/layout/index.vue)
vue
<script setup lang="ts">
// 布局容器:承载二级路由页面 + 底部 Tabbar
</script>
<template>
<div class="layout-page">
<!-- 二级路由出口 -->
<router-view />
<!-- 底部导航:route 开启路由模式 -->
<van-tabbar route>
<van-tabbar-item to="/home">
首页
<template #icon="{ active }">
<cp-icon :name="`home-index-${active ? 'active' : 'default'}`" />
</template>
</van-tabbar-item>
<van-tabbar-item to="/article">
健康百科
<template #icon="{ active }">
<cp-icon :name="`article-${active ? 'active' : 'default'}`" />
</template>
</van-tabbar-item>
<van-tabbar-item to="/notify">
消息通知
<template #icon="{ active }">
<cp-icon :name="`notice-${active ? 'active' : 'default'}`" />
</template>
</van-tabbar-item>
<van-tabbar-item to="/user">
我的
<template #icon="{ active }">
<cp-icon :name="`mine-${active ? 'active' : 'default'}`" />
</template>
</van-tabbar-item>
</van-tabbar>
</div>
</template>
<style scoped lang="scss">
/* 按钮大小、文字大小、文字颜色变浅的调整样式,见笔记,直接粘贴到此处 */
</style>关键行与易错点
<router-view />是二级路由出口,四个二级页面都渲染在这个位置;Tabbar 固定在它下面。<van-tabbar route>的route是路由模式开关,漏写就只是普通 tabbar,点击不跳转;每项再用to指定地址。#icon是van-tabbar-item的图标插槽,作用域参数解构出{ active }:当前项被选中为true,失活为false。- 图标动态切换:
:name="\xxx-${active ? 'active' : 'default'}`",激活取绿色active图标,失活取灰色default图标;图标名要和icons` 目录下的文件名严格一致,否则渲染空白。 - 使用官方
van-tabbar基础用法时要删掉v-model数据绑定,本场景靠路由控制选中态,不需要手动维护索引。 - 默认图标和文字偏大、颜色偏深,需在
style中补上样式调整。
代码三:个人中心——取数 API + 信息渲染(src/services/user.ts + src/views/user/index.vue)
ts
// src/services/user.ts —— 获取个人用户信息(GET,无传参)
import request from '@/utils/request'
import type { UserInfo } from '@/types/user'
export const getUserInfo = () => request.get<UserInfo>('/patient/user')vue
<!-- src/views/user/index.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/services/user'
import type { UserInfo } from '@/types/user'
// 注意:是 UserInfo(不含 token),不是 User
const user = ref<UserInfo>()
// 组件挂载完毕后获取数据
onMounted(async () => {
const res = await getUserInfo()
user.value = res.data
})
</script>
<template>
<div class="user-page">
<!-- 头部:头像 + 名称 -->
<div class="top">
<img class="avatar" :src="user?.avatar" alt="" />
<span class="name">{{ user?.account }}</span>
</div>
<!-- 数据统计 -->
<div class="statistics">
<div class="item"><span>{{ user?.collectionNumber }}</span>收藏</div>
<div class="item"><span>{{ user?.likeNumber }}</span>关注</div>
<div class="item"><span>{{ user?.score }}</span>积分</div>
<div class="item"><span>{{ user?.couponNumber }}</span>优惠券</div>
</div>
<!-- 药品订单:用 van-badge 在图标右上角展示数量 -->
<div class="order">
<van-badge :content="user?.orderInfo?.payNumber || ''">
<cp-icon name="user-pay" />
<span>待付款</span>
</van-badge>
<van-badge :content="user?.orderInfo?.shipNumber || ''">
<cp-icon name="user-ship" />
<span>待发货</span>
</van-badge>
<van-badge :content="user?.orderInfo?.receiveNumber || ''">
<cp-icon name="user-receive" />
<span>待收货</span>
</van-badge>
<van-badge :content="user?.orderInfo?.finishNumber || ''">
<cp-icon name="user-finish" />
<span>已完成</span>
</van-badge>
</div>
</div>
</template>关键行与易错点
getUserInfo无入参,直接request.get<UserInfo>('/patient/user'),泛型决定返回data的类型,别写成User。ref<UserInfo>()初值是undefined,模板里必须用可选链user?.avatar,否则首次渲染会报错。- 取数放在
onMounted里并用async/await,user.value = res.data赋值后模板自动更新。 - 徽标用
van-badge包裹图标,数量通过content绑定;数量为 0 时必须写成|| '',否则会渲染出一个“0”的徽标。 orderInfo是嵌套对象,要写user?.orderInfo?.payNumber两级可选链;具体字段名以接口文档/类型注释为准。- 结构上分三块:头部(头像、名称)、数据统计(收藏/关注/积分/优惠券)、药品订单(四个徽标)。
常见错误
现象:访问
/只看到一片空白或页面不完整。 原因:布局容器的 children 配置了二级路由,但没有给一级路由加redirect,/命中布局容器后没有二级组件可渲染。 修法:在布局容器规则上补redirect: '/home'(或指向默认二级路由)。现象:Tabbar 点击能跳转,但地址变了页面不切换 / 点击后刷新又回到首页。 原因:只在
van-tabbar上加了route,van-tabbar-item漏写to;或to里写的是相对路径、与二级路由 path 不一致。 修法:逐项核对每个 item 的to与路由表里二级路由 path 完全一致(/home、/article、/notify、/user)。现象:图标一直是绿色,或一直灰色,切换标签时不变。 原因:
#icon插槽里把name写死成xxx-index-active,没有用插槽作用域提供的active变量。 修法:#icon="{ active }"解构出active,用模板字符串动态拼接active ? '-active' : '-default'后缀。现象:未登录状态下直接访问
/user、/notify仍能进入页面。 原因:beforeEach里写了判断但没return重定向地址;或白名单用includes判断时传错了值(如传了to.name而不是to.path)。 修法:确认分支内return '/login',且白名单数组存的是完整 path,判断用to.path。现象:登录页本身也被拦截,出现无限重定向 / 页面卡死。 原因:白名单里漏了
/login,无 token 访问登录页时又被重定向到登录页。 修法:白名单至少包含/login,后续注册、回调页也一并加入。现象:标签页标题显示
undefined或拼接出undefined优医问诊。 原因:to.meta.title在部分路由(如重定向的/、404)上未定义,直接拼接。 修法:取值时兜底空串to.meta.title ?? '',并给RouteMeta扩展title?: string类型,避免无提示。现象:进度条出现后不消失,一直停在半途。 原因:只在
beforeEach里start(),漏了afterEach的done();或afterEach写在了beforeEach的 return 之后/被提前 return 跳过。 修法:afterEach与beforeEach同级注册,且确保afterEach中无条件调用done()。现象:订单数量为 0 时,图标右上角仍显示一个“0”徽标。 原因:
content直接绑定了数值,0 是有效值,Badge 会照常渲染。 修法:content绑定表达式里做判断,为 0 时给空字符串(或undefined)以隐藏徽标。
练习
- 在布局容器路由上故意删掉
redirect,然后访问/,观察页面表现;再补回并验证,说明为什么必须配置重定向。 - 给 Tabbar 增加第五个 item(如“用药记录”),要求同时完成:新建二级路由组件、配置嵌套路由、添加
to、自定义图标并实现激活/失活切换。 - 把白名单从数组改成
Set或正则匹配实现,保持行为不变,并额外把/register加入白名单后验证未被拦截。 - 给 404 页面补充
meta.title,并处理一个没有meta.title的路由,确认标题不会出现undefined。 - 用
Omit和Pick从已有的User类型派生出两个新类型:一个“不含 token 的用户基础信息”,一个“只含 id 与 account 的精简类型”,并在组件中用ref声明变量验证类型提示是否生效。
课件图示






原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 布局容器-组件与路由配置 | 06:20 |
| 2 | 布局容器-Tabbar切换效果 | 10:56 |
| 3 | 布局容器-访问权限控制 | 07:58 |
| 4 | 布局容器-页面标题 | 08:41 |
| 5 | 布局容器-加载进度 | 07:03 |
| 6 | 个人中心-用户信息类型 | 09:46 |
| 7 | 个人中心-用户信息渲染 | 11:04 |
小结
- 布局容器是二级路由的“壳”,必须先有壳再有内容:一级路由 +
children+redirect三者缺一不可。 - Tabbar 的两类切换是两件独立的事:
route+to解决路由切换,#icon插槽 +active解决图标切换,不要混为一谈。 - 访问权限控制的判定条件只有一条主线:无 token 且不在白名单 → 重定向登录,其余一律放行;Vue3 用
return替代next。 - 白名单是一份会增长的清单,实现时就要按“多页面”设计,而不是只塞一个
/login。 - 页面标题应放在
afterEach而非beforeEach,因为要等资源加载完成,标题与页面才匹配。 - 进度条遵循“前置开启、后置结束”的配对原则,且 Vue3 中重定向场景后置守卫依然会执行,无需额外兼容。
Omit与Pick是复用类型的两个方向:一个做减法、一个做加法,配合交叉类型可避免重复定义用户信息字段。- 渲染数量类信息时要把“0 值”当作特殊态处理,徽标、统计等场景都应显式判断而不是直接绑定数值。