1.优化渠道管理中直属投注额度和总投注额度
2.管理员管理中三个菜单数据显示限制
This commit is contained in:
@@ -25,6 +25,7 @@ class Channel extends Backend
|
|||||||
'settleStats',
|
'settleStats',
|
||||||
'dividendRecordList',
|
'dividendRecordList',
|
||||||
'directBetRecordList',
|
'directBetRecordList',
|
||||||
|
'companyBetRecordList',
|
||||||
'settlementBetRecordList',
|
'settlementBetRecordList',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -751,6 +752,7 @@ class Channel extends Backend
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$paidDividendTotal = '0.00';
|
$paidDividendTotal = '0.00';
|
||||||
|
$companyTotalBetAmount = '0.00';
|
||||||
if ($channelIdList !== []) {
|
if ($channelIdList !== []) {
|
||||||
$paidRow = Db::name('agent_commission_record')
|
$paidRow = Db::name('agent_commission_record')
|
||||||
->where('channel_id', 'in', $channelIdList)
|
->where('channel_id', 'in', $channelIdList)
|
||||||
@@ -758,6 +760,12 @@ class Channel extends Backend
|
|||||||
->field('SUM(commission_amount) AS s')
|
->field('SUM(commission_amount) AS s')
|
||||||
->find();
|
->find();
|
||||||
$paidDividendTotal = bcadd(strval(is_array($paidRow) ? ($paidRow['s'] ?? '0') : '0'), '0', 2);
|
$paidDividendTotal = bcadd(strval(is_array($paidRow) ? ($paidRow['s'] ?? '0') : '0'), '0', 2);
|
||||||
|
|
||||||
|
$companyBetRow = Db::name('game_play_record')
|
||||||
|
->where('channel_id', 'in', $channelIdList)
|
||||||
|
->field('SUM(total_amount) AS s')
|
||||||
|
->find();
|
||||||
|
$companyTotalBetAmount = bcadd(strval(is_array($companyBetRow) ? ($companyBetRow['s'] ?? '0') : '0'), '0', 2);
|
||||||
}
|
}
|
||||||
return $this->success('', [
|
return $this->success('', [
|
||||||
'channel_total' => $total,
|
'channel_total' => $total,
|
||||||
@@ -767,6 +775,7 @@ class Channel extends Backend
|
|||||||
'carryover_total' => $carryoverTotal,
|
'carryover_total' => $carryoverTotal,
|
||||||
'carryover_positive_total' => $carryoverPositiveTotal,
|
'carryover_positive_total' => $carryoverPositiveTotal,
|
||||||
'paid_dividend_total' => $paidDividendTotal,
|
'paid_dividend_total' => $paidDividendTotal,
|
||||||
|
'company_total_bet_amount' => $companyTotalBetAmount,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,7 +788,7 @@ class Channel extends Backend
|
|||||||
if ($response !== null) {
|
if ($response !== null) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
if (!$this->auth->check('channel/viewDividendRecords')) {
|
if (!$this->auth->check('channel/index')) {
|
||||||
return $this->error(__('You have no permission'));
|
return $this->error(__('You have no permission'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,7 +849,7 @@ class Channel extends Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道直属玩家下注记录(直属投注额列点击)
|
* 渠道直属玩家下注记录(直属投注额列点击;该渠道名下玩家全部注单)
|
||||||
*/
|
*/
|
||||||
public function directBetRecordList(WebmanRequest $request): Response
|
public function directBetRecordList(WebmanRequest $request): Response
|
||||||
{
|
{
|
||||||
@@ -848,7 +857,7 @@ class Channel extends Backend
|
|||||||
if ($response !== null) {
|
if ($response !== null) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
if (!$this->auth->check('channel/viewDirectBetRecords')) {
|
if (!$this->auth->check('channel/index')) {
|
||||||
return $this->error(__('You have no permission'));
|
return $this->error(__('You have no permission'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,27 +870,31 @@ class Channel extends Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参与分红口径的下注记录(操作列「查看总投注金额」)
|
* 公司总投注记录下注明细(顶部公司总投注额点击;含未结算,当前账号可见渠道范围)
|
||||||
*/
|
*/
|
||||||
public function settlementBetRecordList(WebmanRequest $request): Response
|
public function companyBetRecordList(WebmanRequest $request): Response
|
||||||
{
|
{
|
||||||
$response = $this->initializeBackend($request);
|
$response = $this->initializeBackend($request);
|
||||||
if ($response !== null) {
|
if ($response !== null) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
if (!$this->auth->check('channel/viewSettlementBetRecords')) {
|
if (!$this->auth->check('channel/index')) {
|
||||||
return $this->error(__('You have no permission'));
|
return $this->error(__('You have no permission'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$channelId = (int) ($request->get('channel_id', 0));
|
return $this->success('', $this->fetchChannelPlayRecordListPayload($request, 0, false));
|
||||||
if ($channelId <= 0 || !$this->assertChannelAccessible($channelId)) {
|
|
||||||
return $this->error(__('You have no permission'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success('', $this->fetchChannelPlayRecordListPayload($request, $channelId, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated 请使用 companyBetRecordList
|
||||||
|
*/
|
||||||
|
public function settlementBetRecordList(WebmanRequest $request): Response
|
||||||
|
{
|
||||||
|
return $this->companyBetRecordList($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $channelId 0=当前账号可见全部渠道
|
||||||
* @return array{list: array<int, array<string, mixed>>, total: int, summary: array{record_count:int,total_bet_amount:string,total_win_amount:string}}
|
* @return array{list: array<int, array<string, mixed>>, total: int, summary: array{record_count:int,total_bet_amount:string,total_win_amount:string}}
|
||||||
*/
|
*/
|
||||||
private function fetchChannelPlayRecordListPayload(WebmanRequest $request, int $channelId, bool $settledOnly): array
|
private function fetchChannelPlayRecordListPayload(WebmanRequest $request, int $channelId, bool $settledOnly): array
|
||||||
@@ -1054,8 +1067,21 @@ class Channel extends Backend
|
|||||||
$query = Db::name('game_play_record')->alias('pr')
|
$query = Db::name('game_play_record')->alias('pr')
|
||||||
->leftJoin('user u', 'u.id = pr.user_id')
|
->leftJoin('user u', 'u.id = pr.user_id')
|
||||||
->leftJoin('game_record gr', 'gr.id = pr.period_id')
|
->leftJoin('game_record gr', 'gr.id = pr.period_id')
|
||||||
->leftJoin('channel c', 'c.id = pr.channel_id')
|
->leftJoin('channel c', 'c.id = pr.channel_id');
|
||||||
->where('pr.channel_id', $channelId);
|
if ($channelId > 0) {
|
||||||
|
if (!$this->assertChannelAccessible($channelId)) {
|
||||||
|
$query->where('pr.channel_id', 0);
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
$query->where('pr.channel_id', $channelId);
|
||||||
|
} else {
|
||||||
|
$scope = $this->readableChannelIds();
|
||||||
|
if ($scope === []) {
|
||||||
|
$query->where('pr.channel_id', 0);
|
||||||
|
} elseif ($scope !== null) {
|
||||||
|
$query->whereIn('pr.channel_id', $scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($settledOnly) {
|
if ($settledOnly) {
|
||||||
$query->where('pr.status', 2);
|
$query->where('pr.status', 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ class AdminWallet extends Backend
|
|||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||||
$table = strtolower($this->model->getTable());
|
$table = strtolower($this->model->getTable());
|
||||||
$mainShort = $alias[$table] ?? '';
|
$mainShort = $alias[$table] ?? '';
|
||||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
$scopedAdminIds = $this->getManageableScopeAdminIds();
|
||||||
// 非超管仅可查看自己钱包
|
if ($mainShort !== '' && $scopedAdminIds !== []) {
|
||||||
$where[] = [$mainShort . '.admin_id', '=', intval($this->auth->id ?? 0)];
|
$where[] = [$mainShort . '.admin_id', 'in', $scopedAdminIds];
|
||||||
}
|
}
|
||||||
$res = $this->model
|
$res = $this->model
|
||||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ class AdminWalletRecord extends Backend
|
|||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||||
$table = strtolower($this->model->getTable());
|
$table = strtolower($this->model->getTable());
|
||||||
$mainShort = $alias[$table] ?? '';
|
$mainShort = $alias[$table] ?? '';
|
||||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
$scopedAdminIds = $this->getManageableScopeAdminIds();
|
||||||
$where[] = [$mainShort . '.admin_id', '=', intval($this->auth->id ?? 0)];
|
if ($mainShort !== '' && $scopedAdminIds !== []) {
|
||||||
|
$where[] = [$mainShort . '.admin_id', 'in', $scopedAdminIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $this->model
|
$res = $this->model
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ class AdminWithdrawOrder extends Backend
|
|||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||||
$table = strtolower($this->model->getTable());
|
$table = strtolower($this->model->getTable());
|
||||||
$mainShort = $alias[$table] ?? '';
|
$mainShort = $alias[$table] ?? '';
|
||||||
$channelScope = $this->readableChannelIds();
|
$scopedAdminIds = $this->getManageableScopeAdminIds();
|
||||||
if ($mainShort !== '' && $channelScope !== null) {
|
if ($mainShort !== '' && $scopedAdminIds !== []) {
|
||||||
$where[] = [$mainShort . '.channel_id', 'in', $channelScope];
|
$where[] = [$mainShort . '.admin_id', 'in', $scopedAdminIds];
|
||||||
}
|
}
|
||||||
$res = $this->model
|
$res = $this->model
|
||||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||||
@@ -171,9 +171,9 @@ class AdminWithdrawOrder extends Backend
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
$query = Db::name('admin_withdraw_order');
|
$query = Db::name('admin_withdraw_order');
|
||||||
$channelScope = $this->readableChannelIds();
|
$scopedAdminIds = $this->getManageableScopeAdminIds();
|
||||||
if ($channelScope !== null) {
|
if ($scopedAdminIds !== []) {
|
||||||
$query->where('channel_id', 'in', $channelScope);
|
$query->where('admin_id', 'in', $scopedAdminIds);
|
||||||
}
|
}
|
||||||
$rows = $query->field(['status', 'amount', 'actual_amount'])->select()->toArray();
|
$rows = $query->field(['status', 'amount', 'actual_amount'])->select()->toArray();
|
||||||
$total = count($rows);
|
$total = count($rows);
|
||||||
@@ -232,16 +232,16 @@ class AdminWithdrawOrder extends Backend
|
|||||||
if ($this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
if ($this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$channelId = intval($order['channel_id'] ?? 0);
|
$adminId = intval($order['admin_id'] ?? 0);
|
||||||
if ($channelId <= 0) {
|
if ($adminId <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$allowed = $this->readableChannelIds();
|
$scopedAdminIds = $this->getManageableScopeAdminIds();
|
||||||
if ($allowed === null) {
|
if ($scopedAdminIds === []) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return in_array($channelId, $allowed, true);
|
return in_array($adminId, $scopedAdminIds, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace app\common\controller;
|
|||||||
use Throwable;
|
use Throwable;
|
||||||
use app\admin\library\Auth;
|
use app\admin\library\Auth;
|
||||||
use app\common\service\AdminChannelScopeService;
|
use app\common\service\AdminChannelScopeService;
|
||||||
|
use app\common\service\AdminCommissionDistributionService;
|
||||||
|
use support\think\Db;
|
||||||
use app\common\library\token\TokenExpirationException;
|
use app\common\library\token\TokenExpirationException;
|
||||||
use app\admin\library\traits\Backend as BackendTrait;
|
use app\admin\library\traits\Backend as BackendTrait;
|
||||||
use support\Response;
|
use support\Response;
|
||||||
@@ -542,4 +544,49 @@ class Backend extends Api
|
|||||||
|
|
||||||
return array_values(array_unique($paths));
|
return array_values(array_unique($paths));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色组管理范围内可见的管理员 ID(本人 + 代理树下级 + 本人所在组及下级组内管理员)
|
||||||
|
* 超管或全平台只读范围返回空数组表示不限制
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
protected function getManageableScopeAdminIds(): array
|
||||||
|
{
|
||||||
|
if ($this->auth === null || !$this->auth->isLogin()) {
|
||||||
|
return [0];
|
||||||
|
}
|
||||||
|
if ($this->auth->isSuperAdmin() || $this->hasGlobalReadScope()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$operatorId = intval($this->auth->id);
|
||||||
|
$ids = AdminCommissionDistributionService::getVisibleAdminIdsForOperator($operatorId, false);
|
||||||
|
|
||||||
|
$ownGroupIds = Db::name('admin_group_access')->where('uid', $operatorId)->column('group_id');
|
||||||
|
$childGroupIds = $this->auth->getAdminChildGroups();
|
||||||
|
$groupIds = array_values(array_unique(array_merge(
|
||||||
|
array_map(static fn($id) => intval(strval($id)), $ownGroupIds),
|
||||||
|
array_map(static fn($id) => intval(strval($id)), $childGroupIds)
|
||||||
|
)));
|
||||||
|
if ($groupIds !== []) {
|
||||||
|
$groupAdminIds = Db::name('admin_group_access')
|
||||||
|
->where('group_id', 'in', $groupIds)
|
||||||
|
->column('uid');
|
||||||
|
foreach ($groupAdminIds as $uid) {
|
||||||
|
$uidInt = intval(strval($uid));
|
||||||
|
if ($uidInt > 0) {
|
||||||
|
$ids[] = $uidInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($operatorId > 0) {
|
||||||
|
$ids[] = $operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_values(array_unique(array_filter($ids, static fn(int $id): bool => $id > 0)));
|
||||||
|
|
||||||
|
return $ids === [] ? [0] : $ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ return [
|
|||||||
'viewCommissionRecords' => 'View commission records',
|
'viewCommissionRecords' => 'View commission records',
|
||||||
'viewDividendRecords' => 'View paid dividend records',
|
'viewDividendRecords' => 'View paid dividend records',
|
||||||
'viewDirectBetRecords' => 'View direct bet records',
|
'viewDirectBetRecords' => 'View direct bet records',
|
||||||
'viewSettlementBetRecords' => 'View settlement-scope bets',
|
'viewSettlementBetRecords' => 'View company bet records',
|
||||||
'viewAllChannels' => 'View all channels',
|
'viewAllChannels' => 'View all channels',
|
||||||
|
|
||||||
// 其它中文按钮文案
|
// 其它中文按钮文案
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ return [
|
|||||||
'viewCommissionRecords' => '查看代理佣金记录',
|
'viewCommissionRecords' => '查看代理佣金记录',
|
||||||
'viewDividendRecords' => '查看已分红记录',
|
'viewDividendRecords' => '查看已分红记录',
|
||||||
'viewDirectBetRecords' => '查看直属投注记录',
|
'viewDirectBetRecords' => '查看直属投注记录',
|
||||||
'viewSettlementBetRecords' => '查看总投注金额',
|
'viewSettlementBetRecords' => '查看公司总投注记录',
|
||||||
'viewAllChannels' => '查看所有渠道',
|
'viewAllChannels' => '查看所有渠道',
|
||||||
'walletAdjust' => '钱包加减点',
|
'walletAdjust' => '钱包加减点',
|
||||||
'Markdown文档' => 'Markdown文档',
|
'Markdown文档' => 'Markdown文档',
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ Documents the **Channel Management** page (`/admin/channel`): summary cards, lis
|
|||||||
| Enabled | `status = 1` |
|
| Enabled | `status = 1` |
|
||||||
| Pending dividend (count) | `carryover_balance > 0` |
|
| Pending dividend (count) | `carryover_balance > 0` |
|
||||||
| Pending dividend (amount) | Sum of those balances |
|
| Pending dividend (amount) | Sum of those balances |
|
||||||
| Paid dividend | Sum of paid `agent_commission_record` in scope; clickable dialog requires `viewDividendRecords` |
|
| Paid dividend | Sum of paid `agent_commission_record` in scope; clickable dialog requires `channel/index` |
|
||||||
|
| Company total bet | Total bets in readable scope; clickable records dialog requires `channel/index` |
|
||||||
|
|
||||||
List filters: **All / With balance / No balance / Enabled only / Disabled only** (UI search only).
|
List filters: **All / With balance / No balance / Enabled only / Disabled only** (UI search only).
|
||||||
|
|
||||||
@@ -36,9 +37,7 @@ List filters: **All / With balance / No balance / Enabled only / Disabled only**
|
|||||||
|
|
||||||
| Node | Label | Behavior |
|
| Node | Label | Behavior |
|
||||||
|------|-------|----------|
|
|------|-------|----------|
|
||||||
| `channel/viewDividendRecords` | Paid dividend records | Top card + dialog |
|
| `channel/index` | View | List, stat card clicks, bet/dividend record dialogs |
|
||||||
| `channel/viewDirectBetRecords` | Direct bet records | Direct bet column click |
|
|
||||||
| `channel/viewSettlementBetRecords` | Settlement-scope bets | Row action |
|
|
||||||
| `channel/manualSettle` | Manual settle | Preview + submit (readable channel) |
|
| `channel/manualSettle` | Manual settle | Preview + submit (readable channel) |
|
||||||
| `channel/batchSettlePending` | Batch settle | Writable enabled channels in scope |
|
| `channel/batchSettlePending` | Batch settle | Writable enabled channels in scope |
|
||||||
|
|
||||||
@@ -59,8 +58,8 @@ Re-login after role changes to refresh `authNode`.
|
|||||||
|
|
||||||
| Entry | API | Data |
|
| Entry | API | Data |
|
||||||
|-------|-----|------|
|
|-------|-----|------|
|
||||||
| Direct bet amount | `directBetRecordList` | All play records for channel |
|
| Direct bet amount (column) | `directBetRecordList` | All play records for that channel |
|
||||||
| View settlement bets | `settlementBetRecordList` | `status = 2` only |
|
| Company total bet (top card) | `companyBetRecordList` | All play records in readable scope |
|
||||||
|
|
||||||
**Filters (GET):** `period_no`, `user_keyword`, `result_number`, `pick_number`, `win_hit` (`won`/`lost`/`pending`), `page`, `limit`.
|
**Filters (GET):** `period_no`, `user_keyword`, `result_number`, `pick_number`, `win_hit` (`won`/`lost`/`pending`), `page`, `limit`.
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,9 @@
|
|||||||
* **创建总代/子代账号**:在 **管理员管理**(`/admin/auth/admin`)维护代理树:`parent_admin_id`、`commission_share_rate`(顶级角色组从渠道总佣金分得 %,子代理从上级实得抽取 %)、`channel_id`、邀请码。
|
* **创建总代/子代账号**:在 **管理员管理**(`/admin/auth/admin`)维护代理树:`parent_admin_id`、`commission_share_rate`(顶级角色组从渠道总佣金分得 %,子代理从上级实得抽取 %)、`channel_id`、邀请码。
|
||||||
* **代理树状图 (Tree View)**:管理员列表以树形展示;非超管仅见本人及全部下级。
|
* **代理树状图 (Tree View)**:管理员列表以树形展示;非超管仅见本人及全部下级。
|
||||||
* **渠道管理页**(`/admin/channel`):
|
* **渠道管理页**(`/admin/channel`):
|
||||||
* 顶部统计:渠道数、待分红、已分红(可点开记录);列表支持分红余额/启用状态筛选。
|
* 顶部统计:渠道数、待分红、已分红、**公司总投注额**(可点开全部下注记录);列表支持分红余额/启用状态筛选。
|
||||||
* **数据范围**:`AdminChannelScopeService`;全平台只读条件见 `docs/渠道管理后台说明.md` §3。
|
* **数据范围**:`AdminChannelScopeService`;全平台只读条件见 `docs/渠道管理后台说明.md` §3。
|
||||||
* **操作**:查看总投注金额 / 直属投注记录(弹窗 + 筛选);手动结算(超管或 `channel/manualSettle`)。
|
* **操作**:直属投注额列点击 / 公司总投注额卡片(弹窗 + 筛选);手动结算(超管或 `channel/manualSettle`)。
|
||||||
* **渠道佣金结算**:
|
* **渠道佣金结算**:
|
||||||
* 按渠道 `agent_mode` 与已结算注单计算渠道总佣金(非充值口径)。
|
* 按渠道 `agent_mode` 与已结算注单计算渠道总佣金(非充值口径)。
|
||||||
* 按代理树拆分各管理员实得,写入 `agent_commission_record` 并 **即时入账** `admin_wallet`。
|
* 按代理树拆分各管理员实得,写入 `agent_commission_record` 并 **即时入账** `admin_wallet`。
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
| 启用渠道 | `status = 1` 的渠道数 |
|
| 启用渠道 | `status = 1` 的渠道数 |
|
||||||
| 待分红渠道 | `carryover_balance > 0` 的渠道数 |
|
| 待分红渠道 | `carryover_balance > 0` 的渠道数 |
|
||||||
| 待分红总额 | 上述渠道 `carryover_balance` 合计 |
|
| 待分红总额 | 上述渠道 `carryover_balance` 合计 |
|
||||||
| 已分红金额 | 可读范围内渠道下,已发放佣金(`agent_commission_record.status = 1`)合计;**可点击**打开已分红记录弹窗(需 `viewDividendRecords` 权限) |
|
| 公司总投注额 | 可读范围内全部玩家总投注(含未结算);**可点击**打开全部下注记录(需 `channel/index`) |
|
||||||
|
| 已分红金额 | 可读范围内渠道下,已发放佣金(`agent_commission_record.status = 1`)合计;**可点击**打开已分红记录弹窗(需 `channel/index`) |
|
||||||
|
|
||||||
列表上方筛选:**全部 / 有分红余额 / 无分红余额 / 仅启用 / 仅停用**(前端 `search` 条件,不改变数据范围规则)。
|
列表上方筛选:**全部 / 有分红余额 / 无分红余额 / 仅启用 / 仅停用**(前端 `search` 条件,不改变数据范围规则)。
|
||||||
|
|
||||||
@@ -54,17 +55,15 @@
|
|||||||
### 4.1 常用列
|
### 4.1 常用列
|
||||||
|
|
||||||
- 渠道标识、名称、代理模式、联营负结转、契约编号、结算周期等
|
- 渠道标识、名称、代理模式、联营负结转、契约编号、结算周期等
|
||||||
- **直属投注额**:该渠道下 `game_play_record` 投注合计;**可点击**打开直属下注记录弹窗(需 `viewDirectBetRecords`)
|
- **直属投注额**:该渠道名下全部玩家的总投注额(含未结算);**可点击**打开该渠道直属玩家游戏下注记录(需 `channel/index`)
|
||||||
- 操作列:**查看总投注金额**、**手动结算**、编辑、删除(后两者受写权限约束)
|
- 顶部统计卡片 **公司总投注额**:当前账号可见渠道范围内的真实码量合计(含未结算);**可点击**打开全部下注记录明细(需 `channel/index`)
|
||||||
|
- 操作列:**手动结算**、编辑、删除(后两者受写权限约束)
|
||||||
|
|
||||||
### 4.2 操作按钮权限
|
### 4.2 操作按钮权限
|
||||||
|
|
||||||
| 按钮权限节点 | 名称 | 行为 |
|
| 按钮权限节点 | 名称 | 行为 |
|
||||||
|--------------|------|------|
|
|--------------|------|------|
|
||||||
| `channel/index` | 查看 | 列表与详情 |
|
| `channel/index` | 查看 | 列表、统计卡片点击、下注/分红记录弹窗 |
|
||||||
| `channel/viewDividendRecords` | 查看已分红记录 | 顶部「已分红金额」卡片与弹窗 |
|
|
||||||
| `channel/viewDirectBetRecords` | 查看直属投注记录 | 「直属投注额」列点击 |
|
|
||||||
| `channel/viewSettlementBetRecords` | 查看总投注金额 | 操作列;分红口径已结算注单 |
|
|
||||||
| `channel/manualSettle` | 手动结算 | 操作列;预览并提交渠道结算(见 §5) |
|
| `channel/manualSettle` | 手动结算 | 操作列;预览并提交渠道结算(见 §5) |
|
||||||
| `channel/batchSettlePending` | 一键批量结算 | 批量结算当前账号**可写范围**内启用渠道 |
|
| `channel/batchSettlePending` | 一键批量结算 | 批量结算当前账号**可写范围**内启用渠道 |
|
||||||
| `channel/add` / `edit` / `del` | 增删改 | 须对目标渠道具备写权限 |
|
| `channel/add` / `edit` / `del` | 增删改 | 须对目标渠道具备写权限 |
|
||||||
@@ -91,8 +90,8 @@
|
|||||||
|
|
||||||
| 入口 | 接口 | 数据范围 |
|
| 入口 | 接口 | 数据范围 |
|
||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| 直属投注额 | `GET /admin/channel/directBetRecordList` | 该渠道全部游玩记录 |
|
| 直属投注额(列点击) | `GET /admin/channel/directBetRecordList` | 该渠道名下玩家 **全部** 游玩记录(含未结算) |
|
||||||
| 查看总投注金额 | `GET /admin/channel/settlementBetRecordList` | 该渠道 **已结算** 记录(`status = 2`,参与分红口径) |
|
| 公司总投注额(顶部卡片) | `GET /admin/channel/companyBetRecordList` | 当前账号可见渠道范围内 **全部** 游玩记录(含未结算) |
|
||||||
|
|
||||||
### 6.1 顶部统计(Card)
|
### 6.1 顶部统计(Card)
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@
|
|||||||
## 7. 已分红记录弹窗
|
## 7. 已分红记录弹窗
|
||||||
|
|
||||||
- **接口**:`GET /admin/channel/dividendRecordList`
|
- **接口**:`GET /admin/channel/dividendRecordList`
|
||||||
- **权限**:`channel/viewDividendRecords`
|
- **权限**:`channel/index`
|
||||||
- **字段**:结算单号、渠道名、代理账号、分红金额、结算周期、发放时间等
|
- **字段**:结算单号、渠道名、代理账号、分红金额、结算周期、发放时间等
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -110,11 +110,12 @@ export default {
|
|||||||
settle_stats_pending_dividend: 'Channels pending dividend',
|
settle_stats_pending_dividend: 'Channels pending dividend',
|
||||||
settle_stats_pending_amount: 'Pending dividend amount',
|
settle_stats_pending_amount: 'Pending dividend amount',
|
||||||
settle_stats_paid_dividend: 'Paid dividend amount',
|
settle_stats_paid_dividend: 'Paid dividend amount',
|
||||||
|
settle_stats_company_total_bet: 'Company total bet',
|
||||||
direct_bet_amount: 'Direct bet amount',
|
direct_bet_amount: 'Direct bet amount',
|
||||||
view_settlement_bet: 'View settlement bets',
|
direct_bet_amount_tip: 'Total bets from all players under this channel (including unsettled)',
|
||||||
|
company_bet_record_dialog_title: 'Company bet records',
|
||||||
|
direct_bet_record_dialog_title: 'Direct player game bets',
|
||||||
dividend_record_dialog_title: 'Paid dividend records',
|
dividend_record_dialog_title: 'Paid dividend records',
|
||||||
direct_bet_record_dialog_title: 'Direct player bet records',
|
|
||||||
settlement_bet_record_dialog_title: 'Dividend-scope bet records',
|
|
||||||
bet_record_period_no: 'Period No.',
|
bet_record_period_no: 'Period No.',
|
||||||
bet_record_user_username: 'Player',
|
bet_record_user_username: 'Player',
|
||||||
bet_record_total_amount: 'Bet amount',
|
bet_record_total_amount: 'Bet amount',
|
||||||
|
|||||||
@@ -109,11 +109,12 @@ export default {
|
|||||||
settle_stats_pending_dividend: '待分红渠道',
|
settle_stats_pending_dividend: '待分红渠道',
|
||||||
settle_stats_pending_amount: '待分红总额',
|
settle_stats_pending_amount: '待分红总额',
|
||||||
settle_stats_paid_dividend: '已分红金额',
|
settle_stats_paid_dividend: '已分红金额',
|
||||||
|
settle_stats_company_total_bet: '公司总投注额',
|
||||||
direct_bet_amount: '直属投注额',
|
direct_bet_amount: '直属投注额',
|
||||||
view_settlement_bet: '查看总投注金额',
|
direct_bet_amount_tip: '该渠道名下全部玩家的总投注额(含未结算)',
|
||||||
|
company_bet_record_dialog_title: '公司总投注记录下注明细',
|
||||||
|
direct_bet_record_dialog_title: '直属玩家游戏下注记录',
|
||||||
dividend_record_dialog_title: '已分红记录',
|
dividend_record_dialog_title: '已分红记录',
|
||||||
direct_bet_record_dialog_title: '直属玩家下注记录',
|
|
||||||
settlement_bet_record_dialog_title: '分红口径下注记录',
|
|
||||||
bet_record_period_no: '游戏期号',
|
bet_record_period_no: '游戏期号',
|
||||||
bet_record_user_username: '玩家名',
|
bet_record_user_username: '玩家名',
|
||||||
bet_record_total_amount: '投注金额',
|
bet_record_total_amount: '投注金额',
|
||||||
|
|||||||
@@ -27,7 +27,16 @@
|
|||||||
<el-card
|
<el-card
|
||||||
shadow="never"
|
shadow="never"
|
||||||
class="channel-stat-card"
|
class="channel-stat-card"
|
||||||
:class="{ 'channel-stat-card-clickable': auth('viewDividendRecords') }"
|
:class="{ 'channel-stat-card-clickable': auth('index') }"
|
||||||
|
@click="onCompanyTotalBetCardClick"
|
||||||
|
>
|
||||||
|
<div class="label">{{ t('channel.settle_stats_company_total_bet') }}</div>
|
||||||
|
<div class="value">{{ settleStats.company_total_bet_amount }}</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
shadow="never"
|
||||||
|
class="channel-stat-card"
|
||||||
|
:class="{ 'channel-stat-card-clickable': auth('index') }"
|
||||||
@click="onPaidDividendCardClick"
|
@click="onPaidDividendCardClick"
|
||||||
>
|
>
|
||||||
<div class="label">{{ t('channel.settle_stats_paid_dividend') }}</div>
|
<div class="label">{{ t('channel.settle_stats_paid_dividend') }}</div>
|
||||||
@@ -353,6 +362,7 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
|
v-if="betRecordDialog.mode === 'company'"
|
||||||
prop="channel_name"
|
prop="channel_name"
|
||||||
:label="t('channel.bet_record_channel_name')"
|
:label="t('channel.bet_record_channel_name')"
|
||||||
min-width="100"
|
min-width="100"
|
||||||
@@ -518,20 +528,6 @@ const dismissFloatingTooltips = () => {
|
|||||||
}
|
}
|
||||||
const tableRef = useTemplateRef('tableRef')
|
const tableRef = useTemplateRef('tableRef')
|
||||||
let optButtons: OptButton[] = [
|
let optButtons: OptButton[] = [
|
||||||
{
|
|
||||||
render: 'tipButton',
|
|
||||||
name: 'viewSettlementBet',
|
|
||||||
title: 'channel.view_settlement_bet',
|
|
||||||
text: '',
|
|
||||||
type: 'info',
|
|
||||||
icon: 'fa fa-list-alt',
|
|
||||||
class: 'table-row-view-settlement-bet',
|
|
||||||
disabledTip: false,
|
|
||||||
display: () => auth('viewSettlementBetRecords'),
|
|
||||||
click: (row: TableRow) => {
|
|
||||||
void openBetRecordDialog('settlement', row)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
render: 'tipButton',
|
render: 'tipButton',
|
||||||
name: 'manualSettle',
|
name: 'manualSettle',
|
||||||
@@ -658,6 +654,7 @@ const settleStats = reactive({
|
|||||||
carryover_total: '0.00',
|
carryover_total: '0.00',
|
||||||
carryover_positive_total: '0.00',
|
carryover_positive_total: '0.00',
|
||||||
paid_dividend_total: '0.00',
|
paid_dividend_total: '0.00',
|
||||||
|
company_total_bet_amount: '0.00',
|
||||||
})
|
})
|
||||||
|
|
||||||
const dividendDialog = reactive({
|
const dividendDialog = reactive({
|
||||||
@@ -680,7 +677,7 @@ const createBetRecordFilters = () => ({
|
|||||||
const betRecordDialog = reactive({
|
const betRecordDialog = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
mode: '' as '' | 'direct' | 'settlement',
|
mode: '' as '' | 'direct' | 'company',
|
||||||
channelId: 0,
|
channelId: 0,
|
||||||
title: '',
|
title: '',
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -952,6 +949,7 @@ const loadSettleStats = async () => {
|
|||||||
settleStats.carryover_total = String(res.data.carryover_total ?? '0.00')
|
settleStats.carryover_total = String(res.data.carryover_total ?? '0.00')
|
||||||
settleStats.carryover_positive_total = String(res.data.carryover_positive_total ?? '0.00')
|
settleStats.carryover_positive_total = String(res.data.carryover_positive_total ?? '0.00')
|
||||||
settleStats.paid_dividend_total = String(res.data.paid_dividend_total ?? '0.00')
|
settleStats.paid_dividend_total = String(res.data.paid_dividend_total ?? '0.00')
|
||||||
|
settleStats.company_total_bet_amount = String(res.data.company_total_bet_amount ?? '0.00')
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadDividendRecords = async () => {
|
const loadDividendRecords = async () => {
|
||||||
@@ -973,7 +971,7 @@ const loadDividendRecords = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPaidDividendCardClick = () => {
|
const onPaidDividendCardClick = () => {
|
||||||
if (!auth('viewDividendRecords')) {
|
if (!auth('index')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dividendDialog.page = 1
|
dividendDialog.page = 1
|
||||||
@@ -981,6 +979,18 @@ const onPaidDividendCardClick = () => {
|
|||||||
void loadDividendRecords()
|
void loadDividendRecords()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onCompanyTotalBetCardClick = () => {
|
||||||
|
if (!auth('index')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resetBetRecordDialog()
|
||||||
|
betRecordDialog.mode = 'company'
|
||||||
|
betRecordDialog.channelId = 0
|
||||||
|
betRecordDialog.title = t('channel.company_bet_record_dialog_title')
|
||||||
|
betRecordDialog.visible = true
|
||||||
|
void loadBetRecords()
|
||||||
|
}
|
||||||
|
|
||||||
const closeDividendDialog = () => {
|
const closeDividendDialog = () => {
|
||||||
dividendDialog.visible = false
|
dividendDialog.visible = false
|
||||||
dividendDialog.list = []
|
dividendDialog.list = []
|
||||||
@@ -1030,22 +1040,30 @@ const buildBetRecordFilterParams = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadBetRecords = async () => {
|
const loadBetRecords = async () => {
|
||||||
if (!betRecordDialog.channelId || !betRecordDialog.mode) {
|
if (!betRecordDialog.mode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (betRecordDialog.mode === 'direct' && !betRecordDialog.channelId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const url =
|
const url =
|
||||||
betRecordDialog.mode === 'direct' ? '/admin/channel/directBetRecordList' : '/admin/channel/settlementBetRecordList'
|
betRecordDialog.mode === 'direct'
|
||||||
|
? '/admin/channel/directBetRecordList'
|
||||||
|
: '/admin/channel/companyBetRecordList'
|
||||||
betRecordDialog.loading = true
|
betRecordDialog.loading = true
|
||||||
try {
|
try {
|
||||||
|
const params: Record<string, string | number> = {
|
||||||
|
page: betRecordDialog.page,
|
||||||
|
limit: betRecordDialog.limit,
|
||||||
|
...buildBetRecordFilterParams(),
|
||||||
|
}
|
||||||
|
if (betRecordDialog.mode === 'direct') {
|
||||||
|
params.channel_id = betRecordDialog.channelId
|
||||||
|
}
|
||||||
const res = await createAxios({
|
const res = await createAxios({
|
||||||
url,
|
url,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params,
|
||||||
channel_id: betRecordDialog.channelId,
|
|
||||||
page: betRecordDialog.page,
|
|
||||||
limit: betRecordDialog.limit,
|
|
||||||
...buildBetRecordFilterParams(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if (res.code !== 1 || !res.data) {
|
if (res.code !== 1 || !res.data) {
|
||||||
return
|
return
|
||||||
@@ -1063,9 +1081,8 @@ const loadBetRecords = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openBetRecordDialog = (mode: 'direct' | 'settlement', row: TableRow) => {
|
const openBetRecordDialog = (mode: 'direct' | 'company', row: TableRow) => {
|
||||||
const permission = mode === 'direct' ? 'viewDirectBetRecords' : 'viewSettlementBetRecords'
|
if (!auth('index')) {
|
||||||
if (!auth(permission)) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resetBetRecordDialog()
|
resetBetRecordDialog()
|
||||||
@@ -1074,7 +1091,7 @@ const openBetRecordDialog = (mode: 'direct' | 'settlement', row: TableRow) => {
|
|||||||
betRecordDialog.title =
|
betRecordDialog.title =
|
||||||
mode === 'direct'
|
mode === 'direct'
|
||||||
? `${t('channel.direct_bet_record_dialog_title')} - ${row.name ?? row.id}`
|
? `${t('channel.direct_bet_record_dialog_title')} - ${row.name ?? row.id}`
|
||||||
: `${t('channel.settlement_bet_record_dialog_title')} - ${row.name ?? row.id}`
|
: t('channel.company_bet_record_dialog_title')
|
||||||
betRecordDialog.visible = true
|
betRecordDialog.visible = true
|
||||||
void loadBetRecords()
|
void loadBetRecords()
|
||||||
}
|
}
|
||||||
@@ -1234,8 +1251,8 @@ const baTable = new baTableClass(
|
|||||||
formatter: formatAmount2,
|
formatter: formatAmount2,
|
||||||
customRenderAttr: {
|
customRenderAttr: {
|
||||||
tag: ({ row }: { row: TableRow }) => ({
|
tag: ({ row }: { row: TableRow }) => ({
|
||||||
class: auth('viewDirectBetRecords') ? 'channel-direct-bet-tag' : '',
|
class: auth('index') ? 'channel-direct-bet-tag' : '',
|
||||||
onClick: auth('viewDirectBetRecords') ? () => openDirectBetDialog(row) : undefined,
|
onClick: auth('index') ? () => openDirectBetDialog(row) : undefined,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1382,11 +1399,20 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.channel-stats-cards {
|
.channel-stats-cards {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-stats-cards :deep(.el-card__body) {
|
||||||
|
padding: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-stat-card {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-stat-card-clickable {
|
.channel-stat-card-clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: box-shadow 0.2s ease;
|
transition: box-shadow 0.2s ease;
|
||||||
@@ -1468,15 +1494,18 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.channel-stat-card .label {
|
.channel-stat-card .label {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
|
line-height: 1.3;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-stat-card .value {
|
.channel-stat-card .value {
|
||||||
margin-top: 6px;
|
margin-top: 4px;
|
||||||
font-size: 20px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-action-row {
|
.channel-action-row {
|
||||||
|
|||||||
Reference in New Issue
Block a user