一、目标效果
此篇博客重在分享vue3实现动态增减标签页实现方法,项目界面可能比较丑、性能优化也没有做到极致,请多多包涵,此文章核心是实现动态增减标签的逻辑。本来想要用视频展示效果的,结果csdn上传视频一致审核中,只能截几个图给你们看看了。
项目的注释写的比较清晰,自己将项目跑起来,慢慢内化成自己的东西,项目用到数组的方法、vue3语法、vue-router、vuex、动态绑定class、三元运算符知识。
二、运行项目
(1)项目地址:git clone https://gitee.com/liu-wenxin/operateTags.git
(2)npm install 安装依赖
(3)npm run serve 运行项目
(4)http://localhost:8080访问项目即可
三、实现过程
(1)界面样式
<template>
<!-- 首页 -->
<div>
<el-container>
<!-- 头部 -->
<el-header>Header</el-header>
<el-container>
<!-- 侧边栏 -->
<el-aside width="200px">
<el-menu active-text-color="#6787df" background-color="#252b41" default-active="1" text-color="#fff"
router>
<el-menu-item index="1">
<span>页面1</span>
</el-menu-item>
<el-menu-item index="2">
<span>页面2</span>
</el-menu-item>
<el-menu-item index="3">
<span>页面3</span>
</el-menu-item>
<el-menu-item index="4">
<span>页面4</span>
</el-menu-item>
<el-menu-item index="5">
<span>页面5</span>
</el-menu-item>
<el-menu-item index="6">
<span>页面6</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<!-- tags栏 -->
<div class="tagList" v-if="showTags">
<ul>
<li v-for="(item, index) in tagsList" :class="{ 'active': isActive(item.path) }"
:key="index">
<router-link :to="item.path" class="tag">
{
{ item.name }}
</router-link>
<el-icon @click="closeTags(index)" class="close">
<Close />
</el-icon>
</li>
</ul>
<el-dropdown>
<el-button type="primary">
标签选项
<el-icon>
<ArrowDown />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.native="closeOther">关闭其它</el-dropdown-item>
<el-dropdown-item @click.native="closeAll">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 内容渲染区 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'
import { computed } from 'vue'
export default {
setup() {
const route = useRoute();
const router = useRouter();
const store = useStore();
// 获取vuex里面所有的tag
const tagsList = computed(() => store.state.tagsList)
// 关闭一个标签
const closeTags = index => {
//获取删除的标签的信息
const delItem = tagsList.value[index];
//让vuex删除指定的标签
store.commit('delTagsItem', { index });
// 如果关闭的是当前标签且标签不止一个,则激活前一个标签,如果关闭的是不是当前标签且标签不止一个,则激活后一个标签
const item = tagsList.value[index] ? tagsList.value[index] : tagsList.value[index - 1];
if (item) {
// 如果删除的是当前标签,则跳往上面item判断好的目标标签的地址
if (isActive(delItem.path)) {
router.push(item.path)
}
} else {
// 如果tagsList只有一个标签,删除了标签就没有标签,则跳转到首页
router.push("/");
}
}
// 设置标签
const setTags = (route) => {
const isExist = tagsList.value.some(item => item.path === route.fullPath)
if (!isExist) {
// tag超过5个就删除第一个
if (tagsList.value.length >= 5) {
store.commit('delTagsItem', { index: 0 })
}
store.commit('setTagsItem', {
name: route.name,
path: route.fullPath,
title: route.meta.title
})
}
}
//首次加载页面的时候便添加标签页
setTags(route);
// 监听路由变化,在当前页面即将要离开的时候触发
onBeforeRouteUpdate((to) => {
setTags(to)
})
// 关全部标签
const closeAll = () => {
store.commit('clearTags');
router.push("/");
}
// 关闭其它标签
const closeOther = () => {
const curItem = tagsList.value.filter(item => {
return item.path === route.fullPath;
})
store.commit('clearTagsOther', curItem);
}
// 显示标签栏
const showTags = computed(() => tagsList.value.length > 0)
// 判断tag是否被激活,如果被激活就高亮当前标签
const isActive = path => path === route.fullPath
return { tagsList, isActive, showTags, setTags, closeAll, closeOther, closeTags }
}
}
</script>
<style scoped>
.active,
.active .tag {
background-color: #409eff !important;
color: #fff !important;
}
.close:hover {
background-color: #ffffff;
color: #409eff;
cursor: pointer;
}
.tag {
margin: 0 5px;
height: 100%;
font-size: 18px;
}
.tagList ul li {
float: left;
margin: 3px 5px 2px 3px;
height: 23px;
border: 1px solid #e9eaec;
line-height: 23px;
border-radius: 3px;
text-align: center;
background-color: #fff;
color: #666;
}
.tagList {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #ffffff;
}
.el-main {
margin: 0;
padding: 0;
background: #efefef;
}
.el-menu-item {
display: flex;
justify-content: center;
font-size: 16px;
}
.el-menu {
height: 100%;
}
.el-aside {
height: calc(100vh - 60px);
}
/* 头部样式 */
.el-header {
height: 60px;
line-height: 60px;
background-color: #252b41;
color: #ffffff;
text-align: center;
font-size: 20px;
}
</style>
(2)vue-router路由
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/index',
},
{
path: '/index',
name: 'index',
redirect: '/1',
meta: {
title: '首页'
},
component: () => import( /* webpackChunkName: "index" */ "../views/index.vue"),
children: [
{
path: '/1',
name: '1',
meta: {
title: '页面1'
},
component: () => import( /* webpackChunkName: "1" */ "../views/1/1.vue")
},
{
path: '/2',
name: '2',
meta: {
title: '页面2'
},
component: () => import( /* webpackChunkName: "2" */ "../views/2/2.vue")
},
{
path: '/3',
name: '3',
meta: {
title: '页面3'
},
component: () => import( /* webpackChunkName: "3" */ "../views/3/3.vue")
},
{
path: '/4',
name: '4',
meta: {
title: '页面4'
},
component: () => import( /* webpackChunkName: "4" */ "../views/4/4.vue")
},
{
path: '/5',
name: '5',
meta: {
title: '页面5'
},
component: () => import( /* webpackChunkName: "5" */ "../views/5/5.vue")
},
{
path: '/6',
name: '6',
meta: {
title: '页面6'
},
component: () => import( /* webpackChunkName: "6" */ "../views/6/6.vue")
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
(3)vuex
import { createStore } from 'vuex'
export default createStore({
state: {
tagsList: [] //存放所有tag标签的数组
},
mutations: {
// 删除一个tag
delTagsItem(state, data) {
state.tagsList.splice(data.index, 1)
},
//添加一个tag
setTagsItem(state, data) {
state.tagsList.push(data);
},
// 清除其他tag
clearTagsOther(state, data) {
state.tagsList = data;
},
// 清除所有tag
clearTags(state) {
state.tagsList = []
}
}
})