先看看最终的效果吧,这样更有助于后面代码逻辑的实现。
布局实现效果分析
image.png我们看第一张图片,可以分析得出 整个导航标签组件(我们最终会封装成一个组件,便于后期其他项目的移植使用),由标签按钮和右侧弹窗层组合成。只要通过css布局 ,就可以实现右侧弹出层的效果,很简单。
image.png
从上面两张图片可以看出,整个导航标签组件,是实现了标签按钮过多后,上线可移动的效果的。
功能实现效果分析
image.png右侧弹窗层 实现了1.刷新 2.关闭右侧 3.关闭其他 4.关闭全部四个功能
- 刷新:只要通过 router.go(0) 刷新当前页就行
- 关闭右侧:因为我们整个导航标签肯定是由一个数组渲染的,通过数组的splice就能实现数据的“切割”。
- 关闭其他:通过也是通过实现的,只不过切割了当前位置的一前一后的数据,但这里需要注意一个点,就是我们这个项目一进来是自动定位到首页标签的,所以关闭其他和关闭全部是不能关闭到首页hone标签按钮的
-
关闭全部:把数组置未空就行了,但要定位到首页,其他按钮标签是由关闭功能的,但首页是没有关闭功能的,也就是首页是没有关闭当前页的功能的,
image.png
其他问题分析
- 路由过渡动画的实现
使用vue官方的过渡动画的方式
html
<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
- 数据的缓存
我们使用keep-alive结合vuex的实现方式
<template>
<div class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.name" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
好了,总体的实现思路到讲完了,我们直接看代码吧
监听路由,往数组里里增加标签,再通过循环router-view 实现渲染
/**
* 监听路由变化
*/
watch(
route,
(to, from) => {
// const cachedViews = store.getters.tagsViewList
// console.log("store.getters.tagsViewList",cachedViews)
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
}
)
<template>
<div class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.name" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
<script setup>
const cachedViews = computed(() => {
console.log(store.getters.tagsViewList.map((x) => x.name))
return store.getters.tagsViewList.map((x) => x.name)
})
</script>
tagsViewList的数据和操作动作全都封装到vuex里
import {TAGS_VIEW} from '@/constant'
import {getItem, setItem} from '@/utils/storage'
export default {
namespaced: true,
state: () => ({
sidebarOpened: true,
tagsViewList: getItem(TAGS_VIEW) || []
}),
mutations: {
triggerSidebarOpened(state) {
state.sidebarOpened = !state.sidebarOpened
},
/**
* 添加 tags
*/
addTagsViewList(state, tag) {
const isFind = state.tagsViewList.find(item => {
return item.path === tag.path
})
// 处理重复
if (!isFind) {
state.tagsViewList.push(tag)
setItem(TAGS_VIEW, state.tagsViewList)
}
},
/**
* 删除 tag
* @param {type: 'other'||'right'||'index', index: index} payload
*/
removeTagsView(state, payload) {
if (payload.type === 'index') {
state.tagsViewList.splice(payload.index, 1)
} else if (payload.type === 'other') {
state.tagsViewList.splice(
payload.index + 1,
state.tagsViewList.length - payload.index + 1
)
state.tagsViewList.splice(0, payload.index)
if(payload.index !=0){
//list第一位加入删除了的首页tag
state.tagsViewList.unshift({
fullPath:'/home',
meta:{title: '首页', affix: true},
name:'home',
params:{},
path:'/home',
query:{},
title: "首页"
})
}
} else if (payload.type === 'right') {
state.tagsViewList.splice(
payload.index + 1,
state.tagsViewList.length - payload.index + 1
)
} else if (payload.type === 'all') {
state.tagsViewList = []
}
setItem(TAGS_VIEW, state.tagsViewList)
},
},
actions: {}
}
右侧弹窗组件的实现
<template>
<ul class="context-menu-container">
<li @click="onRefreshClick">
刷新
</li>
<li @click="onCloseRightClick">
关闭右侧
</li>
<li @click="onCloseOtherClick">
关闭其他
</li>
<li @click="onCloseAllClick">
关闭全部
</li>
</ul>
</template>
<script setup>
import { defineProps } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
const props = defineProps({
index: {
type: Number,
required: true
}
})
const router = useRouter()
const store = useStore()
const onRefreshClick = () => {
router.go(0)
}
const onCloseRightClick = () => {
store.commit('app/removeTagsView', {
type: 'right',
index: props.index
})
}
const onCloseOtherClick = () => {
store.commit('app/removeTagsView', {
type: 'other',
index: props.index
})
}
const onCloseAllClick = () => {
store.commit('app/removeTagsView', {
type: 'all',
index: props.index
})
router.push('/')
}
</script>
具体的css代码很简单,就展示了,需要的私信我
整个tagView组件的实现
<template>
<div class="tags-view-container">
<el-scrollbar class="tags-view-wrapper">
<router-link
class="tags-view-item"
:class="isActive(tag) ? 'active' : ''"
:style="{
backgroundColor: isActive(tag) ? $store.getters.cssVar.menuActiveText : '',
borderColor: isActive(tag) ? $store.getters.cssVar.menuActiveText : ''
}"
v-for="(tag, index) in $store.getters.tagsViewList"
:key="tag.fullPath"
:to="{ path: tag.fullPath }"
@contextmenu.prevent="openMenu($event, index)"
>
{{ tag.title }}
<template v-if="!isAffiix(tag)">
<i
class="el-icon-close"
@click.prevent.stop="onCloseClick(index,tag)"
/>
</template>
</router-link>
</el-scrollbar>
<context-menu
v-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,useRouter } from 'vue-router'
import { useStore } from 'vuex'
const route = useRoute()
/**
* 是否被选中
*/
const isActive = tag => {
return tag.path === route.path
}
const isAffiix = tag =>{
return tag.meta && tag.meta.affix
}
// 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 = index
visible.value = true
}
/**
* 关闭 tag 的点击事件
*/
const store = useStore()
const router = useRouter()
const onCloseClick = (index,tag) => {
store.commit('app/removeTagsView', {
type: 'index',
index: index
})
//如果删除的是当前页面,自动定位到上一个页面
if (isActive(tag)) {
let tagsViewList = store.getters.tagsViewList
if(index ==0 && tagsViewList.length>=1){
let pre_index = 0
let pre_page =tagsViewList[pre_index]
router.push(pre_page.fullPath)
}else if(tagsViewList.length == 0){//如果是最后一个,定位到首页
router.push('/')
}else{
let pre_index = index-1
let pre_page =tagsViewList[pre_index]
router.push(pre_page.fullPath)
}
}
}
/**
* 关闭 menu
*/
const closeMenu = () => {
visible.value = false
}
/**
* 监听变化
*/
watch(visible, val => {
if (val) {
document.body.addEventListener('click', closeMenu)
} else {
document.body.removeEventListener('click', closeMenu)
}
})
</script>
需要注意的是,,如果删除的是当前页,自动定位到上一个页面,删除的如果是最后一个,自动定位到首页
tagsView 方案总结
那么到这里关于 tagsView
的内容我们就已经处理完成了。
整个 tagsView
就像我们之前说的,拆开来看之后,会显得明确很多。
整个 tagsView
整体来看就是三块大的内容:
-
tags
:tagsView
组件 -
contextMenu
:contextMenu
组件 -
view
:页面路由处理
再加上一部分的数据处理即可。
网友评论