feat: 完善环境规则、会诊治理、知识审核与效果评估
This commit is contained in:
@@ -11,6 +11,12 @@ export interface Consultation {
|
||||
snapshot?: any;
|
||||
status: string;
|
||||
expertId?: string;
|
||||
assigneeId?: string;
|
||||
assignedAt?: string;
|
||||
acceptedAt?: string;
|
||||
needsInfoAt?: string;
|
||||
slaDeadline?: string;
|
||||
overdueAt?: string;
|
||||
opinion?: string;
|
||||
plan?: string;
|
||||
resolvedAt?: string;
|
||||
@@ -25,6 +31,6 @@ export const createConsultation = (data: Partial<Consultation>) =>
|
||||
post<Consultation>('/consultations', data);
|
||||
export const updateConsultation = (id: string, data: Partial<Consultation>) =>
|
||||
patch<Consultation>(`/consultations/${id}`, data);
|
||||
export const resolveConsultation = (id: string, data: { opinion: string; plan: string }) =>
|
||||
export const resolveConsultation = (id: string, data: { opinion: string; plan: string; reason?: string }) =>
|
||||
post<Consultation>(`/consultations/${id}/resolve`, data);
|
||||
export const archiveConsultation = (id: string) => post<Consultation>(`/consultations/${id}/archive`);
|
||||
|
||||
@@ -11,9 +11,23 @@ export interface HealthProfile {
|
||||
batch?: any;
|
||||
}
|
||||
|
||||
export interface EffectReport {
|
||||
eventId: string;
|
||||
beforeRiskAvg?: number;
|
||||
afterRiskAvg?: number;
|
||||
beforePositiveRate?: number;
|
||||
afterPositiveRate?: number;
|
||||
recurrences: number;
|
||||
missing: string[];
|
||||
conclusion: string;
|
||||
}
|
||||
|
||||
export const getHealthProfile = (roomId: string) =>
|
||||
get<HealthProfile>(`/health-profiles/${roomId}`);
|
||||
|
||||
export const getRoomEffect = (roomId: string, params?: any) =>
|
||||
get<EffectReport>(`/health-profiles/${roomId}/effect`, { params });
|
||||
|
||||
export const getMonthlyStats = (year?: number) =>
|
||||
get<{ year: number; stats: { month: string; total: number; diseases: Record<string, number> }[] }>(
|
||||
'/trace-records/monthly-stats',
|
||||
|
||||
@@ -17,6 +17,10 @@ export interface Disease {
|
||||
detection?: string;
|
||||
prevention?: string;
|
||||
imageUrl?: string;
|
||||
status?: string;
|
||||
source?: string;
|
||||
sourceDate?: string;
|
||||
expertConfirmedBy?: string;
|
||||
sortOrder?: number;
|
||||
enabled?: boolean;
|
||||
createdAt?: string;
|
||||
@@ -29,6 +33,10 @@ export interface KnowledgeArticle {
|
||||
title: string;
|
||||
summary?: string;
|
||||
content?: string;
|
||||
status?: string;
|
||||
source?: string;
|
||||
sourceDate?: string;
|
||||
expertConfirmedBy?: string;
|
||||
sortOrder?: number;
|
||||
enabled?: boolean;
|
||||
createdAt?: string;
|
||||
|
||||
@@ -14,8 +14,10 @@ import {
|
||||
import { authService } from '../services/auth';
|
||||
|
||||
const STATUS_LABELS: Record<string, { color: string; text: string }> = {
|
||||
pending: { color: 'gold', text: '待会诊' },
|
||||
consulting: { color: 'blue', text: '会诊中' },
|
||||
unassigned: { color: 'gold', text: '待分派' },
|
||||
assigned: { color: 'blue', text: '已分派' },
|
||||
accepted: { color: 'cyan', text: '已受理' },
|
||||
needs_info: { color: 'purple', text: '待补充' },
|
||||
resolved: { color: 'green', text: '已出方案' },
|
||||
archived: { color: 'default', text: '已归档' },
|
||||
};
|
||||
@@ -53,11 +55,23 @@ export default function ConsultationsPage() {
|
||||
</a>,
|
||||
...(canWrite()
|
||||
? [
|
||||
r.status === 'pending' ? (
|
||||
r.status === 'unassigned' ? (
|
||||
<a
|
||||
key="take"
|
||||
onClick={async () => {
|
||||
await updateConsultation(r.id, { status: 'consulting' });
|
||||
await updateConsultation(r.id, { status: 'assigned' });
|
||||
message.success('已受理');
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
>
|
||||
分派给我
|
||||
</a>
|
||||
) : null,
|
||||
r.status === 'assigned' ? (
|
||||
<a
|
||||
key="accept"
|
||||
onClick={async () => {
|
||||
await updateConsultation(r.id, { status: 'accepted' });
|
||||
message.success('已受理');
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
@@ -65,7 +79,7 @@ export default function ConsultationsPage() {
|
||||
受理
|
||||
</a>
|
||||
) : null,
|
||||
r.status === 'pending' || r.status === 'consulting' ? (
|
||||
r.status === 'accepted' || r.status === 'needs_info' || r.status === 'assigned' ? (
|
||||
<a
|
||||
key="resolve"
|
||||
onClick={() => {
|
||||
|
||||
@@ -33,6 +33,13 @@ const KIND_LABELS: Record<string, string> = {
|
||||
seasonal_tip: '季节性提醒',
|
||||
};
|
||||
|
||||
const STATUS_LABELS: Record<string, { color: string; text: string }> = {
|
||||
draft: { color: 'gold', text: '草稿' },
|
||||
review: { color: 'blue', text: '审核中' },
|
||||
published: { color: 'green', text: '已发布' },
|
||||
withdrawn: { color: 'default', text: '已撤回' },
|
||||
};
|
||||
|
||||
const canWrite = () => authService.hasPermission('knowledge:write');
|
||||
|
||||
function DiseaseTab() {
|
||||
@@ -63,6 +70,7 @@ function DiseaseTab() {
|
||||
width: 80,
|
||||
render: (_, r) => (r.enabled === false ? <Tag color="default">停用</Tag> : <Tag color="green">启用</Tag>),
|
||||
},
|
||||
{ title: '审核状态', dataIndex: 'status', search: false, render: (_, r) => (r.status ? <Tag color={STATUS_LABELS[r.status]?.color}>{STATUS_LABELS[r.status]?.text || r.status}</Tag> : '-') },
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
@@ -254,6 +262,8 @@ function ArticleTab() {
|
||||
render: (_, r) => <Tag>{KIND_LABELS[r.kind] || r.kind}</Tag>,
|
||||
},
|
||||
{ title: '摘要', dataIndex: 'summary', search: false, ellipsis: true, render: (_, r) => r.summary || '-' },
|
||||
{ title: '审核状态', dataIndex: 'status', search: false, render: (_, r) => (r.status ? <Tag color={STATUS_LABELS[r.status]?.color}>{STATUS_LABELS[r.status]?.text || r.status}</Tag> : '-') },
|
||||
{ title: '来源', dataIndex: 'source', search: false, render: (_, r) => r.source || '-' },
|
||||
{ title: '排序', dataIndex: 'sortOrder', search: false, width: 70 },
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type RegionStat,
|
||||
} from '../dal/trace';
|
||||
import { getMonthlyStats } from '../dal/health';
|
||||
import { getRoomEffect, type EffectReport } from '../dal/health';
|
||||
import { authService } from '../services/auth';
|
||||
|
||||
const STATUS_LABELS: Record<string, { color: string; text: string }> = {
|
||||
@@ -40,6 +41,8 @@ export default function TracesPage() {
|
||||
const [detail, setDetail] = useState<TraceRecord | null>(null);
|
||||
const [checklist, setChecklist] = useState<ChecklistItem[]>([]);
|
||||
const [answers, setAnswers] = useState<Record<string, string>>({});
|
||||
const [effect, setEffect] = useState<EffectReport | null>(null);
|
||||
const [effectRoomId, setEffectRoomId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
getRegionStats(90)
|
||||
@@ -94,6 +97,12 @@ export default function TracesPage() {
|
||||
setAnswers({});
|
||||
};
|
||||
|
||||
const openEffect = async (r: TraceRecord) => {
|
||||
if (!r.roomId) return;
|
||||
setEffectRoomId(r.roomId);
|
||||
setEffect(await getRoomEffect(r.roomId).catch(() => null));
|
||||
};
|
||||
|
||||
const columns: ProColumns<TraceRecord>[] = [
|
||||
{ title: '序号', valueType: 'indexBorder', search: false, width: 60 },
|
||||
{ title: '病种', dataIndex: 'disease' },
|
||||
@@ -126,6 +135,11 @@ export default function TracesPage() {
|
||||
<a key="view" onClick={() => openDetail(r)}>
|
||||
详情
|
||||
</a>,
|
||||
r.roomId ? (
|
||||
<a key="effect" onClick={() => openEffect(r)}>
|
||||
效果
|
||||
</a>
|
||||
) : null,
|
||||
...(canWrite()
|
||||
? [
|
||||
r.status === 'pending' ? (
|
||||
@@ -242,6 +256,29 @@ export default function TracesPage() {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`防控效果报告:${effectRoomId}`}
|
||||
open={!!effect}
|
||||
onCancel={() => setEffect(null)}
|
||||
footer={<Button onClick={() => setEffect(null)}>关闭</Button>}
|
||||
>
|
||||
{effect ? (
|
||||
<div>
|
||||
<Typography.Paragraph>
|
||||
结论:<b>{effect.conclusion}</b>
|
||||
<br />
|
||||
处置前风险均值:{effect.beforeRiskAvg !== undefined ? effect.beforeRiskAvg.toFixed(1) : '缺失'}
|
||||
<br />
|
||||
处置后风险均值:{effect.afterRiskAvg !== undefined ? effect.afterRiskAvg.toFixed(1) : '缺失'}
|
||||
<br />
|
||||
复发次数:{effect.recurrences}
|
||||
<br />
|
||||
缺失维度:{effect.missing.join('、') || '无'}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Drawer title={`溯源详情 - ${detail?.disease || ''}`} open={!!detail} width={680} onClose={() => setDetail(null)}>
|
||||
{detail ? (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user