需求 2.5 / 2.6 / 2.7 / 2.8 / 5.1 / 5.2 / 5.3 设置页 - 个人资料:身高/体重/出生日期/性别,另显示算出的 BMI 与年龄 - 单位:公制 / 英制 - 同步:自动同步开关、频率、历史范围 - 改动即存,不放「保存」按钮——每项都是单值且效果直观, 而能保持脏状态的表单就是会悄悄丢编辑的表单 - 开关用原生 checkbox 只换绘制,键盘与读屏行为保持不变 /rating-basis/ 评分依据 - 11 项参考区间逐条列出边界与出处,包括「一般性参考,无权威标准」这种 诚实的答案。判断从哪来必须能查到。 同步页 - 首要按钮「同步最新数据」,等几秒直接返回结果 - 次要按钮「同步历史」,按设置里的历史范围后台跑 - 状态压缩成三格:上次同步 / 自动同步 / 已同步天数 - 删掉原来四段说明文字,它们把两个动作埋在了下面 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { apiClient, errorMessage, RatingBasis } from '../services/api';
|
||
import Screen from '../components/Screen';
|
||
import Skeleton from '../components/Skeleton';
|
||
import './BodyAge.css';
|
||
|
||
/**
|
||
* Where every rating in the app comes from.
|
||
*
|
||
* A band that paints a number 偏低 is making a claim about the user's health.
|
||
* This screen is the receipt for that claim — one row per metric, with the
|
||
* source named, including the ones whose honest source is "general guidance,
|
||
* no authoritative standard".
|
||
*/
|
||
function RatingBasisPage() {
|
||
const [basis, setBasis] = useState<RatingBasis | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState('');
|
||
|
||
useEffect(() => {
|
||
apiClient
|
||
.getRatingBasis()
|
||
.then(setBasis)
|
||
.catch((err) => setError(errorMessage(err, '加载失败')))
|
||
.finally(() => setLoading(false));
|
||
}, []);
|
||
|
||
if (loading) return <Screen title="评分依据" backLink><Skeleton count={5} /></Screen>;
|
||
if (error || !basis) {
|
||
return (
|
||
<Screen title="评分依据" backLink>
|
||
<div className="screen-error">{error || '加载失败'}</div>
|
||
</Screen>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Screen title="评分依据" backLink>
|
||
<p className="ba-summary">{basis.note}</p>
|
||
|
||
<section className="sec">
|
||
<h3 className="sec-title">各指标参考区间</h3>
|
||
<div className="ba-basis">
|
||
{basis.bands.map((b) => (
|
||
<div className="ba-basis-item" key={b.metric}>
|
||
<div className="ba-basis-name">{b.metric}</div>
|
||
<div className="ba-basis-detail rb-bands">{b.bands}</div>
|
||
<div className="ba-basis-source">来源:{b.source}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="sec">
|
||
<h3 className="sec-title">{basis.fitnessAge.title}</h3>
|
||
<p className="ba-summary">{basis.fitnessAge.summary}</p>
|
||
<div className="ba-basis">
|
||
{basis.fitnessAge.steps.map((s) => (
|
||
<div className="ba-basis-item" key={s.name}>
|
||
<div className="ba-basis-name">{s.name}</div>
|
||
<div className="ba-basis-detail">{s.detail}</div>
|
||
<div className="ba-basis-source">来源:{s.source}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<p className="screen-disclaimer">{basis.fitnessAge.caveat}</p>
|
||
</Screen>
|
||
);
|
||
}
|
||
|
||
export default RatingBasisPage;
|