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) {