fix(health): 全链路流通性验证修复
- 创建 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:
@@ -72,8 +72,8 @@ function StatValue({ value, loading }: { value: number; loading: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ACTION_LABELS: Record<string, string> = {
|
const ACTION_LABELS: Record<string, string> = {
|
||||||
create: '创建', update: '更新', delete: '删除',
|
create: '创建', created: '创建', update: '更新', updated: '更新', delete: '删除', deleted: '删除',
|
||||||
login: '登录', user_login: '登录', 'user.login': '登录',
|
login: '登录', login_failed: '登录失败', user_login: '登录', 'user.login': '登录',
|
||||||
'user.create': '创建', 'user.update': '更新', 'user.delete': '删除',
|
'user.create': '创建', 'user.update': '更新', 'user.delete': '删除',
|
||||||
'role.create': '创建', 'role.update': '更新', 'role.delete': '删除',
|
'role.create': '创建', 'role.update': '更新', 'role.delete': '删除',
|
||||||
'patient.create': '创建', 'patient.update': '更新',
|
'patient.create': '创建', 'patient.update': '更新',
|
||||||
|
|||||||
@@ -28,16 +28,18 @@ import { DoctorSelect } from './components/DoctorSelect';
|
|||||||
/** 预约类型选项 */
|
/** 预约类型选项 */
|
||||||
const APPOINTMENT_TYPE_OPTIONS = [
|
const APPOINTMENT_TYPE_OPTIONS = [
|
||||||
{ value: 'outpatient', label: '门诊' },
|
{ value: 'outpatient', label: '门诊' },
|
||||||
{ value: 'physical_checkup', label: '体检' },
|
{ value: 'recheck', label: '复诊' },
|
||||||
{ value: 'follow_up', label: '随访' },
|
{ value: 'health_checkup', label: '体检' },
|
||||||
{ value: 'consultation', label: '咨询' },
|
{ value: 'consultation', label: '咨询' },
|
||||||
|
{ value: 'dialysis', label: '透析' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const APPOINTMENT_TYPE_MAP: Record<string, string> = {
|
const APPOINTMENT_TYPE_MAP: Record<string, string> = {
|
||||||
outpatient: '门诊',
|
outpatient: '门诊',
|
||||||
physical_checkup: '体检',
|
recheck: '复诊',
|
||||||
follow_up: '随访',
|
health_checkup: '体检',
|
||||||
consultation: '咨询',
|
consultation: '咨询',
|
||||||
|
dialysis: '透析',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 状态筛选选项 */
|
/** 状态筛选选项 */
|
||||||
@@ -138,6 +140,10 @@ export default function AppointmentList() {
|
|||||||
message.warning('请选择患者');
|
message.warning('请选择患者');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!selectedDoctorId) {
|
||||||
|
message.warning('请选择医护');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const req: CreateAppointmentReq = {
|
const req: CreateAppointmentReq = {
|
||||||
patient_id: selectedPatientId,
|
patient_id: selectedPatientId,
|
||||||
@@ -312,7 +318,7 @@ export default function AppointmentList() {
|
|||||||
setSelectedDoctorId(undefined);
|
setSelectedDoctorId(undefined);
|
||||||
}}
|
}}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
width={560}
|
width={560}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||||
@@ -323,11 +329,11 @@ export default function AppointmentList() {
|
|||||||
placeholder="搜索选择患者"
|
placeholder="搜索选择患者"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="医护">
|
<Form.Item label="医护" required>
|
||||||
<DoctorSelect
|
<DoctorSelect
|
||||||
value={selectedDoctorId}
|
value={selectedDoctorId}
|
||||||
onChange={(val) => setSelectedDoctorId(val)}
|
onChange={(val) => setSelectedDoctorId(val)}
|
||||||
placeholder="搜索选择医护(可选)"
|
placeholder="搜索选择医护"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function PatientDetail() {
|
|||||||
const handleEdit = async (values: {
|
const handleEdit = async (values: {
|
||||||
name?: string;
|
name?: string;
|
||||||
gender?: string;
|
gender?: string;
|
||||||
birth_date?: string;
|
birth_date?: unknown;
|
||||||
blood_type?: string;
|
blood_type?: string;
|
||||||
id_number?: string;
|
id_number?: string;
|
||||||
allergy_history?: string;
|
allergy_history?: string;
|
||||||
@@ -74,9 +74,15 @@ export default function PatientDetail() {
|
|||||||
notes?: string;
|
notes?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!patient) return;
|
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 {
|
try {
|
||||||
const req: UpdatePatientReq & { version: number } = {
|
const req: UpdatePatientReq & { version: number } = {
|
||||||
...values,
|
...formatted,
|
||||||
version: patient.version,
|
version: patient.version,
|
||||||
};
|
};
|
||||||
await patientApi.update(patient.id, req);
|
await patientApi.update(patient.id, req);
|
||||||
|
|||||||
@@ -77,22 +77,28 @@ export default function PatientList() {
|
|||||||
const handleCreateOrEdit = async (values: {
|
const handleCreateOrEdit = async (values: {
|
||||||
name: string;
|
name: string;
|
||||||
gender?: string;
|
gender?: string;
|
||||||
birth_date?: string;
|
birth_date?: unknown;
|
||||||
blood_type?: string;
|
blood_type?: string;
|
||||||
id_number?: string;
|
id_number?: string;
|
||||||
allergy_history?: string;
|
allergy_history?: string;
|
||||||
notes?: 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 {
|
try {
|
||||||
if (editingPatient) {
|
if (editingPatient) {
|
||||||
const req: UpdatePatientReq & { version: number } = {
|
const req: UpdatePatientReq & { version: number } = {
|
||||||
...values,
|
...formatted,
|
||||||
version: (editingPatient as PatientListItem & { version?: number }).version ?? 0,
|
version: (editingPatient as PatientListItem & { version?: number }).version ?? 0,
|
||||||
};
|
};
|
||||||
await patientApi.update(editingPatient.id, req);
|
await patientApi.update(editingPatient.id, req);
|
||||||
message.success('患者信息更新成功');
|
message.success('患者信息更新成功');
|
||||||
} else {
|
} else {
|
||||||
const req: CreatePatientReq = values;
|
const req: CreatePatientReq = formatted;
|
||||||
await patientApi.create(req);
|
await patientApi.create(req);
|
||||||
message.success('患者创建成功');
|
message.success('患者创建成功');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ mod m20260424_000046_health_constraints_fix;
|
|||||||
mod m20260424_000047_health_index_fix;
|
mod m20260424_000047_health_index_fix;
|
||||||
mod m20260425_000048_add_patient_id_number_hash;
|
mod m20260425_000048_add_patient_id_number_hash;
|
||||||
mod m20260425_000049_widen_patient_id_number;
|
mod m20260425_000049_widen_patient_id_number;
|
||||||
|
mod m20260425_00050_add_doctor_name_column;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
@@ -105,6 +106,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260424_000047_health_index_fix::Migration),
|
Box::new(m20260424_000047_health_index_fix::Migration),
|
||||||
Box::new(m20260425_000048_add_patient_id_number_hash::Migration),
|
Box::new(m20260425_000048_add_patient_id_number_hash::Migration),
|
||||||
Box::new(m20260425_000049_widen_patient_id_number::Migration),
|
Box::new(m20260425_000049_widen_patient_id_number::Migration),
|
||||||
|
Box::new(m20260425_00050_add_doctor_name_column::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user