# 预约时间自由选择 - 开发文档 > 文档版本:v1.0 > 更新日期:2024年 > 文档类型:开发人员版 --- ## 一、改动概述 ### 1.1 需求说明 1. 将固定时段选择(6小时一段)改为自由时间选择 2. 房间列表正确显示跨时段预约的占用状态 ### 1.2 影响范围评估 - **前端**:主要改动,需重构时间选择UI - **后端**:少量改动,逻辑已基本支持 --- ## 二、前端改动详情 ### 2.1 appointment-page.vue(主要改动) **文件路径**:`/uniapp/mahjong_group/pages/appointment/appointment-page.vue` #### 2.1.1 移除旧的时段选择代码 **需删除的部分**: ```vue {{ slot.label }} ``` ```javascript // 删除:固定时段计算函数 const calculateTimeFromSlot = () => { const date = new Date(selectedDate.value); let startHour, endHour; switch (selectedTimeSlot.value) { case 0: startHour = 0; endHour = 6; break; case 1: startHour = 6; endHour = 12; break; case 2: startHour = 12; endHour = 18; break; case 3: startHour = 18; endHour = 24; break; } // ... }; ``` #### 2.1.2 新增时间选择器代码 **Template部分**: ```vue ``` **Script部分**: ```javascript ``` **Style部分**: ```scss ``` ### 2.2 book-room-page.vue(验证,可能无需改动) **文件路径**:`/uniapp/mahjong_group/pages/appointment/book-room-page.vue` 当前时段显示已从后端获取数据,后端已支持跨时段检测,**理论上无需前端改动**。 **验证方法**: 1. 创建一个15:00-20:00的预约 2. 查看房间列表,确认下午和晚上都显示为已预约状态 如显示不正确,检查后端`SQRoomsServices.cs`中的时段状态返回逻辑。 --- ## 三、后端改动详情 ### 3.1 SQController.cs(验证/微调) **文件路径**:`/server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs` #### 3.1.1 AddSQReservation 接口验证 当前接口已支持接收`start_time`和`end_time`参数,主要验证: ```csharp [HttpPost("AddSQReservation")] public async Task AddSQReservation([FromBody] AddReservationDto dto) { // 验证时间有效性(建议新增) if (dto.end_time <= dto.start_time) { return BadRequest(new { code = 400, msg = "结束时间必须晚于开始时间" }); } // 验证最短时长(可选,如最少1小时) var duration = (dto.end_time - dto.start_time).TotalHours; if (duration < 1) { return BadRequest(new { code = 400, msg = "预约时长不能少于1小时" }); } // 验证最长时长(可选,如最多12小时) if (duration > 12) { return BadRequest(new { code = 400, msg = "预约时长不能超过12小时" }); } // ... 现有逻辑 } ``` ### 3.2 SQRoomsServices.cs(验证,无需改动) **文件路径**:`/server/CoreCms.Net.Services/SQ/SQRoomsServices.cs` 当前时段状态判断逻辑已正确支持跨时段检测: ```csharp // 现有代码(已支持跨时段) bool isReserved = reservations?.Any(r => r.start_time < slotEnd && r.end_time > slotStart) ?? false; ``` **逻辑验证**: | 预约时间 | 时段 | 判断公式 | 结果 | |:--------:|:----:|:--------:|:----:| | 15:00-20:00 | 下午(12-18) | 15 < 18 && 20 > 12 | true ✓ | | 15:00-20:00 | 晚上(18-24) | 15 < 24 && 20 > 18 | true ✓ | | 15:00-20:00 | 上午(6-12) | 15 < 12 | false ✓ | **结论**:后端逻辑无需改动。 --- ## 四、签到功能代码参考 ### 4.1 前端签到组件 **文件路径**:`/uniapp/mahjong_group/components/com/page/qiandao-popup.vue` **签到按钮显示条件**(reservation-item.vue): ```javascript // 签到按钮显示条件 const isQianDaoVisible = computed(() => { const item = props.reservation; // 1. 预约状态必须为 1(已锁定) if (item.status !== 1) return false; // 2. 只有发起者可签到 if (item.role !== 1) return false; // 3. 时间范围检查 const now = new Date(); const startTime = new Date(item.start_time); const endTime = new Date(item.end_time); const tenMinutes = 10 * 60 * 1000; const timeToStart = startTime.getTime() - now.getTime(); const timeToEnd = endTime.getTime() - now.getTime(); // 可签到:开始前10分钟内 或 已开始但未结束 return (timeToStart <= tenMinutes && timeToStart > 0) || (timeToStart <= 0 && timeToEnd > 0); }); ``` ### 4.2 后端签到接口 **文件路径**:`/server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs` **方法位置**:第1205-1431行 `CheckInReservation` **请求格式**: ```json POST /api/sq/checkinreservation { "reservation_id": 123, "attendeds": [ { "user_id": 2, "isAttended": true }, { "user_id": 3, "isAttended": false } ] } ``` **核心处理逻辑**: ```csharp // 1. 更新预约状态为"进行中" reservation.status = 2; // 2. 处理参与者 foreach (var attendee in dto.attendeds) { if (attendee.isAttended) { // 已到场:信誉+0.2,标记为已赴约 participant.is_arrive = 1; user.credit_score = Math.Min(5, user.credit_score + 0.2); } else { // 未到场:信誉-0.5,鸽子+1,踢出 participant.is_arrive = 2; participant.status = 1; // 已退出 user.credit_score = Math.Max(0, user.credit_score - 0.5); user.dove_count += 1; } } // 3. 处理押金退款 if (reservation.deposit_fee > 0) { // 已到场的参与者:发起退款 // 未到场的参与者:不退款 } ``` --- ## 五、数据库相关 ### 5.1 相关表结构 **SQReservations(预约表)**: | 字段 | 类型 | 说明 | |:----:|:----:|:----:| | id | int | 主键 | | status | int | 0=待开始,1=已锁定,2=进行中,3=已结束,4=已取消 | | start_time | datetime | 开始时间 | | end_time | datetime | 结束时间 | | room_id | int | 房间ID | | deposit_fee | decimal | 押金金额 | **SQReservationParticipants(参与者表)**: | 字段 | 类型 | 说明 | |:----:|:----:|:----:| | id | int | 主键 | | reservation_id | int | 预约ID | | user_id | int | 用户ID | | role | int | 0=参与者,1=发起者 | | status | int | 0=正常,1=已退出 | | is_arrive | int | 0=默认,1=已赴约,2=未赴约 | | is_refund | int | 退款状态 | ### 5.2 无需数据库改动 本次改动不涉及数据库结构变更,现有字段已满足需求: - `start_time` / `end_time` 已支持任意时间 - 时段状态在服务层动态计算 --- ## 六、测试用例 ### 6.1 时间选择功能测试 | 用例ID | 测试场景 | 输入 | 预期结果 | |:------:|:--------:|:----:|:--------:| | T001 | 正常选择 | 开始15:00,结束20:00 | 成功,显示"5小时",跨越"下午、晚上" | | T002 | 同时段 | 开始14:00,结束17:00 | 成功,显示"3小时",跨越"下午" | | T003 | 跨三时段 | 开始10:00,结束20:00 | 成功,显示"10小时",跨越"上午、下午、晚上" | | T004 | 时间倒置 | 开始20:00,结束15:00 | 失败,提示"结束时间必须晚于开始时间" | | T005 | 时间相同 | 开始15:00,结束15:00 | 失败,提示"结束时间必须晚于开始时间" | | T006 | 时长过短 | 开始15:00,结束15:30 | 失败,提示"预约时长不能少于1小时" | ### 6.2 时段冲突测试 | 用例ID | 已有预约 | 新预约 | 预期结果 | |:------:|:--------:|:------:|:--------:| | T101 | 15:00-20:00 | 10:00-14:00 | 成功(无重叠) | | T102 | 15:00-20:00 | 14:00-16:00 | 失败(重叠) | | T103 | 15:00-20:00 | 19:00-22:00 | 失败(重叠) | | T104 | 15:00-20:00 | 20:00-22:00 | 成功(边界无重叠) | | T105 | 15:00-20:00 | 12:00-22:00 | 失败(完全包含) | ### 6.3 房间列表显示测试 | 用例ID | 预约时间 | 凌晨 | 上午 | 下午 | 晚上 | |:------:|:--------:|:----:|:----:|:----:|:----:| | T201 | 02:00-05:00 | 🟠 | 🟢 | 🟢 | 🟢 | | T202 | 05:00-08:00 | 🟠 | 🟠 | 🟢 | 🟢 | | T203 | 10:00-14:00 | 🟢 | 🟠 | 🟠 | 🟢 | | T204 | 15:00-20:00 | 🟢 | 🟢 | 🟠 | 🟠 | | T205 | 08:00-22:00 | 🟢 | 🟠 | 🟠 | 🟠 | ### 6.4 签到功能测试 | 用例ID | 测试场景 | 预期结果 | |:------:|:--------:|:--------:| | T301 | 开始前11分钟签到 | 签到按钮不显示 | | T302 | 开始前10分钟签到 | 签到按钮显示,可签到 | | T303 | 开始后5分钟签到 | 签到按钮显示,可签到 | | T304 | 结束后签到 | 签到按钮不显示 | | T305 | 参与者尝试签到 | 签到按钮不显示 | | T306 | 重复签到 | 提示"已签到,无法重复签到" | --- ## 七、改动文件清单 ### 7.1 必须改动 | 序号 | 文件路径 | 改动类型 | 说明 | |:----:|:--------:|:--------:|:----:| | 1 | `pages/appointment/appointment-page.vue` | 修改 | 时间选择器重构 | ### 7.2 验证确认 | 序号 | 文件路径 | 改动类型 | 说明 | |:----:|:--------:|:--------:|:----:| | 2 | `pages/appointment/book-room-page.vue` | 验证 | 确认跨时段显示正确 | | 3 | `SQController.cs` | 验证 | 确认时间参数接收正确 | | 4 | `SQRoomsServices.cs` | 验证 | 确认跨时段检测正确 | ### 7.3 可选优化 | 序号 | 文件路径 | 改动类型 | 说明 | |:----:|:--------:|:--------:|:----:| | 5 | `SQController.cs` | 新增 | 添加时长限制验证 | --- ## 八、工具函数 ### 8.1 跨时段计算(前端) ```javascript /** * 判断时间范围跨越哪些时段 * @param {number} startHour 开始小时 (0-23) * @param {number} endHour 结束小时 (0-24) * @returns {Array} 跨越的时段数组 */ function getCrossedSlots(startHour, endHour) { const slots = []; const slotRanges = [ { name: '凌晨', start: 0, end: 6 }, { name: '上午', start: 6, end: 12 }, { name: '下午', start: 12, end: 18 }, { name: '晚上', start: 18, end: 24 } ]; for (const slot of slotRanges) { if (startHour < slot.end && endHour > slot.start) { slots.push(slot.name); } } return slots; } // 使用示例 getCrossedSlots(15, 20); // ['下午', '晚上'] getCrossedSlots(10, 14); // ['上午', '下午'] getCrossedSlots(2, 5); // ['凌晨'] ``` ### 8.2 时长格式化(前端) ```javascript /** * 格式化时长显示 * @param {number} minutes 分钟数 * @returns {string} 格式化后的时长 */ function formatDuration(minutes) { if (minutes <= 0) return '-'; const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (hours === 0) { return `${mins}分钟`; } if (mins === 0) { return `${hours}小时`; } return `${hours}小时${mins}分钟`; } ``` --- ## 九、注意事项 1. **向后兼容**:已有预约数据不受影响,仍可正常显示 2. **时间精度**:UniApp的time picker默认支持分钟级别选择 3. **时区处理**:前后端统一使用本地时间,避免时区转换问题 4. **边界条件**:特别注意24:00的处理(可视为次日00:00) 5. **并发控制**:创建预约时需考虑并发冲突,建议后端加锁