在本次开发中,我们将实现一个 Vue 组件,用于展示和切换标签页。
背景有移动动画效果
该组件将具有以下功能:
- 标签页左右滚动
- 点击标签页切换内容
- 关闭指定标签页
- 支持多种标签页风格
以下是实现该组件的具体步骤:
- 创建 Vue 组件
<template>
<!--
tag-items-smooth columns 圆润
.comprehensive comprehensive卡片
.routine flexibility 灵动的
-->
<div class="tacscontainerwidth comprehensive"
:class="configStore.config.labelStyleSwitch=='columns'? 'columns' : configStore.config.labelStyleSwitch=='comprehensive'? 'comprehensive':'flexibility'">
<!-- 左箭头按钮 -->
<el-icon @click="scrollLeft">
<ArrowLeft />
</el-icon>
<!-- 标签滚动条容器 -->
<div class="tacspage" ref="scrollbarRef" style="width: 100%;">
<!-- 标签内容容器 -->
<div class="tag-items" ref="innerRef">
<!-- 标签项 -->
<div v-for="(item, index) in tabconf.tabsView"
:class="index === tabconf.activeIndex ? 'basistag basis-tag-item active' : 'basistag basis-tag-item'"
ref="tabsRefs" @click="tagitemchange(index)">
<!-- 标签内容 -->
<div class=" basis-tag-item-content">
<!-- 标签图标 -->
<span v-if="typeof item.icon!== 'undefined' &&item.icon !=''&&item.icon!==null"
style="width: 15px; margin-right: 5px; display: flex; align-items: center;">
<component :is="item.icon" class=" basis-tag-item-content-icon"
v-if="configStore.config.labelIconSwitch" />
</span>
<!-- 标签标题 -->
<span class=" basis-tag-item-content-title">{{ item.title }}</span>
</div>
<!-- 关闭图标 -->
<el-icon v-if="index!=0" class="tacscontainerwidth-Close" @click="closeTagItem(index,$event)">
<Close />
</el-icon>
</div>
<!-- 选中标签页的指示框-背景图-->
<div :style="activeBoxStyle" class="nav-tabs-active-box"></div>
</div>
</div>
<!-- 右箭头按钮 -->
<el-icon @click="scrollRight">
<ArrowRight />
</el-icon>
<!-- 更多下拉 -->
<el-dropdown style="margin-left: 15px;s">
<span class="username-span" >
<!-- 更多图标 -->
<el-icon>
<Grid />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item command="user">刷新</el-dropdown-item> -->
<!-- <el-dropdown-item @click="addTagItem()">添加</el-dropdown-item> -->
<el-dropdown-item @click="CloseTagItemLeft()">关闭左侧</el-dropdown-item>
<el-dropdown-item @click="CloseTagItemRight()">关闭右侧</el-dropdown-item>
<el-dropdown-item @click="CloseTagItemOther()">关闭其他</el-dropdown-item>
<el-dropdown-item @click="CloseTagItemAll()">关闭全部</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
-
引入所需的图标和组件
-
定义组件的样式(还没优化好、请自己定义)
<style scoped lang="scss">
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
.tacscontainerwidth {
display: flex;
align-items: center;
position: absolute;
.tacspage {
overflow-y: hidden;
overflow-x: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
/* 隐藏滚动条 */
}
}
.tag-items {
// white-space: nowrap;position: relative;
// transition: transform 0.3s ease;
display: flex;
white-space: nowrap;
position: relative;
transition: transform var(--el-transition-duration);
float: left;
}
.tag-items-card .basis-tag-item {
margin-left: 10px;
border: 1px solid #ccc;
display: flex;
display: flex;
align-items: center;
justify-items: center;
}
.tag-items-smooth .basis-tag-item {
margin-left: 10px;
border: 1px solid #ccc;
border-radius: 5px;
// width: 100px;
display: flex;
align-items: center;
justify-items: center;
}
.tag-items-smart .basis-tag-item {
margin-left: 10px;
border: 0px;
display: flex;
align-items: center;
}
.basis-tag-item * {
flex-shrink: 0;
// display: flex;
// align-items: center;
// justify-items: center;
}
.nav-tabs-active-box {
position: absolute;
height: 5px;
bottom: 0px;
border-radius: var(--el-border-radius-base);
background-color: #fcc;
box-shadow: var(--el-box-shadow-light);
transition: all 0.2s;
-webkit-transition: all 0.2s;
z-index: 2;
}
.basis-tag-item {
padding: 5px;
}
.tag-items-smart .basis-tag-item {
color: var(--el-color-info-light-3);
// background-color: var(--el-menu-bg-color);
}
.active {
color: var(--el-menu-text-color);
// background-color: var(--el-menu-bg-color);
border-bottom: 2px solid var(--el-menu-text-color);
// background-color: #cfc;
}
.tacscontainerwidth-Close{margin-left: 5px;width: 14px; position: relative;
font-size: 12px;
height: 14px;
overflow: hidden;
right: -2px;
transform-origin: 100% 50%;}
@keyframes moveBackground {
0% {
background-position: left top;
}
100% {
background-position: right top;
}
}
</style>
- 定义组件的数据和方法
4.1 下面的navTabs
和useConfig
用了结合pinia和piniaPluginPersistedstate的使用案例
代码中用`navTabs`来存储标签基础信息,如:`activeIndex` 当前激活索引,`tabsView` 数组缓存;
代码中用`useConfig`来存储配置基础信息,如: `labelSwitch` 标签开关、 `labelIconSwitch` 标签图标开关 、`labelStyleSwitch` 标签样式
4.2下面的useRouter
用了Vue Router 使用教程
代码中获取`useRoute`r的路由并进行标签激活判断、以及进行跳转路由
<script setup lang="ts">
import { onMounted, reactive, ref, defineProps, watch, nextTick } from 'vue';
import { useConfig } from '../../../../stores/config' // 使用配置 store
import { navTabs } from '../../../../stores/navTabs' // 使用配置 store
import { useRouter } from 'vue-router' // 使用配置 useRouter
const router = useRouter();
//组件接受参数
const props = defineProps({
tacscontainerwidth: {
type: String,
default: '90%',
},
tabconf: {
type: Object,
default: () => ({})
},
});
// 获取缓存
const configStore = useConfig();
const navTabsstores = navTabs();
// console.log(navTabsstores)
const tabconf = navTabsstores
const activeBoxStyle = reactive({
width: "0px",
transform: 'translateX(0px)',
})
// 滚动条容器的ref
const scrollbarRef = ref();
//所有标签项的ref
const tabsRefs = ref();
// 切换标签是的动画样式初始化
onMounted(() => {
let routerView=router.currentRoute.value.path;
tabconf.tabsView.forEach(function(item, index) {
if(routerView==item.path){
tabconf.activeIndex=index;
}
});
settagitemchange(tabconf.activeIndex)
})
watch(router.currentRoute, (newValue, oldValue) => {
nextTick(function () {
// 在这里进行 v-for 重新加载完毕后的操作
let routerView=router.currentRoute.value.path;
tabconf.tabsView.forEach(function(item, index) {
if(routerView==item.path){
tabconf.activeIndex=index;
}
});
settagitemchange(tabconf.activeIndex)
})
});
/* 向左滚动标签页 */
function scrollLeft() {
scrollbarRefscroll(-100);
}
/* 向右滚动标签页 */
function scrollRight() {
scrollbarRefscroll(100);
}
/* 关闭所有标签页 */
function CloseTagItemAll() {
tabconf.activeIndex=0
tabsViewSplice(1, tabconf.tabsView.length)
// 重新设置选中背景
}
/*
* 关闭其他标签页
*/
function CloseTagItemOther() {
tabconf.tabsView.splice(tabconf.activeIndex + 1, tabconf.tabsView.length - tabconf.activeIndex);
tabconf.tabsView.splice(1, tabconf.activeIndex - 1);
settagitemchange(1);
}
/*
* 关闭左侧标签页
*/
function CloseTagItemLeft() {
tabsViewSplice(1, tabconf.activeIndex - 1,1)
}
/*
* 关闭右侧标签页
*/
function CloseTagItemRight() {
tabsViewSplice(tabconf.activeIndex + 1, tabconf.tabsView.length - tabconf.activeIndex,tabconf.activeIndex)
}
/*
* 关闭指定索引的标签页
* @param {number} index - 要关闭的标签页索引
*/
function closeTagItem(index,event) {
// 阻止事件冒泡,防止子事件调用父级事件
event.stopPropagation();
// 关闭指定索引的标签页,并确保 activeIndex 的有效性
let activeIndex=0;
if(tabconf.activeIndex>=index&&index>0){
activeIndex=tabconf.activeIndex-1
}else if(index>0){
activeIndex=tabconf.activeIndex-1
}
tabsViewSplice(index, 1,activeIndex)
}
/*
* 切换标签页
* @param {Event} event - 鼠标点击事件对象
*/
function tagitemchange(index) {
tabconf.activeIndex = index;
router.push(tabconf.tabsView[index].path)
settagitemchange(index)
}
//滚动条移动
function scrollbarRefscroll(scrollNum) {
scrollbarRef.value.scrollLeft += scrollNum
}
// 删除
function tabsViewSplice(start, length,activeIndex=0) {
tabconf.activeIndex = activeIndex;
if (start >= 1) {
tabconf.tabsView.splice(start, length);
}
router.push(tabconf.tabsView[activeIndex].path)
// 重新设置选中背景
settagitemchange(tabconf.activeIndex)
}
/*
* 切换标签动画
* @param {Event} event - 鼠标点击事件对象
*/
function settagitemchange(activeIndex) {
// console.log(tabconf.tabsView,activeIndex,"index");
tabconf.activeIndex=activeIndex;
// 动画
const event = tabsRefs.value[activeIndex];
const element = event;
const width = element.offsetWidth;
// 获取元素的水平滚动位置
const offsetLeft = element.offsetLeft;
// 修改动画参数
activeBoxStyle.width = width + 'px'
activeBoxStyle.transform = `translateX(${offsetLeft}px)`
// console.log('element', element);
// console.log('索引:', tabconf.activeIndex);
// console.log('path:', tabconf.activeRoute);
// console.log('宽度:', width);
// console.log('scrollLeft:', scrollLeft);
}
</script>
- 在模板中使用组件
通过以上步骤,我们实现了一个具有标签页切换功能的 Vue 组件。在实际应用中,可以根据需要进一步扩展和定制组件的功能。
希望这篇开发博客对你有所帮助!如果你有任何问题或建议,请随时留言。