style(entry): 训练浮动入口美化,移除 DEV 标签

主按钮:
- 移除右上角红色 DEV 标签
- 单字"练" 48rpx 粗字号居中,不再 emoji+文字双层
- emerald 色由 #34d399 → #047857 渐变,跟节拍器主色统一
- 加双层立体阴影 + inset 高光,看起来像凸起的玻璃按钮
- 加 2 圈错相位呼吸光环(2.4s 周期),暗示可点击
- 关闭态用 CSS 几何 X 图标,旋转 135° 动画过渡

展开菜单:
- 圆角药丸卡片,带 backdrop-blur 玻璃质感,层级阴影
- "练一练"圆形 icon emerald 渐变 + 💪 emoji
- "节拍器"图标改成 3 根高低柱 CSS 模拟节拍/均衡器,
  琥珀色渐变跟节拍器页面重音色呼应
- 右侧加细 › 箭头暗示可跳转
- 入场动画从 scale(0.92) 弹性放大,更顺滑

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-25 18:24:35 +08:00
co-authored by Cursor
parent 5aa0cda252
commit 769d912eef
@@ -1,26 +1,42 @@
<template>
<view v-if="visible" class="dev-entry-wrap" :style="{ bottom: props.bottom + 'rpx' }">
<!-- 展开后的菜单卡片(从下往上动画) -->
<view v-if="expanded" class="menu-list" @click.stop>
<view class="menu-item" @click="goto('/pages/training/index')">
<view class="menu-icon">💪</view>
<view class="menu-icon menu-icon--train">
<text class="menu-icon-text">💪</text>
</view>
<view class="menu-info">
<text class="menu-title">练一练</text>
<text class="menu-sub">器械跟练</text>
</view>
<view class="menu-arrow"></view>
</view>
<view class="menu-item" @click="goto('/pages/training/metronome')">
<view class="menu-icon metronome-icon">🎵</view>
<view class="menu-icon menu-icon--metro">
<view class="metro-bar metro-bar-1" />
<view class="metro-bar metro-bar-2" />
<view class="metro-bar metro-bar-3" />
</view>
<view class="menu-info">
<text class="menu-title">节拍器</text>
<text class="menu-sub">独立工具</text>
<text class="menu-sub">健走配速</text>
</view>
<view class="menu-arrow"></view>
</view>
</view>
<view class="entry-fab" :class="{ expanded }" @click="toggle">
<text class="fab-icon">{{ expanded ? '✕' : '💪' }}</text>
<text v-if="!expanded" class="fab-text"></text>
<view v-if="!expanded" class="fab-tag">DEV</view>
<!-- 主浮动按钮(单字"练" + 呼吸光环) -->
<view class="fab-container">
<view v-if="!expanded" class="fab-pulse" />
<view v-if="!expanded" class="fab-pulse fab-pulse-2" />
<view class="entry-fab" :class="{ expanded }" @click="toggle">
<view v-if="expanded" class="icon-close">
<view class="cross-bar bar-1" />
<view class="cross-bar bar-2" />
</view>
<text v-else class="fab-text"></text>
</view>
</view>
</view>
</template>
@@ -36,7 +52,7 @@ const props = withDefaults(
)
const isDevMode = (): boolean => {
// HBuilderX 工程:发行(release)模式下 NODE_ENV === 'production'
/* HBuilderX 工程:发行(release)模式下 NODE_ENV === 'production' */
try {
return process.env.NODE_ENV !== 'production'
} catch (_) {
@@ -58,6 +74,10 @@ const goto = (url: string) => {
</script>
<style lang="scss" scoped>
$brand: #10b981;
$brand-deep: #047857;
$brand-light: #34d399;
.dev-entry-wrap {
position: fixed;
right: 24rpx;
@@ -65,70 +85,120 @@ const goto = (url: string) => {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 16rpx;
gap: 18rpx;
}
/* ============================================================
* 主浮动按钮
* ============================================================ */
.fab-container {
position: relative;
width: 108rpx;
height: 108rpx;
}
.entry-fab {
width: 110rpx;
height: 110rpx;
position: absolute;
inset: 0;
border-radius: 50%;
background: linear-gradient(135deg, #22c55e, #16a34a);
box-shadow: 0 6rpx 20rpx rgba(34, 197, 94, 0.4);
background: linear-gradient(140deg, $brand-light 0%, $brand-deep 100%);
box-shadow:
0 8rpx 20rpx rgba(16, 185, 129, 0.42),
0 2rpx 6rpx rgba(15, 23, 42, 0.12),
inset 0 -8rpx 16rpx rgba(0, 0, 0, 0.12),
inset 0 8rpx 16rpx rgba(255, 255, 255, 0.18);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: transform 0.2s, background 0.2s;
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s;
&:active {
transform: scale(0.92);
}
&.expanded {
background: linear-gradient(135deg, #64748b, #334155);
transform: rotate(90deg);
}
.fab-icon {
font-size: 36rpx;
line-height: 1;
background: linear-gradient(140deg, #94a3b8 0%, #475569 100%);
transform: rotate(135deg);
box-shadow:
0 6rpx 16rpx rgba(15, 23, 42, 0.2),
inset 0 -6rpx 14rpx rgba(0, 0, 0, 0.12),
inset 0 6rpx 14rpx rgba(255, 255, 255, 0.12);
}
.fab-text {
font-size: 18rpx;
font-size: 48rpx;
font-weight: 800;
color: #fff;
margin-top: 4rpx;
line-height: 1;
}
.fab-tag {
position: absolute;
top: -8rpx;
right: -8rpx;
background: #ef4444;
color: #fff;
font-size: 16rpx;
padding: 2rpx 8rpx;
border-radius: 8rpx;
line-height: 1.4;
letter-spacing: 2rpx;
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.18);
}
}
/* 关闭态 X 图标 (CSS 几何) */
.icon-close {
position: relative;
width: 36rpx;
height: 36rpx;
.cross-bar {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 5rpx;
background: #fff;
border-radius: 3rpx;
transform-origin: center;
}
.bar-1 {
transform: translateY(-50%) rotate(45deg);
}
.bar-2 {
transform: translateY(-50%) rotate(-45deg);
}
}
/* 呼吸光环(2 圈错相位扩散) */
.fab-pulse {
position: absolute;
inset: 0;
border-radius: 50%;
background: rgba(16, 185, 129, 0.25);
animation: fab-ring 2.4s ease-out infinite;
pointer-events: none;
}
.fab-pulse-2 {
animation-delay: 1.2s;
}
@keyframes fab-ring {
0% {
transform: scale(0.95);
opacity: 0.7;
}
100% {
transform: scale(1.55);
opacity: 0;
}
}
/* ============================================================
* 展开菜单
* ============================================================ */
.menu-list {
display: flex;
flex-direction: column;
gap: 12rpx;
animation: slide-up 0.2s ease-out;
gap: 14rpx;
animation: slide-up 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(20rpx);
transform: translateY(20rpx) scale(0.92);
}
to {
opacity: 1;
transform: translateY(0);
transform: translateY(0) scale(1);
}
}
@@ -136,45 +206,88 @@ const goto = (url: string) => {
display: flex;
align-items: center;
gap: 16rpx;
background: #fff;
padding: 16rpx 24rpx;
border-radius: 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
min-width: 240rpx;
background: rgba(255, 255, 255, 0.96);
padding: 14rpx 20rpx 14rpx 14rpx;
border-radius: 999rpx;
box-shadow:
0 8rpx 24rpx rgba(15, 23, 42, 0.1),
0 1rpx 2rpx rgba(15, 23, 42, 0.06);
min-width: 280rpx;
transition: transform 0.15s;
backdrop-filter: blur(12rpx);
&:active {
transform: scale(0.97);
background: #f0fdf4;
}
}
.menu-icon {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: linear-gradient(135deg, #22c55e, #16a34a);
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
.menu-icon {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 2rpx 8rpx rgba(16, 185, 129, 0.32);
&.metronome-icon {
background: linear-gradient(135deg, #6366f1, #4f46e5);
}
&--train {
background: linear-gradient(140deg, $brand-light, $brand-deep);
}
.menu-info {
display: flex;
flex-direction: column;
&--metro {
background: linear-gradient(140deg, #fbbf24, #d97706);
box-shadow: 0 2rpx 8rpx rgba(245, 158, 11, 0.36);
gap: 4rpx;
}
}
.menu-title {
font-size: 28rpx;
color: #111827;
font-weight: 600;
}
.menu-sub {
font-size: 20rpx;
color: #9ca3af;
}
.menu-icon-text {
font-size: 32rpx;
line-height: 1;
}
/* 节拍器小 icon: 3 根高低柱模拟均衡器/节拍 */
.metro-bar {
width: 4rpx;
background: #fff;
border-radius: 2rpx;
box-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.18);
}
.metro-bar-1 {
height: 16rpx;
}
.metro-bar-2 {
height: 28rpx;
}
.metro-bar-3 {
height: 22rpx;
}
.menu-info {
display: flex;
flex-direction: column;
gap: 2rpx;
flex: 1;
}
.menu-title {
font-size: 28rpx;
color: #0f172a;
font-weight: 700;
letter-spacing: 1rpx;
}
.menu-sub {
font-size: 20rpx;
color: #94a3b8;
letter-spacing: 0.5rpx;
}
.menu-arrow {
font-size: 32rpx;
color: #cbd5e1;
font-weight: 300;
line-height: 1;
margin-left: 4rpx;
}
</style>