mahjong_group/历史需求/前端对接文档_预约系统.md
2025-12-07 21:17:34 +08:00

1008 lines
27 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.

# 预约系统前端对接文档
## 📌 文档说明
**版本**v2.0
**更新日期**2025-12-06
**适用范围**:预约房间功能前端开发
**后端接口版本**:预约时段优化版
---
## 📋 目录
1. [业务流程概述](#业务流程概述)
2. [页面结构](#页面结构)
3. [接口调用流程](#接口调用流程)
4. [接口详细说明](#接口详细说明)
5. [数据结构定义](#数据结构定义)
6. [前端实现指南](#前端实现指南)
7. [常见问题FAQ](#常见问题faq)
8. [调试技巧](#调试技巧)
---
## 业务流程概述
### 用户预约完整流程
```
┌─────────────────┐
│ 进入预约模块 │
└────────┬────────┘
┌─────────────────┐
│ 【房间展示页】 │
│ 1. 选择日期 │
│ 2. 查看房间列表 │
│ 3. 查看时段状态 │
└────────┬────────┘
│ 点击【预约】
┌─────────────────┐
│ 【预约页】 │
│ 1. 选择时段 │
│ 2. 填写人数 │
│ 3. 设置鸽子费 │
│ 4. 填写其他信息 │
└────────┬────────┘
│ 点击【提交】
┌─────────────────┐
│ 创建预约 │
│ - 成功:跳转列表 │
│ - 失败:显示错误 │
└─────────────────┘
```
### 核心概念
#### 时段定义
| 时段类型 | 时段名称 | 时间范围 | 代码值 |
|---------|---------|---------|--------|
| 凌晨 | Dawn | 00:00-05:59 | 0 |
| 上午 | Morning | 06:00-11:59 | 1 |
| 下午 | Afternoon | 12:00-17:59 | 2 |
| 晚上 | Evening | 18:00-23:59 | 3 |
#### 时段状态
| 状态值 | 含义 | 展示方式 | 可否预约 |
|-------|------|---------|---------|
| available | 可预约 | 绿色✓ | 是 |
| reserved | 已预约 | 红色✗ | 否 |
| unavailable | 不可用 | 灰色🚫 | 否 |
| using | 使用中 | 蓝色🔵 | 否 |
---
## 页面结构
### 页面1房间展示页
**页面元素**
```
┌──────────────────────────────────────┐
│ 预约房间 │
├──────────────────────────────────────┤
│ [ 今天 ] [ 明天 ] [ 12/08 ] ... │ ← 日期选择器
├──────────────────────────────────────┤
│ [ ] 只显示当前时段可用房间 │ ← 筛选开关
├──────────────────────────────────────┤
│ ┌────────────────────────────────┐ │
│ │ 101包间 [豪华包间] │ │
│ │ 📷 [房间图片] │ │
│ │ 💰 标准价80元/时段 │ │
│ │ 会员价60元/时段 │ │
│ │ 时段状态: │ │
│ │ 凌晨✓ 上午✗ 下午✓ 晚上🔵 │ │
│ │ [ 预约 ] 按钮 │ │
│ └────────────────────────────────┘ │
│ [更多房间...] │
└──────────────────────────────────────┘
```
### 页面2预约页
**页面元素**
```
┌──────────────────────────────────────┐
│ 创建预约 │
├──────────────────────────────────────┤
│ 预约日期2025年12月06日 (只读) │
│ 房间号101包间 (豪华包间) (只读) │
├──────────────────────────────────────┤
│ 预约时间: │
│ [ 上午 ▼ ] │ ← 下拉选择(可预约时段)
│ 时间范围06:00-11:59 │
│ 价格标准价80元 | 会员价60元 │
├──────────────────────────────────────┤
│ 最晚到店时间: │
│ [ 10:30 ] (时间选择器) │
├──────────────────────────────────────┤
│ 人数: │
│ ( ) 2人 ( ) 3人 ( ) 4人 │
│ ( ) 无需组局 │
├──────────────────────────────────────┤
│ 鸽子费: │
│ ( ) 0元 ( ) 10元 ( ) 20元 │
│ (•) 自定义:[ 30 ] 元 │
├──────────────────────────────────────┤
│ 组局名称:[ 周末开黑 ] │
│ 玩法类型:[ 德州扑克 ] │
│ ... 其他信息 ... │
├──────────────────────────────────────┤
│ [ 提交预约 ] 按钮 │
└──────────────────────────────────────┘
```
---
## 接口调用流程
### 时序图
```
前端页面 → 后端API
│ 1. 进入房间展示页
├────────────────→ GET /api/SQ/GetAvailableDates
│←─────────────── 返回未来7天日期列表
│ 2. 获取房间列表(默认今天)
├────────────────→ GET /api/SQ/GetRoomListWithSlotsNew?date=xxx
│←─────────────── 返回房间列表及4个时段状态
│ 3. 用户切换日期
├────────────────→ GET /api/SQ/GetRoomListWithSlotsNew?date=xxx
│←─────────────── 返回:新日期的房间列表
│ 4. 用户点击【预约】按钮
│ 携带roomId, date, availableSlots
│ 跳转到预约页
│ 5. (可选)用户选择时段后实时校验
├────────────────→ POST /api/SQ/ValidateReservationBySlot
│←─────────────── 返回:是否可以预约
│ 6. 用户点击【提交预约】
├────────────────→ POST /api/SQ/AddSQReservationBySlot
│←─────────────── 返回:预约结果(成功/失败)
│ 7. 成功后跳转到"我的预约"列表
```
---
## 接口详细说明
### 接口1获取可选日期列表
#### 基本信息
- **接口路径**`GET /api/SQ/GetAvailableDates`
- **调用时机**房间展示页初始化时调用1次
- **是否需要登录**:否
#### 请求参数
无参数
#### 返回数据
```typescript
interface Response {
code: number; // 0=成功
msg: string; // 消息
data: DateItem[]; // 日期列表
}
interface DateItem {
date: number; // Unix时间戳秒级
dateText: string; // 文本:今天/明天/后天/日期
dateDisplay: string; // 展示文本12月06日 周五
}
```
#### 返回示例
```json
{
"code": 0,
"msg": "ok",
"data": [
{
"date": 1733443200,
"dateText": "今天",
"dateDisplay": "周五"
},
{
"date": 1733529600,
"dateText": "明天",
"dateDisplay": "周六"
},
{
"date": 1733616000,
"dateText": "后天",
"dateDisplay": "周日"
},
{
"date": 1733702400,
"dateText": "12月09日",
"dateDisplay": "周一"
}
// ... 共7条今天+未来6天
]
}
```
---
### 接口2获取房间列表及时段状态
#### 基本信息
- **接口路径**`GET /api/SQ/GetRoomListWithSlotsNew`
- **调用时机**:初始化、切换日期时调用
- **是否需要登录**:否
#### 请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|-------|------|------|------|------|
| date | number | 是 | Unix时间戳秒级 | 1733443200 |
| showOnlyAvailable | boolean | 否 | 是否只显示当前时段可用房间 | false |
| currentTimeSlot | number | 否 | 当前时段类型(0-3)配合showOnlyAvailable | 1 |
#### 返回数据
```typescript
interface Response {
code: number;
msg: string;
data: RoomItem[];
}
interface RoomItem {
id: number; // 房间ID
name: string; // 房间名称
room_type_name: string; // 房间类型
image_url: string; // 房间图片
capacity: number; // 容纳人数
description: string; // 房间描述
standard_price_desc: string; // 标准价格说明
member_price_desc: string; // 会员价格说明
time_slots: TimeSlot[]; // 4个时段信息
status: string; // 房间状态
is_available: boolean; // 是否可用
can_reserve: boolean; // 是否可预约
}
interface TimeSlot {
slot_type: number; // 时段类型0-3
slot_name: string; // 时段名称:凌晨/上午/下午/晚上
status: string; // 状态available/reserved/unavailable/using
standard_price: number; // 标准价格
member_price: number; // 会员价格
price_desc_standard: string; // 标准价格说明
price_desc_member: string; // 会员价格说明
}
```
#### 返回示例
```json
{
"code": 0,
"msg": "ok",
"data": [
{
"id": 1,
"name": "101包间",
"room_type_name": "豪华包间",
"image_url": "https://example.com/room1.jpg",
"capacity": 8,
"description": "宽敞舒适的豪华包间",
"standard_price_desc": "80元/时段",
"member_price_desc": "60元/时段",
"time_slots": [
{
"slot_type": 0,
"slot_name": "凌晨",
"status": "available",
"standard_price": 60.00,
"member_price": 50.00,
"price_desc_standard": "60元/时段",
"price_desc_member": "50元/时段"
},
{
"slot_type": 1,
"slot_name": "上午",
"status": "reserved",
"standard_price": 80.00,
"member_price": 60.00,
"price_desc_standard": "80元/时段",
"price_desc_member": "60元/时段"
},
{
"slot_type": 2,
"slot_name": "下午",
"status": "available",
"standard_price": 80.00,
"member_price": 60.00,
"price_desc_standard": "80元/时段",
"price_desc_member": "60元/时段"
},
{
"slot_type": 3,
"slot_name": "晚上",
"status": "using",
"standard_price": 100.00,
"member_price": 80.00,
"price_desc_standard": "100元/时段",
"price_desc_member": "80元/时段"
}
],
"status": "using",
"is_available": false,
"can_reserve": true
}
]
}
```
### 接口3校验是否可创建预约可选
#### 基本信息
- **接口路径**`POST /api/SQ/ValidateReservationBySlot`
- **调用时机**:用户选择时段后实时校验(可选)
- **是否需要登录**需要Token
#### 请求参数
与"创建预约"接口相同,但不会真正创建预约
#### 返回数据
```typescript
interface Response {
code: number; // 0=可以创建500=不可以
msg: string; // 原因说明
data: {
canCreate: boolean; // 是否可以创建
reason: string; // 不能创建的原因
}
}
```
#### 返回示例
```json
// 可以创建
{
"code": 0,
"msg": "",
"data": {
"canCreate": true,
"reason": ""
}
}
// 不能创建
{
"code": 500,
"msg": "您有其它预约时间冲突",
"data": {
"canCreate": false,
"reason": "您有其它预约时间冲突"
}
}
```
#### 前端使用
```javascript
// 用户选择时段时实时校验
async function onSlotChange(timeSlotType) {
const data = {
room_id: this.roomId,
date: this.date,
time_slot_type: timeSlotType,
player_count: 4,
// ... 其他字段
};
try {
const response = await axios.post('/api/SQ/ValidateReservationBySlot', data, {
headers: { Authorization: `Bearer ${this.token}` }
});
if (response.data.code === 0) {
// 可以预约,显示价格等信息
this.showPriceInfo(timeSlotType);
} else {
// 不能预约,显示原因
this.$message.warning(response.data.msg);
}
} catch (error) {
console.error('校验失败', error);
}
}
```
---
### 接口4创建预约核心
#### 基本信息
- **接口路径**`POST /api/SQ/AddSQReservationBySlot`
- **调用时机**:用户点击【提交预约】按钮
- **是否需要登录**需要Token
#### 请求参数
```typescript
interface CreateReservationRequest {
room_id: number; // 必填房间ID
date: number; // 必填预约日期Unix时间戳-秒)
time_slot_type: number; // 必填时段类型0-3
latest_arrival_time?: number; // 可选最晚到店时间Unix时间戳-秒)
is_solo_mode: boolean; // 必填,是否无需组局
player_count: number; // 必填人数无需组局时固定为1
deposit_fee: number; // 必填鸽子费0-50整数
title: string; // 必填,组局名称
game_type: string; // 必填,玩法类型
game_rule?: string; // 可选,游戏规则
extra_info?: string; // 可选,其他补充
is_smoking: number; // 可选是否禁烟0=不限1=禁烟2=不禁烟
gender_limit: number; // 可选性别限制0=不限1=男2=女
credit_limit?: number; // 可选,最低信誉分
min_age?: number; // 可选,最小年龄
max_age?: number; // 可选最大年龄0=不限
important_data?: string; // 可选重要数据支付相关JSON
}
```
#### 请求示例
```json
{
"room_id": 1,
"date": 1733443200,
"time_slot_type": 1,
"latest_arrival_time": 1733461200,
"is_solo_mode": false,
"player_count": 4,
"deposit_fee": 20,
"title": "周末开黑",
"game_type": "德州扑克",
"game_rule": "经典玩法",
"extra_info": "欢迎新手",
"is_smoking": 0,
"gender_limit": 0,
"credit_limit": 3.5,
"min_age": 18,
"max_age": 0,
"important_data": "{\"paymentId\":\"ORDER123456\"}"
}
```
#### 返回数据
```typescript
interface Response {
code: number; // 0=成功,其他=失败
msg: string; // 消息
data?: {
reservation_id: number; // 预约ID
start_time: string; // 开始时间
end_time: string; // 结束时间
actual_price: number; // 实际价格
}
}
```
#### 返回示例(成功)
```json
{
"code": 0,
"msg": "预约成功",
"data": {
"reservation_id": 123,
"start_time": "2025-12-06 06:00:00",
"end_time": "2025-12-06 11:59:59",
"actual_price": 80.00
}
}
```
#### 返回示例(失败)
```json
// 时间冲突
{
"code": 402,
"msg": "您有其它预约时间冲突,无法创建该预约!",
"data": null
}
// 房间已被预约
{
"code": 500,
"msg": "该时间段房间已被预约",
"data": null
}
// 鸽子费超限
{
"code": 500,
"msg": "鸽子费必须在0-50元之间",
"data": null
}
```
#### 前端使用
```javascript
async function submitReservation() {
// 1. 构建请求数据
const data = {
room_id: this.roomId,
date: this.date,
time_slot_type: this.selectedSlot,
latest_arrival_time: this.convertToTimestamp(this.arrivalTime),
is_solo_mode: this.playerCount === 1,
player_count: this.playerCount,
deposit_fee: this.depositFee,
title: this.title,
game_type: this.gameType,
game_rule: this.gameRule,
extra_info: this.extraInfo,
is_smoking: this.isSmoking,
gender_limit: this.genderLimit,
credit_limit: this.creditLimit,
min_age: this.minAge,
max_age: this.maxAge,
important_data: this.getPaymentData()
};
// 2. 参数校验
if (!this.validate(data)) {
return;
}
// 3. 发送请求
try {
const response = await axios.post('/api/SQ/AddSQReservationBySlot', data, {
headers: { Authorization: `Bearer ${this.token}` }
});
if (response.data.code === 0) {
// 成功
this.$message.success('预约成功!');
// 可以显示预约详情
console.log('预约ID:', response.data.data.reservation_id);
console.log('时间:', response.data.data.start_time, '-', response.data.data.end_time);
console.log('价格:', response.data.data.actual_price);
// 跳转到我的预约列表
this.$router.push('/my-reservations');
} else {
// 失败
this.$message.error(response.data.msg);
}
} catch (error) {
this.$message.error('预约失败,请重试');
console.error(error);
}
}
// 参数校验
function validate(data) {
if (!data.title) {
this.$message.warning('请输入组局名称');
return false;
}
if (!data.game_type) {
this.$message.warning('请选择玩法类型');
return false;
}
if (data.deposit_fee < 0 || data.deposit_fee > 50) {
this.$message.warning('鸽子费必须在0-50元之间');
return false;
}
if (!Number.isInteger(data.deposit_fee)) {
this.$message.warning('鸽子费必须是整数');
return false;
}
return true;
}
```
---
## 数据结构定义
### TypeScript类型定义
```typescript
// ========== 基础类型 ==========
/** 时段类型枚举 */
enum TimeSlotType {
Dawn = 0, // 凌晨
Morning = 1, // 上午
Afternoon = 2, // 下午
Evening = 3 // 晚上
}
/** 时段状态枚举 */
enum TimeSlotStatus {
Available = 'available', // 可预约
Reserved = 'reserved', // 已预约
Unavailable = 'unavailable', // 不可用
Using = 'using' // 使用中
}
// ========== 日期相关 ==========
/** 日期项 */
interface DateItem {
date: number; // Unix时间戳
dateText: string; // 文本显示
dateDisplay: string; // 完整显示
}
// ========== 房间相关 ==========
/** 时段信息 */
interface TimeSlot {
slot_type: TimeSlotType; // 时段类型
slot_name: string; // 时段名称
status: TimeSlotStatus; // 时段状态
standard_price: number; // 标准价格
member_price: number; // 会员价格
price_desc_standard: string; // 标准价格说明
price_desc_member: string; // 会员价格说明
}
/** 房间信息 */
interface RoomItem {
id: number; // 房间ID
name: string; // 房间名称
room_type_name: string; // 房间类型
image_url: string; // 房间图片
capacity: number; // 容纳人数
description: string; // 房间描述
standard_price_desc: string; // 标准价格说明
member_price_desc: string; // 会员价格说明
time_slots: TimeSlot[]; // 时段列表
status: string; // 房间状态
is_available: boolean; // 是否可用
can_reserve: boolean; // 是否可预约
}
// ========== 预约相关 ==========
/** 创建预约请求 */
interface CreateReservationRequest {
room_id: number;
date: number;
time_slot_type: TimeSlotType;
latest_arrival_time?: number;
is_solo_mode: boolean;
player_count: number;
deposit_fee: number;
title: string;
game_type: string;
game_rule?: string;
extra_info?: string;
is_smoking: number;
gender_limit: number;
credit_limit?: number;
min_age?: number;
max_age?: number;
important_data?: string;
}
/** 创建预约响应 */
interface CreateReservationResponse {
reservation_id: number;
start_time: string;
end_time: string;
actual_price: number;
}
// ========== API响应 ==========
/** 通用API响应 */
interface ApiResponse<T = any> {
code: number;
msg: string;
data: T;
}
```
---
## 常见问题FAQ
### Q1: 时间戳是秒级还是毫秒级?
**A:** 所有接口使用的都是**秒级时间戳**Unix Timestamp不是毫秒级。
```javascript
// 正确:秒级
const timestamp = Math.floor(Date.now() / 1000);
// 错误:毫秒级
const timestamp = Date.now();
```
### Q2: 如何判断当前是哪个时段?
**A:** 根据当前时间的小时数判断:
```javascript
function getCurrentTimeSlot() {
const hour = new Date().getHours();
if (hour >= 0 && hour < 6) return 0; // 凌晨
if (hour >= 6 && hour < 12) return 1; // 上午
if (hour >= 12 && hour < 18) return 2; // 下午
return 3; // 晚上
}
```
### Q3: 最晚到店时间如何处理?
**A:** 最晚到店时间必须在所选时段的时间范围内:
```javascript
// 1. 获取时段的时间范围
const timeRanges = {
0: { min: '00:00', max: '05:59' },
1: { min: '06:00', max: '11:59' },
2: { min: '12:00', max: '17:59' },
3: { min: '18:00', max: '23:59' }
};
// 2. 限制时间选择器的范围
const range = timeRanges[selectedSlot];
<input type="time" :min="range.min" :max="range.max">
// 3. 转换为时间戳
const [hour, minute] = arrivalTime.split(':');
const d = new Date(date * 1000);
d.setHours(Number(hour), Number(minute), 0, 0);
const timestamp = Math.floor(d.getTime() / 1000);
```
### Q4: "无需组局"模式如何处理?
**A:** 当选择"无需组局"时:
```javascript
// 1. 人数固定为1
if (playerCount === 1) {
form.is_solo_mode = true;
form.player_count = 1;
}
// 2. 后端会拒绝其他人加入该预约
```
### Q5: 鸽子费的限制是什么?
**A:** 鸽子费必须满足:
- 范围0-50元
- 类型:整数(不能有小数)
```javascript
// 校验
if (depositFee < 0 || depositFee > 50) {
alert('鸽子费必须在0-50元之间');
return false;
}
if (!Number.isInteger(depositFee)) {
alert('鸽子费必须是整数');
return false;
}
```
### Q6: 如何处理时间冲突?
**A:** 后端会自动检测时间冲突返回错误码402
```javascript
if (response.data.code === 402) {
alert('您有其它预约时间冲突,无法创建该预约!');
// 可以引导用户查看"我的预约"
}
```
### Q7: Token如何传递
**A:** 所有需要登录的接口都需要在请求头中携带Token
```javascript
// Axios示例
axios.post('/api/SQ/AddSQReservationBySlot', data, {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`
}
});
// 或配置全局拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
```
### Q8: 如何处理已过时段?
**A:** 后端会自动过滤已过去的时段,前端不需要额外处理。如果某个时段已过去,它会在`time_slots`中显示为`unavailable`或不返回。
### Q9: 房间图片加载失败怎么办?
**A:** 建议添加图片加载失败的占位图:
```vue
<img
:src="room.image_url"
:alt="room.name"
@error="handleImageError"
>
<script>
function handleImageError(e) {
e.target.src = '/default-room.png'; // 默认图片
}
</script>
```
### Q10: 如何实现实时刷新房间状态?
**A:** 可以使用定时轮询或WebSocket
```javascript
// 方式1定时轮询简单
let timer = null;
onMounted(() => {
loadRooms();
timer = setInterval(() => {
loadRooms();
}, 30000); // 每30秒刷新一次
});
onUnmounted(() => {
if (timer) clearInterval(timer);
});
// 方式2WebSocket实时性更好需要后端支持
// 连接WebSocket监听房间状态变化
```
---
## 调试技巧
### 1. 使用浏览器开发者工具
#### 查看网络请求
```
F12 → Network → XHR
- 查看请求URL、参数、响应
- 检查状态码
- 查看响应时间
```
#### 查看控制台日志
```javascript
// 添加详细日志
console.log('请求参数:', data);
console.log('响应数据:', response.data);
console.error('错误信息:', error);
```
### 2. Postman测试接口
#### 测试步骤
1. 先调用登录接口获取Token
2. 复制Token到环境变量
3. 测试各个接口
4. 保存常用请求到Collection
#### 示例请求
```
GET http://localhost:5000/api/SQ/GetAvailableDates
GET http://localhost:5000/api/SQ/GetRoomListWithSlotsNew?date=1733443200
POST http://localhost:5000/api/SQ/AddSQReservationBySlot
Headers:
Authorization: Bearer eyJhbGc...
Body: { ... }
```
### 3. 常见错误排查
| 错误 | 可能原因 | 解决方法 |
|------|---------|---------|
| 401 Unauthorized | Token过期或无效 | 重新登录获取Token |
| 404 Not Found | 接口路径错误 | 检查URL是否正确 |
| 500 Internal Server Error | 服务器内部错误 | 查看后端日志 |
| CORS错误 | 跨域配置问题 | 联系后端配置CORS |
| 参数错误 | 参数格式不正确 | 检查参数类型和值 |
### 4. 开发环境代理配置
#### Vite配置示例
```javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
}
}
```
#### Vue CLI配置示例
```javascript
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
}
}
}
}
```
---
## 附录
### A. 错误码对照表
| 错误码 | 说明 | 处理建议 |
|-------|------|---------|
| 0 | 成功 | 继续业务流程 |
| 400 | 业务错误 | 显示错误信息 |
| 401 | 未授权 | 跳转到登录页 |
| 402 | 时间冲突 | 提示用户选择其他时段 |
| 403 | 权限不足 | 提示权限不足 |
| 404 | 资源不存在 | 提示资源不存在 |
| 500 | 系统错误 | 提示系统错误,稍后重试 |
### B. 测试数据
```javascript
// 测试用日期时间戳
const testDates = {
today: Math.floor(Date.now() / 1000),
tomorrow: Math.floor(Date.now() / 1000) + 86400,
nextWeek: Math.floor(Date.now() / 1000) + 604800
};
// 测试用房间ID
const testRoomIds = [1, 2, 3];
// 测试用时段类型
const testTimeSlots = [0, 1, 2, 3];
```
### C. 联系方式
**技术支持**
- 邮箱jianweie@163.com
- 文档版本v2.0
- 更新日期2025-12-06
---
**祝开发顺利!** 🚀