前端路由缓存实现

场景:以一体化为例:目前页面涉及页签和大量菜单路由,用户想要实现页面缓存,即列表页、详情页甚至是编辑弹框页都要实现数据缓存。

方案:使用router-view的keep-alive实现 。

一、实现思路

1.需求梳理

需要缓存模块:

  • 打开页签的页面
  • 新增、编辑的弹框和抽屉等表单项
  • 列表页点击同一条数据的编辑页

无需缓存模块:

  • 首页
  • 登录页
  • 已关闭的页签页
  • 列表页点击不同数据的编辑页
  • 特殊声明无需缓存页

2.缓存的两种方式

2.1使用name方式

注意点:name是vue组件实例的name

include:需缓存的vue组件

exclude:不做缓存的vue组件

  <router-view v-slot="{ Component }"><keep-alive :include="tabKeepAliveNameList" :exclude="excludeNameList"><component :is="Component"></component></keep-alive></router-view>

2.2使用meta的keepAlive方式

通过v-if实现对应路由keepAlive为true的路由缓存

  <router-view v-slot="{ Component }"><keep-alive><component v-if="$route.meta.keepAlive" :key="$route.path" :is="Component" /></keep-alive><component v-if="!$route.meta.keepAlive" :key="$route.path" :is="Component" /></router-view>

2.3最终选择

采用1.1的vue组件实例的name方式

优点:

  • 精确控制:直接指定要缓存的组件名,颗粒度细,适合明确知道需要缓存的组件。
  • 静态匹配:匹配逻辑简单,性能较高,基于组件自身的name属性。
  • 组件独立性:不依赖路由配置,组件自身决定是否可被缓存。
  • 路由跳转:结合动态路由生成的name,方便页面使用name跳转。

2.4缓存实例的生命周期

请注意:

  • onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用。
  • 这两个钩子不仅适用于 <KeepAlive> 缓存的根组件,也适用于缓存树中的后代组件。
<script setup>
import { onActivated, onDeactivated } from 'vue'onActivated(() => {// 调用时机为首次挂载// 以及每次从缓存中被重新插入时
})onDeactivated(() => {// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用
})
</script>

3.pinia新增缓存维护字段

在pinia中新增keepAliveNameList: [],存入需要缓存的组件实例的name

import { defineStore } from "pinia"
import { loginOut } from "@/api/common.js"
import router from "@/router"export default defineStore("storeUser", {persist: {storage: sessionStorage,paths: ["keepAliveNameList"]},state: () => {return {keepAliveNameList: [],}},getters: {getUserInfo: state => {return state.userInfo}},actions: {async loginOut() {await loginOut()this.clearUserInfo()router.push({ path: "/login" })},clearUserInfo() {sessionStorage.clear()this.keepAliveNameList = [] // 缓存name数据}}
})

4.导航守卫

4.1全局前置守卫router.beforeEach

在跳转之前判断地址栏参数是否一致,不同则需要将to页的缓存去除,正常获取

例如:两次点击列表里的数据编辑按钮;点击同一条是需要缓存该条表单数据,点击不同条时候需要去除缓存重新获取

router.beforeEach(async (to, _from, next) => {// 对于地址栏变化的需要清空缓存if (userStore.tabStore[userStore.tabStore.findIndex(it => it.path === to.path)] && JSON.stringify(userStore.tabStore[userStore.tabStore.findIndex(it => it.path === to.path)].query) !== JSON.stringify(to.query)) {userStore.$patch(state => {state.refreshUrl = to.path})let oldName = userStore.keepAliveNameListuserStore.$patch(state => {state.keepAliveNameList = oldName.filter(it => it !== to.name)})}...next()
})
4.2全局解析守卫router.beforeResolve

在离开当前页签时候,将该页签进行数据缓存。结合上方的进入前判断,是否需要清除缓存,达成页签页面正确的区分加入缓存和清除缓存

注意:此时可通过路由的方式获取到路由name;但需要保证路由name同vue组件的name一致(目前通过脚本实现一致)

router.beforeResolve((to, from, next) => {const { userStore } = useStore()let keepAliveName = from.matched[from.matched.length - 1]?.namelet tabStoreList = (userStore.tabStore || []).map(ele => ele.name) // 页签集合if (!userStore.keepAliveNameList.includes(keepAliveName) && keepAliveName && tabStoreList.includes(keepAliveName)) {userStore.$patch(state => {state.keepAliveNameList.unshift(keepAliveName)})}next()
})

4.3清除缓存
  • 在关闭页签时候,需要将缓存keepAliveNameList当前页的name移除
  • 相同菜单,但是地址栏参数变化时候,也需要清除缓存(点击查看、编辑列表页不同数据)
// 关闭页签同时去除缓存
const deleteKeepAliveName = () => {userStore.$patch(state => {state.keepAliveNameList = tabStoreList.value.map(it => it.name)})
}

细节处理

1.vue组件设置name

问题:现有vue组件存在部分未设置name情况,需要统一设置name

方案:通过脚本,统一遍历src/view下的所有组件,有路由name的设置路由name,无路由的组件使用当前路径命名

优点:保证路由页面的name和组件实例name一致

1.1新增auto-set-component-name.mjs脚本

import constantRoutes from "./src/router/constant_routes.js"
import { generateRoutes } from "./src/router/static_routes.js"
// 动态添加路由添加
const dynamicRoutes = constantRoutes.concat(generateRoutes() || [])// 递归找对象
const findItem = (pathUrl, array) => {for (const item of array) {let componentPath// 使用示例componentPath = getComponentPath(item.component) ? getComponentPath(item.component).replace(/^@|\.vue$/g, '') : undefined// 检查当前项的id是否匹配if (componentPath === pathUrl) return item;// 如果有子节点则递归查找if (item.children?.length) {const result = findItem(pathUrl, item.children);if (result) return result; // 找到则立即返回}}return undefined; // 未找到返回undefined
}// 提取组件路径的正则表达式
const IMPORT_PATH_REGEX = /import\(["'](.*?)["']\)/;// 获取路径字符串
const getComponentPath = (component) => {if (!component?.toString) return null;const funcString = component.toString();const match = funcString.match(IMPORT_PATH_REGEX);return match ? match[1] : null;
};import fs from "fs"; // 文件系统模块,用于读写文件
import path from "path"; // 路径处理模块
import { fileURLToPath } from "url"; // 用于转换URL路径const __filename = fileURLToPath(import.meta.url); // 当前文件绝对路径
const __dirname = path.dirname(__filename); // 当前文件所在目录// 🔧 配置区 ============================================
const targetDir = path.join(__dirname, "src/views"); // 目标目录:当前目录下的src/views
const PATH_DEPTH = Infinity;  // 路径深度设置 自由修改数字:2→最后两级,3→最后三级,Infinity→全部路径
// =====================================================const toPascalCase = (str) => {return str// .replace(/[-_](.)/g, (_, c) => c.toUpperCase()) // 转换连字符/下划线后的字母为大写// .replace(/(^\w)/, (m) => m.toUpperCase()) // 首字母大写.replace(/\.vue$/, ""); // 移除.vue后缀
};const processDirectory = (dir) => {const files = fs.readdirSync(dir, { withFileTypes: true }); // 读取目录内容console.log('%c【' + 'dir' + '】打印', 'color:#fff;background:#0f0', dir)files.forEach((file) => {const fullPath = path.join(dir, file.name); // 获取完整路径file.isDirectory() ? processDirectory(fullPath) : processVueFile(fullPath); // 递归处理目录,直接处理文件});
};const processVueFile = (filePath) => {if (path.extname(filePath) !== ".vue") return; // 过滤非Vue文件// 生成组件名逻辑const relativePath = path.relative(targetDir, filePath);console.log('%c【' + 'targetDir' + '】打印', 'color:#fff;background:#0f0', targetDir)console.log('%c【' + 'filePath' + '】打印', 'color:#fff;background:#0f0', filePath)console.log('%c【' + 'relativePath' + '】打印', 'color:#fff;background:#0f0', relativePath)const pathSegments = relativePath.split(path.sep)  // 按路径分隔符拆分.slice(-PATH_DEPTH)  // 根据配置截取路径段.map((segment) => toPascalCase(segment)); // 转换为PascalCaseconst vuePath = '/views/' + pathSegments.join("/"); // 拼接成最终组件名let componentName = findItem(vuePath, dynamicRoutes)?.name ? findItem(vuePath, dynamicRoutes)?.name : vuePathconsole.log(filePath, componentName);let content = fs.readFileSync(filePath, "utf8"); // 文件内容处理const oldContent = content; // 保存原始内容用于后续对比const scriptSetupRegex = /<script\s+((?:.(?!\/script>))*?\bsetup\b[^>]*)>/gim; // 灵活匹配找到scriptlet hasDefineOptions = false; // 标识是否找到defineOptions// 处理已存在的 defineOptionsconst defineOptionsRegex = /defineOptions\(\s*{([\s\S]*?)}\s*\)/g;content = content.replace(defineOptionsRegex, (match, inner) => {hasDefineOptions = true; // 标记已存在defineOptions// 替换或添加 name 属性const nameRegex = /(name\s*:\s*['"])([^'"]*)(['"])/;let newInner = inner;if (nameRegex.test(newInner)) { // 存在name属性时替换newInner = newInner.replace(nameRegex, `$1${componentName}$3`);console.log(`✅ 成功替换【name】: ${componentName} → ${filePath}`);} else { // 不存在时添加newInner = newInner.trim() === ""? `name: '${componentName}'`: `name: '${componentName}',\n${newInner}`;console.log(`✅ 成功添加【name】: ${componentName} → ${filePath}`);}return `defineOptions({${newInner}})`; // 重组defineOptions});// 新增 defineOptions(如果不存在)if (!hasDefineOptions) {content = content.replace(scriptSetupRegex, (match, attrs) => {return `<script ${attrs}>
defineOptions({name: '${componentName}'
})`;});console.log(`✅ 成功添加【defineOptions和name】: ${componentName} → ${filePath}`);}// 仅在内容变化时写入文件if (content !== oldContent) {fs.writeFileSync(filePath, content);// console.log(`✅ 成功更新 name: ${componentName} → ${filePath}`);}
};processDirectory(targetDir);
console.log("🎉 所有 Vue 组件 name 处理完成!");

1.2通过执行pnpm setName脚本命令,给每个vue组件设置name

    "dev": "pnpm setName && vite --mode beta --host","setName": "node auto-set-component-name.mjs",

2.路由跳转

问题:因为涉及到动态路由,导致原先跳转到具体菜单页的逻辑不可行,路径是不固定的。

方案:使用路由name跳转;通过需要跳转的文件路径,找到对应跳转的路由name即可

router.push({ name: ComponentA })

3.弹框类缓存处理

问题:默认弹框、抽屉、删除确认框的遮罩层是全局的,当弹框存在时会阻挡点击菜单或页签进行跳转

方案:给jg-dialog弹框设置挂载属性,通过append-to将其挂载在某个具体div下,解决遮罩区域

代码:

    :append-to-body="false":append-to="'.append_to_class'" 

修改前:

修改后:

弹框:

抽屉:

删除确认框:

4.tab类切换缓存

方案:使用<keep-alive>和<component>动态组件实现

<template><el-space class="wbg pt pl"><el-radio-group v-model="activeName"><el-radio-button label="巡检类" value="1" /><el-radio-button label="巡检项" value="2" /></el-radio-group></el-space><!-- <inspect-cate v-if="activeName == '1'"></inspec t-cate><inspect-item v-else></inspect-item> --><!-- 采用组件缓存 注释上方切换刷新--><keep-alive><component :is="componentName[activeName]"></component></keep-alive>
</template><script setup lang="jsx">
defineOptions({name: 'InspectionParam'
})
import { ref, shallowRef } from "vue"
import InspectCate from "./components/inspectCate.vue"
import InspectItem from "./components/inspectItem.vue"
const activeName = ref("1")
const componentName = ref({'1': shallowRef(InspectCate),'2': shallowRef(InspectItem),
})
</script><style lang="scss" scoped></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/77246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Buildroot编译过程中下载源码失败

RK3588编译编译一下recovery&#xff0c;需要把buildroot源码编译一遍。遇到好几个文件都下载失败&#xff0c;如下所示 pm-utils 1.4.1这个包下载失败&#xff0c;下载地址http://pm-utils.freedesktop.org/releases 解决办法&#xff0c;换个网络用windows浏览器下载后&…

Operator 开发入门系列(一):Hello World

背景 我们公司最近计划将产品迁移到 Kubernetes 环境。 为了更好地管理和自动化我们的应用程序&#xff0c;我们决定使用 Kubernetes Operator。 本系列博客将记录我们学习和开发 Operator 的过程&#xff0c;希望能帮助更多的人入门 Operator 开发。 目标读者 对 Kubernete…

Java基础知识面试题(已整理Java面试宝典pdf版)

什么是Java Java是一门面向对象编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论…

科学视角下的打坐:身心获益的实证探究

在快节奏的现代生活中&#xff0c;人们在追求物质丰富的同时&#xff0c;也愈发关注身心的健康与平衡。古老的打坐修行方式&#xff0c;正逐渐走进科学研究的视野&#xff0c;并以大量实证数据展现出对人体多方面的积极影响。​ 什么是打坐&#xff1a; 打坐是一种养生健身法…

javaSE————网络编程套接字

网络编程套接字~~~~~ 好久没更新啦&#xff0c;蓝桥杯爆掉了&#xff0c;从今天开始爆更嗷&#xff1b; 1&#xff0c;网络编程基础 为啥要有网络编程呢&#xff0c;我们进行网络通信就是为了获取丰富的网络资源&#xff0c;说实话真的很神奇&#xff0c;想想我们躺在床上&a…

MySQL性能调优(三):MySQL中的系统库(mysql系统库)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 MySQL中的系统库1.5.Mysql中mysql系统库1.5.1.权限系统表1.5.2.统计信息表1.5.2.1.innodb_table_stats1.5.2.2.innodb_index_stats 1.5.3.日志记录表1.5.3.1. general_log1.5.3.2. slow_log 1.5.4.InnoDB中的…

多个路由器互通(静态路由)无单臂路由(简单版)

多个路由器互通&#xff08;静态路由&#xff09;无单臂路由&#xff08;简单版&#xff09; 开启端口并配ip地址 维护1 Router>en Router#conf t Router(config)#int g0/0 Router(config-if)#no shutdown Router(config-if)#ip address 192.168.10.254 255.255.255.0 Ro…

关于 AI驱动的智慧家居、智慧城市、智慧交通、智慧医疗和智慧生活 的详细解析,涵盖其定义、核心技术、应用场景、典型案例及未来趋势

以下是关于 AI驱动的智慧家居、智慧城市、智慧交通、智慧医疗和智慧生活 的详细解析&#xff0c;涵盖其定义、核心技术、应用场景、典型案例及未来趋势&#xff1a; 一、AI智慧家居 1. 定义与核心功能 定义&#xff1a;通过AI与物联网&#xff08;IoT&#xff09;技术&#…

【ESP32|音频】一文读懂WAV音频文件格式【详解】

简介 最近在学习I2S音频相关内容&#xff0c;无可避免会涉及到关于音频格式的内容&#xff0c;所以刚开始接触的时候有点一头雾水&#xff0c;后面了解了下WAV相关内容&#xff0c;大致能够看懂wav音频格式是怎么样的了。本文主要为后面ESP32 I2S音频系列文章做铺垫&#xff0…

端侧大模型综述On-Device Language Models: A Comprehensive Review

此为机器翻译&#xff0c;仅做个人学习使用 设备端语言模型&#xff1a;全面回顾 DOI&#xff1a;10.48550/arXiv.2409.00088 1 摘要 大型语言模型 &#xff08;LLM&#xff09; 的出现彻底改变了自然语言处理应用程序&#xff0c;由于减少延迟、数据本地化和个性化用户体验…

推流265视频,网页如何支持显示265的webrtc

科技发展真快&#xff0c;以前在网页上&#xff08;一般指谷歌浏览器&#xff09;&#xff0c;要显示265的视频流&#xff0c;都是很鸡肋的办法&#xff0c;要么转码&#xff0c;要么用很慢的hls&#xff0c;体验非常不好&#xff0c;而今谷歌官方最新的浏览器已经支持265的web…

redis的sorted set的应用场景

Redis 的 Sorted Set&#xff08;有序集合&#xff0c;简称 ZSet&#xff09; 结合了 Set 的去重特性 和 按分数&#xff08;score&#xff09;排序 的特性&#xff0c;非常适合需要 高效排序 或 范围查询 的场景。以下是它的典型应用场景及示例&#xff1a; 实时排行榜 场景&…

18-21源码剖析——Mybatis整体架构设计、核心组件调用关系、源码环境搭建

学习视频资料来源&#xff1a;https://www.bilibili.com/video/BV1R14y1W7yS 文章目录 1. 架构设计2. 核心组件及调用关系3. 源码环境搭建3.1 测试类3.2 实体类3.3 核心配置文件3.4 映射配置文件3.5 遇到的问题 1. 架构设计 Mybatis整体架构分为4层&#xff1a; 接口层&#…

未启用CUDA支持的PyTorch环境** 中使用GPU加速解决方案

1. 错误原因分析 根本问题&#xff1a;当前安装的PyTorch是CPU版本&#xff0c;无法调用GPU硬件加速。当运行以下代码时会报错&#xff1a;model YOLO("yolov8n.pt").to("cuda") # 或 .cuda()2. 解决方案步骤 步骤1&#xff1a;验证CUDA可用性 在Pyth…

JVM-基于Hotspot

前言 Java虚拟机&#xff08;Java Virtual Machine简称JVM&#xff09;是运行所有Java程序的抽象计算机&#xff0c;是Java语言的运行环境&#xff0c;其主要任务为将字节码装载到内部&#xff0c;解释/编译为对应平台上的机器指令执行。 Java虚拟机规范定义了一个抽象的——…

智能合约安全审计平台——可视化智能合约漏洞扫描

目录 可视化智能合约漏洞扫描 —— 理论、实践与安全保障1. 引言2. 理论背景与漏洞原理2.1 智能合约简介2.2 常见漏洞类型2.3 漏洞扫描与安全评估原理3. 系统架构与工作流程3.1 系统总体架构3.2 模块说明4. 漏洞扫描流程详解4.1 代码上传与静态解析4.2 漏洞模式检测4.3 风险评估…

【MySQL数据库】数据类型详解

目录 数据类型tinyint类型(整形)bit类型小数浮点数 float、doubledecimal 字符串类型charvarcharchar与varchar的比较 日期时间类型enum和set总结 数据类型 tinyint类型(整形) 例&#xff1a; mysql> create table tt1(num tinyint);mysql> insert into tt1 values(1)…

咪咕MG101_晨星MSO9380芯片_安卓5.1.1_免拆卡刷固件包

咪咕MG101_晨星MSO9380芯片_安卓5.1.1_免拆卡刷固件包&#xff08;内有教程&#xff09; 刷机教程简单说明&#xff1a; 1、把下载好的刷机包&#xff0c;U盘里建立一个upgrade文件夹&#xff0c;固件放入此文件夹里&#xff0c;放入U盘中&#xff0c;注意升级包为压缩包不要对…

CS61A:STRING REPRESENTATION

Python 规定所有对象都应该产生两种不同的字符串表示形式&#xff1a;一种是人类可解释的文本&#xff0c;另一种是 Python 可解释的表达式。字符串的构造函数 str 返回一个人类可读的字符串。在可能的情况下&#xff0c;repr 函数会返回一个计算结果相等的 Python 表达式。rep…

LangChain缓存嵌入技术完全指南:CacheBackedEmbedding原理与实践(附代码示例)

一、嵌入缓存技术背景与应用场景 1.1 为什么需要嵌入缓存&#xff1f; 算力消耗问题&#xff1a;现代嵌入模型&#xff08;如text-embedding-3-small&#xff09;单次推理需要约0.5-1秒/文本 资源浪费现状&#xff1a;实际业务中约30%-60%的文本存在重复计算 成本压力&#…