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

后端:
- alert_service: list_alerts 批量查询 patient_name 填充 AlertResponse
- consultation_service: list_sessions 批量查询 patient_name/doctor_name
- erp-ai handler: list_analysis 通过 raw SQL 查询 patient_name

前端:
- AlertList/AlertDashboard: 使用后端返回的 patient_name 替代 ID 截断
- ConsultationDetail: 使用 patient_name/doctor_name 替代 ID 截断
- AiAnalysisList: 使用 patient_name 替代 ID 截断
- constants/health: SEVERITY 补 high/medium, STATUS 补 active
- AdminDashboard: 医护人数改为 API 查询(useStatsData 新增 doctorCount)
- DeviceManage: 患者 ID 输入改为 PatientSelect 搜索选择器
This commit is contained in:
iven
2026-05-04 00:03:40 +08:00
parent 20bd9e8cb4
commit 5140552ff6
14 changed files with 155 additions and 29 deletions

View File

@@ -291,8 +291,46 @@ where
.analysis
.list_analysis(ctx.tenant_id, params.patient_id, params.analysis_type, &pagination)
.await?;
// 批量查询 patient_name通过 raw SQL 避免跨 crate 依赖 erp-health
let patient_ids: std::collections::HashSet<uuid::Uuid> = items
.iter()
.filter(|a| a.patient_id != uuid::Uuid::nil())
.map(|a| a.patient_id)
.collect();
let patient_names: std::collections::HashMap<uuid::Uuid, String> = if !patient_ids.is_empty() {
#[derive(sea_orm::FromQueryResult)]
struct PatientName { id: uuid::Uuid, name: String }
let ids: Vec<uuid::Uuid> = patient_ids.into_iter().collect();
use sea_orm::FromQueryResult;
PatientName::find_by_statement(sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
"SELECT id, name FROM patient WHERE id = ANY($1) AND tenant_id = $2 AND deleted_at IS NULL",
[ids.into(), ctx.tenant_id.into()],
))
.all(&state.db)
.await
.unwrap_or_default()
.into_iter()
.map(|p| (p.id, p.name))
.collect()
} else {
std::collections::HashMap::new()
};
let data: Vec<serde_json::Value> = items.into_iter().map(|a| {
let mut val = serde_json::to_value(&a).unwrap_or_default();
if let Some(obj) = val.as_object_mut() {
obj.insert("patient_name".to_string(), serde_json::json!(
patient_names.get(&a.patient_id).cloned()
));
}
val
}).collect();
Ok(Json(ApiResponse::ok(serde_json::json!({
"data": items,
"data": data,
"total": total,
"page": pagination.page.unwrap_or(1),
"page_size": pagination.limit(),

View File

@@ -74,6 +74,7 @@ pub struct AcknowledgeAlertRequest {
pub struct AlertResponse {
pub id: Uuid,
pub patient_id: Uuid,
pub patient_name: Option<String>,
pub rule_id: Uuid,
pub severity: String,
pub title: String,

View File

@@ -6,8 +6,9 @@ use uuid::Uuid;
use erp_core::error::check_version;
use crate::dto::alert_dto::AlertResponse;
use crate::entity::alerts;
use crate::entity::patient_doctor_relation;
use crate::entity::{patient, patient_doctor_relation};
use crate::error::{HealthError, HealthResult};
use crate::service::validation;
use crate::state::HealthState;
@@ -20,7 +21,7 @@ pub async fn list_alerts(
status: Option<&str>,
page: u64,
page_size: u64,
) -> HealthResult<(Vec<alerts::Model>, u64)> {
) -> HealthResult<(Vec<AlertResponse>, u64)> {
let limit = page_size.min(100);
let offset = page.saturating_sub(1) * limit;
@@ -32,7 +33,6 @@ pub async fn list_alerts(
if let Some(did) = doctor_id {
let patient_ids = get_patient_ids_for_doctor(&state.db, tenant_id, did).await?;
if patient_ids.is_empty() {
// 没有管床患者 → 直接返回空结果
return Ok((vec![], 0));
}
query = query.filter(alerts::Column::PatientId.is_in(patient_ids));
@@ -47,13 +47,44 @@ pub async fn list_alerts(
}
let total = query.clone().count(&state.db).await?;
let items = query
let models = query
.order_by_desc(alerts::Column::CreatedAt)
.limit(limit)
.offset(offset)
.all(&state.db)
.await?;
// 批量查询 patient_name
let patient_ids: std::collections::HashSet<Uuid> = models.iter().map(|m| m.patient_id).collect();
let patient_names: std::collections::HashMap<Uuid, String> = if !patient_ids.is_empty() {
patient::Entity::find()
.filter(patient::Column::Id.is_in(patient_ids))
.filter(patient::Column::TenantId.eq(tenant_id))
.all(&state.db)
.await?
.into_iter()
.map(|p| (p.id, p.name))
.collect()
} else {
std::collections::HashMap::new()
};
let items = models.into_iter().map(|m| AlertResponse {
id: m.id,
patient_id: m.patient_id,
patient_name: patient_names.get(&m.patient_id).cloned(),
rule_id: m.rule_id,
severity: m.severity,
title: m.title,
detail: m.detail,
status: m.status,
acknowledged_by: m.acknowledged_by,
acknowledged_at: m.acknowledged_at,
resolved_at: m.resolved_at,
created_at: m.created_at,
version: m.version,
}).collect();
Ok((items, total))
}

View File

@@ -142,8 +142,43 @@ pub async fn list_sessions(
.all(&state.db)
.await?;
// 批量查询 patient_name 和 doctor_name
let patient_ids: std::collections::HashSet<Uuid> = models.iter().map(|m| m.patient_id).collect();
let doctor_ids: std::collections::HashSet<Uuid> = models.iter().filter_map(|m| m.doctor_id).collect();
let patient_names: std::collections::HashMap<Uuid, String> = if !patient_ids.is_empty() {
patient::Entity::find()
.filter(patient::Column::Id.is_in(patient_ids))
.filter(patient::Column::TenantId.eq(tenant_id))
.all(&state.db)
.await?
.into_iter()
.map(|p| (p.id, p.name))
.collect()
} else {
std::collections::HashMap::new()
};
let doctor_names: std::collections::HashMap<Uuid, String> = if !doctor_ids.is_empty() {
doctor_profile::Entity::find()
.filter(doctor_profile::Column::Id.is_in(doctor_ids))
.filter(doctor_profile::Column::TenantId.eq(tenant_id))
.all(&state.db)
.await?
.into_iter()
.map(|d| (d.id, d.name))
.collect()
} else {
std::collections::HashMap::new()
};
let total_pages = total.div_ceil(limit.max(1));
let data = models.into_iter().map(model_to_session_resp).collect();
let data = models.into_iter().map(|m| {
let mut resp = model_to_session_resp(m.clone());
resp.patient_name = patient_names.get(&m.patient_id).cloned();
resp.doctor_name = m.doctor_id.and_then(|did| doctor_names.get(&did).cloned());
resp
}).collect();
Ok(PaginatedResponse { data, total, page, page_size: limit, total_pages })
}