ewq
This commit is contained in:
parent
4b069089ec
commit
ce50875e8d
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(tree:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(grep:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
744
PROJECT_DESCRIPTION.md
Normal file
744
PROJECT_DESCRIPTION.md
Normal file
|
|
@ -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. 押金退还、收益分配
|
||||
|
||||
项目采用前后端分离架构,数据库设计合理,业务逻辑清晰。通过信誉体系、押金机制、评价系统等功能,有效防止了恶意爽约问题,提高了用户参与组局的积极性和可靠性。
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 广告api控制器
|
||||
/// </summary>
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class AdvertController : ControllerBase
|
||||
{
|
||||
|
||||
private IHttpContextUser _user;
|
||||
private readonly ICoreCmsArticleServices _articleServices;
|
||||
private readonly ICoreCmsAdvertPositionServices _advertPositionServices;
|
||||
private readonly ICoreCmsAdvertisementServices _advertisementServices;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="articleServices"></param>
|
||||
/// <param name="advertPositionServices"></param>
|
||||
/// <param name="advertisementServices"></param>
|
||||
public AdvertController(IHttpContextUser user
|
||||
, ICoreCmsArticleServices articleServices
|
||||
, ICoreCmsAdvertPositionServices advertPositionServices
|
||||
, ICoreCmsAdvertisementServices advertisementServices
|
||||
)
|
||||
{
|
||||
_user = user;
|
||||
_articleServices = articleServices;
|
||||
_advertPositionServices = advertPositionServices;
|
||||
_advertisementServices = advertisementServices;
|
||||
}
|
||||
|
||||
#region 获取广告列表=============================================================================
|
||||
/// <summary>
|
||||
/// 获取广告列表
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<WebApiDto> 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 代理请求接口
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="agentServices"></param>
|
||||
/// <param name="settingServices"></param>
|
||||
/// <param name="agentOrderServices"></param>
|
||||
/// <param name="userServices"></param>
|
||||
/// <param name="goodsServices"></param>
|
||||
/// <param name="agentGoodsServices"></param>
|
||||
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 获取店铺信息
|
||||
/// <summary>
|
||||
/// 获取店铺信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 根据查询条件获取分页数据============================================================
|
||||
/// <summary>
|
||||
/// 根据查询条件获取分页数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetGoodsPageList([FromBody] FMPageByWhereOrder entity)
|
||||
{
|
||||
var jm = new WebApiCallBack();
|
||||
|
||||
var where = PredicateBuilder.True<CoreCmsGoods>();
|
||||
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 查询用户是否可以成为代理商
|
||||
/// <summary>
|
||||
/// 查询用户是否可以成为代理商
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> Info()
|
||||
{
|
||||
var jm = await _agentServices.GetInfo(_user.ID);
|
||||
return jm;
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 申请成为代理商接口
|
||||
/// <summary>
|
||||
/// 申请成为代理商接口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取我的下级用户数量
|
||||
/// <summary>
|
||||
/// 获取我的下级用户数量
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取我的订单统计
|
||||
/// <summary>
|
||||
/// 获取我的订单统计
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 我推广的订单
|
||||
/// <summary>
|
||||
/// 我推广的订单
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> MyOrder([FromBody] FMPageByIntId entity)
|
||||
{
|
||||
var jm = await _agentServices.GetMyOrderList(_user.ID, entity.page, entity.limit, entity.id);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 店铺设置
|
||||
/// <summary>
|
||||
/// 店铺设置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取代理商排行
|
||||
/// <summary>
|
||||
/// 获取代理商排行
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 文章api控制器
|
||||
/// </summary>
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class ArticleController : ControllerBase
|
||||
{
|
||||
|
||||
private IHttpContextUser _user;
|
||||
private readonly ICoreCmsArticleServices _articleServices;
|
||||
private readonly ICoreCmsArticleTypeServices _articleTypeServices;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="articleServices"></param>
|
||||
/// <param name="articleTypeServices"></param>
|
||||
public ArticleController(IHttpContextUser user, ICoreCmsArticleServices articleServices, ICoreCmsArticleTypeServices articleTypeServices)
|
||||
{
|
||||
_user = user;
|
||||
_articleServices = articleServices;
|
||||
_articleTypeServices = articleTypeServices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region 获取通知列表
|
||||
/// <summary>
|
||||
/// 获取通知列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取文章列表
|
||||
/// <summary>
|
||||
/// 获取文章列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个文章内容
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<WebApiCallBack> 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 购物车操作
|
||||
/// </summary>
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class CartController : ControllerBase
|
||||
{
|
||||
private readonly IHttpContextUser _user;
|
||||
private readonly ICoreCmsCartServices _cartServices;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public CartController(IHttpContextUser user, ICoreCmsCartServices cartServices)
|
||||
{
|
||||
_user = user;
|
||||
_cartServices = cartServices;
|
||||
}
|
||||
|
||||
//公共接口====================================================================================================
|
||||
|
||||
//验证接口====================================================================================================
|
||||
|
||||
#region 添加单个货品到购物车
|
||||
|
||||
/// <summary>
|
||||
/// 添加单个货品到购物车
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取购物车列表======================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 获取购物车列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 删除购物车信息
|
||||
|
||||
/// <summary>
|
||||
/// 获取购物车列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 设置购物车商品数量
|
||||
|
||||
/// <summary>
|
||||
/// 设置购物车商品数量
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> SetCartNum([FromBody] FMSetCartNum entity)
|
||||
{
|
||||
var jm = await _cartServices.SetCartNum(entity.id, entity.nums, _user.ID, 2, 1);
|
||||
return jm;
|
||||
}
|
||||
|
||||
#endregion 设置购物车商品数量
|
||||
|
||||
#region 根据提交的数据判断哪些购物券可以使用==================================================
|
||||
|
||||
/// <summary>
|
||||
/// 根据提交的数据判断哪些购物券可以使用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> GetCartAvailableCoupon([FromBody] FMCouponForUserCouponPost entity)
|
||||
{
|
||||
var ids = CommonHelper.StringToIntArray(entity.ids);
|
||||
var jm = await _cartServices.GetCartAvailableCoupon(_user.ID, ids);
|
||||
return jm;
|
||||
}
|
||||
|
||||
#endregion 根据提交的数据判断哪些购物券可以使用==================================================
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 优惠券接口
|
||||
/// </summary>
|
||||
[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;
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="couponServices"></param>
|
||||
/// <param name="promotionServices"></param>
|
||||
/// <param name="unionOfWork"></param>
|
||||
public CouponController(IHttpContextUser user
|
||||
, ICoreCmsCouponServices couponServices, ICoreCmsPromotionServices promotionServices, IUnitOfWork unionOfWork)
|
||||
{
|
||||
_user = user;
|
||||
_couponServices = couponServices;
|
||||
_promotionServices = promotionServices;
|
||||
_unionOfWork = unionOfWork;
|
||||
}
|
||||
|
||||
//公共接口====================================================================================================
|
||||
|
||||
#region 获取 可领取的优惠券==================================================
|
||||
/// <summary>
|
||||
/// 获取 可领取的优惠券
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
//[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取优惠券 详情==================================================
|
||||
/// <summary>
|
||||
/// 获取优惠券 详情
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取用户已领取的优惠券==================================================
|
||||
/// <summary>
|
||||
/// 获取用户已领取的优惠券
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> UserCoupon([FromBody] FMCouponForUserCouponPost entity)
|
||||
{
|
||||
var jm = await _couponServices.GetMyCoupon(_user.ID, 0, entity.display, entity.page, entity.limit);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 用户领取优惠券==================================================
|
||||
/// <summary>
|
||||
/// 用户领取优惠券
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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领取优惠券==================================================
|
||||
/// <summary>
|
||||
/// 用户输入code领取优惠券
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认接口示例
|
||||
/// </summary>
|
||||
public class DemoController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认首页
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IActionResult Index()
|
||||
{
|
||||
return Content("已结束");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 分销请求接口
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public DistributionController(IHttpContextUser user, ICoreCmsDistributionServices distributionServices,
|
||||
ICoreCmsSettingServices settingServices, ICoreCmsUserServices userServices,
|
||||
ICoreCmsDistributionOrderServices distributionOrderServices)
|
||||
{
|
||||
_user = user;
|
||||
_distributionServices = distributionServices;
|
||||
_settingServices = settingServices;
|
||||
_userServices = userServices;
|
||||
_distributionOrderServices = distributionOrderServices;
|
||||
}
|
||||
|
||||
//公共接口====================================================================================================
|
||||
|
||||
#region 获取店铺信息
|
||||
|
||||
/// <summary>
|
||||
/// 获取店铺信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 查询用户是否可以成为分销商
|
||||
|
||||
/// <summary>
|
||||
/// 查询用户是否可以成为分销商
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> Info()
|
||||
{
|
||||
var jm = await _distributionServices.GetInfo(_user.ID, true);
|
||||
return jm;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 申请成为分销商接口
|
||||
|
||||
/// <summary>
|
||||
/// 申请成为分销商接口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 我推广的订单
|
||||
|
||||
/// <summary>
|
||||
/// 我推广的订单
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> MyOrder([FromBody] FMPageByIntId entity)
|
||||
{
|
||||
var jm = await _distributionServices.GetMyOrderList(_user.ID, entity.page, entity.limit, entity.id);
|
||||
return jm;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 店铺设置
|
||||
|
||||
/// <summary>
|
||||
/// 店铺设置
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取我的订单统计
|
||||
|
||||
/// <summary>
|
||||
/// 获取我的订单统计
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取我的下级用户数量
|
||||
|
||||
/// <summary>
|
||||
/// 获取我的下级用户数量
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 获取分销商排行
|
||||
|
||||
/// <summary>
|
||||
/// 获取分销商排行
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表单接口
|
||||
/// </summary>
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class FormController : ControllerBase
|
||||
{
|
||||
private readonly ICoreCmsFormServices _formServices;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="formServices"></param>
|
||||
public FormController(ICoreCmsFormServices formServices)
|
||||
{
|
||||
_formServices = formServices;
|
||||
}
|
||||
|
||||
|
||||
#region 万能表单/获取活动商品详情=============================================================================
|
||||
/// <summary>
|
||||
/// 万能表单/获取活动商品详情
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetFormDetial([FromBody] FmGetForm entity)
|
||||
{
|
||||
var jm = await _formServices.GetFormInfo(entity.id, entity.token);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 万能表单/提交表单=============================================================================
|
||||
/// <summary>
|
||||
/// 万能表单/提交表单
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> AddSubmit([FromBody] FmAddSubmit entity)
|
||||
{
|
||||
var jm = await _formServices.AddSubmit(entity);
|
||||
|
||||
jm.otherData = entity;
|
||||
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品相关接口处理
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
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 获取所有商品分类栏目数据
|
||||
/// <summary>
|
||||
/// 获取所有商品分类栏目数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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<WxGoodCategoryDto>();
|
||||
|
||||
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<WxGoodCategoryChild>();
|
||||
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 根据查询条件获取分页数据============================================================
|
||||
/// <summary>
|
||||
/// 根据查询条件获取分页数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetGoodsPageList([FromBody] FMPageByWhereOrder entity)
|
||||
{
|
||||
var jm = new WebApiCallBack();
|
||||
|
||||
var where = PredicateBuilder.True<CoreCmsGoods>();
|
||||
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 获取商品详情======================================================================
|
||||
/// <summary>
|
||||
/// 获取商品详情
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取单个货品信息======================================================================
|
||||
/// <summary>
|
||||
/// 获取单个货品信息
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取商品评价列表分页数据======================================================================
|
||||
/// <summary>
|
||||
/// 获取商品评价列表分页数据
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取商品参数======================================================================
|
||||
/// <summary>
|
||||
/// 获取单个商品参数
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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<WxNameValueDto>();
|
||||
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 获取随机推荐商品==================================================
|
||||
/// <summary>
|
||||
/// 获取随机推荐商品
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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获取商品详情======================================================================
|
||||
/// <summary>
|
||||
/// 根据Token获取商品详情
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 团购调用接口数据
|
||||
/// </summary>
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class GroupController : ControllerBase
|
||||
{
|
||||
|
||||
private readonly IHttpContextUser _user;
|
||||
private readonly ICoreCmsPromotionServices _coreCmsPromotionServices;
|
||||
private ICoreCmsGoodsServices _goodsServices;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public GroupController(IHttpContextUser user, ICoreCmsPromotionServices coreCmsPromotionServices, ICoreCmsGoodsServices goodsServices)
|
||||
{
|
||||
_user = user;
|
||||
_coreCmsPromotionServices = coreCmsPromotionServices;
|
||||
_goodsServices = goodsServices;
|
||||
}
|
||||
|
||||
|
||||
//公共接口====================================================================================================
|
||||
|
||||
#region 获取秒杀团购列表===========================================================
|
||||
/// <summary>
|
||||
/// 获取秒杀团购列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetList([FromBody] FMGroupGetListPost entity)
|
||||
{
|
||||
var jm = await _coreCmsPromotionServices.GetGroupList(entity.type, _user.ID, entity.status, entity.page, entity.limit);
|
||||
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取秒杀团购详情===========================================================
|
||||
/// <summary>
|
||||
/// 获取秒杀团购详情
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetGoodsDetial([FromBody] FMGetGoodsDetial entity)
|
||||
{
|
||||
var jm = await _coreCmsPromotionServices.GetGroupDetail(entity.id, 0, "group", entity.groupId);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//验证接口====================================================================================================
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面接口
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public PageController(IMapper mapper
|
||||
, ICoreCmsSettingServices settingServices
|
||||
, ICoreCmsPagesServices pagesServices
|
||||
, ICoreCmsOrderServices orderServices
|
||||
, ICoreCmsUserServices userServices)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_settingServices = settingServices;
|
||||
_pagesServices = pagesServices;
|
||||
_orderServices = orderServices;
|
||||
_userServices = userServices;
|
||||
}
|
||||
|
||||
//公共接口====================================================================================================
|
||||
|
||||
#region 获取页面布局数据=============================================================
|
||||
|
||||
/// <summary>
|
||||
/// 获取页面布局数据
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Description("获取页面布局数据")]
|
||||
public async Task<WebApiCallBack> GetPageConfig([FromBody] FMWxPost entity)
|
||||
{
|
||||
var jm = await _pagesServices.GetPageConfig(entity.code);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取用户购买记录=============================================================
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户购买记录
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Description("获取用户购买记录")]
|
||||
public async Task<WebApiCallBack> 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<RandUser>();
|
||||
|
||||
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
|
||||
|
||||
|
||||
//验证接口====================================================================================================
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 拼团接口
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
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 拼团列表
|
||||
/// <summary>
|
||||
/// 拼团列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取拼团商品信息
|
||||
/// <summary>
|
||||
/// 获取拼团商品信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 获取货品信息
|
||||
/// <summary>
|
||||
/// 获取货品信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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<CoreCmsPinTuanRule, CoreCmsPinTuanGoods, CoreCmsPinTuanRule>(
|
||||
(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取拼团信息,用在订单详情页
|
||||
/// <summary>
|
||||
/// 根据订单id取拼团信息,用在订单详情页
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务卡控制器
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="servicesServices"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="userServicesOrderServices"></param>
|
||||
/// <param name="userServicesTicketServices"></param>
|
||||
/// <param name="userServices"></param>
|
||||
/// <param name="clerkServices"></param>
|
||||
/// <param name="ticketVerificationLogServices"></param>
|
||||
/// <param name="storeServices"></param>
|
||||
/// <param name="userGradeServices"></param>
|
||||
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 取得服务卡列表信息
|
||||
/// <summary>
|
||||
/// 取得服务卡列表信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
//[Authorize]
|
||||
public async Task<WebApiCallBack> GetPageList([FromBody] FMPageByIntId entity)
|
||||
{
|
||||
var jm = new WebApiCallBack();
|
||||
|
||||
var dt = DateTime.Now;
|
||||
var where = PredicateBuilder.True<CoreCmsServices>();
|
||||
|
||||
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 获取服务卡详情
|
||||
/// <summary>
|
||||
/// 获取服务卡详情
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
//[Authorize]
|
||||
public async Task<WebApiCallBack> 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 添加服务订单
|
||||
/// <summary>
|
||||
/// 取得服务卡列表信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 店铺核销的服务券列表
|
||||
/// <summary>
|
||||
/// 店铺核销的服务券列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> VerificationPageList([FromBody] FMPageByIntId entity)
|
||||
{
|
||||
var jm = await _ticketVerificationLogServices.GetVerificationLogs(_user.ID, entity.page, entity.limit);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 软删除服务券核销单数据
|
||||
/// <summary>
|
||||
/// 软删除服务券核销单数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> LogDelete([FromBody] FMIntId entity)
|
||||
{
|
||||
var jm = await _ticketVerificationLogServices.LogDelete(entity.id, _user.ID);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 获取单个提货单详情
|
||||
/// <summary>
|
||||
/// 获取单个提货单详情
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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<GlobalEnumVars.ServicesTicketStatus>(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 核销服务券
|
||||
/// <summary>
|
||||
/// 核销服务券
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店调用接口数据
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
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 获取默认的门店
|
||||
/// <summary>
|
||||
/// 获取默认的门店
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetDefaultStore()
|
||||
{
|
||||
var jm = new WebApiCallBack();
|
||||
|
||||
var ship = await _storeServices.QueryByClauseAsync(p => p.isDefault == true);
|
||||
jm.status = true;
|
||||
jm.data = ship;
|
||||
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取门店列表数据
|
||||
/// <summary>
|
||||
/// 获取门店列表数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetStoreList([FromBody] FMGetStoreQueryPageByCoordinate entity)
|
||||
{
|
||||
var jm = new WebApiCallBack();
|
||||
try
|
||||
{
|
||||
var where = PredicateBuilder.True<CoreCmsStore>();
|
||||
|
||||
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 获取推荐关键词
|
||||
/// <summary>
|
||||
/// 获取推荐关键词
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 判断是否开启门店自提
|
||||
/// <summary>
|
||||
/// 判断是否开启门店自提
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> 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 根据序列获取门店数据
|
||||
/// <summary>
|
||||
/// 根据序列获取门店数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<WebApiCallBack> GetStoreById([FromBody] FMIntId entity)
|
||||
{
|
||||
var jm = new WebApiCallBack
|
||||
{
|
||||
status = true,
|
||||
msg = "获取成功",
|
||||
data = await _storeServices.QueryByClauseAsync(p => p.id == entity.id)
|
||||
};
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//验证接口======================================================================================================
|
||||
|
||||
#region 判断访问用户是否是店员
|
||||
/// <summary>
|
||||
/// 判断访问用户是否是店员
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> IsClerk()
|
||||
{
|
||||
var jm = await _clerkServices.IsClerk(_user.ID);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 根据用户序列获取门店数据
|
||||
/// <summary>
|
||||
/// 根据用户序列获取门店数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> GetStoreByUserId()
|
||||
{
|
||||
var jm = new WebApiCallBack
|
||||
{
|
||||
status = true,
|
||||
msg = "获取成功",
|
||||
data = await _storeServices.GetStoreByUserId(_user.ID)
|
||||
};
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取个人订单列表
|
||||
|
||||
/// <summary>
|
||||
/// 获取个人订单列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 搜索订单
|
||||
|
||||
/// <summary>
|
||||
/// 搜索订单
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 店铺提货单列表
|
||||
/// <summary>
|
||||
/// 店铺提货单列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> StoreLadingList([FromBody] FMPageByIntId entity)
|
||||
{
|
||||
var jm = await _billLadingServices.GetStoreLadingList(_user.ID, entity.page, entity.limit);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 删除提货单数据
|
||||
/// <summary>
|
||||
/// 删除提货单数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> LadingDelete([FromBody] FMStringId entity)
|
||||
{
|
||||
var jm = await _billLadingServices.LadingDelete(entity.id, _user.ID);
|
||||
return jm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 获取单个提货单详情
|
||||
/// <summary>
|
||||
/// 获取单个提货单详情
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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 核销订单
|
||||
/// <summary>
|
||||
/// 核销订单
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<WebApiCallBack> 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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 316d1e2b5d7013d0b9f7507ac2f15ead5ff4d703
|
||||
Subproject commit 05ad1348cf7b9ec80590919c3f23c2cb2c077e70
|
||||
25
v1.0.0_需求文档.md
Normal file
25
v1.0.0_需求文档.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
一、会员档案功能
|
||||
1、会员第一次使用需要授权微信号登录,绑定联系电话 并填写个人信息
|
||||
如: 打什么类型的麻将(扑克) 性别 年龄 是否抽烟 常消费包厢类型(大中小包)
|
||||
2、会员行为标签
|
||||
设定会员星级及黑名单,主要用于规避恶意组局及恶意逃单人员
|
||||
星级包括牌品 牌技 形象 初始全为4分
|
||||
组局结束后可找到历史对局,并对历史牌友进行评价
|
||||
统计顾客组局成功率 被投诉次数 消费频次 消费能力 鸽子次数并且公开可查看
|
||||
3、要有相应的后台管理顾客档案
|
||||
二、麻友匹配机制
|
||||
1、需求发布
|
||||
用户可发布组局需求,需填写信息包含时间地点包厢号包厢类型是否禁烟游戏偏好等
|
||||
地点为单一店名 主要填写包厢号 包厢号需和包厢类型已经包厢费用绑定
|
||||
2、组局参与
|
||||
其他客户可查看组局人员信息,时间地点信息等详情 同意即可参与
|
||||
3、组局成功后自动发送微信消息给参与者,消息包含组局的时间地点信息,该消息可在后台设置相应模板
|
||||
4、组局成功后发起组局者(局头)会收到组局其他参与者的信息,其中包含其他人联系方式微信名,及一张现金券,通知组局者本次消费成功后他将会获得现金奖励或当局立减
|
||||
5、组局成功后店铺经营人员微信可收到推送消息,并查看组局情况以确定核销内容及包间情况
|
||||
6、鸽子费用(定金),组局成功可设置相应鸽子费用,分为三挡0/5/15,发起组局时发起人可选择是否设置鸽子费,如设置鸽子费则需线上支付鸽子费至平台账户,四人皆到店后鸽子费原路返还,若有人爽约,爽约人定金作为其他三人补偿不予退还,并记录其爽约
|
||||
7、组局奖励推送
|
||||
三、广告及推送内容
|
||||
1、使用软件时会跳出店铺会员注册的二维码弹窗广告(用于线下引流)
|
||||
2、免责声明及保证金说明
|
||||
3、本店铺相应logo及图片广告位
|
||||
|
||||
114
v1.0.1_需求文档.md
Normal file
114
v1.0.1_需求文档.md
Normal file
|
|
@ -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. 已打款:通过线下转帐后,将该记录手动改变为本状态。
|
||||
|
||||
1190
业务逻辑文档 copy.md
Normal file
1190
业务逻辑文档 copy.md
Normal file
File diff suppressed because it is too large
Load Diff
556
预约时间自由选择_改动文档 copy.md
Normal file
556
预约时间自由选择_改动文档 copy.md
Normal file
|
|
@ -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
|
||||
<template>
|
||||
<!-- 时段选择(单选按钮) -->
|
||||
<view class="time-slot-selector">
|
||||
<view v-for="slot in timeSlotOptions" :key="slot.value"
|
||||
:class="['slot-item', selectedTimeSlot === slot.value ? 'active' : '']"
|
||||
@click="selectTimeSlot(slot.value)">
|
||||
{{ slot.label }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const selectedTimeSlot = ref(null);
|
||||
const timeSlotOptions = ref([]);
|
||||
|
||||
const calculateTimeFromSlot = () => {
|
||||
switch (selectedTimeSlot.value) {
|
||||
case 0: startHour = 0; endHour = 6; break;
|
||||
case 1: startHour = 6; endHour = 12; break;
|
||||
case 2: startHour = 12; endHour = 18; break;
|
||||
case 3: startHour = 18; endHour = 24; break;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 改动后代码
|
||||
```vue
|
||||
<template>
|
||||
<!-- 时间选择器 -->
|
||||
<view class="time-picker-container">
|
||||
<view class="time-picker-row">
|
||||
<text class="label">开始时间</text>
|
||||
<picker mode="time" :value="startTime" @change="onStartTimeChange">
|
||||
<view class="picker-value">{{ startTime || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="time-picker-row">
|
||||
<text class="label">结束时间</text>
|
||||
<picker mode="time" :value="endTime" @change="onEndTimeChange">
|
||||
<view class="picker-value">{{ endTime || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="time-info">
|
||||
<text>预计时长: {{ calculateDuration() }}</text>
|
||||
<text>跨越时段: {{ calculateCrossSlots() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const startTime = ref('');
|
||||
const endTime = ref('');
|
||||
|
||||
// 开始时间变更
|
||||
const onStartTimeChange = (e) => {
|
||||
startTime.value = e.detail.value;
|
||||
validateTimeRange();
|
||||
};
|
||||
|
||||
// 结束时间变更
|
||||
const onEndTimeChange = (e) => {
|
||||
endTime.value = e.detail.value;
|
||||
validateTimeRange();
|
||||
};
|
||||
|
||||
// 计算时长
|
||||
const calculateDuration = () => {
|
||||
if (!startTime.value || !endTime.value) return '-';
|
||||
const start = parseTime(startTime.value);
|
||||
const end = parseTime(endTime.value);
|
||||
const hours = (end - start) / (1000 * 60 * 60);
|
||||
return `${hours}小时`;
|
||||
};
|
||||
|
||||
// 计算跨越的时段
|
||||
const calculateCrossSlots = () => {
|
||||
if (!startTime.value || !endTime.value) return '-';
|
||||
const slots = [];
|
||||
const startHour = parseInt(startTime.value.split(':')[0]);
|
||||
const endHour = parseInt(endTime.value.split(':')[0]);
|
||||
|
||||
if (startHour < 6 || endHour > 0 && endHour <= 6) slots.push('凌晨');
|
||||
if ((startHour < 12 && endHour > 6) || (startHour >= 6 && startHour < 12)) slots.push('上午');
|
||||
if ((startHour < 18 && endHour > 12) || (startHour >= 12 && startHour < 18)) slots.push('下午');
|
||||
if (endHour > 18 || startHour >= 18) slots.push('晚上');
|
||||
|
||||
return slots.join('、') || '-';
|
||||
};
|
||||
|
||||
// 验证时间范围
|
||||
const validateTimeRange = () => {
|
||||
if (startTime.value && endTime.value) {
|
||||
const start = parseTime(startTime.value);
|
||||
const end = parseTime(endTime.value);
|
||||
if (end <= start) {
|
||||
uni.showToast({ title: '结束时间必须晚于开始时间', icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 构建预约数据(提交时)
|
||||
const buildReservationData = () => {
|
||||
const date = new Date(selectedDate.value);
|
||||
const [startH, startM] = startTime.value.split(':').map(Number);
|
||||
const [endH, endM] = endTime.value.split(':').map(Number);
|
||||
|
||||
const startDateTime = new Date(date);
|
||||
startDateTime.setHours(startH, startM, 0, 0);
|
||||
|
||||
const endDateTime = new Date(date);
|
||||
endDateTime.setHours(endH, endM, 0, 0);
|
||||
|
||||
return {
|
||||
start_time: startDateTime.toISOString(),
|
||||
end_time: endDateTime.toISOString(),
|
||||
// ... 其他字段
|
||||
};
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### 5.2 后端 SQController.cs 改动
|
||||
|
||||
#### 改动说明
|
||||
后端接口已支持接收 `start_time` 和 `end_time` 参数,主要验证逻辑需确认:
|
||||
|
||||
```csharp
|
||||
// 添加预约时的冲突检测
|
||||
[HttpPost("AddSQReservation")]
|
||||
public async Task<IActionResult> AddSQReservation([FromBody] AddReservationRequest request)
|
||||
{
|
||||
// 验证时间有效性
|
||||
if (request.end_time <= request.start_time)
|
||||
{
|
||||
return BadRequest("结束时间必须晚于开始时间");
|
||||
}
|
||||
|
||||
// 检查时段冲突(已有逻辑)
|
||||
var conflicts = await _reservationService.CheckTimeConflict(
|
||||
request.room_id,
|
||||
request.start_time,
|
||||
request.end_time
|
||||
);
|
||||
|
||||
if (conflicts.Any())
|
||||
{
|
||||
return BadRequest("所选时段已被预约");
|
||||
}
|
||||
|
||||
// 创建预约...
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 后端时段显示逻辑验证
|
||||
|
||||
**文件**: `SQRoomsServices.cs`
|
||||
|
||||
当前逻辑已正确支持跨时段检测:
|
||||
```csharp
|
||||
// 判断预约是否与时段重叠
|
||||
bool isReserved = reservations?.Any(r =>
|
||||
r.start_time < slotEnd && r.end_time > slotStart) ?? false;
|
||||
```
|
||||
|
||||
**验证场景**:
|
||||
- 预约 15:00-20:00
|
||||
- 下午时段(12:00-18:00): `15:00 < 18:00 && 20:00 > 12:00` = true ✅
|
||||
- 晚上时段(18:00-24:00): `15:00 < 24:00 && 20:00 > 18:00` = true ✅
|
||||
|
||||
**结论**:后端逻辑无需改动,已支持跨时段显示。
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据流对比
|
||||
|
||||
### 6.1 改动前数据流
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant F as 前端
|
||||
participant B as 后端
|
||||
participant DB as 数据库
|
||||
|
||||
U->>F: 选择日期
|
||||
U->>F: 选择房间
|
||||
F->>B: 获取房间时段状态
|
||||
B->>DB: 查询已有预约
|
||||
DB-->>B: 返回预约列表
|
||||
B-->>F: 返回4个时段状态
|
||||
F-->>U: 显示可选时段
|
||||
U->>F: 选择"下午"时段
|
||||
F->>F: calculateTimeFromSlot()
|
||||
Note over F: 固定设置 12:00-18:00
|
||||
F->>B: 提交预约(12:00-18:00)
|
||||
B->>DB: 保存预约
|
||||
DB-->>B: 保存成功
|
||||
B-->>F: 返回成功
|
||||
F-->>U: 预约成功
|
||||
```
|
||||
|
||||
### 6.2 改动后数据流
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant F as 前端
|
||||
participant B as 后端
|
||||
participant DB as 数据库
|
||||
|
||||
U->>F: 选择日期
|
||||
U->>F: 选择房间
|
||||
F->>B: 获取房间时段状态
|
||||
B->>DB: 查询已有预约
|
||||
DB-->>B: 返回预约列表
|
||||
B-->>F: 返回4个时段状态
|
||||
F-->>U: 显示时间选择器
|
||||
U->>F: 选择开始时间 15:00
|
||||
U->>F: 选择结束时间 20:00
|
||||
F->>F: validateTimeRange()
|
||||
F->>F: calculateCrossSlots()
|
||||
Note over F: 显示"下午、晚上"
|
||||
F->>B: 提交预约(15:00-20:00)
|
||||
B->>B: 检查时段冲突
|
||||
B->>DB: 保存预约
|
||||
DB-->>B: 保存成功
|
||||
B-->>F: 返回成功
|
||||
F-->>U: 预约成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试用例
|
||||
|
||||
### 7.1 时间选择测试
|
||||
|
||||
| 测试场景 | 开始时间 | 结束时间 | 预期结果 |
|
||||
|----------|----------|----------|----------|
|
||||
| 正常选择 | 15:00 | 20:00 | 成功,显示跨越"下午、晚上" |
|
||||
| 同时段选择 | 14:00 | 17:00 | 成功,显示跨越"下午" |
|
||||
| 跨三时段 | 10:00 | 20:00 | 成功,显示跨越"上午、下午、晚上" |
|
||||
| 无效时间 | 20:00 | 15:00 | 失败,提示"结束时间必须晚于开始时间" |
|
||||
| 相同时间 | 15:00 | 15:00 | 失败,提示"结束时间必须晚于开始时间" |
|
||||
|
||||
### 7.2 时段冲突测试
|
||||
|
||||
| 已有预约 | 新预约 | 预期结果 |
|
||||
|----------|--------|----------|
|
||||
| 15:00-20:00 | 10:00-14:00 | 成功(无重叠) |
|
||||
| 15:00-20:00 | 14:00-16:00 | 失败(重叠) |
|
||||
| 15:00-20:00 | 19:00-22:00 | 失败(重叠) |
|
||||
| 15:00-20:00 | 20:00-22:00 | 成功(边界无重叠) |
|
||||
|
||||
### 7.3 房间列表显示测试
|
||||
|
||||
| 预约时间 | 凌晨 | 上午 | 下午 | 晚上 |
|
||||
|----------|------|------|------|------|
|
||||
| 02:00-05:00 | 🟠 | 🟢 | 🟢 | 🟢 |
|
||||
| 10:00-14:00 | 🟢 | 🟠 | 🟠 | 🟢 |
|
||||
| 15:00-20:00 | 🟢 | 🟢 | 🟠 | 🟠 |
|
||||
| 22:00-04:00 | 🟠 | 🟢 | 🟢 | 🟠 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 工作量评估
|
||||
|
||||
| 模块 | 工作内容 | 复杂度 |
|
||||
|------|----------|--------|
|
||||
| 前端-预约页面 | 时间选择器组件替换、时间计算逻辑 | ⭐⭐⭐ |
|
||||
| 前端-房间列表 | 验证显示逻辑(可能无需改动) | ⭐ |
|
||||
| 前端-预约详情 | 时间格式显示调整 | ⭐ |
|
||||
| 后端-控制器 | 参数验证、冲突检测 | ⭐⭐ |
|
||||
| 后端-服务层 | 逻辑验证(可能无需改动) | ⭐ |
|
||||
| 测试 | 各场景测试 | ⭐⭐ |
|
||||
|
||||
**总体评估**:中等复杂度改动,主要工作集中在前端预约页面的UI和逻辑重构。
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与注意事项
|
||||
|
||||
1. **向后兼容**:已有的固定时段预约数据仍可正常显示
|
||||
2. **边界条件**:跨天预约(如23:00-02:00)需特殊处理
|
||||
3. **时间精度**:建议时间选择以30分钟为最小单位
|
||||
4. **用户体验**:需添加明确的时长提示和时段跨越提示
|
||||
5. **数据验证**:前后端都需要进行时间有效性验证
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录:时段判断工具函数
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 判断时间范围跨越哪些时段
|
||||
* @param {number} startHour 开始小时 (0-23)
|
||||
* @param {number} endHour 结束小时 (0-24)
|
||||
* @returns {Array} 跨越的时段数组
|
||||
*/
|
||||
function getCrossedSlots(startHour, endHour) {
|
||||
const slots = [];
|
||||
const slotRanges = [
|
||||
{ name: '凌晨', start: 0, end: 6 },
|
||||
{ name: '上午', start: 6, end: 12 },
|
||||
{ name: '下午', start: 12, end: 18 },
|
||||
{ name: '晚上', start: 18, end: 24 }
|
||||
];
|
||||
|
||||
for (const slot of slotRanges) {
|
||||
// 重叠判断:预约开始 < 时段结束 AND 预约结束 > 时段开始
|
||||
if (startHour < slot.end && endHour > slot.start) {
|
||||
slots.push(slot.name);
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
getCrossedSlots(15, 20); // ['下午', '晚上']
|
||||
getCrossedSlots(10, 14); // ['上午', '下午']
|
||||
getCrossedSlots(2, 5); // ['凌晨']
|
||||
```
|
||||
665
预约时间自由选择_改动文档_开发版.md
Normal file
665
预约时间自由选择_改动文档_开发版.md
Normal file
|
|
@ -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
|
||||
<!-- 删除:固定时段选择UI -->
|
||||
<view class="time-slot-selector">
|
||||
<view v-for="slot in timeSlotOptions" :key="slot.value"
|
||||
:class="['slot-item', selectedTimeSlot === slot.value ? 'active' : '']"
|
||||
@click="selectTimeSlot(slot.value)">
|
||||
{{ slot.label }}
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
```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
|
||||
<template>
|
||||
<!-- 新增:时间选择器 -->
|
||||
<view class="time-picker-container">
|
||||
<!-- 开始时间 -->
|
||||
<view class="time-picker-row">
|
||||
<text class="label">开始时间</text>
|
||||
<picker mode="time" :value="startTime" @change="onStartTimeChange">
|
||||
<view class="picker-value">
|
||||
{{ startTime || '请选择开始时间' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 结束时间 -->
|
||||
<view class="time-picker-row">
|
||||
<text class="label">结束时间</text>
|
||||
<picker mode="time" :value="endTime" @change="onEndTimeChange">
|
||||
<view class="picker-value">
|
||||
{{ endTime || '请选择结束时间' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 时间信息展示 -->
|
||||
<view class="time-info" v-if="startTime && endTime">
|
||||
<view class="info-item">
|
||||
<text class="info-label">预计时长</text>
|
||||
<text class="info-value">{{ calculateDuration() }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">跨越时段</text>
|
||||
<text class="info-value">{{ calculateCrossSlots() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view class="time-error" v-if="timeError">
|
||||
<text>{{ timeError }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
**Script部分**:
|
||||
|
||||
```javascript
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// 响应式数据
|
||||
const startTime = ref('');
|
||||
const endTime = ref('');
|
||||
const timeError = ref('');
|
||||
|
||||
// 开始时间变更
|
||||
const onStartTimeChange = (e) => {
|
||||
startTime.value = e.detail.value;
|
||||
validateTimeRange();
|
||||
};
|
||||
|
||||
// 结束时间变更
|
||||
const onEndTimeChange = (e) => {
|
||||
endTime.value = e.detail.value;
|
||||
validateTimeRange();
|
||||
};
|
||||
|
||||
// 验证时间范围
|
||||
const validateTimeRange = () => {
|
||||
timeError.value = '';
|
||||
|
||||
if (!startTime.value || !endTime.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [startH, startM] = startTime.value.split(':').map(Number);
|
||||
const [endH, endM] = endTime.value.split(':').map(Number);
|
||||
|
||||
const startMinutes = startH * 60 + startM;
|
||||
const endMinutes = endH * 60 + endM;
|
||||
|
||||
if (endMinutes <= startMinutes) {
|
||||
timeError.value = '结束时间必须晚于开始时间';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 可选:最短时长检查(如最少1小时)
|
||||
if (endMinutes - startMinutes < 60) {
|
||||
timeError.value = '预约时长不能少于1小时';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 计算时长
|
||||
const calculateDuration = () => {
|
||||
if (!startTime.value || !endTime.value) return '-';
|
||||
|
||||
const [startH, startM] = startTime.value.split(':').map(Number);
|
||||
const [endH, endM] = endTime.value.split(':').map(Number);
|
||||
|
||||
const startMinutes = startH * 60 + startM;
|
||||
const endMinutes = endH * 60 + endM;
|
||||
const diffMinutes = endMinutes - startMinutes;
|
||||
|
||||
if (diffMinutes <= 0) return '-';
|
||||
|
||||
const hours = Math.floor(diffMinutes / 60);
|
||||
const minutes = diffMinutes % 60;
|
||||
|
||||
if (minutes === 0) {
|
||||
return `${hours}小时`;
|
||||
}
|
||||
return `${hours}小时${minutes}分钟`;
|
||||
};
|
||||
|
||||
// 计算跨越的时段
|
||||
const calculateCrossSlots = () => {
|
||||
if (!startTime.value || !endTime.value) return '-';
|
||||
|
||||
const [startH] = startTime.value.split(':').map(Number);
|
||||
const [endH] = endTime.value.split(':').map(Number);
|
||||
|
||||
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 (startH < slot.end && endH > slot.start) {
|
||||
slots.push(slot.name);
|
||||
}
|
||||
}
|
||||
|
||||
return slots.join('、') || '-';
|
||||
};
|
||||
|
||||
// 构建预约数据(提交时调用)
|
||||
const buildReservationData = () => {
|
||||
if (!validateTimeRange()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = new Date(selectedDate.value);
|
||||
const [startH, startM] = startTime.value.split(':').map(Number);
|
||||
const [endH, endM] = endTime.value.split(':').map(Number);
|
||||
|
||||
const startDateTime = new Date(date);
|
||||
startDateTime.setHours(startH, startM, 0, 0);
|
||||
|
||||
const endDateTime = new Date(date);
|
||||
endDateTime.setHours(endH, endM, 0, 0);
|
||||
|
||||
return {
|
||||
start_time: formatDateTime(startDateTime),
|
||||
end_time: formatDateTime(endDateTime),
|
||||
// ... 其他字段保持不变
|
||||
};
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = '00';
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
// 修改提交函数
|
||||
const submitReservation = async () => {
|
||||
const data = buildReservationData();
|
||||
if (!data) {
|
||||
uni.showToast({ title: timeError.value || '请选择正确的时间', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用API提交
|
||||
// ...
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
**Style部分**:
|
||||
|
||||
```scss
|
||||
<style lang="scss">
|
||||
.time-picker-container {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.time-picker-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
padding: 10rpx 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time-info {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.time-error {
|
||||
margin-top: 16rpx;
|
||||
padding: 16rpx;
|
||||
background-color: #fff2f0;
|
||||
border-radius: 8rpx;
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 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<IActionResult> 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. **并发控制**:创建预约时需考虑并发冲突,建议后端加锁
|
||||
524
预约时间自由选择_改动文档_甲方版.md
Normal file
524
预约时间自由选择_改动文档_甲方版.md
Normal file
|
|
@ -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 | 🟠 | 🟢 | 🟢 | 🟠 |
|
||||
Loading…
Reference in New Issue
Block a user