前端路由緩存實現

場景:以一體化為例:目前頁面涉及頁簽和大量菜單路由,用戶想要實現頁面緩存,即列表頁、詳情頁甚至是編輯彈框頁都要實現數據緩存。

方案:使用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.pswp.cn/bicheng/77246.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77246.shtml
英文地址,請注明出處:http://en.pswp.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%的文本存在重復計算 成本壓力&#…