fix: 实机验证发现的七个缺陷

部署与时区
- client/.env.production 写死 REACT_APP_API_URL=/api。之前没有这个文件,
  构建靠命令行临时传参,一旦忘了就把开发默认值 localhost:5000 打进包里,
  部署后整站 Network Error。
- 新增 lib/day.ts,所有日期改用本地日历日。原先用 toISOString() 取的是 UTC 日期,
  在 UTC+8 每天前 8 小时都会少查一天——当天的数据佳明已经有了,应用却够不到。

界面
- 覆盖 Framework7 9 给 .navbar .left/.right 加的 frosted pill,
  就是各页右上角和返回键旁边那个半透明椭圆。
- .metric-tab 显式 width:auto。F7 把每个 button 渲染成整宽块元素,
  运动详情的四个 Tab 因此竖着堆成四行。
- 主要收益为 UNKNOWN 时不显示该区块,那是「没有结论」的哨兵值。

正确性
- 心率区间百分比改用整次运动时长作分母。原先除以「落在区间内的总时长」,
  把低于区间 1 的时间挤掉了:44:06 的登山里区间 1 占 23:03,
  手表显示 52%,我算成了 90%。现在对上了。

性能
- 按进程缓存已认证的 Garmin 会话(15 分钟 TTL)。实测 _connect 单次 11 秒,
  而七个数据接口加起来才 4 秒——瓶颈全在每次重新认证。
  冷启 16s → 热 7s → 命中缓存 0.8s,不再撞客户端超时。
- get_activity_details 的 maxchart 由 2000 降到 500,反正写入时抽稀到 300。
- 重新绑定账号时丢弃缓存会话。

删除前端重做前遗留的 5 个无引用页面文件。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-24 01:16:44 +08:00
parent 8430ff335e
commit fa865ca8a6
22 changed files with 173 additions and 1416 deletions

View File

@@ -9,6 +9,7 @@ import Skeleton from '../components/Skeleton';
import { useCountUp } from '../lib/motion';
import { RANGES } from '../lib/ranges';
import { METRICS, metricHref } from '../lib/metrics';
import { daysAgo, iso, shiftDay, today as todayIso } from '../lib/day';
import './Today.css';
/* A year is loaded up front so stepping back a day costs no request. */
@@ -125,10 +126,6 @@ const SECTIONS: Array<{ title: string; items: string[] }> = [
{ title: '睡眠', items: ['sleepDuration', 'sleepQuality'] },
];
const iso = (d: Date) => d.toISOString().slice(0, 10);
const shift = (date: string, days: number) =>
iso(new Date(new Date(`${date}T12:00:00`).getTime() + days * 86400000));
function TodayPage() {
const [days, setDays] = useState<HealthDay[]>([]);
const [selected, setSelected] = useState<string | null>(null);
@@ -140,9 +137,9 @@ function TodayPage() {
try {
// A year in one read: browsing back a day should not cost a request,
// and the cards' sparklines need the surrounding days anyway.
const end = new Date();
const start = new Date(end.getTime() - (HISTORY_DAYS - 1) * 86400000);
setDays(await apiClient.getHealthSummary(iso(start), iso(end)));
setDays(await apiClient.getHealthSummary(
daysAgo(HISTORY_DAYS - 1), todayIso()
));
} catch (err: any) {
setError(errorMessage(err, '加载数据失败'));
} finally {
@@ -167,7 +164,7 @@ function TodayPage() {
const go = (delta: number) => {
if (!date) return;
const target = shift(date, delta);
const target = shiftDay(date, delta);
// Days with no record at all are skipped over rather than shown blank.
const nearest = delta < 0
? [...days].reverse().find((d) => d.date <= target)
@@ -196,7 +193,7 @@ function TodayPage() {
calendar.open();
};
const isToday = date === iso(new Date());
const isToday = date === todayIso();
const weekday = date
? ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][
new Date(`${date}T12:00:00`).getDay()
@@ -206,7 +203,7 @@ function TodayPage() {
const open = (id: string) => f7.views.current.router.navigate(metricHref(id));
return (
<Screen title={isToday ? '今日' : '每日数据'} subtitle={date ?? undefined}>
<Screen title={!date || isToday ? '今日' : '每日数据'} subtitle={date ?? undefined}>
{loading && (
<>
<div className="hero-skeleton" aria-hidden="true" />