This commit is contained in:
ray zhou
2026-05-29 11:21:40 +08:00
parent 5d6d482efe
commit f71a5c59af
447 changed files with 32245 additions and 116 deletions

View File

@@ -0,0 +1,183 @@
---
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` 发布数据;未发布游戏不会出现在下拉,也无法通过校验提交。