0


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

文章目录

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
再把

tags

view

合并起来思考。
实现方案:

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

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

  1. 监听路由变化,组成用于渲染 tags 的数据源
  2. 创建 tags 组件,根据数据源渲染 tag,渲染出来的 tags 需要同时具备 1. 国际化 title2. 路由跳转
  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'exportdefault{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
 */exportfunctionisTags(path){return!whiteList.includes(path)}

image.png

<scriptsetup>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
 */constgetTitle=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 }= to
    store.commit('app/addTagsViewList',{
      fullPath,
      meta,
      name,
      params,
      path,
      query,title:getTitle(to)})},{// 组件初始化的时候也需被执行一次immediate:true})</script>

生成 tagsView

创建

store

tagsViewList

的快捷访问 (getters)

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

image.png

<template><divclass="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><scriptsetup>import{ useRoute }from'vue-router'const route =useRoute()/**
 * 是否被选中
 */constisActive=tag=>{return tag.path === route.path
}/**
 * 关闭 tag 的点击事件
 */constonCloseClick=index=>{}</script><stylelang="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

的国际化处理可以理解为修改现有

tags

title


tags的数据都保存在了tagsViewList,它里的tile是啥类型语言,tag这里的名字就应该显示啥语言。
=>

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

store

中,创建修改

ttile

mutations

给某个

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
 */constopenMenu=(e, index)=>{
  visible.value =true}

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

1、

contextMenu

的展示:

<template><ulclass="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><scriptsetup>import{ defineProps }from'vue'// 操作具体哪个tag,做标记,创建propsdefineProps({index:{type: Number,required:true}})constonRefreshClick=()=>{}constonCloseRightClick=()=>{}constonCloseOtherClick=()=>{}</script><stylelang="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><divclass="tags-view-container"><el-scrollbarclass="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><scriptsetup>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
 */constopenMenu=(e, index)=>{const{ x, y }= e // 事件对象中,得到鼠标点击的位置// 作为行内样式绑定
    menuStyle.left = x +'px'
    menuStyle.top = y +'px'// 点击项
    selectIndex.value = index
    visible.value =true}</script>

contextMenu 事件处理

对于

contextMenu

的事件一共分为三个:

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

刷新 =>

router.go(n)

是 Vue Router 提供的一个方法,它可以在浏览器的历史记录中前进或后退

n

步。 当

n

为正数时,

router.go(n)

会前进

n

步;当

n

为负数时,会后退

n

步;当

n

0

时,它会重新加载当前的页面。在 如下 中,

router.go(0)

相当于刷新当前页面。

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

store

中,创建删除

tags

mutations

,该

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}elseif(payload.type ==='other'){// 保留自己,删掉它之前和之后
      state.tagsViewList.splice(
        payload.index +1,
        state.tagsViewList.length - payload.index +1)// 删除它之后的所有的
      state.tagsViewList.splice(0, payload.index)// 删除它之前的}elseif(payload.type ==='right'){
      state.tagsViewList.splice(
        payload.index +1,
        state.tagsViewList.length - payload.index +1)// 删除它之后的}setItem(TAGS_VIEW, state.tagsViewList)// 同步本地缓存(localStorage)},

关闭右侧事件

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

关闭其他

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

关闭当前(

tagsview

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

处理 contextMenu 的关闭行为

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

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

处理基于路由的动态过渡

处理基于路由的动态过渡 官方已经给出了示例代码,结合

router-view

transition

我们可以非常方便的实现这个功能,除此之外再此基础上添加keep-alive。
image.png

<template><divclass="app-main"><!-- 利用v-slot 解构一些值,作用域插槽语法,它允许子组件将数据传递给父组件,父组件通过这个作用域插槽能够接收子组件传递的数据,并可以根据这些数据动态地渲染内容或进行其他逻辑处理 --><!-- Component 是当前路由匹配的组件,route 是当前的路由对象,包含路径、参数、查询等信息。 --><router-viewv-slot="{ Component, route }"><!-- 利用transition 指定动画效果 --><transitionname="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 像素,直到完全消失。

应用场景

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

本文转载自: https://blog.csdn.net/u013946061/article/details/141573533
版权归原作者 溜_x_i_a_o_迪 所有, 如有侵权,请联系我们删除。

“前端(Vue)tagsView(子标签页视图切换) 原理及通用解决方案”的评论:

还没有评论