Files
hms/apps/web/src/pages/health/DiagnosisList.tsx
iven 0774dd75ad
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat(web): 危急值阈值 + 诊断记录 Web UI — Phase 2b-2/2b-3
危急值阈值:CRUD 列表页(指标/方向/阈值/级别/科室/年龄范围)
诊断记录:患者范围 CRUD 列表页(ICD编码/类型/状态/确诊日期)
2026-05-04 23:59:22 +08:00

272 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useCallback, useMemo } from 'react';
import {
Button, DatePicker, Form, Input, message, Modal, Popconfirm,
Result, Select, Space, Table, Tag,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import {
diagnosisApi,
type Diagnosis,
type CreateDiagnosisReq,
type UpdateDiagnosisReq,
DIAGNOSIS_TYPE_OPTIONS,
DIAGNOSIS_STATUS_OPTIONS,
DIAGNOSIS_TYPE_COLOR,
DIAGNOSIS_STATUS_COLOR,
DIAGNOSIS_TYPE_LABEL,
DIAGNOSIS_STATUS_LABEL,
} from '../../api/health/diagnoses';
import { PageContainer } from '../../components/PageContainer';
import { usePermission } from '../../hooks/usePermission';
export default function DiagnosisList() {
const { hasPermission } = usePermission('health.health-data.list');
const [data, setData] = useState<Diagnosis[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [patientId, setPatientId] = useState('');
const [searchInput, setSearchInput] = useState('');
const [modalOpen, setModalOpen] = useState(false);
const [editRecord, setEditRecord] = useState<Diagnosis | null>(null);
const [submitting, setSubmitting] = useState(false);
const [form] = Form.useForm();
const pageSize = 20;
const fetchData = useCallback(async (pid: string, p: number) => {
if (!pid) return;
setLoading(true);
try {
const resp = await diagnosisApi.list(pid, { page: p, page_size: pageSize });
setData(resp.data);
setTotal(resp.total);
setPage(p);
} catch {
message.error('加载诊断记录失败');
} finally {
setLoading(false);
}
}, []);
const handleSearch = () => {
const pid = searchInput.trim();
if (!pid) {
message.warning('请输入患者 ID');
return;
}
setPatientId(pid);
fetchData(pid, 1);
};
const handleCreate = () => {
setEditRecord(null);
form.resetFields();
form.setFieldsValue({ patient_id: patientId, diagnosis_type: 'primary', status: 'active' });
setModalOpen(true);
};
const handleEdit = (record: Diagnosis) => {
setEditRecord(record);
form.setFieldsValue({
icd_code: record.icd_code,
diagnosis_name: record.diagnosis_name,
diagnosis_type: record.diagnosis_type,
diagnosed_date: record.diagnosed_date ? dayjs(record.diagnosed_date) : undefined,
status: record.status,
diagnosed_by: record.diagnosed_by,
notes: record.notes,
});
setModalOpen(true);
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
const req = {
...values,
diagnosed_date: values.diagnosed_date?.format('YYYY-MM-DD'),
};
setSubmitting(true);
if (editRecord) {
await diagnosisApi.update(editRecord.id, {
...req,
version: editRecord.version,
} as UpdateDiagnosisReq & { version: number });
message.success('诊断记录已更新');
} else {
await diagnosisApi.create(req.patient_id, req as CreateDiagnosisReq);
message.success('诊断记录已创建');
}
setModalOpen(false);
fetchData(patientId, page);
} catch {
// validation
} finally {
setSubmitting(false);
}
};
const handleDelete = async (record: Diagnosis) => {
try {
await diagnosisApi.delete(record.id, record.version);
message.success('诊断记录已删除');
fetchData(patientId, page);
} catch {
message.error('删除失败');
}
};
const columns: ColumnsType<Diagnosis> = useMemo(() => [
{
title: 'ICD 编码',
dataIndex: 'icd_code',
width: 110,
},
{
title: '诊断名称',
dataIndex: 'diagnosis_name',
width: 200,
ellipsis: true,
},
{
title: '类型',
dataIndex: 'diagnosis_type',
width: 100,
render: (v: string) => (
<Tag color={DIAGNOSIS_TYPE_COLOR[v] ?? 'default'}>
{DIAGNOSIS_TYPE_LABEL[v] ?? v}
</Tag>
),
},
{
title: '确诊日期',
dataIndex: 'diagnosed_date',
width: 110,
render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD') : '-',
},
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (v: string) => (
<Tag color={DIAGNOSIS_STATUS_COLOR[v] ?? 'default'}>
{DIAGNOSIS_STATUS_LABEL[v] ?? v}
</Tag>
),
},
{
title: '备注',
dataIndex: 'notes',
width: 160,
ellipsis: true,
render: (v: string) => v ?? '-',
},
{
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>
),
},
], [patientId, page]);
if (!hasPermission) {
return <Result status="403" title="权限不足" subTitle="您没有查看诊断记录的权限" />;
}
return (
<PageContainer
title="诊断记录"
actions={patientId ? <Button type="primary" onClick={handleCreate}></Button> : undefined}
>
<Space style={{ marginBottom: 16 }} wrap>
<Input.Search
placeholder="输入患者 ID 搜索"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
onSearch={handleSearch}
style={{ width: 360 }}
enterButton="查询"
/>
{patientId && (
<Tag color="blue">: {patientId}</Tag>
)}
</Space>
{!patientId ? (
<Result title="请输入患者 ID 查询诊断记录" />
) : (
<Table<Diagnosis>
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={{
current: page,
pageSize,
total,
showTotal: (t) => `${t}`,
onChange: (p) => fetchData(patientId, p),
}}
/>
)}
<Modal
title={editRecord ? '编辑诊断' : '添加诊断'}
open={modalOpen}
onOk={handleSubmit}
onCancel={() => setModalOpen(false)}
confirmLoading={submitting}
width={600}
>
<Form form={form} layout="vertical">
{!editRecord && (
<Form.Item name="patient_id" label="患者 ID" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
)}
<Space style={{ width: '100%' }} size="middle">
<Form.Item name="icd_code" label="ICD 编码" rules={[{ required: true, message: '请输入 ICD 编码' }]} style={{ width: 200 }}>
<Input placeholder="如N18.9" />
</Form.Item>
<Form.Item name="diagnosis_name" label="诊断名称" rules={[{ required: true, message: '请输入诊断名称' }]} style={{ width: 320 }}>
<Input placeholder="如:慢性肾脏病" />
</Form.Item>
</Space>
<Space style={{ width: '100%' }} size="middle">
<Form.Item name="diagnosis_type" label="类型" style={{ width: 200 }}>
<Select options={DIAGNOSIS_TYPE_OPTIONS} />
</Form.Item>
<Form.Item name="diagnosed_date" label="确诊日期" rules={[{ required: true, message: '请选择确诊日期' }]} style={{ width: 260 }}>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
</Space>
<Space style={{ width: '100%' }} size="middle">
<Form.Item name="status" label="状态" style={{ width: 200 }}>
<Select options={DIAGNOSIS_STATUS_OPTIONS} />
</Form.Item>
<Form.Item name="diagnosed_by" label="确诊医生 ID" style={{ width: 320 }}>
<Input placeholder="医生 UUID可选" />
</Form.Item>
</Space>
<Form.Item name="notes" label="备注">
<Input.TextArea rows={2} />
</Form.Item>
</Form>
</Modal>
</PageContainer>
);
}