Skip to content

布局容器与家庭档案(一) ​

本页对应 布局容器与家庭档案,共 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.vue
  • views/home/index.vue
  • views/article/index.vue(转写稿中“arctic/artic”指健康百科,按语义纠正为 article)
  • views/notify/index.vue
  • views/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。

易错点汇总 ​

  1. Vue3 守卫优先用返回值,不要习惯性调用 next。
  2. 拦截条件是“无 token 且不在白名单”,不是“无 token 就拦截”。
  3. 白名单判断用 to.path,不要用 to。
  4. 重定向地址直接 return '/login'。
  5. 白名单目前只写登录页,后续注册页、第三方回调页再按需加入。
  6. 调试要覆盖未登录、登录后、删除 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 router
ts
// 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 两级可选链;具体字段名以接口文档/类型注释为准。
  • 结构上分三块:头部(头像、名称)、数据统计(收藏/关注/积分/优惠券)、药品订单(四个徽标)。

常见错误 ​

  1. 现象:访问 / 只看到一片空白或页面不完整。 原因:布局容器的 children 配置了二级路由,但没有给一级路由加 redirect,/ 命中布局容器后没有二级组件可渲染。 修法:在布局容器规则上补 redirect: '/home'(或指向默认二级路由)。

  2. 现象:Tabbar 点击能跳转,但地址变了页面不切换 / 点击后刷新又回到首页。 原因:只在 van-tabbar 上加了 route,van-tabbar-item 漏写 to;或 to 里写的是相对路径、与二级路由 path 不一致。 修法:逐项核对每个 item 的 to 与路由表里二级路由 path 完全一致(/home、/article、/notify、/user)。

  3. 现象:图标一直是绿色,或一直灰色,切换标签时不变。 原因:#icon 插槽里把 name 写死成 xxx-index-active,没有用插槽作用域提供的 active 变量。 修法:#icon="{ active }" 解构出 active,用模板字符串动态拼接 active ? '-active' : '-default' 后缀。

  4. 现象:未登录状态下直接访问 /user、/notify 仍能进入页面。 原因:beforeEach 里写了判断但没 return 重定向地址;或白名单用 includes 判断时传错了值(如传了 to.name 而不是 to.path)。 修法:确认分支内 return '/login',且白名单数组存的是完整 path,判断用 to.path。

  5. 现象:登录页本身也被拦截,出现无限重定向 / 页面卡死。 原因:白名单里漏了 /login,无 token 访问登录页时又被重定向到登录页。 修法:白名单至少包含 /login,后续注册、回调页也一并加入。

  6. 现象:标签页标题显示 undefined 或拼接出 undefined优医问诊。 原因:to.meta.title 在部分路由(如重定向的 /、404)上未定义,直接拼接。 修法:取值时兜底空串 to.meta.title ?? '',并给 RouteMeta 扩展 title?: string 类型,避免无提示。

  7. 现象:进度条出现后不消失,一直停在半途。 原因:只在 beforeEach 里 start(),漏了 afterEach 的 done();或 afterEach 写在了 beforeEach 的 return 之后/被提前 return 跳过。 修法:afterEach 与 beforeEach 同级注册,且确保 afterEach 中无条件调用 done()。

  8. 现象:订单数量为 0 时,图标右上角仍显示一个“0”徽标。 原因:content 直接绑定了数值,0 是有效值,Badge 会照常渲染。 修法:content 绑定表达式里做判断,为 0 时给空字符串(或 undefined)以隐藏徽标。

练习 ​

  1. 在布局容器路由上故意删掉 redirect,然后访问 /,观察页面表现;再补回并验证,说明为什么必须配置重定向。
  2. 给 Tabbar 增加第五个 item(如“用药记录”),要求同时完成:新建二级路由组件、配置嵌套路由、添加 to、自定义图标并实现激活/失活切换。
  3. 把白名单从数组改成 Set 或正则匹配实现,保持行为不变,并额外把 /register 加入白名单后验证未被拦截。
  4. 给 404 页面补充 meta.title,并处理一个没有 meta.title 的路由,确认标题不会出现 undefined。
  5. 用 Omit 和 Pick 从已有的 User 类型派生出两个新类型:一个“不含 token 的用户基础信息”,一个“只含 id 与 account 的精简类型”,并在组件中用 ref 声明变量验证类型提示是否生效。

课件图示 ​

课件第4页:布局容器-组件与路由配置 一级路由:布局容器 / • 二级路由:首页组件 /ho

课件第6页:布局容器-Tabbar切换效果 tabbar组件 支持路由切换 自定义图标 图标

课件第10页:布局容器-页面标题 • 路由配置元信息 meta 中定义标题 • 路由全局后置守

课件第12页:布局容器-加载进度 开启进度条 beforeEach 结束进度条 afterEa

课件第8页:布局容器-访问权限控制 全局前置守卫变化: 访问权限控制实现: 路由跳转 没有t

课件第14页:个人中心-用户信息类型 TS内置类型 Pick 与 Omit Pick 可以从一

原视频对照 ​

#视频时长
1布局容器-组件与路由配置06:20
2布局容器-Tabbar切换效果10:56
3布局容器-访问权限控制07:58
4布局容器-页面标题08:41
5布局容器-加载进度07:03
6个人中心-用户信息类型09:46
7个人中心-用户信息渲染11:04

小结 ​

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