导航守卫

@Guards
控制路由的访问权限,如:未登录,不可以下单、发帖、留言或领取优惠券,通常会引导用户/重定向到特定页面,如登录页面
完整路由属性和方法,请访问 Router
导航解析流程
导航被触发
在失活的组件里调用 beforeRouteLeave 守卫
调用全局的 beforeEach 守卫
在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)
在路由配置里调用 beforeEnter
解析异步路由组件
在被激活的组件里调用 beforeRouteEnter
调用全局的 beforeResolve 守卫(2.5+)
导航被确认
调用全局的 afterEach 钩子
触发 DOM 更新
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入

相当于是路由的生命周期函数

假设路由配置实例如下

const router = createRouter({ ... })
beforeEach

使用路由实例 router 注册一个全局前置守卫 beforeEach,执行函数 fn:接收3个参数:

to:进入的路由

from:离开的路由

next():是一个函数,表示是否允许,实现路由控制,如:未登录不允许访问,并引导|重定向到指定页面

正常访问;或不使用 next 形参
router.beforeEach((to, from, next) => {
  // ...
  next()
})
router.beforeEach((to, from,) => {
  // ...
})
不允许访问 - 简单粗暴,直接拒绝;不建议直接使用
router.beforeEach((to, from, next) => {
  if(to.path=='/order')
    next(false)
})
在 next() 中指定跳转的路由,引导到指定页面 - 最佳体验
router.beforeEach((to, from, next) => {
  if (to.path == '/order') {
    next({ name: 'home' })
  } else {
    next()
  }
})
如果使用了 next(),但是不执行,则路由 不会跳转,也就不会执行全局后置钩子 afterEach()
router.beforeEach((to, from, next) => {
    // ...   
})
[] 登录认证 - 借助登录 token 或本地存储的登录信息实现路由控制
router.beforeEach((to, from, next) => {
    console.log('hi');
    let token = localStorage.getItem('token')
    if (to.path == '/order' && !token) {
        next('/login')
    } else {
        next()
    }
})
[] 登录认证 - Supabase 中,采用 会话 Session 验证身份

思路:每次页面跳转时,获取元数据;如果需要验证就验证用户会话是否存在

创建 Supabase 项目
创建 Vue 项目
主页面 - 实现注册 sinUp、登录 signIn、注销 logOut 等逻辑
创建若干测试页面
配置路由 - routes,其中一个路由需要验证
{
  path: "/about",
  name: "about",
  component: () => import("@/views/AboutView.vue"),
  meta: {
    requiresAuth: true,
  },
},
注册用户到 Supabase - 使用用户元数据 metadata
const signUp = async () => {
  isLoading.value = true;
  let { data, error } = await supabase.auth.signUp({
    email: user.value.email,
    password: user.value.password,
    options: {
      data: {
        name: 'coffee',
        avatar: '/coffee.jpg',
      }
    }
  })

  if (error) {
    console.log(error);
    isLoading.value = false;
    return;
  }
  isLoading.value = false;
  console.log(data);
}
用户登录
const signIn = async () => {
  isLoading.value = true;
  let { data, error } = await supabase.auth.signInWithPassword({
    email: user.value.email,
    password: user.value.password
  })
  if (error) {
    console.log(error);
    isLoading.value = false;
    return;
  }
  isLoading.value = false;
  console.log(data);
}
配置路由守卫 - 判断会话是否有效;否则拒绝访问某些页面/视图;记得引入 supabase
router.beforeEach(async (to, from, next) => {
  if (to.meta.requiresAuth) {
    console.log("page requiresAuth");
    let { data, error } = await supabase.auth.getSession();
    if (data.session) {
      console.log("authenticated");
      next();
    } else {
      next({ name: "login" });
    }
  } else {
    next();
  }
});
其它
const logOut = async () => {
  isLoading.value = true;
  let { error } = await supabase.auth.signOut()

  if (error) {
    console.log(error);
    isLoading.value = false;
    return;
  }
  isLoading.value = false;
  console.log('log out');
}
改进

封装获取用户会话

afterEach
使用路由实例 router 注册一个全局后置守卫 afterEach,执行函数 fn,接收2个参数:to、from
不接受 next 函数也不会改变导航本身,因为导航已结束
router.afterEach((to, from) => {
  // ...
})
[] 统计访问量
beforeEnter
路由独享的守卫 - 局部路由使用守卫
路由配置时指定
针对特定路由的控制; 只在 进入 路由时触发
动态路由对应的多个路由之间不会触发,因为他们对应路由配置中同一条路由
路由守卫和状态管理
在 setup() 中,你不需要再做任何特别处理即可开箱使用 - 引入、实例、使用;这是因为全局共享一个 store 实例,main.js 中的 useStore() 给你的 app 自动注入了 pinia 实例
如果没有,则需要手动注入
//....                
import { createPinia } from 'pinia'

const pinia = createPinia()

app.use(pinia)
//这之后可以随意使用Pinia...
导航守卫运行在 Vue 应用初始化的早期阶段,此时可能还未创建或注入 Pinia store 实例;如果直接在路由中引入,会提示
"getActivePinia()" was called but there was no active Pinia.
更多信息,请访问 Pinia - 在 setup() 外部使用 store
[] 路由守卫和状态管理;实操 - 汉堡菜单
在路由文件中,引入仓库 menu.js
每次进入,创建菜单实例并设置菜单栏样式为隐藏
import { useMenuStore } from '@/stores/menu'

router.beforeEach((to, from, next) => {
    // 其它逻辑

    // MUST
    const store = useMenuStore();
    store.flag = false;
    next();
})

router.afterEach((to, from) => {
    // 其它逻辑

    if (to.path === from.path) {
        const store = useMenuStore()
        store.flag = false
    }
})
守卫中的两个 store 返回的是同一个 Pinia store 实例。虽然变量名相同,但是在不同的作用域中,引用的都是同一个对象实例,因此不会导致逻辑上的冲突
更多路由控制,应由后端服务器实现,前端不可靠
路由配置时,如果不是每个路由都指定 meta,使用时应先判断再使用
浏览器的返回和前进,不会触发守卫
Summary & Homework
Summary
afterEach
beforeEach
beforeEnter
Pinia*
Homework
[] 每次导航结束后,设置页面标题 title,并让页面滚动到顶部
为路由配置 meta 属性,指定 title
如果不是每个路由都配置 meta 属性,需判断再使用;必要时,应该指定默认值
{
  path: "/mine",
  name: "mine",
  component: () => import("../views/MineView.vue"),
  meta: { showNav: true, title: "我的 - 个人中心" },
},
router.afterEach((to, from) => {
  window.scrollTo(0, 0);
  if(to.meta.title){
    document.title = to.meta.title
  }
})
[] 添加页面 NProgress - 进度条 特效
导航前开始,导航后结束
router.beforeEach((to, from, next) => {
  NProgress.start();
  next()
})
    
router.afterEach((to, from) => {
  NProgress.done();
})