feat(health+mp): S2-3 Patient DTO 最小化
后端: - 新增 PatientSummary DTO(id/name/gender/birth_date/status 5 字段) - 新增 GET /health/patients/summary 端点(权限 health.patient.list) - patient_service::list_summaries 仅查询非敏感字段 前端: - 新增 PatientSummary 类型 + getPatientSummaries() API - auth store loadPatients 改用 summary 端点 - setCurrentPatient 仅存储非敏感字段到 secureSet
This commit is contained in:
@@ -57,3 +57,18 @@ export async function getPatients() {
|
||||
const res = await api.get<PaginatedData<PatientInfo>>('/health/patients');
|
||||
return Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : []);
|
||||
}
|
||||
|
||||
/** 患者摘要 — 列表用,字段最小化,不含敏感信息 */
|
||||
export interface PatientSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
gender?: string;
|
||||
birth_date?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/** 获取患者摘要列表(字段最小化,替代 getPatients) */
|
||||
export async function getPatientSummaries() {
|
||||
const res = await api.get<PaginatedData<PatientSummary>>('/health/patients/summary');
|
||||
return Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : []);
|
||||
}
|
||||
|
||||
@@ -222,16 +222,30 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
setCurrentPatient: (patient) => {
|
||||
secureSet('current_patient_id', patient.id);
|
||||
secureSet('current_patient', JSON.stringify(patient));
|
||||
setCachedPatientId(patient.id);
|
||||
const safePatient: authApi.PatientInfo = {
|
||||
id: patient.id,
|
||||
name: patient.name,
|
||||
gender: patient.gender,
|
||||
birth_date: patient.birth_date,
|
||||
relation: patient.relation,
|
||||
};
|
||||
secureSet('current_patient_id', safePatient.id);
|
||||
secureSet('current_patient', JSON.stringify(safePatient));
|
||||
setCachedPatientId(safePatient.id);
|
||||
clearRequestCache();
|
||||
set({ currentPatient: patient });
|
||||
set({ currentPatient: safePatient });
|
||||
},
|
||||
|
||||
loadPatients: async () => {
|
||||
try {
|
||||
const patients = await authApi.getPatients();
|
||||
const summaries = await authApi.getPatientSummaries();
|
||||
const patients: authApi.PatientInfo[] = summaries.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
gender: p.gender,
|
||||
birth_date: p.birth_date,
|
||||
relation: 'self',
|
||||
}));
|
||||
set({ patients });
|
||||
if (patients.length > 0 && !get().currentPatient) {
|
||||
get().setCurrentPatient(patients[0]);
|
||||
|
||||
@@ -246,3 +246,13 @@ pub struct ReferResultResp {
|
||||
pub from_doctor_id: Option<Uuid>,
|
||||
pub to_doctor_id: Uuid,
|
||||
}
|
||||
|
||||
/// 患者摘要 — 列表/切换用,不含敏感字段
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PatientSummary {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
use crate::dto::DeleteWithVersion;
|
||||
use crate::dto::patient_dto::{
|
||||
BatchImportPatientReq, BatchResultResp, BindByPhoneReq, BindResultResp, CreatePatientReq,
|
||||
FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp, ReferPatientReq,
|
||||
FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp, PatientSummary, ReferPatientReq,
|
||||
ReferResultResp, UpdatePatientReq,
|
||||
};
|
||||
use crate::service::patient_service;
|
||||
@@ -57,6 +57,23 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
/// GET /health/patients/summary — 患者摘要列表(字段最小化,不含敏感信息)
|
||||
pub async fn list_patient_summaries<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Query(params): Query<PatientListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<PatientSummary>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.list")?;
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20).min(100);
|
||||
let result = patient_service::list_summaries(&state, ctx.tenant_id, page, page_size).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
pub async fn create_patient<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -8,6 +8,10 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Router::new()
|
||||
.route(
|
||||
"/health/patients/summary",
|
||||
axum::routing::get(patient_handler::list_patient_summaries),
|
||||
)
|
||||
.route(
|
||||
"/health/patients",
|
||||
axum::routing::get(patient_handler::list_patients)
|
||||
|
||||
@@ -550,3 +550,47 @@ pub async fn bind_by_phone(
|
||||
patient_name: updated.name,
|
||||
})
|
||||
}
|
||||
|
||||
/// 患者摘要列表 — 仅返回非敏感字段,供小程序切换/列表使用
|
||||
pub async fn list_summaries(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
page: u64,
|
||||
page_size: u64,
|
||||
) -> HealthResult<PaginatedResponse<PatientSummary>> {
|
||||
let limit = page_size.min(100);
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
|
||||
let query = patient::Entity::find()
|
||||
.filter(patient::Column::TenantId.eq(tenant_id))
|
||||
.filter(patient::Column::DeletedAt.is_null());
|
||||
|
||||
let total = query.clone().count(&state.db).await?;
|
||||
|
||||
let models = query
|
||||
.order_by_desc(patient::Column::CreatedAt)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.all(&state.db)
|
||||
.await?;
|
||||
|
||||
let total_pages = total.div_ceil(limit.max(1));
|
||||
let data: Vec<PatientSummary> = models
|
||||
.into_iter()
|
||||
.map(|m| PatientSummary {
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
gender: m.gender,
|
||||
birth_date: m.birth_date,
|
||||
status: m.status,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(PaginatedResponse {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
page_size: limit,
|
||||
total_pages,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ mod tag;
|
||||
// 从各子模块重新导出所有公开函数,保持 handler 层调用路径不变
|
||||
pub use crud::{
|
||||
batch_import_patients, bind_by_phone, create_patient, delete_patient, get_patient,
|
||||
list_patients, update_patient,
|
||||
list_patients, list_summaries, update_patient,
|
||||
};
|
||||
pub use relation::{
|
||||
assign_doctor, create_family_member, delete_family_member, get_health_summary,
|
||||
|
||||
Reference in New Issue
Block a user