使用vue3模拟element-ui中el-tabs的实现

一. 最终实现

组件没有背景颜色, 为了凸显组件文字,才设置了背景颜色
在这里插入图片描述

二. 使用

<wq-tabs v-model="activeName" style="background:grey; padding: 20px">
	<wq-tab-pane label="User" name="first">User</wq-tab-pane>
	<wq-tab-pane label="Config" name="second">Config</wq-tab-pane>
	<wq-tab-pane label="Role" name="third">Role</wq-tab-pane>
	<wq-tab-pane label="Task" name="fourth">Task</wq-tab-pane>
</wq-tabs>

嘿嘿

三. 代码

type.ts

export interface WqTabItem {
	label: string; // 标签
	name: string; // 唯一标识
	disabled?: boolean; // 是否禁用
}

constant.ts

export const WQ_TABS_PROVIDE = Symbol();

是的, 这个文件只有一行代码, 主要用于 tabs组件 与 tab-pane 之间使用依赖注入的标识

WqTabs.vue

<template>
	<div class="wq-tabs">
		<tab-nav :active-name="activeName" :on-tab-click="handleTabClick" :tab-item-arr="tabsArr"></tab-nav>
		<slot></slot>
	</div>
</template>

<script setup lang="tsx">
import { computed, onMounted, provide, ref, useSlots } from 'vue';
import tabNav from './wq-tab-nav.vue';
import { WQ_TABS_PROVIDE } from '@/components/WqTabs/constant';
import { WqTabItem } from '@/components/WqTabs/type';

type Props = {
	modelValue: string;
	beforeLeave?: (newTabName: string, oldTabName: string) => boolean | Promise<boolean>;
};

const props = withDefaults(defineProps<Props>(), { beforeLeave: () => true });
const emits = defineEmits<{ (name: 'tabClick', tabItem: any): void; (name: 'update:modelValue', value: string): void }>();
// 需要nav渲染的数据
const tabsArr = ref<any[]>([]);
// 当前激活的name
const activeName = computed({
	get() {
		return props.modelValue;
	},
	set(newVal) {
		emits('update:modelValue', newVal);
	},
});

// 将激活的名字提供给自组件使用
provide(WQ_TABS_PROVIDE, activeName);

const slots = useSlots();
const calcTabItems = () => {
	if (slots.default) {
		// 收集wq-tabs中间的插槽内容
		let slotTabItemArr = slots.default();
		// 收集他们的props数据
		tabsArr.value = slotTabItemArr.map((item) => {
			return { ...item.props };
		});
	} else {
		tabsArr.value = [];
	}
};
calcTabItems();

// 可能有异步
const setCurrentName = async (newTabName: string) => {
	let oldTabName = activeName.value;
	const res = await props.beforeLeave(newTabName, oldTabName);
	if (res) {
		activeName.value = newTabName; // 自身也更新一下
	}
};

const handleTabClick = (tabItem: WqTabItem) => {
	// 触发父组件的事件, 将当前点击的tab作为参数传给父组件
	emits('tabClick', tabItem);
	// 更新
	setCurrentName(tabItem.name);
};
</script>

↑↑↑这里使用了useSlots()来获取插槽内容,
提取props中的数据, 交给nav进行渲染

WqTabNav.vue

<template>
	<div class="wq-tab-nav">
		<div
			v-for="(tabItem, index) in tabItemArr"
			:key="index"
			:class="['wq-tab-nav-item', tabItem.name === activeName ? 'highLight' : '', tabItem.disabled ? 'isForbiddenItem' : '']"
			@click="changeActiveName(tabItem)"
		>
			{{ tabItem.label }}
		</div>
	</div>
</template>
<script setup lang="ts">
import { WqTabItem } from '@/components/WqTabs/type';

type Prop = {
	tabItemArr: WqTabItem[];
	activeName: string;
	onTabClick?: (tabItem: WqTabItem) => void;
};

const props = withDefaults(defineProps<Prop>(), {
	tabItemArr: () => [],
	activeName: '',
	onTabClick: () => {},
});

// 更换激活
const changeActiveName = (tabItem: any) => {
	// 点自己不执行
	if (tabItem.name === props.activeName) {
		return;
	}
	// 禁用不执行
	if (tabItem.disabled) {
		return;
	}
	props.onTabClick(tabItem);
};
</script>
<style lang="scss" scoped>
.wq-tab-nav {
	width: 100%;
	//border-bottom: 1px solid #e9e9e9;
	&-item {
		display: inline-block;
		height: 40px;
		line-height: 40px;
		font-size: 14px;
		font-weight: 500;
		color: #303133;
		margin: 0 12px;
		cursor: pointer;
	}
	&-item:not(.isForbiddenItem):hover {
		color: #409eff;
	}
	// 高亮项样式
	.highLight {
		color: #409eff;
		border-bottom: 2px solid #409eff;
	}
	// 禁用项样式
	.isForbiddenItem {
		cursor: not-allowed;
		color: #aaa;
	}
}
</style>

wq-tab-pane.vue

<template>
	<div v-show="isActive" class="wq-tab-pane">
		<slot></slot>
	</div>
</template>
<script setup lang="ts">
import { computed, inject, Ref } from 'vue';
import { WQ_TABS_PROVIDE } from '@/components/WqTabs/constant';

type Props = {
	label: string;
	name: string;
	disable?: boolean;
};

const props = withDefaults(defineProps<Props>(), {
	disable: false,
});

// 激活状态
const isActive = computed(() => {
	// 使用 inject 获取被激活的内容
	const navActive = inject<Ref<string>>(WQ_TABS_PROVIDE);
	const activeName = navActive?.value;
	const currentName = props.name;
	return activeName === currentName;
});
</script>

<style lang="scss" scoped>
.wq-tab-pane {
	padding: 12px;
}
</style>

四. 缺陷

写的不是很灵活, 不能动态更新
若tab-pane是动态的, 会有nav不刷新的情况

嘿嘿~

等组件再实现的灵活一些再来填坑

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值