导航守卫

@Guards
引言 Introduction
. 控制路由的访问权限,如:未登录,不可以下单、发帖、留言或领取优惠券,通常会引导用户/重定向到特定页面,如登录页面
. 完整路由属性和方法,请访问 Router
过程 Workflow

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

导航被触发
在失活的组件里调用 beforeRouteLeave 守卫
调用全局的 beforeEach 守卫
在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)
在路由配置里调用 beforeEnter
解析异步路由组件
在被激活的组件里调用 beforeRouteEnter
调用全局的 beforeResolve 守卫(2.5+)
导航被确认
调用全局的 afterEach 钩子
触发 DOM 更新
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
全局前置守卫 beforeEach()

. 添加一个导航钩子,每次导航之前被执行

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

守卫参数
item desc
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) => {
    // ...   
})
全局后置守卫 afterEach()
. 添加一个导航钩子,它会在每次导航之后被执行
. 使用路由实例 router 注册一个全局后置守卫 afterEach,执行函数 fn,接收 to、from 2个参数;含义同上
. 不接受 next,也不会改变导航本身,因为导航已结束
router.afterEach((to, from) => {
  // ...
})
局部路由守卫 beforeEnter
. 某个路由独享的守卫;针对特定路由的控制
. 只在进入路由时触发
. 路由配置时指定
. 动态路由对应的多个路由之间不会触发,因为他们对应路由配置中同一条路由
. 建议和其它属性使用语法保持一致
1. 使用箭头函数表达式定义的函数,并将其作为属性值赋给 beforeEnter
{
  path: "/mall",
  name: "mall",
  component: () => import("@/views/MallView.vue"),
  beforeEnter: (to, from, next) => {
    console.log({ to, from, next });
    next();
  }
}
2. 使用具名函数;传统的函数定义
{
  path: "/mall",
  name: "mall",
  component: () => import("@/views/MallView.vue"),
  beforeEnter(to, from, next) {
    console.log({ to, from, next });
    next();
  }
}
注意 Warning
. 在 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
Drill
[] 每次导航结束后,设置文档 页面标题 title,并让页面 滚动 scrollTo 到顶部
. 为路由配置 meta 属性,指定 title
. 如果不是每个路由都配置 meta 属性,需判断再使用;必要时,应该指定默认值
{
  path: "/mine",
  name: "mine",
  component: () => import("@/views/MineView.vue"),
  meta: { title: "我的 - 个人中心" },
},
router.afterEach((to, from) => {
  if(to.meta.title){
    document.title = to.meta.title;
    window.scrollTo(0, 0);
  }
})
如何更优雅的处理数据异常?
Summary
beforeEach()
afterEach()
beforeEnter
Homework
[] 添加页面 NProgress - 进度条 特效:导航前开始,导航后结束
[] 登录认证 - 借助登录 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');
}
改进

封装获取用户会话

[] 统计访问量