feat: 为标签组件添加悬停动画和对齐选项

This commit is contained in:
2026-02-07 11:34:45 +08:00
parent 069b3c2c07
commit bb58866192

View File

@@ -68,9 +68,9 @@
<!-- 标签导航 -->
<view class="tab-nav" :style="tabNavStyle">
<view v-for="(tab, idx) in mergedValue.tabs" :key="tab.id || idx" class="tab-item"
:class="{ active: activeTab === idx }"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}]">
:class="[{ active: activeTab === idx }, { 'hover-animation': mergedValue.animation }]"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}, tabItemHoverStyle]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}, mergedValue.animation ? tabTextHoverStyle : {}]">
{{ getTabTitle(tab.title) }}
</text>
</view>
@@ -83,9 +83,9 @@
<!-- 左侧标签导航 -->
<view class="tab-nav tab-nav-vertical" :style="tabNavStyle">
<view v-for="(tab, idx) in mergedValue.tabs" :key="tab.id || idx" class="tab-item tab-item-vertical"
:class="{ active: activeTab === idx }"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}]">
:class="[{ active: activeTab === idx }, { 'hover-animation': mergedValue.animation }]"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}, tabItemHoverStyle]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}, mergedValue.animation ? tabTextHoverStyle : {}]">
{{ getTabTitle(tab.title) }}
</text>
</view>
@@ -147,9 +147,9 @@
<!-- 右侧标签导航 -->
<view class="tab-nav tab-nav-vertical" :style="tabNavStyle">
<view v-for="(tab, idx) in mergedValue.tabs" :key="tab.id || idx" class="tab-item tab-item-vertical"
:class="{ active: activeTab === idx }"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}]">
:class="[{ active: activeTab === idx }, { 'hover-animation': mergedValue.animation }]"
:style="[tabItemStyle, activeTab === idx ? tabItemActiveStyle : {}, tabItemHoverStyle]" @tap="switchTab(idx)">
<text class="tab-text" :style="[tabTextStyle, activeTab === idx ? tabTextActiveStyle : {}, mergedValue.animation ? tabTextHoverStyle : {}]">
{{ getTabTitle(tab.title) }}
</text>
</view>
@@ -233,7 +233,8 @@ export default {
showIndicator: true,
activeTabIndex: 0,
tabStyle: 'default',
tabPosition: 'top'
tabPosition: 'top',
animation: true
};
// 导航栏样式
@@ -241,15 +242,19 @@ export default {
tabHeight: 24,
tabBgColor: '#ffffff',
tabPadding: '0',
tabGap: 10
tabGap: 10,
navAlign: 'start'
};
// 标签项样式
const tabItemConfig = {
tabGap: 10,
fontSize: 14,
fontWeight: 400,
activeColor: '#339af0',
inactiveColor: '#6c757d'
inactiveColor: '#6c757d',
hoverColor: '#3b82f6',
activeFontWeight: 500
};
// 卡片样式
@@ -278,7 +283,8 @@ export default {
const indicatorConfig = {
indicatorColor: '#339af0',
indicatorHeight: 2,
indicatorWidth: '100%'
indicatorWidth: '100%',
indicatorBorderRadius: '1px'
};
// 内容区域样式
@@ -310,8 +316,10 @@ export default {
nav: {},
tabItem: {},
tabItemActive: {},
tabItemHover: {},
tabText: {},
tabTextActive: {},
tabTextHover: {},
indicator: {},
content: {},
panel: {},
@@ -383,8 +391,26 @@ export default {
// 对于水平布局(顶部和底部),设置固定高度
if (this.mergedValue.tabPosition === 'top' || this.mergedValue.tabPosition === 'bottom') {
style.height = this.formatStyleValue(this.mergedValue.tabHeight, 24);
// 应用对齐方式
if (this.mergedValue.navAlign === 'start') {
style.justifyContent = 'flex-start';
} else if (this.mergedValue.navAlign === 'center') {
style.justifyContent = 'center';
} else if (this.mergedValue.navAlign === 'end') {
style.justifyContent = 'flex-end';
}
}
// 对于垂直布局(左侧和右侧),不设置固定高度,让它自适应内容
else {
// 垂直布局应用对齐方式
if (this.mergedValue.navAlign === 'start') {
style.alignItems = 'flex-start';
} else if (this.mergedValue.navAlign === 'center') {
style.alignItems = 'center';
} else if (this.mergedValue.navAlign === 'end') {
style.alignItems = 'flex-end';
}
}
// 应用导航栏边框样式
if (this.mergedValue.showNavBorder) {
@@ -434,6 +460,7 @@ export default {
tabTextStyle() {
return {
fontSize: this.formatStyleValue(this.mergedValue.fontSize, 14),
fontWeight: this.mergedValue.fontWeight || 400,
color: this.mergedValue.inactiveColor,
...this.mergedValue.customStyles.tabText
};
@@ -443,10 +470,26 @@ export default {
tabTextActiveStyle() {
return {
color: this.mergedValue.activeColor,
fontWeight: this.mergedValue.activeFontWeight || 500,
...this.mergedValue.customStyles.tabTextActive
};
},
// 标签项悬停样式
tabItemHoverStyle() {
return {
...this.mergedValue.customStyles.tabItemHover
};
},
// 标签文本悬停样式
tabTextHoverStyle() {
return {
color: this.mergedValue.hoverColor || '#3b82f6',
...this.mergedValue.customStyles.tabTextHover
};
},
// 指示器样式(与后端版本一致)
indicatorStyle() {
// 计算高度,考虑不同样式
@@ -986,7 +1029,6 @@ export default {
padding: 0px 20px 0px;
cursor: pointer;
position: relative;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: inline-flex;
align-items: center;
justify-content: center;
@@ -997,6 +1039,16 @@ export default {
color: var(--active-color, #339af0);
}
// 悬停动画效果
&.hover-animation {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
color: var(--hover-color, #3b82f6);
transform: translateY(-1px);
}
}
// 减少动画效果(针对偏好减少动画的用户)
@media (prefers-reduced-motion: reduce) {
transition: none;
@@ -1015,7 +1067,6 @@ export default {
font-size: 14px;
font-weight: 400;
color: inherit;
transition: color 0.3s ease;
}
// ===== 标签指示器样式 =====
@@ -1027,6 +1078,9 @@ export default {
bottom: 0;
left: 16px;
right: 16px;
// 支持圆角
border-radius: var(--indicator-border-radius, 1px);
}
// 垂直指示器(用于左右布局)
@@ -1122,7 +1176,6 @@ export default {
border-radius: 16px;
margin: 0 5px;
padding: 0 10px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
color: var(--card-text-color, #6c757d);
@@ -1130,10 +1183,14 @@ export default {
height: auto;
padding: 8px 16px;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
transform: translateY(-1px);
background-color: #f8f9fa;
&.hover-animation {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
transform: translateY(-1px);
background-color: #f8f9fa;
}
}
&.active {
@@ -1142,7 +1199,7 @@ export default {
box-shadow: 0 2px 10px rgba(51, 154, 240, 0.4);
transform: translateY(-1px);
&:hover {
&.hover-animation:hover {
background-color: var(--card-active-hover-bg-color, #228be6);
color: var(--card-active-hover-text-color, #ffffff);
box-shadow: 0 3px 12px rgba(51, 154, 240, 0.45);
@@ -1162,16 +1219,19 @@ export default {
margin: 5px 10px;
padding: 12px 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-bottom: none;
position: relative;
overflow: hidden;
color: var(--card-text-color, #6c757d);
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
transform: translateX(-2px);
background-color: #f8f9fa;
&.hover-animation {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
transform: translateX(-2px);
background-color: #f8f9fa;
}
}
&.active {
@@ -1180,7 +1240,7 @@ export default {
box-shadow: 0 2px 10px rgba(51, 154, 240, 0.4);
transform: translateX(-2px);
&:hover {
&.hover-animation:hover {
background-color: #228be6;
box-shadow: 0 3px 12px rgba(51, 154, 240, 0.45);
}