|
|
@@ -23,6 +23,7 @@ import {
|
|
|
} from "@radix-ui/react-icons";
|
|
|
import {
|
|
|
FlowStack,
|
|
|
+ KeyboardInput,
|
|
|
KeyboardTextarea,
|
|
|
MobileScroll,
|
|
|
type FlowControls,
|
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
|
|
|
type RootTab = "home" | "services" | "records" | "me";
|
|
|
type AssistantMode = "general" | "registration" | "report" | "tongue";
|
|
|
+type AssistantStage = "ready" | "routing" | "followup" | "complete";
|
|
|
|
|
|
function PageHeader({ flow, title, step }: { flow: FlowControls; title: string; step?: string }) {
|
|
|
return (
|
|
|
@@ -125,17 +127,40 @@ function HomeContent({ flow }: { flow: FlowControls }) {
|
|
|
</span>
|
|
|
</button>
|
|
|
|
|
|
- <button className="todo-strip" type="button" onClick={() => flow.push(makeAssistantScreen("report"))}>
|
|
|
- <span className="todo-label">
|
|
|
- <FileTextIcon />
|
|
|
- 今日待办
|
|
|
- </span>
|
|
|
- <span className="todo-copy">
|
|
|
- <strong>1 份新报告待解读</strong>
|
|
|
- <small>来自检验科 · 07-25 16:30</small>
|
|
|
- </span>
|
|
|
- <span className="todo-action">查看</span>
|
|
|
- </button>
|
|
|
+ <section className="patient-state-panel">
|
|
|
+ <div className="section-heading compact-heading">
|
|
|
+ <h3>患者状态与待办</h3>
|
|
|
+ <span>3 项</span>
|
|
|
+ </div>
|
|
|
+ <button className="patient-state-row appointment" type="button" onClick={() => flow.push(registrationResultScreen)}>
|
|
|
+ <span className="state-icon"><CalendarIcon /></span>
|
|
|
+ <span>
|
|
|
+ <strong>明天 09:30 神经内科预约</strong>
|
|
|
+ <small>李明 主任医师 · 请提前 15 分钟到院</small>
|
|
|
+ </span>
|
|
|
+ <ChevronRightIcon />
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ className="patient-state-row report"
|
|
|
+ type="button"
|
|
|
+ onClick={() => flow.push(makeAssistantScreen("report", "帮我解读刚出的血常规报告", "complete"))}
|
|
|
+ >
|
|
|
+ <span className="state-icon"><FileTextIcon /></span>
|
|
|
+ <span>
|
|
|
+ <strong>1 份新报告待解读</strong>
|
|
|
+ <small>来自检验科 · 07-25 16:30</small>
|
|
|
+ </span>
|
|
|
+ <em>待解读</em>
|
|
|
+ </button>
|
|
|
+ <button className="patient-state-row tongue" type="button" onClick={() => flow.push(tongueCaptureScreen)}>
|
|
|
+ <span className="state-icon"><HeartIcon /></span>
|
|
|
+ <span>
|
|
|
+ <strong>中医舌诊待完成</strong>
|
|
|
+ <small>问答已保存,继续拍摄舌象</small>
|
|
|
+ </span>
|
|
|
+ <em>继续</em>
|
|
|
+ </button>
|
|
|
+ </section>
|
|
|
|
|
|
<section className="service-section">
|
|
|
<article className="registration-hero">
|
|
|
@@ -291,6 +316,16 @@ function RecordsContent({ flow }: { flow: FlowControls }) {
|
|
|
<span><strong>神经内科预约</strong><small>明天 09:30 · 已完成</small><p>李明 主任医师</p></span>
|
|
|
<em>查看</em>
|
|
|
</button>
|
|
|
+ <button type="button" onClick={() => flow.push(registrationFailureScreen)}>
|
|
|
+ <span className="record-icon warning"><ReloadIcon /></span>
|
|
|
+ <span><strong>智能挂号任务</strong><small>今天 12:20 · 未完成</small><p>号源发生变化,挂号条件已保留</p></span>
|
|
|
+ <em>重试</em>
|
|
|
+ </button>
|
|
|
+ <button type="button" onClick={() => flow.push(reportFileFailureScreen)}>
|
|
|
+ <span className="record-icon warning"><FileTextIcon /></span>
|
|
|
+ <span><strong>报告解读任务</strong><small>今天 11:08 · 待重新上传</small><p>原图片模糊,患者信息已保留</p></span>
|
|
|
+ <em>继续</em>
|
|
|
+ </button>
|
|
|
</section>
|
|
|
</main>
|
|
|
</MobileScroll>
|
|
|
@@ -358,11 +393,22 @@ const patientScreen: FlowScreen = {
|
|
|
),
|
|
|
};
|
|
|
|
|
|
-function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: AssistantMode }) {
|
|
|
- const [text, setText] = useState("");
|
|
|
- const [stage, setStage] = useState<"ready" | "followup" | "complete">("ready");
|
|
|
+function AssistantExperience({
|
|
|
+ flow,
|
|
|
+ mode,
|
|
|
+ initialText = "",
|
|
|
+ initialStage = "ready",
|
|
|
+}: {
|
|
|
+ flow: FlowControls;
|
|
|
+ mode: AssistantMode;
|
|
|
+ initialText?: string;
|
|
|
+ initialStage?: AssistantStage;
|
|
|
+}) {
|
|
|
+ const [text, setText] = useState(initialText);
|
|
|
+ const [stage, setStage] = useState<AssistantStage>(initialStage);
|
|
|
const [doctor, setDoctor] = useState("");
|
|
|
const [editingSlot, setEditingSlot] = useState("");
|
|
|
+ const [routeIntent, setRouteIntent] = useState<Exclude<AssistantMode, "general">>("registration");
|
|
|
const [slots, setSlots] = useState({
|
|
|
department: "神经内科",
|
|
|
date: "明天 07-27",
|
|
|
@@ -393,10 +439,20 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
}[mode];
|
|
|
|
|
|
const start = () => {
|
|
|
- if (text.includes("报告")) flow.push(reportUploadScreen);
|
|
|
- else if (text.includes("舌") || text.includes("体质")) flow.push(tongueIntroScreen);
|
|
|
- else if (text.includes("胸痛") || text.includes("呼吸困难")) flow.push(emergencyScreen);
|
|
|
- else {
|
|
|
+ const submittedText = text || modeCopy.sample;
|
|
|
+ setText(submittedText);
|
|
|
+ if (submittedText.includes("胸痛") || submittedText.includes("呼吸困难")) {
|
|
|
+ flow.push(emergencyScreen);
|
|
|
+ } else if (mode === "general") {
|
|
|
+ if (submittedText.includes("报告")) setRouteIntent("report");
|
|
|
+ else if (submittedText.includes("舌") || submittedText.includes("体质")) setRouteIntent("tongue");
|
|
|
+ else setRouteIntent("registration");
|
|
|
+ setStage("routing");
|
|
|
+ } else if (mode === "report") {
|
|
|
+ setStage("complete");
|
|
|
+ } else if (mode === "tongue") {
|
|
|
+ setStage("complete");
|
|
|
+ } else {
|
|
|
setText(text || modeCopy.sample);
|
|
|
setStage("followup");
|
|
|
}
|
|
|
@@ -404,11 +460,35 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
|
|
|
const simulateVoice = () => {
|
|
|
setText(modeCopy.sample);
|
|
|
- if (mode === "report") setStage("complete");
|
|
|
+ if (mode === "general") {
|
|
|
+ setRouteIntent("registration");
|
|
|
+ setStage("routing");
|
|
|
+ } else if (mode === "report") setStage("complete");
|
|
|
else if (mode === "tongue") setStage("complete");
|
|
|
else setStage("followup");
|
|
|
};
|
|
|
|
|
|
+ const demonstrateRoute = (intent: Exclude<AssistantMode, "general"> | "emergency") => {
|
|
|
+ if (intent === "emergency") {
|
|
|
+ setText("我胸痛,还喘不过气,想挂明天心内科");
|
|
|
+ flow.push(emergencyScreen);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const routeSamples = {
|
|
|
+ registration: "我头疼,帮我挂神经内科明天早上九点的专家号",
|
|
|
+ report: "帮我解读刚出的血常规报告",
|
|
|
+ tongue: "我最近容易疲倦,想做一次中医舌诊",
|
|
|
+ };
|
|
|
+ setText(routeSamples[intent]);
|
|
|
+ setRouteIntent(intent);
|
|
|
+ setStage("routing");
|
|
|
+ };
|
|
|
+
|
|
|
+ const enterRoutedTask = () => {
|
|
|
+ const routedStage: AssistantStage = routeIntent === "registration" ? "followup" : "complete";
|
|
|
+ flow.replace(makeAssistantScreen(routeIntent, text, routedStage));
|
|
|
+ };
|
|
|
+
|
|
|
const chooseDoctor = (name: string) => {
|
|
|
setDoctor(name);
|
|
|
setStage("complete");
|
|
|
@@ -417,7 +497,7 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
const nextTask = () => {
|
|
|
if (mode === "report") flow.push(reportUploadScreen);
|
|
|
else if (mode === "tongue") flow.push(tongueIntroScreen);
|
|
|
- else flow.push(scheduleScreen);
|
|
|
+ else flow.push(registrationSearchingScreen);
|
|
|
};
|
|
|
|
|
|
const updateSlot = (key: keyof typeof slots, value: string) => {
|
|
|
@@ -476,15 +556,62 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
</label>
|
|
|
{mode === "general" ? (
|
|
|
<section>
|
|
|
- <h3 className="minor-heading">也可以直接选择</h3>
|
|
|
+ <h3 className="minor-heading">演示不同路由</h3>
|
|
|
<div className="quick-actions">
|
|
|
- <button type="button" onClick={() => flow.replace(makeAssistantScreen("registration"))}>语音挂号</button>
|
|
|
- <button type="button" onClick={() => flow.replace(makeAssistantScreen("report"))}>报告解读</button>
|
|
|
- <button type="button" onClick={() => flow.replace(makeAssistantScreen("tongue"))}>中医舌诊</button>
|
|
|
+ <button type="button" onClick={() => demonstrateRoute("registration")}>挂号诉求</button>
|
|
|
+ <button type="button" onClick={() => demonstrateRoute("report")}>报告诉求</button>
|
|
|
+ <button type="button" onClick={() => demonstrateRoute("tongue")}>舌诊诉求</button>
|
|
|
+ <button className="danger-chip" type="button" onClick={() => demonstrateRoute("emergency")}>急诊风险</button>
|
|
|
</div>
|
|
|
</section>
|
|
|
) : null}
|
|
|
</>
|
|
|
+ ) : stage === "routing" ? (
|
|
|
+ <section className="slot-dialogue route-dialogue" aria-live="polite">
|
|
|
+ <div className="dialogue-context">
|
|
|
+ <span className="dialogue-label">需求识别</span>
|
|
|
+ <article className="voice-transcript">
|
|
|
+ <span>您说</span>
|
|
|
+ <p>{text}</p>
|
|
|
+ </article>
|
|
|
+ <article className="assistant-reply">
|
|
|
+ <strong>我已经理解您的需求</strong>
|
|
|
+ <p>
|
|
|
+ {routeIntent === "registration"
|
|
|
+ ? "您希望根据头痛症状完成神经内科预约。继续前会先完成安全判断和缺失信息补充。"
|
|
|
+ : routeIntent === "report"
|
|
|
+ ? "您希望解读最新的血常规报告。进入后需要确认报告来源和归属。"
|
|
|
+ : "您希望通过体质问答和舌象分析了解近期疲倦与体质情况。"}
|
|
|
+ </p>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ <section className={`route-confirm-card ${routeIntent}`}>
|
|
|
+ <span className="route-kicker">即将进入</span>
|
|
|
+ <div className="route-title">
|
|
|
+ {routeIntent === "registration" ? <CalendarIcon /> : routeIntent === "report" ? <FileTextIcon /> : <HeartIcon />}
|
|
|
+ <div>
|
|
|
+ <strong>
|
|
|
+ {routeIntent === "registration" ? "智能分诊挂号" : routeIntent === "report" ? "报告智能解读" : "中医舌诊"}
|
|
|
+ </strong>
|
|
|
+ <small>
|
|
|
+ {routeIntent === "registration"
|
|
|
+ ? "已带入:头疼、神经内科、明天 09:00、专家号"
|
|
|
+ : routeIntent === "report"
|
|
|
+ ? "已带入:最新血常规报告"
|
|
|
+ : "已带入:容易疲倦、了解体质"}
|
|
|
+ </small>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={enterRoutedTask}>
|
|
|
+ 确认并进入服务 <ChevronRightIcon />
|
|
|
+ </button>
|
|
|
+ </section>
|
|
|
+ <section className="notice-card neutral route-note">
|
|
|
+ <InfoCircledIcon />
|
|
|
+ <div><strong>统一助手只负责路由</strong><p>确认后将创建独立业务任务,不在通用会话中继续办理。</p></div>
|
|
|
+ </section>
|
|
|
+ <button className="text-button" type="button" onClick={() => setStage("ready")}>重新描述需求</button>
|
|
|
+ </section>
|
|
|
) : (
|
|
|
<section className="slot-dialogue" aria-live="polite">
|
|
|
<div className="dialogue-context">
|
|
|
@@ -605,7 +732,7 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
<button type="button" onClick={() => chooseDoctor("不限医生")}>不限医生</button>
|
|
|
</div>
|
|
|
<label className="task-text-fallback">
|
|
|
- <input
|
|
|
+ <KeyboardInput
|
|
|
value={doctor}
|
|
|
onChange={(event) => setDoctor(event.target.value)}
|
|
|
placeholder="也可以输入医生姓名"
|
|
|
@@ -639,14 +766,25 @@ function AssistantExperience({ flow, mode }: { flow: FlowControls; mode: Assista
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-function makeAssistantScreen(mode: AssistantMode): FlowScreen {
|
|
|
+function makeAssistantScreen(
|
|
|
+ mode: AssistantMode,
|
|
|
+ initialText = "",
|
|
|
+ initialStage: AssistantStage = "ready",
|
|
|
+): FlowScreen {
|
|
|
const title = mode === "registration" ? "语音挂号" : mode === "report" ? "报告解读" : mode === "tongue" ? "中医舌诊" : "智能助手";
|
|
|
return {
|
|
|
- id: `assistant-${mode}`,
|
|
|
+ id: `assistant-${mode}-${initialStage}`,
|
|
|
title,
|
|
|
headerHeight: 52,
|
|
|
header: (flow) => <PageHeader flow={flow} title={title} />,
|
|
|
- render: (flow) => <AssistantExperience flow={flow} mode={mode} />,
|
|
|
+ render: (flow) => (
|
|
|
+ <AssistantExperience
|
|
|
+ flow={flow}
|
|
|
+ mode={mode}
|
|
|
+ initialText={initialText}
|
|
|
+ initialStage={initialStage}
|
|
|
+ />
|
|
|
+ ),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
@@ -797,6 +935,72 @@ const departmentScreen: FlowScreen = {
|
|
|
),
|
|
|
};
|
|
|
|
|
|
+const registrationSearchingScreen: FlowScreen = {
|
|
|
+ id: "registration-searching",
|
|
|
+ title: "查询号源",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="智能分诊挂号" step="查询中" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll">
|
|
|
+ <main className="task-page processing-page">
|
|
|
+ <span className="processing-icon"><CalendarIcon /></span>
|
|
|
+ <h2>正在查询医生与号源</h2>
|
|
|
+ <p>正在按神经内科、明天上午、专家号和李明主任的条件查询 Mock 号源。</p>
|
|
|
+ <div className="processing-steps">
|
|
|
+ <span className="done"><CheckCircledIcon /> 挂号信息完整性检查</span>
|
|
|
+ <span className="done"><CheckCircledIcon /> 急诊风险门禁通过</span>
|
|
|
+ <span><ReloadIcon /> 查询医生排班与实时号源</span>
|
|
|
+ </div>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.replace(scheduleScreen)}>
|
|
|
+ 查看可用号源 <ChevronRightIcon />
|
|
|
+ </button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(registrationFailureScreen)}>
|
|
|
+ 演示无号源 / 查询失败
|
|
|
+ </button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
+const registrationFailureScreen: FlowScreen = {
|
|
|
+ id: "registration-failure",
|
|
|
+ title: "号源发生变化",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="智能分诊挂号" step="需要处理" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll">
|
|
|
+ <main className="task-page result-page failure-page">
|
|
|
+ <span className="result-icon failure"><CrossCircledIcon /></span>
|
|
|
+ <h2>当前号源暂不可用</h2>
|
|
|
+ <p>可能是号源已被占用或 Mock HIS 暂时未响应。您的挂号条件已经保留。</p>
|
|
|
+ <section className="preserved-context">
|
|
|
+ <div><span>科室</span><strong>神经内科</strong></div>
|
|
|
+ <div><span>日期与时间</span><strong>明天 上午</strong></div>
|
|
|
+ <div><span>号别</span><strong>专家号</strong></div>
|
|
|
+ <div><span>医生</span><strong>李明 主任医师</strong></div>
|
|
|
+ </section>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.replace(registrationSearchingScreen)}>
|
|
|
+ 保留条件,重新查询 <ReloadIcon />
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ className="secondary-button"
|
|
|
+ type="button"
|
|
|
+ onClick={() => flow.replace(makeAssistantScreen(
|
|
|
+ "registration",
|
|
|
+ "我头疼,帮我挂神经内科明天早上九点的专家号,李明主任",
|
|
|
+ "complete",
|
|
|
+ ))}
|
|
|
+ >
|
|
|
+ 返回修改挂号条件
|
|
|
+ </button>
|
|
|
+ <button className="text-button" type="button" onClick={() => flow.replace(makeRootScreen("records"))}>
|
|
|
+ 稍后处理,在健康记录中继续
|
|
|
+ </button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
function ScheduleExperience({ flow }: { flow: FlowControls }) {
|
|
|
const [slot, setSlot] = useState("09:30");
|
|
|
return (
|
|
|
@@ -846,7 +1050,7 @@ const registrationConfirmScreen: FlowScreen = {
|
|
|
headerHeight: 52,
|
|
|
header: (flow) => <PageHeader flow={flow} title="确认挂号" step="5/5" />,
|
|
|
footerHeight: 82,
|
|
|
- footer: (flow) => <FixedAction label="确认并模拟支付" onClick={() => flow.push(registrationResultScreen)} />,
|
|
|
+ footer: (flow) => <FixedAction label="确认并模拟执行" onClick={() => flow.push(registrationProcessingScreen)} />,
|
|
|
render: () => (
|
|
|
<MobileScroll className="task-scroll has-action-footer">
|
|
|
<main className="task-page">
|
|
|
@@ -867,6 +1071,33 @@ const registrationConfirmScreen: FlowScreen = {
|
|
|
),
|
|
|
};
|
|
|
|
|
|
+const registrationProcessingScreen: FlowScreen = {
|
|
|
+ id: "registration-processing",
|
|
|
+ title: "正在办理挂号",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="确认挂号" step="执行中" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll">
|
|
|
+ <main className="task-page processing-page">
|
|
|
+ <span className="processing-icon"><ReloadIcon /></span>
|
|
|
+ <h2>正在模拟锁号与挂号</h2>
|
|
|
+ <p>当前为 P0 演示,不会产生真实预约或支付。</p>
|
|
|
+ <div className="processing-steps">
|
|
|
+ <span className="done"><CheckCircledIcon /> 挂号信息再次确认</span>
|
|
|
+ <span className="done"><CheckCircledIcon /> Mock 锁号成功</span>
|
|
|
+ <span><ReloadIcon /> 创建挂号记录与模拟支付</span>
|
|
|
+ </div>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.replace(registrationResultScreen)}>
|
|
|
+ 完成模拟挂号
|
|
|
+ </button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(registrationFailureScreen)}>
|
|
|
+ 演示锁号失效
|
|
|
+ </button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
const registrationResultScreen: FlowScreen = {
|
|
|
id: "registration-result",
|
|
|
title: "挂号结果",
|
|
|
@@ -910,6 +1141,42 @@ const reportUploadScreen: FlowScreen = {
|
|
|
<button type="button" onClick={() => flow.push(reportConfirmScreen)}><FileTextIcon /><span><strong>血常规</strong><small>本院已审核 · 重点演示血小板指标</small></span><ChevronRightIcon /></button>
|
|
|
<button type="button" onClick={() => flow.push(reportConfirmScreen)}><FileTextIcon /><span><strong>甲状腺超声</strong><small>本院已审核 · 检查报告文字解读</small></span><ChevronRightIcon /></button>
|
|
|
</div>
|
|
|
+ <button className="failure-demo-link" type="button" onClick={() => flow.push(reportFileFailureScreen)}>
|
|
|
+ <CrossCircledIcon /> 演示图片模糊 / OCR 失败
|
|
|
+ </button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
+const reportFileFailureScreen: FlowScreen = {
|
|
|
+ id: "report-file-failure",
|
|
|
+ title: "报告无法识别",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="报告智能解读" step="需要重传" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll">
|
|
|
+ <main className="task-page result-page failure-page">
|
|
|
+ <span className="result-icon failure"><CrossCircledIcon /></span>
|
|
|
+ <h2>这张报告暂时无法识别</h2>
|
|
|
+ <p>图片存在明显模糊,部分指标名称和数值无法可靠读取。</p>
|
|
|
+ <section className="failure-reasons">
|
|
|
+ <strong>请重新拍摄并检查</strong>
|
|
|
+ <ul>
|
|
|
+ <li>报告四角完整入镜</li>
|
|
|
+ <li>文字清晰,无反光和遮挡</li>
|
|
|
+ <li>避免倾斜或距离过远</li>
|
|
|
+ </ul>
|
|
|
+ </section>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.replace(reportUploadScreen)}>
|
|
|
+ 重新上传报告 <UploadIcon />
|
|
|
+ </button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(reportConfirmScreen)}>
|
|
|
+ 改用演示血常规
|
|
|
+ </button>
|
|
|
+ <button className="text-button" type="button" onClick={() => flow.replace(makeRootScreen("records"))}>
|
|
|
+ 保存任务,稍后继续
|
|
|
+ </button>
|
|
|
</main>
|
|
|
</MobileScroll>
|
|
|
),
|
|
|
@@ -970,6 +1237,7 @@ const reportProcessingScreen: FlowScreen = {
|
|
|
<span><ReloadIcon /> 指标标准化与知识检索</span>
|
|
|
</div>
|
|
|
<button className="primary-button inline-action" type="button" onClick={() => flow.replace(reportResultScreen)}>查看解读结果</button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(reportFileFailureScreen)}>演示 OCR 识别失败</button>
|
|
|
</main>
|
|
|
</MobileScroll>
|
|
|
),
|
|
|
@@ -999,6 +1267,9 @@ const reportResultScreen: FlowScreen = {
|
|
|
<h3>下一步建议</h3>
|
|
|
<p>如出现皮肤出血点、鼻出血或牙龈出血,建议尽快联系医生;可咨询血液科或原就诊科室。</p>
|
|
|
</section>
|
|
|
+ <button className="risk-demo-link" type="button" onClick={() => flow.push(reportCriticalScreen)}>
|
|
|
+ <ExclamationTriangleIcon /> 演示报告高风险提示
|
|
|
+ </button>
|
|
|
<button className="primary-button inline-action" type="button" onClick={() => flow.replace(makeRootScreen("records"))}>保存并查看健康记录</button>
|
|
|
<button className="text-button" type="button" onClick={() => flow.replace(makeRootScreen("home"))}>返回首页</button>
|
|
|
<small className="rule-note">当前阈值为演示配置,正式上线以医院确认规则为准。</small>
|
|
|
@@ -1007,6 +1278,33 @@ const reportResultScreen: FlowScreen = {
|
|
|
),
|
|
|
};
|
|
|
|
|
|
+const reportCriticalScreen: FlowScreen = {
|
|
|
+ id: "report-critical",
|
|
|
+ title: "报告风险提示",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="报告智能解读" step="风险优先" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll danger-surface">
|
|
|
+ <main className="task-page emergency-page report-critical-page">
|
|
|
+ <span className="emergency-icon"><ExclamationTriangleIcon /></span>
|
|
|
+ <h2>请优先联系医生确认</h2>
|
|
|
+ <p>演示规则识别到血小板结果可能需要尽快复核。风险提示会先于普通指标解释展示。</p>
|
|
|
+ <section className="emergency-actions stacked">
|
|
|
+ <button type="button">联系原就诊科室</button>
|
|
|
+ <button type="button">查看院内急诊位置</button>
|
|
|
+ </section>
|
|
|
+ <section className="notice-card neutral">
|
|
|
+ <InfoCircledIcon />
|
|
|
+ <div><strong>当前为演示规则</strong><p>正式上线后必须使用医院检验科确认的危急值配置。</p></div>
|
|
|
+ </section>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.pop()}>
|
|
|
+ 已了解,返回完整解读
|
|
|
+ </button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
const tongueIntroScreen: FlowScreen = {
|
|
|
id: "tongue-intro",
|
|
|
title: "中医舌诊",
|
|
|
@@ -1090,6 +1388,37 @@ const tongueProcessingScreen: FlowScreen = {
|
|
|
<span><ReloadIcon /> 问答与舌象证据融合</span>
|
|
|
</div>
|
|
|
<button className="primary-button inline-action" type="button" onClick={() => flow.replace(tongueResultScreen)}>查看综合结果</button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(tongueFailureScreen)}>演示舌诊 MCP 失败</button>
|
|
|
+ </main>
|
|
|
+ </MobileScroll>
|
|
|
+ ),
|
|
|
+};
|
|
|
+
|
|
|
+const tongueFailureScreen: FlowScreen = {
|
|
|
+ id: "tongue-failure",
|
|
|
+ title: "舌诊分析未完成",
|
|
|
+ headerHeight: 52,
|
|
|
+ header: (flow) => <PageHeader flow={flow} title="中医舌诊" step="可恢复" />,
|
|
|
+ render: (flow) => (
|
|
|
+ <MobileScroll className="task-scroll">
|
|
|
+ <main className="task-page result-page failure-page">
|
|
|
+ <span className="result-icon failure"><CrossCircledIcon /></span>
|
|
|
+ <h2>本次分析暂未完成</h2>
|
|
|
+ <p>舌诊 MCP 暂时没有返回结果。已保留问答内容和合格舌象,不需要重新开始。</p>
|
|
|
+ <section className="preserved-context">
|
|
|
+ <div><span>体质问答</span><strong>已保存</strong></div>
|
|
|
+ <div><span>舌象图片</span><strong>质量合格</strong></div>
|
|
|
+ <div><span>分析状态</span><strong>等待重试</strong></div>
|
|
|
+ </section>
|
|
|
+ <button className="primary-button inline-action" type="button" onClick={() => flow.replace(tongueProcessingScreen)}>
|
|
|
+ 重新调用舌诊 MCP <ReloadIcon />
|
|
|
+ </button>
|
|
|
+ <button className="secondary-button" type="button" onClick={() => flow.replace(tongueCaptureScreen)}>
|
|
|
+ 重新拍摄舌象
|
|
|
+ </button>
|
|
|
+ <button className="text-button" type="button" onClick={() => flow.replace(makeRootScreen("records"))}>
|
|
|
+ 保存任务,稍后继续
|
|
|
+ </button>
|
|
|
</main>
|
|
|
</MobileScroll>
|
|
|
),
|