# 活动模块升级设计(报名状态 / 付费 / 状态机 / 签到身份 / 审核发布 / 总结资料) **日期:** 2026-08-01 **状态:** 设计定稿 v1(需求逐项确认完成) **前置文档:** `2026-07-31-activity-registration-form-design.md`(报名表单,已实施) --- ## 1. 概述 活动模块 7 项需求改造,覆盖报名体验、收费模型、状态机、签到身份校验与普通用户发布审核流程。核心变化: 1. 活动卡片显示报名状态(此前硬编码「进行中」) 2. 报名弹窗删除重复的取消/确认按钮(uni-app 内置 `` 自带按钮与插槽按钮重复) 3. 报名时间窗口:新增「报名截止时间」字段,活动发布即开放报名,截止后停止 4. 付费报名:先支付后报名;双费用为 0 时直接报名成功 5. 签到身份校验:有手机号孩子只能本人签到;无手机号孩子家长可代签(校验家庭关系 + 报名记录) 6. 收费模型升级:孩子/家长双收费 × 会员/非会员双价格(4 个费用字段) 7. 卡片状态机:报名中 / 已报名 / 进行中 / 已签到 / 总结中 / 已结束 补充(用户确认):普通用户也可发布活动,但必须经管理员审核后才能正式发布;活动结束后进入「总结中」,发布人可上传活动资料、参与者可点评,手动结束或 7 天未结束自动结束。 ## 2. 需求与决策记录 | # | 需求 | 决策 | |---|------|------| | 1 | 「已经报过名的未显示」 | 根因:卡片硬编码「进行中」,后端 list 不返回当前用户报名状态 → 卡片显示报名状态(displayStatus) | | 2 | 报名弹窗重复确认/取消按钮 | 删除 uni-app 内置 `` 底部「确定/取消」按钮,改自定义 view 遮罩弹窗(三个弹窗统一改造:报名 / 签到惩罚 / 评价) | | 3 | 报名时间窗口 | 新增 `registration_deadline` 字段,仅按截止时间控制(发布即开放);签到仅活动进行中;按钮缩小 | | 4 | 付费报名 | 先支付后报名;双费用均为 0 → 直接报名成功 | | 5 | 签到身份 | 有手机号孩子 → 只能本人签到;无手机号孩子 → 家长可代签(校验家庭关系 + 该孩子 approved 报名记录) | | 6 | 收费模型 | 活动级 `child_fee` / `parent_fee` × 会员/非会员 → **4 个费用字段**(会员价可免费=0),替代 `price` / `memberPrice` | | 7 | 状态机 | 报名中 / 已报名 / 进行中 / 已签到 / 总结中 / 已结束(已结束不在列表,历史记录可查) | | 8 | 发布人范围 | 普通用户可创建活动(draft)→ 提交审核(pending)→ 管理员审核(approved=published / rejected=驳回+原因)→ 驳回可 re-draft 修改再提交 | | 9 | 总结中阶段 | 活动结束后自动进入「总结中」:发布人上传活动资料(图片/PDF/链接,多条,时间排序);参与者(已签到)可点评;发布人手动「结束」或 **7 天未结束自动结束** → 已结束 | | 10 | 未报名签到 | 保留现逻辑:扣能量(`energy_penalty_unregistered`)放行 | ## 3. 状态机 ### 3.1 发布状态(`activities.status`,后端落库) `draft` → `pending`(提交审核)→ `published`(审核通过)/ `rejected`(驳回)→ `withdrawn`(撤回)→ `ended`(结束) - `publish`(draft 直接发布)仅保留给 admin/activity_admin 直发;普通用户走 `submit-review` → `audit` - 现有审核接口已具备:`/api/admin/activity/submit-review`、`/audit`、`/re-draft`、`/withdraw`、`/review` ### 3.2 展示状态(`displayStatus`,卡片用,按用户视角计算) 优先级自上而下(单个活动只命中一个): | 优先级 | displayStatus | 条件 | displayLabel | |:---:|------|------|------| | 1 | `ended` | `status = ended` | 已结束(**列表过滤,不在活动列表**,历史可查) | | 2 | `summarizing` | `status = published` 且 `now > endTime` | 总结中(发布人可传资料,参与者可点评) | | 3 | `checked_in` | 当前用户该活动存在签到记录 | 已签到 | | 4 | `ongoing` | `startTime <= now <= endTime` | 进行中 | | 5 | `registered` | 当前用户存在 approved 报名记录 | 已报名 | | 6 | `pending_start` | `now > registration_deadline`(未报名) | 待开始 | | 7 | `open` | `now <= registration_deadline`(未报名) | 报名中 | - 计算输入:`activity.status / startTime / endTime / registration_deadline` + 当前用户 `userId` 的报名与签到状态 - 7 天自动结束:`status = published` 且 `now > endTime + 7 天` → 惰性更新 `status = ended`(list 查询时批量处理,无需定时任务) ### 3.3 报名状态(`registrationStatus`,详情页用) `not_registered` / `registered`(approved)/ `pending`(报名审核中)/ `rejected`(被拒)/ `cancelled` / `checked_in` ## 4. 数据库设计 ### 4.1 `activities` 表新增 6 列(迁移 142) | 列名 | 类型 | 说明 | |------|------|------| | `registration_deadline` | DATETIME NULL | 报名截止时间(NULL=不限制,默认=活动开始时间) | | `child_fee` | INT DEFAULT 0 | 孩子·非会员价(分) | | `child_member_fee` | INT DEFAULT 0 | 孩子·会员价(分),0=免费 | | `parent_fee` | INT DEFAULT 0 | 家长·非会员价(分) | | `parent_member_fee` | INT DEFAULT 0 | 家长·会员价(分),0=免费 | | `creator_id` | BIGINT NULL | 创建人 userId(普通用户发布者),admin 创建可为空 | - `price` / `member_price` 字段**废弃不再使用**(保留列不删,避免破坏历史数据) ### 4.2 新表 `activity_orders`(迁移 143)— 活动报名订单 | 列名 | 类型 | 说明 | |------|------|------| | `id` | BIGINT PK AUTO | 主键 | | `order_no` | VARCHAR(64) UNIQUE | 订单号(`ACT` + 时间戳 + 随机) | | `activity_id` | BIGINT | 活动 ID | | `user_id` | BIGINT | 下单人 userId | | `total_amount` | INT | 实付金额(分) | | `registrants_json` | TEXT | 报名人快照 JSON(`[{name, phone, childId, type: child/parent, fee}]`) | | `status` | VARCHAR(20) | `pending` / `paid` / `cancelled` | | `transaction_id` | VARCHAR(64) NULL | 微信支付单号 | | `paid_at` | DATETIME NULL | 支付时间 | | `created_at` | DATETIME | 创建时间 | - 支付成功回调 → `status = paid` + 写 `activity_registrations` 报名记录(同一事务/回调幂等:`order_no` 唯一,已处理直接返回成功) ### 4.3 新表 `activity_materials`(迁移 144)— 活动总结资料 | 列名 | 类型 | 说明 | |------|------|------| | `id` | BIGINT PK AUTO | 主键 | | `activity_id` | BIGINT | 活动 ID | | `title` | VARCHAR(200) NULL | 标题 | | `file_url` | VARCHAR(500) | 文件地址(图片/PDF/外链 URL) | | `file_type` | VARCHAR(20) | `image` / `pdf` / `link` | | `created_at` | DATETIME | 创建时间 | - 仅发布人(creator 或 admin/activity_admin)可上传/删除;活动进入「总结中」后开放 - 列表按 `created_at` 倒序(最新在前) ## 5. 后端 API 设计 ### 5.1 改动接口 | 接口 | 变更 | |------|------| | `POST /api/activity/create` | **放开普通用户**(parent 可创建,`status=draft`,记录 `creatorId`);admin/activity_admin 行为不变 | | `POST /api/activity/update` | 同上放开;仅创建人/admin 可改 | | `POST /api/activity/list` | 返回 `displayStatus` / `displayLabel` / `registrationStatus`(按当前 userId);过滤 `ended`;惰性处理 7 天自动结束 | | `POST /api/activity/detail` | 返回 `displayStatus` / `displayLabel` / `registrationStatus` / 4 个费用字段 / `registrationDeadline` / 资料列表 `materials` | | `POST /api/activity/register` | 校验 `registration_deadline`;手机号可选(无手机号孩子允许报名);付费活动必须先有已支付订单(校验 `activity_orders`) | | `POST /api/activity/checkin` | 时间窗校验(仅活动进行中);身份校验:有手机号孩子=本人签,无手机号孩子=家长代签(家庭关系 + 该孩子 approved 报名) | | `POST /api/activity/end` | 保持(手动结束),admin/activity_admin/**创建人**可结束 | | `POST /api/activity/submit-review` | 普通用户提交审核:`draft → pending`(仅创建人/admin 可提交自己的活动)——复用 `ActivityAdminService.submitForReview` 能力,需加 creatorId 校验 | ### 5.2 新接口 | 接口 | 说明 | |------|------| | `POST /api/activity/order/create` | 创建活动报名订单:入参 `{activityId, registrants:[{name, phone, childId}]}`;校验截止时间/名额/重复报名;按下单人会员等级(`MembershipService.getMemberLevel`,FAMILY/PROVIDER=会员)选择费用;总额=Σ(人数×单价);0 元直接走报名成功不建订单 | | `POST /api/activity/order/pay` | 调 `PaymentService.createWechatPrepay` 返回 `wechatPayParams` | | `POST /api/activity/order/handlePaymentSuccess` | 前端支付成功回调:幂等置 `paid` + 写报名记录(仿照 `ProductOrderController.handlePaymentSuccess`) | | `POST /api/activity/order/notify` | 微信支付异步通知:复用 `PaymentService.handleWechatNotify`,**加入 JwtInterceptor 公开白名单** | | `POST /api/activity/order/status` | 查询订单状态(支付页轮询/回调兜底) | | `POST /api/activity/material/upload` | 上传资料(仅发布人,总结中阶段) | | `POST /api/activity/material/list` | 资料列表(按 activityId,倒序) | | `POST /api/activity/material/delete` | 删除资料(仅发布人) | - 费用计算复用现有会员判定:`ActivityDTO.from(activity, isGuest, memberLevel)` 已有会员价格解析先例,新逻辑按下单人 `memberLevel` 取对应 4 个字段 - 回调幂等:`order_no` 唯一索引,`status=paid` 直接返回成功 ## 6. 小程序前端改动 ### 6.1 `activity-detail.vue`(核心改造) - **弹窗改造**:报名 / 签到惩罚 / 评价三个 `` → 自定义 view 遮罩弹窗(去掉内置「确定/取消」按钮,保留插槽内自定义操作按钮);报名弹窗底部「确认报名」按钮缩小 - **报名截止**:`报名截止: MM-DD HH:mm`(用 `registrationDeadline`,不再误用 `endTime`) - **付费流程**:点击报名 → 若费用合计 > 0 → `order/create` → 支付页/`requestPayment` → 成功回调 → 刷新为已报名;合计 = 0 → 直接 `register` 成功 - **费用展示**:孩子价 / 家长价(区分会员与非会员),当前用户为会员时展示会员价(含「会员免费」) - **手机号可选**:报名表单手机号非必填;无手机号孩子可报名 - **总结中**:`displayStatus=summarizing` 时展示资料区(发布人可上传/删除,参与者可查看)+ 评价入口(已签到者) - **签到按钮**:仅 `ongoing` 显示;签到后显示已签到 ### 6.2 `index.vue` - 卡片状态改用后端 `displayLabel`(删除硬编码「进行中」) - 管理 tab:「发布」按钮 → 普通用户调 `submit-review`(提交审核),admin/activity_admin 保留直发 - 列表自动过滤已结束活动 ### 6.3 `create.vue` - 新增字段:报名截止时间、孩子非会员价、孩子会员价、家长非会员价、家长会员价(单位:元,前端转分) - 发布人角色放开(普通用户可进入创建页) ## 7. Web 管理端改动 ### 7.1 `ActivityEdit.vue` - 新增:报名截止时间 + 4 个费用字段(孩子费/孩子会员费/家长费/家长会员费,单位元) - 价格展示不再使用 `price` / `memberPrice` ### 7.2 审核流(已存在,无需大改) - `ActivityReview.vue` 已支持审核(approved/rejected+原因);普通用户提交审核后进入待审核列表 ## 8. 验收标准 - [ ] 卡片显示真实状态(报名中/已报名/进行中/已签到/总结中),不再硬编码 - [ ] 报名弹窗无重复取消/确认按钮,报名按钮缩小 - [ ] 超过报名截止时间无法报名;签到仅活动进行中可操作 - [ ] 付费活动:未支付无法报名;支付成功后自动报名成功;免费活动直接报名 - [ ] 有手机号孩子不能家长代签;无手机号孩子家长可代签(需家庭关系+报名记录) - [ ] 会员看到会员价(可免费),非会员看到非会员价 - [ ] 活动结束进入「总结中」:发布人可传资料(图片/PDF/链接多条)、参与者可点评;手动结束或 7 天自动结束 - [ ] 普通用户可创建活动并提交审核,管理员审核通过后正式发布 - [ ] `mvn clean compile` 通过;迁移幂等可重复执行