添加一个高度可配置的tab组件,支持顶部、底部、左侧、右侧四种布局方式 提供默认、下划线和卡片三种tab样式,支持自定义颜色、间距、指示器等样式 组件包含导航栏和内容区域,支持动态切换和动画效果 - 添加自定义样式配置功能,允许通过 customStyles 完全覆盖组件样式 - 重构代码结构,使用计算属性合并默认值和传入值 - 优化样式处理逻辑,增加 mixin 复用 - 完善注释和文档说明 - 改进响应式动画效果 - 新增 getTabTitle 方法,支持根据当前语言环境显示对应的标签标题。该方法处理对象形式的标题(按语言键值匹配)和字符串形式的标题(支持国际化键翻译),提升组件的多语言适配能力。
803 lines
27 KiB
Vue
803 lines
27 KiB
Vue
<template>
|
||
<!-- DIY 标签页组件 - 支持多种样式和位置的标签页切换 -->
|
||
<view data-component-name="diy-tab" class="diy-tab" :class="'tab-position-' + mergedValue.tabPosition"
|
||
:style="[getCustomStyle('container')]">
|
||
<!-- 标签导航栏 -->
|
||
<view class="tab-nav" :style="[tabNavStyle, getCustomStyle('nav')]">
|
||
<!-- 标签项循环渲染 -->
|
||
<view v-for="(tab, index) in mergedValue.tabs" :key="index"
|
||
:class="['tab-item', mergedValue.tabStyle, { active: activeTab === index }]" @tap="switchTab(index)"
|
||
:style="[tabItemStyle(index), getCustomStyle('tabItem'), activeTab === index ? getCustomStyle('tabItemActive') : {}]">
|
||
<!-- 标签文本 -->
|
||
<text
|
||
:style="[tabTextStyle(index), getCustomStyle('tabText'), activeTab === index ? getCustomStyle('tabTextActive') : {}]">{{
|
||
getTabTitle(tab.title) }}</text>
|
||
<!-- 标签指示器(底部线条) -->
|
||
<view v-if="mergedValue.showIndicator" class="tab-indicator"
|
||
:style="[tabIndicatorStyle(index), getCustomStyle('indicator')]"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 标签内容区域 -->
|
||
<view class="tab-content" :style="[tabContentStyle, getCustomStyle('content')]">
|
||
<!-- 标签面板循环渲染 -->
|
||
<view v-for="(tab, index) in mergedValue.tabs" :key="index"
|
||
:class="['tab-panel', { active: activeTab === index }]"
|
||
:style="[tabPanelStyle(index), getCustomStyle('panel'), activeTab === index ? getCustomStyle('panelActive') : {}]">
|
||
<!-- 渲染每个标签下的组件 -->
|
||
<diy-group v-if="tab.components" :diyData="{ value: tab.components, global: diyGlobal }"
|
||
:scrollTop="tab.scrollTop || 0" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 导入 DIY 混入
|
||
import DiyMinx from './minx.js'
|
||
|
||
export default {
|
||
name: 'diy-tab',
|
||
|
||
// 组件注册 - 使用懒加载解决循环依赖
|
||
components: {
|
||
diyGroup: () => import('./diy-group.vue')
|
||
},
|
||
|
||
// 组件属性定义
|
||
props: {
|
||
// 标签页配置对象
|
||
value: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
// 全局配置对象
|
||
diyGlobal: {
|
||
type: Object,
|
||
default: () => ({})
|
||
}
|
||
},
|
||
|
||
// 混入
|
||
mixins: [DiyMinx],
|
||
|
||
// 组件数据
|
||
data() {
|
||
return {
|
||
activeTab: 0, // 当前激活的标签索引
|
||
};
|
||
},
|
||
|
||
// 组件创建钩子
|
||
created() {
|
||
console.log(`diy-tab-create`, {
|
||
value: this.mergedValue,
|
||
tabs: this.mergedValue.tabs
|
||
});
|
||
},
|
||
|
||
// 计算属性
|
||
computed: {
|
||
// 合并默认值和传入值
|
||
mergedValue() {
|
||
const defaults = {
|
||
/**
|
||
* 标签页数据配置
|
||
* @type {Array<{title: string|Object, scrollTop: number, components: Array}>}
|
||
* @property {string|Object} title - 标签标题
|
||
* • 字符串: 普通文本或国际化键(如 'tab.home')
|
||
* • 对象: 多语言映射(如 { 'zh-cn': '首页', 'en-us': 'Home' })
|
||
* @property {number} scrollTop - 标签滚动位置
|
||
* @property {Array} components - 标签下的组件列表
|
||
*/
|
||
tabs: [
|
||
{
|
||
title: 'Tab 1',
|
||
scrollTop: 0,
|
||
components: []
|
||
},
|
||
{
|
||
title: {
|
||
'zh-cn': '产品',
|
||
'en-us': 'Products',
|
||
'ja-jp': '製品'
|
||
},
|
||
scrollTop: 0,
|
||
components: []
|
||
}
|
||
],
|
||
|
||
/**
|
||
* 是否显示指示器
|
||
* @type {boolean}
|
||
* @default true
|
||
*/
|
||
showIndicator: true,
|
||
|
||
/**
|
||
* 标签样式
|
||
* @type {string}
|
||
* @default 'default'
|
||
* @values 'default', 'underline', 'card'
|
||
*/
|
||
tabStyle: 'default',
|
||
|
||
/**
|
||
* 标签位置
|
||
* @type {string}
|
||
* @default 'top'
|
||
* @values 'top', 'bottom', 'left', 'right'
|
||
*/
|
||
tabPosition: 'top',
|
||
|
||
/**
|
||
* 标签栏高度
|
||
* @type {number|string}
|
||
* @default 24
|
||
* @unit 像素(当为数字时)
|
||
* @range 建议值:20-80
|
||
* @format
|
||
* • 数字: 像素值(如 24)
|
||
* • 字符串: CSS长度值(如 '24px', '3rem', '4em')
|
||
* • CSS变量: 'var(--tab-height)'
|
||
* • 百分比: '10%' (相对父元素高度)
|
||
*/
|
||
tabHeight: 24,
|
||
|
||
/**
|
||
* 标签栏背景色
|
||
* @type {string}
|
||
* @default '#ffffff'
|
||
* @format CSS颜色值
|
||
*/
|
||
tabBgColor: '#ffffff',
|
||
|
||
/**
|
||
* 标签栏内边距
|
||
* @type {string}
|
||
* @default '0'
|
||
* @format CSS padding值
|
||
* @examples
|
||
* • 单个值: '0', '10px', '1rem' (四向相同)
|
||
* • 两个值: '10px 20px' (上下 左右)
|
||
* • 三个值: '10px 20px 30px' (上 左右 下)
|
||
* • 四个值: '10px 20px 30px 40px' (上 右 下 左)
|
||
* • CSS变量: 'var(--tab-padding)'
|
||
* • 百分比: '5% 10%' (相对父元素宽度)
|
||
* @note 卡片样式下会忽略此配置,自动使用基于 tabGap 的内边距
|
||
*/
|
||
tabPadding: '0',
|
||
|
||
/**
|
||
* 标签间距
|
||
* @type {number|string}
|
||
* @default 10
|
||
* @unit 像素(当为数字时)
|
||
* @range 建议值:0-30
|
||
* @format
|
||
* • 数字: 像素值(如 10)
|
||
* • 字符串: CSS长度值(如 '10px', '0.5rem', '1em')
|
||
* • CSS变量: 'var(--tab-gap)'
|
||
* • 百分比: '5%' (相对父元素宽度)
|
||
*/
|
||
tabGap: 10,
|
||
|
||
/**
|
||
* 字体大小
|
||
* @type {number|string}
|
||
* @default 14
|
||
* @unit 像素(当为数字时)
|
||
* @range 建议值:10-20
|
||
* @format
|
||
* • 数字: 像素值(如 14)
|
||
* • 字符串: CSS长度值(如 '14px', '0.875rem', '1.4em')
|
||
* • CSS变量: 'var(--font-size)'
|
||
*/
|
||
fontSize: 14,
|
||
|
||
/**
|
||
* 激活状态颜色
|
||
* @type {string}
|
||
* @default '#ff4444'
|
||
* @format CSS颜色值
|
||
*/
|
||
activeColor: '#ff4444',
|
||
|
||
/**
|
||
* 非激活状态颜色
|
||
* @type {string}
|
||
* @default '#666666'
|
||
* @format CSS颜色值
|
||
*/
|
||
inactiveColor: '#666666',
|
||
|
||
/**
|
||
* 卡片默认背景色
|
||
* @type {string}
|
||
* @default '#f5f5f5'
|
||
* @format CSS颜色值
|
||
*/
|
||
cardBgColor: '#f5f5f5',
|
||
|
||
/**
|
||
* 卡片激活背景色
|
||
* @type {string}
|
||
* @default '#ff4444'
|
||
* @format CSS颜色值
|
||
*/
|
||
cardActiveBgColor: '#ff4444',
|
||
|
||
/**
|
||
* 卡片默认文字颜色
|
||
* @type {string}
|
||
* @default '#666666'
|
||
* @format CSS颜色值
|
||
*/
|
||
cardTextColor: '#666666',
|
||
|
||
/**
|
||
* 卡片激活文字颜色
|
||
* @type {string}
|
||
* @default '#ffffff'
|
||
* @format CSS颜色值
|
||
*/
|
||
cardActiveTextColor: '#ffffff',
|
||
|
||
/**
|
||
* 卡片圆角大小
|
||
* @type {string}
|
||
* @default '16px'
|
||
* @format CSS长度值
|
||
*/
|
||
cardBorderRadius: '16px',
|
||
|
||
/**
|
||
* 卡片外边距
|
||
* @type {string}
|
||
* @default '0 5px'
|
||
* @format CSS margin值
|
||
*/
|
||
cardMargin: '0 5px',
|
||
|
||
/**
|
||
* 卡片内边距
|
||
* @type {string}
|
||
* @default '0 10px'
|
||
* @format CSS padding值
|
||
*/
|
||
cardPadding: '0 10px',
|
||
|
||
/**
|
||
* 下划线颜色
|
||
* @type {string}
|
||
* @default '#ff4444'
|
||
* @format CSS颜色值
|
||
*/
|
||
underlineColor: '#ff4444',
|
||
|
||
/**
|
||
* 下划线高度
|
||
* @type {number}
|
||
* @default 2
|
||
* @unit 像素
|
||
*/
|
||
underlineHeight: 2,
|
||
|
||
/**
|
||
* 下划线圆角大小
|
||
* @type {string}
|
||
* @default '1px'
|
||
* @format CSS长度值
|
||
*/
|
||
underlineBorderRadius: '1px',
|
||
|
||
/**
|
||
* 下划线左右边距
|
||
* @type {number}
|
||
* @default 10
|
||
* @unit 像素
|
||
*/
|
||
underlineMargin: 10,
|
||
|
||
/**
|
||
* 指示器颜色
|
||
* @type {string}
|
||
* @default '#ff4444'
|
||
* @format CSS颜色值
|
||
*/
|
||
indicatorColor: '#ff4444',
|
||
|
||
/**
|
||
* 指示器高度
|
||
* @type {number}
|
||
* @default 2
|
||
* @unit 像素
|
||
*/
|
||
indicatorHeight: 2,
|
||
|
||
/**
|
||
* 内容区内边距
|
||
* @type {number|string}
|
||
* @default 10
|
||
* @unit 像素(当为数字时)
|
||
* @range 建议值:0-50
|
||
* @format
|
||
* • 数字: 像素值(如 10)
|
||
* • 字符串: CSS长度值(如 '10px', '1rem', '2em')
|
||
* • CSS变量: 'var(--content-padding)'
|
||
* • 百分比: '5%' (相对父元素宽度)
|
||
*/
|
||
contentPadding: 10,
|
||
|
||
/**
|
||
* 内容区背景色
|
||
* @type {string}
|
||
* @default '#f5f5f5'
|
||
* @format CSS颜色值
|
||
* @examples
|
||
* • 十六进制: '#ffffff', '#f5f5f5'
|
||
* • RGB: 'rgb(255, 255, 255)'
|
||
* • RGBA: 'rgba(255, 255, 255, 0.5)'
|
||
* • HSL: 'hsl(0, 0%, 100%)'
|
||
* • CSS变量: 'var(--content-bg-color)'
|
||
* • 预定义颜色: 'white', 'black', 'gray'
|
||
*/
|
||
contentBgColor: '#f5f5f5',
|
||
|
||
/**
|
||
* 内容区最小高度
|
||
* @type {number|string}
|
||
* @default 200
|
||
* @unit 像素(当为数字时)
|
||
* @range 建议值:50-1000
|
||
* @format
|
||
* • 数字: 像素值(如 200)
|
||
* • 字符串: CSS长度值(如 '200px', '20vh', '5rem')
|
||
* • CSS变量: 'var(--content-min-height)'
|
||
* • 百分比: '50%' (相对父元素高度)
|
||
*/
|
||
contentMinHeight: 200,
|
||
|
||
/**
|
||
* 自定义样式配置
|
||
* @type {Object}
|
||
* @description 允许外部通过完整的 CSS 样式对象完全覆盖各个部分的样式
|
||
* @property {Object} container - 容器样式
|
||
* @example { width: '100%', height: '500px', backgroundColor: '#f0f0f0' }
|
||
* @property {Object} nav - 导航栏样式
|
||
* @example { backgroundColor: '#ffffff', borderBottom: '1px solid #e0e0e0' }
|
||
* @property {Object} tabItem - 标签项样式(非激活状态)
|
||
* @example { padding: '10px 20px', borderRadius: '4px' }
|
||
* @property {Object} tabItemActive - 标签项激活样式
|
||
* @example { backgroundColor: '#ff4444', color: '#ffffff' }
|
||
* @property {Object} tabText - 标签文本样式(非激活状态)
|
||
* @example { fontSize: '14px', color: '#666666' }
|
||
* @property {Object} tabTextActive - 标签文本激活样式
|
||
* @example { fontSize: '16px', color: '#ffffff', fontWeight: 'bold' }
|
||
* @property {Object} indicator - 指示器样式
|
||
* @example { backgroundColor: '#ff4444', height: '3px' }
|
||
* @property {Object} content - 内容区域样式
|
||
* @example { padding: '20px', backgroundColor: '#f9f9f9' }
|
||
* @property {Object} panel - 标签面板样式(非激活状态)
|
||
* @example { opacity: 0.5, transform: 'translateY(10px)' }
|
||
* @property {Object} panelActive - 标签面板激活样式
|
||
* @example { opacity: 1, transform: 'translateY(0)' }
|
||
*/
|
||
customStyles: {
|
||
container: {},
|
||
nav: {},
|
||
tabItem: {},
|
||
tabItemActive: {},
|
||
tabText: {},
|
||
tabTextActive: {},
|
||
indicator: {},
|
||
content: {},
|
||
panel: {},
|
||
panelActive: {}
|
||
}
|
||
};
|
||
|
||
// 使用展开运算符合并默认值和传入值
|
||
return { ...defaults, ...this.value };
|
||
},
|
||
|
||
// 判断是否为水平布局(顶部或底部)
|
||
isHorizontal() {
|
||
return ['top', 'bottom'].includes(this.mergedValue.tabPosition);
|
||
},
|
||
|
||
// 判断是否为垂直布局(左侧或右侧)
|
||
isVertical() {
|
||
return ['left', 'right'].includes(this.mergedValue.tabPosition);
|
||
},
|
||
|
||
// 判断是否为卡片样式
|
||
isCardStyle() {
|
||
return this.mergedValue.tabStyle === 'card';
|
||
},
|
||
|
||
// 判断是否为下划线样式
|
||
isUnderlineStyle() {
|
||
return this.mergedValue.tabStyle === 'underline';
|
||
},
|
||
|
||
// 计算标签导航栏样式
|
||
tabNavStyle() {
|
||
const style = {
|
||
backgroundColor: this.mergedValue.tabBgColor || '#ffffff'
|
||
};
|
||
|
||
// 根据布局方向设置尺寸
|
||
if (this.isHorizontal) {
|
||
// 水平布局:设置高度
|
||
style.height = this.mergedValue.tabHeight + 'px';
|
||
} else {
|
||
// 垂直布局:设置宽度和高度
|
||
style.width = this.mergedValue.tabHeight + 'px';
|
||
style.height = '100%';
|
||
style.flexDirection = 'column';
|
||
}
|
||
|
||
// 设置导航栏内边距
|
||
if (this.mergedValue.tabPadding) {
|
||
style.padding = this.mergedValue.tabPadding;
|
||
}
|
||
|
||
// 卡片样式下使用标签间距作为内边距
|
||
if (this.isCardStyle) {
|
||
style.padding = this.getPadding(this.mergedValue.tabGap);
|
||
}
|
||
|
||
return style;
|
||
},
|
||
|
||
// 计算标签项样式(返回函数)
|
||
tabItemStyle() {
|
||
return (index) => {
|
||
const style = {};
|
||
|
||
if (!this.isCardStyle) {
|
||
// 非卡片样式:设置内边距
|
||
style.padding = this.getPadding(this.mergedValue.tabGap);
|
||
} else {
|
||
// 卡片样式:设置外边距、内边距、圆角和背景色
|
||
style.margin = this.getCardMargin();
|
||
style.padding = this.getCardPadding();
|
||
style.borderRadius = this.mergedValue.cardBorderRadius || '16px';
|
||
// 根据激活状态设置不同的背景色
|
||
style.backgroundColor = index === this.activeTab
|
||
? (this.mergedValue.cardActiveBgColor || this.mergedValue.activeColor)
|
||
: (this.mergedValue.cardBgColor || '#f5f5f5');
|
||
}
|
||
|
||
return style;
|
||
};
|
||
},
|
||
|
||
// 计算标签内容区域样式
|
||
tabContentStyle() {
|
||
return {
|
||
padding: this.mergedValue.contentPadding + 'px',
|
||
backgroundColor: this.mergedValue.contentBgColor || '#f5f5f5',
|
||
minHeight: (this.mergedValue.contentMinHeight || 200) + 'px'
|
||
};
|
||
},
|
||
|
||
// 计算标签文本样式(返回函数)
|
||
tabTextStyle() {
|
||
return (index) => ({
|
||
color: this.activeColor(index),
|
||
fontSize: (this.mergedValue.fontSize || 14) + 'px'
|
||
});
|
||
},
|
||
|
||
// 计算标签指示器样式(返回函数)
|
||
tabIndicatorStyle() {
|
||
return (index) => {
|
||
const style = {
|
||
backgroundColor: this.mergedValue.indicatorColor || this.mergedValue.activeColor,
|
||
transform: index === this.activeTab ? 'scaleX(1)' : 'scaleX(0)',
|
||
opacity: index === this.activeTab ? 1 : 0
|
||
};
|
||
|
||
// 卡片样式下隐藏指示器
|
||
if (this.isCardStyle) {
|
||
style.display = 'none';
|
||
} else if (this.isUnderlineStyle) {
|
||
// 下划线样式使用下划线颜色
|
||
style.backgroundColor = this.mergedValue.underlineColor || this.mergedValue.activeColor;
|
||
}
|
||
|
||
// 根据样式类型选择指示器尺寸
|
||
const indicatorSize = (this.isUnderlineStyle ? this.mergedValue.underlineHeight : this.mergedValue.indicatorHeight) || 2;
|
||
|
||
// 根据布局方向设置指示器样式
|
||
if (this.isHorizontal) {
|
||
// 水平布局:设置高度,使用 scaleX 动画
|
||
style.height = indicatorSize + 'px';
|
||
style.transform = index === this.activeTab ? 'scaleX(1)' : 'scaleX(0)';
|
||
} else {
|
||
// 垂直布局:设置宽度,使用 scaleY 动画
|
||
style.width = indicatorSize + 'px';
|
||
style.height = 'auto';
|
||
style.transform = index === this.activeTab ? 'scaleY(1)' : 'scaleY(0)';
|
||
}
|
||
|
||
return style;
|
||
};
|
||
},
|
||
|
||
// 计算标签面板样式(返回函数)
|
||
tabPanelStyle() {
|
||
return (index) => {
|
||
const isActive = index === this.activeTab;
|
||
const style = {
|
||
display: isActive ? 'block' : 'none',
|
||
opacity: isActive ? 1 : 0
|
||
};
|
||
|
||
// 根据标签位置定义不同的动画效果
|
||
const transformMap = {
|
||
top: ['translateY(0)', 'translateY(10px)'], // 顶部:从下往上滑入
|
||
bottom: ['translateY(0)', 'translateY(-10px)'], // 底部:从上往下滑入
|
||
left: ['translateX(0)', 'translateX(10px)'], // 左侧:从右往左滑入
|
||
right: ['translateX(0)', 'translateX(-10px)'] // 右侧:从左往右滑入
|
||
};
|
||
|
||
// 获取对应的变换值,默认使用无变换
|
||
const transforms = transformMap[this.mergedValue.tabPosition] || ['translate(0)', 'translate(0)'];
|
||
// 根据激活状态应用不同的变换
|
||
style.transform = isActive ? transforms[0] : transforms[1];
|
||
|
||
return style;
|
||
};
|
||
}
|
||
},
|
||
|
||
// 组件方法
|
||
methods: {
|
||
// 获取自定义样式
|
||
getCustomStyle(type) {
|
||
const customStyles = this.mergedValue.customStyles || {};
|
||
return customStyles[type] || {};
|
||
},
|
||
|
||
// 获取标签文字颜色
|
||
activeColor(index) {
|
||
if (this.isCardStyle) {
|
||
// 卡片样式:使用卡片文字颜色
|
||
return index === this.activeTab
|
||
? (this.mergedValue.cardActiveTextColor || '#ffffff')
|
||
: (this.mergedValue.cardTextColor || this.mergedValue.inactiveColor);
|
||
}
|
||
// 其他样式:使用通用文字颜色
|
||
return index === this.activeTab ? this.mergedValue.activeColor : this.mergedValue.inactiveColor;
|
||
},
|
||
|
||
// 根据布局方向获取内边距
|
||
getPadding(gap) {
|
||
return this.isHorizontal ? `0 ${gap}px` : `${gap}px 0`;
|
||
},
|
||
|
||
// 根据布局方向获取外边距
|
||
getMargin(gap) {
|
||
return this.isHorizontal ? `0 ${gap}px` : `${gap}px 0`;
|
||
},
|
||
|
||
// 获取卡片外边距
|
||
getCardMargin() {
|
||
return this.mergedValue.cardMargin || this.getMargin(this.mergedValue.tabGap / 2);
|
||
},
|
||
|
||
// 获取卡片内边距
|
||
getCardPadding() {
|
||
return this.mergedValue.cardPadding || (this.isHorizontal ? '0 10px' : '10px 0');
|
||
},
|
||
|
||
// 切换标签
|
||
switchTab(index) {
|
||
this.activeTab = index;
|
||
},
|
||
|
||
// 国际化方法:获取标签标题
|
||
getTabTitle(title) {
|
||
const locale = this.$langConfig.getCurrentLocale();
|
||
|
||
// 如果 title 是对象,根据当前语言返回对应值
|
||
if (typeof title === 'object' && title !== null) {
|
||
return title[locale] || title['zh-cn'] || Object.values(title)[0] || '';
|
||
}
|
||
|
||
// 如果 title 是字符串,保持原有逻辑
|
||
if (typeof title === 'string' && title.includes('.')) {
|
||
// 包含点号的标题视为国际化键,使用全局挂载的 $lang 方法翻译
|
||
return this.$lang ? this.$lang(title) : title;
|
||
}
|
||
|
||
// 不包含点号的标题直接返回
|
||
return title;
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// ===== 标签布局 Mixin =====
|
||
// 用于定义不同位置标签的布局方式
|
||
@mixin tab-layout($direction, $nav-order, $content-order) {
|
||
flex-direction: $direction;
|
||
|
||
.tab-nav {
|
||
order: $nav-order;
|
||
}
|
||
|
||
.tab-content {
|
||
order: $content-order;
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
// ===== 指示器位置 Mixin =====
|
||
// 用于定义不同位置指示器的定位
|
||
@mixin indicator-position($position, $start, $end) {
|
||
#{$position}: 0;
|
||
#{$start}: 0;
|
||
#{$end}: 0;
|
||
width: 2px;
|
||
height: auto;
|
||
}
|
||
|
||
// ===== 主容器样式 =====
|
||
.diy-tab {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
display: flex;
|
||
|
||
// 默认顶部布局
|
||
@include tab-layout(column, 1, 2);
|
||
|
||
// 底部布局
|
||
&.tab-position-bottom {
|
||
@include tab-layout(column, 2, 1);
|
||
}
|
||
|
||
// 左侧布局
|
||
&.tab-position-left {
|
||
@include tab-layout(row, 1, 2);
|
||
}
|
||
|
||
// 右侧布局
|
||
&.tab-position-right {
|
||
@include tab-layout(row, 2, 1);
|
||
}
|
||
|
||
// ===== 标签导航栏样式 =====
|
||
.tab-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
overflow-x: auto; // 水平滚动
|
||
overflow-y: hidden; // 禁止垂直滚动
|
||
white-space: nowrap; // 不换行
|
||
position: relative;
|
||
|
||
// 隐藏滚动条(Webkit 浏览器)
|
||
&::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
// 隐藏滚动条(IE/Edge)
|
||
-ms-overflow-style: none;
|
||
// 隐藏滚动条(Firefox)
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
// ===== 标签项样式 =====
|
||
.tab-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
padding: 0 16px;
|
||
height: 100%;
|
||
transition: all 0.3s ease;
|
||
|
||
// 激活状态
|
||
&.active {
|
||
font-weight: 500;
|
||
}
|
||
|
||
// 减少动画效果(针对偏好减少动画的用户)
|
||
@media (prefers-reduced-motion: reduce) {
|
||
transition: none;
|
||
}
|
||
}
|
||
|
||
// 左右布局下的标签项样式
|
||
&.tab-position-left .tab-item,
|
||
&.tab-position-right .tab-item {
|
||
width: 100%;
|
||
height: auto;
|
||
padding: 10px 0;
|
||
white-space: normal; // 允许换行
|
||
}
|
||
|
||
// ===== 标签文本样式 =====
|
||
.tab-text {
|
||
font-size: 14px;
|
||
transition: color 0.3s ease;
|
||
}
|
||
|
||
// ===== 标签指示器样式 =====
|
||
.tab-indicator {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 16px;
|
||
right: 16px;
|
||
transition: all 0.3s ease;
|
||
transform-origin: center;
|
||
}
|
||
|
||
// 左侧布局的指示器
|
||
&.tab-position-left .tab-indicator {
|
||
@include indicator-position(left, top, bottom);
|
||
}
|
||
|
||
// 右侧布局的指示器
|
||
&.tab-position-right .tab-indicator {
|
||
@include indicator-position(right, top, bottom);
|
||
}
|
||
|
||
// ===== 标签内容区域样式 =====
|
||
.tab-content {
|
||
width: 100%;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
// ===== 标签面板样式 =====
|
||
.tab-panel {
|
||
width: 100%;
|
||
min-height: 200px;
|
||
transition: all 0.3s ease;
|
||
|
||
// 减少动画效果
|
||
@media (prefers-reduced-motion: reduce) {
|
||
transition: none;
|
||
}
|
||
}
|
||
|
||
// ===== 默认和下划线样式 =====
|
||
.tab-item.default,
|
||
.tab-item.underline {
|
||
padding: 0 10px;
|
||
transition: all 0.3s ease;
|
||
|
||
&.active {
|
||
color: #ff4444;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
// 下划线样式的伪元素
|
||
.tab-item.underline.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 10px;
|
||
right: 10px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
// ===== 卡片样式 =====
|
||
.tab-item.card {
|
||
border-radius: 16px;
|
||
transition: all 0.3s ease;
|
||
|
||
&.active {
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
</style> |