Files
wx_pbzc/pages/timer/timer.wxml
T
lc 0464eeaba6 feat(timer): 训练页改版 hero 风格 + 进度环刻度 + 醒目姿态提示气泡
- progress-ring: canvas 新增秒刻度层(四分位加粗), 环内加 subText, 移除组件内 statusText
- timer.wxml: 环下方状态词 + 目标信息卡(今日目标·第N天›静态箭头) + 运行提示; 进度环传 ticks/subText
- timer.js: 派生 ringTicks/ringSubText/statusText/goalLine/runningTip/tipType/tipIcon, 各生命周期计算下发
- timer.wxss: 按钮贴底(flex-start+margin-top:auto), 新增 .timer-status/.goal-card/.timer-tip 气泡卡片(主色左边框+小三角+tipPulse 呼吸), 文字放大加粗
- 姿态提示分语义: posture/phase(主色) / done(成功色), 图标用 peopleFill/successFill
- 状态机/计时/循环FSM/呼吸环/彩带/弹窗/语音震动常亮全部保留
- 另含 index 进度地图右上角奖杯图标删除(quest-trophy 样式清理)
2026-08-11 15:38:37 +08:00

232 lines
9.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<view class="container timer-page page-enter" style="{{themeStyle}}">
<!-- 全屏彩带完成庆祝 -->
<view class="confetti" wx:if="{{isCompleted}}">
<view
wx:for="{{confettiPieces}}"
wx:key="i"
class="confetti-piece confetti-piece--{{celebrationLevel}}"
style="left: {{item.left}}%; background: {{item.color}}; animation-delay: {{item.delay}}s; transform: rotate({{item.rotate}}deg); width: {{item.size}}rpx; height: {{item.size * 2}}rpx;"
></view>
</view>
<!-- 成就横幅 (record / milestone / first) -->
<view class="achievement-banner" wx:if="{{isCompleted && celebrationLevel !== 'normal'}}">
<text class="achievement-text">{{celebrationMessage}}</text>
</view>
<!-- 循环训练进度面板:组点阵 + 第 X/Y 组 + 阶段徽章 + 累计秒数。
层次分工:这里管「第几组」(宏观),中间的进度环管「本组还剩几秒」(微观)。 -->
<view
class="circuit-bar {{isResting ? 'is-rest' : ''}} {{status === 'paused' ? 'is-paused' : ''}}"
wx:if="{{isCircuitMode && status !== 'completed'}}"
>
<!-- 组进度点阵:已完成 / 进行中 / 下一组 三态 -->
<view class="circuit-dots" wx:if="{{circuitShowDots}}">
<view
wx:for="{{circuitDots}}"
wx:key="*this"
class="circuit-dot {{item <= dotsDone ? 'is-done' : ''}} {{item === dotsActive ? 'is-active' : ''}} {{item === dotsNext ? 'is-next' : ''}}"
></view>
</view>
<!-- 组数过多(>15)时退化为线性进度条,避免点阵挤成头发丝 -->
<view class="circuit-track" wx:else>
<view class="circuit-track-fill" style="width: {{dotsDone * 100 / totalSets}}%"></view>
</view>
<view class="circuit-meta">
<view class="circuit-count">
<text class="circuit-count-num">{{status === 'idle' ? totalSets : currentSet}}</text>
<text class="circuit-count-total" wx:if="{{status !== 'idle'}}">/{{totalSets}}</text>
<text class="circuit-count-unit">组</text>
</view>
<view class="circuit-phase circuit-phase--{{status === 'paused' ? 'pause' : (status === 'idle' ? 'idle' : (isResting ? 'rest' : 'work'))}}">
<view class="circuit-phase-dot"></view>
<text class="circuit-phase-text">{{status === 'paused' ? '已暂停' : (status === 'idle' ? '准备开始' : (isResting ? '休息中' : '撑住'))}}</text>
</view>
</view>
<text class="circuit-sub">
<block wx:if="{{status === 'idle'}}">每组 {{circuitHold}} 秒{{circuitRest > 0 ? ' · 休息 ' + circuitRest + ' 秒' : ''}}</block>
<block wx:else>已撑 {{workTotal}} 秒 · 目标 {{circuitTotalWork}} 秒</block>
</text>
</view>
<!-- 呼吸引导环 + 进度条 -->
<view class="ring-wrapper">
<view class="breathe-ring breathe-ring-1 {{status === 'running' ? 'fast' : ''}}"
wx:if="{{status !== 'completed'}}"></view>
<view class="breathe-ring breathe-ring-2 {{status === 'running' ? 'fast' : ''}}"
wx:if="{{status !== 'completed'}}"></view>
<view class="breathe-ring breathe-ring-3 {{status === 'running' ? 'fast' : ''}}"
wx:if="{{status !== 'completed'}}"></view>
<progress-ring
wx:if="{{status !== 'completed'}}"
duration="{{duration}}"
remaining="{{isCompleted ? 0 : remaining}}"
elapsed="{{elapsed}}"
size="{{360}}"
ringWidth="{{22}}"
status="{{status}}"
primaryColor="{{theme.primary}}"
primaryLightColor="{{theme.primaryLight}}"
trackColor="{{isCircuitMode ? '#EEEEEE' : (elapsed >= duration ? '#00B578' : '#EEEEEE')}}"
ticks="{{ringTicks}}"
subText="{{ringSubText}}"
></progress-ring>
<!-- 完成庆祝粒子 -->
<view class="celebrate-burst" wx:if="{{isCompleted}}">
<image class="spark s1" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="spark s2" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="spark s3" src="{{icons.crownFill}}" mode="aspectFit"></image>
<image class="spark s4" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="spark s5" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="spark s6" src="{{icons.likeFill}}" mode="aspectFit"></image>
</view>
</view>
<!-- 状态文案(环下方) + 目标信息卡 + 运行提示 -->
<view class="timer-status status--{{status}}" wx:if="{{status !== 'completed'}}">{{statusText}}</view>
<view class="goal-card" wx:if="{{status !== 'completed'}}">
<image class="goal-icon" src="{{icons.targetFill}}" mode="aspectFit"></image>
<text class="goal-text">{{goalLine}}</text>
<text class="goal-arrow"></text>
</view>
<view class="timer-tip timer-tip--{{tipType}}" wx:if="{{runningTip}}">
<image class="timer-tip-icon" src="{{tipIcon}}" mode="aspectFit"></image>
<text class="timer-tip-text">{{runningTip}}</text>
</view>
<!-- 控制按钮 -->
<view class="controls">
<ui-btn
wx:if="{{!isRunning && !isPaused}}"
variant="primary"
size="xl"
block
text="开始"
icon-src="{{icons.playWhite}}"
bindtap="onStart"
></ui-btn>
<view class="ctrl-row" wx:if="{{isRunning || isPaused}}">
<ui-btn
wx:if="{{!isPaused}}"
variant="ghost"
size="xl"
text="暂停"
icon-src="{{icons.notificationForbidFill}}"
custom-style="margin-right: 28rpx;"
bindtap="onPause"
></ui-btn>
<ui-btn
wx:if="{{isPaused}}"
variant="primary"
size="xl"
text="继续"
icon-src="{{icons.playWhite}}"
custom-style="margin-right: 28rpx;"
bindtap="onStart"
></ui-btn>
<ui-btn
variant="outline"
size="xl"
text="结束"
icon-src="{{icons.stop}}"
bindtap="onStop"
></ui-btn>
</view>
</view>
<!-- 自定义"结束训练"确认弹窗 — 替代 wx.showModal,避免系统原生 modal
在 WebView stacking context 下 hit-test 不稳的问题 -->
<view class="stop-confirm-mask" wx:if="{{showStopConfirm}}" catchtouchmove="onNoop">
<view class="stop-confirm-card" catchtap="onNoop">
<text class="stop-confirm-title">结束训练</text>
<text class="stop-confirm-content">确定要结束本次训练吗?</text>
<view class="stop-confirm-actions">
<view class="stop-confirm-btn" bindtap="onStopCancel">取消</view>
<view class="stop-confirm-btn stop-confirm-btn--primary" bindtap="onStopConfirm">确定</view>
</view>
</view>
</view>
<!-- 完成训练全屏弹窗 -->
<wxs module="fmt">
// 秒数 -> 分秒展示: 45->"45"/"秒", 83->"1分23"/"秒", 120->"2"/"分钟"
// 与 utils/util.js 的 formatDuration 口径一致(整分钟走分钟单位)
module.exports = {
numPart: function (s) {
s = Math.floor(s || 0)
if (s < 60) return '' + s
var m = Math.floor(s / 60)
var sec = s % 60
return sec === 0 ? '' + m : m + '分' + sec
},
unitPart: function (s) {
s = Math.floor(s || 0)
if (s < 60) return '秒'
return s % 60 === 0 ? '分钟' : '秒'
}
}
</wxs>
<view class="completion-mask" wx:if="{{showCompletion}}" catchtouchmove="onNoop">
<view class="completion-card">
<view class="completion-emoji-wrap">
<image class="completion-icon" src="{{completionLevel === 'first' ? icons.partyWhite : completionLevel === 'record' ? icons.trophyWhite : completionLevel === 'milestone' ? icons.hotWhite : icons.sparkleWhite}}" mode="aspectFit"></image>
</view>
<text class="completion-title">{{completionMessage}}</text>
<view class="completion-number-wrap">
<text class="completion-number {{completionElapsed >= 60 ? 'completion-number--long' : ''}}">{{fmt.numPart(completionElapsed)}}</text>
<text class="completion-unit">{{fmt.unitPart(completionElapsed)}}</text>
</view>
<view class="completion-overtime" wx:if="{{completionOvertime > 0}}">
<text class="overtime-plus">+{{completionOvertime}}</text>
<text class="overtime-label">秒超长发挥</text>
</view>
<view class="completion-streak" wx:if="{{completionStreak > 0}}">
<image class="streak-flame" src="{{icons.hotFill}}" mode="aspectFit"></image>
<text class="streak-text">连续打卡 {{completionStreak}} 天</text>
</view>
<!-- 科学提示:价值行 + 弱化风险行(config.scienceTips 按时长选段) -->
<view class="completion-tip" wx:if="{{completionTipValue}}">
<text class="completion-tip-value">{{completionTipValue}}</text>
<view class="completion-tip-risk" wx:if="{{completionTipRisk}}">
<text class="completion-tip-risk-tag">小贴士</text>
<text class="completion-tip-risk-text">{{completionTipRisk}}</text>
</view>
</view>
<view class="completion-actions">
<view class="completion-btn completion-btn--primary" bindtap="onTrainAgain">
<text>再来一次</text>
</view>
<view class="completion-btn" bindtap="onViewRecords">
<text>查看记录</text>
</view>
<!-- openType="share" 会触发 Page.onShareAppMessage(已实现),
没有这个属性 + onShareAppMessage 时分享按钮就是灰色的。
ui-btn 透传 openType 到内部原生 button。 -->
<ui-btn
variant="ghost"
size="md"
customStyle="flex:1; height:88rpx;"
text="分享"
openType="share"
></ui-btn>
</view>
<view class="completion-btn completion-btn--text" bindtap="onCloseCompletion">
<text>回到首页</text>
</view>
</view>
</view>
</view>