Files
cursor/plans/注册活动推荐游戏配置_0766f9ed.plan.md
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

184 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: 注册活动推荐游戏配置
overview: 在 slot_admin 活动管理 type=7 编辑页增加「推荐游戏」多选,写入 ext_config.game_ids配套列表展示与保存校验与 slot_console 已实现的领取随机进游戏逻辑对齐。
todos:
- id: admin-model-api
content: GameApiModel 查询方法 + ActivityController::publishedGames
status: completed
- id: admin-validate
content: ActivityValidate::checkRegisterRewardExt + save/update 调用
status: completed
- id: vue-edit
content: activity/edit.vue type=7 推荐游戏多选与 submit/setFormData
status: completed
- id: vue-api-index
content: activity.js API + index.vue type=7 列表展示
status: completed
- id: manual-verify
content: 后台保存后 C 端领取验证 game_id 随机返回
status: completed
isProject: false
---
# 注册活动 type=7 推荐游戏后台配置
## 背景
C 端已在 [`RegisterRewardLogic`](slot_console/app/api/logic/RegisterRewardLogic.php) 读取 `ext_config.game_ids`,领取注册活动成功后随机返回 `game_id`。本任务补齐 **后台配置入口**,范围:
- [`backend/slot_admin_vue`](backend/slot_admin_vue) 活动编辑/列表
- [`backend/slot_admin`](backend/slot_admin) 保存校验与游戏选项 API
不涉及 slot_console 改动(`updateConfig` 已原样持久化 `ext_config` JSON
## 配置契约(与 C 端一致)
```json
{
"game_ids": [101, 205, 308]
}
```
- 值为 `game.id``s_game_api.game_id`),须属于活动 `model_id`**status=1** 的已发布游戏。
- 允许为空:领取仍成功,响应不含 `game_id`
## 数据流
```mermaid
sequenceDiagram
AdminVue->>ActivityController: GET publishedGames?model_id=
ActivityController->>GameApiModel: 已上架 game_id 列表
AdminVue->>ActivityController: POST save/update ext_config.game_ids
ActivityController->>ActivityValidate: checkRegisterRewardExt
ActivityController->>ConsoleInnerapi: ActivityService add/update
ConsoleInnerapi->>DB: s_recharge_gift_config.ext_config
```
## 1. slot_admin 后端
### 1.1 游戏选项 API
在 [`ActivityController`](backend/slot_admin/app/game/controller/ActivityController.php) 新增:
- **`publishedGames(Request $request)`**
- 入参:`model_id`(必填,对应活动「游戏模型」)
- 出参:`[{ game_id, name }]`,供多选下拉
查询放在 [`GameApiModel`](backend/slot_admin/app/model/GameApiModel.php)`s_common.s_game_api`
```php
public static function optionsByGameModelId(int $gameModelId): array
```
条件:`game_model_id``status = 1``field('game_id,name')`,按 `sort` 排序;可对 `game_id` `group` 去重(与 console 侧一致)。
### 1.2 保存校验
在 [`ActivityValidate`](backend/slot_admin/app/game/validate/ActivityValidate.php) 新增 **`checkRegisterRewardExt(array $extConfig, int $gameModelId)`**
| 规则 | 说明 |
| --- | --- |
| `game_ids` 可选 | 缺失或 `[]` 直接通过 |
| 元素为正整数 | 非法 ID 抛 `ValidateException` |
| 与渠道一致(推荐) | 调用 `GameApiModel::publishedGameIds($gameModelId, $ids)`(与 console 同名语义),提交 ID 必须全部在已发布集合内,否则提示未上架 ID |
`ActivityController::save` / `update``updateData` 全量更新时)中,当 `type === 7` 时调用:
```php
$this->validate->checkRegisterRewardExt(
(array) input('ext_config', []),
(int) input('model_id', 0)
);
```
对齐 type=11 的 [`checkFreeCreditsExt`](backend/slot_admin/app/game/validate/ActivityValidate.php) 调用方式。
可在 `GameApiModel` 复用与 console 相同的 `publishedGameIds` 静态方法admin 连接 `s_common`,表结构一致)。
## 2. slot_admin_vue 前端
### 2.1 API
在 [`src/api/game/activity.js`](backend/slot_admin_vue/src/api/game/activity.js) 增加:
```js
publishedGames(params) {
return request({ url: '/game/activity/publishedGames', method: 'get', params })
}
```
(路径随 Webman 路由约定:`game/activity/publishedGames`。)
### 2.2 编辑页 type=7 表单项
文件:[`src/views/game/activity/edit.vue`](backend/slot_admin_vue/src/views/game/activity/edit.vue)
**仅 type=7**`<template>` 中增加(参考 type=11 独立区块,勿影响 type=8 下载奖励):
```vue
<template v-if="formData.type === 7">
<a-form-item label="推荐游戏" field="ext_config.game_ids"
help="用户领取注册奖励后,从此列表随机推荐一款已上架游戏">
<a-select
v-model="formData.ext_config.game_ids"
:options="publishedGameOptions"
multiple allow-search allow-clear
placeholder="请先选择游戏模型,再选择推荐游戏"
:disabled="!formData.model_id"
/>
</a-form-item>
</template>
```
脚本逻辑(对齐现有 type=10/11 的 `setFormData` / `submit` 分支):
| 时机 | 处理 |
| --- | --- |
| `open` / `model_id` 变更 | `loadPublishedGames()` → 调 `publishedGames({ model_id })`,映射 `{ value: game_id, label: name + ' (' + game_id + ')' }` |
| `setFormData` type=7 | `formData.ext_config.game_ids = (data.ext_config?.game_ids ?? []).map(Number)` |
| `submit` type=7 | `data.ext_config = { ...data.ext_config, game_ids: 去重正整数数组 }`**不**覆盖其他 ext 字段 |
初始 `formData.ext_config` 在 type=7 时保证含 `game_ids: []`(避免 `v-model` 未定义)。
**不**复用首页导航的 cascader[`indexGameNav/edit.vue`](backend/slot_admin_vue/src/views/yyladmin/indexGameNav/edit.vue) 存的是 `nav-{id}-{brandId}`,与 `game_id` 语义不同)。
### 2.3 列表页展示
文件:[`src/views/game/activity/index.vue`](backend/slot_admin_vue/src/views/game/activity/index.vue)
`#ext_config` 插槽增加 type=7 分支:
-`game_ids`:展示 `推荐游戏: 101, 205, ...`(或显示数量 + tooltip
- 无配置:显示「未配置」灰色文案
## 3. 与现有编辑页结构的衔接
当前 type=7 属于 `onlyGift`,仍通过 **goods 卡片** 配置赠送金额(`goods_id: -1` 由 console 汇总),`ext_config` 此前为空。本次仅在 `ext_config` 增加 `game_ids`,与 goods 并存:
- 提交时 **不要** 像 type=11 那样 `data.goods = []`
- `submit` 中 type=7 仅 merge `game_ids``ext_config`
## 4. 验证方式
1. 后台新建/编辑 type=7 活动,选择游戏模型后多选 2+ 款已上架游戏,保存成功。
2. DB 检查:`s_recharge_gift_config.ext_config``"game_ids":[...]`
3. C 端 `POST /api/gift/receive` 领取该活动,响应含随机 `game_id`
4. 提交未上架 `game_id` 应被 admin 校验拦截。
5. `game_ids` 留空:保存成功,领取响应无 `game_id`
## 文件清单
| 仓库 | 文件 | 变更 |
| --- | --- | --- |
| slot_admin | `app/model/GameApiModel.php` | `optionsByGameModelId``publishedGameIds` |
| slot_admin | `app/game/controller/ActivityController.php` | `publishedGames`save/update 校验 type=7 |
| slot_admin | `app/game/validate/ActivityValidate.php` | `checkRegisterRewardExt` |
| slot_admin_vue | `src/api/game/activity.js` | `publishedGames` |
| slot_admin_vue | `src/views/game/activity/edit.vue` | type=7 多选 + 加载/读写 |
| slot_admin_vue | `src/views/game/activity/index.vue` | type=7 ext_config 展示 |
## 风险说明
- 切换「游戏模型」后已选 `game_ids` 可能与新模型不匹配:可在 `model_id` `@change` 时清空 `game_ids` 并提示重新选择(建议在实现时一并处理)。
- 游戏选项依赖 `s_game_api` 发布数据;未发布游戏不会出现在下拉,也无法通过校验提交。