Files
AIProd/components/MyTabs/MyTabs.vue

185 lines
3.9 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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';
}
.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>