backup: snapshot before optimization
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
var storage = require('../../utils/storage')
|
||||
var planMod = require('../../utils/plan')
|
||||
var util = require('../../utils/util')
|
||||
var themeMod = require('../../utils/theme')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
theme: { primary: '#FF6B35', primaryLight: '#FF8C5A', primaryBg: '#FFF3ED', primaryRgb: '255,107,53' },
|
||||
themeStyle: '',
|
||||
todayTarget: 0,
|
||||
todayDone: false,
|
||||
todayDuration: 0,
|
||||
todayTargetText: '',
|
||||
streakCount: 0,
|
||||
planName: '',
|
||||
planDay: 1,
|
||||
planTotal: 7,
|
||||
planProgress: 0,
|
||||
showPicker: false,
|
||||
presets: [30, 60, 90, 120, 180, 300],
|
||||
customDuration: 60,
|
||||
customInput: ''
|
||||
},
|
||||
|
||||
onLoad: function () {
|
||||
this.refresh()
|
||||
},
|
||||
|
||||
onShow: function () {
|
||||
themeMod.applyThemeToPage(this)
|
||||
try {
|
||||
var tb = this.getTabBar()
|
||||
if (tb) tb.setData({ selected: 0 })
|
||||
} catch (e) {}
|
||||
this.refresh()
|
||||
},
|
||||
|
||||
onPullDownRefresh: function () {
|
||||
this.refresh()
|
||||
wx.stopPullDownRefresh()
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
var settings = storage.getSettings()
|
||||
var streak = storage.getStreak()
|
||||
var todayRecord = storage.getTodayRecord()
|
||||
|
||||
var records = []
|
||||
var allRecords = storage.getRecords()
|
||||
Object.keys(allRecords).forEach(function (key) {
|
||||
records = records.concat(allRecords[key])
|
||||
})
|
||||
|
||||
var plan = planMod.getPlan(settings.planId)
|
||||
var planDay = planMod.getPlanDay(settings.planId, records)
|
||||
var target = planMod.getTodayTarget(settings.planId, Math.min(planDay, plan.totalDays))
|
||||
|
||||
this.setData({
|
||||
todayTarget: target,
|
||||
todayDone: todayRecord && todayRecord.duration >= target,
|
||||
todayDuration: todayRecord ? todayRecord.duration : 0,
|
||||
todayTargetText: util.formatTime(target),
|
||||
streakCount: streak.count,
|
||||
planName: plan.name,
|
||||
planDay: Math.min(planDay, plan.totalDays),
|
||||
planTotal: plan.totalDays,
|
||||
planProgress: Math.round((Math.min(planDay, plan.totalDays) / plan.totalDays) * 100)
|
||||
})
|
||||
},
|
||||
|
||||
onStartTrain: function () {
|
||||
wx.navigateTo({ url: '/pages/timer/timer' })
|
||||
},
|
||||
|
||||
onFreeTrain: function () {
|
||||
this.setData({ showPicker: true, customInput: '', customDuration: 60 })
|
||||
},
|
||||
|
||||
onClosePicker: function () {
|
||||
this.setData({ showPicker: false })
|
||||
},
|
||||
|
||||
onSelectPreset: function (e) {
|
||||
var val = e.currentTarget.dataset.value
|
||||
this.setData({ customDuration: val, customInput: '' })
|
||||
},
|
||||
|
||||
onCustomInput: function (e) {
|
||||
var val = parseInt(e.detail.value) || 0
|
||||
this.setData({ customInput: e.detail.value, customDuration: val })
|
||||
},
|
||||
|
||||
onConfirmFree: function () {
|
||||
var dur = this.data.customDuration
|
||||
if (!dur || dur <= 0) {
|
||||
wx.showToast({ title: '请输入有效时长', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ showPicker: false })
|
||||
wx.navigateTo({ url: '/pages/timer/timer?free=' + dur })
|
||||
},
|
||||
|
||||
noop: function () {}
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {},
|
||||
"navigationBarTitleText": "平板支撑训练"
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<view class="container" style="{{themeStyle}}">
|
||||
|
||||
<!-- 顶部问候 -->
|
||||
<view class="greeting-row">
|
||||
<view class="greeting-text">
|
||||
<text class="greeting-emoji">{{todayDone ? '💪' : '👋'}}</text>
|
||||
<text class="greeting-title">{{todayDone ? '太棒了!' : '准备好了吗?'}}</text>
|
||||
</view>
|
||||
<text class="greeting-sub">{{todayDone ? '继续保持这个势头' : '今天的平板支撑等着你'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 连续打卡卡片 -->
|
||||
<view class="header-card card">
|
||||
<view class="streak-section">
|
||||
<text class="streak-icon">🔥</text>
|
||||
<text class="streak-num">{{streakCount}}</text>
|
||||
<text class="streak-label">连续打卡</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="plan-info">
|
||||
<view class="plan-badge">
|
||||
<text class="plan-badge-icon">📋</text>
|
||||
<text class="plan-badge-text">{{planName}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-wrap">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{planProgress}}%;">
|
||||
<view class="progress-glow"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="progress-label">第 {{planDay}} / {{planTotal}} 天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日目标卡片 -->
|
||||
<view class="target-section card">
|
||||
<view class="target-header">
|
||||
<text class="target-icon">🎯</text>
|
||||
<text class="section-title">今日目标</text>
|
||||
</view>
|
||||
<view class="target-display">
|
||||
<view class="target-ring">
|
||||
<text class="target-time">{{todayTargetText}}</text>
|
||||
</view>
|
||||
<text class="target-unit">平板支撑</text>
|
||||
</view>
|
||||
<view class="today-status">
|
||||
<block wx:if="{{todayDone}}">
|
||||
<view class="done-badge">
|
||||
<text class="done-emoji">✨</text>
|
||||
<text class="done-text">今日已完成 {{todayDuration}} 秒</text>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<text class="pending-text">⏳ 还未开始训练</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<view class="action-buttons">
|
||||
<button class="start-btn btn-primary" bindtap="onStartTrain">
|
||||
<text class="btn-icon">{{todayDone ? '🔄' : '▶'}}</text>
|
||||
<text>{{todayDone ? '再次训练' : '开始训练'}}</text>
|
||||
</button>
|
||||
|
||||
<button class="free-btn" bindtap="onFreeTrain">
|
||||
<text class="free-icon">⏱</text>
|
||||
<text>自由训练 · 自定义时长</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 小提示 -->
|
||||
<view class="tip-card">
|
||||
<text class="tip-icon">💡</text>
|
||||
<text class="tip-text">保持身体成一条直线,核心收紧,均匀呼吸</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间选择弹窗 -->
|
||||
<view class="modal-overlay" wx:if="{{showPicker}}" bindtap="onClosePicker">
|
||||
<view class="picker-modal" catchtap="noop">
|
||||
<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}}"
|
||||
bindinput="onCustomInput"
|
||||
/>
|
||||
<text class="custom-label">秒</text>
|
||||
</view>
|
||||
<button class="picker-confirm btn-primary" bindtap="onConfirmFree">
|
||||
开始自由训练
|
||||
</button>
|
||||
<text class="picker-cancel" bindtap="onClosePicker">取消</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,414 @@
|
||||
/* ---- greeting ---- */
|
||||
.greeting-row {
|
||||
padding: 16rpx 8rpx 24rpx;
|
||||
animation: cardIn 0.4s ease both;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.greeting-emoji {
|
||||
font-size: 40rpx;
|
||||
margin-right: 12rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.greeting-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.greeting-sub {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-left: 56rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---- header card ---- */
|
||||
.header-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 36rpx 32rpx;
|
||||
}
|
||||
|
||||
.streak-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.streak-icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 4rpx;
|
||||
line-height: 1;
|
||||
animation: float 2.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.streak-num {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.streak-label {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 2rpx;
|
||||
height: 80rpx;
|
||||
background: var(--border);
|
||||
margin: 0 28rpx;
|
||||
}
|
||||
|
||||
.plan-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plan-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.plan-badge-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 8rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.plan-badge-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.progress-bar-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background: var(--border);
|
||||
border-radius: 4rpx;
|
||||
overflow: visible;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary), var(--primary-light));
|
||||
border-radius: 4rpx;
|
||||
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-glow {
|
||||
position: absolute;
|
||||
right: -4rpx;
|
||||
top: -4rpx;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background: var(--primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10rpx var(--primary);
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---- target card ---- */
|
||||
.target-section {
|
||||
text-align: center;
|
||||
padding: 36rpx 32rpx;
|
||||
}
|
||||
|
||||
.target-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.target-header .section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.target-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 8rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.target-display {
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.target-ring {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.08), rgba(var(--primary-rgb), 0.02));
|
||||
border: 4rpx solid rgba(var(--primary-rgb), 0.15);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.target-time {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.target-unit {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.today-status {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.done-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: rgba(76, 175, 80, 0.08);
|
||||
padding: 10rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.done-emoji {
|
||||
font-size: 28rpx;
|
||||
margin-right: 6rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.done-text {
|
||||
font-size: 26rpx;
|
||||
color: var(--success);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pending-text {
|
||||
font-size: 26rpx;
|
||||
color: #CCC;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---- action buttons ---- */
|
||||
.action-buttons {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.free-btn {
|
||||
width: 100%;
|
||||
margin-top: 20rpx;
|
||||
background: transparent;
|
||||
border: 2rpx solid rgba(var(--primary-rgb), 0.3);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 48rpx;
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.free-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.free-btn:active {
|
||||
background: var(--primary-bg);
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.free-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---- tip card ---- */
|
||||
.tip-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 24rpx 32rpx;
|
||||
margin-top: 32rpx;
|
||||
background: rgba(var(--primary-rgb), 0.04);
|
||||
border-radius: 20rpx;
|
||||
animation: cardIn 0.45s 0.4s ease both;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ---- picker modal ---- */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.picker-modal {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 40rpx 32rpx;
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
animation: slideUp 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); }
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.picker-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.preset-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.preset-item {
|
||||
flex: 0 0 calc(33.33% - 12rpx);
|
||||
background: #F5F5F5;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 0;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preset-item:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
.preset-item.selected {
|
||||
background: var(--primary-bg);
|
||||
border: 2rpx solid var(--primary);
|
||||
}
|
||||
|
||||
.preset-num {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.preset-item.selected .preset-num {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.preset-unit {
|
||||
font-size: 20rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.custom-input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.custom-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
width: 160rpx;
|
||||
height: 64rpx;
|
||||
background: #F5F5F5;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
|
||||
.picker-confirm {
|
||||
width: 100%;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.picker-cancel {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
padding: 12rpx 0;
|
||||
}
|
||||
Reference in New Issue
Block a user