Initial commit: HealthCarePregnant project documentation and platform

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
selfrelease
2026-06-18 09:48:05 +08:00
commit eab91174db
301 changed files with 42491 additions and 0 deletions
@@ -0,0 +1,242 @@
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>
);
}