当前位置: 首页 > news >正文

九、vue3后台项目系列——tag标签逻辑

我们基于该系列中的第八文章中的tagsViewStore.js状态管理中的相关方法,进行开始阐述。

import { defineStore } from 'pinia';export const useTagsViewStore = defineStore('tagsView', {// 状态定义(替代Vuex的state)state: () => ({visitedViews: [],  // 已访问的标签页列表cachedViews: []    // 需要缓存的组件名称列表}),// 方法定义(替代Vuex的mutations和actions)actions: {// 添加访问过的标签页addVisitedView(view) {// 如果已存在相同路径的标签,直接返回if (this.visitedViews.some(v => v.path === view.path)) return;this.visitedViews.push(Object.assign({}, view, {title: view.meta.title || 'no-name'  // 确保标题存在}));},// 添加需要缓存的标签页addCachedView(view) {// 如果已缓存或设置了不缓存,直接返回if (this.cachedViews.includes(view.name) || view.meta.noCache) return;this.cachedViews.push(view.name);},// 同时添加访问和缓存标签addView(view) {this.addVisitedView(view);this.addCachedView(view);},// 删除指定访问标签delVisitedView(view) {return new Promise(resolve => {const index = this.visitedViews.findIndex(v => v.path === view.path);if (index > -1) {this.visitedViews.splice(index, 1);}resolve([...this.visitedViews]);  // 返回更新后的列表});},// 删除指定缓存标签delCachedView(view) {return new Promise(resolve => {const index = this.cachedViews.indexOf(view.name);if (index > -1) {this.cachedViews.splice(index, 1);}resolve([...this.cachedViews]);  // 返回更新后的列表});},// 同时删除访问和缓存标签delView(view) {return new Promise(resolve => {this.delVisitedView(view);this.delCachedView(view);resolve({visitedViews: [...this.visitedViews],cachedViews: [...this.cachedViews]});});},// 删除其他访问标签(保留当前和固定标签)delOthersVisitedViews(view) {return new Promise(resolve => {this.visitedViews = this.visitedViews.filter(v => {return v.meta.affix || v.path === view.path;});resolve([...this.visitedViews]);});},// 删除其他缓存标签(只保留当前标签)delOthersCachedViews(view) {return new Promise(resolve => {const index = this.cachedViews.indexOf(view.name);if (index > -1) {this.cachedViews = this.cachedViews.slice(index, index + 1);} else {this.cachedViews = [];}resolve([...this.cachedViews]);});},// 删除其他所有标签delOthersViews(view) {return new Promise(resolve => {this.delOthersVisitedViews(view);this.delOthersCachedViews(view);resolve({visitedViews: [...this.visitedViews],cachedViews: [...this.cachedViews]});});},// 删除所有访问标签(保留固定标签)delAllVisitedViews() {return new Promise(resolve => {const affixTags = this.visitedViews.filter(tag => tag.meta.affix);this.visitedViews = affixTags;resolve([...this.visitedViews]);});},// 清空所有缓存标签delAllCachedViews() {return new Promise(resolve => {this.cachedViews = [];resolve([...this.cachedViews]);});},// 删除所有标签delAllViews() {return new Promise(resolve => {this.delAllVisitedViews();this.delAllCachedViews();resolve({visitedViews: [...this.visitedViews],cachedViews: [...this.cachedViews]});});},// 更新访问标签信息updateVisitedView(view) {const index = this.visitedViews.findIndex(v => v.path === view.path);if (index > -1) {this.visitedViews[index] = Object.assign({}, this.visitedViews[index], view);}}}
});

一、先搞懂每个工具:大白话解释用法

1. Object.assign({}, view, { title: ... }):“复制 + 合并” 对象,避免改原数据

        把几个对象的 “属性” 合并到一个新对象里,就像 “拼积木” 一样,后面的对象会覆盖前面重复的属性。
比如你写的这行:Object.assign({}, view, { title: ... })

  • 第一个参数 {}:先创建一个空对象(相当于 “新积木底座”);
  • 第二个参数 view:把 view 里的所有属性(比如 path: '/dashboard'name: 'Dashboard'meta: { title: '首页' })复制到空对象里;
  • 第三个参数 { title: ... }:给新对象加一个 title 属性 —— 如果 view.meta.title 有值(比如 “首页”),就用它;如果没有(比如 meta 里没写 title),就用默认值 'no-name'
为啥要这么做?(关键!)

        因为 view 是路由对象(比如 $route),直接改 view.title 会 “污染原数据”(就像你借别人的笔记本,直接在上面写字,别人拿回去就变样了)。
用 Object.assign 搞个 “新对象”,既保留了 view 的原有信息,又加了我们需要的 title,还不影响原来的 view,安全又干净。

2. 为什么要写 new Promise(...):让 “异步操作” 能被控制

  Promise 就像 “快递单号”—— 你下单后(调用方法),不会马上拿到快递(结果),但有个单号能查进度,等快递到了(操作完成),再通知你处理。
比如 delVisitedView(view) 里的 new Promise(resolve => { ... resolve(...) })

  • 先执行删除标签的逻辑(splice 删掉数组里的标签);
  • 删完后,调用 resolve([...this.visitedViews]),把 “更新后的标签列表” 当 “快递” 发出去;
  • 组件里用的时候,可以写 await tagsViewStore.delVisitedView(view),意思是 “等删除完成,拿到新的标签列表后,再做下一步”(比如重新渲染标签栏)。
为啥标签页要这么做?

        删除标签是 “瞬间完成的同步操作”,但万一以后加了复杂逻辑(比如删除前要发请求问服务器 “能不能删”),Promise 能直接兼容。而且组件里用 await 能确保 “拿到最新的标签列表”,避免出现 “删了但页面没更” 的 bug—— 比如你删了标签,马上要更新页面显示,必须等删除完成才能更,Promise 就是干这个 “等” 的活。

3. 数组常用方法:大白话 + 场景举例

这些方法都是 “操作标签列表” 的核心工具,我结合标签页的逻辑讲:

方法大白话用法标签页里的场景举例
some(v => v.path === view.path)检查数组里 “有没有至少一个” 元素满足条件,有就返回 true,没有返回 false加新标签时,先查 visitedViews 里有没有 “路径一样” 的标签(比如已经有 /dashboard 了),有就不加了,避免重复。
filter(v => 条件)从数组里 “筛选” 出满足条件的元素,组成一个新数组(原数组不变)删除 “其他标签” 时,只保留两种标签:1. 固定标签(v.meta.affix: true,比如 “首页” 不能删);2. 当前正在看的标签(v.path === view.path),其他都滤掉。
indexOf(值)找 “值” 在数组里的 “位置序号”(索引),找到返回数字,没找到返回 -1删缓存标签时,先找 view.name 在 cachedViews 里的位置(比如 'Dashboard' 在数组里是第 0 位),找到才删(splice(位置, 1))。
findIndex(v => 条件)和 indexOf 类似,但能按 “自定义条件” 找位置(indexOf 只能找固定值)删访问标签时,按 “路径匹配” 找位置(v.path === view.path),因为 visitedViews 里存的是对象,indexOf 用不了,就用 findIndex
splice(位置, 1)从数组的 “指定位置” 删掉 “1 个元素”(会改原数组)删单个标签时,找到位置后,用 splice 把它从 visitedViews 或 cachedViews 里删掉。
slice(开始, 结束)从数组里 “切一段” 出来组成新数组(原数组不变),“结束” 位置不包含删其他缓存标签时,只保留 “当前标签”—— 比如 cachedViews 是 ['Dashboard', 'User', 'Role'],当前标签是 'User'(索引 1),slice(1,2) 就切出 ['User'],其他都扔了。
push(值)往数组 “末尾” 加一个值(会改原数组)加新标签时,把 Object.assign 搞出来的新标签对象,塞到 visitedViews 末尾;把组件名塞到 cachedViews 末尾。

二、标签页核心逻辑:为什么要这么设计?(从 0 理解)

        你第一次做标签页,肯定想知道 “这些逻辑到底是为了解决什么问题”。我先讲标签页的核心需求,再对应到代码逻辑:

标签页的核心需求(用户视角)

就像浏览器的标签栏一样:

  1. 点新菜单,加新标签(不重复加);
  2. 点标签上的叉,能删标签(固定标签不能删,比如 “首页”);
  3. 点 “关闭其他”,只留当前标签和固定标签;
  4. 点 “关闭全部”,只留固定标签;
  5. 切换标签时,之前的页面状态要保留(比如表单填了一半,切回来不能清空)—— 这就是 cachedViews 的作用。

代码逻辑对应需求:逐块讲明白

我们把 tagsView 的方法按 “需求场景” 拆,你就懂了:

场景 1:打开新页面,加标签(addViewaddVisitedViewaddCachedView

需求:点菜单打开 /user 页面,要加一个 “用户管理” 标签,不能重复加;如果页面设置了 “不缓存”(meta.noCache: true),就不保留状态。

代码逻辑

  1. addVisitedView(view):加 “访问标签” 到 visitedViews

    • 先查 visitedViews.some(v => v.path === view.path):有没有路径一样的标签?有就不加(避免重复);
    • 没有就用 Object.assign 搞个新对象(加 title),push 到数组里 —— 这样标签栏就能显示这个标签了。
  2. addCachedView(view):加 “缓存组件名” 到 cachedViews

    • 先查两个条件:cachedViews.includes(view.name)(有没有缓存过?)、view.meta.noCache(是不是设置了不缓存?);
    • 两个条件都不满足,才把 view.name(比如 'User'push 到 cachedViews—— 这样切换标签时,keep-alive 会缓存这个组件,状态不丢。
  3. addView(view):把上面两个方法打包,组件里调用一次就行(不用分别调两个)。

场景 2:删单个标签(delViewdelVisitedViewdelCachedView

需求:点标签上的叉,删掉这个标签,同时清除它的缓存。

代码逻辑

  1. delVisitedView(view):删 “访问标签”

    • 用 findIndex 找标签在 visitedViews 里的位置;
    • 找到就 splice(位置, 1) 删掉;
    • 用 Promise 返回新数组,组件拿到后更新标签栏显示。
  2. delCachedView(view):删 “缓存”

    • 用 indexOf 找组件名在 cachedViews 里的位置;
    • 找到就 splice 删掉 —— 这样下次打开这个页面,就不会用缓存的状态了。
  3. delView(view):打包两个删除方法,组件里调用一次,同时删标签和缓存。

场景 3:删其他标签(delOthersViews 系列)

需求:点标签的 “关闭其他”,只留当前标签和 “固定标签”(比如 “首页”,meta.affix: true),同时只缓存当前标签。

代码逻辑

  1. delOthersVisitedViews(view):只留当前和固定标签

    • 用 filter 筛选:v.meta.affix || v.path === view.path
      • v.meta.affix:固定标签,要保留;
      • v.path === view.path:当前标签,要保留;
      • 其他标签都被滤掉,visitedViews 就只剩这两种。
  2. delOthersCachedViews(view):只缓存当前标签

    • 用 indexOf 找当前组件名的位置;
    • 用 slice(位置, 位置+1) 切出 “当前组件名”,赋值给 cachedViews—— 其他缓存都清掉。
  3. delOthersViews(view):打包上面两个方法,组件里调用一次。

场景 4:删所有标签(delAllViews 系列)

需求:点 “关闭全部”,只留固定标签,清空所有缓存。

代码逻辑

  1. delAllVisitedViews():只留固定标签

    • 用 filter 筛选 v.meta.affix,只保留固定标签,其他都删掉。
  2. delAllCachedViews():清空缓存

    • 直接把 cachedViews 设为 [](空数组),所有缓存都没了。
  3. delAllViews():打包两个方法,组件里调用一次。

场景 5:更新标签信息(updateVisitedView

需求:有些页面标题可能动态变(比如编辑用户时,标题从 “编辑用户” 变成 “编辑张三”),标签栏要跟着更。

代码逻辑

  • 用 findIndex 找 “路径匹配” 的标签位置;
  • 用 Object.assign(this.visitedViews[index], view) 把新的 view 信息合并到旧标签里 —— 比如更新 title,标签栏就显示新标题了。

三、总结:标签页逻辑的核心思路

其实整个 tagsView 就是围绕两个数组做操作:

  • visitedViews:管 “标签栏显示哪些标签”,存的是标签的具体信息(路径、标题等);
  • cachedViews:管 “哪些页面要缓存状态”,存的是组件名(和 keep-alive 配合)。

所有方法都是 “按用户操作需求”,对这两个数组做 “加、删、改、筛”,再用 Promise 确保组件能拿到最新的数组状态 —— 本质就是 “用数组管理标签列表,用缓存保留页面状态”,和浏览器标签栏的逻辑一模一样,只是用代码实现了一遍。

你如果第一次写,建议先在组件里调用这些方法试试(比如加个按钮调用 addView,加个按钮调用 delView),看数组怎么变,慢慢就有感觉了~


文章转载自:

http://tkJSc1ka.pzjrm.cn
http://eKkxIGPT.pzjrm.cn
http://7BRLcPi1.pzjrm.cn
http://b9yityVm.pzjrm.cn
http://6NvlXLVP.pzjrm.cn
http://Slo8YYrJ.pzjrm.cn
http://sT1JlnKP.pzjrm.cn
http://3RmGzdjh.pzjrm.cn
http://jZPgiwMp.pzjrm.cn
http://26t1ADHP.pzjrm.cn
http://c8bDGQVG.pzjrm.cn
http://5lzXAO7g.pzjrm.cn
http://daWdPnMm.pzjrm.cn
http://OyDGmh4N.pzjrm.cn
http://Ar7sXmjR.pzjrm.cn
http://4vAUJzV5.pzjrm.cn
http://o6CqtXml.pzjrm.cn
http://wKMsFnrL.pzjrm.cn
http://FQte2b1g.pzjrm.cn
http://WWir3ZZB.pzjrm.cn
http://XXTewRNV.pzjrm.cn
http://0M2wqrZq.pzjrm.cn
http://6E9umqNE.pzjrm.cn
http://y518eqTp.pzjrm.cn
http://UwvdURxK.pzjrm.cn
http://PVMKQWRP.pzjrm.cn
http://7N3D69jh.pzjrm.cn
http://wczp8E47.pzjrm.cn
http://uogFSwNQ.pzjrm.cn
http://7BMkTY92.pzjrm.cn
http://www.dtcms.com/a/384135.html

相关文章:

  • 数据结构入门指南:计算机专业核心课精要
  • 贪心算法应用:DNS缓存问题详解
  • Python爬虫实战——使用NetNut网页解锁器获取亚马逊电商数据
  • 知识管理新范式——cpolar+Wiki.js打造企业级分布式知识库
  • NGUI--游戏登录、注册和服务器选择系统​​
  • C++ std::vector
  • 知微集:Transformer
  • 大数据毕业设计选题推荐-基于大数据的客户购物订单数据分析与可视化系统-Hadoop-Spark-数据可视化-BigData
  • C# JPG转PDF实现方案
  • 单变量单步时序预测 | TCN-BiLSTM时间卷积结合长短期记忆神经网络(MATLAB)
  • uniapp scroll-view 设置scrollTop无效
  • Day24_【深度学习(2)—PyTorch框架安装】
  • 未来汽车电气/电子(E/E)架构——迈向全新电气/电子范式之路上的复杂性掌控
  • 【Linux手册】mmap 接口:内存映射实现高效 IO 的
  • 如何使用代理 IP 实现爬虫代理
  • Ubuntu 录制 gif
  • Day24_【深度学习(3)—PyTorch使用—张量的创建和类型转换】
  • IP-Prefix 配置核心要点与典型应用场景
  • 为什么企业需要高防IP
  • 通过 DNS 解析SCAN IP
  • 网络:TCP/IP协议
  • 【后端】数据库四大范式详细解析
  • 银河麒麟部署mysql8.0并连接应用
  • Mysql中有那些锁
  • React 状态管理(手写实现react-redux)
  • C++:类和对象(下)
  • 智能驾驶再加速:L4 级 AI 系统落地难点与城市试点经验总结
  • 第4章:CPU进阶命令
  • brew@homebrew@linux通用包管理工具linuxbrew
  • NumPy 是 Python 科学计算的基石