fix(health): 走查止血 — 患者名显示修复 + 枚举补全 + 医护统计 + 设备选择器
后端: - 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:
@@ -4,6 +4,7 @@ import type { PaginatedResponse } from '../types';
|
|||||||
export interface AnalysisItem {
|
export interface AnalysisItem {
|
||||||
id: string;
|
id: string;
|
||||||
patient_id: string;
|
patient_id: string;
|
||||||
|
patient_name?: string;
|
||||||
analysis_type: string;
|
analysis_type: string;
|
||||||
source_ref: string;
|
source_ref: string;
|
||||||
model_used: string;
|
model_used: string;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { PaginatedResponse } from '../types';
|
|||||||
export interface Alert {
|
export interface Alert {
|
||||||
id: string;
|
id: string;
|
||||||
patient_id: string;
|
patient_id: string;
|
||||||
|
patient_name?: string;
|
||||||
rule_id: string;
|
rule_id: string;
|
||||||
severity: string;
|
severity: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ export const SEVERITY_COLOR: Record<string, string> = {
|
|||||||
warning: 'orange',
|
warning: 'orange',
|
||||||
critical: 'red',
|
critical: 'red',
|
||||||
urgent: 'magenta',
|
urgent: 'magenta',
|
||||||
|
high: 'red',
|
||||||
|
medium: 'orange',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SEVERITY_LABEL: Record<string, string> = {
|
export const SEVERITY_LABEL: Record<string, string> = {
|
||||||
@@ -47,18 +49,23 @@ export const SEVERITY_LABEL: Record<string, string> = {
|
|||||||
warning: '警告',
|
warning: '警告',
|
||||||
critical: '严重',
|
critical: '严重',
|
||||||
urgent: '紧急',
|
urgent: '紧急',
|
||||||
|
high: '严重',
|
||||||
|
medium: '中等',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SEVERITY_OPTIONS = [
|
export const SEVERITY_OPTIONS = [
|
||||||
{ value: 'info', label: '提示' },
|
{ value: 'info', label: '提示' },
|
||||||
{ value: 'warning', label: '警告' },
|
{ value: 'warning', label: '警告' },
|
||||||
|
{ value: 'medium', label: '中等' },
|
||||||
{ value: 'critical', label: '严重' },
|
{ value: 'critical', label: '严重' },
|
||||||
|
{ value: 'high', label: '严重' },
|
||||||
{ value: 'urgent', label: '紧急' },
|
{ value: 'urgent', label: '紧急' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- 告警状态(统一 3 处: AlertDashboard, AlertList) ---
|
// --- 告警状态(统一 3 处: AlertDashboard, AlertList) ---
|
||||||
export const ALERT_STATUS_COLOR: Record<string, string> = {
|
export const ALERT_STATUS_COLOR: Record<string, string> = {
|
||||||
pending: 'orange',
|
pending: 'orange',
|
||||||
|
active: 'gold',
|
||||||
acknowledged: 'blue',
|
acknowledged: 'blue',
|
||||||
resolved: 'green',
|
resolved: 'green',
|
||||||
dismissed: 'default',
|
dismissed: 'default',
|
||||||
@@ -66,6 +73,7 @@ export const ALERT_STATUS_COLOR: Record<string, string> = {
|
|||||||
|
|
||||||
export const ALERT_STATUS_LABEL: Record<string, string> = {
|
export const ALERT_STATUS_LABEL: Record<string, string> = {
|
||||||
pending: '待处理',
|
pending: '待处理',
|
||||||
|
active: '活跃',
|
||||||
acknowledged: '已确认',
|
acknowledged: '已确认',
|
||||||
resolved: '已恢复',
|
resolved: '已恢复',
|
||||||
dismissed: '已忽略',
|
dismissed: '已忽略',
|
||||||
@@ -73,6 +81,7 @@ export const ALERT_STATUS_LABEL: Record<string, string> = {
|
|||||||
|
|
||||||
export const ALERT_STATUS_OPTIONS = [
|
export const ALERT_STATUS_OPTIONS = [
|
||||||
{ value: '', label: '全部状态' },
|
{ value: '', label: '全部状态' },
|
||||||
|
{ value: 'active', label: '活跃' },
|
||||||
{ value: 'pending', label: '待处理' },
|
{ value: 'pending', label: '待处理' },
|
||||||
{ value: 'acknowledged', label: '已确认' },
|
{ value: 'acknowledged', label: '已确认' },
|
||||||
{ value: 'resolved', label: '已恢复' },
|
{ value: 'resolved', label: '已恢复' },
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||||
import { analysisApi, type AnalysisItem } from '../../api/ai/analysis';
|
import { analysisApi, type AnalysisItem } from '../../api/ai/analysis';
|
||||||
import { suggestionApi, type SuggestionItem } from '../../api/ai/suggestions';
|
import { suggestionApi, type SuggestionItem } from '../../api/ai/suggestions';
|
||||||
|
import { EntityName } from '../../components/EntityName';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -321,9 +322,9 @@ export default function AiAnalysisList() {
|
|||||||
dataIndex: 'patient_id',
|
dataIndex: 'patient_id',
|
||||||
key: 'patient_id',
|
key: 'patient_id',
|
||||||
width: 140,
|
width: 140,
|
||||||
render: (v: string) => (
|
render: (_: unknown, record: AnalysisItem) => (
|
||||||
<Link to={`/health/patients/${v}`} style={{ fontFamily: 'monospace', fontSize: 12 }}>
|
<Link to={`/health/patients/${record.patient_id}`}>
|
||||||
{v.slice(0, 8)}
|
<EntityName name={record.patient_name} id={record.patient_id} />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export default function AlertDashboard() {
|
|||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
患者: <EntityName id={alert.patient_id} />
|
患者: <EntityName name={alert.patient_name} id={alert.patient_id} />
|
||||||
{' · '}
|
{' · '}
|
||||||
{new Date(alert.created_at).toLocaleString('zh-CN')}
|
{new Date(alert.created_at).toLocaleString('zh-CN')}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|||||||
@@ -137,12 +137,11 @@ export default function AlertList() {
|
|||||||
dataIndex: 'patient_id',
|
dataIndex: 'patient_id',
|
||||||
key: 'patient_id',
|
key: 'patient_id',
|
||||||
width: 140,
|
width: 140,
|
||||||
render: (id: string) => (
|
render: (_: unknown, record: Alert) => (
|
||||||
<Link to={`/health/patients/${id}`}>
|
<Link to={`/health/patients/${record.patient_id}`}>
|
||||||
<EntityName
|
<EntityName
|
||||||
name={undefined}
|
name={record.patient_name}
|
||||||
id={id}
|
id={record.patient_id}
|
||||||
fallbackLabel={id.length > 8 ? id.slice(0, 8) + '...' : id}
|
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { StatusTag } from './components/StatusTag';
|
|||||||
import { ImagePreview } from './components/ImagePreview';
|
import { ImagePreview } from './components/ImagePreview';
|
||||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||||
import { AuthButton } from '../../components/AuthButton';
|
import { AuthButton } from '../../components/AuthButton';
|
||||||
|
import { EntityName } from '../../components/EntityName';
|
||||||
|
|
||||||
const PAGE_SIZE = 30;
|
const PAGE_SIZE = 30;
|
||||||
const POLL_INTERVAL = 10_000;
|
const POLL_INTERVAL = 10_000;
|
||||||
@@ -297,10 +298,10 @@ export default function ConsultationDetail() {
|
|||||||
{session && (
|
{session && (
|
||||||
<>
|
<>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
||||||
患者: {session.patient_id.slice(0, 8)}
|
患者: <EntityName name={session.patient_name} id={session.patient_id} />
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
|
||||||
医护: {session.doctor_id ? session.doctor_id.slice(0, 8) : '-'}
|
医护: <EntityName name={session.doctor_name} id={session.doctor_id} fallbackLabel="未分配" />
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<StatusTag status={session.status} />
|
<StatusTag status={session.status} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
import { deviceApi, type DeviceItem } from '../../api/health/devices';
|
import { deviceApi, type DeviceItem } from '../../api/health/devices';
|
||||||
import { DEVICE_TYPE_OPTIONS, DEVICE_TYPE_COLOR } from '../../constants/health';
|
import { DEVICE_TYPE_OPTIONS, DEVICE_TYPE_COLOR } from '../../constants/health';
|
||||||
|
import { PatientSelect } from './components/PatientSelect';
|
||||||
|
|
||||||
function formatTime(val?: string | null): string {
|
function formatTime(val?: string | null): string {
|
||||||
if (!val) return '-';
|
if (!val) return '-';
|
||||||
@@ -109,13 +110,13 @@ export default function DeviceManage() {
|
|||||||
<h2 style={{ marginBottom: 16 }}>设备管理</h2>
|
<h2 style={{ marginBottom: 16 }}>设备管理</h2>
|
||||||
|
|
||||||
<Space style={{ marginBottom: 16 }} wrap>
|
<Space style={{ marginBottom: 16 }} wrap>
|
||||||
<Input
|
<div style={{ width: 240 }}>
|
||||||
placeholder="患者 ID"
|
<PatientSelect
|
||||||
value={filterPatientId}
|
value={filterPatientId || undefined}
|
||||||
onChange={(e) => setFilterPatientId(e.target.value)}
|
onChange={(val) => setFilterPatientId(val || '')}
|
||||||
style={{ width: 200 }}
|
placeholder="搜索患者"
|
||||||
allowClear
|
/>
|
||||||
/>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
placeholder="设备类型"
|
placeholder="设备类型"
|
||||||
value={filterDeviceType}
|
value={filterDeviceType}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { useCountUp } from '../../../hooks/useCountUp';
|
|||||||
import HealthDataCenter from './HealthDataCenter';
|
import HealthDataCenter from './HealthDataCenter';
|
||||||
|
|
||||||
export function AdminDashboard() {
|
export function AdminDashboard() {
|
||||||
const { patientStats, followUpStats, healthDataStats, dialysisStats, loading } = useStatsData();
|
const { patientStats, followUpStats, healthDataStats, dialysisStats, doctorCount, loading } = useStatsData();
|
||||||
const patientCount = useCountUp(patientStats?.total_patients ?? 0);
|
const patientCount = useCountUp(patientStats?.total_patients ?? 0);
|
||||||
const appointmentCount = useCountUp(healthDataStats?.appointments?.this_month ?? 0);
|
const appointmentCount = useCountUp(healthDataStats?.appointments?.this_month ?? 0);
|
||||||
const doctorCount = useCountUp(0);
|
const doctorCountDisplay = useCountUp(doctorCount);
|
||||||
|
|
||||||
if (loading && !patientStats) return <Spin size="large" style={{ display: 'block', margin: '80px auto' }} />;
|
if (loading && !patientStats) return <Spin size="large" style={{ display: 'block', margin: '80px auto' }} />;
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export function AdminDashboard() {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} sm={8} lg={4}>
|
<Col xs={12} sm={8} lg={4}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="医护人数" value={doctorCount} prefix={<UserOutlined />} />
|
<Statistic title="医护人数" value={doctorCountDisplay} prefix={<UserOutlined />} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
type HealthDataStats,
|
type HealthDataStats,
|
||||||
type DialysisStatistics,
|
type DialysisStatistics,
|
||||||
} from '../../../api/health/points';
|
} from '../../../api/health/points';
|
||||||
|
import { doctorApi } from '../../../api/health/doctors';
|
||||||
|
|
||||||
export interface StatsData {
|
export interface StatsData {
|
||||||
patientStats: PatientStatistics | null;
|
patientStats: PatientStatistics | null;
|
||||||
@@ -16,6 +17,7 @@ export interface StatsData {
|
|||||||
pointsStats: PointsStatistics | null;
|
pointsStats: PointsStatistics | null;
|
||||||
healthDataStats: HealthDataStats | null;
|
healthDataStats: HealthDataStats | null;
|
||||||
dialysisStats: DialysisStatistics | null;
|
dialysisStats: DialysisStatistics | null;
|
||||||
|
doctorCount: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
@@ -31,6 +33,7 @@ export function useStatsData(): StatsData {
|
|||||||
const [pointsStats, setPointsStats] = useState<PointsStatistics | null>(null);
|
const [pointsStats, setPointsStats] = useState<PointsStatistics | null>(null);
|
||||||
const [healthDataStats, setHealthDataStats] = useState<HealthDataStats | null>(null);
|
const [healthDataStats, setHealthDataStats] = useState<HealthDataStats | null>(null);
|
||||||
const [dialysisStats, setDialysisStats] = useState<DialysisStatistics | null>(null);
|
const [dialysisStats, setDialysisStats] = useState<DialysisStatistics | null>(null);
|
||||||
|
const [doctorCount, setDoctorCount] = useState(0);
|
||||||
|
|
||||||
const fetchAllStats = useCallback(async () => {
|
const fetchAllStats = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -56,9 +59,14 @@ export function useStatsData(): StatsData {
|
|||||||
tryFetch(pointsApi.getStatistics, setPointsStats, '积分'),
|
tryFetch(pointsApi.getStatistics, setPointsStats, '积分'),
|
||||||
tryFetch(pointsApi.getHealthDataStats, setHealthDataStats, '健康数据'),
|
tryFetch(pointsApi.getHealthDataStats, setHealthDataStats, '健康数据'),
|
||||||
tryFetch(pointsApi.getDialysisStats, setDialysisStats, '透析'),
|
tryFetch(pointsApi.getDialysisStats, setDialysisStats, '透析'),
|
||||||
|
tryFetch(
|
||||||
|
async () => { const r = await doctorApi.list({ page: 1, page_size: 1 }); return r.total; },
|
||||||
|
setDoctorCount,
|
||||||
|
'医护',
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (hasAnyError && errors.length === 6) {
|
if (hasAnyError && errors.length === 7) {
|
||||||
setError('加载统计数据失败');
|
setError('加载统计数据失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +78,7 @@ export function useStatsData(): StatsData {
|
|||||||
}, [fetchAllStats]);
|
}, [fetchAllStats]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
patientStats, consultationStats, followUpStats, pointsStats, healthDataStats, dialysisStats,
|
patientStats, consultationStats, followUpStats, pointsStats, healthDataStats, dialysisStats, doctorCount,
|
||||||
loading, error, refresh: fetchAllStats,
|
loading, error, refresh: fetchAllStats,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,8 +291,46 @@ where
|
|||||||
.analysis
|
.analysis
|
||||||
.list_analysis(ctx.tenant_id, params.patient_id, params.analysis_type, &pagination)
|
.list_analysis(ctx.tenant_id, params.patient_id, params.analysis_type, &pagination)
|
||||||
.await?;
|
.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!({
|
Ok(Json(ApiResponse::ok(serde_json::json!({
|
||||||
"data": items,
|
"data": data,
|
||||||
"total": total,
|
"total": total,
|
||||||
"page": pagination.page.unwrap_or(1),
|
"page": pagination.page.unwrap_or(1),
|
||||||
"page_size": pagination.limit(),
|
"page_size": pagination.limit(),
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ pub struct AcknowledgeAlertRequest {
|
|||||||
pub struct AlertResponse {
|
pub struct AlertResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub patient_id: Uuid,
|
pub patient_id: Uuid,
|
||||||
|
pub patient_name: Option<String>,
|
||||||
pub rule_id: Uuid,
|
pub rule_id: Uuid,
|
||||||
pub severity: String,
|
pub severity: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use erp_core::error::check_version;
|
use erp_core::error::check_version;
|
||||||
|
|
||||||
|
use crate::dto::alert_dto::AlertResponse;
|
||||||
use crate::entity::alerts;
|
use crate::entity::alerts;
|
||||||
use crate::entity::patient_doctor_relation;
|
use crate::entity::{patient, patient_doctor_relation};
|
||||||
use crate::error::{HealthError, HealthResult};
|
use crate::error::{HealthError, HealthResult};
|
||||||
use crate::service::validation;
|
use crate::service::validation;
|
||||||
use crate::state::HealthState;
|
use crate::state::HealthState;
|
||||||
@@ -20,7 +21,7 @@ pub async fn list_alerts(
|
|||||||
status: Option<&str>,
|
status: Option<&str>,
|
||||||
page: u64,
|
page: u64,
|
||||||
page_size: u64,
|
page_size: u64,
|
||||||
) -> HealthResult<(Vec<alerts::Model>, u64)> {
|
) -> HealthResult<(Vec<AlertResponse>, u64)> {
|
||||||
let limit = page_size.min(100);
|
let limit = page_size.min(100);
|
||||||
let offset = page.saturating_sub(1) * limit;
|
let offset = page.saturating_sub(1) * limit;
|
||||||
|
|
||||||
@@ -32,7 +33,6 @@ pub async fn list_alerts(
|
|||||||
if let Some(did) = doctor_id {
|
if let Some(did) = doctor_id {
|
||||||
let patient_ids = get_patient_ids_for_doctor(&state.db, tenant_id, did).await?;
|
let patient_ids = get_patient_ids_for_doctor(&state.db, tenant_id, did).await?;
|
||||||
if patient_ids.is_empty() {
|
if patient_ids.is_empty() {
|
||||||
// 没有管床患者 → 直接返回空结果
|
|
||||||
return Ok((vec![], 0));
|
return Ok((vec![], 0));
|
||||||
}
|
}
|
||||||
query = query.filter(alerts::Column::PatientId.is_in(patient_ids));
|
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 total = query.clone().count(&state.db).await?;
|
||||||
let items = query
|
let models = query
|
||||||
.order_by_desc(alerts::Column::CreatedAt)
|
.order_by_desc(alerts::Column::CreatedAt)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.all(&state.db)
|
.all(&state.db)
|
||||||
.await?;
|
.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))
|
Ok((items, total))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,8 +142,43 @@ pub async fn list_sessions(
|
|||||||
.all(&state.db)
|
.all(&state.db)
|
||||||
.await?;
|
.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 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 })
|
Ok(PaginatedResponse { data, total, page, page_size: limit, total_pages })
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user