前期回顾
目录
👍 动态组件
顾名思义:动态显示指定的组件,动态组件不需要响应式。参考vue3动态组件的使用 - 掘金 (juejin.cn)
效果图:
代码示例:
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
// 需要加载的组件集合
const components = new Map<string, any>();
// 默认加载的组件名
const compName = 'MyTag';
// 引入组件
components.value.set(
'MyTag',
defineAsyncComponent(() => import('./a.vue'))
);
components.value.set(
'Item',
defineAsyncComponent(() => import('./b.vue'))
);
// 组件切换
function onClick() {
if (compName.value === 'MyTag') {
compName.value = 'Item';
} else {
compName.value = 'MyTag';
}
}
</script>
<template>
<div>
<el-button @click="onClick">改变组件</el-button>
<component :is="components.get(compName)"></component>
</div>
</template>
👀 动态路由
动态路由分为两种:
1:前端路由 - 使用 import.meta.glob 获取项目文件结构动态生成路由配置。如果是嵌套菜单可以利用微信小程序的写法,在文件下新建meatPage.ts,用来配置meat信息2:后端接口,使用生成 addRoute 将响应数据做递归放入路由示例中
前端路由:
更新中……
后端路由:
index.ts 静态路由,不需要权限的
/* 静态路由 :不需要权限的路由
* createWebHistory 与 createWebHashHistory 的区别
* createWebHistory:使用 HTML5 History API 的路由模式。注意:这种模式要玩好,还需要后台配置支持。后台不配置你本地开发没问题,
* 一旦部署上线,刷新就会出现 404。
* createWebHashHistory:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History API 的浏览器。
*
* 详细来说,createWebHistory 是基于 HTML5 History API 的,而 createWebHashHistory 是基于 URL 的 hash 值的。
* 在 Vue3 中,你可以通过调用 createWebHistory 或 createWebHashHistory 函数来创建路由。
*
* createWebHistory 监听浏览器的 history.pushState 和 history.replaceState 事件,并使用 HTML5 的 history API 来管理路由。
* 拥有更简单的 URL,不包含 "#" 符号。
*
* createWebHashHistory 使用浏览器的 window.location.hash 属性来管理路由。在 URL 中将使用 "#" 符号,例如:`http://localhost:300/#/about`。
* 变化时无需向服务器发送请求,对于只需要处理前端路由的应用程序来说,使用 Hash 模式足以满足需求。Hash 模式在传输数据量方面更小,而且兼容性最好。
*
* 在选择使用哪种模式之前,你应该考虑以下因素:
*
* - **历史访问记录管理**:createWebHistory 可以管理浏览历史记录,使浏览器的后退/前进按钮可用,而 createWebHashHistory 不支持这些功能。
* - **URL 文本可读性**:createWebHistory 生成的 URL 更具可读性,不包含任何无用信息,通常比 createWebHashHistory 生成的 URL 更优。
* - **部署环境**:如果你的应用程序必须在较旧的浏览器上运行(如 IE 11 等),则应使用 createWebHashHistory。
* 由于旧版浏览器不支持 HTML5 history API,使用 createWebHistory 可能会导致问题。
* - **服务器配置**:在使用 createWebHistory 时需要确保你的服务器(例如,Apache 或 Nginx)已正确配置,以避免服务端路由失败的问题。
* createWebHashHistory 不需要服务器配置,因为 URL 中的哈希符号是在客户端处理的,不会向服务器发送任何请求。
*
* 因此,如果你的应用程序仅使用前端路由,无需后退/前进按钮,或者你专注于支持现代浏览器,则应使用 createWebHistory。
* 否则,如果应用程序部署在旧的浏览器上,则应使用 createWebHashHistory。
*/
/*
RouteRecordRaw是Vue Router的一个类型定义,它用于描述路由配置的对象。它包含以下属性:
path:字符串,表示路由的路径。
name:字符串,表示路由的名称。
component:组件类型,表示路由所匹配的组件。
children:子路由配置数组,用于描述嵌套路由。
meta:对象,用于存储额外的路由元数据,例如需要验证用户权限的信息。
*/
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
// pinia路由
import pinia from "./modules/pinia-store";
// 默认静态路由,不需要权限的路由
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
name: "home",
meta: {
loading: true,
},
component: () => import("@/views/home-page/home-page.vue"),
},
{
path: "/about",
name: "about",
meta: {
loading: true,
},
component: () => import("@/views/about-page/about-page.vue"),
},
pinia,
{
// vue-router4动态加载的模式下,当我们在当前页面刷新浏览器时,会出现一个警告
// [Vue Router warn]: No match found for location with path
// 解决方法: 在路由配置中添加一个通配符的路由,用来匹配所有的路由地址 404
// 如果url找不到就会报404,必须放在路由页面最下面
path: "/:catchAll(.*)",
component: () => import("@/views/errors-view/not-found.vue"),
},
];
export const router = createRouter({
history: createWebHashHistory(),
routes,
});
动态路由、路由拦截
// 路由守卫 用来动态生成路由
import { router, routes } from "./index";
import { getLocalKey } from "@/utils/storage";
//引入main.ts中的app
import app from "../main";
const hideLoading = () => app.config.globalProperties.$Loading.hideLoading();
const showLoading = () => app.config.globalProperties.$Loading.showLoading();
import { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
let isRoutesGenerated = false; // 添加一个标志位,用来判断是否已经生成了动态路由
// 前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.loading) showLoading();
/**
* token 是登录成功得到的。如果用户本地模拟token,也会调用接口,如果token过期或者被非法篡改,会在axios的拦截器中进行处理。
*/
if (to.path === "/login") {
next();
return;
}
if (getLocalKey("token")) {
addRouters(next, to);
} else {
// 没token不是权限页面
if (!to.meta.isRelease) {
addRouters(next, to);
} else {
next({
path: "/login",
replace: true,
});
}
}
});
// 调用接口获取数据,动态生成路由
function addRouters(next: NavigationGuardNext, to: RouteLocationNormalized) {
//先执行的是 isRoutesGenerated,默认false,然后再取反。第一次进来是true,所以会执行里面的代码
if (!isRoutesGenerated) {
// 判断是否已经生成了动态路由
try {
// 从后台获取菜单 axios.get('/api/menu')
const menu: RouteItem[] = [
{
path: "/test1",
name: "test1",
meta: {
loading: true,
keepAlive: true,
},
component: () => import("@/views/dynamic-routing/index-test1.vue"),
},
{
path: "/test2",
name: "test2",
meta: {
loading: true,
keepAlive: true,
isRelease: true,
},
component: () => import("@/views/dynamic-routing/index-test2.vue"),
},
{
path: "/test3",
name: "test3",
meta: {
loading: true,
keepAlive: true,
isRelease: true,
},
component: () => import("@/views/dynamic-routing/index-test3.vue"),
},
{
path: "/menu",
name: "menu",
meta: {
loading: true,
keepAlive: true,
isRelease: true,
},
component: () => import("@/views/menu/index.vue"),
},
];
// 生成动态路由
generateRoutes(menu);
isRoutesGenerated = true; // 设置标志位为true,表示已经生成了动态路由
//解决动态路由刷新页面后,404 next()是放行,next({path:to.path,replace:true})是重定向
next({
path: to.path,
replace: true,
});
} catch (error) {
hideLoading();
new Error(error as string);
}
} else {
next();
}
}
// 根据菜单数据动态生成路由
function generateRoutes(menu: string | any[]): void {
for (let i = 0; i < menu.length; i++) {
const item = menu[i];
const {
path,
name,
meta: { loading, keepAlive, isRelease },
component,
} = item;
const route: RouteItem = {
path: path,
name: name,
meta: {
loading: loading,
keepAlive: keepAlive,
isRelease: isRelease,
},
component: component,
};
// 递归生成子路由
if (item.children && item.children.length > 0) {
route.children = generateRoutes(item.children);
}
// 追加在404页面前面
routes.splice(routes.length - 1, 0, route);
// 在路由中添加新路由
router.addRoute(route);
}
}
router.afterEach((to) => {
if (to.meta.loading) hideLoading();
});
export default router;
封装本地存储:
/**
* window.localStorage 浏览器永久缓存
* @method set 设置永久缓存
* @method get 获取永久缓存
* @method remove 移除永久缓存
* @method clear 移除全部永久缓存
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
const json = <string>window.localStorage.getItem(key);
// !null为true
if (!json) return null;
// 这里是防止 在本地直接修改了localStorage的值,不经过上面转换,导致JSON.parse报错
return JSON.parse(JSON.stringify(json));
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
/**
* window.sessionStorage 浏览器临时缓存
* @method set 设置临时缓存
* @method get 获取临时缓存
* @method remove 移除临时缓存
* @method clear 移除全部临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
const json = <string>window.sessionStorage.getItem(key);
if (!json) return null;
return JSON.parse(JSON.stringify(json));
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
},
};
/**
* 获取本地的key集合
* @method getLocalKey
* @param { string } key - 要获取的key值
* @param { object } type - 从那里获取 localStorage、sessionStorage
* @description 传入key值,返回匹配的key,不传返回全部key数组
* @returns { string } 返回匹配的key或者全部key数组
* @example
* > getLocalKey('token') // 返回local token
* > getLocalKey('token', localStorage) // 返回local token
* > getLocalKey('token', sessionStorage) // 返回session token
* > getLocalKey() // 返回全部key数组
* @author zk
* @createDate 2023/08/17 13:58:19
* @lastFixDate 2023/08/17 13:58:19
*/
export const getLocalKey = (
key?: string,
type: object = localStorage
): string[] | string | undefined => {
const keys = Object.keys(type);
if (!key) return keys;
if (keys.length > 0) {
for (let i = 0; i < keys.length; i++) {
const item = keys[i];
if (item.indexOf(key) > -1) {
return item;
}
}
}
return undefined;
};