1.优化后端管理员提现方式
2.优化后端
This commit is contained in:
@@ -21,16 +21,16 @@ class Dashboard extends Backend
|
||||
return $response;
|
||||
}
|
||||
|
||||
$scope = $this->channelScopeOrNull();
|
||||
$ownerAdminId = $this->ownerAdminIdOrNull();
|
||||
|
||||
$todayStart = strtotime(date('Y-m-d'));
|
||||
$todayEnd = $todayStart + 86400 - 1;
|
||||
$yesterdayStart = $todayStart - 86400;
|
||||
$yesterdayEnd = $todayStart - 1;
|
||||
|
||||
$userTotal = $this->countUsers($scope);
|
||||
$newToday = $this->countUsersInRange($scope, $todayStart, $todayEnd);
|
||||
$newYesterday = $this->countUsersInRange($scope, $yesterdayStart, $yesterdayEnd);
|
||||
$userTotal = $this->countUsers($ownerAdminId);
|
||||
$newToday = $this->countUsersInRange($ownerAdminId, $todayStart, $todayEnd);
|
||||
$newYesterday = $this->countUsersInRange($ownerAdminId, $yesterdayStart, $yesterdayEnd);
|
||||
$growthPct = null;
|
||||
if ($newYesterday > 0) {
|
||||
$growthPct = round(($newToday - $newYesterday) / $newYesterday * 100, 1);
|
||||
@@ -38,14 +38,14 @@ class Dashboard extends Backend
|
||||
$growthPct = 100.0;
|
||||
}
|
||||
|
||||
$depositAgg = $this->aggregateDepositToday($scope, $todayStart, $todayEnd);
|
||||
$withdrawPending = $this->countWithdrawPending($scope);
|
||||
$betAgg = $this->aggregateBetToday($scope, $todayStart, $todayEnd);
|
||||
$depositAgg = $this->aggregateDepositToday($ownerAdminId, $todayStart, $todayEnd);
|
||||
$withdrawPending = $this->countWithdrawPending($ownerAdminId);
|
||||
$betAgg = $this->aggregateBetToday($ownerAdminId, $todayStart, $todayEnd);
|
||||
|
||||
$trend = $this->buildSevenDayTrend($scope);
|
||||
$channelShare = $this->buildChannelShare($scope);
|
||||
$depositAmountChannelShare = $this->buildDepositAmountChannelShare($scope);
|
||||
$recentUsers = $this->fetchRecentUsers($scope, 10);
|
||||
$trend = $this->buildSevenDayTrend($ownerAdminId);
|
||||
$channelShare = $this->buildChannelShare($ownerAdminId);
|
||||
$depositAmountChannelShare = $this->buildDepositAmountChannelShare($ownerAdminId);
|
||||
$recentUsers = $this->fetchRecentUsers($ownerAdminId, 10);
|
||||
|
||||
return $this->success('', [
|
||||
'remark' => get_route_remark(),
|
||||
@@ -68,27 +68,27 @@ class Dashboard extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|null $scope null=超管不限制;非 null 时 whereIn channel_id
|
||||
* @param int|null $ownerAdminId null=超管不限制;非 null 时 where admin_id
|
||||
*/
|
||||
private function countUsers(?array $scope): int
|
||||
private function countUsers(?int $ownerAdminId): int
|
||||
{
|
||||
$q = Db::name('user');
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->where('admin_id', '=', $ownerAdminId);
|
||||
}
|
||||
return intval($q->count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
*/
|
||||
private function countUsersInRange(?array $scope, int $start, int $end): int
|
||||
private function countUsersInRange(?int $ownerAdminId, int $start, int $end): int
|
||||
{
|
||||
$q = Db::name('user')
|
||||
->where('create_time', '>=', $start)
|
||||
->where('create_time', '<=', $end);
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->where('admin_id', '=', $ownerAdminId);
|
||||
}
|
||||
return intval($q->count());
|
||||
}
|
||||
@@ -96,17 +96,17 @@ class Dashboard extends Backend
|
||||
/**
|
||||
* 今日成功充值:status=1,按创建日落在今日(与 mock 即时成功一致)。
|
||||
*
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return array{count:int, amount:string}
|
||||
*/
|
||||
private function aggregateDepositToday(?array $scope, int $todayStart, int $todayEnd): array
|
||||
private function aggregateDepositToday(?int $ownerAdminId, int $todayStart, int $todayEnd): array
|
||||
{
|
||||
$q = Db::name('deposit_order')
|
||||
->where('status', 1)
|
||||
->where('create_time', '>=', $todayStart)
|
||||
->where('create_time', '<=', $todayEnd);
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
$rows = $q->fieldRaw('COUNT(*) AS c, COALESCE(SUM(CAST(amount AS DECIMAL(18,2))),0) AS s')->find();
|
||||
if (!is_array($rows)) {
|
||||
@@ -120,13 +120,13 @@ class Dashboard extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
*/
|
||||
private function countWithdrawPending(?array $scope): int
|
||||
private function countWithdrawPending(?int $ownerAdminId): int
|
||||
{
|
||||
$q = Db::name('withdraw_order')->where('status', 0);
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
return intval($q->count());
|
||||
}
|
||||
@@ -134,17 +134,17 @@ class Dashboard extends Backend
|
||||
/**
|
||||
* 今日投注:创建时间在今日且订单未作废(status 1 或 2)。
|
||||
*
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return array{count:int, amount:string}
|
||||
*/
|
||||
private function aggregateBetToday(?array $scope, int $todayStart, int $todayEnd): array
|
||||
private function aggregateBetToday(?int $ownerAdminId, int $todayStart, int $todayEnd): array
|
||||
{
|
||||
$q = Db::name('bet_order')
|
||||
->whereIn('status', [1, 2])
|
||||
->where('create_time', '>=', $todayStart)
|
||||
->where('create_time', '<=', $todayEnd);
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
$rows = $q->fieldRaw('COUNT(*) AS c, COALESCE(SUM(CAST(total_amount AS DECIMAL(18,2))),0) AS s')->find();
|
||||
if (!is_array($rows)) {
|
||||
@@ -157,10 +157,10 @@ class Dashboard extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return array{days:string[], new_users:int[], deposit_amount:string[], bet_amount:string[]}
|
||||
*/
|
||||
private function buildSevenDayTrend(?array $scope): array
|
||||
private function buildSevenDayTrend(?int $ownerAdminId): array
|
||||
{
|
||||
$days = [];
|
||||
$newUsers = [];
|
||||
@@ -172,14 +172,14 @@ class Dashboard extends Backend
|
||||
$dayEnd = $dayStart + 86400 - 1;
|
||||
$days[] = date('m-d', $dayStart);
|
||||
|
||||
$newUsers[] = $this->countUsersInRange($scope, $dayStart, $dayEnd);
|
||||
$newUsers[] = $this->countUsersInRange($ownerAdminId, $dayStart, $dayEnd);
|
||||
|
||||
$dq = Db::name('deposit_order')
|
||||
->where('status', 1)
|
||||
->where('create_time', '>=', $dayStart)
|
||||
->where('create_time', '<=', $dayEnd);
|
||||
if ($scope !== null) {
|
||||
$dq->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$dq->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
$drow = $dq->fieldRaw('COALESCE(SUM(CAST(amount AS DECIMAL(18,2))),0) AS s')->find();
|
||||
$dsum = is_array($drow) && isset($drow['s']) ? strval($drow['s']) : '0';
|
||||
@@ -189,8 +189,8 @@ class Dashboard extends Backend
|
||||
->whereIn('status', [1, 2])
|
||||
->where('create_time', '>=', $dayStart)
|
||||
->where('create_time', '<=', $dayEnd);
|
||||
if ($scope !== null) {
|
||||
$bq->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$bq->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
$brow = $bq->fieldRaw('COALESCE(SUM(CAST(total_amount AS DECIMAL(18,2))),0) AS s')->find();
|
||||
$bsum = is_array($brow) && isset($brow['s']) ? strval($brow['s']) : '0';
|
||||
@@ -208,14 +208,14 @@ class Dashboard extends Backend
|
||||
/**
|
||||
* 用户按渠道分布(取前 8 名,其余合并为「其他」)。
|
||||
*
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return list<array{name:string, value:int}>
|
||||
*/
|
||||
private function buildChannelShare(?array $scope): array
|
||||
private function buildChannelShare(?int $ownerAdminId): array
|
||||
{
|
||||
$q = Db::name('user')->fieldRaw('channel_id, COUNT(*) AS c')->group('channel_id');
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->where('admin_id', '=', $ownerAdminId);
|
||||
}
|
||||
$rows = $q->orderRaw('c DESC')->select()->toArray();
|
||||
if ($rows === []) {
|
||||
@@ -257,17 +257,17 @@ class Dashboard extends Backend
|
||||
/**
|
||||
* 成功充值金额按订单归属渠道汇总(status=1,受渠道范围限制)。
|
||||
*
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return list<array{name:string, value:string}> value 为两位小数字符串,供前端饼图展示
|
||||
*/
|
||||
private function buildDepositAmountChannelShare(?array $scope): array
|
||||
private function buildDepositAmountChannelShare(?int $ownerAdminId): array
|
||||
{
|
||||
$q = Db::name('deposit_order')
|
||||
->where('status', 1)
|
||||
->fieldRaw('channel_id, COALESCE(SUM(CAST(amount AS DECIMAL(18,2))),0) AS s')
|
||||
->group('channel_id');
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->whereIn('user_id', $this->scopedUserIds($ownerAdminId));
|
||||
}
|
||||
$rows = $q->select()->toArray();
|
||||
if ($rows === []) {
|
||||
@@ -322,17 +322,17 @@ class Dashboard extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[]|null $scope
|
||||
* @param int|null $ownerAdminId
|
||||
* @return list<array{id:int, username:string, create_time:int, channel_name:string, head_image:string}>
|
||||
*/
|
||||
private function fetchRecentUsers(?array $scope, int $limit): array
|
||||
private function fetchRecentUsers(?int $ownerAdminId, int $limit): array
|
||||
{
|
||||
$q = Db::name('user')
|
||||
->field(['id', 'username', 'create_time', 'channel_id', 'head_image'])
|
||||
->order('id', 'desc')
|
||||
->limit($limit);
|
||||
if ($scope !== null) {
|
||||
$q->whereIn('channel_id', $scope);
|
||||
if ($ownerAdminId !== null) {
|
||||
$q->where('admin_id', '=', $ownerAdminId);
|
||||
}
|
||||
$rows = $q->select()->toArray();
|
||||
if ($rows === []) {
|
||||
@@ -372,22 +372,31 @@ class Dashboard extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* 非超管:按管理员所属渠道过滤;未绑定渠道时按 channel_id IN (0) 与列表页一致。
|
||||
* 超管:返回 null,表示 SQL 不加渠道条件。
|
||||
* 非超管:按当前管理员名下用户过滤。
|
||||
* 超管:返回 null,表示 SQL 不加管理员条件。
|
||||
*
|
||||
* @return int[]|null
|
||||
* @return int|null
|
||||
*/
|
||||
private function channelScopeOrNull(): ?array
|
||||
private function ownerAdminIdOrNull(): ?int
|
||||
{
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
return null;
|
||||
}
|
||||
$admin = Db::name('admin')->field(['id', 'channel_id'])->where('id', $this->auth->id)->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
$idRaw = $this->auth->id;
|
||||
if ($idRaw === null || $idRaw === '' || !is_numeric(strval($idRaw))) {
|
||||
return 0;
|
||||
}
|
||||
$id = intval(strval($idRaw));
|
||||
return $id > 0 ? $id : 0;
|
||||
}
|
||||
|
||||
return $ids !== [] ? array_values(array_unique($ids)) : [0];
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function scopedUserIds(int $ownerAdminId): array
|
||||
{
|
||||
$ids = Db::name('user')->where('admin_id', '=', $ownerAdminId)->column('id');
|
||||
$ids = array_map('intval', $ids);
|
||||
return $ids === [] ? [0] : array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,32 @@ class UserNoticeRead extends Backend
|
||||
$this->model = new \app\common\model\UserNoticeRead();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
return $this->select($this->request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$where[] = ['user.admin_id', '=', intval(strval($this->auth->id))];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace app\admin\controller\order;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
@@ -79,8 +78,7 @@ class BetOrder extends Backend
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
$where[] = ['user.admin_id', '=', intval(strval($this->auth->id))];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
@@ -103,25 +101,4 @@ class BetOrder extends Backend
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')
|
||||
->field(['id', 'channel_id'])
|
||||
->where('id', $this->auth->id)
|
||||
->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace app\admin\controller\order;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
@@ -49,8 +48,7 @@ class DepositOrder extends Backend
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
$where[] = ['user.admin_id', '=', intval(strval($this->auth->id))];
|
||||
}
|
||||
$this->appendDepositOrderIndexWhere($where, $mainShort);
|
||||
|
||||
@@ -115,7 +113,7 @@ class DepositOrder extends Backend
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'user' => ['username', 'phone'],
|
||||
'user' => ['username', 'phone', 'admin_id'],
|
||||
'channel' => ['name'],
|
||||
])
|
||||
->where($this->model->getTable() . '.id', $id)
|
||||
@@ -131,36 +129,18 @@ class DepositOrder extends Backend
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
if ($channelIds === []) {
|
||||
$userRow = $row['user'] ?? null;
|
||||
if (!is_array($userRow)) {
|
||||
return false;
|
||||
}
|
||||
$raw = $row['channel_id'] ?? null;
|
||||
if ($raw === null || $raw === '') {
|
||||
$adminIdRaw = $userRow['admin_id'] ?? null;
|
||||
if ($adminIdRaw === null || $adminIdRaw === '') {
|
||||
return false;
|
||||
}
|
||||
if (!is_numeric(strval($raw))) {
|
||||
if (!is_numeric(strval($adminIdRaw))) {
|
||||
return false;
|
||||
}
|
||||
return in_array(intval(strval($raw)), $channelIds, true);
|
||||
return intval(strval($adminIdRaw)) === intval(strval($this->auth->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')->field(['id', 'channel_id'])->where('id', $this->auth->id)->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ class WithdrawOrder extends Backend
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
$where[] = ['user.admin_id', '=', intval(strval($this->auth->id))];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
@@ -386,17 +385,17 @@ class WithdrawOrder extends Backend
|
||||
if (!$this->auth || $this->auth->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
if ($channelIds === []) {
|
||||
$uidRaw = is_array($row) ? ($row['user_id'] ?? null) : ($row->user_id ?? null);
|
||||
$uid = $this->intParam($uidRaw);
|
||||
if ($uid <= 0) {
|
||||
return false;
|
||||
}
|
||||
$raw = is_array($row) ? ($row['channel_id'] ?? null) : ($row->channel_id ?? null);
|
||||
if ($raw === null || $raw === '') {
|
||||
// 无归属渠道的数据只有超管可见
|
||||
$user = Db::name('user')->field(['id', 'admin_id'])->where('id', $uid)->find();
|
||||
if (!is_array($user)) {
|
||||
return false;
|
||||
}
|
||||
$cid = $this->intParam($raw);
|
||||
return in_array($cid, $channelIds, true);
|
||||
$ownerAdminId = $this->intParam($user['admin_id'] ?? 0);
|
||||
return $ownerAdminId > 0 && $ownerAdminId === $this->intParam($this->auth->id ?? 0);
|
||||
}
|
||||
|
||||
private function intParam($raw): int
|
||||
@@ -453,22 +452,4 @@ class WithdrawOrder extends Backend
|
||||
return $negative ? ('-' . $v) : $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')->field(['id', 'channel_id'])->where('id', $this->auth->id)->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ use Webman\Http\Request as WebmanRequest;
|
||||
*/
|
||||
class User extends Backend
|
||||
{
|
||||
/**
|
||||
* 这些接口只要求登录,不单独校验权限节点
|
||||
*/
|
||||
protected array $noNeedPermission = ['adminScopeTree'];
|
||||
|
||||
/**
|
||||
* User模型对象
|
||||
* @var object|null
|
||||
@@ -22,6 +27,11 @@ class User extends Backend
|
||||
*/
|
||||
protected ?object $model = null;
|
||||
|
||||
/**
|
||||
* 渠道管理员仅可管理自己名下(admin_id=当前管理员)的用户
|
||||
*/
|
||||
protected bool|string|int $dataLimit = true;
|
||||
|
||||
protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time', 'invite_code', 'coin', 'total_deposit_coin', 'total_withdraw_coin', 'bet_flow_coin'];
|
||||
|
||||
protected array $withJoinTable = ['channel', 'admin'];
|
||||
@@ -390,9 +400,17 @@ class User extends Backend
|
||||
return $response;
|
||||
}
|
||||
|
||||
$currentAdminLeaf = $this->getCurrentAdminLeaf();
|
||||
if ($currentAdminLeaf === null) {
|
||||
return $this->error(__('Record not found'));
|
||||
}
|
||||
|
||||
$groupIds = $this->getManageableAdminGroupIds();
|
||||
if ($groupIds === []) {
|
||||
return $this->success('', ['list' => []]);
|
||||
return $this->success('', [
|
||||
'list' => [$currentAdminLeaf],
|
||||
'current_admin_id' => $currentAdminLeaf['value'],
|
||||
]);
|
||||
}
|
||||
|
||||
$groups = Db::name('admin_group')
|
||||
@@ -488,12 +506,45 @@ class User extends Backend
|
||||
foreach ($roots as $rid) {
|
||||
$tree[] = $buildNode($rid);
|
||||
}
|
||||
if (!isset($adminPrimary[intval(strval($currentAdminLeaf['value']))])) {
|
||||
$tree[] = $currentAdminLeaf;
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $tree,
|
||||
'current_admin_id' => $currentAdminLeaf['value'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function getCurrentAdminLeaf(): ?array
|
||||
{
|
||||
$adminIdRaw = $this->auth->id ?? null;
|
||||
if ($adminIdRaw === null || $adminIdRaw === '' || !is_numeric(strval($adminIdRaw))) {
|
||||
return null;
|
||||
}
|
||||
$adminId = intval(strval($adminIdRaw));
|
||||
if ($adminId <= 0) {
|
||||
return null;
|
||||
}
|
||||
$row = Db::name('admin')
|
||||
->field(['id', 'username', 'channel_id', 'invite_code'])
|
||||
->where('id', $adminId)
|
||||
->find();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
$invite = $row['invite_code'] ?? '';
|
||||
$invite = is_string($invite) ? $invite : '';
|
||||
$channelId = $row['channel_id'] ?? null;
|
||||
return [
|
||||
'value' => strval($adminId),
|
||||
'label' => strval($row['username'] ?? ('#' . strval($adminId))),
|
||||
'is_leaf' => true,
|
||||
'channel_id' => $channelId === null || $channelId === '' ? null : intval(strval($channelId)),
|
||||
'invite_code' => $invite,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
@@ -79,8 +78,7 @@ class UserWalletRecord extends Backend
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
$where[] = ['user.admin_id', '=', intval(strval($this->auth->id))];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
@@ -103,27 +101,4 @@ class UserWalletRecord extends Backend
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 非超管:与渠道管理一致,仅本账号相关渠道
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')
|
||||
->field(['id', 'channel_id'])
|
||||
->where('id', $this->auth->id)
|
||||
->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,55 @@
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('channel.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
<div class="channel-top-actions">
|
||||
<div class="channel-stats-cards">
|
||||
<el-card shadow="never" class="channel-stat-card">
|
||||
<div class="label">{{ t('channel.settle_stats_channel_total') }}</div>
|
||||
<div class="value">{{ settleStats.channel_total }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="channel-stat-card">
|
||||
<div class="label">{{ t('channel.settle_stats_enabled') }}</div>
|
||||
<div class="value">{{ settleStats.enabled_count }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="channel-stat-card">
|
||||
<div class="label">{{ t('channel.settle_stats_pending_dividend') }}</div>
|
||||
<div class="value">{{ settleStats.carryover_positive_count }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="channel-stat-card">
|
||||
<div class="label">{{ t('channel.settle_stats_pending_amount') }}</div>
|
||||
<div class="value">{{ settleStats.carryover_positive_total }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="channel-action-row">
|
||||
<el-radio-group v-model="settleFilterMode" size="small" @change="onSettleFilterChange">
|
||||
<el-radio-button label="all">{{ t('channel.settle_filter_all') }}</el-radio-button>
|
||||
<el-radio-button label="with_balance">{{ t('channel.settle_filter_with_balance') }}</el-radio-button>
|
||||
<el-radio-button label="no_balance">{{ t('channel.settle_filter_no_balance') }}</el-radio-button>
|
||||
<el-radio-button label="enabled">{{ t('channel.settle_filter_enabled') }}</el-radio-button>
|
||||
<el-radio-button label="disabled">{{ t('channel.settle_filter_disabled') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button v-if="auth('batchSettlePending')" type="warning" @click="onBatchSettlePending">
|
||||
{{ t('channel.batch_settle_pending') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="manualSettle.visible" @close="closeManualSettleDialog">
|
||||
<el-dialog
|
||||
class="ba-operate-dialog manual-settle-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="manualSettle.visible"
|
||||
width="860px"
|
||||
@close="closeManualSettleDialog"
|
||||
>
|
||||
<template #header>
|
||||
<div class="title">{{ t('channel.manual_settle') }}</div>
|
||||
</template>
|
||||
<div v-loading="manualSettle.previewLoading" class="manual-settle-dialog-body">
|
||||
<el-form :model="manualSettle.form" label-width="140px">
|
||||
<el-form :model="manualSettle.form" label-width="140px" class="manual-settle-form">
|
||||
<el-form-item :label="t('channel.manual_settle_settlement_no')">
|
||||
<el-input v-model="manualSettle.form.settlement_no" readonly />
|
||||
</el-form-item>
|
||||
@@ -44,8 +82,8 @@
|
||||
<el-form-item :label="t('channel.manual_settle_commission_amount')">
|
||||
<el-input v-model="manualSettle.form.commission_amount" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('channel.share_config')">
|
||||
<el-table :data="manualSettle.form.commission_split" border size="small" class="w100">
|
||||
<el-form-item :label="t('channel.share_config')" class="manual-settle-form-item-full">
|
||||
<el-table :data="manualSettle.form.commission_split" border size="small" class="w100" max-height="220">
|
||||
<el-table-column prop="admin_username" :label="t('channel.admin__username')" min-width="100" />
|
||||
<el-table-column prop="share_rate" :label="t('channel.share_rate_percent')" min-width="90">
|
||||
<template #default="scope">{{ scope.row.share_rate }}%</template>
|
||||
@@ -53,16 +91,18 @@
|
||||
<el-table-column prop="commission_amount" :label="t('channel.manual_settle_commission_amount')" min-width="110" />
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('channel.manual_settle_remark')">
|
||||
<el-form-item :label="t('channel.manual_settle_remark')" class="manual-settle-form-item-full">
|
||||
<el-input v-model="manualSettle.form.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="closeManualSettleDialog">{{ t('Cancel') }}</el-button>
|
||||
<el-button type="primary" :disabled="manualSettle.previewLoading" :loading="manualSettle.loading" @click="submitManualSettle">
|
||||
{{ t('Save') }}
|
||||
</el-button>
|
||||
<div class="manual-settle-footer">
|
||||
<el-button @click="closeManualSettleDialog">{{ t('Cancel') }}</el-button>
|
||||
<el-button type="primary" :disabled="manualSettle.previewLoading" :loading="manualSettle.loading" @click="submitManualSettle">
|
||||
{{ t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -115,7 +155,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, provide, reactive, useTemplateRef } from 'vue'
|
||||
import { computed, onMounted, provide, reactive, ref, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PopupForm from './popupForm.vue'
|
||||
@@ -207,6 +247,15 @@ const shareDialog = reactive({
|
||||
channelId: 0,
|
||||
list: [] as Array<{ admin_id: number; username: string; role_group_name: string; role_level: number; status: number; share_rate: number | null }>,
|
||||
})
|
||||
const settleFilterMode = ref<'all' | 'with_balance' | 'no_balance' | 'enabled' | 'disabled'>('all')
|
||||
const settleStats = reactive({
|
||||
channel_total: 0,
|
||||
enabled_count: 0,
|
||||
disabled_count: 0,
|
||||
carryover_positive_count: 0,
|
||||
carryover_total: '0.00',
|
||||
carryover_positive_total: '0.00',
|
||||
})
|
||||
|
||||
const shareEnabledTotal = computed(() => {
|
||||
let sum = 0
|
||||
@@ -370,6 +419,35 @@ const submitManualSettle = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onBatchSettlePending = async () => {
|
||||
await createAxios(
|
||||
{
|
||||
url: '/admin/channel/batchSettlePending',
|
||||
method: 'post',
|
||||
},
|
||||
{ showSuccessMessage: true }
|
||||
)
|
||||
await loadSettleStats()
|
||||
baTable.onTableHeaderAction('refresh', { event: 'batch-settle' })
|
||||
}
|
||||
|
||||
const onSettleFilterChange = () => {
|
||||
baTable.getData()
|
||||
}
|
||||
|
||||
const loadSettleStats = async () => {
|
||||
const res = await createAxios({ url: '/admin/channel/settleStats', method: 'get' })
|
||||
if (res.code !== 1 || !res.data) {
|
||||
return
|
||||
}
|
||||
settleStats.channel_total = Number(res.data.channel_total ?? 0)
|
||||
settleStats.enabled_count = Number(res.data.enabled_count ?? 0)
|
||||
settleStats.disabled_count = Number(res.data.disabled_count ?? 0)
|
||||
settleStats.carryover_positive_count = Number(res.data.carryover_positive_count ?? 0)
|
||||
settleStats.carryover_total = String(res.data.carryover_total ?? '0.00')
|
||||
settleStats.carryover_positive_total = String(res.data.carryover_positive_total ?? '0.00')
|
||||
}
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/channel/'),
|
||||
{
|
||||
@@ -564,6 +642,27 @@ const baTable = new baTableClass(
|
||||
}
|
||||
)
|
||||
|
||||
baTable.before.getData = () => {
|
||||
const filter = baTable.table.filter || {}
|
||||
const searchRaw = filter.search
|
||||
const search = Array.isArray(searchRaw) ? searchRaw.filter((item: any) => item && item.field !== 'carryover_balance' && item.field !== 'status') : []
|
||||
if (settleFilterMode.value === 'with_balance') {
|
||||
search.push({ field: 'carryover_balance', operator: 'gt', val: 0 })
|
||||
} else if (settleFilterMode.value === 'no_balance') {
|
||||
search.push({ field: 'carryover_balance', operator: 'elt', val: 0 })
|
||||
} else if (settleFilterMode.value === 'enabled') {
|
||||
search.push({ field: 'status', operator: 'eq', val: 1 })
|
||||
} else if (settleFilterMode.value === 'disabled') {
|
||||
search.push({ field: 'status', operator: 'eq', val: 0 })
|
||||
}
|
||||
filter.search = search
|
||||
baTable.table.filter = filter
|
||||
}
|
||||
|
||||
baTable.after.getData = () => {
|
||||
void loadSettleStats()
|
||||
}
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
@@ -573,6 +672,7 @@ onMounted(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
void loadSettleStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -597,4 +697,71 @@ onMounted(() => {
|
||||
.share-group-empty {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
.channel-top-actions {
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.channel-stats-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.channel-stat-card .label {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.channel-stat-card .value {
|
||||
margin-top: 6px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.channel-action-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.manual-settle-dialog-body {
|
||||
max-height: min(70vh, 680px);
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.manual-settle-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
.manual-settle-form :deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.manual-settle-form-item-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.manual-settle-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.channel-stats-cards {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.manual-settle-form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -210,6 +210,7 @@ type TreeNode = {
|
||||
const adminScopeTree = ref<TreeNode[]>([])
|
||||
const adminIdToChannelId = ref<Record<string, number>>({})
|
||||
const adminIdToInviteCode = ref<Record<string, string>>({})
|
||||
const currentAdminId = ref('')
|
||||
|
||||
const treeProps = {
|
||||
value: 'value',
|
||||
@@ -309,6 +310,8 @@ const loadAdminScopeTree = async () => {
|
||||
method: 'get',
|
||||
})
|
||||
const list = (res.data?.list ?? []) as TreeNode[]
|
||||
const currentIdRaw = res.data?.current_admin_id
|
||||
currentAdminId.value = currentIdRaw === undefined || currentIdRaw === null ? '' : String(currentIdRaw)
|
||||
adminScopeTree.value = list
|
||||
|
||||
const { mapCh, mapInv } = buildAdminMapsFromTree(list)
|
||||
@@ -316,6 +319,14 @@ const loadAdminScopeTree = async () => {
|
||||
adminIdToInviteCode.value = mapInv
|
||||
|
||||
await nextTick()
|
||||
if (
|
||||
baTable.form.operate === 'Add' &&
|
||||
baTable.form.items &&
|
||||
(baTable.form.items.admin_id === undefined || baTable.form.items.admin_id === null || baTable.form.items.admin_id === '') &&
|
||||
currentAdminId.value !== ''
|
||||
) {
|
||||
baTable.form.items.admin_id = currentAdminId.value
|
||||
}
|
||||
const aid = baTable.form.items?.admin_id
|
||||
if (aid !== undefined && aid !== null && aid !== '') {
|
||||
onAdminTreeChange(aid as string | number)
|
||||
@@ -398,6 +409,14 @@ watch(
|
||||
(op) => {
|
||||
if (op === 'Add') {
|
||||
syncRiskFromFlags(0)
|
||||
if (
|
||||
baTable.form.items &&
|
||||
(baTable.form.items.admin_id === undefined || baTable.form.items.admin_id === null || baTable.form.items.admin_id === '') &&
|
||||
currentAdminId.value !== ''
|
||||
) {
|
||||
baTable.form.items.admin_id = currentAdminId.value
|
||||
onAdminTreeChange(currentAdminId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user