| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- <template>
- <div class="tech-tabs-container" :class="`is-${type}`" @mouseenter="pauseTimer" @mouseleave="resumeTimer">
-
- <div class="tabs-header">
- <div v-if="type === 'segmented'" class="segmented-slider" :style="sliderStyle"></div>
- <div
- v-for="(pane, index) in panes"
- :key="index"
- class="tab-item"
- :class="{ 'is-active': value === pane.name }"
- @click="handleTabClick(pane.name)"
- >
- {{ pane.label }}
- </div>
- </div>
- <div class="tabs-content">
- <slot></slot>
- </div>
- </div>
- </template>
- <script>
- export default {
- name: 'TechTabs',
- props: {
- // 绑定的当前选中的 tab name (v-model)
- value: { type: [String, Number], required: true },
- // 风格类型:'underline' (下划线大Tab) 或 'segmented' (分段胶囊小Tab)
- type: { type: String, default: 'underline' },
- // 是否开启自动轮播
- autoPlay: { type: Boolean, default: false },
- // 轮播间隔时间 (毫秒),默认 3 秒
- interval: { type: Number, default: 3000 }
- },
- data() {
- return {
- panes: [], // 自动收集的所有子面板实例
- timer: null, // 定时器实例
- isHovering: false // 记录鼠标是否悬浮在组件上
- };
- },
- computed: {
- // 计算当前选中项的索引
- activeIndex() {
- const index = this.panes.findIndex(pane => pane.name === this.value);
- return index === -1 ? 0 : index;
- },
- // 动态计算滑块的宽度和偏移量
- sliderStyle() {
- const total = this.panes.length || 1;
- return {
- // 宽度平分
- width: `${100 / total}%`,
- // 根据索引移动自己的宽度倍数 (100% 就是移动一个滑块的距离)
- transform: `translateX(${this.activeIndex * 100}%)`
- };
- }
- },
- watch: {
- // 监听 autoPlay 的变化,支持动态开启/关闭
- autoPlay(newVal) {
- newVal ? this.startTimer() : this.stopTimer();
- }
- },
- mounted() {
- // 等待所有的 TechTabPane 子组件注册完毕后,启动定时器
- this.$nextTick(() => {
- if (this.autoPlay) {
- this.startTimer();
- }
- });
- },
- beforeDestroy() {
- // 销毁时务必清理定时器,防止内存泄漏
- this.stopTimer();
- },
- methods: {
- handleTabClick(name) {
- if (this.value !== name) {
- this.$emit('input', name);
- this.$emit('tab-click', name);
- // 用户手动干预后,重置定时器,重新开始倒计时
- if (this.autoPlay && !this.isHovering) {
- this.startTimer();
- }
- }
- },
- // 切换到下一个
- switchToNext() {
- if (this.panes.length <= 1) return;
- // 找到当前选中项的索引
- const currentIndex = this.panes.findIndex(pane => pane.name === this.value);
-
- // 计算下一个索引 (到了最后一个就回到第一个)
- let nextIndex = currentIndex + 1;
- if (nextIndex >= this.panes.length) {
- nextIndex = 0;
- }
- // 拿到下一个的 name 并触发更新
- const nextName = this.panes[nextIndex].name;
- this.$emit('input', nextName);
- this.$emit('tab-click', nextName);
- },
- // 启动定时器
- startTimer() {
- this.stopTimer(); // 启动前先清空旧的,防止多开定时器飙车
- if (this.panes.length > 1) {
- this.timer = setInterval(() => {
- this.switchToNext();
- }, this.interval);
- }
- },
- // 停止定时器
- stopTimer() {
- if (this.timer) {
- clearInterval(this.timer);
- this.timer = null;
- }
- },
- // 鼠标悬浮时暂停自动切换,方便用户阅读数据
- pauseTimer() {
- this.isHovering = true;
- if (this.autoPlay) this.stopTimer();
- },
- // 鼠标离开后恢复自动切换
- resumeTimer() {
- this.isHovering = false;
- if (this.autoPlay) this.startTimer();
- },
- // 供子组件 TechTabPane 注册自己
- addPane(pane) {
- this.panes.push(pane);
- },
- // 供子组件销毁时移除自己
- removePane(pane) {
- const index = this.panes.indexOf(pane);
- if (index !== -1) this.panes.splice(index, 1);
- }
- }
- };
- </script>
- <style scoped>
- .tech-tabs-container {
- width: 100%;
- display: flex;
- flex-direction: column;
- }
- .tabs-content {
- flex: 1;
- padding-top: 15px; /* 内容与头部的间距 */
- }
- /* ================== 风格 1:下划线类型 (is-underline) ================== */
- .is-underline .tabs-header {
- display: flex;
- align-items: center;
- gap: 20px;
- padding: 0 10px;
- justify-content: space-between;
- }
- .is-underline .tab-item {
- color: #ffffff;
- font-size: 18px;
- line-height: 25px;
- font-weight: bold;
- padding: 10px 4px;
- cursor: pointer;
- position: relative;
- transition: all 0.3s;
- letter-spacing: 1px;
- user-select: none;
- }
- .is-underline>.tabs-header .tab-item:hover { color: #e2e8f0; }
- .is-underline>.tabs-header .tab-item.is-active {
- color: #1FCEFB;
- text-shadow: 0 0 8px rgba(0, 229, 255, 0.4);
- }
- /* 选中态的底部发光下划线 */
- .is-underline>.tabs-header .tab-item::after {
- content: '';
- position: absolute;
- bottom: -1px;
- left: 50%;
- transform: translateX(-50%);
- width: 0;
- height: 2px;
- background-color: #1FCEFB;
- box-shadow: 0 0 8px #1FCEFB;
- transition: width 0.3s ease;
- }
- .is-underline>.tabs-header .tab-item.is-active::after { width: 100%; }
- /* ================== 风格 2:分段胶囊类型 (is-segmented) ================== */
- .is-segmented .tabs-header {
- position: relative;
- display: flex;
- align-items: center;
- background: rgba(119,161,255,0.14);
- border: 1px solid rgba(119,161,255,0.2);
- overflow: hidden;
- height: 28px;
- gap: 0;
- padding: 0;
- }
- /* 独立的高亮滑块 */
- .segmented-slider {
- position: absolute;
- top: 0;
- left: 0;
- height: 100%;
- background: linear-gradient( 180deg, rgba(119,161,255,0) 0%, #77A1FF 100%);
- border: 1px solid rgba(161,190,255,0.7);
- border-radius: 2px;
- box-sizing: border-box;
- z-index: 0;
- transition: transform 0.35s cubic-bezier(0.645, 0.045, 0.355, 1);
- }
- .is-segmented .tab-item {
- flex: 1;
- position: relative;
- z-index: 1;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
- color: rgba(255, 255, 255, 0.65);
- font-size: 14px;
- line-height: 20px;
- cursor: pointer;
- transition: all 0.3s;
- border-right: 1px solid rgba(119,161,255,0.2);
- padding: 0;
- }
- .is-segmented .tab-item:last-child { border-right: none; }
- .is-segmented .tab-item:hover:not(.is-active) {
- background: rgba(119,161,255,0.1);
- /* color: #ffffff; */
- }
- .is-segmented .tab-item.is-active {
- color: #ffffff;
- font-weight: bold;
- border-right-color: transparent;
- }
- </style>
|