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 (
-
);
@@ -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)}
+ )}
+
+
结算方式: 个人支付 (微信 / 支付宝)
+
+
+ 确认支付 ¥{totalCurrent.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)}
/>
-
+