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 样式清理)
This commit is contained in:
2026-08-11 15:38:37 +08:00
parent 6f89494c0b
commit 0464eeaba6
8 changed files with 209 additions and 82 deletions
+32 -8
View File
@@ -14,26 +14,28 @@ Component({
trackColor: { type: String, value: '#EEEEEE' },
// Total elapsed seconds (since start). Shown in the ring center once the
// countdown hits 0 (overtime mode) - replaces the old +Xs badge.
elapsed: { type: Number, value: 0 }
elapsed: { type: Number, value: 0 },
// Tick count. Caller passes the duration (one tick per second, capped at
// 60 to avoid clutter). 0 = no ticks drawn. Quarter ticks are emphasized.
ticks: { type: Number, value: 0 },
// Small secondary label under the big time (e.g. "目标 30 秒"). Empty = hidden.
subText: { type: String, value: '' }
},
data: {
displayTime: '00:00',
statusText: '准备开始'
displayTime: '00:00'
},
observers: {
'remaining, status, duration, elapsed'() {
const texts = { idle: '准备开始', running: '坚持住', paused: '已暂停', completed: '完成!' }
// 倒计时阶段显示剩余;达标后 remaining 归零,改显总训练时长(overtime 模式)
const shown = this.data.remaining > 0 ? this.data.remaining : (this.data.elapsed || 0)
this.setData({
displayTime: util.formatTime(shown),
statusText: texts[this.data.status] || ''
displayTime: util.formatTime(shown)
})
},
'remaining, duration, size, ringWidth, primaryColor, primaryLightColor, trackColor'() {
'remaining, duration, size, ringWidth, primaryColor, primaryLightColor, trackColor, ticks'() {
this._draw()
}
},
@@ -70,7 +72,7 @@ Component({
const ctx = this._ctx
if (!ctx) return
const { size, ringWidth, duration, remaining, primaryColor, primaryLightColor, trackColor } = this.data
const { size, ringWidth, duration, remaining, primaryColor, primaryLightColor, trackColor, ticks } = this.data
const dpr = this._dpr
const w = size * dpr
@@ -92,6 +94,28 @@ Component({
ctx.lineCap = 'round'
ctx.stroke()
// ---- tick marks (inside the band) ----
if (ticks > 0) {
const innerR = radius - lw / 2 - (4 * dpr) // just inside the band
const q = Math.max(1, Math.round(ticks / 4)) // quarter emphasis step
const shortLen = 6 * dpr
const longLen = 12 * dpr
for (let i = 0; i < ticks; i++) {
const ang = -Math.PI / 2 + (i / ticks) * Math.PI * 2
const isLong = (i % q === 0)
const len = isLong ? longLen : shortLen
const cos = Math.cos(ang)
const sin = Math.sin(ang)
ctx.beginPath()
ctx.moveTo(cx + innerR * cos, cy + innerR * sin)
ctx.lineTo(cx + (innerR - len) * cos, cy + (innerR - len) * sin)
ctx.lineWidth = (isLong ? 3 : 1.5) * dpr
ctx.lineCap = 'round'
ctx.strokeStyle = isLong ? 'rgba(0,0,0,0.28)' : 'rgba(0,0,0,0.12)'
ctx.stroke()
}
}
// progress arc (clockwise from 12 o'clock)
const ratio = duration > 0 ? Math.max(0, Math.min(1, remaining / duration)) : 0
if (ratio > 0.001) {
+1 -1
View File
@@ -2,6 +2,6 @@
<canvas type="2d" id="ringCanvas" class="ring-canvas" style="width: {{size}}rpx; height: {{size}}rpx;"></canvas>
<view class="ring-center">
<text class="time-text">{{displayTime}}</text>
<text class="status-text">{{statusText}}</text>
<text class="sub-text" wx:if="{{subText}}">{{subText}}</text>
</view>
</view>
+4 -4
View File
@@ -21,7 +21,7 @@
}
.time-text {
font-size: 88rpx;
font-size: 96rpx;
font-weight: 800;
color: var(--text);
font-variant-numeric: tabular-nums;
@@ -29,9 +29,9 @@
line-height: 1;
}
.status-text {
font-size: 28rpx;
.sub-text {
font-size: 26rpx;
color: var(--text-secondary);
margin-top: 10rpx;
margin-top: 14rpx;
line-height: 1;
}