134 lines
3.5 KiB
Vue
134 lines
3.5 KiB
Vue
<template>
|
||
<view class="tab-container relative">
|
||
<view class="tab-wrapper flex flex-items-center">
|
||
<view v-for="(item, index) in tabList" :key="item.id" :class="[
|
||
'tab-item flex flex-items-center flex-justify-center relative',
|
||
activeIndex === index && 'tab-item-active',
|
||
]" @click="handleTabClick(index)">
|
||
<view class="tab-item-inner flex flex-items-center">
|
||
<uni-icons :class="['icon mr-4', activeIndex === index && 'icon-active']" fontFamily="znicons" size="20" color="opacity">
|
||
{{ zniconsMap[item.iconCode] }}
|
||
</uni-icons>
|
||
|
||
<text :class="[
|
||
'font-size-16 font-500 color-525866 ',
|
||
activeIndex === index && 'tab-text-active',
|
||
]">
|
||
{{ item.iconTitle }}
|
||
</text>
|
||
</view>
|
||
|
||
<!-- 每项内的下划线指示器,通过类控制显示/隐藏 -->
|
||
<view class="tab-item-indicator" :class="{ visible: activeIndex === index }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onMounted, ref, watch } from "vue";
|
||
import { zniconsMap } from "@/static/fonts/znicons";
|
||
import { commodityTypePageList } from "@/request/api/GoodsApi";
|
||
|
||
// Props
|
||
const props = defineProps({
|
||
tabs: {
|
||
type: Array,
|
||
default: () => [
|
||
{ iconTitle: "客房", typeCode: "0", iconCode: "zn-nav-room" },
|
||
{ iconTitle: "门票", typeCode: "1", iconCode: "zn-nav-ticket" },
|
||
{ iconTitle: "餐食", typeCode: "2", iconCode: "zn-nav-meal" },
|
||
{ iconTitle: "套餐", typeCode: "3", iconCode: "zn-package" },
|
||
{ iconTitle: "文创", typeCode: "4", iconCode: "zn-package" },
|
||
],
|
||
},
|
||
defaultActive: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
indicatorColor: {
|
||
type: String,
|
||
default: "#1890ff"
|
||
},
|
||
});
|
||
|
||
// Emits
|
||
const emit = defineEmits(["change", "update:modelValue"]);
|
||
|
||
// 响应式数据
|
||
const activeIndex = ref(props.defaultActive);
|
||
const tabList = ref([]);
|
||
|
||
// 处理Tab点击
|
||
const handleTabClick = (index) => {
|
||
changeTabItem(index);
|
||
};
|
||
|
||
// 支持 force 参数,强制触发(即使 index 与当前相同)
|
||
const changeTabItem = (index, force = false) => {
|
||
if (!force && activeIndex.value === index) return;
|
||
activeIndex.value = index;
|
||
|
||
emit("change", {
|
||
index,
|
||
item: tabList.value[index],
|
||
});
|
||
emit("update:modelValue", index);
|
||
}
|
||
|
||
|
||
// 监听tabs变化
|
||
watch(
|
||
() => props.tabs,
|
||
(newTabs) => {
|
||
tabList.value = newTabs;
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
// 监听defaultActive变化
|
||
watch(
|
||
() => props.defaultActive,
|
||
(newActive) => {
|
||
if (newActive !== activeIndex.value) {
|
||
activeIndex.value = newActive;
|
||
}
|
||
}
|
||
);
|
||
|
||
// 暴露方法
|
||
defineExpose({
|
||
setActiveIndex: (index) => {
|
||
if (index >= 0 && index < tabList.value.length) {
|
||
handleTabClick(index);
|
||
}
|
||
},
|
||
getActiveIndex: () => activeIndex.value,
|
||
getActiveItem: () => tabList.value[activeIndex.value],
|
||
});
|
||
|
||
onMounted(() => {
|
||
getCommodityTypePageList();
|
||
});
|
||
|
||
// 获取商品类型列表(示例方法,实际使用时根据需要调用)
|
||
const getCommodityTypePageList = async () => {
|
||
const res = await commodityTypePageList({size: 20, current: 1});
|
||
if (res && res.data && res.data.records) {
|
||
tabList.value = res.data.records;
|
||
} else {
|
||
tabList.value = props.tabs;
|
||
}
|
||
// 加载完成后强制选中第一个项并通知外部
|
||
if (tabList.value && tabList.value.length > 0) {
|
||
changeTabItem(0, true);
|
||
} else {
|
||
changeTabItem(props.defaultActive, true);
|
||
}
|
||
};
|
||
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
@import "./styles/index.scss";
|
||
</style>
|