From ce50875e8d22b9ade4f738a8c5dcfdc90aaf9495 Mon Sep 17 00:00:00 2001 From: zpc Date: Fri, 26 Dec 2025 16:08:55 +0800 Subject: [PATCH] ewq --- .claude/settings.local.json | 9 + PROJECT_DESCRIPTION.md | 744 +++++++ .../Controllers/AdvertController.cs | 86 - .../Controllers/AgentController.cs | 441 ---- .../Controllers/ArticleController.cs | 130 -- .../Controllers/CartController.cs | 142 -- .../Controllers/CouponController.cs | 253 --- .../Controllers/DemoController.cs | 29 - .../Controllers/DistributionController.cs | 313 --- .../Controllers/FormController.cs | 79 - .../Controllers/GoodController.cs | 492 ----- .../Controllers/GroupController.cs | 84 - .../Controllers/PageController.cs | 162 -- .../Controllers/PinTuanController.cs | 175 -- .../Controllers/ServiceController.cs | 404 ---- .../Controllers/StoreController.cs | 365 ---- uniapp/mahjong_group | 2 +- v1.0.0_需求文档.md | 25 + v1.0.1_需求文档.md | 114 ++ 业务逻辑文档 copy.md | 1190 +++++++++++ 业务逻辑文档.md | 1782 +++++++++++++++++ 预约时间自由选择_改动文档 copy.md | 556 +++++ ...间自由选择_改动文档_开发版.md | 665 ++++++ ...间自由选择_改动文档_甲方版.md | 524 +++++ 24 files changed, 5610 insertions(+), 3156 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 PROJECT_DESCRIPTION.md delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/AdvertController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/AgentController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/ArticleController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/CartController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/CouponController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/DemoController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/DistributionController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/FormController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/GoodController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/GroupController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/PageController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/PinTuanController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/ServiceController.cs delete mode 100644 server/CoreCms.Net.Web.WebApi/Controllers/StoreController.cs create mode 100644 v1.0.0_需求文档.md create mode 100644 v1.0.1_需求文档.md create mode 100644 业务逻辑文档 copy.md create mode 100644 业务逻辑文档.md create mode 100644 预约时间自由选择_改动文档 copy.md create mode 100644 预约时间自由选择_改动文档_开发版.md create mode 100644 预约时间自由选择_改动文档_甲方版.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..8183f53 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(tree:*)", + "Bash(find:*)", + "Bash(grep:*)" + ] + } +} diff --git a/PROJECT_DESCRIPTION.md b/PROJECT_DESCRIPTION.md new file mode 100644 index 0000000..65ff5f2 --- /dev/null +++ b/PROJECT_DESCRIPTION.md @@ -0,0 +1,744 @@ +# 麻将组局预约小程序项目说明 + +## 项目概述 + +这是一个**麻将组局预约小程序**,帮助麻将爱好者在线上发起、加入麻将局,线下到店进行游戏。小程序提供了完整的预约流程管理,包括房间选择、预约发起、参与管理、签到评价等功能。 + +**技术栈:** +- **前端**: UniApp(支持多端发布) +- **后端**: .NET Core + SqlSugar ORM +- **数据库**: Microsoft SQL Server + +--- + +## 核心功能模块 + +### 1. 首页预约列表 + +**文件位置:** +- 前端: `uniapp/mahjong_group/pages/index/index.vue` +- 后端: `server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs:170` (GetReservationList) + +**功能描述:** +- 展示所有未结束的预约列表(按开始时间升序) +- 自动过滤黑名单用户发起的预约 +- 分页加载,支持下拉刷新 +- 卡片式展示预约信息(标题、时间、房间、人数、玩法等) + +**关键逻辑:** +```sql +-- 查询未结束的预约(status < 3,end_time > now) +-- 如果用户已登录,排除黑名单用户发起的预约 +-- 按开始时间升序排列 +``` + +--- + +### 2. 发起预约 + +**文件位置:** +- 前端: `uniapp/mahjong_group/pages/appointment/appointment-page.vue` +- 后端API: `SQController.cs:524` (AddSQReservation) + +**预约流程:** + +#### 第一步:选择房间和日期 +- 页面: `book-room-page.vue` +- API: `GetRoomListWithSlotsNew` (获取房间列表及时段状态) +- 支持按**四个时段**查看房间可用性: + - 凌晨: 00:00-06:00 + - 上午: 06:00-12:00 + - 下午: 12:00-18:00 + - 晚上: 18:00-00:00 + +#### 第二步:填写预约信息 +填写内容包括: + +**基本信息:** +- 组局名称 +- 人数(最多4人) +- 玩法类型(血战、血流成河等) +- 具体规则(几番起胡、几倍封顶等) +- 其他补充说明 + +**参与者限制:** +- 是否禁烟(可选) +- 性别限制(不限/男/女) +- 年龄范围(最小年龄-最大年龄) +- 信誉要求(0.0-5.0分) + +**鸽子费(押金):** +- 固定金额(0元、5元、10元、20元) +- 自定义金额(0-50元) +- 说明: 参与者需缴纳押金,爽约者押金由到场者平分,预约完成后全额返还 + +#### 第三步:提交预约 +1. 调用 `canCreateSQReservation` 验证是否可以创建 +2. 如有鸽子费,调用 `usePay` 发起微信支付 +3. 调用 `addSQReservation` 创建预约记录 +4. 创建发起者的参与记录(role=1) + +**验证规则:** +- 房间是否存在且可用 +- 时间段是否已被预约 +- 用户是否有时间冲突的预约 +- 房间在该时段是否有不可用时间 + +--- + +### 3. 加入预约 + +**后端API:** `SQController.cs:804` (JoinReservation) + +**加入流程:** +1. 校验预约是否存在且未结束 +2. 校验用户是否已加入 +3. 校验是否为"独享模式"(无需组局) +4. 校验用户是否符合参与条件: + - 信誉分是否达到要求 + - 性别是否符合限制 + - 年龄是否在范围内 +5. 校验是否有时间冲突的预约 +6. 校验预约是否已满员 +7. 如有鸽子费,验证支付信息 +8. 创建参与者记录(role=0) + +**重要提示:** +- 参与者需满足发起者设置的所有限制条件 +- 加入预约后可以取消,但在开始前30分钟内无法取消 +- 如有鸽子费需先完成支付 + +--- + +### 4. 预约签到 + +**后端API:** `SQController.cs:1207` (CheckInReservation) + +**签到权限:** 仅发起者可操作 + +**签到时机:** 预约开始后,由发起者确认实际到场人员 + +**签到流程:** +1. 发起者在预约详情页点击"签到"按钮 +2. 勾选实际到场的参与者(发起者默认到场) +3. 提交签到 + +**签到效果:** +- 预约状态变更为"进行中"(status=2) +- 到场人员标记为`is_arrive=1` +- 未到场人员标记为`is_arrive=2`并退出预约 +- 爽约者扣除0.5信誉分,增加鸽子次数 +- 到场者增加0.2信誉分(最高5.0) +- 如有鸽子费,到场者标记为"发起退款"状态 + +**信誉系统:** +```javascript +// 爽约处罚 +credit_score -= 0.5 +dove_count++ + +// 守约奖励(信誉<5.0时) +credit_score += 0.2 (最高5.0) +``` + +--- + +### 5. 评价系统 + +**后端API:** +- 获取评价列表: `SQController.cs:252` (GetEvaluateServices) +- 添加评价: `SQController.cs:330` (AddEvaluateServices) + +**评价条件:** +- 预约已完成且已签到 +- 只能评价实际到场的参与者 +- 每个参与者只能被评价一次 + +**评价维度:** +- 游戏水平(play_level): 1-5分 +- 技能水平(skills_level): 1-5分 + +**评价计算:** +用户的最终评分采用加权平均: +```javascript +// 初始值为4分 +play_level = (sum(评价分数) + 4) / (评价次数 + 1) +skills_level = (sum(评价分数) + 4) / (评价次数 + 1) + +// 如果只有1次评价,分母+1避免偏差 +if (评价次数 == 1) { + 分母 = 评价次数 + 2 +} +``` + +**评价作用:** +- 其他用户可在预约详情看到参与者的平均评分 +- 评分影响用户在列表中的展示排序 +- 高评分用户更容易被其他人接受加入 + +--- + +### 6. 取消预约 + +**后端API:** `SQController.cs:1036` (CancelReservation) + +**取消规则:** + +**发起者取消:** +- 预约开始前30分钟内无法取消 +- 取消后预约状态变为"已取消"(status=4) +- 所有参与者自动退出 +- 如有押金,已支付者发起退款 +- 通知所有参与者"发起者解散组局" + +**参与者取消:** +- 预约开始前30分钟内无法取消 +- 只退出自己,不影响其他人 +- 如有押金,发起退款 + +**退款状态说明:** +- `is_refund=1`: 待支付 +- `is_refund=2`: 已支付 +- `is_refund=3`: 待退款(发起退款流程) +- `is_refund=4`: 已退款 +- `is_refund=5`: 退款异常 + +--- + +### 7. 我的预约记录 + +**后端API:** `SQController.cs:120` (GetMyReservation) + +**分类查看:** +- `type=0`: 我参与的预约(role=0) +- `type=1`: 我发起的预约(role=1) + +**记录状态:** +- 待开始(status=0) +- 已锁定(status=1)- 人满 +- 进行中(status=2)- 已签到 +- 已结束(status=3) +- 已取消(status=4) + +**我正在进行的预约:** +API: `GetMyUseReservation` +- 查询未取消且未结束的预约 +- 按状态排序:进行中 > 已锁定 > 待开始 > 已结束 + +--- + +### 8. 黑名单功能 + +**说明:** +- 用户可将不友好的参与者加入黑名单 +- 首页自动过滤黑名单用户发起的预约 +- 黑名单用户无法加入我发起的预约(待实现) + +**相关表:** `CoreCmsUserBlacklist` + +--- + +### 9. 消息系统 + +**后端API:** +- 获取消息列表: `SQController.cs:1778` (GetMessageList) +- 获取未读数量: `SQController.cs:1814` (GetUnreadCount) +- 全部标记已读: `SQController.cs:1850` (MarkAllAsRead) + +**消息类型:** +- 系统消息:预约相关通知(组局成功、被取消等) +- 私信消息(暂未实现) + +**消息状态:** +- 未读:红点提示 +- 已读:正常显示 + +**相关表:** +- `SQMessage`: 消息表 +- `SQMessageRead`: 已读记录表 + +--- + +### 10. 收益系统 + +**后端API:** +- 获取收益统计: `SQController.cs:1897` (GetEarningsSummary) +- 获取收益记录: `SQController.cs:1970` (GetEarningsRecordList) +- 申请提现: `SQController.cs:2050` (ApplyWithdraw) + +**收益来源:** +- 发起预约的抽成(具体规则待配置) +- 爽约者的鸽子费分成 + +**提现规则:** +- 最低提现金额:0.01元 +- 提现到账时间:3-5个工作日 +- 提现记录可查询 + +**相关表:** +- `SQEarningsRecord`: 收益记录表 +- `SQWithdrawRecord`: 提现记录表 + +--- + +## 数据库设计 + +### 核心数据表 + +#### 1. SQReservations (预约表) +主要字段: +```sql +id -- 预约ID +room_id -- 房间ID +room_name -- 房间名称 +start_time -- 开始时间 +end_time -- 结束时间 +duration_minutes -- 时长(分钟) +title -- 组局名称 +game_type -- 游戏类型 +game_rule -- 游戏规则 +player_count -- 需要人数 +status -- 状态(0待开始 1已锁定 2进行中 3已结束 4已取消) +deposit_fee -- 押金费用 +credit_limit -- 最低信誉要求 +gender_limit -- 性别限制(0不限 1男 2女) +min_age -- 最小年龄 +max_age -- 最大年龄 +is_smoking -- 是否禁烟 +latest_arrival_time -- 最晚到店时间 +extra_info -- 其他说明 +is_solo_mode -- 是否独享模式(无需组局) +created_at -- 创建时间 +updated_at -- 更新时间 +``` + +#### 2. SQReservationParticipants (参与者表) +主要字段: +```sql +id -- 参与记录ID +reservation_id -- 预约ID +user_id -- 用户ID +role -- 角色(0参与者 1发起者) +status -- 状态(0正常 1已退出) +join_time -- 加入时间 +quit_time -- 退出时间 +is_arrive -- 是否到场(0未签到 1到场 2未到场) +check_reservation -- 签到时间 +is_refund -- 退款状态(1待支付 2已支付 3待退款 4已退款 5异常) +paymentId -- 支付订单号 +important_data -- 重要数据(JSON) +``` + +#### 3. SQRooms (房间表) +主要字段: +```sql +id -- 房间ID +name -- 房间名称 +capacity -- 容量(人数) +price_per_hour -- 每小时价格 +description -- 描述 +image_url -- 图片 +status -- 状态(true可用 false不可用) +created_at -- 创建时间 +``` + +#### 4. SQReservationEvaluate (评价表) +主要字段: +```sql +id -- 评价ID +reservation_id -- 预约ID +user_id -- 评价人ID +to_user_id -- 被评价人ID +role -- 被评价人角色 +play_level -- 游戏水平评分 +skills_level -- 技能水平评分 +created_at -- 评价时间 +``` + +#### 5. SQReservationReputation (声誉记录表) +主要字段: +```sql +id -- 记录ID +user_id -- 用户ID +reservation_id -- 相关预约ID +reputation_value -- 声誉变化值(±0.5、±0.2等) +remark -- 变化原因 +created_at -- 记录时间 +``` + +#### 6. SQRoomUnavailableTimes (房间不可用时间表) +主要字段: +```sql +id -- 记录ID +room_id -- 房间ID +start_time -- 不可用开始时间 +end_time -- 不可用结束时间 +reason -- 原因 +created_at -- 创建时间 +``` + +#### 7. SQMessage (消息表) +主要字段: +```sql +id -- 消息ID +user_id -- 接收用户ID(0表示全体用户) +title -- 消息标题 +content -- 消息内容 +message_type -- 消息类型(0系统 1私信) +created_at -- 创建时间 +``` + +#### 8. SQEarningsRecord (收益记录表) +主要字段: +```sql +id -- 记录ID +user_id -- 用户ID +reservation_id -- 相关预约ID +amount -- 收益金额 +type -- 收益类型 +description -- 描述 +created_at -- 创建时间 +``` + +--- + +## 前端页面结构 + +### TabBar(底部导航) +1. **首页** (`pages/index/index`) + - 预约列表展示 + - 支持下拉刷新、上拉加载 + - 点击卡片查看详情/加入预约 + +2. **预约** (`pages/appointment/book-room-page`) + - 选择房间和日期 + - 查看房间时段可用性 + - 进入预约表单 + +3. **我的** (`pages/me/me-page`) + - 用户信息展示 + - 预约记录入口 + - 我的收益入口 + - 消息通知入口 + +### 主要功能页面 + +#### 预约相关 +- `pages/appointment/book-room-page.vue` - 选择房间页面 +- `pages/appointment/appointment-page.vue` - 发起预约页面 + +#### 个人中心 +- `pages/me/appointment-record-page.vue` - 预约记录 +- `pages/me/my-earnings-page.vue` - 我的收益 +- `pages/me/my-message-page.vue` - 消息列表 +- `pages/me/my-record.vue` - 历史记录 +- `pages/me/blacklist-page.vue` - 黑名单管理 +- `pages/me/edit-info.vue` - 编辑个人信息 +- `pages/me/login.vue` - 登录页面 + +#### 其他页面 +- `pages/other/agreement.vue` - 用户协议 +- `pages/other/payment-records.vue` - 支付记录 +- `pages/other/faq.vue` - 常见问题 + +--- + +## API接口汇总 + +### 预约相关接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取预约列表 | `api/sq/GetReservationList` | GET | 无需 | 首页预约列表 | +| 获取预约详情 | `api/sq/GetReservationDetail` | GET | 无需 | 根据ID获取详情 | +| 我的预约记录 | `api/sq/GetMyReservation` | GET | 需要 | 我参与/发起的预约 | +| 正在进行的预约 | `api/sq/GetMyUseReservation` | GET | 需要 | 未结束的预约 | +| 验证是否可创建 | `api/sq/CanCreateSQReservation` | POST | 需要 | 创建预约前验证 | +| 创建预约 | `api/sq/AddSQReservation` | POST | 需要 | 发起新预约 | +| 加入预约 | `api/sq/JoinReservation` | POST | 需要 | 参与现有预约 | +| 取消预约 | `api/sq/CancelReservation` | POST | 需要 | 发起者/参与者取消 | +| 预约签到 | `api/sq/CheckInReservation` | POST | 需要 | 发起者签到确认 | + +### 房间相关接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取可选日期 | `api/sq/GetAvailableDates` | GET | 无需 | 今天+未来6天 | +| 获取房间列表 | `api/sq/GetRoomListWithSlotsNew` | GET | 无需 | 按时段显示房间状态 | +| 获取房间详情 | `api/sq/GetRoomDetail` | GET | 无需 | 房间信息及可用时段 | +| 获取可预约房间 | `api/sq/GetReservationRoomList` | GET | 无需 | 指定时间段可预约房间 | + +### 评价相关接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取预约评价 | `api/sq/GetEvaluateServices` | GET | 需要 | 获取可评价参与者 | +| 添加评价 | `api/sq/AddEvaluateServices` | POST | 需要 | 评价参与者 | +| 获取声誉记录 | `api/sq/GetReputationByUser` | GET | 需要 | 我的信誉变化记录 | +| 获取评价给我的 | `api/sq/GetEvaluateToMe` | GET | 需要 | 别人给我的评价 | + +### 消息相关接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取消息列表 | `api/sq/GetMessageList` | GET | 需要 | 站内信列表 | +| 获取未读数量 | `api/sq/GetUnreadCount` | GET | 需要 | 未读消息数量 | +| 全部标记已读 | `api/sq/MarkAllAsRead` | POST | 需要 | 标记所有消息已读 | + +### 收益相关接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取收益统计 | `api/sq/GetEarningsSummary` | GET | 需要 | 总收益、可提现等 | +| 获取收益记录 | `api/sq/GetEarningsRecordList` | POST | 需要 | 收益明细列表 | +| 获取提现记录 | `api/sq/GetWithdrawRecordList` | POST | 需要 | 提现记录列表 | +| 申请提现 | `api/sq/ApplyWithdraw` | POST | 需要 | 发起提现申请 | +| 获取收益规则 | `api/sq/GetEarningsRule` | GET | 无需 | 收益规则说明 | + +### 其他接口 + +| 接口名称 | 路径 | 方法 | 权限 | 说明 | +|---------|------|------|------|------| +| 获取支付记录 | `api/sq/GetPaymentRecords` | GET | 需要 | 鸽子费支付记录 | +| 获取营业时间 | `api/sq/GetBusinessHours` | GET | 无需 | 店铺营业时间配置 | + +--- + +## 业务流程图 + +### 完整预约流程 + +``` +用户浏览首页预约列表 + ↓ +选择"发起预约"或"加入预约" + ↓ +[发起预约流程] [加入预约流程] + ↓ ↓ +选择房间和日期 查看预约详情 + ↓ ↓ +选择时段 检查参与条件 + ↓ ↓ +填写组局信息 支付鸽子费(如有) + ↓ ↓ +设置参与限制 加入成功 + ↓ ↓ +设置鸽子费 等待预约开始 + ↓ ↓ +支付鸽子费(如有) 收到开始通知 + ↓ +发起成功 + ↓ +等待参与者加入 + ↓ +人满/时间到达开始 + ↓ +发起者签到确认到场人员 + ↓ +预约进行中 + ↓ +预约时间结束 + ↓ +参与者互相评价 + ↓ +预约完成(鸽子费退还) +``` + +### 签到流程详解 + +``` +预约开始时间到达 + ↓ +发起者收到签到通知 + ↓ +发起者打开预约详情 + ↓ +点击"签到"按钮 + ↓ +勾选实际到场的参与者 + ↓ +提交签到 + ↓ +系统处理: + - 预约状态→进行中 + - 到场者:is_arrive=1,信誉+0.2 + - 未到场者:is_arrive=2,信誉-0.5,鸽子次数+1 + - 未到场者押金→到场者平分(待定时任务处理) + - 到场者押金→发起退款 + ↓ +签到完成 + ↓ +预约正常进行 +``` + +### 取消预约流程 + +``` +用户查看我的预约 + ↓ +选择要取消的预约 + ↓ +点击"取消预约" + ↓ +检查取消条件: + - 是否在开始前30分钟 + - 是否已开始(只有发起者可取消已开始的) + - 是否已结束或已取消 + ↓ +[发起者取消] [参与者取消] + ↓ ↓ +预约状态→已取消 只退出自己 + ↓ ↓ +所有参与者→已退出 预约继续有效 + ↓ ↓ +发起押金退款 发起押金退款(自己的) + ↓ ↓ +通知所有参与者 无需通知 + ↓ ↓ +取消完成 +``` + +--- + +## 业务规则总结 + +### 时间规则 +1. 预约可选时间:今天 + 未来6天 +2. 时段划分:凌晨(0-6h)、上午(6-12h)、下午(12-18h)、晚上(18-24h) +3. 营业时间:09:00-23:00 +4. 取消限制:开始前30分钟内无法取消 + +### 人数规则 +1. 每个预约最少1人(独享模式),最多4人 +2. 独享模式(player_count=1)不接受其他人加入 +3. 人满后预约状态→已锁定(status=1) + +### 押金规则 +1. 押金范围:0-50元 +2. 押金用途:防止爽约 +3. 退款时机: + - 签到后到场者:全额退还 + - 爽约者:押金被扣除,由到场者平分 + - 预约取消:全额退还 + +### 信誉规则 +1. 初始信誉:5.0分(满分) +2. 守约奖励:+0.2分/次(最高5.0) +3. 爽约惩罚:-0.5分/次 +4. 信誉作用:发起者可设置最低信誉要求 + +### 评价规则 +1. 评价时机:预约完成后 +2. 评价对象:实际到场的参与者 +3. 评价维度:游戏水平、技能水平(各1-5分) +4. 评价次数:每个参与者只能被评价一次 +5. 平均分计算:(所有评价之和 + 4) / (评价次数 + 1) + +### 参与限制规则 +1. 性别限制:不限/男/女 +2. 年龄限制:最小-最大年龄 +3. 信誉限制:最低信誉要求 +4. 时间冲突检查:不能同时参与多个时间重叠的预约 + +--- + +## 定时任务(推测) + +根据代码逻辑,应该有以下定时任务: + +1. **押金退款任务** + - 扫描 `is_refund=3`(待退款)的记录 + - 调用微信退款API + - 更新退款状态为 `is_refund=4`(已退款) + +2. **预约自动结束任务** + - 扫描 `end_time < now` 且 `status=2`(进行中)的预约 + - 更新状态为 `status=3`(已结束) + +3. **预约失败通知任务** + - 扫描开始时间到达但人数不足的预约 + - 通知参与者"组局失败" + - 退还所有押金 + +--- + +## 项目特色 + +### 1. 完善的信誉体系 +- 守约加分、爽约扣分 +- 鸽子次数统计 +- 参与条件限制 + +### 2. 灵活的押金机制 +- 防止恶意爽约 +- 爽约者押金补偿到场者 +- 守约者全额退还 + +### 3. 双向评价系统 +- 游戏水平、技能水平分开评价 +- 加权平均算法避免偏差 +- 评价影响用户可信度 + +### 4. 智能房间管理 +- 按时段展示房间可用性 +- 自动过滤已预约/不可用时段 +- 支持房间不可用时间配置 + +### 5. 黑名单机制 +- 避免与不友好用户组局 +- 首页自动过滤黑名单预约 + +--- + +## 注意事项 + +### 代码特点 +1. 后端代码是从商城系统改造的,包含一些未使用的表和字段 +2. 核心预约功能集中在 `SQController` 中 +3. 前端使用 UniApp 开发,支持多端发布 +4. 数据库使用 SqlSugar ORM,部分查询直接使用原生SQL + +### 需要改进的地方 +1. 部分业务逻辑写在 Controller 中,应该抽离到 Service 层 +2. 定时任务的具体实现需要补充 +3. 消息推送功能需要完善(微信模板消息/订阅消息) +4. 收益分配规则需要明确配置 +5. 单元测试和接口文档需要完善 + +--- + +## 部署建议 + +### 环境要求 +- .NET Core 6.0+ +- SQL Server 2016+ +- Redis(如需缓存) + +### 配置项 +1. 数据库连接字符串 +2. 微信小程序配置(AppId、AppSecret) +3. 微信支付配置(商户号、密钥) +4. 营业时间配置 +5. 收益规则配置 + +### 运行步骤 +1. 恢复数据库(执行建表脚本) +2. 配置 `appsettings.json` +3. 编译后端项目 +4. 部署到IIS或使用Kestrel +5. 配置前端小程序AppId +6. 编译上传小程序 + +--- + +## 总结 + +这是一个功能完善的**麻将组局预约小程序**,核心流程包括: +1. 用户浏览首页预约列表 +2. 发起/加入预约 +3. 支付鸽子费(押金) +4. 预约开始后发起者签到 +5. 预约完成后互相评价 +6. 押金退还、收益分配 + +项目采用前后端分离架构,数据库设计合理,业务逻辑清晰。通过信誉体系、押金机制、评价系统等功能,有效防止了恶意爽约问题,提高了用户参与组局的积极性和可靠性。 diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/AdvertController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/AdvertController.cs deleted file mode 100644 index 5c41b17..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/AdvertController.cs +++ /dev/null @@ -1,86 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 广告api控制器 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class AdvertController : ControllerBase - { - - private IHttpContextUser _user; - private readonly ICoreCmsArticleServices _articleServices; - private readonly ICoreCmsAdvertPositionServices _advertPositionServices; - private readonly ICoreCmsAdvertisementServices _advertisementServices; - - /// - /// 构造函数 - /// - /// - /// - /// - /// - public AdvertController(IHttpContextUser user - , ICoreCmsArticleServices articleServices - , ICoreCmsAdvertPositionServices advertPositionServices - , ICoreCmsAdvertisementServices advertisementServices - ) - { - _user = user; - _articleServices = articleServices; - _advertPositionServices = advertPositionServices; - _advertisementServices = advertisementServices; - } - - #region 获取广告列表============================================================================= - /// - /// 获取广告列表 - /// - /// - /// - [HttpGet] - public async Task GetAdvertList([FromQuery] string code) - { - if (string.IsNullOrEmpty(code)) - { - return null; - } - var jm = new WebApiDto(); - - var list = await _advertisementServices.QueryListByClauseAsync(p => p.code == code, p => p.createTime, OrderByType.Desc); - jm.Code = 0; - jm.Data = list; - - return jm; - - } - #endregion - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/AgentController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/AgentController.cs deleted file mode 100644 index 483d983..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/AgentController.cs +++ /dev/null @@ -1,441 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.Entities.Expression; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Utility.Extensions; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 代理请求接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class AgentController : ControllerBase - { - private IHttpContextUser _user; - private readonly ICoreCmsAgentServices _agentServices; - private readonly ICoreCmsAgentOrderServices _agentOrderServices; - private readonly ICoreCmsAgentGoodsServices _agentGoodsServices; - private readonly ICoreCmsSettingServices _settingServices; - private readonly ICoreCmsUserServices _userServices; - private readonly ICoreCmsGoodsServices _goodsServices; - - /// - /// 构造函数 - /// - /// - /// - /// - /// - /// - /// - /// - public AgentController(IHttpContextUser user, ICoreCmsAgentServices agentServices, ICoreCmsSettingServices settingServices, ICoreCmsAgentOrderServices agentOrderServices, ICoreCmsUserServices userServices, ICoreCmsGoodsServices goodsServices, ICoreCmsAgentGoodsServices agentGoodsServices) - { - _user = user; - _agentServices = agentServices; - _settingServices = settingServices; - _agentOrderServices = agentOrderServices; - _userServices = userServices; - _goodsServices = goodsServices; - _agentGoodsServices = agentGoodsServices; - } - - //公共接口==================================================================================================== - - #region 获取店铺信息 - /// - /// 获取店铺信息 - /// - /// - [HttpPost] - public async Task GetStoreInfo([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - if (entity.id == 0) - { - jm.msg = "店铺信息丢失"; - return jm; - } - var store = UserHelper.GetUserIdByShareCode(entity.id); - if (store <= 0) - { - jm.msg = "店铺信息丢失"; - return jm; - } - jm = await _agentServices.GetStore(store); - return jm; - - } - #endregion - - - #region 根据查询条件获取分页数据============================================================ - /// - /// 根据查询条件获取分页数据 - /// - /// - [HttpPost] - public async Task GetGoodsPageList([FromBody] FMPageByWhereOrder entity) - { - var jm = new WebApiCallBack(); - - var where = PredicateBuilder.True(); - where = where.And(p => p.isDel == false); - where = where.And(p => p.isMarketable == true); - - var className = string.Empty; - if (!string.IsNullOrEmpty(entity.where)) - { - var obj = JsonConvert.DeserializeAnonymousType(entity.where, new - { - priceFrom = "", - priceTo = "", - catId = "", - brandId = "", - labelId = "", - searchName = "", - }); - - if (!string.IsNullOrEmpty(obj.priceFrom)) - { - var priceF = obj.priceFrom.ObjectToDouble(0); - if (priceF >= 0) - { - var f = Convert.ToDecimal(priceF); - where = where.And(p => p.price >= f); - } - } - if (!string.IsNullOrEmpty(obj.priceTo)) - { - var priceT = obj.priceTo.ObjectToDouble(0); - if (priceT >= 0) - { - var f = Convert.ToDecimal(priceT); - where = where.And(p => p.price <= f); - } - } - if (!string.IsNullOrEmpty(obj.brandId)) - { - var brandId = obj.brandId.ObjectToInt(0); - if (brandId >= 0) - { - where = where.And(p => p.brandId == brandId); - } - } - if (!string.IsNullOrEmpty(obj.labelId)) - { - var brandId = obj.brandId.ObjectToInt(0); - if (brandId >= 0) - { - where = where.And(p => p.brandId == brandId); - } - } - if (!string.IsNullOrEmpty(obj.searchName)) - { - where = where.And(p => p.name.Contains(obj.searchName)); - } - } - - var orderBy = " isRecommend desc,isHot desc"; - if (!string.IsNullOrEmpty(entity.order)) - { - orderBy += "," + entity.order; - } - - var list = await _goodsServices.QueryAgentGoodsPageAsync(where, orderBy, entity.page, entity.limit, false); - if (list.Any()) - { - foreach (var goods in list) - { - goods.images = !string.IsNullOrEmpty(goods.images) ? goods.images.Split(",")[0] : "/static/images/common/empty.png"; - } - } - - //返回数据 - jm.status = true; - jm.data = new - { - list, - className, - entity.page, - list.TotalCount, - list.TotalPages, - entity.limit, - entity.where, - entity.order, - }; - jm.msg = "数据调用成功!"; - - return jm; - } - #endregion - - - - //验证接口==================================================================================================== - - #region 查询用户是否可以成为代理商 - /// - /// 查询用户是否可以成为代理商 - /// - /// - [HttpPost] - [Authorize] - public async Task Info() - { - var jm = await _agentServices.GetInfo(_user.ID); - return jm; - - } - #endregion - - #region 申请成为代理商接口 - /// - /// 申请成为代理商接口 - /// - /// - [HttpPost] - [Authorize] - public async Task ApplyAgent([FromBody] FMAgentApply entity) - { - var jm = new WebApiCallBack(); - - if (entity.agreement != "on") - { - jm.msg = "请勾选代理商协议"; - return jm; - } - var iData = new CoreCmsAgent(); - iData.mobile = entity.mobile; - iData.name = entity.name; - iData.weixin = entity.weixin; - iData.qq = entity.qq; - jm = await _agentServices.AddData(iData, _user.ID); - - return jm; - - } - #endregion - - #region 获取我的下级用户数量 - /// - /// 获取我的下级用户数量 - /// - /// - [HttpPost] - [Authorize] - public async Task GetTeamSum() - { - var jm = new WebApiCallBack(); - - //发展人数 - var first = await _userServices.QueryChildCountAsync(_user.ID, 1); - //订单数 - var second = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID); - - //当月发展人数 - var monthFirst = await _userServices.QueryChildCountAsync(_user.ID, 1, true); - - DateTime dt = DateTime.Now; - //本月第一天时间 - DateTime dtFirst = dt.AddDays(1 - (dt.Day)); - dtFirst = new DateTime(dtFirst.Year, dtFirst.Month, dtFirst.Day, 0, 0, 0); - //获得某年某月的天数 - int year = dt.Date.Year; - int month = dt.Date.Month; - int dayCount = DateTime.DaysInMonth(year, month); - //本月最后一天时间 - DateTime dtLast = dtFirst.AddDays(dayCount - 1); - - var monthSecond = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID && p.createTime > dtFirst && p.createTime < dtLast, true); - - jm.status = true; - jm.data = new - { - count = first, - first, - second, - monthCount = monthFirst, - monthFirst, - monthSecond - }; - - return jm; - } - - #endregion - - #region 获取我的订单统计 - /// - /// 获取我的订单统计 - /// - /// - [HttpPost] - [Authorize] - public async Task GetOrderSum() - { - var jm = new WebApiCallBack(); - - DateTime dt = DateTime.Now; - //本月第一天时间 - DateTime dtFirst = dt.AddDays(1 - (dt.Day)); - dtFirst = new DateTime(dtFirst.Year, dtFirst.Month, dtFirst.Day, 0, 0, 0); - //获得某年某月的天数 - int dayCount = DateTime.DaysInMonth(dt.Date.Year, dt.Date.Month); - //本月最后一天时间 - DateTime dtLast = dtFirst.AddDays(dayCount - 1); - - - //全部订单 - var allOrder = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID, true); - //代购订单 - var procurementServiceOrder = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID && p.buyUserId == _user.ID, true); - //推广订单 - var customerOrder = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID && p.buyUserId != _user.ID, true); - //本月订单 - var monthOrder = await _agentOrderServices.GetCountAsync(p => p.userId == _user.ID && p.createTime > dtFirst && p.createTime < dtLast, true); - - - //全部订单金额 - var allOrderMoney = await _agentOrderServices.GetSumAsync(p => p.userId == _user.ID, p => p.amount, true); - //代购订单金额 - var procurementServiceOrderMoney = await _agentOrderServices.GetSumAsync(p => p.userId == _user.ID && p.buyUserId == _user.ID, p => p.amount, true); - //推广订单金额 - var customerOrderMoney = await _agentOrderServices.GetSumAsync(p => p.userId == _user.ID && p.buyUserId != _user.ID, p => p.amount, true); - //本月订单金额 - var monthOrderMoney = await _agentOrderServices.GetSumAsync(p => p.userId == _user.ID && p.createTime > dtFirst && p.createTime < dtLast, p => p.amount, true); - - jm.status = true; - jm.data = new - { - allOrder, - procurementServiceOrder, - customerOrder, - monthOrder, - allOrderMoney, - procurementServiceOrderMoney, - customerOrderMoney, - monthOrderMoney - }; - - return jm; - } - - #endregion - - #region 我推广的订单 - /// - /// 我推广的订单 - /// - /// - [HttpPost] - [Authorize] - public async Task MyOrder([FromBody] FMPageByIntId entity) - { - var jm = await _agentServices.GetMyOrderList(_user.ID, entity.page, entity.limit, entity.id); - return jm; - } - #endregion - - #region 店铺设置 - /// - /// 店铺设置 - /// - /// - [HttpPost] - [Authorize] - public async Task SetStore([FromBody] FMSetAgentStorePost entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.storeName)) - { - jm.msg = "请填写店铺名称"; - return jm; - } - if (string.IsNullOrEmpty(entity.storeLogo)) - { - jm.msg = "请上传店铺logo"; - return jm; - } - if (string.IsNullOrEmpty(entity.storeBanner)) - { - jm.msg = "请上传店铺banner"; - return jm; - } - - var info = await _agentServices.QueryByClauseAsync(p => p.userId == _user.ID); - if (info != null) - { - info.storeLogo = entity.storeLogo; - info.storeBanner = entity.storeBanner; - info.storeDesc = entity.storeDesc; - info.storeName = entity.storeName; - await _agentServices.UpdateAsync(info); - } - jm.status = true; - jm.msg = "保存成功"; - - return jm; - - } - #endregion - - #region 获取代理商排行 - /// - /// 获取代理商排行 - /// - /// - [HttpPost] - [Authorize] - public async Task GetAgentRanking([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - var list = await _agentServices.QueryRankingPageAsync(entity.page, entity.limit); - - jm.status = true; - jm.data = new - { - data = list, - list.HasNextPage, - list.HasPreviousPage, - list.PageIndex, - list.PageSize, - list.TotalPages, - list.TotalCount, - - }; - - return jm; - } - - #endregion - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/ArticleController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/ArticleController.cs deleted file mode 100644 index 1e32b6a..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/ArticleController.cs +++ /dev/null @@ -1,130 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using System.Linq; -using System.Threading.Tasks; - -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; - -using Microsoft.AspNetCore.Mvc; - -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 文章api控制器 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class ArticleController : ControllerBase - { - - private IHttpContextUser _user; - private readonly ICoreCmsArticleServices _articleServices; - private readonly ICoreCmsArticleTypeServices _articleTypeServices; - - /// - /// 构造函数 - /// - /// - /// - /// - public ArticleController(IHttpContextUser user, ICoreCmsArticleServices articleServices, ICoreCmsArticleTypeServices articleTypeServices) - { - _user = user; - _articleServices = articleServices; - _articleTypeServices = articleTypeServices; - } - - - - #region 获取通知列表 - /// - /// 获取通知列表 - /// - /// - [HttpPost] - public async Task NoticeList([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - var list = await _articleServices.QueryPageAsync(p => p.isDel == false, p => p.createTime, OrderByType.Desc, - entity.page, entity.limit); - jm.status = true; - jm.data = list; - - return jm; - } - - #endregion - - - #region 获取文章列表 - /// - /// 获取文章列表 - /// - /// - [HttpPost] - public async Task GetArticleList([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - var list = await _articleServices.QueryPageAsync(p => p.isDel == false && p.typeId == entity.id, p => p.createTime, OrderByType.Desc, - entity.page, entity.limit); - - var articleType = await _articleTypeServices.QueryAsync(); - var typeName = string.Empty; - if (articleType.Any()) - { - var type = articleType.Find(p => p.id == entity.id); - typeName = type != null ? type.name : ""; - } - jm.status = true; - jm.data = new - { - list, - articleType, - type_name = typeName, - count = list.TotalCount - }; - - return jm; - } - - #endregion - - - /// - /// 获取单个文章内容 - /// - /// - /// - [HttpGet] - public async Task GetArticleDetail([FromQuery] int id) - { - var jm = new WebApiCallBack(); - - var model = await _articleServices.ArticleDetail(id); - if (model == null) - { - jm.msg = "数据获取失败"; - return jm; - } - jm.status = true; - jm.data = model; - return jm; - - } - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/CartController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/CartController.cs deleted file mode 100644 index 6a4a93c..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/CartController.cs +++ /dev/null @@ -1,142 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Model.ViewModels.DTO; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 购物车操作 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class CartController : ControllerBase - { - private readonly IHttpContextUser _user; - private readonly ICoreCmsCartServices _cartServices; - - - /// - /// 构造函数 - /// - public CartController(IHttpContextUser user, ICoreCmsCartServices cartServices) - { - _user = user; - _cartServices = cartServices; - } - - //公共接口==================================================================================================== - - //验证接口==================================================================================================== - - #region 添加单个货品到购物车 - - /// - /// 添加单个货品到购物车 - /// - /// - /// - [HttpPost] - [Authorize] - public async Task AddCart([FromBody] FMCartAdd entity) - { - var jm = await _cartServices.Add(_user.ID, entity.ProductId, entity.Nums, entity.type, entity.cartType, entity.objectId); - return jm; - } - - #endregion 添加单个货品到购物车 - - #region 获取购物车列表====================================================================== - - /// - /// 获取购物车列表 - /// - /// - [HttpPost] - [Authorize] - public async Task GetList([FromBody] FMCartGetList entity) - { - var ids = CommonHelper.StringToIntArray(entity.ids); - //判断免费运费 - var freeFreight = entity.receiptType != 1; - //获取数据 - var jm = await _cartServices.GetCartInfos(_user.ID, ids, entity.type, entity.areaId, entity.point, entity.couponCode, freeFreight, entity.receiptType, entity.objectId); - - return jm; - } - - #endregion 获取购物车列表====================================================================== - - #region 删除购物车信息 - - /// - /// 获取购物车列表 - /// - /// - [HttpPost] - [Authorize] - public async Task DoDelete([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - if (entity.id <= 0) - { - jm.msg = "请提交要删除的货品"; - return jm; - } - jm = await _cartServices.DeleteByIdsAsync(entity.id, _user.ID); - - return jm; - } - - #endregion 删除购物车信息 - - #region 设置购物车商品数量 - - /// - /// 设置购物车商品数量 - /// - /// - [HttpPost] - [Authorize] - public async Task SetCartNum([FromBody] FMSetCartNum entity) - { - var jm = await _cartServices.SetCartNum(entity.id, entity.nums, _user.ID, 2, 1); - return jm; - } - - #endregion 设置购物车商品数量 - - #region 根据提交的数据判断哪些购物券可以使用================================================== - - /// - /// 根据提交的数据判断哪些购物券可以使用 - /// - /// - [HttpPost] - [Authorize] - public async Task GetCartAvailableCoupon([FromBody] FMCouponForUserCouponPost entity) - { - var ids = CommonHelper.StringToIntArray(entity.ids); - var jm = await _cartServices.GetCartAvailableCoupon(_user.ID, ids); - return jm; - } - - #endregion 根据提交的数据判断哪些购物券可以使用================================================== - } -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/CouponController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/CouponController.cs deleted file mode 100644 index 11f3dd5..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/CouponController.cs +++ /dev/null @@ -1,253 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using System; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IRepository.UnitOfWork; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 优惠券接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class CouponController : ControllerBase - { - - private readonly IHttpContextUser _user; - private readonly ICoreCmsCouponServices _couponServices; - private readonly ICoreCmsPromotionServices _promotionServices; - private readonly IUnitOfWork _unionOfWork; - /// - /// 构造函数 - /// - /// - /// - /// - /// - public CouponController(IHttpContextUser user - , ICoreCmsCouponServices couponServices, ICoreCmsPromotionServices promotionServices, IUnitOfWork unionOfWork) - { - _user = user; - _couponServices = couponServices; - _promotionServices = promotionServices; - _unionOfWork = unionOfWork; - } - - //公共接口==================================================================================================== - - #region 获取 可领取的优惠券================================================== - /// - /// 获取 可领取的优惠券 - /// - /// - [HttpPost] - //[Authorize] - public async Task CouponList([FromBody] FMCouponForUserCouponPost entity) - { - var jm = new WebApiCallBack() { msg = "获取失败" }; - - var list = await _promotionServices.GetReceiveCouponList(entity.page, entity.limit); - jm.status = true; - jm.data = list; - jm.msg = "获取成功"; - jm.otherData = new - { - totalCount = 0, - totalPages = 0, - }; - if (list != null && list.Any()) - { - jm.data = list; - jm.otherData = new - { - list.TotalCount, - list.TotalPages - }; - } - return jm; - } - #endregion - - //验证接口==================================================================================================== - - #region 获取优惠券 详情================================================== - /// - /// 获取优惠券 详情 - /// - /// - [HttpPost] - [Authorize] - public async Task CouponDetail([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack() { msg = "获取失败" }; - - if (entity.id == 0) - { - jm.status = false; - jm.msg = GlobalErrorCodeVars.Code15006; - return jm; - } - - var promotionModel = await _promotionServices.QueryByClauseAsync(p => p.id == entity.id); - if (promotionModel != null) - { - jm.status = true; - jm.data = promotionModel; - jm.msg = "获取成功"; - } - return jm; - - } - #endregion - - #region 获取用户已领取的优惠券================================================== - /// - /// 获取用户已领取的优惠券 - /// - /// - [HttpPost] - [Authorize] - public async Task UserCoupon([FromBody] FMCouponForUserCouponPost entity) - { - var jm = await _couponServices.GetMyCoupon(_user.ID, 0, entity.display, entity.page, entity.limit); - return jm; - } - #endregion - - #region 用户领取优惠券================================================== - /// - /// 用户领取优惠券 - /// - /// - [HttpPost] - [Authorize] - public async Task GetCoupon([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - if (entity.id == 0) - { - jm.msg = GlobalErrorCodeVars.Code15006; - return jm; - } - - try - { - _unionOfWork.BeginTran(); - - - //判断优惠券是否可以领取? - var promotionModel = await _promotionServices.ReceiveCoupon(entity.id); - if (promotionModel.status == false) - { - _unionOfWork.RollbackTran(); - return promotionModel; - } - - - - var promotion = (CoreCmsPromotion)promotionModel.data; - if (promotion == null) - { - _unionOfWork.RollbackTran(); - jm.msg = GlobalErrorCodeVars.Code15019; - return jm; - } - - if (promotion.maxNums > 0) - { - //判断用户是否已领取?领取次数 - var couponResult = await _couponServices.GetMyCoupon(_user.ID, entity.id, "all", 1, 9999); - if (couponResult.status && couponResult.code >= promotion.maxNums) - { - _unionOfWork.RollbackTran(); - jm.msg = GlobalErrorCodeVars.Code15018; - return jm; - } - } - - jm = await _couponServices.AddData(_user.ID, entity.id, promotion); - - _unionOfWork.CommitTran(); - - jm.otherData = promotionModel; - - } - catch (Exception e) - { - _unionOfWork.RollbackTran(); - jm.msg = GlobalErrorCodeVars.Code10000; - } - - return jm; - } - #endregion - - #region 用户输入code领取优惠券================================================== - /// - /// 用户输入code领取优惠券 - /// - /// - [HttpPost] - [Authorize] - public async Task GetCouponKey([FromBody] FMCouponForGetCouponKeyPost entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.key)) - { - jm.msg = GlobalErrorCodeVars.Code15006; - return jm; - } - - var coupon = await _couponServices.QueryByClauseAsync(p => p.couponCode == entity.key); - if (coupon == null || coupon.promotionId <= 0) - { - jm.msg = GlobalErrorCodeVars.Code15009; - return jm; - } - - //判断优惠券是否可以领取? - var promotionModel = await _promotionServices.ReceiveCoupon(coupon.promotionId); - if (promotionModel.status == false) - { - return promotionModel; - } - //判断用户是否已领取? - if (promotionModel.data is CoreCmsPromotion { maxNums: > 0 } info) - { - //判断用户是否已领取?领取次数 - var couponResult = await _couponServices.GetMyCoupon(_user.ID, coupon.promotionId, "all", 1, 9999); - if (couponResult.status && couponResult.code > info.maxNums) - { - jm.msg = GlobalErrorCodeVars.Code15018; - return jm; - } - } - // - jm = await _couponServices.ReceiveCoupon(_user.ID, entity.key); - - return jm; - } - #endregion - - } -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/DemoController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/DemoController.cs deleted file mode 100644 index 88cefb8..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/DemoController.cs +++ /dev/null @@ -1,29 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - -using Microsoft.AspNetCore.Mvc; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 默认接口示例 - /// - public class DemoController : ControllerBase - { - /// - /// 默认首页 - /// - /// - public IActionResult Index() - { - return Content("已结束"); - } - } -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/DistributionController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/DistributionController.cs deleted file mode 100644 index bdf07cc..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/DistributionController.cs +++ /dev/null @@ -1,313 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 分销请求接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class DistributionController : ControllerBase - { - private readonly ICoreCmsDistributionOrderServices _distributionOrderServices; - private readonly ICoreCmsDistributionServices _distributionServices; - private readonly ICoreCmsSettingServices _settingServices; - private readonly ICoreCmsUserServices _userServices; - private readonly IHttpContextUser _user; - - /// - /// 构造函数 - /// - public DistributionController(IHttpContextUser user, ICoreCmsDistributionServices distributionServices, - ICoreCmsSettingServices settingServices, ICoreCmsUserServices userServices, - ICoreCmsDistributionOrderServices distributionOrderServices) - { - _user = user; - _distributionServices = distributionServices; - _settingServices = settingServices; - _userServices = userServices; - _distributionOrderServices = distributionOrderServices; - } - - //公共接口==================================================================================================== - - #region 获取店铺信息 - - /// - /// 获取店铺信息 - /// - /// - [HttpPost] - public async Task GetStoreInfo([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - if (entity.id == 0) - { - jm.msg = "店铺信息丢失"; - return jm; - } - - var store = UserHelper.GetUserIdByShareCode(entity.id); - if (store <= 0) - { - jm.msg = "店铺信息丢失"; - return jm; - } - - jm = await _distributionServices.GetStore(store); - return jm; - } - - #endregion - - //验证接口==================================================================================================== - - #region 查询用户是否可以成为分销商 - - /// - /// 查询用户是否可以成为分销商 - /// - /// - [HttpPost] - [Authorize] - public async Task Info() - { - var jm = await _distributionServices.GetInfo(_user.ID, true); - return jm; - } - - #endregion - - #region 申请成为分销商接口 - - /// - /// 申请成为分销商接口 - /// - /// - [HttpPost] - [Authorize] - public async Task ApplyDistribution([FromBody] FMDistributionApply entity) - { - var jm = new WebApiCallBack(); - - if (entity.agreement != "on") - { - jm.msg = "请勾选分销协议"; - return jm; - } - - var iData = new CoreCmsDistribution(); - iData.mobile = entity.mobile; - iData.name = entity.name; - iData.weixin = entity.weixin; - iData.qq = entity.qq; - jm = await _distributionServices.AddData(iData, _user.ID); - - return jm; - } - - #endregion - - #region 我推广的订单 - - /// - /// 我推广的订单 - /// - /// - [HttpPost] - [Authorize] - public async Task MyOrder([FromBody] FMPageByIntId entity) - { - var jm = await _distributionServices.GetMyOrderList(_user.ID, entity.page, entity.limit, entity.id); - return jm; - } - - #endregion - - #region 店铺设置 - - /// - /// 店铺设置 - /// - /// - [HttpPost] - [Authorize] - public async Task SetStore([FromBody] FMSetDistributionStorePost entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.storeName)) - { - jm.msg = "请填写店铺名称"; - return jm; - } - - if (string.IsNullOrEmpty(entity.storeLogo)) - { - jm.msg = "请上传店铺logo"; - return jm; - } - - if (string.IsNullOrEmpty(entity.storeBanner)) - { - jm.msg = "请上传店铺banner"; - return jm; - } - - var info = await _distributionServices.QueryByClauseAsync(p => p.userId == _user.ID); - if (info != null) - { - info.storeLogo = entity.storeLogo; - info.storeBanner = entity.storeBanner; - info.storeDesc = entity.storeDesc; - info.storeName = entity.storeName; - await _distributionServices.UpdateAsync(info); - } - - jm.status = true; - jm.msg = "保存成功"; - - return jm; - } - - #endregion - - #region 获取我的订单统计 - - /// - /// 获取我的订单统计 - /// - /// - [HttpPost] - [Authorize] - public async Task GetOrderSum() - { - var jm = new WebApiCallBack(); - - //全部订单 - var allOrder = await _distributionOrderServices.QueryChildOrderCountAsync(_user.ID, 0); - //一级订单 - var firstOrder = await _distributionOrderServices.QueryChildOrderCountAsync(_user.ID); - //二级订单 - var secondOrder = await _distributionOrderServices.QueryChildOrderCountAsync(_user.ID, 2); - //本月订单 - var monthOrder = await _distributionOrderServices.QueryChildOrderCountAsync(_user.ID, 0, true); - - //全部订单金额 - var allOrderMoney = await _distributionOrderServices.QueryChildOrderMoneySumAsync(_user.ID, 0); - //代购订单金额 - var firstOrderMoney = await _distributionOrderServices.QueryChildOrderMoneySumAsync(_user.ID); - //推广订单金额 - var secondOrderMoney = await _distributionOrderServices.QueryChildOrderMoneySumAsync(_user.ID, 2); - //本月订单金额 - var monthOrderMoney = await _distributionOrderServices.QueryChildOrderMoneySumAsync(_user.ID, 0, true); - - - jm.status = true; - jm.data = new - { - allOrder, - firstOrder, - secondOrder, - monthOrder, - allOrderMoney, - firstOrderMoney, - secondOrderMoney, - monthOrderMoney - }; - - return jm; - } - - #endregion - - #region 获取我的下级用户数量 - - /// - /// 获取我的下级用户数量 - /// - /// - [HttpPost] - [Authorize] - public async Task GetTeamSum() - { - var jm = new WebApiCallBack(); - - //一级统计人数 - var first = await _userServices.QueryChildCountAsync(_user.ID); - //二级发展人数 - var second = await _userServices.QueryChildCountAsync(_user.ID, 2); - - //当月发展一级人数 - var monthFirst = await _userServices.QueryChildCountAsync(_user.ID, 1, true); - //当月发展二级分数 - var monthSecond = await _userServices.QueryChildCountAsync(_user.ID, 2, true); - - jm.status = true; - jm.data = new - { - count = first + second, - first, - second, - monthCount = monthFirst + monthSecond, - monthFirst, - monthSecond - }; - - return jm; - } - - #endregion - - #region 获取分销商排行 - - /// - /// 获取分销商排行 - /// - /// - [HttpPost] - [Authorize] - public async Task GetDistributionRanking([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - var list = await _distributionServices.QueryRankingPageAsync(entity.page, entity.limit); - - jm.status = true; - jm.data = new - { - data = list, - list.HasNextPage, - list.HasPreviousPage, - list.PageIndex, - list.PageSize, - list.TotalPages, - list.TotalCount - }; - - return jm; - } - - #endregion - } -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/FormController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/FormController.cs deleted file mode 100644 index e0a5d08..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/FormController.cs +++ /dev/null @@ -1,79 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 表单接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class FormController : ControllerBase - { - private readonly ICoreCmsFormServices _formServices; - - /// - /// 构造函数 - /// - /// - public FormController(ICoreCmsFormServices formServices) - { - _formServices = formServices; - } - - - #region 万能表单/获取活动商品详情============================================================================= - /// - /// 万能表单/获取活动商品详情 - /// - /// - /// - [HttpPost] - public async Task GetFormDetial([FromBody] FmGetForm entity) - { - var jm = await _formServices.GetFormInfo(entity.id, entity.token); - return jm; - } - #endregion - - - #region 万能表单/提交表单============================================================================= - /// - /// 万能表单/提交表单 - /// - /// - /// - [HttpPost] - public async Task AddSubmit([FromBody] FmAddSubmit entity) - { - var jm = await _formServices.AddSubmit(entity); - - jm.otherData = entity; - - return jm; - } - #endregion - - - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/GoodController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/GoodController.cs deleted file mode 100644 index 1a146c2..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/GoodController.cs +++ /dev/null @@ -1,492 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using AutoMapper; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.Entities.Expression; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Model.ViewModels.DTO; -using CoreCms.Net.Utility.Extensions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; -using SqlSugar; -using CoreCms.Net.Utility.Helper; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 商品相关接口处理 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class GoodController : ControllerBase - { - private IMapper _mapper; - private readonly IHttpContextUser _user; - - private ICoreCmsSettingServices _settingServices; - private ICoreCmsGoodsCategoryServices _goodsCategoryServices; - private ICoreCmsGoodsServices _goodsServices; - private ICoreCmsProductsServices _productsServices; - private ICoreCmsBrandServices _brandServices; - private ICoreCmsOrderItemServices _orderItemServices; - private ICoreCmsGoodsCommentServices _goodsCommentServices; - private ICoreCmsGoodsParamsServices _goodsParamsServices; - private ICoreCmsGoodsCollectionServices _goodsCollectionServices; - private ICoreCmsUserServices _userServices; - private ICoreCmsGoodsCategoryExtendServices _goodsCategoryExtendServices; - - /// - /// 构造函数 - /// - public GoodController(IMapper mapper - , IHttpContextUser user - , ICoreCmsSettingServices settingServices - , ICoreCmsGoodsCategoryServices goodsCategoryServices - , ICoreCmsGoodsServices goodsServices - , ICoreCmsProductsServices productsServices - , ICoreCmsBrandServices brandServices - , ICoreCmsOrderItemServices orderItemServices - , ICoreCmsGoodsCommentServices goodsCommentServices - , ICoreCmsGoodsParamsServices goodsParamsServices - , ICoreCmsGoodsCollectionServices goodsCollectionServices - , ICoreCmsUserServices userServices, ICoreCmsGoodsCategoryExtendServices goodsCategoryExtendServices) - { - _mapper = mapper; - _user = user; - _settingServices = settingServices; - _goodsCategoryServices = goodsCategoryServices; - _goodsServices = goodsServices; - _productsServices = productsServices; - _brandServices = brandServices; - _orderItemServices = orderItemServices; - _goodsCommentServices = goodsCommentServices; - _goodsParamsServices = goodsParamsServices; - _goodsCollectionServices = goodsCollectionServices; - _userServices = userServices; - _goodsCategoryExtendServices = goodsCategoryExtendServices; - } - - //公共接口==================================================================================================== - - #region 获取所有商品分类栏目数据 - /// - /// 获取所有商品分类栏目数据 - /// - /// - [HttpPost] - public async Task GetAllCategories() - { - var jm = new WebApiCallBack() { status = true }; - - var data = await _goodsCategoryServices.QueryListByClauseAsync(p => p.isShow == true, p => p.sort, - OrderByType.Asc); - var wxGoodCategoryDto = new List(); - - var parents = data.Where(p => p.parentId == 0).ToList(); - if (parents.Any()) - { - parents.ForEach(p => - { - var model = new WxGoodCategoryDto(); - model.id = p.id; - model.name = p.name; - model.imageUrl = !string.IsNullOrEmpty(p.imageUrl) ? p.imageUrl : "/static/images/common/empty.png"; - model.sort = p.sort; - - var childs = data.Where(p => p.parentId == model.id).ToList(); - if (childs.Any()) - { - var childsList = new List(); - childs.ForEach(o => - { - childsList.Add(new WxGoodCategoryChild() - { - id = o.id, - imageUrl = !string.IsNullOrEmpty(o.imageUrl) ? o.imageUrl : "/static/images/common/empty.png", - name = o.name, - sort = o.sort - }); - }); - model.child = childsList; - } - wxGoodCategoryDto.Add(model); - }); - } - jm.status = true; - jm.data = wxGoodCategoryDto; - - return jm; - } - - #endregion - - #region 根据查询条件获取分页数据============================================================ - /// - /// 根据查询条件获取分页数据 - /// - /// - [HttpPost] - public async Task GetGoodsPageList([FromBody] FMPageByWhereOrder entity) - { - var jm = new WebApiCallBack(); - - var where = PredicateBuilder.True(); - where = where.And(p => p.isDel == false); - where = where.And(p => p.isMarketable == true); - - var className = string.Empty; - if (!string.IsNullOrWhiteSpace(entity.where)) - { - var obj = JsonConvert.DeserializeAnonymousType(entity.where, new - { - priceFrom = "", - priceTo = "", - catId = "", - brandId = "", - labelId = "", - searchName = "", - }); - - if (!string.IsNullOrWhiteSpace(obj.priceFrom)) - { - var priceF = obj.priceFrom.ObjectToDouble(0); - if (priceF >= 0) - { - var f = Convert.ToDecimal(priceF); - where = where.And(p => p.price >= f); - } - } - if (!string.IsNullOrWhiteSpace(obj.priceTo)) - { - var priceT = obj.priceTo.ObjectToDouble(0); - if (priceT > 0) - { - var f = Convert.ToDecimal(priceT); - where = where.And(p => p.price <= f); - } - } - if (!string.IsNullOrWhiteSpace(obj.catId)) - { - var catId = obj.catId.ObjectToInt(0); - if (catId > 0) - { - var category = await _goodsCategoryServices.QueryByIdAsync(catId, true); - if (category != null) - { - className = category.name; - } - - var categories = await _goodsCategoryServices.QueryAsync(true); - var ids = GoodsHelper.GetChildIds(categories, catId); - //扩展分类 - var extends = await _goodsCategoryExtendServices.QueryListByClauseAsync(p => p.goodsCategroyId == catId); - if (extends.Any()) - { - var extGoodIds = extends.Select(p => p.goodsId).ToList(); - where = where.And(p => ids.Contains(p.goodsCategoryId) || extGoodIds.Contains(p.id)); - } - else - { - where = where.And(p => ids.Contains(p.goodsCategoryId)); - } - } - } - if (!string.IsNullOrWhiteSpace(obj.brandId)) - { - var brandId = obj.brandId.ObjectToInt(0); - if (brandId > 0) - { - where = where.And(p => p.brandId == brandId); - } - } - if (!string.IsNullOrWhiteSpace(obj.labelId)) - { - where = where.And(p => (',' + p.labelIds.Trim(',') + ',').Contains(',' + obj.labelId.Trim(',') + ',')); - } - if (!string.IsNullOrWhiteSpace(obj.searchName)) - { - where = where.And(p => p.name.Contains(obj.searchName)); - } - } - - //获取数据 - var list = await _goodsServices.QueryPageForLinqAsync(where, entity.order, entity.page, entity.limit, false); - if (list.Any()) - { - foreach (var goods in list) - { - goods.images = !string.IsNullOrEmpty(goods.images) ? goods.images.Split(",")[0] : "/static/images/common/empty.png"; - } - } - - //获取品牌 - var brands = await _brandServices.QueryListByClauseAsync(p => p.isShow == true, p => p.sort, OrderByType.Desc); - - - //返回数据 - jm.status = true; - jm.data = new - { - list, - className, - entity.page, - list.TotalCount, - list.TotalPages, - entity.limit, - entity.where, - entity.order, - brands - }; - jm.msg = "数据调用成功!"; - - return jm; - } - #endregion - - #region 获取商品详情====================================================================== - /// - /// 获取商品详情 - /// - /// - /// - [HttpPost] - public async Task GetDetial([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - var userId = 0; - if (_user != null) - { - userId = _user.ID; - } - - var model = await _goodsServices.GetGoodsDetial(entity.id, userId, false); - if (model == null) - { - jm.msg = "商品获取失败"; - return jm; - } - - jm.status = true; - jm.msg = "获取商品详情成功"; - jm.data = model; - jm.methodDescription = JsonConvert.SerializeObject(_user); - - return jm; - } - #endregion - - #region 获取单个货品信息====================================================================== - /// - /// 获取单个货品信息 - /// - /// - /// - [HttpPost] - public async Task GetProductInfo([FromBody] FMGetProductInfo entity) - { - var jm = new WebApiCallBack(); - - var userId = 0; - if (_user != null) - { - userId = _user.ID; - } - - bool bl = entity.type == "pinTuan" || entity.type == "group"; - - var getProductInfo = await _productsServices.GetProductInfo(entity.id, bl, userId, entity.type, entity.groupId); - if (getProductInfo == null) - { - jm.msg = "获取单个货品失败"; - return jm; - } - - jm.status = true; - jm.msg = "获取单个货品成功"; - jm.data = getProductInfo; - - return jm; - } - - #endregion - - #region 获取商品评价列表分页数据====================================================================== - /// - /// 获取商品评价列表分页数据 - /// - /// - /// - [HttpPost] - public async Task GetGoodsComment([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - //获取数据 - var list = await _goodsCommentServices.QueryPageAsync(p => p.goodsId == entity.id && p.isDisplay == true, p => p.createTime, OrderByType.Desc, entity.page, entity.limit); - - if (list.Any()) - { - foreach (var item in list) - { - item.imagesArr = !string.IsNullOrEmpty(item.images) ? item.images.Split(",") : null; - } - } - - jm.status = true; - jm.msg = "获取评论成功"; - jm.data = new - { - list, - commentsCount = list.TotalCount, - totalPages = list.TotalPages - }; - - return jm; - } - #endregion - - #region 获取商品参数====================================================================== - /// - /// 获取单个商品参数 - /// - /// - /// - [HttpPost] - public async Task GetGoodsParams([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - //获取数据 - var goods = await _goodsServices.QueryByIdAsync(entity.id); - if (goods == null) - { - jm.msg = GlobalConstVars.DataisNo; - return jm; - } - var list = new List(); - var goodsParams = await _goodsParamsServices.QueryAsync(); - - if (!string.IsNullOrEmpty(goods.parameters)) - { - var arrItem = goods.parameters.Split("|"); - foreach (var item in arrItem) - { - if (!item.Contains(":")) continue; - - var childArr = item.Split(":"); - if (childArr.Length == 2) - { - var paramsId = Convert.ToInt32(childArr[0]); - var paramsModel = goodsParams.First(p => p.id == paramsId); - if (paramsModel != null) - { - list.Add(new WxNameValueDto() - { - name = paramsModel.name, - value = childArr[1] - }); - } - } - } - } - jm.status = true; - jm.msg = "获取商品参数成功"; - jm.data = list; - - return jm; - } - #endregion - - #region 获取随机推荐商品================================================== - /// - /// 获取随机推荐商品 - /// - /// - [HttpPost] - public async Task GetGoodsRecommendList([FromBody] FMIntId entity) - { - if (entity.id <= 0) - { - entity.id = 10; - } - - var bl = entity.data.ObjectToBool(); - - var jm = new WebApiCallBack() - { - status = true, - code = 0, - msg = "获取成功", - data = await _goodsServices.GetGoodsRecommendList(entity.id, bl) - }; - return jm; - } - #endregion - - //验证接口==================================================================================================== - - - #region 根据Token获取商品详情====================================================================== - /// - /// 根据Token获取商品详情 - /// - /// - /// - [HttpPost] - [Authorize] - public async Task GetDetialByToken([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - var userId = 0; - if (_user != null) - { - userId = _user.ID; - } - - var model = await _goodsServices.GetGoodsDetial(entity.id, userId, false); - if (model == null) - { - jm.msg = "商品获取失败"; - return jm; - } - - await _goodsServices.UpdateAsync(p => new CoreCmsGoods() { viewCount = p.viewCount + 1 }, - p => p.id == entity.id); - - - jm.status = true; - jm.msg = "获取商品详情成功"; - jm.data = model; - jm.methodDescription = JsonConvert.SerializeObject(_user); - - return jm; - } - #endregion - - - - } -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/GroupController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/GroupController.cs deleted file mode 100644 index cbeab97..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/GroupController.cs +++ /dev/null @@ -1,84 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 团购调用接口数据 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class GroupController : ControllerBase - { - - private readonly IHttpContextUser _user; - private readonly ICoreCmsPromotionServices _coreCmsPromotionServices; - private ICoreCmsGoodsServices _goodsServices; - - - /// - /// 构造函数 - /// - public GroupController(IHttpContextUser user, ICoreCmsPromotionServices coreCmsPromotionServices, ICoreCmsGoodsServices goodsServices) - { - _user = user; - _coreCmsPromotionServices = coreCmsPromotionServices; - _goodsServices = goodsServices; - } - - - //公共接口==================================================================================================== - - #region 获取秒杀团购列表=========================================================== - /// - /// 获取秒杀团购列表 - /// - /// - [HttpPost] - public async Task GetList([FromBody] FMGroupGetListPost entity) - { - var jm = await _coreCmsPromotionServices.GetGroupList(entity.type, _user.ID, entity.status, entity.page, entity.limit); - - return jm; - } - #endregion - - #region 获取秒杀团购详情=========================================================== - /// - /// 获取秒杀团购详情 - /// - /// - [HttpPost] - public async Task GetGoodsDetial([FromBody] FMGetGoodsDetial entity) - { - var jm = await _coreCmsPromotionServices.GetGroupDetail(entity.id, 0, "group", entity.groupId); - return jm; - } - #endregion - - //验证接口==================================================================================================== - - - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/PageController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/PageController.cs deleted file mode 100644 index 236e505..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/PageController.cs +++ /dev/null @@ -1,162 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Model.ViewModels.DTO; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 页面接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class PageController : ControllerBase - { - private IMapper _mapper; - private readonly ICoreCmsSettingServices _settingServices; - private readonly ICoreCmsPagesServices _pagesServices; - private readonly ICoreCmsOrderServices _orderServices; - private readonly ICoreCmsUserServices _userServices; - - /// - /// 构造函数 - /// - public PageController(IMapper mapper - , ICoreCmsSettingServices settingServices - , ICoreCmsPagesServices pagesServices - , ICoreCmsOrderServices orderServices - , ICoreCmsUserServices userServices) - { - _mapper = mapper; - _settingServices = settingServices; - _pagesServices = pagesServices; - _orderServices = orderServices; - _userServices = userServices; - } - - //公共接口==================================================================================================== - - #region 获取页面布局数据============================================================= - - /// - /// 获取页面布局数据 - /// - /// - /// - [HttpPost] - [Description("获取页面布局数据")] - public async Task GetPageConfig([FromBody] FMWxPost entity) - { - var jm = await _pagesServices.GetPageConfig(entity.code); - return jm; - } - #endregion - - #region 获取用户购买记录============================================================= - - /// - /// 获取用户购买记录 - /// - [HttpPost] - [Description("获取用户购买记录")] - public async Task GetRecod([FromBody] FMGetRecodPost entity) - { - var jm = new WebApiCallBack() { status = true, msg = "获取成功", otherData = entity }; - - /*** - * 随机数 - * 其它随机数据,需要自己补充 - */ - //logo作为头像 - Random rand = new Random(); - - var allConfigs = await _settingServices.GetConfigDictionaries(); - - var avatar = CommonHelper.GetConfigDictionary(allConfigs, SystemSettingConstVars.ShopLogo); - var names = new string[] { "无人像你", "啭裑①羣豞", "朕射妳无罪", "骑着蜗牛狂奔", "残孤星", "上网可以,别开QVOD", "请把QQ留下!", "蹭网可以,一小时两块钱", "I~在。哭泣", "不倾国倾城只倾他一人", "你再发光我就拔你插头", "家,世间最温暖的地方", "挥着鸡翅膀的女孩", "难不难过都是一个人过", "原谅我盛装出席只为错过你", "残孤星", "只适合被遗忘", "爱情,算个屁丶", "执子辶掱", "朕今晚翻你牌子", "①苆兜媞命", "中华一样的高傲", "始于心动止于枯骨", "我们幸福呢", "表白失败,勿扰", "髮型吥能亂", "陽咣丅啲憂喐", "你棺材是翻盖的还是滑盖的", "孤枕", "泪颜葬相思", "喵星人", "超拽霸气的微博名字", "晚安晚安晚晚难安", "却输给了秒", "为什么我吃德芙没有黑丝飘", "请输入我大" }; - var listUsers = new List(); - - foreach (var itemName in names) - { - var min = rand.Next(100, 1000); - var createTime = DateTime.Now.AddMinutes(-min); - listUsers.Add(new RandUser() - { - avatar = avatar, - createTime = CommonHelper.TimeAgo(createTime), - nickname = itemName, - desc = "下单成功", - dt = createTime - }); - } - - if (entity.type == "home") - { - //数据库里面随机取出来几条数据 - var orders = await _orderServices.QueryListByClauseAsync(p => p.isdel == false, 20, p => p.createTime, - OrderByType.Desc); - if (orders != null && orders.Any()) - { - Random rd = new Random(); - var index = rd.Next(orders.Count); - var orderItem = orders[index]; - if (orderItem != null) - { - var user = await _userServices.QueryByIdAsync(orderItem.userId); - if (user != null && !string.IsNullOrEmpty(user.nickName)) - { - jm.data = new RandUser() - { - avatar = !string.IsNullOrEmpty(user.avatarImage) ? user.avatarImage : avatar, - createTime = CommonHelper.TimeAgo(orderItem.createTime), - nickname = user.nickName, - desc = "下单成功", - dt = orderItem.createTime - }; - } - } - } - else - { - Random rd = new Random(); - var listI = rd.Next(listUsers.Count); - jm.data = listUsers[listI]; - } - } - return jm; - } - #endregion - - - //验证接口==================================================================================================== - - } - - - -} \ No newline at end of file diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/PinTuanController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/PinTuanController.cs deleted file mode 100644 index 05d66f6..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/PinTuanController.cs +++ /dev/null @@ -1,175 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Utility.Extensions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 拼团接口 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class PinTuanController : ControllerBase - { - - private readonly IHttpContextUser _user; - private readonly ICoreCmsPinTuanGoodsServices _pinTuanGoodsServices; - private readonly ICoreCmsPinTuanRuleServices _pinTuanRuleServices; - private readonly ICoreCmsProductsServices _productsServices; - private readonly ICoreCmsPinTuanRecordServices _pinTuanRecordServices; - private readonly ICoreCmsGoodsServices _goodsServices; - - - /// - /// 构造函数 - /// - public PinTuanController(IHttpContextUser user - , ICoreCmsPinTuanGoodsServices pinTuanGoodsServices - , ICoreCmsPinTuanRuleServices pinTuanRuleServices - , ICoreCmsProductsServices productsServices - , ICoreCmsPinTuanRecordServices pinTuanRecordServices, ICoreCmsGoodsServices goodsServices) - { - _user = user; - _pinTuanGoodsServices = pinTuanGoodsServices; - _pinTuanRuleServices = pinTuanRuleServices; - _productsServices = productsServices; - _pinTuanRecordServices = pinTuanRecordServices; - _goodsServices = goodsServices; - } - - - #region 拼团列表 - /// - /// 拼团列表 - /// - /// - [HttpPost] - public async Task GetList([FromBody] FMIntId entity) - { - WebApiCallBack jm; - - var userId = 0; - if (_user != null) - { - userId = _user.ID; - } - var id = 0; - if (entity.id > 0) - { - id = entity.id; - } - jm = await _pinTuanRuleServices.GetPinTuanList(id, userId); - return jm; - - } - - #endregion - - #region 获取拼团商品信息 - /// - /// 获取拼团商品信息 - /// - /// - [HttpPost] - public async Task GetGoodsInfo([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - var userId = 0; - if (_user != null) - { - userId = _user.ID; - } - var pinTuanStatus = entity.data.ObjectToInt(1); - - jm.status = true; - jm.msg = "获取详情成功"; - jm.data = await _pinTuanGoodsServices.GetGoodsInfo(entity.id, userId, pinTuanStatus); - - return jm; - - } - - #endregion - - #region 获取货品信息 - /// - /// 获取货品信息 - /// - /// - [HttpPost] - public async Task GetProductInfo([FromBody] FMGetProductInfo entity) - { - var jm = new WebApiCallBack(); - - var products = await _productsServices.GetProductInfo(entity.id, false, 0, entity.type); - if (products == null) - { - jm.msg = GlobalErrorCodeVars.Code10000; - return jm; - } - //把拼团的一些属性等加上 - var info = await _pinTuanRuleServices.QueryMuchFirstAsync( - (join1, join2) => new object[] { JoinType.Left, join1.id == join2.ruleId }, - (join1, join2) => join1, (join1, join2) => join2.goodsId == products.goodsId); - - if (info == null) - { - jm.msg = GlobalErrorCodeVars.Code10000; - return jm; - } - products.pinTuanRule = info; - jm.status = true; - jm.data = products; - return jm; - - } - - #endregion - - #region 根据订单id取拼团信息,用在订单详情页 - /// - /// 根据订单id取拼团信息,用在订单详情页 - /// - /// - [HttpPost] - public async Task GetPinTuanTeam([FromBody] FMGetPinTuanTeamPost entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.orderId) && entity.teamId == 0) - { - jm.msg = GlobalErrorCodeVars.Code15606; - return jm; - } - jm = await _pinTuanRecordServices.GetTeamList(entity.teamId, entity.orderId); - - return jm; - } - - #endregion - - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/ServiceController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/ServiceController.cs deleted file mode 100644 index 422911e..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/ServiceController.cs +++ /dev/null @@ -1,404 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.Entities.Expression; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Utility.Extensions; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 服务卡控制器 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class ServiceController : ControllerBase - { - private readonly ICoreCmsServicesServices _servicesServices; - private readonly ICoreCmsUserServicesOrderServices _userServicesOrderServices; - private readonly ICoreCmsUserServicesTicketServices _userServicesTicketServices; - private readonly ICoreCmsUserServices _userServices; - private readonly ICoreCmsUserServicesTicketVerificationLogServices _ticketVerificationLogServices; - private readonly ICoreCmsClerkServices _clerkServices; - private readonly ICoreCmsStoreServices _storeServices; - private readonly ICoreCmsUserGradeServices _userGradeServices; - - - private readonly IHttpContextUser _user; - - /// - /// 构造函数 - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ServiceController(ICoreCmsServicesServices servicesServices, IHttpContextUser user, ICoreCmsUserServicesOrderServices userServicesOrderServices, ICoreCmsUserServicesTicketServices userServicesTicketServices, ICoreCmsUserServices userServices, ICoreCmsClerkServices clerkServices, ICoreCmsUserServicesTicketVerificationLogServices ticketVerificationLogServices, ICoreCmsStoreServices storeServices, ICoreCmsUserGradeServices userGradeServices) - { - _servicesServices = servicesServices; - _user = user; - _userServicesOrderServices = userServicesOrderServices; - _userServicesTicketServices = userServicesTicketServices; - _userServices = userServices; - _clerkServices = clerkServices; - _ticketVerificationLogServices = ticketVerificationLogServices; - _storeServices = storeServices; - _userGradeServices = userGradeServices; - } - - - #region 取得服务卡列表信息 - /// - /// 取得服务卡列表信息 - /// - /// - [HttpPost] - //[Authorize] - public async Task GetPageList([FromBody] FMPageByIntId entity) - { - var jm = new WebApiCallBack(); - - var dt = DateTime.Now; - var where = PredicateBuilder.True(); - - where = where.And(p => p.status == (int)GlobalEnumVars.ServicesStatus.Shelve); - where = where.And(p => p.amount > 0); - where = where.And(p => p.startTime < dt && p.endTime > dt); - - var list = await _servicesServices.QueryPageAsync(where, p => p.createTime, OrderByType.Desc, entity.page, entity.limit); - - if (list.Any()) - { - var storesAll = await _storeServices.QueryAsync(); - var userGradesAll = await _userGradeServices.QueryAsync(); - - foreach (var data in list) - { - TimeSpan ts = data.endTime.Subtract(dt); - data.timestamp = (int)ts.TotalSeconds; - - if (!string.IsNullOrEmpty(data.consumableStore)) - { - var consumableStoreStr = CommonHelper.GetCaptureInterceptedText(data.consumableStore, ","); - var consumableStoreIds = CommonHelper.StringToIntArray(consumableStoreStr); - if (consumableStoreIds.Any()) - { - var stores = storesAll.Where(p => consumableStoreIds.Contains(p.id)).ToList(); - data.consumableStores = stores.Select(p => p.storeName).ToList(); - } - } - - if (!string.IsNullOrEmpty(data.allowedMembership)) - { - var allowedMembershipStr = CommonHelper.GetCaptureInterceptedText(data.allowedMembership, ","); - var allowedMembershipIds = CommonHelper.StringToIntArray(allowedMembershipStr); - if (allowedMembershipIds.Any()) - { - var userGrades = userGradesAll.Where(p => allowedMembershipIds.Contains(p.id)).ToList(); - data.allowedMemberships = userGrades.Select(p => p.title).ToList(); - } - } - } - } - - jm.status = true; - jm.data = new - { - list = list, - count = list.TotalCount, - }; - return jm; - - } - - #endregion - - #region 获取服务卡详情 - /// - /// 获取服务卡详情 - /// - /// - [HttpPost] - //[Authorize] - public async Task GetDetails([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - var data = await _servicesServices.QueryByClauseAsync(p => p.id == entity.id); - - if (data != null) - { - var dt = DateTime.Now; - TimeSpan ts = data.endTime.Subtract(dt); - data.timestamp = (int)ts.TotalSeconds; - - if (!string.IsNullOrEmpty(data.consumableStore)) - { - var consumableStoreStr = CommonHelper.GetCaptureInterceptedText(data.consumableStore, ","); - var consumableStoreIds = CommonHelper.StringToIntArray(consumableStoreStr); - if (consumableStoreIds.Any()) - { - var stores = await _storeServices.QueryListByClauseAsync(p => consumableStoreIds.Contains(p.id)); - data.consumableStores = stores.Select(p => p.storeName).ToList(); - } - } - - if (!string.IsNullOrEmpty(data.allowedMembership)) - { - var allowedMembershipStr = CommonHelper.GetCaptureInterceptedText(data.allowedMembership, ","); - var allowedMembershipIds = CommonHelper.StringToIntArray(allowedMembershipStr); - if (allowedMembershipIds.Any()) - { - var userGrades = await _userGradeServices.QueryListByClauseAsync(p => allowedMembershipIds.Contains(p.id)); - data.allowedMemberships = userGrades.Select(p => p.title).ToList(); - } - } - } - - jm.status = true; - jm.data = data; - return jm; - - } - - #endregion - - //验证接口==================================================================================================== - - #region 添加服务订单 - /// - /// 取得服务卡列表信息 - /// - /// - [HttpPost] - [Authorize] - public async Task AddServiceOrder([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack(); - - var data = await _servicesServices.QueryByClauseAsync(p => p.id == entity.id); - - if (data == null) - { - jm.msg = "服务数据获取失败"; - return jm; - } - - var user = await _userServices.QueryByIdAsync(_user.ID); - if (user == null) - { - jm.msg = "用户数据获取失败"; - return jm; - } - - if (!data.allowedMembership.Contains("," + user.grade + ",")) - { - jm.msg = "您所在的用户级别不支持购买"; - return jm; - } - - var order = new CoreCmsUserServicesOrder(); - order.serviceOrderId = CommonHelper.GetSerialNumberType((int)GlobalEnumVars.SerialNumberType.服务订单编号); - order.userId = _user.ID; - order.servicesId = entity.id; - order.isPay = false; - order.status = (int)GlobalEnumVars.ServicesOrderStatus.正常; - order.createTime = DateTime.Now; - - var bl = await _userServicesOrderServices.InsertAsync(order) > 0; - - jm.status = bl; - jm.data = order.serviceOrderId; - return jm; - - } - - - - #endregion - - #region 店铺核销的服务券列表 - /// - /// 店铺核销的服务券列表 - /// - /// - [HttpPost] - [Authorize] - public async Task VerificationPageList([FromBody] FMPageByIntId entity) - { - var jm = await _ticketVerificationLogServices.GetVerificationLogs(_user.ID, entity.page, entity.limit); - return jm; - } - #endregion - - #region 软删除服务券核销单数据 - /// - /// 软删除服务券核销单数据 - /// - /// - [HttpPost] - [Authorize] - public async Task LogDelete([FromBody] FMIntId entity) - { - var jm = await _ticketVerificationLogServices.LogDelete(entity.id, _user.ID); - return jm; - } - #endregion - - - #region 获取单个提货单详情 - /// - /// 获取单个提货单详情 - /// - /// - [HttpPost] - [Authorize] - public async Task GetTicketInfo([FromBody] FMStringId entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.id)) - { - jm.msg = "请提交查询数据关键词"; - return jm; - } - - var ticket = await _userServicesTicketServices.QueryByClauseAsync(p => p.redeemCode == entity.id); - if (ticket == null) - { - jm.msg = "未查询到服务券"; - return jm; - } - - ticket.statusStr = EnumHelper.GetEnumDescriptionByValue(ticket.status); - - var service = await _servicesServices.QueryByClauseAsync(p => p.id == ticket.serviceId); - var serviceOrder = - await _userServicesOrderServices.QueryByClauseAsync(p => p.serviceOrderId == ticket.serviceOrderId); - - jm.status = true; - jm.data = new - { - ticket, - service, - serviceOrder - }; - - return jm; - } - #endregion - - #region 核销服务券 - /// - /// 核销服务券 - /// - /// - [HttpPost] - [Authorize] - public async Task VerificationTicket([FromBody] FMStringId entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.id)) - { - jm.msg = "请提交查询数据关键词"; - return jm; - } - var ticket = await _userServicesTicketServices.QueryByClauseAsync(p => p.redeemCode == entity.id); - if (ticket == null) - { - jm.msg = "未查询到服务券"; - return jm; - } - - if (ticket.status != (int)GlobalEnumVars.ServicesTicketStatus.Normal) - { - jm.msg = "服务券状态不支持核销"; - return jm; - } - - var service = await _servicesServices.QueryByIdAsync(ticket.serviceId); - if (service == null) - { - jm.msg = "服务项目获取失败"; - return jm; - } - - var user = await _userServices.QueryByIdAsync(_user.ID); - if (user == null) - { - jm.msg = "未获取到审核权限"; - return jm; - } - - var clerk = await _clerkServices.QueryByClauseAsync(p => p.userId == user.id); - if (clerk == null) - { - jm.msg = "非门店店员无权限核验"; - return jm; - } - - if (!service.consumableStore.Contains("," + clerk.storeId + ",")) - { - jm.msg = "您所在的门店无权核销此券"; - return jm; - } - - //开始更新数据 - var log = new CoreCmsUserServicesTicketVerificationLog - { - storeId = clerk.storeId, - verificationUserId = _user.ID, - ticketId = ticket.id, - ticketRedeemCode = ticket.redeemCode, - verificationTime = DateTime.Now, - serviceId = ticket.serviceId, - isDel = false - }; - - ticket.status = (int)GlobalEnumVars.ServicesTicketStatus.Verification; - ticket.verificationTime = DateTime.Now; - ticket.isVerification = true; - var up = await _userServicesTicketServices.UpdateAsync(ticket); - var bl = false; - if (up) - { - bl = await _ticketVerificationLogServices.InsertAsync(log) > 0; - } - jm.status = up && bl; - jm.msg = jm.status ? "核销成功" : "核销失败"; - - return jm; - } - #endregion - - - } -} diff --git a/server/CoreCms.Net.Web.WebApi/Controllers/StoreController.cs b/server/CoreCms.Net.Web.WebApi/Controllers/StoreController.cs deleted file mode 100644 index 454a74a..0000000 --- a/server/CoreCms.Net.Web.WebApi/Controllers/StoreController.cs +++ /dev/null @@ -1,365 +0,0 @@ -/*********************************************************************** - * Project: CoreCms - * ProjectName: 核心内容管理系统 - * Web: https://www.corecms.net - * Author: 大灰灰 - * Email: jianweie@163.com - * CreateTime: 2021/1/31 21:45:10 - * Description: 暂无 - ***********************************************************************/ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CoreCms.Net.Auth.HttpContextUser; -using CoreCms.Net.Configuration; -using CoreCms.Net.IServices; -using CoreCms.Net.Model.Entities; -using CoreCms.Net.Model.Entities.Expression; -using CoreCms.Net.Model.FromBody; -using CoreCms.Net.Model.ViewModels.UI; -using CoreCms.Net.Model.ViewModels.DTO; -using CoreCms.Net.Utility.Extensions; -using CoreCms.Net.Utility.Helper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using SqlSugar; - -namespace CoreCms.Net.Web.WebApi.Controllers -{ - /// - /// 门店调用接口数据 - /// - [Route("api/[controller]/[action]")] - [ApiController] - public class StoreController : ControllerBase - { - private readonly IHttpContextUser _user; - private readonly ICoreCmsStoreServices _storeServices; - private readonly ICoreCmsClerkServices _clerkServices; - private readonly ICoreCmsSettingServices _settingServices; - private readonly ICoreCmsBillLadingServices _billLadingServices; - private readonly ICoreCmsOrderServices _orderServices; - - - /// - /// 构造函数 - /// - public StoreController(IHttpContextUser user - , ICoreCmsStoreServices storeServices - , ICoreCmsClerkServices clerkServices - , ICoreCmsSettingServices settingServices - , ICoreCmsBillLadingServices billLadingServices, ICoreCmsOrderServices orderServices) - { - _user = user; - _storeServices = storeServices; - _clerkServices = clerkServices; - _settingServices = settingServices; - _billLadingServices = billLadingServices; - _orderServices = orderServices; - } - - //公共接口====================================================================================================== - - #region 获取默认的门店 - /// - /// 获取默认的门店 - /// - /// - [HttpPost] - public async Task GetDefaultStore() - { - var jm = new WebApiCallBack(); - - var ship = await _storeServices.QueryByClauseAsync(p => p.isDefault == true); - jm.status = true; - jm.data = ship; - - return jm; - } - #endregion - - #region 获取门店列表数据 - /// - /// 获取门店列表数据 - /// - /// - [HttpPost] - public async Task GetStoreList([FromBody] FMGetStoreQueryPageByCoordinate entity) - { - var jm = new WebApiCallBack(); - try - { - var where = PredicateBuilder.True(); - - if (!string.IsNullOrEmpty(entity.key)) - { - where = where.And(p => p.storeName.Contains(entity.key)); - } - - jm.status = true; - - var data = await _storeServices.QueryPageAsyncByCoordinate(where, p => p.distance, OrderByType.Asc, entity.page, entity.limit, entity.latitude, entity.longitude); - - foreach (var item in data) - { - if (item.distance > 0) - { - if (item.distance > 1000) - { - item.distanceStr = Math.Round(item.distance / 1000, 2) + "km"; - } - else - { - item.distanceStr = Math.Round(item.distance, 2) + "m"; - } - } - else - { - item.distanceStr = "未知"; - } - } - jm.data = data; - jm.otherData = new - { - totalCount = data.TotalCount, - totalPages = data.TotalPages, - }; - } - catch (Exception e) - { - - jm.msg = GlobalConstVars.DataHandleEx; - jm.data = e.ToString(); - } - return jm; - } - #endregion - - #region 获取推荐关键词 - /// - /// 获取推荐关键词 - /// - /// - [HttpPost] - public async Task GetRecommendKeys() - { - var jm = new WebApiCallBack(); - - var allConfigs = await _settingServices.GetConfigDictionaries(); - var recommendKeysStr = CommonHelper.GetConfigDictionary(allConfigs, SystemSettingConstVars.RecommendKeys); - jm.status = true; - jm.msg = "获取成功"; - jm.data = !string.IsNullOrEmpty(recommendKeysStr) ? recommendKeysStr.Split("|") : new string[] { }; - - return jm; - } - #endregion - - #region 判断是否开启门店自提 - /// - /// 判断是否开启门店自提 - /// - /// - [HttpPost] - public async Task GetStoreSwitch() - { - var jm = new WebApiCallBack { status = true, msg = "获取成功" }; - - var allConfigs = await _settingServices.GetConfigDictionaries(); - jm.data = CommonHelper.GetConfigDictionary(allConfigs, SystemSettingConstVars.StoreSwitch).ObjectToInt(2); ; - return jm; - } - #endregion - - #region 根据序列获取门店数据 - /// - /// 根据序列获取门店数据 - /// - /// - [HttpPost] - public async Task GetStoreById([FromBody] FMIntId entity) - { - var jm = new WebApiCallBack - { - status = true, - msg = "获取成功", - data = await _storeServices.QueryByClauseAsync(p => p.id == entity.id) - }; - return jm; - } - #endregion - - //验证接口====================================================================================================== - - #region 判断访问用户是否是店员 - /// - /// 判断访问用户是否是店员 - /// - /// - [HttpPost] - [Authorize] - public async Task IsClerk() - { - var jm = await _clerkServices.IsClerk(_user.ID); - return jm; - } - #endregion - - #region 根据用户序列获取门店数据 - /// - /// 根据用户序列获取门店数据 - /// - /// - [HttpPost] - [Authorize] - public async Task GetStoreByUserId() - { - var jm = new WebApiCallBack - { - status = true, - msg = "获取成功", - data = await _storeServices.GetStoreByUserId(_user.ID) - }; - return jm; - } - #endregion - - #region 获取个人订单列表 - - /// - /// 获取个人订单列表 - /// - /// - [HttpPost] - [Authorize] - public async Task GetOrderPageByMerchant([FromBody] GetOrderPageByMerchantPost entity) - { - var jm = new WebApiCallBack(); - - var store = await _storeServices.GetStoreByUserId(_user.ID); - if (store != null) - { - jm = await _orderServices.GetOrderPageByMerchant(entity.dateType, entity.date, entity.status, entity.storeId, entity.page, entity.limit); - } - else - { - jm.status = false; - jm.msg = "你不是店员"; - } - - return jm; - } - - #endregion - - #region 搜索订单 - - /// - /// 搜索订单 - /// - /// - [HttpPost] - [Authorize] - public async Task GetOrderPageByMerchantSearch([FromBody] GetOrderPageByMerchantSearcgPost entity) - { - var jm = new WebApiCallBack(); - - var store = await _storeServices.GetStoreByUserId(_user.ID); - if (store != null) - { - jm = await _orderServices.GetOrderPageByMerchantSearch(entity.keyword, entity.status, entity.receiptType, entity.storeId, entity.page, entity.limit); - } - else - { - jm.status = false; - jm.msg = "你不是店员"; - } - - return jm; - } - - #endregion - - - #region 店铺提货单列表 - /// - /// 店铺提货单列表 - /// - /// - [HttpPost] - [Authorize] - public async Task StoreLadingList([FromBody] FMPageByIntId entity) - { - var jm = await _billLadingServices.GetStoreLadingList(_user.ID, entity.page, entity.limit); - return jm; - } - #endregion - - #region 删除提货单数据 - /// - /// 删除提货单数据 - /// - /// - [HttpPost] - [Authorize] - public async Task LadingDelete([FromBody] FMStringId entity) - { - var jm = await _billLadingServices.LadingDelete(entity.id, _user.ID); - return jm; - } - #endregion - - #region 获取单个提货单详情 - /// - /// 获取单个提货单详情 - /// - /// - [HttpPost] - [Authorize] - public async Task LadingInfo([FromBody] FMStringId entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.id)) - { - jm.msg = "请提交查询数据关键词"; - return jm; - } - jm = await _billLadingServices.GetInfo(entity.id, _user.ID); - - return jm; - } - #endregion - - #region 核销订单 - /// - /// 核销订单 - /// - /// - [HttpPost] - [Authorize] - public async Task Lading([FromBody] FMStringId entity) - { - var jm = new WebApiCallBack(); - - if (string.IsNullOrEmpty(entity.id)) - { - jm.msg = "请提交查询数据关键词"; - return jm; - } - var array = entity.id.Split(","); - var result = await _billLadingServices.LadingOperating(array, _user.ID); - jm.status = result.code == 0; - jm.msg = result.msg; - - return jm; - } - #endregion - - - } -} \ No newline at end of file diff --git a/uniapp/mahjong_group b/uniapp/mahjong_group index 316d1e2..05ad134 160000 --- a/uniapp/mahjong_group +++ b/uniapp/mahjong_group @@ -1 +1 @@ -Subproject commit 316d1e2b5d7013d0b9f7507ac2f15ead5ff4d703 +Subproject commit 05ad1348cf7b9ec80590919c3f23c2cb2c077e70 diff --git a/v1.0.0_需求文档.md b/v1.0.0_需求文档.md new file mode 100644 index 0000000..78957ae --- /dev/null +++ b/v1.0.0_需求文档.md @@ -0,0 +1,25 @@ +一、会员档案功能 +1、会员第一次使用需要授权微信号登录,绑定联系电话 并填写个人信息 +如: 打什么类型的麻将(扑克) 性别 年龄 是否抽烟 常消费包厢类型(大中小包) +2、会员行为标签 + 设定会员星级及黑名单,主要用于规避恶意组局及恶意逃单人员 +星级包括牌品 牌技 形象 初始全为4分 +组局结束后可找到历史对局,并对历史牌友进行评价 + 统计顾客组局成功率 被投诉次数 消费频次 消费能力 鸽子次数并且公开可查看 +3、要有相应的后台管理顾客档案 +二、麻友匹配机制 +1、需求发布 +用户可发布组局需求,需填写信息包含时间地点包厢号包厢类型是否禁烟游戏偏好等 +地点为单一店名 主要填写包厢号 包厢号需和包厢类型已经包厢费用绑定 +2、组局参与 +其他客户可查看组局人员信息,时间地点信息等详情 同意即可参与 +3、组局成功后自动发送微信消息给参与者,消息包含组局的时间地点信息,该消息可在后台设置相应模板 +4、组局成功后发起组局者(局头)会收到组局其他参与者的信息,其中包含其他人联系方式微信名,及一张现金券,通知组局者本次消费成功后他将会获得现金奖励或当局立减 +5、组局成功后店铺经营人员微信可收到推送消息,并查看组局情况以确定核销内容及包间情况 +6、鸽子费用(定金),组局成功可设置相应鸽子费用,分为三挡0/5/15,发起组局时发起人可选择是否设置鸽子费,如设置鸽子费则需线上支付鸽子费至平台账户,四人皆到店后鸽子费原路返还,若有人爽约,爽约人定金作为其他三人补偿不予退还,并记录其爽约 +7、组局奖励推送 +三、广告及推送内容 +1、使用软件时会跳出店铺会员注册的二维码弹窗广告(用于线下引流) +2、免责声明及保证金说明 +3、本店铺相应logo及图片广告位 + diff --git a/v1.0.1_需求文档.md b/v1.0.1_需求文档.md new file mode 100644 index 0000000..26310a3 --- /dev/null +++ b/v1.0.1_需求文档.md @@ -0,0 +1,114 @@ +v1.1版本更新 +需求简介 +1. 增加新功能: +1.1. 站内信。 +1.2. 预约页优化。 +1.3. 我的收益。 +一、站内信 +(1) 入口 +1. 我的页面,功能区,新增站内信入口【我的消息】。 + +站内信 入口 +1.1. 有新消息时,icon右上角显示“红点”。 +(2) 详情页 +1. 显示所有通知,以最近时间为排序。 + +站内信 详情页 +1.1. 显示标题、正文、通知时间。 +1.2. 正文过长时,自动延长本条消息的长度。 +1.3. 进入本页面时,默认已读所有消息。 +(3) 通知发送 +1. 在后台可配置通知的发送。 +2. 通知分为自动发送、手动发送。 +2.1. 自动发送的通知,根据不同的条件提前固定好模板内容。 +3. 自动发送: +3.1. 组局成功时,所有参与人收到相应的通知。 +3.2. 组局失败时,所有参与人收到相应的通知。 +3.3. ……待补充其他自动发送规则。 +4. 手动发送: +4.1. 可指定单个、多个用户发送相同的通知,通知内容可自定义。 +4.2. 可向全部用户发送相同的通知,通知内容可自定义。 +5. 用户只能单方向接收通知,不能对通知进行回复。 +二、预约页优化 +(1) 新增每天房间空闲/使用展示页 + +每天房间空闲/使用展示 +1. 展示从“今天”起,到未来7天内的房间列表。 +1.1. 点击日期,切换对应日期的房间预约和使用情况。 +2. 时间段规则: +2.1. 凌晨:0点 ~ 5点59分。 +2.2. 上午:6点 ~ 11点59分。 +2.3. 下午:12点 ~ 17点59分。 +2.4. 晚上:18点 ~ 23点59分。 +3. 【查看当前时段空闲房间】按钮默认为“关闭”状态,点击后变为“开启”状态。开启后只展示当前时段内“可预约”的房间。 +4. 房间列表,展示房间图片、房间号/房间名、房间类型、标准价格、会员价格、每天4个时段的可预约状态、当前时段的使用状态、【预约】按钮。 + +房间信息和当日时段预约状态 +4.1. 后台可配置房间图片、房间号/房间名、房间类型、标准价格、会员价格。 +4.1.1. 会员,为线下店内的会员,与小程序无关,小程序仅作价格展示。 +4.1.2. 标准价格与会员价格,在配置时,按“xx元/xx小时”为格式进行配置,例: +4.1.2.1. 标准价格:30元/4小时。 +4.1.2.2. 会员价格:30元/5小时 或 20元/4小时。 +4.2. 4个时段的可预约状态,根据用户预约单、后台操作自动改变。 +5. 若今日内,所有时间段都已被约满,【预约】按钮处于“灰色不可点击”状态。 +6. 点击【预约】跳转到“预约页”。 +7. 后台可设置每个房间号在不同日期、时间段内的预约状态。 +7.1. 用于线下预约房间后,将房间状态同步至线上。 +(2) 预约页 + +预约页 +1. 预约日期,不可更改,根据上级页面的选项自动展示。 +2. 预约时间,可选择“凌晨”“上午”“下午”“晚上”。 +3. 最晚到店时间: +3.1. 日期不可选,默认当天。 +3.2. 选择时间,不可小于当前时段,不可超出当天。 +4. 房间号,不可更改,根据上级页面的选项自动展示。 +5. 人数,新增“无需组局”选项。 +5.1. 相当于“人数:1人”,只有发起者自己。 +6. 鸽子费,新增“自定义”选项。 + +鸽子费 +6.1. 不能超出50元。 +6.2. 只能输入整数。 +三、我的收益 +(1) 入口 +1. 我的页面,新增【我的收益】。 + +站内信 入口 +(2) 详情页 + +我的收益 详情页 +1. 待提现收益,未提现的剩余金额。 +2. 已提现收益,所有已提现的金额总和。 +3. 点击【查看规则】,弹出“规则说明弹窗”。 + +弹窗说明 +3.1. 内容后台配置。 +4. 收益记录,展示每次收益的数据。 + +收益记录 +5. 提现记录,展示每次提现的数据。 + +提现记录 +5.1. 多种状态,“提现中”“已提现”“已取消”。 +5.1.1. 提现中:申请后,处于该状态。 +5.1.2. 已提现:已提现,线下打款后处于该状态。 +5.1.3. 已取消:后台取消/拒绝该提现申请。 +(3) 收益如何获取 +1. 线下员工通过后台,给发起者的账号添加佣金。 +1.1. 只有发起者有佣金,参与者没有。 +2. 佣金 = 房费10%。 +(4) 提现 +1. 点击【去提现】,弹出提现弹窗。 + +提现弹窗 +1.1. 支持小数点后两位。 +1.2. 点击【全部提现】,自动输入所有待提现金额。 +1.3. 点击【申请提现】: +1.3.1. 高于待提现金额,弹出系统提示“超出可提现金额”。 +1.3.2. 无其他问题,关闭弹窗,弹出系统提示“已提交申请”,页面自动刷新,更新待提现金额。 +2. 后台展示收到的提现申请记录,可对申请进行操作。 +2.1. 同意:同意该申请,线下联系客户打款。 +2.2. 拒绝/取消:因其他原因拒绝该申请。 +2.3. 已打款:通过线下转帐后,将该记录手动改变为本状态。 + diff --git a/业务逻辑文档 copy.md b/业务逻辑文档 copy.md new file mode 100644 index 0000000..ea7f9e5 --- /dev/null +++ b/业务逻辑文档 copy.md @@ -0,0 +1,1190 @@ +# 麻将组局预约小程序 - 业务逻辑详解文档 + +## 目录 + +1. [项目概述](#1-项目概述) +2. [系统架构](#2-系统架构) +3. [核心业务模块](#3-核心业务模块) +4. [核心业务流程图](#4-核心业务流程图) +5. [数据模型](#5-数据模型) +6. [API接口详解](#6-api接口详解) +7. [业务规则汇总](#7-业务规则汇总) +8. [状态机定义](#8-状态机定义) + +--- + +## 1. 项目概述 + +### 1.1 产品定位 + +这是一个**麻将组局预约小程序**,帮助麻将爱好者在线上发起、加入麻将局,线下到店进行游戏。小程序提供了完整的预约流程管理,包括房间选择、预约发起、参与管理、签到评价等功能。 + +### 1.2 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前端 | UniApp + Vue 3 | 支持微信小程序、H5、APP多端发布 | +| 后端 | .NET Core 6.0+ | RESTful API | +| ORM | SqlSugar | 数据库访问层 | +| 数据库 | Microsoft SQL Server | 数据持久化 | +| 支付 | 微信支付 | 鸽子费(押金)支付 | + +### 1.3 核心功能清单 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 麻将组局预约系统 │ +├─────────────────────────────────────────────────────────┤ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 首页 │ │ 预约 │ │ 个人中心│ │ 消息 │ │ +│ │ 预约列表│ │ 房间选择│ │ 我的预约│ │ 站内信 │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +├─────────────────────────────────────────────────────────┤ +│ 核心业务: │ +│ • 发起预约 • 加入预约 • 取消预约 │ +│ • 预约签到 • 牌友评价 • 信誉管理 │ +│ • 鸽子费支付 • 退款处理 • 收益提现 │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 系统架构 + +### 2.1 前端架构 + +``` +uniapp/mahjong_group/ +├── pages/ # 页面目录 +│ ├── index/ +│ │ └── index.vue # 首页 - 预约列表 +│ ├── appointment/ +│ │ ├── book-room-page.vue # 房间选择页(Tab页) +│ │ └── appointment-page.vue # 预约表单页 +│ └── me/ +│ ├── me-page.vue # 个人中心 +│ ├── appointment-record-page.vue # 预约记录 +│ ├── my-earnings-page.vue # 我的收益 +│ └── my-message-page.vue # 消息中心 +├── components/ # 组件目录 +│ ├── index/ +│ │ └── MahjongCard.vue # 麻将预约卡片(九宫格) +│ └── com/ +│ ├── index/ +│ │ └── ReservationPopup.vue # 预约详情弹窗 +│ └── page/ +│ ├── container-base.vue # 基础容器(含签到、评价弹窗) +│ └── qiandao-popup.vue # 签到弹窗 +└── common/ + ├── server/ + │ └── interface/ # API接口定义 + │ ├── sq.js # 预约相关接口 + │ ├── user.js # 用户相关接口 + │ ├── earnings.js # 收益相关接口 + │ └── message.js # 消息相关接口 + └── system/ + ├── request.js # 请求封装(含签名) + └── cacheService.js # 缓存服务 +``` + +### 2.2 后端架构 + +``` +server/CoreCms.Net.Web.WebApi/ +└── Controllers/ + └── SQController.cs # 预约核心控制器(全部预约相关API) + ├── GetReservationList # 首页预约列表 + ├── AddSQReservation # 创建预约 + ├── JoinReservation # 加入预约 + ├── CancelReservation # 取消预约 + ├── CheckInReservation # 签到 + ├── GetEvaluateServices # 获取评价 + ├── AddEvaluateServices # 添加评价 + ├── GetMessageList # 消息列表 + ├── GetEarningsSummary # 收益统计 + └── ApplyWithdraw # 申请提现 +``` + +### 2.3 系统架构图 + +```mermaid +graph TB + subgraph 客户端 + A[微信小程序] --> B[UniApp] + C[H5网页] --> B + D[APP] --> B + end + + subgraph 前端层 + B --> E[Vue 3 组件] + E --> F[API接口层] + F --> G[请求封装/签名] + end + + subgraph 后端层 + G --> H[.NET Core WebAPI] + H --> I[SQController] + I --> J[Services服务层] + J --> K[SqlSugar ORM] + end + + subgraph 数据层 + K --> L[(SQL Server)] + end + + subgraph 第三方服务 + H --> M[微信支付] + H --> N[微信登录] + end +``` + +--- + +## 3. 核心业务模块 + +### 3.1 用户认证模块 + +#### 3.1.1 登录流程 + +用户通过微信小程序登录,获取手机号授权后绑定账号。 + +```mermaid +sequenceDiagram + participant U as 用户 + participant MP as 小程序 + participant WX as 微信服务器 + participant BE as 后端服务 + participant DB as 数据库 + + U->>MP: 点击登录 + MP->>U: 请求授权手机号 + U->>MP: 同意授权 + MP->>WX: 获取code和encryptedData + WX->>MP: 返回加密数据 + MP->>BE: POST /user/UseWxPhoneNumberLogin + Note over MP,BE: 传递code和sessionAuthId + BE->>WX: 解密手机号 + WX->>BE: 返回手机号 + BE->>DB: 查询/创建用户 + DB->>BE: 返回用户信息 + BE->>MP: 返回token和用户信息 + MP->>MP: 存储token到本地 + MP->>U: 登录成功 +``` + +#### 3.1.2 用户信息结构 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | +| nickName | string | 昵称 | +| avatarImage | string | 头像URL | +| sex | int | 性别:1男 2女 | +| birthday | DateTime? | 生日(用于计算年龄) | +| credit_score | decimal | 信誉分(0-5分,默认5分) | +| play_level | decimal | 牌品评分(1-5分,默认4分) | +| skills_level | decimal | 牌技评分(1-5分,默认4分) | +| dove_count | int | 鸽子次数 | + +--- + +### 3.2 预约管理模块 + +#### 3.2.1 预约生命周期 + +```mermaid +stateDiagram-v2 + [*] --> 待开始: 创建预约 + 待开始 --> 已锁定: 人满 + 待开始 --> 已取消: 发起者取消 + 待开始 --> 待开始: 参与者加入/退出 + 已锁定 --> 待开始: 参与者退出 + 已锁定 --> 已取消: 发起者取消 + 已锁定 --> 进行中: 签到 + 待开始 --> 进行中: 签到(人未满也可签到) + 进行中 --> 已结束: 时间结束 + 已取消 --> [*] + 已结束 --> [*] +``` + +#### 3.2.2 预约状态说明 + +| status | 状态名 | 说明 | +|--------|--------|------| +| 0 | 待开始 | 预约已创建,等待参与者加入或等待开始时间 | +| 1 | 已锁定 | 参与人数已满,等待开始 | +| 2 | 进行中 | 发起者已签到,游戏进行中 | +| 3 | 已结束 | 预约时间已过,预约完成 | +| 4 | 已取消 | 预约被取消 | + +#### 3.2.3 发起预约流程 + +```mermaid +flowchart TD + A[用户点击发起预约] --> B[选择日期] + B --> C[查看房间列表及时段状态] + C --> D{选择房间和时段} + D --> E[填写预约信息] + + subgraph 预约信息 + E --> E1[组局名称] + E --> E2[人数 1-4人] + E --> E3[玩法类型] + E --> E4[游戏规则] + E --> E5[参与限制] + E --> E6[鸽子费设置] + end + + E1 & E2 & E3 & E4 & E5 & E6 --> F[提交前验证] + F --> G{调用CanCreateSQReservation} + G -->|验证失败| H[显示错误信息] + H --> E + G -->|验证通过| I{是否有鸽子费} + I -->|有| J[发起微信支付] + J --> K{支付结果} + K -->|成功| L[调用AddSQReservation创建预约] + K -->|失败| M[提示支付失败] + I -->|无| L + L --> N[创建预约记录] + N --> O[创建发起者参与记录 role=1] + O --> P[返回预约ID] + P --> Q[跳转到预约详情] +``` + +#### 3.2.4 加入预约流程 + +```mermaid +flowchart TD + A[用户点击加入预约] --> B{预约是否存在且未结束} + B -->|否| C[提示:预约不存在或已结束] + B -->|是| D{是否已加入该预约} + D -->|是| E[提示:您已加入该预约] + D -->|否| F{是否为独享模式} + F -->|是| G[提示:该预约不接受其他人加入] + F -->|否| H[校验参与条件] + + subgraph 条件校验 + H --> H1{信誉分是否达标} + H1 -->|否| I1[提示:信誉分不足] + H1 -->|是| H2{性别是否符合} + H2 -->|否| I2[提示:性别不符合要求] + H2 -->|是| H3{年龄是否在范围内} + H3 -->|否| I3[提示:年龄不符合限制] + H3 -->|是| H4{是否有时间冲突} + H4 -->|是| I4[提示:有其他预约时间冲突] + H4 -->|否| H5{预约是否已满} + H5 -->|是| I5[提示:预约已满] + end + + H5 -->|否| J{是否有鸽子费} + J -->|有| K[发起微信支付] + K --> L{支付结果} + L -->|成功| M[创建参与者记录 role=0] + L -->|失败| N[提示支付失败] + J -->|无| M + M --> O[加入成功] +``` + +#### 3.2.5 取消预约流程 + +```mermaid +flowchart TD + A[用户点击取消预约] --> B{预约是否存在} + B -->|否| C[提示:预约不存在] + B -->|是| D{距开始是否<30分钟} + D -->|是| E[提示:开始前30分钟无法取消] + D -->|否| F{预约状态是否>=3} + F -->|是| G[提示:已结束或已取消] + F -->|否| H{用户角色} + + H -->|发起者 role=1| I[发起者取消整个预约] + I --> J[预约状态改为已取消 status=4] + J --> K[所有参与者标记为已退出] + K --> L{是否有已支付押金} + L -->|有| M[已支付者标记为待退款 is_refund=3] + M --> N[发送通知给所有参与者] + L -->|无| N + + H -->|参与者 role=0| O{预约是否已开始} + O -->|是| P[提示:只有发起者可取消] + O -->|否| Q[参与者退出] + Q --> R[标记该用户为已退出 status=1] + R --> S{该用户是否已支付押金} + S -->|是| T[标记为待退款 is_refund=3] + T --> U[退出成功] + S -->|否| U + + N --> V[取消完成] +``` + +--- + +### 3.3 签到模块 + +#### 3.3.1 签到流程 + +签到是预约流程的关键环节,只有发起者可以操作,用于确认实际到场人员。 + +```mermaid +flowchart TD + A[发起者点击签到] --> B{预约是否存在} + B -->|否| C[提示:预约不存在] + B -->|是| D{预约状态是否>=3} + D -->|是| E[提示:已结束或已取消] + D -->|否| F{是否已签到 status=2} + F -->|是| G[提示:已签到,无法重复] + F -->|否| H{是否为发起者} + H -->|否| I[提示:仅发起者可签到] + H -->|是| J[显示参与者列表] + + J --> K[勾选实际到场人员] + K --> L[提交签到] + + L --> M[开启数据库事务] + M --> N[预约状态改为进行中 status=2] + N --> O[所有未退出参与者默认标记为到场 is_arrive=1] + O --> P{是否有未到场人员} + + P -->|有| Q[未到场人员处理] + subgraph 爽约惩罚 + Q --> Q1[标记为未到场 is_arrive=2] + Q1 --> Q2[标记为已退出 status=1] + Q2 --> Q3[扣除信誉分 -0.5] + Q3 --> Q4[增加鸽子次数 dove_count++] + Q4 --> Q5[记录信誉变更日志] + end + + P -->|无| R[到场人员处理] + Q5 --> R + + subgraph 守约奖励 + R --> R1[增加信誉分 +0.2 最高5.0] + R1 --> R2[记录信誉变更日志] + end + + R2 --> S{是否有押金} + S -->|有| T[到场且已支付者标记为待退款 is_refund=3] + T --> U[提交事务] + S -->|无| U + U --> V[签到完成] +``` + +#### 3.3.2 签到后信誉变化 + +| 情况 | 信誉变化 | 其他影响 | +|------|----------|----------| +| 到场(守约) | +0.2(最高5.0) | - | +| 未到场(爽约) | -0.5 | 鸽子次数+1 | + +--- + +### 3.4 评价模块 + +#### 3.4.1 评价流程 + +```mermaid +flowchart TD + A[用户进入预约详情] --> B{预约是否已完成且已签到} + B -->|否| C[不显示评价入口] + B -->|是| D[显示评价入口] + D --> E[点击牌友评价] + E --> F[获取可评价参与者列表] + F --> G[只显示实际到场的其他参与者] + + G --> H[选择要评价的人] + H --> I[填写评价] + + subgraph 评价维度 + I --> I1[牌品评分 1-5星] + I --> I2[牌技评分 1-5星] + end + + I1 & I2 --> J[提交评价] + J --> K[创建评价记录] + K --> L[重新计算被评价人的平均分] + + subgraph 平均分计算 + L --> L1[获取所有历史评价] + L1 --> L2[计算 牌品 = sum评价 + 4 / 评价次数 + 1] + L2 --> L3[计算 牌技 = sum评价 + 4 / 评价次数 + 1] + L3 --> L4[如果只有1次评价,分母+1避免偏差] + end + + L4 --> M[更新用户评分] + M --> N[评价完成] +``` + +#### 3.4.2 评价规则 + +1. **评价时机**:预约状态为"已结束"且已签到后 +2. **评价对象**:只能评价实际到场的其他参与者(排除自己) +3. **评价次数**:每个参与者只能被评价一次 +4. **评价维度**: + - 牌品(play_level):1-5分 + - 牌技(skills_level):1-5分 + +#### 3.4.3 评分计算公式 + +``` +新评分 = (历史评分总和 + 初始分4) / (评价次数 + 1) + +特殊情况:当只有1次评价时,分母为(评价次数 + 2)避免偏差 +``` + +--- + +### 3.5 押金(鸽子费)模块 + +#### 3.5.1 押金流程总览 + +```mermaid +flowchart TD + subgraph 发起预约 + A[发起者设置鸽子费] --> B[发起者支付鸽子费] + B --> C[创建预约成功] + C --> D[参与者记录 is_refund=1 待支付] + end + + subgraph 加入预约 + E[参与者加入] --> F{预约是否有鸽子费} + F -->|有| G[参与者支付鸽子费] + G --> H[参与者记录 is_refund=2 已支付] + F -->|无| I[参与者记录 is_refund=0] + end + + subgraph 签到/取消 + J[签到或取消] --> K{参与者是否已支付} + K -->|是| L[标记为 is_refund=3 待退款] + K -->|否| M[无需退款] + end + + subgraph 定时任务 + N[定时扫描 is_refund=3] --> O[调用微信退款API] + O --> P{退款结果} + P -->|成功| Q[更新 is_refund=4 已退款] + P -->|失败| R[更新 is_refund=5 退款异常] + end +``` + +#### 3.5.2 押金状态说明 + +| is_refund | 状态名 | 说明 | +|-----------|--------|------| +| 0 | 无押金 | 预约不需要鸽子费 | +| 1 | 待支付 | 需要支付但未支付 | +| 2 | 已支付 | 已完成支付 | +| 3 | 待退款 | 已发起退款流程,等待处理 | +| 4 | 已退款 | 退款完成 | +| 5 | 退款异常 | 退款失败 | + +#### 3.5.3 押金分配规则 + +```mermaid +flowchart TD + A[签到确认] --> B{是否有人爽约} + B -->|无| C[所有人押金原路退还] + B -->|有| D[计算爽约人数和到场人数] + D --> E[爽约者押金不退还] + E --> F[爽约者押金平分给到场者] + F --> G[到场者获得额外收益] + G --> H[到场者原押金退还] +``` + +--- + +### 3.6 消息通知模块 + +#### 3.6.1 消息类型 + +| 类型 | message_type | 说明 | +|------|--------------|------| +| 系统消息 | 0 | 预约相关自动通知 | +| 私信 | 1 | 暂未实现 | + +#### 3.6.2 自动发送场景 + +```mermaid +flowchart LR + A[组局成功] --> B[通知所有参与者] + C[组局失败/解散] --> D[通知所有参与者] + E[预约即将开始] --> F[提醒参与者] + G[签到完成] --> H[通知到场/未到场情况] +``` + +#### 3.6.3 消息流程 + +```mermaid +sequenceDiagram + participant S as 系统 + participant DB as 数据库 + participant U as 用户 + + S->>DB: 插入消息记录 (SQMessage) + Note over DB: user_id=0 表示全体用户 + U->>DB: 获取消息列表 + DB->>U: 返回消息 + 已读状态 + Note over DB: 已读状态存储在 SQMessageRead 表 + U->>DB: 标记全部已读 + Note over DB: 插入已读记录 +``` + +--- + +### 3.7 收益模块 + +#### 3.7.1 收益来源 + +1. **发起预约佣金**:房费的10%(需线下员工在后台添加) +2. **爽约者押金分成**:爽约者的鸽子费平分给到场者 + +#### 3.7.2 收益流程 + +```mermaid +flowchart TD + A[预约完成] --> B{发起者是否有佣金} + B -->|有| C[后台员工添加佣金记录] + C --> D[更新待提现金额] + + E[签到确认有人爽约] --> F[计算爽约押金] + F --> G[平分给到场者] + G --> D + + D --> H[用户申请提现] + H --> I{金额是否超出可提现} + I -->|是| J[提示:超出可提现金额] + I -->|否| K[创建提现申请记录] + K --> L[状态:提现中] + L --> M[后台审核] + M --> N{审核结果} + N -->|同意| O[线下打款] + O --> P[状态:已提现] + N -->|拒绝| Q[状态:已取消] +``` + +#### 3.7.3 提现状态 + +| 状态 | 说明 | +|------|------| +| 提现中 | 申请已提交,等待审核 | +| 已提现 | 线下打款完成 | +| 已取消 | 后台拒绝/取消申请 | + +--- + +### 3.8 黑名单模块 + +#### 3.8.1 黑名单功能 + +```mermaid +flowchart TD + A[用户A将用户B加入黑名单] --> B[写入黑名单表] + B --> C[首页预约列表] + C --> D{用户B是否在黑名单} + D -->|是| E[过滤B发起的预约] + D -->|否| F[正常显示] +``` + +#### 3.8.2 黑名单影响 + +1. 首页自动过滤黑名单用户发起的预约 +2. 黑名单用户无法加入我发起的预约(待实现) +3. 评价页面显示黑名单状态 + +--- + +## 4. 核心业务流程图 + +### 4.1 完整预约生命周期 + +```mermaid +flowchart TB + subgraph 发起阶段 + A1[选择房间日期] --> A2[填写预约信息] + A2 --> A3[设置参与限制] + A3 --> A4[设置鸽子费] + A4 --> A5{有鸽子费?} + A5 -->|是| A6[微信支付] + A5 -->|否| A7[创建预约] + A6 --> A7 + A7 --> A8[预约状态: 待开始] + end + + subgraph 参与阶段 + A8 --> B1[等待参与者加入] + B1 --> B2{人数已满?} + B2 -->|是| B3[预约状态: 已锁定] + B2 -->|否| B4{有人退出?} + B4 -->|是| B1 + B4 -->|否| B5{到达开始时间?} + B3 --> B5 + B5 -->|否| B1 + end + + subgraph 签到阶段 + B5 -->|是| C1[发起者签到] + C1 --> C2[确认到场人员] + C2 --> C3[处理到场/未到场] + C3 --> C4[更新信誉分] + C4 --> C5[预约状态: 进行中] + end + + subgraph 结束阶段 + C5 --> D1[游戏进行中] + D1 --> D2{到达结束时间?} + D2 -->|否| D1 + D2 -->|是| D3[预约状态: 已结束] + D3 --> D4[可以互相评价] + D4 --> D5[处理押金退款] + D5 --> D6[发放佣金] + end + + subgraph 取消分支 + A8 --> E1{发起者取消?} + B1 --> E1 + B3 --> E1 + E1 -->|是| E2[预约状态: 已取消] + E2 --> E3[所有人退出] + E3 --> E4[退还已支付押金] + end +``` + +### 4.2 时序图:完整预约流程 + +```mermaid +sequenceDiagram + participant U1 as 发起者 + participant U2 as 参与者 + participant FE as 前端 + participant BE as 后端 + participant DB as 数据库 + participant WX as 微信支付 + + %% 发起预约 + rect rgb(200, 230, 200) + Note over U1,WX: 发起预约阶段 + U1->>FE: 选择房间和时段 + FE->>BE: GetRoomListWithSlotsNew + BE->>DB: 查询房间状态 + DB->>BE: 返回房间列表 + BE->>FE: 房间时段状态 + U1->>FE: 填写预约信息 + FE->>BE: CanCreateSQReservation + BE->>DB: 校验时间冲突等 + DB->>BE: 校验结果 + BE->>FE: 可以创建 + FE->>WX: 支付鸽子费 + WX->>FE: 支付成功 + FE->>BE: AddSQReservation + BE->>DB: 创建预约+参与者记录 + DB->>BE: 返回预约ID + BE->>FE: 预约成功 + end + + %% 加入预约 + rect rgb(200, 200, 230) + Note over U1,WX: 加入预约阶段 + U2->>FE: 浏览预约列表 + FE->>BE: GetReservationList + BE->>DB: 查询预约列表 + DB->>BE: 预约数据 + BE->>FE: 预约列表 + U2->>FE: 点击加入 + FE->>BE: JoinReservation + BE->>DB: 校验条件 + DB->>BE: 校验通过 + BE->>FE: 需要支付鸽子费 + FE->>WX: 支付鸽子费 + WX->>FE: 支付成功 + FE->>BE: JoinReservation(带支付信息) + BE->>DB: 创建参与者记录 + DB->>BE: 成功 + BE->>FE: 加入成功 + end + + %% 签到 + rect rgb(230, 200, 200) + Note over U1,WX: 签到阶段 + U1->>FE: 点击签到 + FE->>FE: 显示参与者列表 + U1->>FE: 确认到场人员 + FE->>BE: CheckInReservation + BE->>DB: 开启事务 + BE->>DB: 更新预约状态 + BE->>DB: 更新到场状态 + BE->>DB: 更新信誉分 + BE->>DB: 标记待退款 + BE->>DB: 提交事务 + DB->>BE: 成功 + BE->>FE: 签到成功 + end + + %% 评价 + rect rgb(230, 230, 200) + Note over U1,WX: 评价阶段 + U1->>FE: 点击评价 + FE->>BE: GetEvaluateServices + BE->>DB: 查询可评价人员 + DB->>BE: 参与者列表 + BE->>FE: 可评价列表 + U1->>FE: 提交评价 + FE->>BE: AddEvaluateServices + BE->>DB: 创建评价记录 + BE->>DB: 更新被评价人分数 + DB->>BE: 成功 + BE->>FE: 评价成功 + end +``` + +### 4.3 房间时段状态机 + +```mermaid +stateDiagram-v2 + [*] --> 可预约: 默认状态 + 可预约 --> 已预约: 有人预约该时段 + 可预约 --> 不可预约: 后台设置不可用 + 已预约 --> 可预约: 预约取消 + 已预约 --> 进行中: 签到开始 + 进行中 --> 可预约: 预约结束 + 不可预约 --> 可预约: 后台取消不可用 + + note right of 可预约: 绿色 + note right of 已预约: 橙色 + note right of 不可预约: 灰色 +``` + +--- + +## 5. 数据模型 + +### 5.1 核心数据表关系图 + +```mermaid +erDiagram + CoreCmsUser ||--o{ SQReservationParticipants : "参与" + CoreCmsUser ||--o{ SQReservationEvaluate : "评价" + CoreCmsUser ||--o{ SQReservationReputation : "信誉记录" + CoreCmsUser ||--o{ CoreCmsUserBlacklist : "黑名单" + CoreCmsUser ||--o{ SQMessage : "接收消息" + CoreCmsUser ||--o{ SQEarningsRecord : "收益" + CoreCmsUser ||--o{ SQWithdrawRecord : "提现" + + SQReservations ||--o{ SQReservationParticipants : "包含" + SQReservations ||--o{ SQReservationEvaluate : "评价关联" + SQReservations }o--|| SQRooms : "使用房间" + + SQRooms ||--o{ SQRoomUnavailableTimes : "不可用时间" + SQRooms ||--o{ SQRoomPricing : "价格配置" + + CoreCmsUser { + int id PK + string nickName + string avatarImage + int sex + datetime birthday + decimal credit_score + decimal play_level + decimal skills_level + int dove_count + } + + SQReservations { + int id PK + int room_id FK + string room_name + datetime start_time + datetime end_time + int duration_minutes + string title + string game_type + string game_rule + int player_count + int status + decimal deposit_fee + decimal credit_limit + int gender_limit + int min_age + int max_age + bool is_smoking + datetime latest_arrival_time + string extra_info + bool is_solo_mode + datetime created_at + datetime updated_at + } + + SQReservationParticipants { + int id PK + int reservation_id FK + int user_id FK + int role + int status + datetime join_time + datetime quit_time + int is_arrive + datetime check_reservation + int is_refund + string paymentId + string important_data + } + + SQRooms { + int id PK + string name + int capacity + decimal price_per_hour + string description + string image_url + bool status + datetime created_at + } + + SQReservationEvaluate { + int id PK + int reservation_id FK + int user_id FK + int to_user_id FK + int role + decimal play_level + decimal skills_level + datetime created_at + } + + SQReservationReputation { + int id PK + int user_id FK + int reservation_id FK + decimal reputation_value + string remark + datetime created_at + } +``` + +### 5.2 数据表详细说明 + +#### 5.2.1 SQReservations(预约表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 主键 | +| room_id | int | 房间ID(外键) | +| room_name | string | 房间名称(冗余) | +| start_time | datetime | 开始时间 | +| end_time | datetime | 结束时间 | +| duration_minutes | int | 时长(分钟) | +| title | string | 组局名称 | +| game_type | string | 游戏类型(血战/血流成河等) | +| game_rule | string | 游戏规则 | +| player_count | int | 需要人数(1-4) | +| status | int | 状态(0待开始/1已锁定/2进行中/3已结束/4已取消) | +| deposit_fee | decimal | 押金费用 | +| credit_limit | decimal | 最低信誉要求 | +| gender_limit | int | 性别限制(0不限/1男/2女) | +| min_age | int | 最小年龄限制 | +| max_age | int | 最大年龄限制 | +| is_smoking | bool | 是否禁烟 | +| latest_arrival_time | datetime | 最晚到店时间 | +| extra_info | string | 其他补充说明 | +| is_solo_mode | bool | 是否独享模式(人数=1时自动设为true) | +| remarks | string | 备注(如取消原因) | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 更新时间 | + +#### 5.2.2 SQReservationParticipants(参与者表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 主键 | +| reservation_id | int | 预约ID(外键) | +| user_id | int | 用户ID(外键) | +| role | int | 角色(0参与者/1发起者) | +| status | int | 状态(0正常/1已退出) | +| join_time | datetime | 加入时间 | +| quit_time | datetime | 退出时间 | +| is_arrive | int | 是否到场(0未签到/1到场/2未到场) | +| check_reservation | datetime | 签到时间 | +| is_refund | int | 退款状态(0无/1待支付/2已支付/3待退款/4已退款/5异常) | +| paymentId | string | 支付订单号 | +| important_data | string | 重要数据(JSON格式,含支付信息等) | + +--- + +## 6. API接口详解 + +### 6.1 预约相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 首页预约列表 | GET | /api/sq/GetReservationList | 无 | 获取未结束的预约列表,自动过滤黑名单 | +| 预约详情 | GET | /api/sq/GetReservationDetail | 无 | 根据ID获取预约详情 | +| 我的预约 | GET | /api/sq/GetMyReservation | 需要 | type=0参与的/type=1发起的 | +| 正在进行 | GET | /api/sq/GetMyUseReservation | 需要 | 获取未结束的预约 | +| 验证创建 | POST | /api/sq/CanCreateSQReservation | 需要 | 创建前校验 | +| 创建预约 | POST | /api/sq/AddSQReservation | 需要 | 发起新预约 | +| 加入预约 | POST | /api/sq/JoinReservation | 需要 | 加入现有预约 | +| 取消预约 | POST | /api/sq/CancelReservation | 需要 | 发起者/参与者取消 | +| 签到 | POST | /api/sq/CheckInReservation | 需要 | 发起者签到确认 | + +### 6.2 房间相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 可选日期 | GET | /api/sq/GetAvailableDates | 无 | 今天+未来6天 | +| 房间时段 | GET | /api/sq/GetRoomListWithSlotsNew | 无 | 房间列表及时段状态 | +| 房间详情 | GET | /api/sq/GetRoomDetail | 无 | 房间信息及时段 | +| 可预约房间 | GET | /api/sq/GetReservationRoomList | 无 | 指定时间可预约的房间 | +| 营业时间 | GET | /api/sq/GetBusinessHours | 无 | 09:00-23:00 | + +### 6.3 评价相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 获取评价 | GET | /api/sq/GetEvaluateServices | 需要 | 获取可评价的参与者 | +| 添加评价 | POST | /api/sq/AddEvaluateServices | 需要 | 评价参与者 | +| 信誉记录 | GET | /api/sq/GetReputationByUser | 需要 | 我的信誉变化记录 | +| 评价给我 | GET | /api/sq/GetEvaluateToMe | 需要 | 别人给我的评价 | + +### 6.4 消息相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 消息列表 | GET | /api/sq/GetMessageList | 需要 | 站内信列表 | +| 未读数量 | GET | /api/sq/GetUnreadCount | 需要 | 未读消息数 | +| 标记已读 | POST | /api/sq/MarkAllAsRead | 需要 | 全部标记已读 | + +### 6.5 收益相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 收益统计 | GET | /api/sq/GetEarningsSummary | 需要 | 待提现+已提现 | +| 收益记录 | POST | /api/sq/GetEarningsRecordList | 需要 | 收益明细 | +| 提现记录 | POST | /api/sq/GetWithdrawRecordList | 需要 | 提现历史 | +| 申请提现 | POST | /api/sq/ApplyWithdraw | 需要 | 发起提现 | +| 收益规则 | GET | /api/sq/GetEarningsRule | 无 | 规则说明 | + +--- + +## 7. 业务规则汇总 + +### 7.1 时间规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 可预约范围 | 今天+未来6天 | 共7天 | +| 时段划分 | 凌晨(0-6h)/上午(6-12h)/下午(12-18h)/晚上(18-24h) | 4个时段 | +| 营业时间 | 09:00-23:00 | 可在后台配置 | +| 取消限制 | 开始前30分钟 | 30分钟内无法取消 | + +### 7.2 人数规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 最少人数 | 1人 | 独享模式 | +| 最多人数 | 4人 | 麻将标准人数 | +| 独享模式 | player_count=1 | 自动设置,不接受其他人加入 | +| 人满锁定 | 达到player_count | 状态变为已锁定(1) | + +### 7.3 押金规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 金额范围 | 0-50元 | 可选0/5/10/20或自定义 | +| 用途 | 防止爽约 | 到场退还,爽约扣除 | +| 爽约分配 | 平分给到场者 | 由到场者瓜分 | +| 退款时机 | 签到后/取消后 | 标记待退款,定时任务处理 | + +### 7.4 信誉规则 + +| 情况 | 变化 | 说明 | +|------|------|------| +| 初始信誉 | 5.0分 | 满分 | +| 守约奖励 | +0.2分 | 最高5.0 | +| 爽约惩罚 | -0.5分 | 最低0分,鸽子次数+1 | +| 信誉限制 | 发起者设置 | 低于限制无法加入 | + +### 7.5 评价规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 评价时机 | 预约完成后 | 状态为已结束 | +| 评价对象 | 到场的其他参与者 | 排除自己和未到场 | +| 评价维度 | 牌品/牌技 | 各1-5分 | +| 评价次数 | 每人一次 | 不可重复评价 | +| 初始分 | 4分 | 用于计算平均 | + +### 7.6 参与限制规则 + +| 限制项 | 可选值 | 说明 | +|--------|--------|------| +| 性别限制 | 不限/男/女 | 0/1/2 | +| 年龄限制 | 最小-最大 | 0表示不限 | +| 信誉限制 | 0-5分 | 最低信誉要求 | +| 禁烟 | 是/否 | 是否禁止吸烟 | + +--- + +## 8. 状态机定义 + +### 8.1 预约状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> S0_待开始: 创建预约 + + S0_待开始 --> S1_已锁定: 人满 + S0_待开始 --> S2_进行中: 签到 + S0_待开始 --> S4_已取消: 取消 + + S1_已锁定 --> S0_待开始: 有人退出 + S1_已锁定 --> S2_进行中: 签到 + S1_已锁定 --> S4_已取消: 发起者取消 + + S2_进行中 --> S3_已结束: 时间结束 + + S3_已结束 --> [*] + S4_已取消 --> [*] + + note right of S0_待开始: status=0 + note right of S1_已锁定: status=1 + note right of S2_进行中: status=2 + note right of S3_已结束: status=3 + note right of S4_已取消: status=4 +``` + +### 8.2 参与者状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> P0_正常: 加入预约 + + P0_正常 --> P1_已退出: 主动退出 + P0_正常 --> P1_已退出: 被踢出(签到时未到场) + P0_正常 --> P1_已退出: 预约取消 + + P1_已退出 --> [*] + + note right of P0_正常: status=0 + note right of P1_已退出: status=1 +``` + +### 8.3 到场状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> A0_未签到: 加入预约 + + A0_未签到 --> A1_到场: 签到确认到场 + A0_未签到 --> A2_未到场: 签到确认未到场 + + A1_到场 --> [*]: 完成 + A2_未到场 --> [*]: 被踢出 + + note right of A0_未签到: is_arrive=0 + note right of A1_到场: is_arrive=1 + note right of A2_未到场: is_arrive=2 +``` + +### 8.4 退款状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> R0_无押金: 预约无鸽子费 + [*] --> R1_待支付: 需要支付鸽子费 + + R1_待支付 --> R2_已支付: 支付成功 + R1_待支付 --> R0_无押金: 预约取消(未支付) + + R2_已支付 --> R3_待退款: 签到/取消触发 + + R3_待退款 --> R4_已退款: 定时任务退款成功 + R3_待退款 --> R5_退款异常: 定时任务退款失败 + + R4_已退款 --> [*] + R5_退款异常 --> R3_待退款: 人工处理后重试 + R5_退款异常 --> [*]: 人工处理 + R0_无押金 --> [*] + + note right of R0_无押金: is_refund=0 + note right of R1_待支付: is_refund=1 + note right of R2_已支付: is_refund=2 + note right of R3_待退款: is_refund=3 + note right of R4_已退款: is_refund=4 + note right of R5_退款异常: is_refund=5 +``` + +--- + +## 附录A:前端核心文件路径 + +| 功能 | 文件路径 | +|------|----------| +| 首页 | `uniapp/mahjong_group/pages/index/index.vue` | +| 房间选择 | `uniapp/mahjong_group/pages/appointment/book-room-page.vue` | +| 预约表单 | `uniapp/mahjong_group/pages/appointment/appointment-page.vue` | +| 个人中心 | `uniapp/mahjong_group/pages/me/me-page.vue` | +| 预约记录 | `uniapp/mahjong_group/pages/me/appointment-record-page.vue` | +| 我的收益 | `uniapp/mahjong_group/pages/me/my-earnings-page.vue` | +| 消息中心 | `uniapp/mahjong_group/pages/me/my-message-page.vue` | +| 登录 | `uniapp/mahjong_group/pages/me/login.vue` | +| 预约卡片组件 | `uniapp/mahjong_group/components/index/MahjongCard.vue` | +| 预约弹窗组件 | `uniapp/mahjong_group/components/com/index/ReservationPopup.vue` | +| 签到弹窗组件 | `uniapp/mahjong_group/components/com/page/qiandao-popup.vue` | +| API接口 | `uniapp/mahjong_group/common/server/interface/sq.js` | + +## 附录B:后端核心文件路径 + +| 功能 | 文件路径 | +|------|----------| +| 预约控制器 | `server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs` | +| 预约服务 | `server/CoreCms.Net.Services/SQ/SQReservationsServices.cs` | +| 房间服务 | `server/CoreCms.Net.Services/SQ/SQRoomsServices.cs` | +| 消息服务 | `server/CoreCms.Net.Services/SQ/SQMessageServices.cs` | +| 收益服务 | `server/CoreCms.Net.Services/SQ/SQEarningsServices.cs` | + +--- + +## 附录C:版本更新记录 + +### v1.0.0 核心功能 + +1. **会员档案功能** + - 微信授权登录,绑定手机号 + - 会员行为标签:星级评分、黑名单 + - 组局成功率、被投诉次数、消费频次、鸽子次数统计 + +2. **麻友匹配机制** + - 发布组局需求 + - 其他客户查看并加入 + - 组局成功后自动发送消息 + - 鸽子费(押金)机制 + +### v1.0.1 新增功能 + +1. **站内信功能** + - 消息入口,红点提示 + - 自动发送通知(组局成功/失败) + - 手动发送通知(后台配置) + +2. **预约页优化** + - 房间时段展示(4个时段) + - 房间可用状态可视化 + - 无需组局选项(独享模式) + - 鸽子费自定义金额(最高50元) + +3. **我的收益功能** + - 待提现/已提现金额展示 + - 收益记录查询 + - 提现申请(后台审核,线下打款) + - 佣金规则:房费10% + +--- + +*文档生成时间:2024年* +*版本:v1.0.1* diff --git a/业务逻辑文档.md b/业务逻辑文档.md new file mode 100644 index 0000000..e91ad60 --- /dev/null +++ b/业务逻辑文档.md @@ -0,0 +1,1782 @@ +# 麻将组局预约小程序 - 业务逻辑详解文档 + +## 目录 + +1. [项目概述](#1-项目概述) +2. [系统架构](#2-系统架构) +3. [**界面原型图与交互说明**](#3-界面原型图与交互说明) ⭐ 新增 +4. [核心业务模块](#4-核心业务模块) +5. [核心业务流程图](#5-核心业务流程图) +6. [数据模型](#6-数据模型) +7. [API接口详解](#7-api接口详解) +8. [业务规则汇总](#8-业务规则汇总) +9. [状态机定义](#9-状态机定义) + +--- + +## 1. 项目概述 + +### 1.1 产品定位 + +这是一个**麻将组局预约小程序**,帮助麻将爱好者在线上发起、加入麻将局,线下到店进行游戏。小程序提供了完整的预约流程管理,包括房间选择、预约发起、参与管理、签到评价等功能。 + +### 1.2 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前端 | UniApp + Vue 3 | 支持微信小程序、H5、APP多端发布 | +| 后端 | .NET Core 6.0+ | RESTful API | +| ORM | SqlSugar | 数据库访问层 | +| 数据库 | Microsoft SQL Server | 数据持久化 | +| 支付 | 微信支付 | 鸽子费(押金)支付 | + +### 1.3 核心功能清单 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 麻将组局预约系统 │ +├─────────────────────────────────────────────────────────┤ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 首页 │ │ 预约 │ │ 个人中心│ │ 消息 │ │ +│ │ 预约列表│ │ 房间选择│ │ 我的预约│ │ 站内信 │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +├─────────────────────────────────────────────────────────┤ +│ 核心业务: │ +│ • 发起预约 • 加入预约 • 取消预约 │ +│ • 预约签到 • 牌友评价 • 信誉管理 │ +│ • 鸽子费支付 • 退款处理 • 收益提现 │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 系统架构 + +### 2.1 前端架构 + +``` +uniapp/mahjong_group/ +├── pages/ # 页面目录 +│ ├── index/ +│ │ └── index.vue # 首页 - 预约列表 +│ ├── appointment/ +│ │ ├── book-room-page.vue # 房间选择页(Tab页) +│ │ └── appointment-page.vue # 预约表单页 +│ └── me/ +│ ├── me-page.vue # 个人中心 +│ ├── appointment-record-page.vue # 预约记录 +│ ├── my-earnings-page.vue # 我的收益 +│ └── my-message-page.vue # 消息中心 +├── components/ # 组件目录 +│ ├── index/ +│ │ └── MahjongCard.vue # 麻将预约卡片(九宫格) +│ └── com/ +│ ├── index/ +│ │ └── ReservationPopup.vue # 预约详情弹窗 +│ └── page/ +│ ├── container-base.vue # 基础容器(含签到、评价弹窗) +│ └── qiandao-popup.vue # 签到弹窗 +└── common/ + ├── server/ + │ └── interface/ # API接口定义 + │ ├── sq.js # 预约相关接口 + │ ├── user.js # 用户相关接口 + │ ├── earnings.js # 收益相关接口 + │ └── message.js # 消息相关接口 + └── system/ + ├── request.js # 请求封装(含签名) + └── cacheService.js # 缓存服务 +``` + +### 2.2 后端架构 + +``` +server/CoreCms.Net.Web.WebApi/ +└── Controllers/ + └── SQController.cs # 预约核心控制器(全部预约相关API) + ├── GetReservationList # 首页预约列表 + ├── AddSQReservation # 创建预约 + ├── JoinReservation # 加入预约 + ├── CancelReservation # 取消预约 + ├── CheckInReservation # 签到 + ├── GetEvaluateServices # 获取评价 + ├── AddEvaluateServices # 添加评价 + ├── GetMessageList # 消息列表 + ├── GetEarningsSummary # 收益统计 + └── ApplyWithdraw # 申请提现 +``` + +### 2.3 系统架构图 + +```mermaid +graph TB + subgraph 客户端 + A[微信小程序] --> B[UniApp] + C[H5网页] --> B + D[APP] --> B + end + + subgraph 前端层 + B --> E[Vue 3 组件] + E --> F[API接口层] + F --> G[请求封装/签名] + end + + subgraph 后端层 + G --> H[.NET Core WebAPI] + H --> I[SQController] + I --> J[Services服务层] + J --> K[SqlSugar ORM] + end + + subgraph 数据层 + K --> L[(SQL Server)] + end + + subgraph 第三方服务 + H --> M[微信支付] + H --> N[微信登录] + end +``` + +--- + +## 3. 界面原型图与交互说明 + +本章节展示小程序的主要界面原型图,帮助理解各页面的布局和交互逻辑。**⭐ 标记的区域为重点功能区域**。 + +### 3.1 整体页面结构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 小程序整体结构 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 首页 │ │ 预约 │ │ 我的 │ │ +│ │ index.vue │ │book-room │ │ me-page │ │ +│ │ │ │ -page.vue │ │ .vue │ │ +│ │ 预约列表 │ │ 房间选择 │ │ 个人中心 │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ v v v │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ReservationP │ │appointment │ │appointment │ │ +│ │ opup.vue │ │ -page.vue │ │-record.vue │ │ +│ │ 预约详情弹窗│ │ 预约表单 │ │ 预约记录 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + │ │ │ + └────────────────┼────────────────┘ + v + ┌─────────────────────────────┐ + │ TabBar │ + │ [首页] [预约] [我的] │ + └─────────────────────────────┘ +``` + +--- + +### 3.2 首页 - 预约列表页 + +**文件**: `pages/index/index.vue` + +``` +┌─────────────────────────────────────────┐ +│ ▓▓▓▓▓▓▓▓▓▓ 状态栏 ▓▓▓▓▓▓▓▓▓▓ │ +├─────────────────────────────────────────┤ +│ ┌─────────────────────────────────────┐ │ +│ │ ╔═══════════════════════════════╗ │ │◄── ⭐ Banner轮播图 +│ │ ║ 🀄 麻将棋牌馆广告 ║ │ │ (可配置) +│ │ ║ 点击查看详情 ║ │ │ +│ │ ╚═══════════════════════════════╝ │ │ +│ │ ○ ● ○ │ │◄── 轮播指示器 +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 📢 公告:本店营业时间 9:00-23:00 │ │◄── 滚动公告栏 +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ │ +│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ +│ │ │ 组局中... │ │ │ │ 待开始 │ │ │◄── 状态标签 +│ │ ├───────────┤ │ │ ├───────────┤ │ │ +│ │ │ 👤 │ │ │ │ 👤 👤 │ │ │ +│ │ │ 👤 👤 │ │ │ │ 🀄 │ │ │◄── ⭐ 九宫格麻将桌 +│ │ │ 🀄 │ │ │ │ 👤 👤 │ │ │ (显示已加入玩家) +│ │ │ ➕ ➕ │ │ │ │ 👤 │ │ │ +│ │ │ ➕ │ │ │ ├───────────┤ │ │ +│ │ ├───────────┤ │ │ │休闲局 │ │ │ +│ │ │休闲娱乐局 │ │ │ │12/20 周五 │ │ │◄── 预约信息 +│ │ │12/18 周三 │ │ │ │20:00-24:00│ │ │ +│ │ │18:00-22:00│ │ │ │304包厢 │ │ │ +│ │ │302包厢 │ │ │ │血战 禁烟 │ │ │ +│ │ │血流成河 │ │ │ │信誉≧4.0 │ │ │ +│ │ └───────────┘ │ └───────────────┘ │ +│ └───────────────┘ │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ │◄── 更多预约卡片... +│ │ ... │ │ ... │ │ +│ └───────────────┘ └───────────────┘ │ +│ │ +│ ┌───┐ │ +│ │ 🔘│ │◄── 悬浮球 +│ └───┘ │ +├─────────────────────────────────────────┤ +│ [🏠首页] [📅预约] [👤我的] │◄── TabBar +└─────────────────────────────────────────┘ +``` + +#### 重点说明: + +| 区域 | 重点级别 | 说明 | +|------|---------|------| +| ⭐ 九宫格麻将桌 | 高 | **核心展示区域**,直观显示当前已加入人数,空位显示➕,已加入显示头像 | +| ⭐ Banner轮播 | 中 | 后台可配置,用于活动推广 | +| 状态标签 | 高 | 组局中/待开始/进行中/已结束,一眼看出预约状态 | +| 预约信息 | 中 | 关键信息:时间、房间、玩法、限制条件 | + +--- + +### 3.3 麻将卡片组件详解 + +**文件**: `components/index/MahjongCard.vue` + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 九宫格布局说明 (4人局) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 位置1 (上) │ +│ 👤 ← 第3个加入的玩家 │ +│ │ +│ 位置2 🀄 位置3 │ +│ 👤 (麻将桌logo) 👤 │ +│ ↑ ↑ │ +│ 第1个加入 第2个加入 │ +│ (通常是发起者) 的玩家 │ +│ │ +│ 位置4 (下) │ +│ 👤 ← 第4个加入的玩家 │ +│ │ +├─────────────────────────────────────────────────────────────────┤ +│ 不同人数局的布局: │ +│ │ +│ 2人局: 3人局: 4人局(标准): │ +│ 👤 👤 │ +│ 👤 👤 👤 👤 👤 🀄 👤 │ +│ 👤 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.4 预约详情弹窗 + +**文件**: `components/com/index/ReservationPopup.vue` + +点击首页的预约卡片后弹出: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 预约信息 │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 发起者 👤 张三 │ │◄── ⭐ 发起者信息 +│ │ │ │ +│ │ 参与者 👤李四 👤王五 [黑名单]👤赵六 │ │◄── ⭐ 参与者列表 +│ │ ↑ │ │ (可显示黑名单标记) +│ │ 红色标记 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 开始时间 2025-12-20 18:00 │ │ +│ │ 结束时间 2025-12-20 22:00 │ │◄── 时间信息 +│ │ 合计:4小时 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 房间号: 304包厢-大包 │ │ +│ │ 人数: 4人 │ │◄── ⭐ 预约详情 +│ │ 玩法类型: 血战到底 │ │ +│ │ 具体规则: 2番起胡,3倍封顶 │ │ +│ │ 补充信息: 新手勿扰 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 是否禁烟: 禁烟 │ │ +│ │ 性别: 男 │ │◄── ⭐ 参与限制条件 +│ │ 年龄范围: 25岁 ~ 45岁 │ │ (重点检查项) +│ │ 信誉: ≧4.0 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 鸽子费: 10元 │ │◄── ⭐ 押金信息 +│ │ 组局成功后若有牌友未赴约,其鸽子费平均分给其他牌友。 │ │ +│ │ 组局成功或失败后鸽子费将全额返还。 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────┐ ┌─────────────────────────────┐ │ +│ │ 关闭 │ │ 参与组局 │ │◄── ⭐ 操作按钮 +│ └────────┘ └─────────────────────────────┘ │ (根据身份变化) +│ │ +│ 按钮状态说明: │ +│ • 未加入用户 → [关闭] [参与组局] │ +│ • 参与者 → [关闭] [退出组局] │ +│ • 发起者 → [关闭] [取消组局] [分享] │ +│ • 已结束 → [关闭] │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.5 发起预约 - 房间选择页 + +**文件**: `pages/appointment/book-room-page.vue` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ▓▓▓▓▓▓▓▓▓▓▓▓ 状态栏 ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 发起预约 │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ ┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌────┐│ │ +│ │ │12/18 ││12/19 ││12/20 ││12/21 ││12/22 ││12/23 ││... ││ │◄── ⭐ 日期选择 +│ │ │ 周三 ││ 周四 ││ 周五 ││ 周六 ││ 周日 ││ 周一 ││ ││ │ (今天+6天) +│ │ │[选中]││ ││ ││ ││ ││ ││ ││ │ +│ │ └──────┘└──────┘└──────┘└──────┘└──────┘└──────┘└────┘│ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ 营业时间:早09点 至 晚23点 │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 查看当前时段空闲时间 [ 🔘 开关 ] │ │◄── 过滤开关 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ ┌─────────────────────────────────────────────────────┐ │ │ +│ │ │ ┌──────┐ │ │ │ +│ │ │ │ 🖼️ │ 302包厢 [当前使用中] │ │ │◄── 房间状态 +│ │ │ │ 图片 │ 小包 2-4人 │ │ │ +│ │ │ └──────┘ 标准价:38元/小时 │ │ │ +│ │ │ 会员价:30元/小时 [预约] │ │ │◄── ⭐ 预约按钮 +│ │ │ │ │ │ +│ │ │ ━━ 已预定 ━━ 可预定 ━━ 不可预定 │ │ │◄── 图例说明 +│ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │ +│ │ │ │ 🟠 │ │ 🟢 │ │ 🟢 │ │ 🔴 │ │ │ │◄── ⭐ 时段状态 +│ │ │ │凌晨│ │上午│ │下午│ │晚上│ │ │ │ (核心展示) +│ │ │ └────┘ └────┘ └────┘ └────┘ │ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 304包厢 - 大包 4-8人 │ │ +│ │ ... │ │◄── 更多房间 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ [🏠首页] [📅预约] [👤我的] │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 时段状态颜色说明: + +| 颜色 | 状态 | 说明 | +|------|------|------| +| 🟢 绿色 | available | **可预约** - 该时段空闲 | +| 🟠 橙色 | reserved | **已预约** - 已被他人预约 | +| 🔴 灰色 | unavailable | **不可预约** - 后台设置不可用 | +| 🔵 蓝色 | using | **使用中** - 当前正在使用 | + +--- + +### 3.6 发起预约 - 预约表单页 + +**文件**: `pages/appointment/appointment-page.vue` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ◀ 返回 发起预约 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 时间设置 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ 日期 12/20 周五 [选择]│ │◄── 日期选择 +│ │ │ │ +│ │ 时间段 下午 (12:00-17:59) [选择]│ │◄── ⭐ 时段选择 +│ │ │ │ (仅显示可预约) +│ │ 最晚到店 2025-12-20 12:30 [选择]│ │◄── 最晚到店时间 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 基本信息 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ 组局名称 [ 休闲娱乐,老少皆宜 ] │ │◄── ⭐ 必填 +│ │ │ │ +│ │ 人数 [ 4人 ▼ ] │ │◄── ⭐ 人数选择 +│ │ 说明:选择1人为"无需组局"(独享模式) │ │ +│ │ │ │ +│ │ 玩法类型 [ 血战到底 ▼ ] │ │◄── ⭐ 必填 +│ │ │ │ +│ │ 具体规则 [ 2番起胡,3倍封顶 ▼ ] │ │◄── ⭐ 必填 +│ │ │ │ +│ │ 其他补充 [ 新手勿扰,打牌快 ] │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 参与限制 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ 是否禁烟 ○ 不禁烟 ● 禁烟 │ │ +│ │ │ │ +│ │ 性别 ● 不限 ○ 男 ○ 女 │ │◄── ⭐ 筛选条件 +│ │ │ │ +│ │ 年龄范围 [ 25岁 ] - [ 45岁 ] [选择] │ │ +│ │ │ │ +│ │ 信誉 大于等于 [ ➖ ] 4.0 [ ➕ ] │ │◄── ⭐ 信誉门槛 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 鸽子费(押金) │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ │ │ +│ │ ○ 0元 ○ 5元 ● 10元 ○ 自定义 │ │◄── ⭐ 核心功能 +│ │ │ │ +│ │ 自定义金额 [ 20 ] 元 (0-50元) │ │ +│ │ │ │ +│ │ 说明:鸽子费(保证金),参与人需缴纳鸽子费。若有参与 │ │ +│ │ 者在预约后没有赴约,其鸽子费由在场的所有人平分。组 │ │ +│ │ 局成功或失败后鸽子费将全额返还。 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────┐ ┌─────────────────────────────────────────┐ │ +│ │ 重置 │ │ 发起预约 │ │◄── ⭐ 提交按钮 +│ └─────────┘ └─────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 表单必填项说明: + +| 字段 | 必填 | 说明 | +|------|------|------| +| 日期 | ✅ | 从房间页带入 | +| 时间段 | ✅ | 仅显示该房间该日期可预约的时段 | +| 组局名称 | ✅ | 用于首页展示 | +| 人数 | ✅ | 1人=独享模式,2-4人=组局模式 | +| 玩法类型 | ✅ | 血战/血流成河等 | +| 具体规则 | ✅ | 依赖玩法类型 | +| 鸽子费 | ❌ | 默认0元,最高50元 | + +--- + +### 3.7 个人中心页 + +**文件**: `pages/me/me-page.vue` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ▓▓▓▓▓▓▓▓▓▓▓▓ 状态栏 ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 张三 │ │◄── ⭐ 用户头像和昵称 +│ │ UID: 10086 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ 🖼️ 当前没有预约 │ │ │◄── 当前预约状态 +│ │ └────────────────────────────────────────────────────┘ │ │ +│ │ 或者 │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ 待开始 | 休闲局 │ │ │◄── ⭐ 当前预约卡片 +│ │ │ 12/20 18:00-22:00 | 304包厢 │ │ │ (如果有预约) +│ │ │ 血战到底 | 4人 │ │ │ +│ │ │ [牌友评价] [签到] │ │ │◄── 快捷操作按钮 +│ │ └────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 我的信誉 4.8 │ │◄── ⭐ 信誉分 +│ │ │ │ (点击查看详情) +│ │ 牌品 ⭐⭐⭐⭐⭐ 4.5 │ │◄── ⭐ 牌品评分 +│ │ │ │ +│ │ 牌技 ⭐⭐⭐⭐☆ 4.2 │ │◄── ⭐ 牌技评分 +│ │ │ │ +│ │ 鸽子数 2 次 │ │◄── 爽约次数 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 常用功能 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ │ │ +│ │ 📋 预约记录 📑 订单记录 ❓ 常见问题 │ │ +│ │ │ │ +│ │ 🚫 黑名单 📞 联系我们 📩 我的消息 │ │◄── 功能入口 +│ │ │ │ +│ │ 💰 我的收益 │ │◄── ⭐ 收益入口 +│ │ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ [🏠首页] [📅预约] [👤我的] │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.8 签到弹窗 + +**文件**: `components/com/page/qiandao-popup.vue` + +签到是预约流程的关键步骤,**仅发起者可操作**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 预约签到 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 请确认实际到场的参与者: │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ ☑️ 👤 张三 (发起者) ← 默认勾选 │ │ +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ ☑️ 👤 李四 │ │◄── ⭐ 勾选到场人员 +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ ☐ 👤 王五 ← 未勾选=爽约 │ │◄── ⭐ 未勾选=爽约 +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ ☑️ 👤 赵六 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ⚠️ 注意: │ +│ • 未勾选的参与者将被标记为"爽约" │ +│ • 爽约者信誉分 -0.5,鸽子次数 +1 │ +│ • 爽约者的押金将平分给到场者 │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 确认签到 │ │◄── ⭐ 提交签到 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.9 评价弹窗 + +**文件**: `components/com/page/reservation-evaluate.vue` + +预约完成后,参与者可以互相评价: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 牌友评价 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 请为到场的牌友评分: │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 李四 │ │ +│ │ │ │ +│ │ 牌品 ⭐⭐⭐⭐⭐ │ │◄── ⭐ 牌品评分 +│ │ │ │ (1-5星) +│ │ 牌技 ⭐⭐⭐⭐☆ │ │◄── ⭐ 牌技评分 +│ │ │ │ (1-5星) +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 赵六 [已评价] ✅ │ │◄── 已评价标记 +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────┐ ┌─────────────────────────────────────────┐ │ +│ │ 关闭 │ │ 提交评价 │ │ +│ └─────────────┘ └─────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.10 我的收益页 + +**文件**: `pages/me/my-earnings-page.vue` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ◀ 返回 我的收益 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ 待提现收益 已提现收益 │ │ +│ │ ¥128.50 ¥350.00 │ │◄── ⭐ 收益统计 +│ │ │ │ +│ │ [ 申请提现 ] │ │◄── ⭐ 提现按钮 +│ │ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────┬───────────────────────┐ │ +│ │ 收益记录 │ 提现记录 │ │◄── Tab切换 +│ └───────────────────────┴───────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 2025-12-18 14:30 │ │ +│ │ 组局佣金 - 304包厢 +¥12.50 │ │◄── 收益明细 +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ 2025-12-17 20:00 │ │ +│ │ 爽约补偿 - 302包厢 +¥5.00 │ │◄── 爽约补偿 +│ ├─────────────────────────────────────────────────────────┤ │ +│ │ 2025-12-15 18:00 │ │ +│ │ 组局佣金 - 306包厢 +¥18.00 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3.11 页面跳转关系图 + +```mermaid +graph TD + subgraph TabBar页面 + A[首页
index.vue] + B[预约
book-room-page.vue] + C[我的
me-page.vue] + end + + subgraph 弹窗组件 + D[预约详情弹窗
ReservationPopup] + E[签到弹窗
qiandao-popup] + F[评价弹窗
reservation-evaluate] + end + + subgraph 子页面 + G[预约表单
appointment-page] + H[预约记录
appointment-record] + I[我的收益
my-earnings-page] + J[我的消息
my-message-page] + K[黑名单
blacklist-page] + L[登录
login] + end + + A -->|点击卡片| D + D -->|参与组局| D + D -->|发起者取消| D + + B -->|选择房间| G + G -->|提交成功| A + + C -->|预约记录| H + C -->|我的收益| I + C -->|我的消息| J + C -->|黑名单| K + C -->|未登录| L + + H -->|点击预约| D + H -->|牌友评价| F + H -->|签到| E + + C -->|当前预约卡片| D + C -->|牌友评价| F + C -->|签到| E + + style A fill:#e1f5fe + style B fill:#e1f5fe + style C fill:#e1f5fe + style D fill:#fff3e0 + style E fill:#fff3e0 + style F fill:#fff3e0 + style G fill:#e8f5e9 +``` + +--- + +### 3.12 界面交互流程总结 + +```mermaid +flowchart LR + subgraph 浏览流程 + A1[首页浏览预约] --> A2[点击卡片] + A2 --> A3[查看详情弹窗] + A3 --> A4{是否加入?} + A4 -->|是| A5[参与组局] + A4 -->|否| A6[关闭弹窗] + end + + subgraph 发起流程 + B1[点击预约Tab] --> B2[选择日期] + B2 --> B3[选择房间] + B3 --> B4[点击预约按钮] + B4 --> B5[填写预约表单] + B5 --> B6[支付鸽子费] + B6 --> B7[发起成功] + end + + subgraph 参与后流程 + C1[预约开始] --> C2[发起者签到] + C2 --> C3[确认到场人员] + C3 --> C4[预约进行中] + C4 --> C5[预约结束] + C5 --> C6[互相评价] + C6 --> C7[押金退还] + end + + style A5 fill:#4caf50,color:#fff + style B7 fill:#4caf50,color:#fff + style C7 fill:#4caf50,color:#fff +``` + +--- + +## 4. 核心业务模块 + +### 4.1 用户认证模块 + +#### 4.1.1 登录流程 + +用户通过微信小程序登录,获取手机号授权后绑定账号。 + +```mermaid +sequenceDiagram + participant U as 用户 + participant MP as 小程序 + participant WX as 微信服务器 + participant BE as 后端服务 + participant DB as 数据库 + + U->>MP: 点击登录 + MP->>U: 请求授权手机号 + U->>MP: 同意授权 + MP->>WX: 获取code和encryptedData + WX->>MP: 返回加密数据 + MP->>BE: POST /user/UseWxPhoneNumberLogin + Note over MP,BE: 传递code和sessionAuthId + BE->>WX: 解密手机号 + WX->>BE: 返回手机号 + BE->>DB: 查询/创建用户 + DB->>BE: 返回用户信息 + BE->>MP: 返回token和用户信息 + MP->>MP: 存储token到本地 + MP->>U: 登录成功 +``` + +#### 3.1.2 用户信息结构 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | +| nickName | string | 昵称 | +| avatarImage | string | 头像URL | +| sex | int | 性别:1男 2女 | +| birthday | DateTime? | 生日(用于计算年龄) | +| credit_score | decimal | 信誉分(0-5分,默认5分) | +| play_level | decimal | 牌品评分(1-5分,默认4分) | +| skills_level | decimal | 牌技评分(1-5分,默认4分) | +| dove_count | int | 鸽子次数 | + +--- + +### 4.2 预约管理模块 + +#### 4.2.1 预约生命周期 + +```mermaid +stateDiagram-v2 + [*] --> 待开始: 创建预约 + 待开始 --> 已锁定: 人满 + 待开始 --> 已取消: 发起者取消 + 待开始 --> 待开始: 参与者加入/退出 + 已锁定 --> 待开始: 参与者退出 + 已锁定 --> 已取消: 发起者取消 + 已锁定 --> 进行中: 签到 + 待开始 --> 进行中: 签到(人未满也可签到) + 进行中 --> 已结束: 时间结束 + 已取消 --> [*] + 已结束 --> [*] +``` + +#### 4.2.2 预约状态说明 + +| status | 状态名 | 说明 | +|--------|--------|------| +| 0 | 待开始 | 预约已创建,等待参与者加入或等待开始时间 | +| 1 | 已锁定 | 参与人数已满,等待开始 | +| 2 | 进行中 | 发起者已签到,游戏进行中 | +| 3 | 已结束 | 预约时间已过,预约完成 | +| 4 | 已取消 | 预约被取消 | + +#### 4.2.3 发起预约流程 + +```mermaid +flowchart TD + A[用户点击发起预约] --> B[选择日期] + B --> C[查看房间列表及时段状态] + C --> D{选择房间和时段} + D --> E[填写预约信息] + + subgraph 预约信息 + E --> E1[组局名称] + E --> E2[人数 1-4人] + E --> E3[玩法类型] + E --> E4[游戏规则] + E --> E5[参与限制] + E --> E6[鸽子费设置] + end + + E1 & E2 & E3 & E4 & E5 & E6 --> F[提交前验证] + F --> G{调用CanCreateSQReservation} + G -->|验证失败| H[显示错误信息] + H --> E + G -->|验证通过| I{是否有鸽子费} + I -->|有| J[发起微信支付] + J --> K{支付结果} + K -->|成功| L[调用AddSQReservation创建预约] + K -->|失败| M[提示支付失败] + I -->|无| L + L --> N[创建预约记录] + N --> O[创建发起者参与记录 role=1] + O --> P[返回预约ID] + P --> Q[跳转到预约详情] +``` + +#### 4.2.4 加入预约流程 + +```mermaid +flowchart TD + A[用户点击加入预约] --> B{预约是否存在且未结束} + B -->|否| C[提示:预约不存在或已结束] + B -->|是| D{是否已加入该预约} + D -->|是| E[提示:您已加入该预约] + D -->|否| F{是否为独享模式} + F -->|是| G[提示:该预约不接受其他人加入] + F -->|否| H[校验参与条件] + + subgraph 条件校验 + H --> H1{信誉分是否达标} + H1 -->|否| I1[提示:信誉分不足] + H1 -->|是| H2{性别是否符合} + H2 -->|否| I2[提示:性别不符合要求] + H2 -->|是| H3{年龄是否在范围内} + H3 -->|否| I3[提示:年龄不符合限制] + H3 -->|是| H4{是否有时间冲突} + H4 -->|是| I4[提示:有其他预约时间冲突] + H4 -->|否| H5{预约是否已满} + H5 -->|是| I5[提示:预约已满] + end + + H5 -->|否| J{是否有鸽子费} + J -->|有| K[发起微信支付] + K --> L{支付结果} + L -->|成功| M[创建参与者记录 role=0] + L -->|失败| N[提示支付失败] + J -->|无| M + M --> O[加入成功] +``` + +#### 4.2.5 取消预约流程 + +```mermaid +flowchart TD + A[用户点击取消预约] --> B{预约是否存在} + B -->|否| C[提示:预约不存在] + B -->|是| D{距开始是否<30分钟} + D -->|是| E[提示:开始前30分钟无法取消] + D -->|否| F{预约状态是否>=3} + F -->|是| G[提示:已结束或已取消] + F -->|否| H{用户角色} + + H -->|发起者 role=1| I[发起者取消整个预约] + I --> J[预约状态改为已取消 status=4] + J --> K[所有参与者标记为已退出] + K --> L{是否有已支付押金} + L -->|有| M[已支付者标记为待退款 is_refund=3] + M --> N[发送通知给所有参与者] + L -->|无| N + + H -->|参与者 role=0| O{预约是否已开始} + O -->|是| P[提示:只有发起者可取消] + O -->|否| Q[参与者退出] + Q --> R[标记该用户为已退出 status=1] + R --> S{该用户是否已支付押金} + S -->|是| T[标记为待退款 is_refund=3] + T --> U[退出成功] + S -->|否| U + + N --> V[取消完成] +``` + +--- + +### 4.3 签到模块 + +#### 4.3.1 签到流程 + +签到是预约流程的关键环节,只有发起者可以操作,用于确认实际到场人员。 + +```mermaid +flowchart TD + A[发起者点击签到] --> B{预约是否存在} + B -->|否| C[提示:预约不存在] + B -->|是| D{预约状态是否>=3} + D -->|是| E[提示:已结束或已取消] + D -->|否| F{是否已签到 status=2} + F -->|是| G[提示:已签到,无法重复] + F -->|否| H{是否为发起者} + H -->|否| I[提示:仅发起者可签到] + H -->|是| J[显示参与者列表] + + J --> K[勾选实际到场人员] + K --> L[提交签到] + + L --> M[开启数据库事务] + M --> N[预约状态改为进行中 status=2] + N --> O[所有未退出参与者默认标记为到场 is_arrive=1] + O --> P{是否有未到场人员} + + P -->|有| Q[未到场人员处理] + subgraph 爽约惩罚 + Q --> Q1[标记为未到场 is_arrive=2] + Q1 --> Q2[标记为已退出 status=1] + Q2 --> Q3[扣除信誉分 -0.5] + Q3 --> Q4[增加鸽子次数 dove_count++] + Q4 --> Q5[记录信誉变更日志] + end + + P -->|无| R[到场人员处理] + Q5 --> R + + subgraph 守约奖励 + R --> R1[增加信誉分 +0.2 最高5.0] + R1 --> R2[记录信誉变更日志] + end + + R2 --> S{是否有押金} + S -->|有| T[到场且已支付者标记为待退款 is_refund=3] + T --> U[提交事务] + S -->|无| U + U --> V[签到完成] +``` + +#### 4.3.2 签到后信誉变化 + +| 情况 | 信誉变化 | 其他影响 | +|------|----------|----------| +| 到场(守约) | +0.2(最高5.0) | - | +| 未到场(爽约) | -0.5 | 鸽子次数+1 | + +--- + +### 4.4 评价模块 + +#### 4.4.1 评价流程 + +```mermaid +flowchart TD + A[用户进入预约详情] --> B{预约是否已完成且已签到} + B -->|否| C[不显示评价入口] + B -->|是| D[显示评价入口] + D --> E[点击牌友评价] + E --> F[获取可评价参与者列表] + F --> G[只显示实际到场的其他参与者] + + G --> H[选择要评价的人] + H --> I[填写评价] + + subgraph 评价维度 + I --> I1[牌品评分 1-5星] + I --> I2[牌技评分 1-5星] + end + + I1 & I2 --> J[提交评价] + J --> K[创建评价记录] + K --> L[重新计算被评价人的平均分] + + subgraph 平均分计算 + L --> L1[获取所有历史评价] + L1 --> L2[计算 牌品 = sum评价 + 4 / 评价次数 + 1] + L2 --> L3[计算 牌技 = sum评价 + 4 / 评价次数 + 1] + L3 --> L4[如果只有1次评价,分母+1避免偏差] + end + + L4 --> M[更新用户评分] + M --> N[评价完成] +``` + +#### 4.4.2 评价规则 + +1. **评价时机**:预约状态为"已结束"且已签到后 +2. **评价对象**:只能评价实际到场的其他参与者(排除自己) +3. **评价次数**:每个参与者只能被评价一次 +4. **评价维度**: + - 牌品(play_level):1-5分 + - 牌技(skills_level):1-5分 + +#### 4.4.3 评分计算公式 + +``` +新评分 = (历史评分总和 + 初始分4) / (评价次数 + 1) + +特殊情况:当只有1次评价时,分母为(评价次数 + 2)避免偏差 +``` + +--- + +### 4.5 押金(鸽子费)模块 + +#### 4.5.1 押金流程总览 + +```mermaid +flowchart TD + subgraph 发起预约 + A[发起者设置鸽子费] --> B[发起者支付鸽子费] + B --> C[创建预约成功] + C --> D[参与者记录 is_refund=1 待支付] + end + + subgraph 加入预约 + E[参与者加入] --> F{预约是否有鸽子费} + F -->|有| G[参与者支付鸽子费] + G --> H[参与者记录 is_refund=2 已支付] + F -->|无| I[参与者记录 is_refund=0] + end + + subgraph 签到/取消 + J[签到或取消] --> K{参与者是否已支付} + K -->|是| L[标记为 is_refund=3 待退款] + K -->|否| M[无需退款] + end + + subgraph 定时任务 + N[定时扫描 is_refund=3] --> O[调用微信退款API] + O --> P{退款结果} + P -->|成功| Q[更新 is_refund=4 已退款] + P -->|失败| R[更新 is_refund=5 退款异常] + end +``` + +#### 4.5.2 押金状态说明 + +| is_refund | 状态名 | 说明 | +|-----------|--------|------| +| 0 | 无押金 | 预约不需要鸽子费 | +| 1 | 待支付 | 需要支付但未支付 | +| 2 | 已支付 | 已完成支付 | +| 3 | 待退款 | 已发起退款流程,等待处理 | +| 4 | 已退款 | 退款完成 | +| 5 | 退款异常 | 退款失败 | + +#### 4.5.3 押金分配规则 + +```mermaid +flowchart TD + A[签到确认] --> B{是否有人爽约} + B -->|无| C[所有人押金原路退还] + B -->|有| D[计算爽约人数和到场人数] + D --> E[爽约者押金不退还] + E --> F[爽约者押金平分给到场者] + F --> G[到场者获得额外收益] + G --> H[到场者原押金退还] +``` + +--- + +### 4.6 消息通知模块 + +#### 4.6.1 消息类型 + +| 类型 | message_type | 说明 | +|------|--------------|------| +| 系统消息 | 0 | 预约相关自动通知 | +| 私信 | 1 | 暂未实现 | + +#### 4.6.2 自动发送场景 + +```mermaid +flowchart LR + A[组局成功] --> B[通知所有参与者] + C[组局失败/解散] --> D[通知所有参与者] + E[预约即将开始] --> F[提醒参与者] + G[签到完成] --> H[通知到场/未到场情况] +``` + +#### 4.6.3 消息流程 + +```mermaid +sequenceDiagram + participant S as 系统 + participant DB as 数据库 + participant U as 用户 + + S->>DB: 插入消息记录 (SQMessage) + Note over DB: user_id=0 表示全体用户 + U->>DB: 获取消息列表 + DB->>U: 返回消息 + 已读状态 + Note over DB: 已读状态存储在 SQMessageRead 表 + U->>DB: 标记全部已读 + Note over DB: 插入已读记录 +``` + +--- + +### 4.7 收益模块 + +#### 4.7.1 收益来源 + +1. **发起预约佣金**:房费的10%(需线下员工在后台添加) +2. **爽约者押金分成**:爽约者的鸽子费平分给到场者 + +#### 4.7.2 收益流程 + +```mermaid +flowchart TD + A[预约完成] --> B{发起者是否有佣金} + B -->|有| C[后台员工添加佣金记录] + C --> D[更新待提现金额] + + E[签到确认有人爽约] --> F[计算爽约押金] + F --> G[平分给到场者] + G --> D + + D --> H[用户申请提现] + H --> I{金额是否超出可提现} + I -->|是| J[提示:超出可提现金额] + I -->|否| K[创建提现申请记录] + K --> L[状态:提现中] + L --> M[后台审核] + M --> N{审核结果} + N -->|同意| O[线下打款] + O --> P[状态:已提现] + N -->|拒绝| Q[状态:已取消] +``` + +#### 4.7.3 提现状态 + +| 状态 | 说明 | +|------|------| +| 提现中 | 申请已提交,等待审核 | +| 已提现 | 线下打款完成 | +| 已取消 | 后台拒绝/取消申请 | + +--- + +### 4.8 黑名单模块 + +#### 4.8.1 黑名单功能 + +```mermaid +flowchart TD + A[用户A将用户B加入黑名单] --> B[写入黑名单表] + B --> C[首页预约列表] + C --> D{用户B是否在黑名单} + D -->|是| E[过滤B发起的预约] + D -->|否| F[正常显示] +``` + +#### 4.8.2 黑名单影响 + +1. 首页自动过滤黑名单用户发起的预约 +2. 黑名单用户无法加入我发起的预约(待实现) +3. 评价页面显示黑名单状态 + +--- + +## 5. 核心业务流程图 + +### 5.1 完整预约生命周期 + +```mermaid +flowchart TB + subgraph 发起阶段 + A1[选择房间日期] --> A2[填写预约信息] + A2 --> A3[设置参与限制] + A3 --> A4[设置鸽子费] + A4 --> A5{有鸽子费?} + A5 -->|是| A6[微信支付] + A5 -->|否| A7[创建预约] + A6 --> A7 + A7 --> A8[预约状态: 待开始] + end + + subgraph 参与阶段 + A8 --> B1[等待参与者加入] + B1 --> B2{人数已满?} + B2 -->|是| B3[预约状态: 已锁定] + B2 -->|否| B4{有人退出?} + B4 -->|是| B1 + B4 -->|否| B5{到达开始时间?} + B3 --> B5 + B5 -->|否| B1 + end + + subgraph 签到阶段 + B5 -->|是| C1[发起者签到] + C1 --> C2[确认到场人员] + C2 --> C3[处理到场/未到场] + C3 --> C4[更新信誉分] + C4 --> C5[预约状态: 进行中] + end + + subgraph 结束阶段 + C5 --> D1[游戏进行中] + D1 --> D2{到达结束时间?} + D2 -->|否| D1 + D2 -->|是| D3[预约状态: 已结束] + D3 --> D4[可以互相评价] + D4 --> D5[处理押金退款] + D5 --> D6[发放佣金] + end + + subgraph 取消分支 + A8 --> E1{发起者取消?} + B1 --> E1 + B3 --> E1 + E1 -->|是| E2[预约状态: 已取消] + E2 --> E3[所有人退出] + E3 --> E4[退还已支付押金] + end +``` + +### 5.2 时序图:完整预约流程 + +```mermaid +sequenceDiagram + participant U1 as 发起者 + participant U2 as 参与者 + participant FE as 前端 + participant BE as 后端 + participant DB as 数据库 + participant WX as 微信支付 + + %% 发起预约 + rect rgb(200, 230, 200) + Note over U1,WX: 发起预约阶段 + U1->>FE: 选择房间和时段 + FE->>BE: GetRoomListWithSlotsNew + BE->>DB: 查询房间状态 + DB->>BE: 返回房间列表 + BE->>FE: 房间时段状态 + U1->>FE: 填写预约信息 + FE->>BE: CanCreateSQReservation + BE->>DB: 校验时间冲突等 + DB->>BE: 校验结果 + BE->>FE: 可以创建 + FE->>WX: 支付鸽子费 + WX->>FE: 支付成功 + FE->>BE: AddSQReservation + BE->>DB: 创建预约+参与者记录 + DB->>BE: 返回预约ID + BE->>FE: 预约成功 + end + + %% 加入预约 + rect rgb(200, 200, 230) + Note over U1,WX: 加入预约阶段 + U2->>FE: 浏览预约列表 + FE->>BE: GetReservationList + BE->>DB: 查询预约列表 + DB->>BE: 预约数据 + BE->>FE: 预约列表 + U2->>FE: 点击加入 + FE->>BE: JoinReservation + BE->>DB: 校验条件 + DB->>BE: 校验通过 + BE->>FE: 需要支付鸽子费 + FE->>WX: 支付鸽子费 + WX->>FE: 支付成功 + FE->>BE: JoinReservation(带支付信息) + BE->>DB: 创建参与者记录 + DB->>BE: 成功 + BE->>FE: 加入成功 + end + + %% 签到 + rect rgb(230, 200, 200) + Note over U1,WX: 签到阶段 + U1->>FE: 点击签到 + FE->>FE: 显示参与者列表 + U1->>FE: 确认到场人员 + FE->>BE: CheckInReservation + BE->>DB: 开启事务 + BE->>DB: 更新预约状态 + BE->>DB: 更新到场状态 + BE->>DB: 更新信誉分 + BE->>DB: 标记待退款 + BE->>DB: 提交事务 + DB->>BE: 成功 + BE->>FE: 签到成功 + end + + %% 评价 + rect rgb(230, 230, 200) + Note over U1,WX: 评价阶段 + U1->>FE: 点击评价 + FE->>BE: GetEvaluateServices + BE->>DB: 查询可评价人员 + DB->>BE: 参与者列表 + BE->>FE: 可评价列表 + U1->>FE: 提交评价 + FE->>BE: AddEvaluateServices + BE->>DB: 创建评价记录 + BE->>DB: 更新被评价人分数 + DB->>BE: 成功 + BE->>FE: 评价成功 + end +``` + +### 5.3 房间时段状态机 + +```mermaid +stateDiagram-v2 + [*] --> 可预约: 默认状态 + 可预约 --> 已预约: 有人预约该时段 + 可预约 --> 不可预约: 后台设置不可用 + 已预约 --> 可预约: 预约取消 + 已预约 --> 进行中: 签到开始 + 进行中 --> 可预约: 预约结束 + 不可预约 --> 可预约: 后台取消不可用 + + note right of 可预约: 绿色 + note right of 已预约: 橙色 + note right of 不可预约: 灰色 +``` + +--- + +## 6. 数据模型 + +### 9.1 核心数据表关系图 + +```mermaid +erDiagram + CoreCmsUser ||--o{ SQReservationParticipants : "参与" + CoreCmsUser ||--o{ SQReservationEvaluate : "评价" + CoreCmsUser ||--o{ SQReservationReputation : "信誉记录" + CoreCmsUser ||--o{ CoreCmsUserBlacklist : "黑名单" + CoreCmsUser ||--o{ SQMessage : "接收消息" + CoreCmsUser ||--o{ SQEarningsRecord : "收益" + CoreCmsUser ||--o{ SQWithdrawRecord : "提现" + + SQReservations ||--o{ SQReservationParticipants : "包含" + SQReservations ||--o{ SQReservationEvaluate : "评价关联" + SQReservations }o--|| SQRooms : "使用房间" + + SQRooms ||--o{ SQRoomUnavailableTimes : "不可用时间" + SQRooms ||--o{ SQRoomPricing : "价格配置" + + CoreCmsUser { + int id PK + string nickName + string avatarImage + int sex + datetime birthday + decimal credit_score + decimal play_level + decimal skills_level + int dove_count + } + + SQReservations { + int id PK + int room_id FK + string room_name + datetime start_time + datetime end_time + int duration_minutes + string title + string game_type + string game_rule + int player_count + int status + decimal deposit_fee + decimal credit_limit + int gender_limit + int min_age + int max_age + bool is_smoking + datetime latest_arrival_time + string extra_info + bool is_solo_mode + datetime created_at + datetime updated_at + } + + SQReservationParticipants { + int id PK + int reservation_id FK + int user_id FK + int role + int status + datetime join_time + datetime quit_time + int is_arrive + datetime check_reservation + int is_refund + string paymentId + string important_data + } + + SQRooms { + int id PK + string name + int capacity + decimal price_per_hour + string description + string image_url + bool status + datetime created_at + } + + SQReservationEvaluate { + int id PK + int reservation_id FK + int user_id FK + int to_user_id FK + int role + decimal play_level + decimal skills_level + datetime created_at + } + + SQReservationReputation { + int id PK + int user_id FK + int reservation_id FK + decimal reputation_value + string remark + datetime created_at + } +``` + +### 9.2 数据表详细说明 + +#### 5.2.1 SQReservations(预约表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 主键 | +| room_id | int | 房间ID(外键) | +| room_name | string | 房间名称(冗余) | +| start_time | datetime | 开始时间 | +| end_time | datetime | 结束时间 | +| duration_minutes | int | 时长(分钟) | +| title | string | 组局名称 | +| game_type | string | 游戏类型(血战/血流成河等) | +| game_rule | string | 游戏规则 | +| player_count | int | 需要人数(1-4) | +| status | int | 状态(0待开始/1已锁定/2进行中/3已结束/4已取消) | +| deposit_fee | decimal | 押金费用 | +| credit_limit | decimal | 最低信誉要求 | +| gender_limit | int | 性别限制(0不限/1男/2女) | +| min_age | int | 最小年龄限制 | +| max_age | int | 最大年龄限制 | +| is_smoking | bool | 是否禁烟 | +| latest_arrival_time | datetime | 最晚到店时间 | +| extra_info | string | 其他补充说明 | +| is_solo_mode | bool | 是否独享模式(人数=1时自动设为true) | +| remarks | string | 备注(如取消原因) | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 更新时间 | + +#### 5.2.2 SQReservationParticipants(参与者表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 主键 | +| reservation_id | int | 预约ID(外键) | +| user_id | int | 用户ID(外键) | +| role | int | 角色(0参与者/1发起者) | +| status | int | 状态(0正常/1已退出) | +| join_time | datetime | 加入时间 | +| quit_time | datetime | 退出时间 | +| is_arrive | int | 是否到场(0未签到/1到场/2未到场) | +| check_reservation | datetime | 签到时间 | +| is_refund | int | 退款状态(0无/1待支付/2已支付/3待退款/4已退款/5异常) | +| paymentId | string | 支付订单号 | +| important_data | string | 重要数据(JSON格式,含支付信息等) | + +--- + +## 7. API接口详解 + +### 9.1 预约相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 首页预约列表 | GET | /api/sq/GetReservationList | 无 | 获取未结束的预约列表,自动过滤黑名单 | +| 预约详情 | GET | /api/sq/GetReservationDetail | 无 | 根据ID获取预约详情 | +| 我的预约 | GET | /api/sq/GetMyReservation | 需要 | type=0参与的/type=1发起的 | +| 正在进行 | GET | /api/sq/GetMyUseReservation | 需要 | 获取未结束的预约 | +| 验证创建 | POST | /api/sq/CanCreateSQReservation | 需要 | 创建前校验 | +| 创建预约 | POST | /api/sq/AddSQReservation | 需要 | 发起新预约 | +| 加入预约 | POST | /api/sq/JoinReservation | 需要 | 加入现有预约 | +| 取消预约 | POST | /api/sq/CancelReservation | 需要 | 发起者/参与者取消 | +| 签到 | POST | /api/sq/CheckInReservation | 需要 | 发起者签到确认 | + +### 9.2 房间相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 可选日期 | GET | /api/sq/GetAvailableDates | 无 | 今天+未来6天 | +| 房间时段 | GET | /api/sq/GetRoomListWithSlotsNew | 无 | 房间列表及时段状态 | +| 房间详情 | GET | /api/sq/GetRoomDetail | 无 | 房间信息及时段 | +| 可预约房间 | GET | /api/sq/GetReservationRoomList | 无 | 指定时间可预约的房间 | +| 营业时间 | GET | /api/sq/GetBusinessHours | 无 | 09:00-23:00 | + +### 9.3 评价相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 获取评价 | GET | /api/sq/GetEvaluateServices | 需要 | 获取可评价的参与者 | +| 添加评价 | POST | /api/sq/AddEvaluateServices | 需要 | 评价参与者 | +| 信誉记录 | GET | /api/sq/GetReputationByUser | 需要 | 我的信誉变化记录 | +| 评价给我 | GET | /api/sq/GetEvaluateToMe | 需要 | 别人给我的评价 | + +### 9.4 消息相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 消息列表 | GET | /api/sq/GetMessageList | 需要 | 站内信列表 | +| 未读数量 | GET | /api/sq/GetUnreadCount | 需要 | 未读消息数 | +| 标记已读 | POST | /api/sq/MarkAllAsRead | 需要 | 全部标记已读 | + +### 9.5 收益相关接口 + +| 接口 | 方法 | 路径 | 权限 | 说明 | +|------|------|------|------|------| +| 收益统计 | GET | /api/sq/GetEarningsSummary | 需要 | 待提现+已提现 | +| 收益记录 | POST | /api/sq/GetEarningsRecordList | 需要 | 收益明细 | +| 提现记录 | POST | /api/sq/GetWithdrawRecordList | 需要 | 提现历史 | +| 申请提现 | POST | /api/sq/ApplyWithdraw | 需要 | 发起提现 | +| 收益规则 | GET | /api/sq/GetEarningsRule | 无 | 规则说明 | + +--- + +## 8. 业务规则汇总 + +### 9.1 时间规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 可预约范围 | 今天+未来6天 | 共7天 | +| 时段划分 | 凌晨(0-6h)/上午(6-12h)/下午(12-18h)/晚上(18-24h) | 4个时段 | +| 营业时间 | 09:00-23:00 | 可在后台配置 | +| 取消限制 | 开始前30分钟 | 30分钟内无法取消 | + +### 9.2 人数规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 最少人数 | 1人 | 独享模式 | +| 最多人数 | 4人 | 麻将标准人数 | +| 独享模式 | player_count=1 | 自动设置,不接受其他人加入 | +| 人满锁定 | 达到player_count | 状态变为已锁定(1) | + +### 9.3 押金规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 金额范围 | 0-50元 | 可选0/5/10/20或自定义 | +| 用途 | 防止爽约 | 到场退还,爽约扣除 | +| 爽约分配 | 平分给到场者 | 由到场者瓜分 | +| 退款时机 | 签到后/取消后 | 标记待退款,定时任务处理 | + +### 9.4 信誉规则 + +| 情况 | 变化 | 说明 | +|------|------|------| +| 初始信誉 | 5.0分 | 满分 | +| 守约奖励 | +0.2分 | 最高5.0 | +| 爽约惩罚 | -0.5分 | 最低0分,鸽子次数+1 | +| 信誉限制 | 发起者设置 | 低于限制无法加入 | + +### 9.5 评价规则 + +| 规则 | 值 | 说明 | +|------|-----|------| +| 评价时机 | 预约完成后 | 状态为已结束 | +| 评价对象 | 到场的其他参与者 | 排除自己和未到场 | +| 评价维度 | 牌品/牌技 | 各1-5分 | +| 评价次数 | 每人一次 | 不可重复评价 | +| 初始分 | 4分 | 用于计算平均 | + +### 9.6 参与限制规则 + +| 限制项 | 可选值 | 说明 | +|--------|--------|------| +| 性别限制 | 不限/男/女 | 0/1/2 | +| 年龄限制 | 最小-最大 | 0表示不限 | +| 信誉限制 | 0-5分 | 最低信誉要求 | +| 禁烟 | 是/否 | 是否禁止吸烟 | + +--- + +## 9. 状态机定义 + +### 9.1 预约状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> S0_待开始: 创建预约 + + S0_待开始 --> S1_已锁定: 人满 + S0_待开始 --> S2_进行中: 签到 + S0_待开始 --> S4_已取消: 取消 + + S1_已锁定 --> S0_待开始: 有人退出 + S1_已锁定 --> S2_进行中: 签到 + S1_已锁定 --> S4_已取消: 发起者取消 + + S2_进行中 --> S3_已结束: 时间结束 + + S3_已结束 --> [*] + S4_已取消 --> [*] + + note right of S0_待开始: status=0 + note right of S1_已锁定: status=1 + note right of S2_进行中: status=2 + note right of S3_已结束: status=3 + note right of S4_已取消: status=4 +``` + +### 9.2 参与者状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> P0_正常: 加入预约 + + P0_正常 --> P1_已退出: 主动退出 + P0_正常 --> P1_已退出: 被踢出(签到时未到场) + P0_正常 --> P1_已退出: 预约取消 + + P1_已退出 --> [*] + + note right of P0_正常: status=0 + note right of P1_已退出: status=1 +``` + +### 9.3 到场状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> A0_未签到: 加入预约 + + A0_未签到 --> A1_到场: 签到确认到场 + A0_未签到 --> A2_未到场: 签到确认未到场 + + A1_到场 --> [*]: 完成 + A2_未到场 --> [*]: 被踢出 + + note right of A0_未签到: is_arrive=0 + note right of A1_到场: is_arrive=1 + note right of A2_未到场: is_arrive=2 +``` + +### 9.4 退款状态机 + +```mermaid +stateDiagram-v2 + direction LR + + [*] --> R0_无押金: 预约无鸽子费 + [*] --> R1_待支付: 需要支付鸽子费 + + R1_待支付 --> R2_已支付: 支付成功 + R1_待支付 --> R0_无押金: 预约取消(未支付) + + R2_已支付 --> R3_待退款: 签到/取消触发 + + R3_待退款 --> R4_已退款: 定时任务退款成功 + R3_待退款 --> R5_退款异常: 定时任务退款失败 + + R4_已退款 --> [*] + R5_退款异常 --> R3_待退款: 人工处理后重试 + R5_退款异常 --> [*]: 人工处理 + R0_无押金 --> [*] + + note right of R0_无押金: is_refund=0 + note right of R1_待支付: is_refund=1 + note right of R2_已支付: is_refund=2 + note right of R3_待退款: is_refund=3 + note right of R4_已退款: is_refund=4 + note right of R5_退款异常: is_refund=5 +``` + +--- + +## 附录A:前端核心文件路径 + +| 功能 | 文件路径 | +|------|----------| +| 首页 | `uniapp/mahjong_group/pages/index/index.vue` | +| 房间选择 | `uniapp/mahjong_group/pages/appointment/book-room-page.vue` | +| 预约表单 | `uniapp/mahjong_group/pages/appointment/appointment-page.vue` | +| 个人中心 | `uniapp/mahjong_group/pages/me/me-page.vue` | +| 预约记录 | `uniapp/mahjong_group/pages/me/appointment-record-page.vue` | +| 我的收益 | `uniapp/mahjong_group/pages/me/my-earnings-page.vue` | +| 消息中心 | `uniapp/mahjong_group/pages/me/my-message-page.vue` | +| 登录 | `uniapp/mahjong_group/pages/me/login.vue` | +| 预约卡片组件 | `uniapp/mahjong_group/components/index/MahjongCard.vue` | +| 预约弹窗组件 | `uniapp/mahjong_group/components/com/index/ReservationPopup.vue` | +| 签到弹窗组件 | `uniapp/mahjong_group/components/com/page/qiandao-popup.vue` | +| API接口 | `uniapp/mahjong_group/common/server/interface/sq.js` | + +## 附录B:后端核心文件路径 + +| 功能 | 文件路径 | +|------|----------| +| 预约控制器 | `server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs` | +| 预约服务 | `server/CoreCms.Net.Services/SQ/SQReservationsServices.cs` | +| 房间服务 | `server/CoreCms.Net.Services/SQ/SQRoomsServices.cs` | +| 消息服务 | `server/CoreCms.Net.Services/SQ/SQMessageServices.cs` | +| 收益服务 | `server/CoreCms.Net.Services/SQ/SQEarningsServices.cs` | + +--- + +## 附录C:版本更新记录 + +### v1.0.0 核心功能 + +1. **会员档案功能** + - 微信授权登录,绑定手机号 + - 会员行为标签:星级评分、黑名单 + - 组局成功率、被投诉次数、消费频次、鸽子次数统计 + +2. **麻友匹配机制** + - 发布组局需求 + - 其他客户查看并加入 + - 组局成功后自动发送消息 + - 鸽子费(押金)机制 + +### v1.0.1 新增功能 + +1. **站内信功能** + - 消息入口,红点提示 + - 自动发送通知(组局成功/失败) + - 手动发送通知(后台配置) + +2. **预约页优化** + - 房间时段展示(4个时段) + - 房间可用状态可视化 + - 无需组局选项(独享模式) + - 鸽子费自定义金额(最高50元) + +3. **我的收益功能** + - 待提现/已提现金额展示 + - 收益记录查询 + - 提现申请(后台审核,线下打款) + - 佣金规则:房费10% + +--- + +*文档生成时间:2024年* +*版本:v1.0.1* diff --git a/预约时间自由选择_改动文档 copy.md b/预约时间自由选择_改动文档 copy.md new file mode 100644 index 0000000..1426469 --- /dev/null +++ b/预约时间自由选择_改动文档 copy.md @@ -0,0 +1,556 @@ +# 预约时间自由选择 - 改动文档 + +## 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 + + + +``` + +#### 改动后代码 +```vue + + + +``` + +### 5.2 后端 SQController.cs 改动 + +#### 改动说明 +后端接口已支持接收 `start_time` 和 `end_time` 参数,主要验证逻辑需确认: + +```csharp +// 添加预约时的冲突检测 +[HttpPost("AddSQReservation")] +public async Task 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); // ['凌晨'] +``` diff --git a/预约时间自由选择_改动文档_开发版.md b/预约时间自由选择_改动文档_开发版.md new file mode 100644 index 0000000..f8c9e60 --- /dev/null +++ b/预约时间自由选择_改动文档_开发版.md @@ -0,0 +1,665 @@ +# 预约时间自由选择 - 开发文档 + +> 文档版本: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. **并发控制**:创建预约时需考虑并发冲突,建议后端加锁 diff --git a/预约时间自由选择_改动文档_甲方版.md b/预约时间自由选择_改动文档_甲方版.md new file mode 100644 index 0000000..405849d --- /dev/null +++ b/预约时间自由选择_改动文档_甲方版.md @@ -0,0 +1,524 @@ +# 预约时间自由选择 - 需求改动文档 + +> 文档版本:v1.0 +> 更新日期:2024年 +> 文档类型:甲方确认版 + +--- + +## 一、需求概述 + +### 1.1 当前问题 +- 用户只能选择固定时段(凌晨/上午/下午/晚上),每个时段固定6小时 +- 无法灵活选择具体的开始和结束时间 +- 跨时段预约时,房间列表显示不够直观 + +### 1.2 改动目标 +1. **时段显示优化**:预约15:00-20:00时,房间列表应显示"下午"和"晚上"都被预定 +2. **自由时间选择**:允许用户自由选择具体的开始时间和结束时间 + +--- + +## 二、当前系统时段定义 + +| 时段名称 | 时间范围 | 图标颜色 | +|:--------:|:--------:|:--------:| +| 凌晨 | 00:00 - 06:00 | - | +| 上午 | 06:00 - 12:00 | - | +| 下午 | 12:00 - 18:00 | - | +| 晚上 | 18:00 - 24:00 | - | + +**时段状态说明**: +- 🟢 可预约 - 该时段空闲,可以预约 +- 🟠 已预约 - 该时段已被其他人预约 +- ⚫ 不可用 - 该时段不开放 +- 🔵 使用中 - 该时段正在使用 + +--- + +## 三、完整业务闭环 + +### 3.1 发起者完整闭环(创建预约→结束) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 发起者完整业务闭环 │ +└─────────────────────────────────────────────────────────────────────────┘ + + ┌──────────┐ + │ 发起者 │ + └────┬─────┘ + │ + ▼ +┌─────────────────┐ +│ 1. 选择日期 │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ 2. 选择房间 │ ←─── 查看房间时段状态 +└────────┬────────┘ 🟢可预约 🟠已预约 + │ + ▼ +┌─────────────────┐ +│ 3. 选择时段 │ ←─── 【改动点】改为自由选择时间 +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ 4. 填写预约信息 │ ←─── 标题、人数、要求等 +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ ┌─────────────────┐ +│ 5. 支付押金 │────▶│ 创建成功 │ +│ (如需要) │ │ 状态:待开始 │ +└─────────────────┘ └────────┬────────┘ + │ + ┌───────────────────────┤ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ 等待参与者加入 │ │ 分享给好友 │ +└────────┬────────┘ └─────────────────┘ + │ + │ 【人满/时间到】 + ▼ +┌─────────────────┐ +│ 状态:已锁定 │ ←─── 不可再加入/退出 +└────────┬────────┘ + │ + │ 【开始前10分钟~开始后】 + ▼ +┌─────────────────┐ +│ 6. 签到确认 ⭐ │ ←─── 确认参与者到场情况 +└────────┬────────┘ + │ + ├─────────────────────────────┐ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ 参与者已到场 │ │ 参与者未到场 │ +│ 信誉分 +0.2 │ │ 信誉分 -0.5 │ +│ 退还押金 │ │ 鸽子数 +1 │ +└────────┬────────┘ │ 押金不退 │ + │ └─────────────────┘ + │ + ▼ +┌─────────────────┐ +│ 状态:进行中 │ ←─── 预约正式开始 +└────────┬────────┘ + │ + │ 【时间结束】 + ▼ +┌─────────────────┐ +│ 7. 互相评价 │ ←─── 牌品、牌技评分 +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ 状态:已结束 │ ←─── 预约完成 +└─────────────────┘ +``` + +### 3.2 参与者完整闭环(加入预约→结束) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 参与者完整业务闭环 │ +└─────────────────────────────────────────────────────────────────────────┘ + + ┌──────────┐ + │ 参与者 │ + └────┬─────┘ + │ + ├───────────────────┬───────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 首页浏览 │ │ 好友分享 │ │ 扫码进入 │ +│ 麻将局列表 │ │ 点击链接 │ │ │ +└────────┬────────┘ └────────┬────────┘ └────────┬────────┘ + │ │ │ + └───────────────────┴───────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 1. 查看预约详情 │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ 2. 点击加入 │────▶│ 检查是否在 │ + └─────────────────┘ │ 黑名单中 │ + └────────┬────────┘ + │ + ┌───────────────────────┴───────────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ 在黑名单中 │ │ 不在黑名单 │ + │ 无法加入 ❌ │ └────────┬────────┘ + └─────────────────┘ │ + ▼ + ┌─────────────────┐ + │ 3. 支付押金 │ + │ (如需要) │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ 加入成功 ✓ │ + │ 等待预约开始 │ + └────────┬────────┘ + │ + ┌──────────────────────────────────────────────────┤ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ 主动退出预约 │ │ 等待锁定 │ + │ (锁定前可退) │ └────────┬────────┘ + └────────┬────────┘ │ + │ │ + ▼ │ 【人满/时间到】 + ┌─────────────────┐ ▼ + │ 退还押金 │ ┌─────────────────┐ + │ 退出成功 │ │ 状态:已锁定 │ + └─────────────────┘ └────────┬────────┘ + │ + │ 【发起者签到】 + ▼ + ┌─────────────────┐ + │ 4. 被签到确认 │ + └────────┬────────┘ + │ + ┌──────────────────────────────────────────────────┤ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ 已到场 ✓ │ │ 未到场 ✗ │ + │ 信誉分 +0.2 │ │ 信誉分 -0.5 │ + │ 押金退还 │ │ 鸽子数 +1 │ + └────────┬────────┘ │ 押金不退 │ + │ │ 强制退出 │ + │ └─────────────────┘ + ▼ + ┌─────────────────┐ + │ 状态:进行中 │ + └────────┬────────┘ + │ + │ 【时间结束】 + ▼ + ┌─────────────────┐ + │ 5. 互相评价 │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ 状态:已结束 │ + └─────────────────┘ +``` + +--- + +## 四、签到功能详解 + +### 4.1 签到流程图 + +```mermaid +flowchart TD + A[预约状态:已锁定] --> B{时间判断} + B -->|开始前10分钟内| C[显示签到按钮] + B -->|预约已开始且未结束| C + B -->|时间未到| D[签到按钮隐藏] + + C --> E[发起者点击签到] + E --> F[弹出签到确认窗口] + F --> G[显示参与者列表] + + G --> H{逐个确认到场} + H -->|已到场| I[标记为到场 ✓] + H -->|未到场| J[标记为缺席 ✗] + + I --> K[提交签到] + J --> K + + K --> L{系统处理} + L --> M[到场者:信誉+0.2,退押金] + L --> N[缺席者:信誉-0.5,扣押金,踢出] + + M --> O[预约状态→进行中] + N --> O +``` + +### 4.2 签到界面原型 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 到场确认 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 请确认以下参与者是否已到场: │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 张三 │ │ +│ │ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ ✓ 已到场 │ │ ✗ 未到场 │ │ │ +│ │ └──────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 李四 │ │ +│ │ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ ✓ 已到场 │ │ ✗ 未到场 │ │ │ +│ │ └──────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 👤 王五 │ │ +│ │ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ ✓ 已到场 │ │ ✗ 未到场 │ │ │ +│ │ └──────────────┘ └──────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ⚠️ 提示:标记为"未到场"的参与者将被扣除信誉分 │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 确认签到 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 4.3 签到规则说明 + +| 项目 | 说明 | +|:----:|:----:| +| **签到人** | 仅发起者可以签到 | +| **签到时间** | 预约开始前10分钟 ~ 预约结束时间 | +| **签到次数** | 每个预约只能签到一次 | +| **到场奖励** | 信誉分 +0.2(上限5分) | +| **缺席惩罚** | 信誉分 -0.5,鸽子数 +1 | +| **押金处理** | 到场者退还,缺席者不退 | + +--- + +## 五、时间选择改动对比 + +### 5.1 改动前(固定时段选择) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 选择预约时段 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ○ 凌晨 (00:00 - 06:00) │ +│ │ +│ ○ 上午 (06:00 - 12:00) │ +│ │ +│ ● 下午 (12:00 - 18:00) ← 只能选择一个完整时段 │ +│ │ +│ ○ 晚上 (18:00 - 24:00) │ +│ │ +│ ⚠️ 限制:只能选择固定6小时时段,无法自定义时间 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 5.2 改动后(自由时间选择) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 选择预约时间 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 开始时间 │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 15 : 00 ▼ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ 结束时间 │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 20 : 00 ▼ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ 📊 预计时长:5 小时 │ │ +│ │ 📅 跨越时段:下午、晚上 │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ +│ 💡 提示:结束时间必须晚于开始时间 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 5.3 房间列表显示对比 + +**场景**:某房间已被预约 15:00 - 20:00 + +``` +【改动前】房间时段显示: +┌─────────────────────────────────────────────────────────────────┐ +│ 304包厢 │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ 凌晨 │ │ 上午 │ │ 下午 │ │ 晚上 │ │ +│ │ 🟢 │ │ 🟢 │ │ 🟠 │ │ 🟢 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ❌ 问题:晚上时段显示为可预约,但实际18:00-20:00已被占用 │ +└─────────────────────────────────────────────────────────────────┘ + +【改动后】房间时段显示: +┌─────────────────────────────────────────────────────────────────┐ +│ 304包厢 │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ 凌晨 │ │ 上午 │ │ 下午 │ │ 晚上 │ │ +│ │ 🟢 │ │ 🟢 │ │ 🟠 │ │ 🟠 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ✓ 正确:下午和晚上都显示为已预约状态 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 六、预约状态流转图 + +```mermaid +stateDiagram-v2 + [*] --> 待开始: 创建预约 + + 待开始 --> 已锁定: 人满或时间临近 + 待开始 --> 已取消: 发起者取消 + + 已锁定 --> 进行中: 发起者签到 + 已锁定 --> 已取消: 发起者取消(需扣分) + + 进行中 --> 已结束: 时间结束 + + 已取消 --> [*] + 已结束 --> [*] + + note right of 待开始 + 可加入/退出 + 可分享 + end note + + note right of 已锁定 + 不可加入/退出 + 等待签到 + end note + + note right of 进行中 + 正在进行 + 可评价 + end note +``` + +### 状态说明表 + +| 状态 | 状态码 | 可执行操作 | 说明 | +|:----:|:------:|:----------:|:----:| +| 待开始 | 0 | 加入、退出、取消、分享 | 预约创建后的初始状态 | +| 已锁定 | 1 | 签到、取消 | 人满或临近开始时间 | +| 进行中 | 2 | 评价 | 签到后预约正式开始 | +| 已结束 | 3 | 查看记录 | 预约时间结束 | +| 已取消 | 4 | 无 | 预约被取消 | + +--- + +## 七、信誉分规则 + +### 7.1 信誉分变化场景 + +| 场景 | 分数变化 | 说明 | +|:----:|:--------:|:----:| +| 按时赴约 | +0.2 | 签到时标记为"已到场" | +| 放鸽子 | -0.5 | 签到时标记为"未到场" | +| 取消预约(锁定前) | 无变化 | 正常取消 | +| 取消预约(锁定后) | -0.3 | 临时取消扣分 | + +### 7.2 信誉分等级 + +| 等级 | 分数范围 | 说明 | +|:----:|:--------:|:----:| +| 优秀 | 4.5 - 5.0 | 信誉极好 | +| 良好 | 4.0 - 4.4 | 信誉较好 | +| 一般 | 3.0 - 3.9 | 信誉一般 | +| 较差 | 2.0 - 2.9 | 需要注意 | +| 很差 | < 2.0 | 可能被限制 | + +--- + +## 八、改动工作内容总结 + +### 8.1 前端改动 + +| 序号 | 页面/组件 | 改动内容 | +|:----:|:--------:|:--------:| +| 1 | 创建预约页面 | 将时段单选改为时间选择器 | +| 2 | 创建预约页面 | 增加时长计算和跨时段提示 | +| 3 | 房间列表页面 | 验证跨时段显示(可能无需改动) | + +### 8.2 后端改动 + +| 序号 | 模块 | 改动内容 | +|:----:|:----:|:--------:| +| 1 | 预约接口 | 支持接收自定义开始/结束时间 | +| 2 | 冲突检测 | 验证跨时段冲突检测逻辑 | + +### 8.3 测试验证 + +| 序号 | 测试场景 | 预期结果 | +|:----:|:--------:|:--------:| +| 1 | 选择15:00-20:00 | 成功,显示跨越"下午、晚上" | +| 2 | 选择20:00-15:00 | 失败,提示时间无效 | +| 3 | 房间已有15:00-20:00预约 | 下午🟠、晚上🟠 | +| 4 | 预约10:00-14:00 | 显示跨越"上午、下午" | + +--- + +## 九、待确认问题 + +请甲方确认以下问题: + +### 问题1:时间选择精度 +- [ ] 选项A:以30分钟为最小单位(如15:00、15:30、16:00) +- [ ] 选项B:以1小时为最小单位(如15:00、16:00、17:00) +- [ ] 选项C:以15分钟为最小单位 + +### 问题2:跨天预约 +- [ ] 是否支持跨天预约?(如23:00 - 次日02:00) +- [ ] 如支持,如何处理跨天的时段显示? + +### 问题3:最短/最长预约时长 +- [ ] 最短预约时长限制:____小时 +- [ ] 最长预约时长限制:____小时 + +### 问题4:时间冲突提示 +- [ ] 选项A:选择时间时实时检测冲突 +- [ ] 选项B:提交时统一检测冲突 + +--- + +## 十、附录 + +### A. 名词解释 + +| 名词 | 解释 | +|:----:|:----:| +| 发起者 | 创建预约的用户 | +| 参与者 | 加入预约的其他用户 | +| 签到 | 发起者确认参与者到场情况的操作 | +| 锁定 | 预约人满或临近开始,不可再加入/退出 | +| 鸽子数 | 用户放鸽子(未到场)的累计次数 | + +### B. 时段重叠判断示例 + +| 预约时间 | 凌晨(0-6) | 上午(6-12) | 下午(12-18) | 晚上(18-24) | +|:--------:|:---------:|:----------:|:-----------:|:-----------:| +| 02:00-05:00 | 🟠 | 🟢 | 🟢 | 🟢 | +| 05:00-08:00 | 🟠 | 🟠 | 🟢 | 🟢 | +| 10:00-14:00 | 🟢 | 🟠 | 🟠 | 🟢 | +| 15:00-20:00 | 🟢 | 🟢 | 🟠 | 🟠 | +| 22:00-02:00 | 🟠 | 🟢 | 🟢 | 🟠 |