本文主要通过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方法即可。