先看看最終的效果吧判沟,這樣更有助于后面代碼邏輯的實(shí)現(xiàn)蛹头。
布局實(shí)現(xiàn)效果分析
image.png
我們看第一張圖片先鱼,可以分析得出 整個(gè)導(dǎo)航標(biāo)簽組件(我們最終會(huì)封裝成一個(gè)組件,便于后期其他項(xiàng)目的移植使用)谷扣,由標(biāo)簽按鈕和右側(cè)彈窗層組合成。只要通過css布局 捎琐,就可以實(shí)現(xiàn)右側(cè)彈出層的效果会涎,很簡(jiǎn)單。
image.png
image.png
從上面兩張圖片可以看出瑞凑,整個(gè)導(dǎo)航標(biāo)簽組件末秃,是實(shí)現(xiàn)了標(biāo)簽按鈕過多后,上線可移動(dòng)的效果的拨黔。
功能實(shí)現(xiàn)效果分析
image.png
右側(cè)彈窗層 實(shí)現(xiàn)了1.刷新 2.關(guān)閉右側(cè) 3.關(guān)閉其他 4.關(guān)閉全部四個(gè)功能
- 刷新:只要通過 router.go(0) 刷新當(dāng)前頁就行
- 關(guān)閉右側(cè):因?yàn)槲覀冋麄€(gè)導(dǎo)航標(biāo)簽肯定是由一個(gè)數(shù)組渲染的蛔溃,通過數(shù)組的splice就能實(shí)現(xiàn)數(shù)據(jù)的“切割”绰沥。
- 關(guān)閉其他:通過也是通過實(shí)現(xiàn)的,只不過切割了當(dāng)前位置的一前一后的數(shù)據(jù)贺待,但這里需要注意一個(gè)點(diǎn)徽曲,就是我們這個(gè)項(xiàng)目一進(jìn)來是自動(dòng)定位到首頁標(biāo)簽的,所以關(guān)閉其他和關(guān)閉全部是不能關(guān)閉到首頁hone標(biāo)簽按鈕的
-
關(guān)閉全部:把數(shù)組置未空就行了麸塞,但要定位到首頁秃臣,其他按鈕標(biāo)簽是由關(guān)閉功能的,但首頁是沒有關(guān)閉功能的哪工,也就是首頁是沒有關(guān)閉當(dāng)前頁的功能的奥此,
image.png
image.png
其他問題分析
- 路由過渡動(dòng)畫的實(shí)現(xiàn)
使用vue官方的過渡動(dòng)畫的方式
html
<!-- 使用動(dòng)態(tài)過渡名稱 -->
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
- 數(shù)據(jù)的緩存
我們使用keep-alive結(jié)合vuex的實(shí)現(xiàn)方式
<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>
好了,總體的實(shí)現(xiàn)思路到講完了雁比,我們直接看代碼吧
監(jiān)聽路由稚虎,往數(shù)組里里增加標(biāo)簽,再通過循環(huán)router-view 實(shí)現(xiàn)渲染
/**
* 監(jiān)聽路由變化
*/
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的數(shù)據(jù)和操作動(dòng)作全都封裝到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
})
// 處理重復(fù)
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: {}
}
右側(cè)彈窗組件的實(shí)現(xiàn)
<template>
<ul class="context-menu-container">
<li @click="onRefreshClick">
刷新
</li>
<li @click="onCloseRightClick">
關(guān)閉右側(cè)
</li>
<li @click="onCloseOtherClick">
關(guān)閉其他
</li>
<li @click="onCloseAllClick">
關(guān)閉全部
</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代碼很簡(jiǎn)單偎捎,就展示了蠢终,需要的私信我
整個(gè)tagView組件的實(shí)現(xiàn)
<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 相關(guān)
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
}
/**
* 關(guān)閉 tag 的點(diǎn)擊事件
*/
const store = useStore()
const router = useRouter()
const onCloseClick = (index,tag) => {
store.commit('app/removeTagsView', {
type: 'index',
index: index
})
//如果刪除的是當(dāng)前頁面,自動(dòng)定位到上一個(gè)頁面
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){//如果是最后一個(gè)茴她,定位到首頁
router.push('/')
}else{
let pre_index = index-1
let pre_page =tagsViewList[pre_index]
router.push(pre_page.fullPath)
}
}
}
/**
* 關(guān)閉 menu
*/
const closeMenu = () => {
visible.value = false
}
/**
* 監(jiān)聽變化
*/
watch(visible, val => {
if (val) {
document.body.addEventListener('click', closeMenu)
} else {
document.body.removeEventListener('click', closeMenu)
}
})
</script>
需要注意的是寻拂,,如果刪除的是當(dāng)前頁丈牢,自動(dòng)定位到上一個(gè)頁面祭钉,刪除的如果是最后一個(gè),自動(dòng)定位到首頁
tagsView 方案總結(jié)
那么到這里關(guān)于 tagsView
的內(nèi)容我們就已經(jīng)處理完成了己沛。
整個(gè) tagsView
就像我們之前說的慌核,拆開來看之后,會(huì)顯得明確很多泛粹。
整個(gè) tagsView
整體來看就是三塊大的內(nèi)容:
-
tags
:tagsView
組件 -
contextMenu
:contextMenu
組件 -
view
:頁面路由處理
再加上一部分的數(shù)據(jù)處理即可遂铡。