修复bug

This commit is contained in:
2025-10-31 15:58:11 +08:00
parent d3375a347f
commit c54f9c9976
29 changed files with 823 additions and 218 deletions

View File

@ -0,0 +1,184 @@
<template>
<div class="my-tabs">
<!-- 导航栏 -->
<div class="my-tabs__nav">
<div
v-for="pane in panes"
:key="pane.name"
:class="{
'my-tabs__item': true,
'is-active': activeName === pane.name,
'is-disabled': pane.disabled
}"
@click="handleClick(pane)"
>
<!-- 标签文本 -->
<span>{{ pane.label }}</span>
<!-- 关闭按钮 -->
<i
v-if="pane.closable"
class="my-tabs__close"
@click.stop="handleClose(pane)"
>×</i>
</div>
<!-- 底部横线和滑块 -->
<div class="my-tabs__slider-wrapper">
<div class="my-tabs__slider-track"></div>
<div
class="my-tabs__slider"
ref="slider"
></div>
</div>
</div>
<!-- 内容区 -->
<div class="my-tabs__content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'MyTabs',
model: {
prop: 'value',
event: 'input'
},
props: {
value: [String, Number], // 当前激活
type: { type: String, default: '' }, // 后期可扩展 card/border-card
closable: Boolean // 全局是否允许关闭
},
data() {
return {
panes: [] // 缓存所有 MyTabPane 实例
}
},
computed: {
activeName: {
get() { return this.value },
set(val) { this.$emit('input', val) }
}
},
watch: {
activeName() {
this.$nextTick(() => {
this.updateSliderPosition();
});
},
panes() {
this.$nextTick(() => {
this.updateSliderPosition();
});
}
},
mounted() {
this.$nextTick(() => {
this.updateSliderPosition();
});
},
methods: {
/* 收集子组件 */
registerPane(pane) {
this.panes.push(pane)
// 默认选中第一个
if (!this.activeName && this.panes.length === 1) {
this.activeName = pane.name
}
},
unregisterPane(pane) {
const idx = this.panes.indexOf(pane)
if (idx > -1) this.panes.splice(idx, 1)
},
/* 点击导航 */
handleClick(pane) {
if (pane.disabled) return
this.activeName = pane.name
this.$emit('tab-click', pane)
},
/* 关闭 */
handleClose(pane) {
this.$emit('edit', pane.name, 'remove')
// 业务层把 v-model 绑定的数组删掉即可
},
/* 更新滑块位置和宽度 */
updateSliderPosition() {
if (!this.activeName || !this.$refs.slider) return;
const activeTab = this.$el.querySelector(`.my-tabs__item.is-active`);
if (!activeTab) return;
const textElement = activeTab.querySelector('span');
if (!textElement) return;
const { width } = textElement.getBoundingClientRect();
const { left } = activeTab.getBoundingClientRect();
const navLeft = this.$el.querySelector('.my-tabs__nav').getBoundingClientRect().left;
this.$refs.slider.style.width = `${width}px`;
this.$refs.slider.style.left = `${left - navLeft + (activeTab.offsetWidth - width) / 2}px`;
}
}
}
</script>
<style scoped lang="scss">
.my-tabs__nav {
display: flex;
position: relative;
padding-bottom: 14px;
}
.my-tabs__item {
padding: 8px 16px;
cursor: pointer;
position: relative;
font-size: 20px;
&:hover {
opacity: 0.8;
}
}
.my-tabs__item.is-active {
color: #409eff;
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
}
.my-tabs__item.is-disabled {
color: #1e293b;
cursor: not-allowed;
font-family: 'Poppins-Regular', serif;
}
.my-tabs__close { margin-left: 6px; color: #999; }
.my-tabs__close:hover { color: #f56c6c; }
.my-tabs__content { padding: 15px 0; }
/* 底部横线和滑块样式 */
.my-tabs__slider-wrapper {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
display: flex;
align-items: center;
}
.my-tabs__slider-track {
width: 100%;
height: 4px;
background-color: #e2e8f0;
}
.my-tabs__slider {
height: 4px;
background: $header-backgroungd;
border-radius: 4px;
position: absolute;
bottom: 0;
transition: all 0.3s ease;
}
</style>