diff --git a/API.md b/API.md new file mode 100644 index 0000000..5303b93 --- /dev/null +++ b/API.md @@ -0,0 +1,481 @@ +# API文档 + +URL 以及 接口结构仅供参考。无需完全对应 + +## 接口定义(JSON 请求 / 响应) + +### 1.1 发送验证码 `POST /api/v1/auth/send-code` +- `传入 手机号` +- **请求** +```json +{ + "phone": "18888888888" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "验证码已发送", + "data": { + "expiresIn": 300 + } +} +``` + +### 1.2 登录 `POST /api/v1/auth/login` +- `传入 手机号 验证码` +- **请求** +```json +{ + "phone": "18888888888", + "code": "123456" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "jwt-token", + "user": { + "id": "user_001", + "name": "操作员", + "role": "admin", + "permissions": [ + "view_revenue", + "view_exam", + "manage_meal_status", + "edit_notes" + ] + } + } +} +``` + +### 2.1 获取今日体检统计 `GET /api/v1/home/daily-stats` +- `获取:今日预约人数 已签到人数 在检人数 已打印导检单数量 已完成人数 已用餐人数` +- **响应** +```json +{ + "code": 200, + "data": { + "dailyAppointment": 80, + "signedIn": 60, + "inExam": 25, + "guidePrinted": 40, + "completed": 30, + "meal": 15 + } +} +``` + +### 3.1 获取营收统计(管理员)`GET /api/v1/home/revenue-stats` +- `获取:体检收入 加项收入 整体收入 目标收入` +- **响应** +```json +{ + "code": 200, + "data": { + "examRevenue": 86000, + "addonRevenue": 12400, + "totalRevenue": 98400, + "targetRevenue": 120000, + "gap": 21600 + } +} +``` + +### 4.1 获取 B1 数据 `GET /api/v1/home/b1-dashboard` +- `获取:当前客户总数 待检人数 在检人数` +- `列表:科室 医生 已检人数 已检部位数 总时长 平均时长` +- **响应** +```json +{ + "code": 200, + "data": { + "summary": { + "totalClients": 33, + "waiting": 10, + "inExam": 10 + }, + "rows": [ + { + "department": "B超1", + "doctor": "张医生", + "examinedCount": 6, + "examinedParts": 18, + "totalDuration": 90, + "avgDuration": 15, + "inExam": 2, + "waiting": 2 + } + ] + } +} +``` + +### 5.1 获取北3数据 `GET /api/v1/home/north3-dashboard` +- `获取:今日家医生数 分配客户数 面诊数` +- `列表:家医 面诊率 分配客户数 面诊数` +- **响应** +```json +{ + "code": 200, + "data": { + "summary": { + "totalDoctors": 3, + "totalAssigned": 45, + "consultations": 26 + }, + "rows": [ + { + "doctor": "刘医生", + "assignmentCount": 15, + "consultations": 9, + "consultRate": 0.6 + } + ] + } +} +``` + +### 7.1 获取体检客户列表 `GET /api/v1/exam/clients` +- `条件:时间段,如 8:00 ~ 12:00 到 12:00 ~ 17:00(或者上午0下午1)` +- `条件:用户等级,如高客 普客 散客 团客` +- `条件:登记情况,如已登记 未登记` +- `获取:套餐名称 渠道 状态 签到时间(耗时起始时间) 是否已签到 已加项数量 是否打印体检单` +- **查询参数** +```json +{ + "timeRange": "08:00-12:00", + "level": "高客", + "customerType": "团客", + "signStatus": "已登记", + "search": "张", + "page": 1, + "pageSize": 20 +} +``` +- **响应** +```json +{ + "code": 200, + "data": { + "total": 100, + "page": 1, + "pageSize": 20, + "list": [ + { + "id": "A001", + "name": "张伟", + "packageName": "高端入职体检套餐", + "channel": "团体客户", + "status": "体检中", + "signedIn": true, + "signTime": "2025-11-18 08:55", + "elapsed": "00:45", + "addonCount": 2, + "guidePrinted": true, + "timeSlot": "上午", + "level": "VIP", + "vipType": "高客", + "customerType": "团客" + } + ] + } +} +``` + +### 8.1 获取体检客户详情 `GET /api/v1/exam/clients/{clientId}` +- `基础信息:姓名 身份证 手机号 性别 年龄 客户级别 渠道 婚姻 家医 团标签` +- `体检进度:已查项目 未查项目 弃检项目 延期项目` +- `历史记录` +- `加项内容` +- `该次体检是否上传身份证(或已上传的身份证信息)` +- `该次体检是否已签名(或已上传的签名图片)` +- `体检知情同意书` +- `可选的加项列表(可以用字段表明已经支付后的加项)` +- `导检单PDF` +- **响应** +```json +{ + "code": 200, + "data": { + "id": "A001", + "basicInfo": { + "name": "张伟", + "idCard": "4401********1234", + "mobile": "137****9988", + "gender": "男", + "age": 35, + "level": "VIP", + "channel": "团检", + "maritalStatus": "未婚", + "familyDoctor": "李医生", + "groupTag": "团检" + }, + "progress": { + "checkedItems": ["签到", "更衣"], + "pendingItems": ["家医面诊"], + "abandonedItems": [], + "deferredItems": [] + }, + "history": [], + "addonSummary": [ + { + "name": "肿瘤标志物筛查", + "paid": true, + "tags": [ + { + "title": "热门", + "type": 1, + }, + { + "title": "肺结节筛查", + "type": 2, + }, + { + "title": "医生推荐", + "type": 3, + }, + { + "title": "渠道八折", + "type": 4, + } + ], + "originalPrice": "320.00", + "currentPrice": "240.00" + } + ], + "idCardUploaded": true, + "idCardImages": { + "front": "https://...", + "back": "https://..." + }, + "consentSigned": true, + "consentImage": "https://...", + "consentDocumentUrl": "https://...", + "addonOptions": [ + { + "id": "addon_001", + "name": "甲状腺彩超", + "price": 300, + "paid": false + } + ], + "guidePdfUrl": "https://example.com/guides/A001.pdf" + } +} +``` + +### 8.2 上传身份证 `POST /api/v1/exam/clients/{clientId}/id-card` +- `具体用户上传身份证正面、反面` +- **请求** +```json +{ + "frontImage": "base64-front", + "backImage": "base64-back" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "身份证上传成功" +} +``` + +### 8.5 上传体检同意书签名 `POST /api/v1/exam/clients/{clientId}/consent` +- `上传体检同意书签名图片` +- **请求** +```json +{ + "signatureImage": "base64-signature", + "documentVersion": "v1.0" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "签名已保存" +} +``` + +### 8.6 生成加项订单 `POST /api/v1/exam/clients/{clientId}/addon-orders` +- `选择加项内容后获取订单号以及支付二维码` +- **请求** +```json +{ + "addonIds": ["addon_001", "addon_002"], + "paymentMethod": "wechat_qr", // 如果是聚合码则取消 +} +``` +- **响应** +```json +{ + "code": 200, + "data": { + "orderId": "order_001", + "amount": 800, + "qrcodeUrl": "https://example.com/pay/order_001.png", + "expiresIn": 300 + } +} +``` + +### 8.7 查询订单支付状态 `GET /api/v1/payments/{orderId}` +- `根据订单号查询 是否已经支付成功(HTTP或SSE)` +- **响应** +```json +{ + "code": 200, + "data": { + "orderId": "order_001", + "status": "paid", + "paidAt": "2025-11-18T09:20:00+08:00" + } +} +``` +- **SSE(可选)** `GET /api/v1/payments/{orderId}/events` + - 服务端推送字段:`event`=`status`, `data`=`{"status":"paid"}`。 + +### 9.0 获取客服咨询信息 `GET /api/v1/support/info` +- `获取 标题 内容 图片` +- **响应** +```json +{ + "code": 200, + "data": { + "title": "客服咨询 · 圆圆客服台卡", + "content": "一站式健康服务说明", + "imageUrl": "https://example.com/assets/support-card.png" + } +} +``` + +### 9.1 获取用餐状态列表 `GET /api/v1/meals` +- `条件 全部 已用餐 未用餐 姓名 体检号` +- **查询参数** +```json +{ + "status": "all", + "clientName": "张", + "clientId": "A001" +} +``` +- **响应** +```json +{ + "code": 200, + "data": [ + { + "id": "A001", + "name": "张伟", + "status": "体检中", + "mealStatus": "done", + "mealTime": "2025-11-18 09:30" + } + ] +} +``` + +### 9.2 更新用餐状态 `POST /api/v1/meals/{clientId}` +- `更新指定用户用餐状态` +- **请求** +```json +{ + "mealStatus": "done" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "用餐状态已更新" +} +``` + +### 9.3 获取太平 VIP 认证二维码 `GET /api/v1/support/taiping-vip` +- `获取 标题 内容 图片` +- **响应** +```json +{ + "code": 200, + "data": { + "title": "太平 VIP 认证", + "content": "扫描二维码完成认证", + "imageUrl": "https://example.com/assets/taiping-vip.png" + } +} +``` + +### 10.1 获取报告寄送登记二维码 `GET /api/v1/support/report-delivery` +- `获取 客服名称 图片` +- **响应** +```json +{ + "code": 200, + "data": { + "serviceName": "圆圆客服", + "imageUrl": "https://example.com/assets/delivery-qrcode.png" + } +} +``` + +### 10.2 报告寄送登记 `POST /api/v1/support/report-delivery` +- `传入 收件人 手机号 地址` +- **请求** +```json +{ + "recipient": "李四", + "phone": "13800000000", + "address": "上海市浦东新区世纪大道100号10楼", + "remark": "加急寄送" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "寄送信息已保存", + "data": { + "deliveryId": "delivery_001" + } +} +``` + +### 10.3 保存操作员备注 `POST /api/v1/notes` +- `保存操作员备注内容` +- **请求** +```json +{ + "operatorId": "user_001", + "content": "客户希望下午完成全部项目" +} +``` +- **响应** +```json +{ + "code": 200, + "message": "备注已保存" +} +``` + +### 10.4 获取操作员备注 `GET /api/v1/notes` +- `获取操作员备注内容` +- **响应** +```json +{ + "code": 200, + "data": [ + { + "operatorId": "user_001", + "content": "客户希望下午完成全部项目", + "createdAt": "2025-11-18 10:00:00" + } + ] +} +``` \ No newline at end of file diff --git a/src/components/exam/ExamModal.tsx b/src/components/exam/ExamModal.tsx index dbf51f0..c27673f 100644 --- a/src/components/exam/ExamModal.tsx +++ b/src/components/exam/ExamModal.tsx @@ -30,49 +30,79 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps) }; return ( -
-
-
-
- {client.name} - 体检号:{client.id} - {client.level} +
+
+ + {/* Header 区域:增加了内边距 padding */} +
+
+ {/* 左侧:姓名 + VIP + 体检号 */} +
+ + {client.name} + + {/* VIP 徽章样式优化:紫色背景 + 左右内边距 */} + + VIP + + + 体检号:{client.id} + +
+ + {/* 右侧:仅保留关闭按钮 */} +
-
-
- {tabs.map((t) => { - const isActive = tab === t.key; - const isDone = tabDone[t.key]; - return ( - - ); - })} + ? 'bg-gray-50 text-gray-400 border-gray-100' + : 'bg-white text-gray-600 border-gray-200 hover:border-gray-300' + }`} + > + {t.label} + {/* 这里保留了原有逻辑,但微调了样式 */} + {t.key === 'addon' && (client.addonCount || 0) > 0 && ( + + ({client.addonCount}) + + )} + {((t.key === 'sign' && signDone) || (t.key === 'print' && printDone)) && ( + + )} + + ); + })} +
+ + {/* 按钮部分:将原本在 Tab 右侧的任何操作可以放这里,或者留空 */}
+
{tab === 'detail' && } {tab === 'sign' && } {tab === 'addon' && } {tab === 'print' && }
+
); @@ -101,32 +131,195 @@ const ExamSignPanel = () => (
); -const ExamAddonPanel = ({ client }: { client: ExamClient }) => ( -
-
-
当前套餐项目
-
    - {client.checkedItems.concat(client.pendingItems).map((item, idx) => ( -
  • - {item} - {client.checkedItems.includes(item) ? '已检查' : '未检查'} -
  • - ))} -
-
-
-
可选加项
-
- {['肿瘤标志物筛查', '甲状腺彩超', '骨密度检测'].map((label) => ( - - ))} +interface AddonTag { + title: string; + type: 1 | 2 | 3 | 4; // 1: 热门(红), 2: 普通(灰), 3: 医生推荐(蓝), 4: 折扣信息 +} + +interface AddonItem { + id?: string; + name: string; + paid?: boolean; + tags?: AddonTag[]; + originalPrice?: string; + currentPrice?: string; + price?: number; // 兼容 addonOptions 结构 +} + +const ExamAddonPanel = ({ client }: { client: ExamClient }) => { + // 从 client 获取加项选项数据 + const addonOptions = (client['addonOptions' as keyof ExamClient] as AddonItem[] | undefined) || []; + const addonSummary = (client['addonSummary' as keyof ExamClient] as AddonItem[] | undefined) || []; + + // 合并数据,优先使用 addonOptions,如果没有则使用 addonSummary + const allAddons: AddonItem[] = addonOptions.length > 0 + ? addonOptions.map(item => ({ + id: item.id || `addon_${item.name}`, + name: item.name, + paid: item.paid || false, + tags: item.tags || [], + originalPrice: item.originalPrice || (item.price ? item.price.toFixed(2) : '0.00'), + currentPrice: item.currentPrice || (item.price ? item.price.toFixed(2) : '0.00'), + })) + : addonSummary; + + const [selectedIds, setSelectedIds] = useState>( + new Set(allAddons.filter(item => item.paid).map(item => item.id || item.name)) + ); + + const maxSelect = 15; + const selectedCount = selectedIds.size; + + const toggleSelect = (id: string) => { + if (selectedIds.has(id)) { + setSelectedIds(prev => { + const next = new Set(prev); + next.delete(id); + return next; + }); + } else { + if (selectedCount < maxSelect) { + setSelectedIds(prev => new Set(prev).add(id)); + } + } + }; + + // 计算价格汇总 + const selectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name)); + const totalOriginal = selectedItems.reduce((sum, item) => { + return sum + parseFloat(item.originalPrice || item.currentPrice || '0'); + }, 0); + const totalCurrent = selectedItems.reduce((sum, item) => { + return sum + parseFloat(item.currentPrice || item.originalPrice || '0'); + }, 0); + const discount = totalOriginal - totalCurrent; + + // 获取标签样式 + const getTagStyle = (tag: AddonTag) => { + switch (tag.type) { + case 1: // 热门 + return 'bg-[#FDF0F0] text-[#BC4845]'; + case 3: // 医生推荐 + return 'bg-[#ECF0FF] text-[#6A6AE5]'; + case 4: // 折扣信息 + return 'bg-[#4C5460] text-[#F1F2F5]'; + default: // 2: 普通 + return 'bg-[#F1F2F5] text-[#464E5B]'; + } + }; + + // 获取折扣信息文字(从 tags 中提取 type 4 的标签,或计算折扣) + const getDiscountText = (item: AddonItem) => { + const discountTag = item.tags?.find(t => t.type === 4); + if (discountTag) return discountTag.title; + + const orig = parseFloat(item.originalPrice || '0'); + const curr = parseFloat(item.currentPrice || '0'); + if (orig > 0 && curr < orig) { + const percent = Math.round((curr / orig) * 100); + return `渠道 ${percent} 折`; + } + return '渠道价'; + }; + + return ( +
+ {/* 标题和说明 */} +
+

体检套餐加项选择

+
+
最多可选 {maxSelect} 项 · 一排 5 个
+
已勾选 {selectedCount} 项,自费加项费用按渠道折扣价结算。
+
+
+ + {/* 加项网格 */} +
+ {allAddons.map((item) => { + const id = item.id || item.name; + const isSelected = selectedIds.has(id); + const origPrice = parseFloat(item.originalPrice || '0'); + const currPrice = parseFloat(item.currentPrice || '0'); + + return ( +
toggleSelect(id)} + > + {/* 复选框 */} +
+ toggleSelect(id)} + onClick={(e) => e.stopPropagation()} + className='mt-0.5 w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500' + /> +
+ {/* 项目名称 */} +
{item.name}
+ + {/* 标签 */} + {item.tags && item.tags.length > 0 && ( +
+ {item.tags + .filter(t => t.type !== 4) // 折扣信息单独显示 + .map((tag, idx) => ( + + {tag.title} + + ))} +
+ )} +
+
+ + {/* 价格信息 */} +
+
+ {origPrice > 0 && origPrice > currPrice && ( + ¥{origPrice.toFixed(0)} + )} +
+ ¥{currPrice.toFixed(0)} + {getDiscountText(item)} +
+
+
+
+ ); + })} +
+ + {/* 底部汇总和支付 */} +
+
+
+ 加项原价合计: ¥{totalOriginal.toFixed(0)} +
+
+ 渠道折扣价: ¥{totalCurrent.toFixed(0)} + {discount > 0 && ( + 已优惠 ¥{discount.toFixed(0)} + )} +
+
结算方式: 个人支付 (微信 / 支付宝)
+
+
-
-
-); + ); +}; const ExamDetailInfo = ({ client }: { client: ExamClient }) => { const [phone, setPhone] = useState((client['mobile' as keyof ExamClient] as string | undefined) || '137****9988'); diff --git a/src/data/mockData.ts b/src/data/mockData.ts index fd05068..0653ca1 100644 --- a/src/data/mockData.ts +++ b/src/data/mockData.ts @@ -82,6 +82,128 @@ export const EXAM_CLIENTS: ExamClient[] = [ customerType: '团客', guidePrinted: true, addonCount: 2, + addonOptions: [ + { + id: 'addon_001', + name: '胸部 CT', + paid: true, + tags: [{ title: '热门', type: 1 }, { title: '肺结节筛查', type: 2 }], + originalPrice: '400.00', + currentPrice: '320.00', + }, + { + id: 'addon_002', + name: '肿瘤标志物组合', + paid: true, + tags: [{ title: '医生推荐', type: 3 }], + originalPrice: '300.00', + currentPrice: '260.00', + }, + { + id: 'addon_003', + name: '颈动脉超声', + paid: false, + tags: [{ title: '脑卒中风险', type: 2 }], + originalPrice: '260.00', + currentPrice: '220.00', + }, + { + id: 'addon_004', + name: '幽门螺杆菌检测', + paid: false, + tags: [{ title: '胃病筛查', type: 2 }], + originalPrice: '180.00', + currentPrice: '150.00', + }, + { + id: 'addon_005', + name: '骨密度检查', + paid: false, + tags: [{ title: '骨质疏松', type: 2 }], + originalPrice: '260.00', + currentPrice: '210.00', + }, + { + id: 'addon_006', + name: '心脏彩超', + paid: false, + tags: [{ title: '心功能评估', type: 2 }], + originalPrice: '380.00', + currentPrice: '320.00', + }, + { + id: 'addon_007', + name: '甲状腺功能全套', + paid: false, + tags: [{ title: '内分泌', type: 2 }], + originalPrice: '260.00', + currentPrice: '230.00', + }, + { + id: 'addon_008', + name: '颅脑核磁共振', + paid: false, + tags: [{ title: '高价值项目', type: 1 }], + originalPrice: '1200.00', + currentPrice: '980.00', + }, + { + id: 'addon_009', + name: '眼底照相 + 眼压', + paid: false, + tags: [{ title: '糖尿病并发症', type: 2 }], + originalPrice: '220.00', + currentPrice: '180.00', + }, + { + id: 'addon_010', + name: '女性宫颈癌筛查 (TCT+HPV)', + paid: false, + tags: [{ title: '女性建议加选', type: 3 }], + originalPrice: '600.00', + currentPrice: '520.00', + }, + { + id: 'addon_011', + name: '男性前列腺专项', + paid: false, + tags: [{ title: '男性专项', type: 2 }], + originalPrice: '260.00', + currentPrice: '220.00', + }, + { + id: 'addon_012', + name: '脑血管 CT (CTA)', + paid: false, + tags: [{ title: '高风险人群', type: 1 }], + originalPrice: '1500.00', + currentPrice: '1200.00', + }, + { + id: 'addon_013', + name: '肝纤维化评估', + paid: false, + tags: [{ title: '肝病风险', type: 2 }], + originalPrice: '360.00', + currentPrice: '300.00', + }, + { + id: 'addon_014', + name: '全身动脉硬化筛查', + paid: false, + tags: [{ title: '血管评估', type: 2 }], + originalPrice: '480.00', + currentPrice: '420.00', + }, + { + id: 'addon_015', + name: '睡眠呼吸监测', + paid: false, + tags: [{ title: '打鼾/睡眠差', type: 2 }], + originalPrice: '520.00', + currentPrice: '460.00', + }, + ], }, { id: 'A002', diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index 5b0c357..8cf2402 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -88,7 +88,7 @@ export const MainLayout = () => { operatorName={operatorName} onLoginClick={() => setLoginModalOpen(true)} /> -
+