# P9→NS1/RNS1 缺失项 #19:客户详情页电话号码的脱敏展示 ## 简要结论 - 状态:✅ 已解决 - 风险等级:🟢 低 - 后端已实现 `_mask_phone()` 脱敏函数(中间 4 位用 `****`),前端实现了"点击查看/复制"交互 ## 详细审查 ### 审查范围 - `apps/backend/app/services/customer_service.py` — `_mask_phone()` 函数、`get_customer_detail()` 返回 - `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.wxml` — 电话展示区域 - `apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts` — 电话交互逻辑 - `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` — 同样的脱敏逻辑 ### 发现 1. 后端 `_mask_phone()` 实现:`phone[:3] + "****" + phone[-4:]`,如 `139****5678` 2. 后端 `get_customer_detail()` 同时返回 `phone`(脱敏)和 `phone_full`(完整),供前端按需展示 3. customer-detail 前端实现了 `phoneVisible` 状态切换: - 默认显示脱敏号码 `138****5678` - 点击"查看"按钮切换为完整号码 - 显示完整号码后按钮变为"复制",调用 `wx.setClipboardData` 4. customer-service-records 页面也实现了相同的脱敏+查看交互: - 前端自行脱敏:`detail.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')` - 同样有 `onTogglePhone()` 和 `onCopyPhone()` 方法 ### 证据 ```python # customer_service.py def _mask_phone(phone: str | None) -> str: """手机号脱敏:139****5678 格式。""" if not phone or len(phone) < 7: return phone or "" return f"{phone[:3]}****{phone[-4:]}" ``` ```html {{phoneVisible ? detail.phone : '138****5678'}} {{phoneVisible ? '复制' : '查看'}} ``` ### 建议 - customer-detail.wxml 中脱敏号码硬编码为 `138****5678`,应改为使用后端返回的 `phone` 字段(已脱敏) - 建议统一脱敏策略:要么全部由后端脱敏,要么全部由前端脱敏,避免两端重复实现