feat(ui): 今日页日期切换 + 卡片可点 + 路由切换进度条

需求 3.7 / 4.5 / 4.6 / 4.7

今日页
- 左右箭头切换日期,中间点开日历选任意一天,范围限定在已有数据内
- 没有记录的日期直接跳过,不会停在一个空白页上
- 一次读一年:往回翻一天不该产生一次请求,卡片的迷你曲线本来也需要前后几天
- 曲线窗口跟着所看的那天结束,卡片上的走势总是通向它上面那个数字
- 卡片改为从 lib/metrics.ts 渲染,点击进入指标详情

路由切换进度条
- 切换本身是瞬时的,用户等的是新页面的第一次请求,没有指示就像点了没反应
- 跑到 90% 停住等页面就位,不谎报完成
- 有最短显示时长,快速跳转时不会闪一下

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-24 00:49:38 +08:00
parent 0c7fecc124
commit 8430ff335e
5 changed files with 308 additions and 66 deletions

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { App as F7App, View, Views, Toolbar, Link } from 'framework7-react';
import { App as F7App, View, Views, Toolbar, Link, f7ready } from 'framework7-react';
import Framework7 from 'framework7/lite-bundle';
import Framework7React from 'framework7-react';
@@ -55,8 +55,49 @@ function useTheme() {
return [theme, setTheme] as const;
}
/**
* A progress line across the top while a screen is being pushed.
*
* The transition itself is instant; what the user waits on is the new page's
* first fetch, which without this reads as a dead tap. Shown on route change
* and cleared once the incoming page has settled, with a floor on how briefly
* it can appear so a fast navigation does not produce a flash.
*/
function useNavProgress() {
const [busy, setBusy] = useState(false);
useEffect(() => {
let shownAt = 0;
let timer: number | undefined;
const show = () => {
shownAt = Date.now();
window.clearTimeout(timer);
setBusy(true);
};
const hide = () => {
const elapsed = Date.now() - shownAt;
const wait = Math.max(0, 260 - elapsed);
window.clearTimeout(timer);
timer = window.setTimeout(() => setBusy(false), wait);
};
f7ready((app) => {
app.on('routeChange', show);
app.on('pageAfterIn', hide);
app.on('pageBeforeRemove', hide);
});
return () => window.clearTimeout(timer);
}, []);
return busy;
}
function App() {
useTheme();
const navigating = useNavProgress();
return (
<F7App
@@ -68,6 +109,15 @@ function App() {
routes={routes}
touch={{ tapHold: true }}
>
<div
className={`nav-progress ${navigating ? 'on' : ''}`}
role="status"
aria-live="polite"
aria-label={navigating ? '正在打开' : ''}
>
<span className="nav-progress-bar" />
</div>
<Views tabs className="safe-areas">
<Toolbar tabbar icons bottom>
{TABS.map((tab) => (