vue3 实现一个tab切换组件

一. 效果图

请添加图片描述

二. 代码

文件 WqTab.vue:

<template>
	<div ref="wqTabs" class="wq-tab">
		<template v-for="tab in tabs" :key="tab">
			<div class="tab-item" :class="{ ac: tabActive === tab.key }" @click="tabActive = tab.key">{{ tab.value || tab.key }}</div>
		</template>
		<div
			class="bg"
			:style="{
				width: bgWidth + 'px',
				left: bgLeft + 'px',
			}"
		></div>
	</div>
</template>
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import { Tab } from '@/components/WqTab/wqTabType';

type Props = {
	tabs: Tab[];
	active: string;
};

const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits(['update:active']);

const tabActive = computed<string>({
	get() {
		return props.active;
	},
	set(newValue) {
		// 改变值
		emit('update:active', newValue);
	},
});
// wqTabs 元素
const wqTabs = ref();
// bg宽度
const bgWidth = ref<number>(0);
// bg 位置
const bgLeft = ref<number>(0);
watch(
	tabActive,
	(newValue, oldValue) => {
		// 改变背景
		nextTick(() => {
			const tabIndex = props.tabs.findIndex((item) => item.key === newValue);

			if (tabIndex >= 0) {
				/**
				 * 当找到值时
				 * 1. 找到相应的元素
				 * 2. 获取元素的当前位置以及大小
				 * 3, 将bg大小进行调整 并移动到相应的位置
				 */
				nextTick(() => {
					const tabs: Element[] | any = wqTabs.value.querySelectorAll('.tab-item');
					if (!tabs || !tabs.length) {
						// 若没有找到tab
						console.error('tab dom find error');
						return;
					}
					const tab = tabs[tabIndex];
					bgLeft.value = tab.offsetLeft as number;
					bgWidth.value = tab.clientWidth;
					// console.log('value', bgLeft.value, bgWidth.value);
				});
			} else {
				// 没有找到值的时候找default
				const defaultTab = props.tabs.find((item) => item.default);
				tabActive.value = defaultTab?.key || props.tabs[0].key;
			}
		});
	},
	{ immediate: true }
);
</script>
<style scoped lang="scss">
.wq-tab {
	//width: 500px;
	//height: 50px;
	//width: 100%;
	margin: auto;
	display: flex;
	align-items: center;
	//justify-content: center;
	position: relative;
	border-radius: 25px;
	border: 1px solid #dfe4ea;
	overflow: hidden;

	.tab-item {
		flex: 1;
		text-align: center;
		//width: 100px;
		//padding: 0 30px;
		//max-width: 100px;
		height: 40px;
		line-height: 40px;

		position: relative;
		z-index: 2;
		//overflow: hidden;
		cursor: pointer;
		user-select: none;
	}

	.ac {
		color: #fff;
	}
	.bg {
		position: absolute;
		left: 0;
		top: 0;
		z-index: 1;
		//width: 150px;
		height: 50px;
		background: #b2bec3;
		transition: all 0.5s;
	}
}
</style>

文件: wqTabType

export type Tab = {
	// 唯一值
	key: string;
	//  非必需, 如果没有将使用key进行替换
	value?: string;
	// 是否为默认选项
	default?: boolean;
};

三. 使用:

<template>
	<wq-tab v-model:active="tabActive" :tabs="tabs"></wq-tab>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import WqTab from '@/components/WqTab/WqTab.vue';
import { Tab } from '@/components/WqTab/wqTabType';

const tabActive = ref('');

const tabs: Tab[] = [
	{
		key: 'comp',
		value: '组件',
		default: true, // 这里是默认值
	},
	{
		key: 'app',
		value: '应用',
	},
	{
		key: 'web',
		value: '网站',
	},
];

</script>

四. 补充

  1. 这个组件的宽度是基于父元素给的,
  2. 传递的 active 是v-model的
  3. 个人代码水平一般, 如果有什么不合理的地方欢迎大佬们留言
  4. 组件的父元素如果是可变大小的可能会产生样式错误, 比如父元素宽度使用vh, vw这种,
  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值