feat: restore 3-page swiper; center mascot page with bubble above head + tap bounce; bigger mascot
This commit is contained in:
+218
-32
@@ -1,27 +1,59 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- 吉祥物:唯一主体,点击互动 -->
|
||||
<image class="mascot {{tapAnim ? 'mascot-bounce' : ''}}" src="/common/dsniang.png" onclick="onTap" />
|
||||
<swiper class="swiper" index="{{currentIndex}}" indicator="false" loop="false"
|
||||
previousmargin="14px" nextmargin="14px" onchange="onSwipeChange">
|
||||
|
||||
<!-- 峰谷实时指示(绿色谷时 / 红色峰时) -->
|
||||
<text class="peak {{isPeak ? 'peak-red' : 'peak-green'}}">{{peakLabel}}</text>
|
||||
<!-- 左页:会话 -->
|
||||
<div class="page-item left-page">
|
||||
<text class="page-title">会话</text>
|
||||
<div class="session-list">
|
||||
<div class="session-row" for="{{sessions}}" onclick="selectSession($index)">
|
||||
<text class="session-title">{{$item.title}}</text>
|
||||
<text class="session-dot" if="{{$item.running}}">●</text>
|
||||
</div>
|
||||
</div>
|
||||
<text class="page-hint" if="{{!sessions.length}}">暂无会话</text>
|
||||
</div>
|
||||
|
||||
<!-- Token 使用统计 / 余额 -->
|
||||
<text class="stats">余额 {{balance}}</text>
|
||||
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
|
||||
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
|
||||
<div class="page-item center-page">
|
||||
<div class="bubble" if="{{bubbleVisible}}">
|
||||
<text class="bubble-title">DeepSeek 余额</text>
|
||||
<text class="bubble-balance">¥{{bubbleBalance}}</text>
|
||||
<text class="bubble-usage">今日已用 ¥{{bubbleUsage}}</text>
|
||||
<text class="bubble-mode {{isPeak ? 'bubble-mode-peak' : 'bubble-mode-off'}}">{{peakLabel}}</text>
|
||||
<text class="bubble-fun">{{bubbleText}}</text>
|
||||
</div>
|
||||
<image class="mascot {{tapAnim ? 'mascot-tap' : ''}}" src="/common/dsniang.png" onclick="onTap" />
|
||||
<text class="peak {{isPeak ? 'peak-red' : 'peak-green'}}">{{peakLabel}}</text>
|
||||
<text class="stats">余额 {{balance}}</text>
|
||||
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
|
||||
</div>
|
||||
|
||||
<!-- 点击吉祥物弹出的“对话小框”(类 DSH 挂件气泡) -->
|
||||
<div class="bubble" if="{{bubbleVisible}}">
|
||||
<text class="bubble-title">DeepSeek 余额</text>
|
||||
<text class="bubble-balance">¥{{bubbleBalance}}</text>
|
||||
<text class="bubble-usage">今日已用 ¥{{bubbleUsage}}</text>
|
||||
<text class="bubble-mode {{isPeak ? 'bubble-mode-peak' : 'bubble-mode-off'}}">{{peakLabel}}</text>
|
||||
<text class="bubble-fun">{{bubbleText}}</text>
|
||||
<!-- 右页:设置 -->
|
||||
<div class="page-item right-page">
|
||||
<text class="page-title">设置</text>
|
||||
<text class="setting-label">服务器</text>
|
||||
<input class="setting-input" type="text" value="{{settings.serverUrl}}" onchange="onServerChange" />
|
||||
<text class="setting-label">用户ID</text>
|
||||
<input class="setting-input" type="text" value="{{settings.userId}}" onchange="onUserChange" />
|
||||
<text class="setting-label">Token</text>
|
||||
<input class="setting-input" type="text" value="{{settings.token}}" onchange="onTokenChange" />
|
||||
<text class="conn-text">{{connText}}</text>
|
||||
</div>
|
||||
</swiper>
|
||||
|
||||
<div class="dots">
|
||||
<div class="dot {{currentIndex == 0 ? 'dot-active' : ''}}"></div>
|
||||
<div class="dot {{currentIndex == 1 ? 'dot-active' : ''}}"></div>
|
||||
<div class="dot {{currentIndex == 2 ? 'dot-active' : ''}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bridge from '../../common/bridge.js'
|
||||
|
||||
// 懒加载 @system.audio,避免模拟器/无扬声器时崩
|
||||
let audioModule = null
|
||||
let audioTried = false
|
||||
@@ -46,6 +78,12 @@ function playDuckSound() {
|
||||
|
||||
export default {
|
||||
private: {
|
||||
currentIndex: 1,
|
||||
sessions: [
|
||||
{ title: '原型演示会话', running: false },
|
||||
{ title: 'UI 对齐中', running: true },
|
||||
{ title: '待接入 Harness', running: false }
|
||||
],
|
||||
isPeak: false,
|
||||
peakLabel: '谷时 · 半价',
|
||||
balance: '--',
|
||||
@@ -56,6 +94,12 @@ export default {
|
||||
bubbleBalance: '--',
|
||||
bubbleUsage: '--',
|
||||
tapAnim: false,
|
||||
settings: {
|
||||
serverUrl: 'ws://127.0.0.1:8787',
|
||||
userId: '',
|
||||
token: ''
|
||||
},
|
||||
connText: '手机/手表未连接',
|
||||
lines: [
|
||||
'我算得比你快,但没你可爱。',
|
||||
'余额在跳,我也在跳,别慌。',
|
||||
@@ -71,7 +115,25 @@ export default {
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 后续接服务器时在这里拉余额/用量
|
||||
this._unsub = bridge.subscribe({
|
||||
status: (s) => {
|
||||
this.connText = s || '未连接'
|
||||
},
|
||||
message: (msg) => {
|
||||
this.handleBridgeMessage(msg)
|
||||
}
|
||||
})
|
||||
bridge.refreshConn()
|
||||
},
|
||||
|
||||
onDestroy() {
|
||||
if (this._unsub) this._unsub()
|
||||
},
|
||||
|
||||
onSwipeChange(e) {
|
||||
if (e && typeof e.index === 'number') {
|
||||
this.currentIndex = e.index
|
||||
}
|
||||
},
|
||||
|
||||
refreshPeak() {
|
||||
@@ -87,19 +149,30 @@ export default {
|
||||
},
|
||||
|
||||
onTap() {
|
||||
// Q 弹互动 + 小黄鸭叫声(无扬声器时静默)
|
||||
playDuckSound()
|
||||
// 敲击一下 Q 弹
|
||||
this.tapAnim = true
|
||||
const self = this
|
||||
setTimeout(() => { self.tapAnim = false }, 400)
|
||||
setTimeout(() => { self.tapAnim = false }, 280)
|
||||
|
||||
// 对话小框:显示一句幽默文案 + 峰谷信息
|
||||
const idx = Math.floor(Math.random() * this.lines.length)
|
||||
const mode = this.isPeak ? '当前是峰时,钱包瑟瑟发抖' : '当前是谷时,薅羊毛好时机'
|
||||
this.bubbleText = this.lines[idx]
|
||||
this.bubbleText = this.lines[idx] + ' | ' + mode
|
||||
this.bubbleBalance = this.balance
|
||||
this.bubbleUsage = this.todayCost
|
||||
this.bubbleVisible = !this.bubbleVisible
|
||||
},
|
||||
|
||||
selectSession(index) {
|
||||
this.currentIndex = 1
|
||||
},
|
||||
|
||||
onServerChange(e) { this.settings.serverUrl = e && e.value || '' },
|
||||
onUserChange(e) { this.settings.userId = e && e.value || '' },
|
||||
onTokenChange(e) { this.settings.token = e && e.value || '' },
|
||||
|
||||
handleBridgeMessage(msg) {
|
||||
// 空壳:后续按 PROTOCOL.md 处理 status/event
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -109,18 +182,85 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #0b1220;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
.swiper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
indicator-size: 0px;
|
||||
}
|
||||
|
||||
.mascot-bounce {
|
||||
transform: scale(0.92);
|
||||
.page-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.left-page,
|
||||
.right-page {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.center-page {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* 给气泡留顶部空间 */
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: #e6f0ff;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-hint {
|
||||
color: #6b7a99;
|
||||
font-size: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.session-list {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.session-row {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 10px;
|
||||
border-radius: 10px;
|
||||
background-color: #152238;
|
||||
}
|
||||
|
||||
.session-title {
|
||||
color: #dbe8ff;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.session-dot {
|
||||
color: #33b4ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 吉祥物:比之前大一点 + 敲击 Q 弹 */
|
||||
.mascot {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.mascot-tap {
|
||||
transform: scale(0.9) translateY(3px);
|
||||
}
|
||||
|
||||
.peak {
|
||||
@@ -143,14 +283,15 @@ export default {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 气泡:头像正上方居中,稍微放大 */
|
||||
.bubble {
|
||||
position: absolute;
|
||||
top: 90px;
|
||||
top: 64px;
|
||||
left: 50%;
|
||||
margin-left: -110px;
|
||||
width: 220px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
margin-left: -120px;
|
||||
width: 240px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
@@ -162,7 +303,7 @@ export default {
|
||||
|
||||
.bubble-balance {
|
||||
color: #1c2c47;
|
||||
font-size: 26px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -193,4 +334,49 @@ export default {
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
color: #8fa4c9;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.setting-input {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
background-color: #152238;
|
||||
color: #e6f0ff;
|
||||
font-size: 15px;
|
||||
padding: 0 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.conn-text {
|
||||
color: #6b7a99;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dots {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 14px;
|
||||
margin-top: 4px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.28);
|
||||
}
|
||||
|
||||
.dot-active {
|
||||
width: 22px;
|
||||
border-radius: 4px;
|
||||
background-color: #33b4ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user