feat(records): 训练趋势图表 + 列表时长改 MM:SS

- 新增 trend-chart 柱状图组件(CSS,近7天/近6月切换)
- records 页加训练趋势卡片(统计卡片后)
- 训练记录列表时长从秒数改为 MM:SS 显示

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-28 11:19:07 +08:00
parent faa49cff1c
commit 75bad477b1
8 changed files with 233 additions and 3 deletions
+60 -2
View File
@@ -19,6 +19,8 @@ Page({
monthRecords: [],
historyList: [],
listMode: 'month',
trendMode: 'week',
trendData: [],
showDayDetail: false,
dayDetail: {},
icons: iconsMod.build()
@@ -52,7 +54,7 @@ Page({
storage.validateStreak()
const stats = storage.getTotalStats()
const monthKey = `${this.data.currentYear}-${String(this.data.currentMonth).padStart(2, '0')}`
const records = storage.getRecordsByMonth(monthKey)
const records = (storage.getRecordsByMonth(monthKey) || []).map(r => ({ ...r, durationText: util.formatTime(r.duration) }))
// Each month is already sorted newest-first in storage. To get top 30 most recent,
// walk months in reverse chrono order and concat — O(months*30) instead of O(n log n).
@@ -61,7 +63,7 @@ Page({
const historyList = []
for (const mk of monthKeys) {
for (const r of byMonth[mk]) {
historyList.push(r)
historyList.push({ ...r, durationText: util.formatTime(r.duration) })
if (historyList.length >= 30) break
}
if (historyList.length >= 30) break
@@ -80,6 +82,7 @@ Page({
historyList,
listMode: this.data.listMode || 'month',
displayList: (this.data.listMode || 'month') === 'month' ? records : historyList,
trendData: this._computeTrend(this.data.trendMode || 'week'),
loading: false
})
},
@@ -115,6 +118,61 @@ Page({
this.refresh()
},
onToggleTrendMode(e) {
const mode = e.currentTarget.dataset.mode
if (!mode || mode === this.data.trendMode) return
this.setData({ trendMode: mode, trendData: this._computeTrend(mode) })
},
/**
* 训练趋势数据:
* week - 近 7 天每天的训练时长(秒)
* month - 近 6 个月每月的训练时长(秒)
* 返回 [{ label, value, valueText, highlight }] 供 trend-chart 渲染。
*/
_computeTrend(mode) {
const allRecords = Object.values(storage.getRecords()).flat()
const today = new Date()
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
const pad = (n) => String(n).padStart(2, '0')
if (mode === 'month') {
const data = []
for (let i = 5; i >= 0; i--) {
const d = new Date(today.getFullYear(), today.getMonth() - i, 1)
const monthKey = `${d.getFullYear()}-${pad(d.getMonth() + 1)}`
const monthRecords = storage.getRecordsByMonth(monthKey) || []
const total = monthRecords.reduce((s, r) => s + (Number(r.duration) || 0), 0)
data.push({
label: `${d.getMonth() + 1}`,
value: total,
valueText: total > 0 ? util.formatDuration(total) : '',
highlight: i === 0
})
}
return data
}
// default: week (近 7 天)
const data = []
for (let i = 6; i >= 0; i--) {
const d = new Date(today)
d.setDate(d.getDate() - i)
const dateStr = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
const total = allRecords
.filter((r) => util.dateOnly(r.date) === dateStr)
.reduce((s, r) => s + (Number(r.duration) || 0), 0)
const isToday = i === 0
data.push({
label: isToday ? '今天' : weekdays[d.getDay()],
value: total,
valueText: total > 0 ? util.formatDuration(total) : '',
highlight: isToday
})
}
return data
},
onDayTap(e) {
const { year, month, day } = e.detail
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
+1
View File
@@ -1,6 +1,7 @@
{
"usingComponents": {
"calendar-heatmap": "/components/calendar-heatmap/calendar-heatmap",
"trend-chart": "/components/trend-chart/trend-chart",
"ui-card": "/components/ui-card/ui-card"
},
"navigationBarTitleText": "训练记录"
+18 -1
View File
@@ -44,6 +44,23 @@
</view>
</ui-card>
<!-- 训练趋势 -->
<ui-card variant="in" hidden="{{totalSessions === 0}}">
<view class="trend-section">
<view class="trend-header">
<view class="trend-title-wrap">
<image class="history-title-icon" src="{{icons.targetFill}}" mode="aspectFit"></image>
<text class="section-title">训练趋势</text>
</view>
<view class="rec-seg">
<view class="rec-seg-item {{trendMode === 'week' ? 'active' : ''}}" data-mode="week" bindtap="onToggleTrendMode">近7天</view>
<view class="rec-seg-item {{trendMode === 'month' ? 'active' : ''}}" data-mode="month" bindtap="onToggleTrendMode">近6月</view>
</view>
</view>
<trend-chart chart-data="{{trendData}}"></trend-chart>
</view>
</ui-card>
<!-- 日历热力图 -->
<ui-card variant="in" hidden="{{totalSessions === 0}}">
<view class="calendar-section">
@@ -104,7 +121,7 @@
<text class="history-plan">{{item.day === 0 ? '自由训练' : '第 ' + item.day + ' 天'}}</text>
</view>
<view class="history-right">
<text class="history-duration">{{item.duration}}s</text>
<text class="history-duration">{{item.durationText}}</text>
<view class="history-delete-btn" catch:tap="onDeleteRecord" data-id="{{item.id}}">
<image class="history-delete-icon" src="{{icons.deleteActive}}" mode="aspectFit"></image>
<text class="history-delete-text">删除</text>
+22
View File
@@ -79,6 +79,28 @@
color: var(--primary);
}
/* ---- trend ---- */
.trend-section {
padding: 0;
}
.trend-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.trend-title-wrap {
display: flex;
align-items: center;
gap: 8rpx;
}
.trend-header .section-title {
margin-bottom: 0;
}
/* ---- history ---- */
.history-section {
padding: 0;