Files
cursor/plans/free_credits_后台统计页_9d6edc45.plan.md
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

14 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
free credits 后台统计页 实现需求 §18 的 Free Credits 后台统计与展示:在 slot_console 增强 innerapi加筛选、加列表字段、富化累计充值与档位聚合slot_lib 新增 FreeCreditsService 并给 WalletService 补一个 statistics 批量方法slot_admin 新增 FreeCreditsStatsController 代理slot_admin_vue 新增 views/game/freeCreditsStats/index.vue 一页搞定,顶部统计走 sa-table 的 otherData 模式。
id content status
slot-console-stats-logic slot_console 新建 innerapi/logic/FreeCreditsStatsLogic.php封装 statistics + list 的查询编排(含 status>=2 口径、批量 package 聚合 SQL completed
id content status
slot-console-list-enrich slot_console FreeCreditsController::list 加筛选 + 字段富化first_cash_status / progress / claimed / first_cashout / remaining / total_recharge completed
id content status
slot-console-stats-filter slot_console FreeCreditsController::statistics 加 source / first_recharge_time / status 等筛选frozen_user_count 改为 status>=2 completed
id content status
slot-lib-wallet-stats slot_lib WalletService 新增 statistics(uids, currency) 方法 → 调 /api/wallet/statistics completed
id content status
slot-lib-free-credits-service slot_lib 新建 FreeCreditsServicebasePath=innerapi/free-credits含 list + statistics completed
id content status
slot-admin-controller slot_admin 新建 game/controller/FreeCreditsStatsController.phpindex 一次调两个 service 合并 otherData 返回 completed
id content status
slot-admin-vue-api slot_admin_vue 新建 api/game/freeCreditsStats.js导出 getPageList completed
id content status
slot-admin-vue-page slot_admin_vue 新建 views/game/freeCreditsStats/index.vuesa-table + #tableAfterButtons 顶部 7 统计 + 12 列表 + 筛选 + 排序白名单 completed
id content status
menu-permission 在 sm_system_menu 加 1 条 L 菜单 + 1 条 B 按钮码(/game/freeCreditsStats/index并分配角色 completed
id content status
manual-verify docker 内 A/B/C 三种玩家手测默认排序、7 项统计、筛选联动、排序白名单、权限拦截 completed
false

数据流

flowchart LR
  vue[freeCreditsStats/index.vue sa-table]
  vue -->|"POST /game/FreeCreditsStats/index"| ctrl[FreeCreditsStatsController]
  ctrl -->|"FreeCreditsService::list"| svc[slot_lib FreeCreditsService]
  ctrl -->|"FreeCreditsService::statistics"| svc
  svc -->|"POST innerapi/free-credits/list"| console[slot_console FreeCreditsController]
  svc -->|"POST innerapi/free-credits/statistics"| console
  console -->|"WalletService statistics uids currency"| wallet["slot_wallet api/wallet/statistics"]
  console -->|"sum on free_credits_package"| db[(s_common.free_credits_player + free_credits_package)]

slot_admin Controller 一次代理两个 innerapi合并成 { data, total, otherData: <statistics> } 给 sa-table避免前端两次请求。

字段契约(最终对外)

/game/FreeCreditsStats/index 请求

筛选(与 §18.3 对齐)

  • uid 精确
  • source 字符串(沿用 channel_game_model + source 二级选择)
  • activity_id 可选(默认取当前生效的 type=11 活动)
  • first_recharge_time 范围 [start, end]whereBetween
  • status 数组player.status0/1/2/3/4/5/6/7/10
  • first_cash_status 数组package.status0/1/2/3/4/5限定 package_no=1
  • is_completed 0/1status=10 与否)
  • page, limit, orderBy, orderType

排序

  • DB 可排序:frozen_amount_qffirst_recharge_time(默认 first_recharge_time desc
  • 其它字段(进度、剩余、累计充值)派生,前端不开启排序

响应

{
  "data": [{
    "uid": 123,
    "source": "us_01",
    "frozen_amount_qf": 78500,
    "first_cash_amount_qf": 20000,
    "total_recharge_amount_qf": 30000,
    "first_cash_status": 3,
    "progress_done": 3,
    "progress_total": 7,
    "status": 7,
    "remaining_amount_qf": 28500,
    "claimed_amount_qf": 30000,
    "first_recharge_time": "2026-05-12 11:23:01",
    "completed_time": null
  }],
  "total": 240,
  "otherData": {
    "frozen_user_count": 240,
    "first_cashout_user_count": 180,
    "completed_user_count": 35,
    "frozen_amount_qf": 18650000,
    "first_cashout_amount_qf": 3520000,
    "claimed_amount_qf": 8800000,
    "pending_amount_qf": 6330000
  }
}

金额字段一律以 *_qf(千分位整数)传到 VueVue 用 / 1000 + toFixed(2) 显示(与 edit.vue 同口径)。


改动清单

1. slot_console — 增强 innerapi

文件:slot_console/app/innerapi/controller/FreeCreditsController.php

1.1 statistics() 加筛选

参照需求 §18.1,新增可选参数并贯穿到所有子查询:

  • activity_idsourcefirst_recharge_time_startfirst_recharge_time_enduid
  • 子查询 frozenTotal / completedCount / firstCashoutCount / firstCashoutAmount / claimedAmount 全部基于同一个 playerIds 数组(按筛选后取出)
  • frozen_user_count 口径改为 status >= STATUS_FROZEN(2) 的玩家数(与需求 "已创建 Free Credits Pool" 一致;当前实现把 status=1 也计入,定义不准)

1.2 list() 加筛选 + 字段富化

  • 新增筛选参数:sourcefirst_recharge_time 范围、status[]first_cash_status[]is_completedorderBy/orderType(白名单:first_recharge_time, frozen_amount_qf
  • 输出每行追加:
    • first_cash_status:对应 free_credits_package.status where player_id=? AND package_no=1
    • progress_done / progress_totalSUM(CASE WHEN status=3 THEN 1 ELSE 0 END) / COUNT(*),一次 GROUP BY player_id 跑完
    • claimed_amount_qfSUM(amount_qf) WHERE package_type=2 AND status=3 GROUP BY player_id
    • first_cashout_amount_qfSUM(amount_qf) WHERE package_type=1 AND status=3 GROUP BY player_id
    • remaining_amount_qffrozen_amount_qf - first_cashout_amount_qf - claimed_amount_qf
    • total_recharge_amount_qf:调用 WalletService::statistics(uids, currency='USD'),把 total_deposit 映射进来。currency 默认 'USD'(活动只面向美国 RMGcurrency 字段后续如多币种再扩展)
  • 一次性聚合查询,避免 N+1
SELECT player_id,
  SUM(CASE WHEN status=3 THEN 1 ELSE 0 END) AS progress_done,
  COUNT(*) AS progress_total,
  SUM(CASE WHEN package_type=1 AND status=3 THEN amount_qf ELSE 0 END) AS first_cashout_amount_qf,
  SUM(CASE WHEN package_type=2 AND status=3 THEN amount_qf ELSE 0 END) AS claimed_amount_qf,
  MAX(CASE WHEN package_no=1 THEN status END) AS first_cash_status
FROM s_common.free_credits_package
WHERE player_id IN (...)
GROUP BY player_id

1.3 控制器分层

按 backend-layering 规则,把 1.1 / 1.2 的查询编排沉到 Logic新增 slot_console/app/innerapi/logic/FreeCreditsStatsLogic.php(或直接挂在已有 api/logic/FreeCreditsLogic.php建议新建避免膨胀Controller 只负责接参 / 调 Logic / 返回。


2. slot_lib — 新增 FreeCreditsService + 补 WalletService

2.1 slot_lib/src/services/FreeCreditsService.php 新建

class FreeCreditsService extends BaseApiService
{
    use SingletonService;
    protected $hostKey = 'consoleApiHost';
    protected $basePath = 'innerapi/free-credits';

    public function statistics(array $params) { return $this->postAction('statistics', $params); }
    public function list(array $params) { return $this->postAction('list', $params); }
}

理由:不与既有 ActivityService::join/add/update 混杂basePath 完全独立,匹配 slot_console 的路由。

2.2 slot_lib/src/services/WalletService.phpstatistics

public function statistics(array $uids, string $currency)
{
    return $this->postAction('statistics', ['uids' => $uids, 'currency' => $currency]);
}

action 名 statisticsURL {walletApiHost}/api/wallet/statistics,对应 slot_wallet WalletController::statistics。slot_console 侧 FreeCreditsStatsLogic 调用此方法。


3. slot_admin — 新增代理 Controller

文件:backend/slot_admin/app/game/controller/FreeCreditsStatsController.php

只做接参 + Service 调用 + 合并:

class FreeCreditsStatsController extends AdminController
{
    public function index(Request $request): Response
    {
        $params = $request->all();
        $params['page']  = (int)$request->get('page', 1);
        $params['limit'] = (int)$request->get('limit', 20);

        $list  = FreeCreditsService::getInstance()->list($params);
        $stats = FreeCreditsService::getInstance()->statistics($params);

        $list['otherData'] = $stats;
        return $this->success($list);
    }
}
  • 不需要 Validate参数全部可选非法值 slot_console 侧拦)
  • 自动路由路径 /game/freeCreditsStats/index
  • 不新增 export() actionV1 不做导出)

4. slot_admin_vue — 新页 + API

4.1 backend/slot_admin_vue/src/api/game/freeCreditsStats.js 新建

import request from '@/utils/request'
const url = '/game/freeCreditsStats'
export default {
  getPageList: (params) => request.get(`${url}/index`, params),
}

4.2 backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue 新建

骨架仿 views/game/order/recharge/index.vue

  • defineOptions({ name: 'game/freeCreditsStats/index' })
  • <sa-table> options.api = api.getPageListoptions.add / options.delete 关闭
  • searchFormuidsource(用 commonStore.allSourcesOptionsNoAll)、first_recharge_timea-range-picker)、statusfirst_cash_statusis_completed
  • 顶部统计走 #tableAfterButtons
<template #tableAfterButtons>
  <a-space>
    <a-typography-text>定格人数{{ stats.frozen_user_count }}</a-typography-text>
    <a-typography-text>完成提现人数{{ stats.first_cashout_user_count }}</a-typography-text>
    <a-typography-text>全部完成人数{{ stats.completed_user_count }}</a-typography-text>
    <a-typography-text>定格总金额${{ qfToDollar(stats.frozen_amount_qf) }}</a-typography-text>
    <a-typography-text>已提现金额${{ qfToDollar(stats.first_cashout_amount_qf) }}</a-typography-text>
    <a-typography-text>已领取金额${{ qfToDollar(stats.claimed_amount_qf) }}</a-typography-text>
    <a-typography-text>待释放金额${{ qfToDollar(stats.pending_amount_qf) }}</a-typography-text>
  </a-space>
</template>
  • stats = computed(() => crudRef.value?.getTableOtherData() ?? {})
  • 列定义12 列对齐 §18.2statusfirst_cash_status 直接用前端 map 表(不新建字典):
const PLAYER_STATUS_MAP = {
  0: '未触发', 1: 'Home Withdraw 已解锁', 2: '已定格', 3: '充值进行中',
  4: '第一档可提现', 5: '第一档处理中', 6: '第一档已提现',
  7: '后续档释放中', 10: '全部完成',
}
const PACKAGE_STATUS_MAP = {
  0: '未解锁', 1: '可操作', 2: '处理中', 3: '已完成', 4: '失败', 5: '已拒绝',
}
  • qfToDollar = (v) => ((Number(v||0))/1000).toFixed(2)
  • 不再调单独的 stats 接口,tableAfterButtons 数据从 otherData

5. 字典与菜单

5.1 字典:不新增

status / first_cash_status 直接走前端 map 表,避免与 RechargeGiftConfigModel 共用字典污染。

5.2 菜单DB 表 sm_system_menu 追加(运营在「系统管理 → 菜单管理」操作即可)

需要的 2 条数据(指引性 SQL实际可走 UI

INSERT INTO sm_system_menu (parent_id, type, title, name, path, component, sort)
  VALUES (<game_parent_id>, 'L', 'Free Credits 统计',
          'game/freeCreditsStats/index', '/game/freeCreditsStats',
          'game/freeCreditsStats/index', 50);
INSERT INTO sm_system_menu (parent_id, type, title, code)
  VALUES (<leaf_id>, 'B', '列表', '/game/freeCreditsStats/index');

写完后给「超级管理员 / 运营」角色分配该菜单与按钮码。


不动的地方

  • slot_console 既有 FreeCreditsLogic C 端逻辑、free_credits_player / free_credits_package 表结构、ext_config 配置读取等不动
  • s_recharge_gift_config 不需要新增列
  • slot_pay 不新增 innerapi不需要订单表精确口径wallet total_deposit 已足够)
  • 现有 ActivityController 编辑表单type=11 分支)已落地,不动
  • RechargeOrderController / withdrawal 等无关模块不动

验收(手测)

dev-environment 在 docker 内调试:

  1. 准备 3 个测试账号A 未定格、B 已定格未提现首档、C 全部完成;各 source 至少一个
  2. 访问 /game/freeCreditsStats 列表:
    • 不传参 → 默认按 first_recharge_time descA 不出现status<2 被过滤B / C 都在
    • 顶部 7 个统计数字与逐条手算结果一致
    • 列表里 total_recharge_amount_qfslot_wallet.api/wallet/statistics 直接拉的 total_deposit 一致
    • progress_done/total 等于 DB 中 free_credits_package 实际行数
  3. 筛选:
    • source=us_01 列表与统计同步收敛
    • first_recharge_time 范围,统计 7 项随之变化
    • is_completed=1,列表只剩 C
    • first_cash_status=3,列表只剩第一档已成功的玩家
  4. 排序分别点击「定格金额」「定格时间」列头order 切换;其它字段不可排序
  5. 权限:用未授权账号访问 → 403授权后正常
  6. 仅改活动状态时(已存在的活动编辑场景)回归一遍,确认未触发 type=11 ext 校验路径回归