fix(health+web): PII 解密日志 + 负年龄防护 + 随访页面中文 placeholder
- helper.rs: 提取 decrypt_field 辅助函数,解密失败时输出 warn 日志而非静默返回 None - format.ts: calcAge 负年龄(未来出生日期)返回 '--' 而非 '-72岁' - FollowUpTaskList.tsx: DatePicker.RangePicker 添加中文 placeholder Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -344,6 +344,7 @@ export default function FollowUpTaskList() {
|
||||
/>
|
||||
<DatePicker.RangePicker
|
||||
style={{ width: 240 }}
|
||||
placeholder={['开始日期', '结束日期']}
|
||||
onChange={(dates) => {
|
||||
if (dates && dates[0] && dates[1]) {
|
||||
setFilters((prev) => ({
|
||||
|
||||
@@ -12,5 +12,5 @@ export const formatRelative = (v: string | null | undefined): string =>
|
||||
export const calcAge = (birthDate: string | null | undefined): string => {
|
||||
if (!birthDate) return '--';
|
||||
const age = dayjs().diff(dayjs(birthDate), 'year');
|
||||
return `${age}岁`;
|
||||
return age >= 0 ? `${age}岁` : '--';
|
||||
};
|
||||
|
||||
@@ -56,34 +56,20 @@ pub(crate) fn model_to_resp(m: patient::Model) -> PatientResp {
|
||||
/// 详情用 — 解密 Tier 1 字段
|
||||
pub(crate) fn model_to_resp_decrypted(crypto: &PiiCrypto, m: patient::Model) -> PatientResp {
|
||||
let kek = crypto.kek();
|
||||
let decrypted_id_number = m
|
||||
.id_number
|
||||
.as_ref()
|
||||
.map(|enc| pii::decrypt(kek, enc))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
let decrypted_allergy = m
|
||||
.allergy_history
|
||||
.as_ref()
|
||||
.map(|enc| pii::decrypt(kek, enc))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
let decrypted_medical = m
|
||||
.medical_history_summary
|
||||
.as_ref()
|
||||
.map(|enc| pii::decrypt(kek, enc))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
let decrypted_phone = m
|
||||
.emergency_contact_phone
|
||||
.as_ref()
|
||||
.map(|enc| pii::decrypt(kek, enc))
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
let decrypted_id_number = decrypt_field(kek, &m.id_number, "id_number", m.id);
|
||||
let decrypted_allergy = decrypt_field(kek, &m.allergy_history, "allergy_history", m.id);
|
||||
let decrypted_medical = decrypt_field(
|
||||
kek,
|
||||
&m.medical_history_summary,
|
||||
"medical_history_summary",
|
||||
m.id,
|
||||
);
|
||||
let decrypted_phone = decrypt_field(
|
||||
kek,
|
||||
&m.emergency_contact_phone,
|
||||
"emergency_contact_phone",
|
||||
m.id,
|
||||
);
|
||||
PatientResp {
|
||||
id: m.id,
|
||||
user_id: m.user_id,
|
||||
@@ -105,3 +91,22 @@ pub(crate) fn model_to_resp_decrypted(crypto: &PiiCrypto, m: patient::Model) ->
|
||||
version: m.version,
|
||||
}
|
||||
}
|
||||
|
||||
/// 解密单个 PII 字段,失败时输出 warn 日志并返回 None
|
||||
fn decrypt_field(
|
||||
kek: &[u8; 32],
|
||||
field: &Option<String>,
|
||||
name: &str,
|
||||
patient_id: Uuid,
|
||||
) -> Option<String> {
|
||||
field
|
||||
.as_ref()
|
||||
.map(|enc| pii::decrypt(kek, enc))
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
tracing::warn!(patient_id = %patient_id, field = name, error = %e, "PII decrypt failed");
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user