eab91174db
Co-Authored-By: Claude <noreply@anthropic.com>
243 lines
8.3 KiB
TypeScript
243 lines
8.3 KiB
TypeScript
import { useState } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import {
|
||
Apple,
|
||
ArrowRight,
|
||
BarChart3,
|
||
CalendarCheck,
|
||
ClipboardCheck,
|
||
Droplet,
|
||
FileText,
|
||
HeartHandshake,
|
||
Leaf,
|
||
LineChart,
|
||
PencilLine,
|
||
Sparkles,
|
||
Sprout,
|
||
Heart,
|
||
Smile,
|
||
CheckCircle2,
|
||
type LucideIcon,
|
||
} from 'lucide-react';
|
||
import { api } from '../api/client';
|
||
import { useToast } from '../components/Toast';
|
||
import { usePatient } from '../lib/usePatient';
|
||
import { BuildArchive } from '../components/BuildArchive';
|
||
import { greeting, riskBadgeClass, riskLabel } from '../lib/format';
|
||
import './HomePage.css';
|
||
|
||
type Tone = 'peach' | 'rose' | 'mint' | 'lilac';
|
||
|
||
interface Intent {
|
||
label: string;
|
||
q?: string;
|
||
to?: string;
|
||
icon: LucideIcon;
|
||
tone: Tone;
|
||
}
|
||
|
||
const INTENTS: Intent[] = [
|
||
{ label: '我能吃这个吗?', q: '孕期可以吃西瓜吗', icon: Apple, tone: 'peach' },
|
||
{ label: '记录今天的血糖', to: '/data', icon: Droplet, tone: 'rose' },
|
||
{ label: '今天该注意什么?', q: '孕期日常需要注意什么', icon: Leaf, tone: 'mint' },
|
||
{ label: '孕期不适怎么办', q: '孕期恶心想吐怎么办', icon: HeartHandshake, tone: 'lilac' },
|
||
];
|
||
|
||
const QUICK: { label: string; to: string; icon: LucideIcon; tone: Tone }[] = [
|
||
{ label: '今日打卡', to: '/data', icon: ClipboardCheck, tone: 'peach' },
|
||
{ label: '血糖趋势', to: '/data', icon: LineChart, tone: 'rose' },
|
||
{ label: '产检提醒', to: '/tasks', icon: CalendarCheck, tone: 'mint' },
|
||
];
|
||
|
||
export function HomePage(): JSX.Element {
|
||
const navigate = useNavigate();
|
||
const { patient, loading, reload } = usePatient();
|
||
const { show } = useToast();
|
||
|
||
const [emotionScore, setEmotionScore] = useState<number | null>(null);
|
||
const [emotionNote, setEmotionNote] = useState('');
|
||
const [hasSubmittedEmotion, setHasSubmittedEmotion] = useState(false);
|
||
const [submittingEmotion, setSubmittingEmotion] = useState(false);
|
||
|
||
async function submitEmotion(): Promise<void> {
|
||
if (!emotionScore) {
|
||
show('请选择一个情绪分值哦~');
|
||
return;
|
||
}
|
||
setSubmittingEmotion(true);
|
||
try {
|
||
await api.createEmotion({ score: emotionScore, note: emotionNote || '今日情绪状况良好' });
|
||
show('打卡成功!AI 已过滤异常信号并同步至管理师,祝您好心情🌸');
|
||
setHasSubmittedEmotion(true);
|
||
setEmotionNote('');
|
||
} catch (err) {
|
||
show('心情记录失败,请稍后重试');
|
||
} finally {
|
||
setSubmittingEmotion(false);
|
||
}
|
||
}
|
||
|
||
function goChat(q?: string): void {
|
||
navigate('/chat', q ? { state: { initialQuestion: q } } : undefined);
|
||
}
|
||
|
||
if (!patient && !loading) {
|
||
return (
|
||
<div className="page">
|
||
<h1 className="section-title" style={{ marginTop: 0 }}>
|
||
你好,欢迎~
|
||
</h1>
|
||
<BuildArchive onDone={reload} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="page home">
|
||
<header className="home__greet">
|
||
<p className="home__hello">
|
||
{greeting()},{patient?.name ?? ''}
|
||
</p>
|
||
{patient && (
|
||
<p className="home__week">
|
||
孕 {patient.gestationalWeeks} 周
|
||
{patient.gestationalDays ? ` ${patient.gestationalDays} 天` : ''}
|
||
<span className={`badge ${riskBadgeClass(patient.initialRiskLevel)}`} style={{ marginLeft: 8 }}>
|
||
{riskLabel(patient.initialRiskLevel)}
|
||
</span>
|
||
</p>
|
||
)}
|
||
</header>
|
||
|
||
{/* 渐变 Hero 聊天主入口(AI-first) */}
|
||
<button className="home__hero" onClick={() => goChat()} type="button">
|
||
<div className="home__hero-glow" aria-hidden />
|
||
<div className="home__hero-body">
|
||
<span className="home__hero-eyebrow">
|
||
<Sparkles size={14} strokeWidth={2} /> 孕期 AI 助手
|
||
</span>
|
||
<span className="home__hero-title">和孕期助手聊聊</span>
|
||
<span className="home__hero-sub">饮食、监测、不适,随时问我~</span>
|
||
</div>
|
||
<span className="home__hero-cta" aria-hidden>
|
||
<ArrowRight size={22} strokeWidth={2.2} />
|
||
</span>
|
||
</button>
|
||
|
||
{/* 暖色暖系低焦虑今日心情自评打卡(T-D.9) */}
|
||
<div className="home__emotion-card">
|
||
<h3 className="emotion-card__title">
|
||
<Heart size={16} fill="var(--color-destructive)" stroke="none" />
|
||
记录今日心情,倾听身体信号
|
||
</h3>
|
||
{!hasSubmittedEmotion ? (
|
||
<div className="emotion-card__box">
|
||
<p className="emotion-card__desc">你今天的心情几分?(1-10分,分低代表压抑或焦虑)</p>
|
||
<div className="emotion-card__selectors">
|
||
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((score) => (
|
||
<button
|
||
key={score}
|
||
type="button"
|
||
className={`emotion-score-btn ${emotionScore === score ? 'active' : ''}`}
|
||
onClick={() => setEmotionScore(score)}
|
||
>
|
||
{score === 10 ? <Smile size={14} /> : score}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<textarea
|
||
className="emotion-card__textarea"
|
||
placeholder="今天有什么烦心事,或者身体上有什么不舒服吗?我们会实时保护您的私密信息,若有剧烈焦虑,个案管理师将主动为您致电疏导..."
|
||
value={emotionNote}
|
||
onChange={(e) => setEmotionNote(e.target.value)}
|
||
rows={2}
|
||
/>
|
||
|
||
<button
|
||
type="button"
|
||
className="emotion-card__submit-btn"
|
||
onClick={submitEmotion}
|
||
disabled={submittingEmotion}
|
||
>
|
||
{submittingEmotion ? '心情记录中…' : '提交今日自评'}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="emotion-card__result animate-fade-in">
|
||
<CheckCircle2 size={18} style={{ color: 'var(--color-calm)' }} />
|
||
<span>今日心情已打卡:<strong>{emotionScore}分</strong> · 感谢您的分享,AI 助手已自动保护并为您分析身心信号,祝您孕期好心情🌸</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 今日健康 */}
|
||
<h2 className="section-title">今日健康</h2>
|
||
<div className="home__quick">
|
||
{QUICK.map((q) => (
|
||
<button
|
||
key={q.label}
|
||
className="home__quick-card"
|
||
data-tone={q.tone}
|
||
onClick={() => navigate(q.to)}
|
||
type="button"
|
||
>
|
||
<span className="home__quick-chip">
|
||
<q.icon size={22} strokeWidth={1.9} />
|
||
</span>
|
||
{q.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* 意图卡片 */}
|
||
<h2 className="section-title">你可能想问 / 想做</h2>
|
||
<div className="home__intents">
|
||
{INTENTS.map((it) => (
|
||
<button
|
||
key={it.label}
|
||
className="home__intent"
|
||
data-tone={it.tone}
|
||
onClick={() => (it.to ? navigate(it.to) : goChat(it.q))}
|
||
type="button"
|
||
>
|
||
<span className="home__intent-chip">
|
||
<it.icon size={20} strokeWidth={1.9} />
|
||
</span>
|
||
<span className="home__intent-label">{it.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* 更多功能 */}
|
||
<h2 className="section-title">更多功能</h2>
|
||
<div className="home__more">
|
||
<button onClick={() => navigate('/data')} type="button">
|
||
<span className="home__more-chip">
|
||
<PencilLine size={22} strokeWidth={1.8} />
|
||
</span>
|
||
手动录入
|
||
</button>
|
||
<button onClick={() => navigate('/me')} type="button">
|
||
<span className="home__more-chip">
|
||
<FileText size={22} strokeWidth={1.8} />
|
||
</span>
|
||
健康档案
|
||
</button>
|
||
<button onClick={() => navigate('/data')} type="button">
|
||
<span className="home__more-chip">
|
||
<BarChart3 size={22} strokeWidth={1.8} />
|
||
</span>
|
||
我的报告
|
||
</button>
|
||
<button onClick={() => goChat('孕期可以做哪些调养')} type="button">
|
||
<span className="home__more-chip">
|
||
<Sprout size={22} strokeWidth={1.8} />
|
||
</span>
|
||
调养建议
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|