fix(test+web): 修复测试编译错误 + 前端构建问题
- 修复透析集成测试 TestApp.dialysis_state() 返回类型不匹配(39个错误) - 修复 erp-core test_helpers SeaORM Database::connect API 变更 - 修复 health_alert/article/data 集成测试函数签名不匹配 - 修复 DailyMonitoringTab 缺失 Input import - 修复 DeviceReadingsTab 未使用接口声明 - 修复 DialysisManageList keyword → search 参数名
This commit is contained in:
248
apps/web/src/pages/health/components/DailyMonitoringTab.tsx
Normal file
248
apps/web/src/pages/health/components/DailyMonitoringTab.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { Table, Button, Modal, Form, Input, InputNumber, DatePicker, message, Popconfirm, Space } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { dayjs } from '../../../utils/dayjs';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { healthDataApi } from '../../../api/health/healthData';
|
||||
import type { DailyMonitoring } from '../../../api/health/healthData';
|
||||
import { usePaginatedData } from '../../../hooks/usePaginatedData';
|
||||
import { AuthButton } from '../../../components/AuthButton';
|
||||
import { handleApiError } from '../../../api/client';
|
||||
|
||||
interface Props {
|
||||
patientId: string;
|
||||
}
|
||||
|
||||
export function DailyMonitoringTab({ patientId }: Props) {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingRecord, setEditingRecord] = useState<DailyMonitoring | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const fetcher = useCallback(
|
||||
async (page: number, pageSize: number) => {
|
||||
return healthDataApi.listDailyMonitoring(patientId, { page, page_size: pageSize });
|
||||
},
|
||||
[patientId],
|
||||
);
|
||||
|
||||
const { data, total, page, loading, refresh } = usePaginatedData<DailyMonitoring>(fetcher, 10);
|
||||
|
||||
const openCreateModal = () => {
|
||||
setEditingRecord(null);
|
||||
form.resetFields();
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const openEditModal = (record: DailyMonitoring) => {
|
||||
setEditingRecord(record);
|
||||
form.setFieldsValue({
|
||||
record_date: dayjs(record.record_date),
|
||||
morning_bp_systolic: record.morning_bp_systolic,
|
||||
morning_bp_diastolic: record.morning_bp_diastolic,
|
||||
evening_bp_systolic: record.evening_bp_systolic,
|
||||
evening_bp_diastolic: record.evening_bp_diastolic,
|
||||
weight: record.weight,
|
||||
blood_sugar: record.blood_sugar,
|
||||
fluid_intake: record.fluid_intake,
|
||||
urine_output: record.urine_output,
|
||||
notes: record.notes,
|
||||
});
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: {
|
||||
record_date: Dayjs;
|
||||
morning_bp_systolic?: number;
|
||||
morning_bp_diastolic?: number;
|
||||
evening_bp_systolic?: number;
|
||||
evening_bp_diastolic?: number;
|
||||
weight?: number;
|
||||
blood_sugar?: number;
|
||||
fluid_intake?: number;
|
||||
urine_output?: number;
|
||||
notes?: string;
|
||||
}) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const payload = {
|
||||
...values,
|
||||
record_date: values.record_date.format('YYYY-MM-DD'),
|
||||
};
|
||||
if (editingRecord) {
|
||||
await healthDataApi.updateDailyMonitoring(editingRecord.id, {
|
||||
...payload,
|
||||
version: editingRecord.version,
|
||||
});
|
||||
message.success('日常监测记录更新成功');
|
||||
} else {
|
||||
await healthDataApi.createDailyMonitoring({ ...payload, patient_id: patientId });
|
||||
message.success('日常监测记录添加成功');
|
||||
}
|
||||
setModalOpen(false);
|
||||
setEditingRecord(null);
|
||||
form.resetFields();
|
||||
refresh();
|
||||
} catch (err) {
|
||||
handleApiError(err, editingRecord ? '更新失败' : '添加失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (record: DailyMonitoring) => {
|
||||
try {
|
||||
await healthDataApi.deleteDailyMonitoring(record.id, record.version);
|
||||
message.success('删除成功');
|
||||
refresh();
|
||||
} catch (err) {
|
||||
handleApiError(err, '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{ title: '记录日期', dataIndex: 'record_date', key: 'record_date', width: 110 },
|
||||
{
|
||||
title: '晨起血压',
|
||||
key: 'morning_bp',
|
||||
width: 120,
|
||||
render: (_: unknown, r: DailyMonitoring) =>
|
||||
r.morning_bp_systolic != null ? `${r.morning_bp_systolic}/${r.morning_bp_diastolic}` : '-',
|
||||
},
|
||||
{
|
||||
title: '晚间血压',
|
||||
key: 'evening_bp',
|
||||
width: 120,
|
||||
render: (_: unknown, r: DailyMonitoring) =>
|
||||
r.evening_bp_systolic != null ? `${r.evening_bp_systolic}/${r.evening_bp_diastolic}` : '-',
|
||||
},
|
||||
{
|
||||
title: '体重(kg)',
|
||||
dataIndex: 'weight',
|
||||
key: 'weight',
|
||||
width: 90,
|
||||
render: (v: number | null) => v ?? '-',
|
||||
},
|
||||
{
|
||||
title: '血糖(mmol/L)',
|
||||
dataIndex: 'blood_sugar',
|
||||
key: 'blood_sugar',
|
||||
width: 110,
|
||||
render: (v: number | null) => v ?? '-',
|
||||
},
|
||||
{
|
||||
title: '入量(ml)',
|
||||
dataIndex: 'fluid_intake',
|
||||
key: 'fluid_intake',
|
||||
width: 90,
|
||||
render: (v: number | null) => v ?? '-',
|
||||
},
|
||||
{
|
||||
title: '出量(ml)',
|
||||
dataIndex: 'urine_output',
|
||||
key: 'urine_output',
|
||||
width: 90,
|
||||
render: (v: number | null) => v ?? '-',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 120,
|
||||
render: (_: unknown, record: DailyMonitoring) => (
|
||||
<AuthButton code="health.health-data.manage">
|
||||
<Space size={0}>
|
||||
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEditModal(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm title="确认删除该记录?" onConfirm={() => handleDelete(record)} okText="确认" cancelText="取消">
|
||||
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</AuthButton>
|
||||
),
|
||||
},
|
||||
],
|
||||
[openEditModal, handleDelete],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button icon={<PlusOutlined />} onClick={openCreateModal}>
|
||||
添加记录
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
size="small"
|
||||
pagination={{
|
||||
current: page,
|
||||
total,
|
||||
pageSize: 10,
|
||||
onChange: (p) => refresh(p),
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
style: { margin: 0 },
|
||||
}}
|
||||
/>
|
||||
<Modal
|
||||
title={editingRecord ? '编辑日常监测' : '添加日常监测'}
|
||||
open={modalOpen}
|
||||
onCancel={() => {
|
||||
setModalOpen(false);
|
||||
setEditingRecord(null);
|
||||
}}
|
||||
onOk={() => form.submit()}
|
||||
confirmLoading={submitting}
|
||||
destroyOnClose
|
||||
width={560}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||
<Form.Item name="record_date" label="记录日期" rules={[{ required: true, message: '请选择日期' }]}>
|
||||
<DatePicker style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="morning_bp_systolic" label="晨起收缩压" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={300} placeholder="mmHg" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="morning_bp_diastolic" label="晨起舒张压" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={200} placeholder="mmHg" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="evening_bp_systolic" label="晚间收缩压" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={300} placeholder="mmHg" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="evening_bp_diastolic" label="晚间舒张压" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={200} placeholder="mmHg" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="weight" label="体重(kg)" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={500} step={0.1} placeholder="kg" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="blood_sugar" label="血糖(mmol/L)" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={50} step={0.1} placeholder="mmol/L" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="fluid_intake" label="入量(ml)" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={10000} placeholder="ml" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="urine_output" label="出量(ml)" style={{ flex: 1 }}>
|
||||
<InputNumber min={0} max={10000} placeholder="ml" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item name="notes" label="备注">
|
||||
<Input.TextArea rows={2} placeholder="可选备注" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -45,11 +45,6 @@ interface Props {
|
||||
|
||||
/* ---------- 原始数据 Tab ---------- */
|
||||
|
||||
interface RawFilters {
|
||||
deviceType: string | undefined;
|
||||
hours: number;
|
||||
}
|
||||
|
||||
function RawDataTab({ patientId }: Props) {
|
||||
const [deviceType, setDeviceType] = useState<string | undefined>(undefined);
|
||||
const [hours, setHours] = useState<number>(24);
|
||||
@@ -162,11 +157,6 @@ function RawDataTab({ patientId }: Props) {
|
||||
|
||||
/* ---------- 小时聚合 Tab ---------- */
|
||||
|
||||
interface HourlyFilters {
|
||||
deviceType: string | undefined;
|
||||
days: number;
|
||||
}
|
||||
|
||||
function HourlyAggTab({ patientId }: Props) {
|
||||
const [deviceType, setDeviceType] = useState<string | undefined>(undefined);
|
||||
const [days, setDays] = useState<number>(7);
|
||||
|
||||
Reference in New Issue
Block a user