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

前端(Vue)tagsView(子标签页视图切换) 原理及通用解决方案

文章目录

  • tagsView 方案总结
  • tagsView 原理分析
  • 创建 tags 数据源
  • 生成 tagsView
  • tagsView 国际化处理
  • contextMenu 展示处理
  • contextMenu 事件处理
  • 处理 contextMenu 的关闭行为
  • 处理基于路由的动态过渡

tagsView 方案总结

整个 tagsView 整体来看就是三块大的内容:

  1. tagstagsView 组件
  2. contextMenucontextMenu 组件
  3. viewappmain 组件

再加上一部分的数据处理(Vuex)即可。

tagsView 原理分析

tagsView 可以分成两部分来去看:

  1. tags
  2. view

image.png
image.png
可以把这两者分开。tags 仅仅就是很简单的 tag 组件。
脱离了 tags 只看 views 就更简单了,所谓 views指的就是一个用来渲染组件的位置容器。

  1. 动画
  2. (数据)缓存

加上这两个功能之后可能会略显复杂,但是 官网已经帮助我们处理了这个问题
image.png
再把tagsview 合并起来思考。
实现方案:

  1. 创建 tagsView 组件:用来处理 tags 的展示
  2. 处理基于路由的动态过渡,在 tags 区域中进行:用于处理 view 的部分

整个的方案就是这么两大部,但是其中还需要处理一些细节相关的。
完整的方案为

  1. 监听路由变化,组成用于渲染 tags 的数据源
  2. 创建 tags 组件,根据数据源渲染 tag,渲染出来的 tags 需要同时具备
    1. 国际化 title
    2. 路由跳转
  3. 处理鼠标右键效果,根据右键处理对应数据源

image.png

  1. 处理基于路由的动态过渡

创建 tags 数据源

tags 的数据源分为两部分:

  1. 保存数据:视图层父级 组件中进行
  2. 展示数据:tags 组件中进行

所以 tags 的数据我们最好把它保存到 vuex 中(及localStorage)
创建 tags 数据源:监听路由的变化,监听到的路由保存到 Tags 数据中。

创建 tagsViewList

import { LANG, TAGS_VIEW } from '@/constant'
import { getItem, setItem } from '@/utils/storage'
export default {namespaced: true,state: () => ({...tagsViewList: getItem(TAGS_VIEW) || []}),mutations: {.../*** 添加 tags*/addTagsViewList(state, tag) {const isFind = state.tagsViewList.find(item => {return item.path === tag.path})// 处理重复【添加 tags,不要重复添加,因为用户可能会切换已经存在的 tag】if (!isFind) {state.tagsViewList.push(tag)setItem(TAGS_VIEW, state.tagsViewList)}}
},
actions: {}
}

视图层父级组件中监听路由的变化 (动态添加tag)
注意:并不是所有的路由都需要保存的,比如登录页面、404等
判断是否需要,创建工具函数 =>

const whiteList = ['/login', '/import', '/404', '/401']/*** path 是否需要被缓存* @param {*} path* @returns*/
export function isTags(path) {return !whiteList.includes(path)
}

image.png

<script setup>
import { watch } from 'vue'
import { isTags } from '@/utils/tags'
import { generateTitle } from '@/utils/i18n'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'const route = useRoute()/*** 生成 title*/
const getTitle = route => {let title = ''if (!route.meta) {// 处理无 meta 的路由,路径中最后一个元素作为titleconst pathArr = route.path.split('/')title = pathArr[pathArr.length - 1]} else {// 包含meta的,直接国际化处理即可title = generateTitle(route.meta.title)}return title
}/*** 监听路由变化*/
const store = useStore()
watch(route,(to, from) => {if (!isTags(to.path)) return// 保存需要保存的路由属性const { fullPath, meta, name, params, path, query } = tostore.commit('app/addTagsViewList', {fullPath,meta,name,params,path,query,title: getTitle(to)})},{// 组件初始化的时候也需被执行一次immediate: true}
)
</script>

生成 tagsView

创建 storetagsViewList 的快捷访问 (getters)

const getters = {token: state => state.user.token,//...tagsViewList: state => state.app.tagsViewList
}
export default getters

image.png

<template><div class="tags-view-container"><!-- 每个tag页面就对应一个router-link --><!-- router-link 有两种状态,一种是被选中的,另一种是不被选中的。绑定一个动态class =>  isActive(tag)  --><!-- 如果是当前被选中的这一项,它的颜色应该是当前的主题色。添加样式即可。 --><!-- to表示link跳转的地址 --><router-linkclass="tags-view-item":class="isActive(tag) ? 'active' : ''"  :style="{backgroundColor: isActive(tag) ? $store.getters.cssVar.menuBg : '',borderColor: isActive(tag) ? $store.getters.cssVar.menuBg : ''}"v-for="(tag, index) in $store.getters.tagsViewList":key="tag.fullPath":to="{ path: tag.fullPath }">{{ tag.title }}<!-- 未被选中的tag上出现一个X号 --><iv-show="!isActive(tag)"class="el-icon-close"@click.prevent.stop="onCloseClick(index)"/></router-link></div>
</template><script setup>
import { useRoute } from 'vue-router'
const route = useRoute()/*** 是否被选中*/
const isActive = tag => {return tag.path === route.path
}/*** 关闭 tag 的点击事件*/
const onCloseClick = index => {}
</script><style lang="scss" scoped>
.tags-view-container {height: 34px;width: 100%;background: #fff;border-bottom: 1px solid #d8dce5;box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);.tags-view-item {display: inline-block;position: relative;cursor: pointer;height: 26px;line-height: 26px;border: 1px solid #d8dce5;color: #495060;background: #fff;padding: 0 8px;font-size: 12px;margin-left: 5px;margin-top: 4px;&:first-of-type {margin-left: 15px;}&:last-of-type {margin-right: 15px;}&.active {color: #fff;&::before {content: '';background: #fff;display: inline-block;width: 8px;height: 8px;border-radius: 50%;position: relative;margin-right: 4px;}}// close 按钮.el-icon-close {width: 16px;height: 16px;line-height: 10px;vertical-align: 2px;border-radius: 50%;text-align: center;transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);transform-origin: 100% 50%;&:before {transform: scale(0.6);display: inline-block;vertical-align: -3px;}&:hover {background-color: #b4bccc;color: #fff;}}}
}
</style>

tagsView 国际化处理

tagsView 的国际化处理可以理解为修改现有 tagstitle
tags的数据都保存在了tagsViewList,它里的tile是啥类型语言,tag这里的名字就应该显示啥语言。
=>

  1. 监听到语言变化
  2. 国际化对应的 title 即可

store 中,创建修改 ttilemutations
给某个tag修改title,只需要触发该mutation即可。

/**
* 为指定的 tag 修改 title
*/
changeTagsView(state, { index, tag }) {state.tagsViewList[index] = tag // 更新最新的tagsetItem(TAGS_VIEW, state.tagsViewList)
}

在 路由视图的父组件 中监听语言变化

import { generateTitle, watchSwitchLang } from '@/utils/i18n'
/*** 国际化 tags*/
watchSwitchLang(() => {store.getters.tagsViewList.forEach((route, index) => {store.commit('app/changeTagsView', {index,tag: {...route,  // 解构route,覆盖掉title即可,其他不变title: getTitle(route)}})})
})

contextMenu 展示处理

image.png
contextMenu 为 鼠标右键事件

contextMenu 事件的处理分为两部分:

  1. contextMenu 的展示
    1. image.png
  2. 右键项对应逻辑处理
    1. image.png

先实现contextMenu 的展示

  1. 创建 ContextMenu 组件,作为右键展示部分

先简单实现测试下:
image.png

const visible = ref(false)
/*** 展示 menu*/
const openMenu = (e, index) => {visible.value = true
}

在router-link下进行基本的展示:
image.png
image.png
接下来实现:
1、绘制视图先不管位置,先处理视图部分
2、视图展示的位置 => 右键点击哪里就在哪里展示,而不是固定展示在一个位置上

1、contextMenu 的展示:

<template><ul class="context-menu-container"><!-- 创建三个li,以及国际化 --><li @click="onRefreshClick">{{ $t('msg.tagsView.refresh') }}</li><li @click="onCloseRightClick">{{ $t('msg.tagsView.closeRight') }}</li><li @click="onCloseOtherClick">{{ $t('msg.tagsView.closeOther') }}</li></ul>
</template><script setup>
import { defineProps } from 'vue'
// 操作具体哪个tag,做标记,创建props
defineProps({index: {type: Number,required: true}
})const onRefreshClick = () => {}const onCloseRightClick = () => {}const onCloseOtherClick = () => {}
</script><style lang="scss" scoped>
.context-menu-container {position: fixed;background: #fff;z-index: 3000;list-style-type: none;padding: 5px 0;border-radius: 4px;font-size: 12px;font-weight: 400;color: #333;box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);li {margin: 0;padding: 7px 16px;cursor: pointer;&:hover {background: #eee;}}
}
</style>

image.png
2、 在 tagsview 中控制 contextMenu 的展示
希望context的位置根据鼠标点击的位置移动。
鼠标右键的时候传递了event对象

<template><div class="tags-view-container"><el-scrollbar class="tags-view-wrapper"><!-- contextmenu.prevent右击事件 --><router-linkclass="tags-view-item":class="isActive(tag) ? 'active' : ''":style="{backgroundColor: isActive(tag) ? $store.getters.cssVar.menuBg : '',borderColor: isActive(tag) ? $store.getters.cssVar.menuBg : ''}"v-for="(tag, index) in $store.getters.tagsViewList":key="tag.fullPath":to="{ path: tag.fullPath }"@contextmenu.prevent="openMenu($event, index)">{{ tag.title }}<svg-iconv-show="!isActive(tag)"icon="close"@click.prevent.stop="onCloseClick(index)"></svg-icon></router-link></el-scrollbar><context-menuv-show="visible":style="menuStyle":index="selectIndex"></context-menu></div>
</template><script setup>import ContextMenu from './ContextMenu.vue'import { ref, reactive, watch } from 'vue'import { useRoute } from 'vue-router'...// contextMenu 相关const selectIndex = ref(0)const visible = ref(false)const menuStyle = reactive({left: 0,top: 0})/*** 展示 menu*/const openMenu = (e, index) => {const { x, y } = e // 事件对象中,得到鼠标点击的位置// 作为行内样式绑定menuStyle.left = x + 'px'menuStyle.top = y + 'px'// 点击项selectIndex.value = indexvisible.value = true}
</script>

contextMenu 事件处理

对于 contextMenu 的事件一共分为三个:

  1. 刷新
  2. 关闭右侧
  3. 关闭所有

刷新 =>
router.go(n) 是 Vue Router 提供的一个方法,它可以在浏览器的历史记录中前进或后退 n 步。 当 n 为正数时,router.go(n) 会前进 n 步;当 n 为负数时,会后退 n 步;当 n0 时,它会重新加载当前的页面。在 如下 中,router.go(0) 相当于刷新当前页面。

const router = useRouter()
const onRefreshClick = () => {router.go(0)
}

store 中,创建删除 tagsmutations,该 mutations 需要同时具备以下三个能力:

  1. 删除 “右侧”2. 删除 “其他”3. 删除 “当前”
/*** 删除 tag* @param {type: 'other'||'right'||'index', index: index} payload
*/
removeTagsView(state, payload) {if (payload.type === 'index') { // 删除当前项state.tagsViewList.splice(payload.index, 1)return} else if (payload.type === 'other') { // 保留自己,删掉它之前和之后state.tagsViewList.splice(payload.index + 1,state.tagsViewList.length - payload.index + 1)  // 删除它之后的所有的state.tagsViewList.splice(0, payload.index) // 删除它之前的} else if (payload.type === 'right') {state.tagsViewList.splice(payload.index + 1,state.tagsViewList.length - payload.index + 1) // 删除它之后的}setItem(TAGS_VIEW, state.tagsViewList) // 同步本地缓存(localStorage)
},

关闭右侧事件

const store = useStore()
const onCloseRightClick = () => {store.commit('app/removeTagsView', {type: 'right',index: props.index})
}

关闭其他

const onCloseOtherClick = () => {store.commit('app/removeTagsView', {type: 'other',index: props.index})
}

关闭当前(tagsview

/*** 关闭 tag 的点击事件*/
const store = useStore()
const onCloseClick = index => {store.commit('app/removeTagsView', {type: 'index',index: index})
}

处理 contextMenu 的关闭行为

其实就改变它的visible,visible为true就为bdoy添加关闭菜单的事件。

/*** 关闭 menu*/
const closeMenu = () => {visible.value = false
}/*** 监听变化*/
watch(visible, val => {if (val) {document.body.addEventListener('click', closeMenu)} else {document.body.removeEventListener('click', closeMenu)}
})

处理基于路由的动态过渡

处理基于路由的动态过渡  官方已经给出了示例代码,结合 router-viewtransition 我们可以非常方便的实现这个功能,除此之外再此基础上添加keep-alive。
image.png

<template><div class="app-main"><!-- 利用v-slot 解构一些值,作用域插槽语法,它允许子组件将数据传递给父组件,父组件通过这个作用域插槽能够接收子组件传递的数据,并可以根据这些数据动态地渲染内容或进行其他逻辑处理 --><!-- Component 是当前路由匹配的组件,route 是当前的路由对象,包含路径、参数、查询等信息。 --><router-view v-slot="{ Component, route }"><!-- 利用transition 指定动画效果 --><transition name="fade-transform" mode="out-in"><keep-alive><!-- 动态组件,动态渲染Component --><!-- :key="route.path" 用于强制 Vue 在路由变化时重新渲染组件。因为每个路径都是唯一的,所以 key 的变化会触发 Vue 重新创建组件实例,从而确保每个路由组件的独立性 --><component :is="Component" :key="route.path" /></keep-alive></transition></router-view></div>
</template>

动画

/* fade-transform */
/* 元素进入和离开视图时都会应用 */
.fade-transform-leave-active,
.fade-transform-enter-active {/* 表示元素的所有可动画属性在 0.5 秒内从初始状态过渡到最终状态。即:所有参与动画的属性(如 opacity 和 transform)都会在 0.5 秒内完成变化。 */transition: all 0.5s;
}/* 进入过渡的初始状态 */
.fade-transform-enter-from {/* 一开始是完全透明 */opacity: 0;/* 一开始是从它本应的位置向左偏移了 30 像素 */transform: translateX(-30px); 
}/* 离开过渡的结束状态 */
.fade-transform-leave-to {/*元素在离开时会变得完全透明 */opacity: 0;/*  元素在离开时会向右移动 30 像素 */transform: translateX(30px);
}

进入视图时:

  • 元素从 fade-transform-enter-from 状态开始,透明度为 0,向左偏移 30 像素。
  • 然后,在 0.5 秒内,元素的透明度逐渐增加到 1(完全可见),同时它从左边的位置平滑地移动到其正常位置。

离开视图时:

  • 元素开始时是正常位置和完全可见的状态。
  • fade-transform-leave-active 触发后,它在 0.5 秒内逐渐变得透明,同时向右移动 30 像素,直到完全消失。

应用场景

  • 这个动画效果通常用于在切换路由或显示/隐藏某个元素时,使得用户界面看起来更加流畅和动态。比如,当用户点击一个按钮切换页面内容时,当前页面内容会向右淡出,而新页面内容会从左边淡入,从而创建一种连贯的过渡效果。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Leetcode 第 408 场周赛题解
  • 数据结构之最短路径
  • 在Mac上打开UE4Editor
  • 爆改YOLOv8 | 利用CPA-Enhancer提高低照度物体检测(适用于雨,雪,雾天)
  • vscode开发uniapp项目教程
  • Nuxt3入门:介绍、项目安装和了解视图(第一节)
  • 【初学人工智能原理】【13】LSTM网络:自然语言处理实践
  • Python | Leetcode Python题解之第381题O(1)时间插入、删除和获取随机元素-允许重复
  • 机器学习:TF-IDF算法原理及代码实现
  • 聚观早报 | 苹果推出AI消除功能;比亚迪2024上半年营收
  • Spring八股文
  • react- native创建pdf
  • FPGA上板项目(三)——RAM测试
  • 一文认识数据库事务(ACID)
  • 论文解读:Prompt-aligned Gradient for Prompt Tuning
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  •  D - 粉碎叛乱F - 其他起义
  • Joomla 2.x, 3.x useful code cheatsheet
  • JS笔记四:作用域、变量(函数)提升
  • Laravel 中的一个后期静态绑定
  • linux学习笔记
  • SAP云平台里Global Account和Sub Account的关系
  • Sublime Text 2/3 绑定Eclipse快捷键
  • Vim Clutch | 面向脚踏板编程……
  • Windows Containers 大冒险: 容器网络
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 大整数乘法-表格法
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 记一次删除Git记录中的大文件的过程
  • 每天10道Java面试题,跟我走,offer有!
  • 深度解析利用ES6进行Promise封装总结
  • 深度学习入门:10门免费线上课程推荐
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 思考 CSS 架构
  • postgresql行列转换函数
  • ​LeetCode解法汇总518. 零钱兑换 II
  • #etcd#安装时出错
  • #include<初见C语言之指针(5)>
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (C11) 泛型表达式
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)关于pipe()的详细解析
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (自用)网络编程
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET Core中的去虚
  • .net 提取注释生成API文档 帮助文档
  • .net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段