feat: 添加管理端前端 (HMS 基座 React 管理面板)
- 从 HMS 基座复制 apps/web/ (React + Ant Design + Vite + TypeScript) - 管理端自动代理 API 到 localhost:3000 (vite.config.ts) - 更新 scripts/dev.sh 支持三端启动: backend/admin/app - 登录验证通过, 用户管理/角色权限/审计日志等页面正常 - 添加 .gitignore 排除 node_modules/dist
This commit is contained in:
180
apps/web/src/components/ai/RichMessage.tsx
Normal file
180
apps/web/src/components/ai/RichMessage.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user