- 从 HMS 基座复制 apps/web/ (React + Ant Design + Vite + TypeScript) - 管理端自动代理 API 到 localhost:3000 (vite.config.ts) - 更新 scripts/dev.sh 支持三端启动: backend/admin/app - 登录验证通过, 用户管理/角色权限/审计日志等页面正常 - 添加 .gitignore 排除 node_modules/dist
181 lines
5.2 KiB
TypeScript
181 lines
5.2 KiB
TypeScript
import { Card, Tag, Typography, theme } from 'antd';
|
||
import {
|
||
WarningOutlined,
|
||
HeartOutlined,
|
||
LineChartOutlined,
|
||
ExperimentOutlined,
|
||
UserOutlined,
|
||
} from '@ant-design/icons';
|
||
import type { DisplayHint } from '../../api/ai/chat';
|
||
|
||
const { Text } = Typography;
|
||
|
||
const SEVERITY_COLOR: Record<string, string> = {
|
||
high: 'red',
|
||
medium: 'orange',
|
||
low: 'green',
|
||
};
|
||
|
||
const RISK_LEVEL_COLOR: Record<string, string> = {
|
||
critical: '#cf1322',
|
||
high: 'red',
|
||
medium: 'orange',
|
||
low: 'green',
|
||
};
|
||
|
||
export default function RichMessage({ hints }: { hints: DisplayHint[] }) {
|
||
const { token } = theme.useToken();
|
||
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
|
||
{hints.map((hint, i) => (
|
||
<RichHint key={i} hint={hint} token={token} />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function RichHint({ hint, token }: { hint: DisplayHint; token: { colorBorderSecondary: string; colorTextSecondary: string; colorPrimary: string } }) {
|
||
switch (hint.type) {
|
||
case 'insight_card':
|
||
return (
|
||
<Card
|
||
size="small"
|
||
title={
|
||
<span style={{ fontSize: 12 }}>
|
||
<HeartOutlined style={{ marginRight: 4, color: token.colorPrimary }} />
|
||
{hint.title}
|
||
</span>
|
||
}
|
||
styles={{ body: { padding: '6px 12px' } }}
|
||
>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
||
{hint.items.map((item, j) => (
|
||
<Tag key={j} color={SEVERITY_COLOR[hint.severity] ?? 'blue'} style={{ fontSize: 11, margin: 0 }}>
|
||
{item}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
);
|
||
|
||
case 'risk_alert':
|
||
return (
|
||
<div
|
||
style={{
|
||
padding: '8px 12px',
|
||
borderRadius: 8,
|
||
border: `1px solid ${RISK_LEVEL_COLOR[hint.level] ?? token.colorBorderSecondary}`,
|
||
background: `${RISK_LEVEL_COLOR[hint.level] ?? token.colorBorderSecondary}10`,
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
<WarningOutlined style={{ color: RISK_LEVEL_COLOR[hint.level] ?? token.colorPrimary, marginRight: 6 }} />
|
||
{hint.message}
|
||
</div>
|
||
);
|
||
|
||
case 'lab_report_card':
|
||
return (
|
||
<Card
|
||
size="small"
|
||
title={
|
||
<span style={{ fontSize: 12 }}>
|
||
<ExperimentOutlined style={{ marginRight: 4 }} />
|
||
化验报告 {hint.report_date}
|
||
</span>
|
||
}
|
||
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
|
||
>
|
||
{hint.abnormal_count > 0 ? (
|
||
<Tag color="red" style={{ fontSize: 11 }}>
|
||
{hint.abnormal_count} 项异常
|
||
</Tag>
|
||
) : (
|
||
<Tag color="green" style={{ fontSize: 11 }}>正常</Tag>
|
||
)}
|
||
</Card>
|
||
);
|
||
|
||
case 'trend_chart':
|
||
return (
|
||
<Card
|
||
size="small"
|
||
title={
|
||
<span style={{ fontSize: 12 }}>
|
||
<LineChartOutlined style={{ marginRight: 4 }} />
|
||
趋势分析({hint.period})
|
||
</span>
|
||
}
|
||
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
|
||
>
|
||
<div style={{ marginBottom: 4 }}>
|
||
{hint.metrics.map((m, j) => (
|
||
<Tag key={j} style={{ fontSize: 11, margin: '0 4px 2px 0' }}>{m}</Tag>
|
||
))}
|
||
</div>
|
||
<Text type="secondary" style={{ fontSize: 11 }}>{hint.summary}</Text>
|
||
</Card>
|
||
);
|
||
|
||
case 'patient_profile':
|
||
return (
|
||
<Card
|
||
size="small"
|
||
title={
|
||
<span style={{ fontSize: 12 }}>
|
||
<UserOutlined style={{ marginRight: 4 }} />
|
||
患者档案
|
||
</span>
|
||
}
|
||
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
|
||
>
|
||
{hint.chronic_conditions.length > 0 && (
|
||
<div style={{ marginBottom: 4 }}>
|
||
{hint.chronic_conditions.map((c, j) => (
|
||
<Tag key={j} color="orange" style={{ fontSize: 11, margin: '0 4px 2px 0' }}>{c}</Tag>
|
||
))}
|
||
</div>
|
||
)}
|
||
{hint.medication_count > 0 && (
|
||
<Text type="secondary" style={{ fontSize: 11 }}>当前用药 {hint.medication_count} 种</Text>
|
||
)}
|
||
</Card>
|
||
);
|
||
|
||
case 'vital_card':
|
||
return (
|
||
<Card
|
||
size="small"
|
||
title={
|
||
<span style={{ fontSize: 12 }}>
|
||
<HeartOutlined style={{ marginRight: 4, color: token.colorPrimary }} />
|
||
体征数据
|
||
</span>
|
||
}
|
||
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
|
||
>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||
{hint.values.slice(-5).map(([date, val], j) => (
|
||
<span key={j}>
|
||
<Text type="secondary" style={{ fontSize: 11 }}>{date.slice(5)}:</Text>{' '}
|
||
{val} {hint.unit}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
);
|
||
|
||
case 'action_confirm':
|
||
return (
|
||
<Card size="small" styles={{ body: { padding: '8px 12px', fontSize: 13 } }}>
|
||
<Text>{hint.summary}</Text>
|
||
</Card>
|
||
);
|
||
|
||
case 'text':
|
||
default:
|
||
return null;
|
||
}
|
||
}
|