12 KiB
12 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| free credits activity edit | 在管理后台活动配置编辑表单中为活动类型 11(首充前免费余额定格与分档释放 / Free Credits)增加专属表单分支,并补齐字典与服务端校验;金额输入沿用 type==10 的「美元小数 → 千分位整数」模式,写入 ext_config 的 _qf 后缀键,与 slot_console `FreeCreditsLogic::configAmount()` 的读优先级一致。 |
|
false |
Free Credits 活动后台编辑落地计划
目标
让运营在「活动管理」编辑弹窗里选活动类型 11(首充前免费余额)时,能直接编辑需求文档第 17 节列出的所有配置项;保存后落到 s_recharge_gift_config.ext_config JSON 字段,C 端 FreeCreditsLogic 立即生效。
现状要点
slot_console已实现RechargeGiftConfigModel::TYPE_FREE_CREDITS = 11、FreeCreditsLogic、运行时配置读取(ext_config.{key}_qf优先)。slot_admin后端 ActivityController 走slotLib\services\ActivityService透传到slot_console innerapi/activity/*,ext_config已原样落库,不需要改动 slot_console。- 管理前端 edit.vue 现仅对 6 / 9 / 10 做了
v-if分支,type==11无任何 UI;字典activity_type也无 value=11 条目。
字段映射(type=11 专属 ext_config)
UI 输入用美元小数,提交时 ×1000 写入 _qf 键(与 type==10 模式一致;FreeCreditsLogic::configAmount() 优先读 _qf):
win_threshold_qf← 首笔赢取门槛(默认 $50)recharge_unlock_amount_qf← 累计充值解锁第一档(默认 $50)first_cash_amount_qf← 第一档免打码金额(默认 $20)package_amount_qf← 后续每档拆分金额(默认 $10)subsequent_min_recharge_qf← 解锁下一档单笔充值下限(默认 $10)max_unlock_per_recharge← 整数,每笔充值最多解锁档数(默认 1,不走 _qf)banner_image← Free Play to Go 弹窗 Banner 图片 URL(可空)
改动清单
1. 字典:追加 activity_type value=11
在系统管理「字典管理 → activity_type」以有,不需要再执行了
2. 前端:backend/slot_admin_vue/src/views/game/activity/edit.vue
2.1 增加 type==11 表单块
参考 edit.vue line 108-123 type==10 的写法,新增:
<template v-if="formData.type == 11">
<a-col :span="24">
<a-form-item label="首笔赢取门槛($)" help="免费余额曾达到该值后解锁首页 Withdraw" :rules="[{ required: true, message: '必填' }]">
<a-input-number v-model="formData.ext_config.win_threshold" placeholder="如 50" :min="0" />
</a-form-item>
<a-form-item label="充值解锁门槛($)" help="累计真实充值满该金额释放第一档" :rules="[{ required: true, message: '必填' }]">
<a-input-number v-model="formData.ext_config.recharge_unlock_amount" placeholder="如 50" :min="0" />
</a-form-item>
<a-form-item label="免打码提现额($)" help="第一档可免打码直接提现金额" :rules="[{ required: true, message: '必填' }]">
<a-input-number v-model="formData.ext_config.first_cash_amount" placeholder="如 20" :min="0" />
</a-form-item>
<a-form-item label="解锁拆分金额($)" help="后续每档释放金额" :rules="[{ required: true, message: '必填' }]">
<a-input-number v-model="formData.ext_config.package_amount" placeholder="如 10" :min="0" />
</a-form-item>
<a-form-item label="后续解锁最小充值($)" help="单笔充值达到该金额才解锁下一档">
<a-input-number v-model="formData.ext_config.subsequent_min_recharge" placeholder="如 10" :min="0" />
</a-form-item>
<a-form-item label="每笔最多解锁档数" help="防止一笔充值解锁多档">
<a-input-number v-model="formData.ext_config.max_unlock_per_recharge" placeholder="如 1" :min="1" :precision="0" />
</a-form-item>
<a-form-item label="Banner 图片" help="Free Play to Go 底部弹窗 Banner">
<sa-upload-image v-model="formData.ext_config.banner_image" :limit="1" :multiple="false" />
</a-form-item>
</a-col>
</template>
2.2 让 type==11 跳过 goods 区块
- edit.vue line 60
onlyGiftcomputed 追加|| formData.type == 11:
let onlyGift = computed(() => {
return formData.type == 7 || formData.type == 8 || formData.type == 9 || formData.type == 11;
})
- edit.vue line 125 把「添加赠送」按钮与下方
<a-row v-else>的 goods 卡片包成一组条件,排除 type==10 和 type==11:
<template v-if="formData.type !== 10 && formData.type !== 11">
<a-button type="primary" ... @click="add()">添加赠送</a-button>
</template>
<a-row :gutter="20" v-if="formData.type !== 9 && formData.type !== 10 && formData.type !== 11">
...goods 卡片...
</a-row>
(保留原 type==9 的 exchange 表格 v-if 不变。)
2.3 submit() 中追加 type==11 的金额 ×1000 转换
edit.vue line 370 类似 type==10 的处理,把 6 个美元字段写到 _qf 键,整数字段原样保留:
if (formData.type === 11) {
const e = data.ext_config || {};
const toQf = (v) => v === '' || v == null ? undefined : Math.round(Number(v) * 1000);
data.ext_config = {
win_threshold_qf: toQf(e.win_threshold),
recharge_unlock_amount_qf: toQf(e.recharge_unlock_amount),
first_cash_amount_qf: toQf(e.first_cash_amount),
package_amount_qf: toQf(e.package_amount),
subsequent_min_recharge_qf: toQf(e.subsequent_min_recharge),
max_unlock_per_recharge: e.max_unlock_per_recharge == null ? undefined : Math.round(Number(e.max_unlock_per_recharge)),
banner_image: e.banner_image || '',
};
data.goods = [];
}
2.4 setFormData() 中追加 type==11 的回填还原
edit.vue line 344-351 仿照 type==10:
if (data.type === 11 && data.ext_config) {
const e = data.ext_config;
formData.ext_config = {
win_threshold: e.win_threshold_qf != null ? e.win_threshold_qf / 1000 : e.win_threshold,
recharge_unlock_amount: e.recharge_unlock_amount_qf != null ? e.recharge_unlock_amount_qf / 1000 : e.recharge_unlock_amount,
first_cash_amount: e.first_cash_amount_qf != null ? e.first_cash_amount_qf / 1000 : e.first_cash_amount,
package_amount: e.package_amount_qf != null ? e.package_amount_qf / 1000 : e.package_amount,
subsequent_min_recharge: e.subsequent_min_recharge_qf != null ? e.subsequent_min_recharge_qf / 1000 : e.subsequent_min_recharge,
max_unlock_per_recharge: e.max_unlock_per_recharge ?? 1,
banner_image: e.banner_image || '',
};
}
3. 后端校验:backend/slot_admin/app/game/validate/ActivityValidate.php
ThinkValidate 对嵌套 JSON 支持有限,采用「在 Controller 按 type 触发额外校验」+「Validate 提供专用方法」的模式,避免污染既有 scene。
3.1 ActivityValidate 增加一个公开方法
/**
* Free Credits(type=11)专用 ext_config 校验。
*
* @param array $extConfig 前端提交的 ext_config,金额已 ×1000 写入 _qf 键
* @throws \think\exception\ValidateException
*/
public function checkFreeCreditsExt(array $extConfig): void
{
$required = [
'win_threshold_qf' => '首笔赢取门槛',
'recharge_unlock_amount_qf' => '充值解锁门槛',
'first_cash_amount_qf' => '免打码提现额',
'package_amount_qf' => '解锁拆分金额',
'subsequent_min_recharge_qf' => '后续解锁最小充值',
'max_unlock_per_recharge' => '每笔最多解锁档数',
];
foreach ($required as $key => $label) {
if (!isset($extConfig[$key]) || $extConfig[$key] === '' || (int)$extConfig[$key] < 0) {
throw new \think\exception\ValidateException("{$label}必须填写且为非负数");
}
}
if ((int)$extConfig['max_unlock_per_recharge'] < 1) {
throw new \think\exception\ValidateException('每笔最多解锁档数必须 ≥ 1');
}
// 业务约束:免打码提现额 ≤ 累计充值解锁门槛
if ((int)$extConfig['first_cash_amount_qf'] > (int)$extConfig['recharge_unlock_amount_qf']) {
throw new \think\exception\ValidateException('免打码提现额不能超过充值解锁门槛');
}
}
3.2 ActivityController save / update 触发
ActivityController::save / update 在 checkData() 之后追加:
if ((int)input('type') === 11) {
$this->validate->checkFreeCreditsExt((array)input('ext_config', []));
}
update 里同样判断,且要兼容仅改状态的请求(无 updateData 时不校验 ext_config):
if (!empty(input('updateData')) && (int)input('type') === 11) {
$this->validate->checkFreeCreditsExt((array)input('ext_config', []));
}
4. 不需要改的部分
slot_console侧ActivityConfigEntity::updateConfig已原样写入ext_config,无变更。Consts::ACTIVITY_TYPE_*不必新增 11;C 端已通过RechargeGiftConfigModel::TYPE_FREE_CREDITS引用。- 不新增独立菜单页,复用通用「活动管理」编辑弹窗。
数据流
flowchart LR
edit[edit.vue type=11 form] -->|"美元×1000 → _qf"| save["POST /game/activity/save|update"]
save --> ctrl[ActivityController]
ctrl -->|"checkFreeCreditsExt"| validate[ActivityValidate]
ctrl --> svc[ActivityService HTTP]
svc --> console["slot_console innerapi/activity/update"]
console --> entity[ActivityConfigEntity::updateConfig]
entity --> db["s_recharge_gift_config.ext_config"]
db --> fc["FreeCreditsLogic::configAmount key_qf 优先"]
验收
- 新建 type=11 活动:填写 6 字段 + Banner,保存成功;DB
ext_config是*_qf整数 +max_unlock_per_recharge+banner_image。 - 编辑回填:再次打开同一条活动,美元字段显示为原值(如 50 / 20 / 10),整数字段为 1,Banner 显示已上传图。
- 必填校验:清空任一必填项保存,返回明确错误信息(如「首笔赢取门槛必须填写且为非负数」)。
- 业务约束:免打码提现额填 60、充值解锁门槛填 50,保存被拒。
- 类型切换:type 选 11→1,再切回 11,goods 区块不出现,formData 不污染;保存 type==1 时
_qf字段不会被带到 ext_config。 - C 端:调
FreeCreditsLogic::status($uid),门槛 / 第一档金额 / 解锁拆分金额能读到刚配的值。 - type 字典:管理后台编辑弹窗活动类型下拉出现「Free Credits(首充前免费余额)」选项。