mahjong_group/docs/预约时间自由选择_改动文档 copy.md
2026-01-01 14:35:52 +08:00

557 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 预约时间自由选择 - 改动文档
## 1. 需求概述
### 1.1 当前问题
- 用户只能选择固定时段凌晨0-6、上午6-12、下午12-18、晚上18-24
- 无法灵活选择具体的开始和结束时间
- 跨时段预约显示不直观
### 1.2 改动目标
1. **时段显示优化**如果预约时间为15:00-20:00房间列表页应显示该房间"下午"和"晚上"都被预定
2. **自由时间选择**:创建预约界面允许用户自由选择开始时间和结束时间
---
## 2. 改动前分析
### 2.1 当前时段定义
```
时段类型 | 时间范围 | 对应值
--------|----------|-------
凌晨 | 00:00-06:00 | 0
上午 | 06:00-12:00 | 1
下午 | 12:00-18:00 | 2
晚上 | 18:00-24:00 | 3
```
### 2.2 当前预约创建流程
```mermaid
flowchart TD
A[用户选择日期] --> B[选择房间]
B --> C[显示可用时段]
C --> D{选择固定时段}
D -->|凌晨| E1[设置 00:00-06:00]
D -->|上午| E2[设置 06:00-12:00]
D -->|下午| E3[设置 12:00-18:00]
D -->|晚上| E4[设置 18:00-24:00]
E1 --> F[提交预约]
E2 --> F
E3 --> F
E4 --> F
F --> G[后端创建预约]
```
### 2.3 当前前端时间计算逻辑
**文件**: `pages/appointment/appointment-page.vue`
```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.4 当前房间时段状态显示
**文件**: `pages/appointment/book-room-page.vue`
```
┌─────────────────────────────────────────┐
│ 房间名称 │
├─────────────────────────────────────────┤
│ [凌晨] [上午] [下午] [晚上] │
│ 🟢 🟢 🟠 🟢 │
│ │
│ 🟢 可预约 🟠 已预约 ⚫ 不可用 🔵 使用中 │
└─────────────────────────────────────────┘
当前问题预约15:00-20:00时仅显示一个时段被占用
```
### 2.5 当前后端时段判断逻辑
**文件**: `CoreCms.Net.Services/SQ/SQRoomsServices.cs`
```csharp
// 后端已有重叠判断逻辑(可复用)
bool isReserved = reservations?.Any(r =>
r.start_time < slotEnd && r.end_time > slotStart) ?? false;
```
**分析**:后端逻辑已支持跨时段检测,问题在于前端只能选择单一时段。
---
## 3. 改动后方案
### 3.1 新的预约创建流程
```mermaid
flowchart TD
A[用户选择日期] --> B[选择房间]
B --> C[显示房间详情]
C --> D[选择开始时间]
D --> E[选择结束时间]
E --> F{时间校验}
F -->|结束时间 > 开始时间| G[计算跨越时段]
F -->|无效| H[提示错误]
G --> I[检查时段冲突]
I -->|无冲突| J[提交预约]
I -->|有冲突| K[提示时段已被预约]
J --> L[后端创建预约]
L --> M[更新房间时段显示]
```
### 3.2 新的时间选择UI设计
```
改动前(固定时段选择):
┌─────────────────────────────────────────┐
│ 选择时段 │
├─────────────────────────────────────────┤
│ ○ 凌晨 (00:00-06:00) │
│ ○ 上午 (06:00-12:00) │
│ ● 下午 (12:00-18:00) ← 只能单选 │
│ ○ 晚上 (18:00-24:00) │
└─────────────────────────────────────────┘
改动后(自由时间选择):
┌─────────────────────────────────────────┐
│ 选择时间 │
├─────────────────────────────────────────┤
│ 开始时间: [15:00 ▼] ← 时间选择器 │
│ 结束时间: [20:00 ▼] ← 时间选择器 │
│ │
│ 预计时长: 5小时 │
│ 跨越时段: 下午、晚上 │
│ │
│ 💡 提示:结束时间必须晚于开始时间 │
└─────────────────────────────────────────┘
```
### 3.3 新的房间列表时段显示
```
改动前15:00-20:00预约后:
┌─────────────────────────────────────────┐
│ [凌晨] [上午] [下午] [晚上] │
│ 🟢 🟢 🟠 🟢 ← 只显示下午 │
└─────────────────────────────────────────┘
改动后15:00-20:00预约后:
┌─────────────────────────────────────────┐
│ [凌晨] [上午] [下午] [晚上] │
│ 🟢 🟢 🟠 🟠 ← 下午+晚上 │
└─────────────────────────────────────────┘
```
### 3.4 时段重叠判断逻辑
```mermaid
flowchart LR
subgraph 预约时间 15:00-20:00
P1[15:00] --> P2[20:00]
end
subgraph 时段判断
S1[凌晨 0-6] --> R1{重叠?}
S2[上午 6-12] --> R2{重叠?}
S3[下午 12-18] --> R3{重叠?}
S4[晚上 18-24] --> R4{重叠?}
end
R1 -->|否| N1[🟢]
R2 -->|否| N2[🟢]
R3 -->|是| Y3[🟠]
R4 -->|是| Y4[🟠]
```
**重叠判断公式**
```
预约与时段重叠 = (预约开始时间 < 时段结束时间) AND (预约结束时间 > 时段开始时间)
示例预约15:00-20:00
- 凌晨(0-6): 15 < 6 = false → 不重叠 🟢
- 上午(6-12): 15 < 12 = false → 不重叠 🟢
- 下午(12-18): 15 < 18 = true AND 20 > 12 = true → 重叠 🟠
- 晚上(18-24): 15 < 24 = true AND 20 > 18 = true → 重叠 🟠
```
---
## 4. 需要改动的文件清单
### 4.1 前端改动
| 序号 | 文件路径 | 改动内容 | 复杂度 |
|------|----------|----------|--------|
| 1 | `pages/appointment/appointment-page.vue` | 将固定时段选择改为时间选择器 | ⭐⭐⭐ |
| 2 | `pages/appointment/book-room-page.vue` | 时段状态显示逻辑优化(可能无需改动,后端已支持) | ⭐ |
| 3 | `components/com/page/reservation-item.vue` | 预约详情显示时间格式调整 | ⭐ |
### 4.2 后端改动
| 序号 | 文件路径 | 改动内容 | 复杂度 |
|------|----------|----------|--------|
| 1 | `SQController.cs` | 接收自定义开始/结束时间参数 | ⭐⭐ |
| 2 | `SQRoomsServices.cs` | 时段状态判断逻辑已支持,可能需微调 | ⭐ |
| 3 | `SQReservationsServices.cs` | 预约冲突检测逻辑验证 | ⭐ |
---
## 5. 详细改动说明
### 5.1 appointment-page.vue 改动
#### 改动前代码
```vue
<template>
<!-- 时段选择单选按钮 -->
<view class="time-slot-selector">
<view v-for="slot in timeSlotOptions" :key="slot.value"
:class="['slot-item', selectedTimeSlot === slot.value ? 'active' : '']"
@click="selectTimeSlot(slot.value)">
{{ slot.label }}
</view>
</view>
</template>
<script setup>
const selectedTimeSlot = ref(null);
const timeSlotOptions = ref([]);
const calculateTimeFromSlot = () => {
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;
}
};
</script>
```
#### 改动后代码
```vue
<template>
<!-- 时间选择器 -->
<view class="time-picker-container">
<view class="time-picker-row">
<text class="label">开始时间</text>
<picker mode="time" :value="startTime" @change="onStartTimeChange">
<view class="picker-value">{{ startTime || '请选择' }}</view>
</picker>
</view>
<view class="time-picker-row">
<text class="label">结束时间</text>
<picker mode="time" :value="endTime" @change="onEndTimeChange">
<view class="picker-value">{{ endTime || '请选择' }}</view>
</picker>
</view>
<view class="time-info">
<text>预计时长: {{ calculateDuration() }}</text>
<text>跨越时段: {{ calculateCrossSlots() }}</text>
</view>
</view>
</template>
<script setup>
const startTime = ref('');
const endTime = ref('');
// 开始时间变更
const onStartTimeChange = (e) => {
startTime.value = e.detail.value;
validateTimeRange();
};
// 结束时间变更
const onEndTimeChange = (e) => {
endTime.value = e.detail.value;
validateTimeRange();
};
// 计算时长
const calculateDuration = () => {
if (!startTime.value || !endTime.value) return '-';
const start = parseTime(startTime.value);
const end = parseTime(endTime.value);
const hours = (end - start) / (1000 * 60 * 60);
return `${hours}小时`;
};
// 计算跨越的时段
const calculateCrossSlots = () => {
if (!startTime.value || !endTime.value) return '-';
const slots = [];
const startHour = parseInt(startTime.value.split(':')[0]);
const endHour = parseInt(endTime.value.split(':')[0]);
if (startHour < 6 || endHour > 0 && endHour <= 6) slots.push('凌晨');
if ((startHour < 12 && endHour > 6) || (startHour >= 6 && startHour < 12)) slots.push('上午');
if ((startHour < 18 && endHour > 12) || (startHour >= 12 && startHour < 18)) slots.push('下午');
if (endHour > 18 || startHour >= 18) slots.push('晚上');
return slots.join('、') || '-';
};
// 验证时间范围
const validateTimeRange = () => {
if (startTime.value && endTime.value) {
const start = parseTime(startTime.value);
const end = parseTime(endTime.value);
if (end <= start) {
uni.showToast({ title: '结束时间必须晚于开始时间', icon: 'none' });
return false;
}
}
return true;
};
// 构建预约数据(提交时)
const buildReservationData = () => {
const date = new Date(selectedDate.value);
const [startH, startM] = startTime.value.split(':').map(Number);
const [endH, endM] = endTime.value.split(':').map(Number);
const startDateTime = new Date(date);
startDateTime.setHours(startH, startM, 0, 0);
const endDateTime = new Date(date);
endDateTime.setHours(endH, endM, 0, 0);
return {
start_time: startDateTime.toISOString(),
end_time: endDateTime.toISOString(),
// ... 其他字段
};
};
</script>
```
### 5.2 后端 SQController.cs 改动
#### 改动说明
后端接口已支持接收 `start_time``end_time` 参数,主要验证逻辑需确认:
```csharp
// 添加预约时的冲突检测
[HttpPost("AddSQReservation")]
public async Task<IActionResult> AddSQReservation([FromBody] AddReservationRequest request)
{
// 验证时间有效性
if (request.end_time <= request.start_time)
{
return BadRequest("结束时间必须晚于开始时间");
}
// 检查时段冲突(已有逻辑)
var conflicts = await _reservationService.CheckTimeConflict(
request.room_id,
request.start_time,
request.end_time
);
if (conflicts.Any())
{
return BadRequest("所选时段已被预约");
}
// 创建预约...
}
```
### 5.3 后端时段显示逻辑验证
**文件**: `SQRoomsServices.cs`
当前逻辑已正确支持跨时段检测:
```csharp
// 判断预约是否与时段重叠
bool isReserved = reservations?.Any(r =>
r.start_time < slotEnd && r.end_time > slotStart) ?? false;
```
**验证场景**
- 预约 15:00-20:00
- 下午时段(12:00-18:00): `15:00 < 18:00 && 20:00 > 12:00` = true ✅
- 晚上时段(18:00-24:00): `15:00 < 24:00 && 20:00 > 18:00` = true ✅
**结论**:后端逻辑无需改动,已支持跨时段显示。
---
## 6. 数据流对比
### 6.1 改动前数据流
```mermaid
sequenceDiagram
participant U as 用户
participant F as 前端
participant B as 后端
participant DB as 数据库
U->>F: 选择日期
U->>F: 选择房间
F->>B: 获取房间时段状态
B->>DB: 查询已有预约
DB-->>B: 返回预约列表
B-->>F: 返回4个时段状态
F-->>U: 显示可选时段
U->>F: 选择"下午"时段
F->>F: calculateTimeFromSlot()
Note over F: 固定设置 12:00-18:00
F->>B: 提交预约(12:00-18:00)
B->>DB: 保存预约
DB-->>B: 保存成功
B-->>F: 返回成功
F-->>U: 预约成功
```
### 6.2 改动后数据流
```mermaid
sequenceDiagram
participant U as 用户
participant F as 前端
participant B as 后端
participant DB as 数据库
U->>F: 选择日期
U->>F: 选择房间
F->>B: 获取房间时段状态
B->>DB: 查询已有预约
DB-->>B: 返回预约列表
B-->>F: 返回4个时段状态
F-->>U: 显示时间选择器
U->>F: 选择开始时间 15:00
U->>F: 选择结束时间 20:00
F->>F: validateTimeRange()
F->>F: calculateCrossSlots()
Note over F: 显示"下午、晚上"
F->>B: 提交预约(15:00-20:00)
B->>B: 检查时段冲突
B->>DB: 保存预约
DB-->>B: 保存成功
B-->>F: 返回成功
F-->>U: 预约成功
```
---
## 7. 测试用例
### 7.1 时间选择测试
| 测试场景 | 开始时间 | 结束时间 | 预期结果 |
|----------|----------|----------|----------|
| 正常选择 | 15:00 | 20:00 | 成功,显示跨越"下午、晚上" |
| 同时段选择 | 14:00 | 17:00 | 成功,显示跨越"下午" |
| 跨三时段 | 10:00 | 20:00 | 成功,显示跨越"上午、下午、晚上" |
| 无效时间 | 20:00 | 15:00 | 失败,提示"结束时间必须晚于开始时间" |
| 相同时间 | 15:00 | 15:00 | 失败,提示"结束时间必须晚于开始时间" |
### 7.2 时段冲突测试
| 已有预约 | 新预约 | 预期结果 |
|----------|--------|----------|
| 15:00-20:00 | 10:00-14:00 | 成功(无重叠) |
| 15:00-20:00 | 14:00-16:00 | 失败(重叠) |
| 15:00-20:00 | 19:00-22:00 | 失败(重叠) |
| 15:00-20:00 | 20:00-22:00 | 成功(边界无重叠) |
### 7.3 房间列表显示测试
| 预约时间 | 凌晨 | 上午 | 下午 | 晚上 |
|----------|------|------|------|------|
| 02:00-05:00 | 🟠 | 🟢 | 🟢 | 🟢 |
| 10:00-14:00 | 🟢 | 🟠 | 🟠 | 🟢 |
| 15:00-20:00 | 🟢 | 🟢 | 🟠 | 🟠 |
| 22:00-04:00 | 🟠 | 🟢 | 🟢 | 🟠 |
---
## 8. 工作量评估
| 模块 | 工作内容 | 复杂度 |
|------|----------|--------|
| 前端-预约页面 | 时间选择器组件替换、时间计算逻辑 | ⭐⭐⭐ |
| 前端-房间列表 | 验证显示逻辑(可能无需改动) | ⭐ |
| 前端-预约详情 | 时间格式显示调整 | ⭐ |
| 后端-控制器 | 参数验证、冲突检测 | ⭐⭐ |
| 后端-服务层 | 逻辑验证(可能无需改动) | ⭐ |
| 测试 | 各场景测试 | ⭐⭐ |
**总体评估**中等复杂度改动主要工作集中在前端预约页面的UI和逻辑重构。
---
## 9. 风险与注意事项
1. **向后兼容**:已有的固定时段预约数据仍可正常显示
2. **边界条件**跨天预约如23:00-02:00需特殊处理
3. **时间精度**建议时间选择以30分钟为最小单位
4. **用户体验**:需添加明确的时长提示和时段跨越提示
5. **数据验证**:前后端都需要进行时间有效性验证
---
## 10. 附录:时段判断工具函数
```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) {
// 重叠判断:预约开始 < 时段结束 AND 预约结束 > 时段开始
if (startHour < slot.end && endHour > slot.start) {
slots.push(slot.name);
}
}
return slots;
}
// 使用示例
getCrossedSlots(15, 20); // ['下午', '晚上']
getCrossedSlots(10, 14); // ['上午', '下午']
getCrossedSlots(2, 5); // ['凌晨']
```