3532d0111b
循环训练(circuit)
- 新增 utils/circuitTimer.js 状态机: 工作->休息->...->完成, stop() 返回有效撑总秒(不含休息)
- 首页新增「循环训练·分组练习」入口 + stepper 配置弹窗
- 配置本地持久化 circuit_config(每组时长/组数/休息/每周目标), 下次自动预填
- 记录以 planId:'circuit' + mode:'circuit' 落库, duration 语义不变 -> 排行榜/统计/每日计划进度零改动
- 记录页展示「循环 N组xMs·休Rs」
计时器顶部循环进度面板
- 组进度点阵按组数等分铺满(flex:1 1 0, 去掉 max-width 封顶), 当前组以增高而非加宽强调
- 大号「X/Y 组」+ 四态阶段徽章(撑住/休息中/准备开始/已暂停) + 累计秒数副行
- 状态灯用纯 CSS 圆点(SVG 图标颜色烤死在 data URI 里, CSS 改不动)
- 超过 15 组自动降级为线性进度条; 面板不设固定高度, 规避真机大字体档裁字
修复: 循环训练全程无语音
- 根因: circuit 的 onTick 走 _circuitCue(只有震动), 从未调用 _remind
- 接入整场进度播报, 基准为 sets x holdPerSet(而非每组), 避免 4 组播 4 次「已完成一半」
- 新增 restStart/nextSet/lastSet 三条不含数字的词条, 客户端与 tts 云函数词表同步
- 修复必然撞车的时序: 偶数组时 halfway 与休息切换同一 tick, 加 2.6s 优先窗口让位
- 防吵闸门: 每组 <10s 不播过场语音, 休息 <5s 不播; 预载按模式取词条
训练期间屏幕常亮
- wx.setKeepScreenOn: onStart 开 / onUnload 关 / onShow 在 isRunning 时重新武装
- 切后台再回来该标志会被系统清掉, 故必须重新武装, 否则后半程丢语音与震动
- 低基础库与 Android 省电模式静默降级, 不弹 toast
修复: 首页打卡卡片左侧两个竖条
- 根因: ui-card 宿主节点无 display 声明为 inline, 页面侧加的 border 被打断成两个零宽行盒碎片
- ui-card 加 :host{display:block}, margin-bottom 从内部 view 上提到宿主
- 高亮由 border 改为 box-shadow spread 描边(不占布局, 无跳动) + 补 border-radius
- 连带修复失效的交错入场: nth-child 跨组件边界恒匹配 1, 改为 index 属性驱动内联 animation-delay, 14 处调用点补序号
注意: tts 云函数需重新部署, 否则新增的 3 条词条返回 Unknown prompt key(静默降级不报错)
210 lines
7.4 KiB
Plaintext
210 lines
7.4 KiB
Plaintext
<view class="container page-enter" style="{{themeStyle}}">
|
||
|
||
<!-- 顶部问候 -->
|
||
<view class="greeting-row">
|
||
<view class="greeting-text">
|
||
<image class="greeting-icon" src="{{todayDone ? icons.successFill : icons.emojiFill}}" mode="aspectFit"></image>
|
||
<text class="greeting-title">{{todayDone ? '太棒了!' : '准备好了吗?'}}</text>
|
||
</view>
|
||
<text class="greeting-sub">{{todayDone ? '继续保持这个势头' : '今天的平板支撑等着你'}}</text>
|
||
</view>
|
||
|
||
<!-- 连续打卡卡片:火焰连胜 + 分段进度 -->
|
||
<ui-card variant="gradient in" index="0" class="{{justCompleted ? 'just-completed' : ''}}">
|
||
<view class="header-card">
|
||
<!-- 上区:连胜视觉重心 + 计划徽章 -->
|
||
<view class="streak-row">
|
||
<view class="streak-section">
|
||
<view class="streak-icon-wrap">
|
||
<view class="streak-halo"></view>
|
||
<image class="streak-icon" src="{{icons.hotWhite}}" mode="aspectFit"></image>
|
||
</view>
|
||
<view class="streak-text">
|
||
<text class="streak-num">{{streakCount}}</text>
|
||
<text class="streak-label">连续打卡</text>
|
||
</view>
|
||
</view>
|
||
<view class="plan-badge">
|
||
<image class="plan-badge-icon" src="{{icons.formWhite}}" mode="aspectFit"></image>
|
||
<text class="plan-badge-text">{{planName}}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 下区:分段格子(<=10天)或里程碑连续条(>10天) -->
|
||
<view class="segment-progress">
|
||
<view class="segment-track" wx:if="{{useSegments}}">
|
||
<view
|
||
wx:for="{{planDays}}"
|
||
wx:key="*this"
|
||
class="segment-cell {{index < planDay - 1 ? 'done' : ''}} {{index === planDay - 1 ? 'current' : ''}}"
|
||
></view>
|
||
</view>
|
||
<view class="milestone-track" wx:else>
|
||
<view class="milestone-bar">
|
||
<view class="milestone-fill" style="width: {{planProgress}}%;"></view>
|
||
<view class="milestone-node" style="left: 0%;"></view>
|
||
<view class="milestone-node" style="left: 25%;"></view>
|
||
<view class="milestone-node" style="left: 50%;"></view>
|
||
<view class="milestone-node" style="left: 75%;"></view>
|
||
<view class="milestone-node" style="left: 100%;"></view>
|
||
</view>
|
||
</view>
|
||
<text class="progress-label">第 {{planDay}} / {{planTotal}} 天</text>
|
||
</view>
|
||
</view>
|
||
</ui-card>
|
||
|
||
<!-- 今日目标卡片 -->
|
||
<ui-card variant="in" index="1">
|
||
<view class="target-section">
|
||
<view class="target-header">
|
||
<image class="section-icon" src="{{icons.targetFill}}" mode="aspectFit"></image>
|
||
<text class="section-title">今日目标</text>
|
||
</view>
|
||
<view class="target-display">
|
||
<view class="target-ring">
|
||
<text class="target-time" style="font-size: {{targetTimeFontSize}}rpx;">{{todayTargetText}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="today-status">
|
||
<block wx:if="{{todayDone}}">
|
||
<view class="done-badge">
|
||
<image class="done-icon" src="{{icons.successFill}}" mode="aspectFit"></image>
|
||
<text class="done-text">今日已完成 {{todayDuration}} 秒</text>
|
||
</view>
|
||
</block>
|
||
<block wx:else>
|
||
<image class="pending-icon" src="{{icons.time}}" mode="aspectFit"></image>
|
||
<text class="pending-text">还未开始训练</text>
|
||
</block>
|
||
</view>
|
||
</view>
|
||
</ui-card>
|
||
|
||
<!-- 操作按钮区 -->
|
||
<view class="action-buttons">
|
||
<ui-btn
|
||
variant="primary"
|
||
size="xl"
|
||
block
|
||
text="{{todayDone ? '再次训练' : '开始训练'}}"
|
||
icon-src="{{icons.playWhite}}"
|
||
bindtap="onStartTrain"
|
||
></ui-btn>
|
||
|
||
<ui-btn
|
||
variant="ghost"
|
||
size="md"
|
||
block
|
||
text="自由训练 · 自定义时长"
|
||
icon-src="{{icons.timeFill}}"
|
||
bindtap="onFreeTrain"
|
||
></ui-btn>
|
||
|
||
<ui-btn
|
||
variant="ghost"
|
||
size="md"
|
||
block
|
||
text="循环训练 · 分组练习"
|
||
icon-src="{{icons.repeatFill}}"
|
||
bindtap="onCircuitTrain"
|
||
></ui-btn>
|
||
</view>
|
||
|
||
<!-- 时间选择弹窗 -->
|
||
<ui-modal
|
||
visible="{{showPicker}}"
|
||
position="bottom"
|
||
bind:close="onClosePicker"
|
||
>
|
||
<text class="picker-title">选择训练时长</text>
|
||
<view class="preset-grid">
|
||
<view
|
||
class="preset-item {{customDuration === item ? 'selected' : ''}}"
|
||
wx:for="{{presets}}"
|
||
wx:key="*this"
|
||
data-value="{{item}}"
|
||
bindtap="onSelectPreset"
|
||
>
|
||
<text class="preset-num">{{item}}</text>
|
||
<text class="preset-unit">秒</text>
|
||
</view>
|
||
</view>
|
||
<view class="custom-input-row">
|
||
<text class="custom-label">自定义:</text>
|
||
<input
|
||
class="custom-input"
|
||
type="number"
|
||
placeholder="输入秒数"
|
||
value="{{customInput}}"
|
||
focus="{{pickerInputFocus}}"
|
||
bindinput="onCustomInput"
|
||
bindfocus="onPickerInputFocus"
|
||
/>
|
||
<text class="custom-label">秒</text>
|
||
</view>
|
||
<ui-btn
|
||
variant="primary"
|
||
size="lg"
|
||
block
|
||
text="开始自由训练"
|
||
bindtap="onConfirmFree"
|
||
></ui-btn>
|
||
<text class="picker-cancel" bindtap="onClosePicker">取消</text>
|
||
</ui-modal>
|
||
|
||
<!-- 循环训练配置弹窗:每组时长 / 组数 / 休息 / 每周次数,保存后持久化 -->
|
||
<ui-modal
|
||
visible="{{showCircuitPicker}}"
|
||
position="bottom"
|
||
bind:close="onCloseCircuitPicker"
|
||
>
|
||
<text class="picker-title">循环训练设置</text>
|
||
<text class="picker-sub">自定义每组时长、组数与组间休息</text>
|
||
|
||
<view class="circuit-row">
|
||
<text class="circuit-row-label">每组时长</text>
|
||
<view class="stepper">
|
||
<view class="stepper-btn" data-field="hold" data-delta="-5" bindtap="onCircuitStep">−</view>
|
||
<text class="stepper-val">{{cHold}}秒</text>
|
||
<view class="stepper-btn" data-field="hold" data-delta="5" bindtap="onCircuitStep">+</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="circuit-row">
|
||
<text class="circuit-row-label">组数</text>
|
||
<view class="stepper">
|
||
<view class="stepper-btn" data-field="sets" data-delta="-1" bindtap="onCircuitStep">−</view>
|
||
<text class="stepper-val">{{cSets}}组</text>
|
||
<view class="stepper-btn" data-field="sets" data-delta="1" bindtap="onCircuitStep">+</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="circuit-row">
|
||
<text class="circuit-row-label">组间休息</text>
|
||
<view class="stepper">
|
||
<view class="stepper-btn" data-field="rest" data-delta="-5" bindtap="onCircuitStep">−</view>
|
||
<text class="stepper-val">{{cRest}}秒</text>
|
||
<view class="stepper-btn" data-field="rest" data-delta="5" bindtap="onCircuitStep">+</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="circuit-row">
|
||
<text class="circuit-row-label">每周目标</text>
|
||
<view class="stepper">
|
||
<view class="stepper-btn" data-field="sessions" data-delta="-1" bindtap="onCircuitStep">−</view>
|
||
<text class="stepper-val">{{cSessions}}次/周</text>
|
||
<view class="stepper-btn" data-field="sessions" data-delta="1" bindtap="onCircuitStep">+</view>
|
||
</view>
|
||
</view>
|
||
|
||
<ui-btn
|
||
variant="primary"
|
||
size="lg"
|
||
block
|
||
text="保存并开始"
|
||
bindtap="onConfirmCircuit"
|
||
></ui-btn>
|
||
<text class="picker-cancel" bindtap="onCloseCircuitPicker">取消</text>
|
||
</ui-modal>
|
||
</view>
|