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

17 KiB
Raw Blame History

预约时间自由选择 - 改动文档

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 当前预约创建流程

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

// 当前代码:根据时段类型计算固定时间
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

// 后端已有重叠判断逻辑(可复用)
bool isReserved = reservations?.Any(r =>
    r.start_time < slotEnd && r.end_time > slotStart) ?? false;

分析:后端逻辑已支持跨时段检测,问题在于前端只能选择单一时段。


3. 改动后方案

3.1 新的预约创建流程

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 时段重叠判断逻辑

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 改动

改动前代码

<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>

改动后代码

<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_timeend_time 参数,主要验证逻辑需确认:

// 添加预约时的冲突检测
[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

当前逻辑已正确支持跨时段检测:

// 判断预约是否与时段重叠
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 改动前数据流

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 改动后数据流

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. 附录:时段判断工具函数

/**
 * 判断时间范围跨越哪些时段
 * @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);   // ['凌晨']