fix(health): 全链路流通性验证修复
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

- 创建 stub migration 解决缺失文件报错
- PatientList/PatientDetail: DatePicker dayjs 对象序列化为 YYYY-MM-DD
- AppointmentList: 预约类型与后端验证对齐(outpatient/recheck/health_checkup/consultation/dialysis)
- AppointmentList: 医生字段改为必填(后端 CAS 排班要求), destroyOnClose→destroyOnHidden
- Home.tsx: 补充审计日志 action 翻译(created/login_failed 等)

全链路验证通过: 医生CRUD→排班→预约创建+状态流转→随访生命周期→咨询会话+消息→患者详情+健康数据
This commit is contained in:
iven
2026-04-25 11:31:54 +08:00
parent 7a2d8e4664
commit 355e8da272
6 changed files with 50 additions and 14 deletions

View File

@@ -72,8 +72,8 @@ function StatValue({ value, loading }: { value: number; loading: boolean }) {
}
const ACTION_LABELS: Record<string, string> = {
create: '创建', update: '更新', delete: '删除',
login: '登录', user_login: '登录', 'user.login': '登录',
create: '创建', created: '创建', update: '更新', updated: '更新', delete: '删除', deleted: '删除',
login: '登录', login_failed: '登录失败', user_login: '登录', 'user.login': '登录',
'user.create': '创建', 'user.update': '更新', 'user.delete': '删除',
'role.create': '创建', 'role.update': '更新', 'role.delete': '删除',
'patient.create': '创建', 'patient.update': '更新',

View File

@@ -28,16 +28,18 @@ import { DoctorSelect } from './components/DoctorSelect';
/** 预约类型选项 */
const APPOINTMENT_TYPE_OPTIONS = [
{ value: 'outpatient', label: '门诊' },
{ value: 'physical_checkup', label: '体检' },
{ value: 'follow_up', label: '随访' },
{ value: 'recheck', label: '复诊' },
{ value: 'health_checkup', label: '体检' },
{ value: 'consultation', label: '咨询' },
{ value: 'dialysis', label: '透析' },
];
const APPOINTMENT_TYPE_MAP: Record<string, string> = {
outpatient: '门诊',
physical_checkup: '体检',
follow_up: '随访',
recheck: '复诊',
health_checkup: '体检',
consultation: '咨询',
dialysis: '透析',
};
/** 状态筛选选项 */
@@ -138,6 +140,10 @@ export default function AppointmentList() {
message.warning('请选择患者');
return;
}
if (!selectedDoctorId) {
message.warning('请选择医护');
return;
}
try {
const req: CreateAppointmentReq = {
patient_id: selectedPatientId,
@@ -312,7 +318,7 @@ export default function AppointmentList() {
setSelectedDoctorId(undefined);
}}
onOk={() => form.submit()}
destroyOnClose
destroyOnHidden
width={560}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
@@ -323,11 +329,11 @@ export default function AppointmentList() {
placeholder="搜索选择患者"
/>
</Form.Item>
<Form.Item label="医护">
<Form.Item label="医护" required>
<DoctorSelect
value={selectedDoctorId}
onChange={(val) => setSelectedDoctorId(val)}
placeholder="搜索选择医护(可选)"
placeholder="搜索选择医护"
/>
</Form.Item>
<Row gutter={16}>

View File

@@ -64,7 +64,7 @@ export default function PatientDetail() {
const handleEdit = async (values: {
name?: string;
gender?: string;
birth_date?: string;
birth_date?: unknown;
blood_type?: string;
id_number?: string;
allergy_history?: string;
@@ -74,9 +74,15 @@ export default function PatientDetail() {
notes?: string;
}) => {
if (!patient) return;
const formatted = {
...values,
birth_date: values.birth_date && typeof values.birth_date === 'object' && 'format' in (values.birth_date as object)
? (values.birth_date as { format: (f: string) => string }).format('YYYY-MM-DD')
: (values.birth_date as string | undefined),
};
try {
const req: UpdatePatientReq & { version: number } = {
...values,
...formatted,
version: patient.version,
};
await patientApi.update(patient.id, req);

View File

@@ -77,22 +77,28 @@ export default function PatientList() {
const handleCreateOrEdit = async (values: {
name: string;
gender?: string;
birth_date?: string;
birth_date?: unknown;
blood_type?: string;
id_number?: string;
allergy_history?: string;
notes?: string;
}) => {
const formatted = {
...values,
birth_date: values.birth_date && typeof values.birth_date === 'object' && 'format' in (values.birth_date as object)
? (values.birth_date as { format: (f: string) => string }).format('YYYY-MM-DD')
: (values.birth_date as string | undefined),
};
try {
if (editingPatient) {
const req: UpdatePatientReq & { version: number } = {
...values,
...formatted,
version: (editingPatient as PatientListItem & { version?: number }).version ?? 0,
};
await patientApi.update(editingPatient.id, req);
message.success('患者信息更新成功');
} else {
const req: CreatePatientReq = values;
const req: CreatePatientReq = formatted;
await patientApi.create(req);
message.success('患者创建成功');
}

View File

@@ -49,6 +49,7 @@ mod m20260424_000046_health_constraints_fix;
mod m20260424_000047_health_index_fix;
mod m20260425_000048_add_patient_id_number_hash;
mod m20260425_000049_widen_patient_id_number;
mod m20260425_00050_add_doctor_name_column;
pub struct Migrator;
@@ -105,6 +106,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260424_000047_health_index_fix::Migration),
Box::new(m20260425_000048_add_patient_id_number_hash::Migration),
Box::new(m20260425_000049_widen_patient_id_number::Migration),
Box::new(m20260425_00050_add_doctor_name_column::Migration),
]
}
}

View File

@@ -0,0 +1,16 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// 已在主库手动执行,此处为空壳保持迁移历史一致性
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}