vue-router + pinia 实现仅限应用内导航功能

3,651 阅读2分钟

本文主要通过vue-router的拦截器以及新兴的状态管理工具pinia,实现禁止用户通过地址栏访问任意页面,仅向用户开放应用内的导航功能。

要实现的效果

  • 用户在地址栏输入地址访问任何页面都会跳到首页
  • 使用浏览器前进后退按钮也会重定向到首页
  • 仅允许用户使用应用内提供的导航按钮来访问页面

实现思路

  • 封装vue-router的push和back方法,在调用push和back之前,向store存入一个标识。
  • 在vue-router的前置拦截器beforeEach检查store是否存在标识,如果没有则重定向到首页
  • 在后置拦截器afterEach清除标识
  • 相当于在做应用导航前先给一个凭证,在经过路由守卫检查时,凭证存在则成功通过,然后立即销毁凭证。而其他方式触发的路由行为则没有凭证,就会在路由守卫这一关被拦截下来。
  • 这个做法可以区分出路由行为是应用内触发的,还是浏览器地址栏或前进后退按钮触发的。

依赖包版本

"dependencies": {
    "pinia": "^2.0.11",
    "vue": "^3.2.4",
    "vue-router": "^4.0.11"
  },

脚手架使用vite

"devDependencies": {
    "@types/node": "^16.9.6",
    "@vitejs/plugin-vue": "^2.0.0",
    "@vue/compiler-sfc": "^3.2.4",
    "typescript": "^4.4.4",
    "vite": "^2.7.13",
    "vue-tsc": "^0.29.8"
  }

store

import { defineStore } from "pinia";
​
export const useStore = defineStore("app", {
  state: () => {
    return {
      token: null, // 凭证
    };
  },
  actions: {
    setToken(token: any) {
      this.token = token;
    },
  },
});
​

pinia用起来比vuex简洁,只需要state和actions

router

import {
  createRouter,
  createWebHistory,
  RouteLocationNormalized,
  RouteRecordRaw,
} from "vue-router";
import { useStore } from "@/store";
// 这里如果写成 const store = useStore()会报错,原因是pinia只能在运行时使用
let store: any = null;
​
export const routes: RouteRecordRaw[] = [
  {
    path: "/",
    redirect: "/home",
  },
  {
    path: "/home",
    component: () => import("@/pages/home.vue"),
  },
  {
    path: "/mobile",
    component: () => import("@/pages/mobile.vue"),
  },
  {
    path: "/*",
    component: () => import("@/pages/404.vue"),
  },
]
​
// 创建historey模式路由
export const router = createRouter({
  history: createWebHistory(),
  routes,
});
​
// 前置拦截器
const interceptor = (
  to: RouteLocationNormalized,
  from: RouteLocationNormalized
) => {
  // 初始化store放在这里
  if (store === null) {
    store = useStore();
  }
  // 白名单
  if (to.path === "/home") {
    return true;
  }
  // 检查权限
  if (store.token === null) {
    return {
      path: "/home",
      replace: true, // 重定向
    };
  }
  // 取消导航
  // return false
  // 不返回或返回true 放行
  // return true
};
​
// 注册全局前置钩子
router.beforeEach(interceptor);
// 注册全局后置钩子
router.afterEach(
  (to: RouteLocationNormalized, from: RouteLocationNormalized) => {
    if (store === null) {
      store = useStore();
    }
    // 销毁凭证
    store.setToken(null)
  }
);
​

main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { router } from "@/router";
import { createPinia } from "pinia";
const app = createApp(App);
​
// 安装路由模块
app.use(router);
// 安装状态管理pinia
app.use(createPinia());
​
app.mount("#app");
​

utils.ts

import {useStore} from '@/store'
let appStore: any = null// 封装push
export const nav = (to: RouteLocationRaw) => {
  if (appStore === null) {
    appStore = useStore()
  } 
  appStore.setToken("token");
  router.push(to);
}
​
// 封装back
export const back = () => {
  if (appStore === null) {
    appStore = useStore()
  } 
  appStore.setToken("token");
  router.back()
}

使用时,只需要用封装的方法替代原来的push和back方法即可。