1. CRITICAL: 修复 SubscriptionHandle 提前 drop 导致所有事件消费者失效 - register_handlers_with_state 中所有 handle 在函数返回时被 drop - cancel channel 关闭导致 subscribe_filtered 的过滤任务退出 - 方案: 收集所有 handle 并 std::mem::forget,生命周期与进程一致 2. HIGH: 修复 critical_alert 消费者 payload 字段映射不匹配 - 消费者读取 alert_type/metric_name 等顶层字段,但实际在 alert 嵌套对象中 - 更新消费者从 alert 对象提取 indicator/value/threshold/level - handle_critical_alert_event 增加 severity 参数 3. MEDIUM: 修复 check_indicator 优先匹配最高严重级别 - 原实现返回第一个匹配的阈值(可能匹配 warning 而非 critical) - 改为遍历所有匹配阈值,选择 severity 最高的(critical > warning) 4. MEDIUM: 修复危急值阈值页面不自动加载数据 - CriticalValueThresholdList 添加 useEffect 初始化加载
241 lines
7.1 KiB
TypeScript
241 lines
7.1 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react';
|
|
import {
|
|
Button, Form, Input, InputNumber, message, Modal, Popconfirm, Result, Select, Space, Switch, Table, Tag,
|
|
} from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
|
|
import {
|
|
criticalValueThresholdApi,
|
|
type CriticalValueThreshold,
|
|
type CreateThresholdReq,
|
|
type UpdateThresholdReq,
|
|
INDICATOR_OPTIONS,
|
|
DIRECTION_OPTIONS,
|
|
LEVEL_OPTIONS,
|
|
LEVEL_COLOR,
|
|
INDICATOR_LABEL,
|
|
DIRECTION_LABEL,
|
|
LEVEL_LABEL,
|
|
} from '../../api/health/criticalValueThresholds';
|
|
import { PageContainer } from '../../components/PageContainer';
|
|
import { usePermission } from '../../hooks/usePermission';
|
|
|
|
export default function CriticalValueThresholdList() {
|
|
const { hasPermission } = usePermission('health.critical-value-thresholds.list');
|
|
|
|
const [data, setData] = useState<CriticalValueThreshold[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [loaded, setLoaded] = useState(false);
|
|
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [editRecord, setEditRecord] = useState<CriticalValueThreshold | null>(null);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [form] = Form.useForm();
|
|
|
|
const fetchData = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const list = await criticalValueThresholdApi.list();
|
|
setData(list);
|
|
setLoaded(true);
|
|
} catch {
|
|
message.error('加载危急值阈值失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
const handleCreate = () => {
|
|
setEditRecord(null);
|
|
form.resetFields();
|
|
form.setFieldsValue({ level: 'critical' });
|
|
setModalOpen(true);
|
|
};
|
|
|
|
const handleEdit = (record: CriticalValueThreshold) => {
|
|
setEditRecord(record);
|
|
form.setFieldsValue({
|
|
indicator: record.indicator,
|
|
direction: record.direction,
|
|
threshold_value: record.threshold_value,
|
|
level: record.level,
|
|
department: record.department,
|
|
age_min: record.age_min,
|
|
age_max: record.age_max,
|
|
});
|
|
setModalOpen(true);
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
try {
|
|
const values = await form.validateFields();
|
|
setSubmitting(true);
|
|
|
|
if (editRecord) {
|
|
const req: UpdateThresholdReq = {
|
|
threshold_value: values.threshold_value,
|
|
level: values.level,
|
|
department: values.department,
|
|
age_min: values.age_min,
|
|
age_max: values.age_max,
|
|
version: editRecord.version,
|
|
};
|
|
await criticalValueThresholdApi.update(editRecord.id, req);
|
|
message.success('阈值已更新');
|
|
} else {
|
|
const req: CreateThresholdReq = values;
|
|
await criticalValueThresholdApi.create(req);
|
|
message.success('阈值已创建');
|
|
}
|
|
|
|
setModalOpen(false);
|
|
fetchData();
|
|
} catch {
|
|
// validation
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (record: CriticalValueThreshold) => {
|
|
try {
|
|
await criticalValueThresholdApi.delete(record.id);
|
|
message.success('阈值已删除');
|
|
fetchData();
|
|
} catch {
|
|
message.error('删除失败');
|
|
}
|
|
};
|
|
|
|
const columns: ColumnsType<CriticalValueThreshold> = [
|
|
{
|
|
title: '指标',
|
|
dataIndex: 'indicator',
|
|
width: 120,
|
|
render: (v: string) => INDICATOR_LABEL[v] ?? v,
|
|
},
|
|
{
|
|
title: '方向',
|
|
dataIndex: 'direction',
|
|
width: 80,
|
|
render: (v: string) => DIRECTION_LABEL[v] ?? v,
|
|
},
|
|
{
|
|
title: '阈值',
|
|
dataIndex: 'threshold_value',
|
|
width: 100,
|
|
render: (v: number) => v,
|
|
},
|
|
{
|
|
title: '级别',
|
|
dataIndex: 'level',
|
|
width: 80,
|
|
render: (v: string) => (
|
|
<Tag color={LEVEL_COLOR[v] ?? 'default'}>{LEVEL_LABEL[v] ?? v}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '科室',
|
|
dataIndex: 'department',
|
|
width: 100,
|
|
render: (v: string) => v ?? '通用',
|
|
},
|
|
{
|
|
title: '年龄范围',
|
|
width: 120,
|
|
render: (_, record) => {
|
|
if (record.age_min == null && record.age_max == null) return '不限';
|
|
return `${record.age_min ?? 0} - ${record.age_max ?? '∞'}`;
|
|
},
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'is_active',
|
|
width: 80,
|
|
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '停用'}</Tag>,
|
|
},
|
|
{
|
|
title: '操作',
|
|
width: 140,
|
|
render: (_, record) => (
|
|
<Space>
|
|
<Button size="small" onClick={() => handleEdit(record)}>编辑</Button>
|
|
<Popconfirm title="确定删除此阈值规则?" onConfirm={() => handleDelete(record)}>
|
|
<Button size="small" danger>删除</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
if (!hasPermission) {
|
|
return <Result status="403" title="权限不足" subTitle="您没有查看危急值阈值的权限" />;
|
|
}
|
|
|
|
return (
|
|
<PageContainer
|
|
title="危急值阈值"
|
|
actions={<Button type="primary" onClick={handleCreate}>添加阈值</Button>}
|
|
>
|
|
<Space style={{ marginBottom: 16 }}>
|
|
<Button type="primary" onClick={fetchData} loading={loading}>
|
|
{loaded ? '刷新' : '加载阈值'}
|
|
</Button>
|
|
</Space>
|
|
|
|
{loaded && (
|
|
<Table<CriticalValueThreshold>
|
|
rowKey="id"
|
|
columns={columns}
|
|
dataSource={data}
|
|
loading={loading}
|
|
pagination={false}
|
|
/>
|
|
)}
|
|
|
|
<Modal
|
|
title={editRecord ? '编辑阈值' : '添加阈值'}
|
|
open={modalOpen}
|
|
onOk={handleSubmit}
|
|
onCancel={() => setModalOpen(false)}
|
|
confirmLoading={submitting}
|
|
width={520}
|
|
>
|
|
<Form form={form} layout="vertical">
|
|
{!editRecord && (
|
|
<>
|
|
<Form.Item name="indicator" label="指标" rules={[{ required: true, message: '请选择指标' }]}>
|
|
<Select options={INDICATOR_OPTIONS} placeholder="选择指标" />
|
|
</Form.Item>
|
|
<Form.Item name="direction" label="方向" rules={[{ required: true, message: '请选择方向' }]}>
|
|
<Select options={DIRECTION_OPTIONS} placeholder="偏高/偏低" />
|
|
</Form.Item>
|
|
</>
|
|
)}
|
|
<Form.Item name="threshold_value" label="阈值" rules={[{ required: true, message: '请输入阈值' }]}>
|
|
<InputNumber style={{ width: '100%' }} step={0.1} />
|
|
</Form.Item>
|
|
<Form.Item name="level" label="级别">
|
|
<Select options={LEVEL_OPTIONS} />
|
|
</Form.Item>
|
|
<Form.Item name="department" label="限定科室">
|
|
<Input placeholder="留空表示通用规则" />
|
|
</Form.Item>
|
|
<Space style={{ width: '100%' }} size="middle">
|
|
<Form.Item name="age_min" label="最小年龄">
|
|
<InputNumber min={0} max={150} placeholder="不限" style={{ width: 200 }} />
|
|
</Form.Item>
|
|
<Form.Item name="age_max" label="最大年龄">
|
|
<InputNumber min={0} max={150} placeholder="不限" style={{ width: 200 }} />
|
|
</Form.Item>
|
|
</Space>
|
|
</Form>
|
|
</Modal>
|
|
</PageContainer>
|
|
);
|
|
}
|