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');
|
const res = await api.get<PaginatedData<PatientInfo>>('/health/patients');
|
||||||
return Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : []);
|
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) => {
|
setCurrentPatient: (patient) => {
|
||||||
secureSet('current_patient_id', patient.id);
|
const safePatient: authApi.PatientInfo = {
|
||||||
secureSet('current_patient', JSON.stringify(patient));
|
id: patient.id,
|
||||||
setCachedPatientId(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();
|
clearRequestCache();
|
||||||
set({ currentPatient: patient });
|
set({ currentPatient: safePatient });
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPatients: async () => {
|
loadPatients: async () => {
|
||||||
try {
|
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 });
|
set({ patients });
|
||||||
if (patients.length > 0 && !get().currentPatient) {
|
if (patients.length > 0 && !get().currentPatient) {
|
||||||
get().setCurrentPatient(patients[0]);
|
get().setCurrentPatient(patients[0]);
|
||||||
|
|||||||
@@ -246,3 +246,13 @@ pub struct ReferResultResp {
|
|||||||
pub from_doctor_id: Option<Uuid>,
|
pub from_doctor_id: Option<Uuid>,
|
||||||
pub to_doctor_id: 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::DeleteWithVersion;
|
||||||
use crate::dto::patient_dto::{
|
use crate::dto::patient_dto::{
|
||||||
BatchImportPatientReq, BatchResultResp, BindByPhoneReq, BindResultResp, CreatePatientReq,
|
BatchImportPatientReq, BatchResultResp, BindByPhoneReq, BindResultResp, CreatePatientReq,
|
||||||
FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp, ReferPatientReq,
|
FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp, PatientSummary, ReferPatientReq,
|
||||||
ReferResultResp, UpdatePatientReq,
|
ReferResultResp, UpdatePatientReq,
|
||||||
};
|
};
|
||||||
use crate::service::patient_service;
|
use crate::service::patient_service;
|
||||||
@@ -57,6 +57,23 @@ where
|
|||||||
Ok(Json(ApiResponse::ok(result)))
|
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>(
|
pub async fn create_patient<S>(
|
||||||
State(state): State<HealthState>,
|
State(state): State<HealthState>,
|
||||||
Extension(ctx): Extension<TenantContext>,
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ where
|
|||||||
S: Clone + Send + Sync + 'static,
|
S: Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Router::new()
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/health/patients/summary",
|
||||||
|
axum::routing::get(patient_handler::list_patient_summaries),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/health/patients",
|
"/health/patients",
|
||||||
axum::routing::get(patient_handler::list_patients)
|
axum::routing::get(patient_handler::list_patients)
|
||||||
|
|||||||
@@ -550,3 +550,47 @@ pub async fn bind_by_phone(
|
|||||||
patient_name: updated.name,
|
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 层调用路径不变
|
// 从各子模块重新导出所有公开函数,保持 handler 层调用路径不变
|
||||||
pub use crud::{
|
pub use crud::{
|
||||||
batch_import_patients, bind_by_phone, create_patient, delete_patient, get_patient,
|
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::{
|
pub use relation::{
|
||||||
assign_doctor, create_family_member, delete_family_member, get_health_summary,
|
assign_doctor, create_family_member, delete_family_member, get_health_summary,
|
||||||
|
|||||||
Reference in New Issue
Block a user