From bf3d50a309d5b046ae99c05cbd6bdcd84f4388f0 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Fri, 17 Apr 2026 13:56:13 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E5=BC=80=E5=A5=96=E9=80=BB?= =?UTF-8?q?=E8=BE=91=202.=E4=BC=98=E5=8C=96=E5=90=8E=E5=8F=B0=E5=BC=80?= =?UTF-8?q?=E5=A5=96=E6=B4=BE=E5=BD=A9=203.=E4=BC=98=E5=8C=96=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/Channel.php | 91 +++--- app/admin/controller/auth/Admin.php | 45 ++- app/admin/controller/auth/Group.php | 133 ++++++++ app/admin/controller/game/Record.php | 2 +- app/admin/controller/order/BetOrder.php | 3 +- app/admin/controller/order/DepositOrder.php | 3 +- app/admin/controller/order/WithdrawOrder.php | 3 +- app/admin/controller/user/User.php | 81 ++++- .../{record => user}/UserWalletRecord.php | 5 +- app/api/controller/Account.php | 1 + app/api/controller/Auth.php | 36 ++- app/api/controller/Game.php | 72 ++++- app/api/controller/User.php | 14 +- app/api/lang/en.php | 3 + app/api/lang/zh-cn.php | 3 + app/common/library/Auth.php | 28 +- app/common/model/Channel.php | 10 - app/common/model/GameRecord.php | 16 +- app/common/model/User.php | 24 ++ app/common/service/GameBetSettleService.php | 172 ++++++++++ app/common/service/GameLiveService.php | 14 +- app/common/service/GameRecordStatService.php | 106 ++++++ app/common/validate/Channel.php | 5 +- web/src/lang/backend/en/auth/group.ts | 7 + web/src/lang/backend/en/channel.ts | 5 +- web/src/lang/backend/en/game/period.ts | 28 -- web/src/lang/backend/en/game/record.ts | 2 + .../en/{record => user}/userWalletRecord.ts | 0 web/src/lang/backend/zh-cn/auth/group.ts | 6 + web/src/lang/backend/zh-cn/channel.ts | 5 +- web/src/lang/backend/zh-cn/game/period.ts | 28 -- web/src/lang/backend/zh-cn/game/record.ts | 2 + web/src/lang/backend/zh-cn/game/user.ts | 2 +- web/src/lang/backend/zh-cn/user/user.ts | 6 +- .../{record => user}/userWalletRecord.ts | 0 web/src/views/backend/auth/admin/index.vue | 12 +- web/src/views/backend/auth/group/index.vue | 30 +- .../views/backend/auth/group/popupForm.vue | 137 +++++++- web/src/views/backend/channel/index.vue | 20 -- web/src/views/backend/channel/popupForm.vue | 59 ---- web/src/views/backend/game/live/index.vue | 16 +- web/src/views/backend/game/period/index.vue | 303 ------------------ .../views/backend/game/period/popupForm.vue | 131 -------- web/src/views/backend/game/record/index.vue | 24 ++ .../views/backend/game/record/popupForm.vue | 2 + .../operation/operationNotice/index.vue | 6 +- .../views/backend/order/betOrder/index.vue | 23 +- .../backend/security/dataRecycle/index.vue | 6 +- .../backend/security/sensitiveData/index.vue | 8 +- .../userWalletRecord/index.vue | 68 ++-- 50 files changed, 1036 insertions(+), 770 deletions(-) rename app/admin/controller/{record => user}/UserWalletRecord.php (94%) create mode 100644 app/common/service/GameBetSettleService.php create mode 100644 app/common/service/GameRecordStatService.php delete mode 100644 web/src/lang/backend/en/game/period.ts rename web/src/lang/backend/en/{record => user}/userWalletRecord.ts (100%) delete mode 100644 web/src/lang/backend/zh-cn/game/period.ts rename web/src/lang/backend/zh-cn/{record => user}/userWalletRecord.ts (100%) delete mode 100644 web/src/views/backend/game/period/index.vue delete mode 100644 web/src/views/backend/game/period/popupForm.vue rename web/src/views/backend/{record => user}/userWalletRecord/index.vue (70%) diff --git a/app/admin/controller/Channel.php b/app/admin/controller/Channel.php index 849664f..ee1743a 100644 --- a/app/admin/controller/Channel.php +++ b/app/admin/controller/Channel.php @@ -25,9 +25,9 @@ class Channel extends Backend */ protected ?object $model = null; - protected array|string $preExcludeFields = ['id', 'user_count', 'profit_amount', 'create_time', 'update_time']; + protected array|string $preExcludeFields = ['id', 'user_count', 'profit_amount', 'create_time', 'update_time', 'admin_id']; - protected array $withJoinTable = ['adminGroup', 'admin']; + protected array $withJoinTable = []; protected string|array $quickSearchField = ['id', 'code', 'name']; @@ -51,7 +51,7 @@ class Channel extends Backend if ($response !== null) return $response; $query = Db::name('channel') - ->field(['id', 'name', 'admin_group_id']) + ->field(['id', 'name']) ->order('id', 'asc'); if (!$this->auth->isSuperAdmin()) { $query = $query->where('id', 'in', $this->currentChannelIds ?: [0]); @@ -79,11 +79,16 @@ class Channel extends Backend $tree = []; foreach ($channels as $ch) { - $groupId = $ch['admin_group_id'] ?? null; + $channelId = (int) ($ch['id'] ?? 0); + $rootGroupIds = Db::name('admin_group') + ->where('channel_id', $channelId) + ->where('pid', 0) + ->where('status', 1) + ->column('id'); $groupIds = []; - if ($groupId !== null && $groupId !== '') { - $groupIds[] = $groupId; - foreach ($getGroupChildren($groupId) as $gid) { + foreach ($rootGroupIds as $rootId) { + $groupIds[] = $rootId; + foreach ($getGroupChildren($rootId) as $gid) { $groupIds[] = $gid; } } @@ -130,7 +135,7 @@ class Channel extends Backend } /** - * 添加(重写:管理员只选顶级组;admin_group_id 后端自动写入) + * 添加(重写:渠道与角色组在「角色组」侧绑定 channel_id,此处不再写入 admin_group_id) * @throws Throwable */ protected function _add(): Response @@ -150,30 +155,10 @@ class Channel extends Backend } unset($data['invite_code']); - $adminId = $data['admin_id'] ?? null; - if ($adminId === null || $adminId === '') { - return $this->error(__('Parameter %s can not be empty', ['admin_id'])); - } - if (array_key_exists('admin_group_id', $data)) { unset($data['admin_group_id']); } - $topGroupId = Db::name('admin_group_access') - ->alias('aga') - ->join('admin_group ag', 'aga.group_id = ag.id') - ->where('aga.uid', $adminId) - ->where('ag.pid', 0) - ->value('ag.id'); - - if ($topGroupId === null || $topGroupId === '') { - return $this->error(__('Record not found')); - } - $data['admin_group_id'] = $topGroupId; - if (!$this->auth->isSuperAdmin()) { - $data['admin_id'] = $this->auth->id; - } - if ($this->dataLimit && $this->dataLimitFieldAutoFill) { $data[$this->dataLimitField] = $this->auth->id; } @@ -207,7 +192,7 @@ class Channel extends Backend } /** - * 编辑(重写:管理员只选顶级组;admin_group_id 后端自动写入) + * 编辑(重写:不再维护 channel.admin_group_id) * @throws Throwable */ protected function _edit(): Response @@ -246,24 +231,6 @@ class Channel extends Backend unset($data['admin_group_id']); } - $nextAdminId = array_key_exists('admin_id', $data) ? $data['admin_id'] : ($row['admin_id'] ?? null); - if ($nextAdminId !== null && $nextAdminId !== '') { - $topGroupId = Db::name('admin_group_access') - ->alias('aga') - ->join('admin_group ag', 'aga.group_id = ag.id') - ->where('aga.uid', $nextAdminId) - ->where('ag.pid', 0) - ->value('ag.id'); - - if ($topGroupId === null || $topGroupId === '') { - return $this->error(__('Record not found')); - } - $data['admin_group_id'] = $topGroupId; - } - if (!$this->auth->isSuperAdmin()) { - $data['admin_id'] = $this->auth->id; - } - $result = false; $this->model->startTrans(); try { @@ -310,9 +277,6 @@ class Channel extends Backend $where[] = [$alias['channel'] . '.id', 'in', $this->currentChannelIds ?: [0]]; } $res = $this->model - ->withJoin($this->withJoinTable, $this->withJoinType) - ->with($this->withJoinTable) - ->visible(['adminGroup' => ['name'], 'admin' => ['username']]) ->alias($alias) ->where($where) ->order($order) @@ -392,9 +356,9 @@ class Channel extends Backend return $this->error('结算单号已存在,请稍后重试'); } - $adminId = $row['admin_id'] ?? null; - if ($adminId === null || $adminId === '' || (int) $adminId <= 0) { - return $this->error('渠道未绑定代理管理员,无法生成佣金记录'); + $adminId = $this->resolveCommissionAdminIdForChannel((int) $row['id']); + if ($adminId === null || $adminId <= 0) { + return $this->error('渠道下无归属管理员账号(请为管理员设置所属渠道),无法生成佣金记录'); } $now = time(); @@ -685,8 +649,25 @@ class Channel extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $byAdmin))); + return array_values(array_unique($ids)); + } + + /** + * 佣金归属管理员:取该渠道下 admin.channel_id 匹配的首个管理员(按 id 升序)。 + */ + private function resolveCommissionAdminIdForChannel(int $channelId): ?int + { + if ($channelId <= 0) { + return null; + } + $aid = Db::name('admin') + ->where('channel_id', $channelId) + ->order('id', 'asc') + ->value('id'); + if ($aid === null || $aid === '') { + return null; + } + return (int) $aid; } private function normalizeAgentModeFields(array $data): array diff --git a/app/admin/controller/auth/Admin.php b/app/admin/controller/auth/Admin.php index 294d2da..e418290 100644 --- a/app/admin/controller/auth/Admin.php +++ b/app/admin/controller/auth/Admin.php @@ -187,12 +187,21 @@ class Admin extends Backend $passwd = $data['password'] ?? ''; $data = $this->excludeFields($data); $creatorChannelId = $this->getCreatorChannelId(); + $groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []); if (!$this->auth->isSuperAdmin()) { if ($creatorChannelId === null || $creatorChannelId === '') { return $this->error(__('You have no permission')); } + if ($groupChannelId === null || $groupChannelId === '') { + return $this->error('所选角色组未绑定渠道'); + } + if ((string) $groupChannelId !== (string) $creatorChannelId) { + return $this->error('所选角色组渠道与当前账号不一致'); + } $data['channel_id'] = $creatorChannelId; $data['parent_admin_id'] = $this->auth->id; + } else { + $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId; } $data['invite_code'] = $this->generateUniqueInviteCode(); $requireCommissionRate = $this->requireCommissionRate($data['group_arr'] ?? []); @@ -343,8 +352,20 @@ class Admin extends Backend $data = $this->excludeFields($data); unset($data['invite_code']); $creatorChannelId = $this->getCreatorChannelId(); - if (!$this->auth->isSuperAdmin() && $creatorChannelId !== null && $creatorChannelId !== '') { + $groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []); + if (!$this->auth->isSuperAdmin()) { + if ($creatorChannelId === null || $creatorChannelId === '') { + return $this->error(__('You have no permission')); + } + if ($groupChannelId === null || $groupChannelId === '') { + return $this->error('所选角色组未绑定渠道'); + } + if ((string) $groupChannelId !== (string) $creatorChannelId) { + return $this->error('所选角色组渠道与当前账号不一致'); + } $data['channel_id'] = $creatorChannelId; + } else { + $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId; } $requireCommissionRate = $this->requireCommissionRate($data['group_arr'] ?? []); if ($requireCommissionRate) { @@ -463,10 +484,24 @@ class Admin extends Backend if ($currentAdmin && !empty($currentAdmin['channel_id'])) { return $currentAdmin['channel_id']; } - $channelId = Db::name('channel') - ->where('admin_id', $this->auth->id) - ->value('id'); - return $channelId ?: null; + + return null; + } + + /** + * @param array $groupIds + */ + private function resolveChannelIdFromPrimaryGroup(array $groupIds): mixed + { + if ($groupIds === []) { + return null; + } + $gid = $groupIds[0]; + if ($gid === null || $gid === '') { + return null; + } + + return Db::name('admin_group')->where('id', $gid)->value('channel_id'); } private function generateUniqueInviteCode(): string diff --git a/app/admin/controller/auth/Group.php b/app/admin/controller/auth/Group.php index 4bd22ca..f0f1adb 100644 --- a/app/admin/controller/auth/Group.php +++ b/app/admin/controller/auth/Group.php @@ -86,6 +86,10 @@ class Group extends Backend if (!$this->auth->isSuperAdmin() && $pidInt !== 0 && !in_array($pidInt, $this->manageableGroupIds, true)) { return $this->error(__('You have no permission')); } + $inheritRes = $this->applyChannelInheritance($data, $pidInt); + if ($inheritRes !== null) { + return $inheritRes; + } $shouldHandleCommissionRate = true; if ($shouldHandleCommissionRate) { if (!$this->isValidCommissionRate($data['commission_rate'] ?? null)) { @@ -165,6 +169,10 @@ class Group extends Backend if (!$this->auth->isSuperAdmin() && $pidInt !== 0 && !in_array($pidInt, $this->manageableGroupIds, true)) { return $this->error(__('You have no permission')); } + $inheritRes = $this->applyChannelInheritance($data, $pidInt); + if ($inheritRes !== null) { + return $inheritRes; + } $shouldHandleCommissionRate = true; if ($shouldHandleCommissionRate) { if (!$this->isValidCommissionRate($data['commission_rate'] ?? null)) { @@ -204,6 +212,7 @@ class Group extends Backend return $this->error($e->getMessage()); } if ($result !== false) { + $this->syncDescendantChannelIds(intval((string)$row['id'])); return $this->success(__('Update successful')); } return $this->error(__('No rows updated')); @@ -223,11 +232,39 @@ class Group extends Backend } $rowData = $row->toArray(); $rowData['rules'] = array_values($rules); + $rowData = $this->enrichChannelDisplay($rowData); return $this->success('', [ 'row' => $rowData ]); } + /** + * 表单只读展示:根据 channel_id 解析渠道名称与渠道负责人(admin.channel_id → admin.username,取首个) + */ + public function channelBindPreview(Request $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + $cid = $request->get('channel_id') ?? $request->post('channel_id'); + if ($cid === null || $cid === '') { + return $this->success('', [ + 'channel_name' => '', + 'channel_admin_username' => '', + ]); + } + if (!Db::name('channel')->where('id', $cid)->value('id')) { + return $this->error(__('Record not found')); + } + $row = $this->enrichChannelDisplay(['channel_id' => $cid]); + + return $this->success('', [ + 'channel_name' => $row['channel_name'] ?? '', + 'channel_admin_username' => $row['channel_admin_username'] ?? '', + ]); + } + public function del(Request $request): Response { $response = $this->initializeBackend($request); @@ -353,7 +390,21 @@ class Group extends Backend } $data = $this->model->where($where)->select()->toArray(); + $channelIds = []; + foreach ($data as $datum) { + $c = $datum['channel_id'] ?? null; + if ($c !== null && $c !== '') { + $channelIds[] = $c; + } + } + $channelNames = []; + if ($channelIds !== []) { + $channelNames = Db::name('channel')->where('id', 'in', array_unique($channelIds))->column('name', 'id'); + } + foreach ($data as &$datum) { + $c = $datum['channel_id'] ?? null; + $datum['channel_name'] = ($c !== null && $c !== '') ? ($channelNames[$c] ?? '') : ''; if ($datum['rules']) { if ($datum['rules'] == '*') { $datum['rules'] = __('Super administrator'); @@ -368,6 +419,7 @@ class Group extends Backend $datum['rules'] = __('No permission'); } } + unset($datum); return $this->assembleTree ? $this->tree->assembleChild($data) : $data; } @@ -418,4 +470,85 @@ class Group extends Backend return null; } + /** + * 顶级角色组可选渠道;子级继承父级 channel_id(不信任客户端提交的子级 channel_id)。 + * + * @param array $data + */ + private function applyChannelInheritance(array &$data, int $pidInt): ?Response + { + if ($pidInt === 0) { + if (!$this->auth->isSuperAdmin()) { + unset($data['channel_id']); + $cc = $this->getCreatorChannelId(); + if ($cc !== null && $cc !== '') { + $data['channel_id'] = $cc; + } + } + $cid = $data['channel_id'] ?? null; + if ($cid !== null && $cid !== '') { + $exists = Db::name('channel')->where('id', $cid)->value('id'); + if (!$exists) { + return $this->error(__('Record not found')); + } + } + + return null; + } + + unset($data['channel_id']); + $parent = Db::name('admin_group')->where('id', $pidInt)->find(); + if (!$parent) { + return $this->error(__('Record not found')); + } + $data['channel_id'] = $parent['channel_id']; + + return null; + } + + /** + * @param array $row + * @return array + */ + private function enrichChannelDisplay(array $row): array + { + $row['channel_name'] = ''; + $row['channel_admin_username'] = ''; + $cid = $row['channel_id'] ?? null; + if ($cid === null || $cid === '') { + return $row; + } + $ch = Db::name('channel')->where('id', $cid)->field(['id', 'name'])->find(); + if (!$ch) { + return $row; + } + $row['channel_name'] = $ch['name'] ?? ''; + $row['channel_admin_username'] = (string) (Db::name('admin')->where('channel_id', $cid)->order('id', 'asc')->value('username') ?? ''); + + return $row; + } + + private function syncDescendantChannelIds(int $groupId): void + { + $channelId = Db::name('admin_group')->where('id', $groupId)->value('channel_id'); + $children = Db::name('admin_group')->where('pid', $groupId)->column('id'); + foreach ($children as $childId) { + Db::name('admin_group')->where('id', $childId)->update(['channel_id' => $channelId]); + $this->syncDescendantChannelIds($childId); + } + } + + private function getCreatorChannelId(): mixed + { + $currentAdmin = Db::name('admin') + ->field(['id', 'channel_id']) + ->where('id', $this->auth->id) + ->find(); + if ($currentAdmin && !empty($currentAdmin['channel_id'])) { + return $currentAdmin['channel_id']; + } + + return null; + } + } diff --git a/app/admin/controller/game/Record.php b/app/admin/controller/game/Record.php index b42096c..16a7fd0 100644 --- a/app/admin/controller/game/Record.php +++ b/app/admin/controller/game/Record.php @@ -13,7 +13,7 @@ class Record extends Backend { protected ?object $model = null; - protected string|array $preExcludeFields = ['id', 'create_time', 'update_time']; + protected string|array $preExcludeFields = ['id', 'create_time', 'update_time', 'platform_profit_amount', 'winner_user_count']; protected string|array $quickSearchField = ['id', 'period_no']; diff --git a/app/admin/controller/order/BetOrder.php b/app/admin/controller/order/BetOrder.php index 9461f1b..e6196a7 100644 --- a/app/admin/controller/order/BetOrder.php +++ b/app/admin/controller/order/BetOrder.php @@ -122,7 +122,6 @@ class BetOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $byAdmin))); + return array_values(array_unique($ids)); } } diff --git a/app/admin/controller/order/DepositOrder.php b/app/admin/controller/order/DepositOrder.php index b02b172..aec067b 100644 --- a/app/admin/controller/order/DepositOrder.php +++ b/app/admin/controller/order/DepositOrder.php @@ -81,7 +81,6 @@ class DepositOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $byAdmin))); + return array_values(array_unique($ids)); } } diff --git a/app/admin/controller/order/WithdrawOrder.php b/app/admin/controller/order/WithdrawOrder.php index 4c64f96..b368569 100644 --- a/app/admin/controller/order/WithdrawOrder.php +++ b/app/admin/controller/order/WithdrawOrder.php @@ -82,7 +82,6 @@ class WithdrawOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $byAdmin))); + return array_values(array_unique($ids)); } } diff --git a/app/admin/controller/user/User.php b/app/admin/controller/user/User.php index b3d4177..ad886ef 100644 --- a/app/admin/controller/user/User.php +++ b/app/admin/controller/user/User.php @@ -20,7 +20,7 @@ class User extends Backend */ protected ?object $model = null; - protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time']; + protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time', 'invite_code']; protected array $withJoinTable = ['channel', 'admin']; @@ -35,7 +35,7 @@ class User extends Backend } /** - * 添加(重写:password 使用 Admin 同款加密;uuid 由 username+channel_id 生成) + * 添加(重写:password 使用 Admin 同款加密;uuid 为 10 位唯一对外标识) * @throws Throwable */ protected function _add(): Response @@ -47,6 +47,10 @@ class User extends Backend } $data = $this->applyInputFilter($data); + $inviteResolved = $this->applyInviteCodeToUserChannel($data); + if ($inviteResolved !== null) { + return $inviteResolved; + } $data = $this->excludeFields($data); $password = $data['password'] ?? null; @@ -60,7 +64,7 @@ class User extends Backend if (!is_string($username) || trim($username) === '' || $channelId === null || $channelId === '') { return $this->error(__('Parameter %s can not be empty', ['username/channel_id'])); } - $data['uuid'] = md5(trim($username) . '|' . $channelId); + $data['uuid'] = \app\common\model\User::generateUniquePublicCode10(); if ($this->dataLimit && $this->dataLimitFieldAutoFill) { $data[$this->dataLimitField] = $this->auth->id; @@ -95,7 +99,7 @@ class User extends Backend } /** - * 编辑(重写:password 使用 Admin 同款加密;uuid 由 username+channel_id 生成) + * 编辑(重写:password 使用 Admin 同款加密;uuid 创建后不因改名改渠道自动变更) * @throws Throwable */ protected function _edit(): Response @@ -130,18 +134,6 @@ class User extends Backend } } - $nextUsername = array_key_exists('username', $data) ? $data['username'] : $row['username']; - $nextChannelId = null; - if (array_key_exists('channel_id', $data)) { - $nextChannelId = $data['channel_id']; - } else { - $nextChannelId = $row['channel_id'] ?? null; - } - - if (is_string($nextUsername) && trim($nextUsername) !== '' && $nextChannelId !== null && $nextChannelId !== '') { - $data['uuid'] = md5(trim($nextUsername) . '|' . $nextChannelId); - } - $result = false; $this->model->startTrans(); try { @@ -337,6 +329,63 @@ class User extends Backend return array_values(array_unique(array_merge($own, $children))); } + /** + * 请求中的 `invite_code`(子代理 admin.invite_code)解析为 `channel_id`,并写入 `register_invite_code`、`admin_id`。 + * 若同时提交 `channel_id` / `admin_id`,须与邀请码对应记录一致。 + */ + private function applyInviteCodeToUserChannel(array &$data): ?Response + { + if (!array_key_exists('invite_code', $data)) { + return null; + } + $raw = $data['invite_code']; + unset($data['invite_code']); + $inviteCode = is_string($raw) ? trim($raw) : ''; + if ($inviteCode === '') { + return null; + } + + $row = Db::name('admin')->field(['id', 'channel_id'])->where('invite_code', $inviteCode)->find(); + if (!$row) { + return $this->error(__('Invite code does not exist')); + } + $cid = $row['channel_id'] ?? null; + if ($cid === null || $cid === '') { + return $this->error(__('Invite code not bound to channel')); + } + $cidInt = intval(trim((string) $cid)); + if ($cidInt <= 0) { + return $this->error(__('Invite code not bound to channel')); + } + + if (isset($data['channel_id']) && $data['channel_id'] !== null && $data['channel_id'] !== '') { + $existing = intval(trim((string) $data['channel_id'])); + if ($existing > 0 && $existing !== $cidInt) { + return $this->error(__('Parameter error')); + } + } + $data['channel_id'] = $cidInt; + $data['register_invite_code'] = $inviteCode; + + $aidRaw = $row['id'] ?? null; + if ($aidRaw === null || $aidRaw === '') { + return $this->error(__('Parameter error')); + } + $aidInt = intval(trim((string) $aidRaw)); + if ($aidInt <= 0) { + return $this->error(__('Parameter error')); + } + if (isset($data['admin_id']) && $data['admin_id'] !== null && $data['admin_id'] !== '') { + $reqAid = intval(trim((string) $data['admin_id'])); + if ($reqAid > 0 && $reqAid !== $aidInt) { + return $this->error(__('Parameter error')); + } + } + $data['admin_id'] = $aidInt; + + return null; + } + /** * 若需重写查看、编辑、删除等方法,请复制 @see \app\admin\library\traits\Backend 中对应的方法至此进行重写 */ diff --git a/app/admin/controller/record/UserWalletRecord.php b/app/admin/controller/user/UserWalletRecord.php similarity index 94% rename from app/admin/controller/record/UserWalletRecord.php rename to app/admin/controller/user/UserWalletRecord.php index eb6537d..de67024 100644 --- a/app/admin/controller/record/UserWalletRecord.php +++ b/app/admin/controller/user/UserWalletRecord.php @@ -1,6 +1,6 @@ where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $byAdmin))); + return array_values(array_unique($ids)); } } diff --git a/app/api/controller/Account.php b/app/api/controller/Account.php index c3e39e6..f09e16d 100644 --- a/app/api/controller/Account.php +++ b/app/api/controller/Account.php @@ -45,6 +45,7 @@ class Account extends Frontend 'code' => 1, 'message' => __('ok'), 'data' => [ + 'uuid' => $user->uuid ?? '', 'username' => $user->username, 'head_image' => $user->avatar ?? '', 'coin' => $user->coin, diff --git a/app/api/controller/Auth.php b/app/api/controller/Auth.php index 99eb532..fef7960 100644 --- a/app/api/controller/Auth.php +++ b/app/api/controller/Auth.php @@ -34,6 +34,9 @@ class Auth extends MobileBase if ($username === '' || $password === '') { return $this->mobileError(1001, 'Missing parameters'); } + if ($inviteCode === '') { + return $this->mobileError(1001, 'Invite code required'); + } if (!preg_match('/^1[3-9]\d{9}$/', $username)) { return $this->mobileError(1003, 'Please enter the correct mobile number'); } @@ -41,19 +44,33 @@ class Auth extends MobileBase $phone = $username; $email = ''; - $extend = []; - if ($inviteCode !== '') { - $inviterAdmin = Db::name('admin')->field(['id', 'channel_id'])->where('invite_code', $inviteCode)->find(); - if (!$inviterAdmin) { - return $this->mobileError(2002, 'Invite code does not exist'); - } - $extend['register_invite_code'] = $inviteCode; - $extend['admin_id'] = $inviterAdmin['id']; - $extend['channel_id'] = $inviterAdmin['channel_id'] ?? null; + if (User::where('username', $username)->find() || User::where('phone', $username)->find()) { + return $this->mobileError(2003, 'Account already registered', [ + 'already_registered' => true, + ]); } + $extend = []; + $inviterAdmin = Db::name('admin')->field(['id', 'channel_id'])->where('invite_code', $inviteCode)->find(); + if (!$inviterAdmin) { + return $this->mobileError(2002, 'Invite code does not exist'); + } + $extend['register_invite_code'] = $inviteCode; + $extend['admin_id'] = $inviterAdmin['id']; + $channelId = $inviterAdmin['channel_id'] ?? null; + if ($channelId === null || $channelId === '' || (int) $channelId <= 0) { + return $this->mobileError(2002, 'Invite code not bound to channel'); + } + $extend['channel_id'] = (int) $channelId; + $registered = $this->auth->register($username, $password, $phone, $email, 1, $extend); if (!$registered) { + $dup = $this->auth->getRegisterDuplicateKind(); + if ($dup === 'username' || $dup === 'email' || $dup === 'phone') { + return $this->mobileError(2003, 'Account already registered', [ + 'already_registered' => true, + ]); + } return $this->mobileError(2000, (string) $this->auth->getError()); } @@ -122,6 +139,7 @@ class Auth extends MobileBase 'expires_in' => config('buildadmin.user_token_keep_time', 259200), 'user' => [ 'username' => $userInfo['username'] ?? '', + 'uuid' => $userInfo['uuid'] ?? '', 'coin' => $userInfo['coin'] ?? '0.0000', 'channel_id' => $userInfo['channel_id'] ?? null, 'risk_flags' => $userInfo['risk_flags'] ?? 0, diff --git a/app/api/controller/Game.php b/app/api/controller/Game.php index 4d016cc..df492bb 100644 --- a/app/api/controller/Game.php +++ b/app/api/controller/Game.php @@ -54,7 +54,7 @@ class Game extends MobileBase 'open_at' => $openAt, ], 'bet_config' => [ - 'max_select_count' => $this->intValue($this->getConfigValue('max_select_count', '5')), + 'pick_max_number_count' => $this->getPickMaxNumberCount(), 'chips' => ['1.0000', '5.0000', '10.0000', '25.0000', '50.0000', '100.0000'], 'single_number_max_bet' => $this->getConfigValue('single_number_max_bet', '500.0000'), ], @@ -140,13 +140,18 @@ class Game extends MobileBase return $response; } $periodNo = trim((string) $request->post('period_no', '')); - $numbers = $request->post('numbers', []); + $numbersRaw = $request->post('numbers', ''); $betAmount = (string) $request->post('bet_amount', ''); $idempotencyKey = trim((string) $request->post('idempotency_key', '')); - if ($periodNo === '' || !is_array($numbers) || $betAmount === '' || $idempotencyKey === '') { + if ($periodNo === '' || $betAmount === '' || $idempotencyKey === '') { return $this->mobileError(1001, 'Missing parameters'); } - if (count($numbers) < 1) { + $numbers = $this->parseBetNumbersFromRequest($numbersRaw); + if ($numbers === []) { + return $this->mobileError(1003, 'Invalid parameter value'); + } + $maxSelect = $this->getPickMaxNumberCount(); + if (count($numbers) > $maxSelect) { return $this->mobileError(1003, 'Invalid parameter value'); } @@ -285,6 +290,49 @@ class Game extends MobileBase ]); } + /** + * 下注号码:`numbers` 为逗号分隔字符串(如 `1,8,16`);兼容旧版 JSON 数组。 + * + * @return list + */ + private function parseBetNumbersFromRequest($numbersRaw): array + { + if (is_array($numbersRaw)) { + $out = []; + foreach ($numbersRaw as $v) { + $n = filter_var($v, FILTER_VALIDATE_INT); + if ($n === false || $n < 1 || $n > 36) { + return []; + } + $out[] = $n; + } + $out = array_values(array_unique($out)); + sort($out); + + return $out; + } + $raw = trim((string) $numbersRaw); + if ($raw === '') { + return []; + } + $parts = preg_split('/\s*,\s*/', $raw); + $out = []; + foreach ($parts as $p) { + if ($p === '') { + continue; + } + $n = filter_var($p, FILTER_VALIDATE_INT); + if ($n === false || $n < 1 || $n > 36) { + return []; + } + $out[] = $n; + } + $out = array_values(array_unique($out)); + sort($out); + + return $out; + } + private function mapPeriodStatus($status): string { if ($this->intValue($status) === 0) { @@ -299,6 +347,22 @@ class Game extends MobileBase return 'finished'; } + /** + * 单注最多可选号码个数:`game_config.config_key = pick_max_number_count` + */ + private function getPickMaxNumberCount(): int + { + $v = $this->intValue($this->getConfigValue('pick_max_number_count', '10')); + if ($v < 1) { + return 1; + } + if ($v > 36) { + return 36; + } + + return $v; + } + private function getConfigValue(string $key, string $default): string { $value = GameConfig::where('config_key', $key)->value('config_value'); diff --git a/app/api/controller/User.php b/app/api/controller/User.php index 66abaa1..58e304c 100644 --- a/app/api/controller/User.php +++ b/app/api/controller/User.php @@ -81,11 +81,15 @@ class User extends Frontend ->where('invite_code', $params['invite_code']) ->find(); if (!$inviterAdmin) { - return $this->error(__('Parameter error')); + return $this->error(__('Invite code does not exist')); + } + $ch = $inviterAdmin['channel_id'] ?? null; + if ($ch === null || $ch === '' || intval(trim((string) $ch)) <= 0) { + return $this->error(__('Invite code not bound to channel')); } $extend['register_invite_code'] = $params['invite_code']; - $extend['inviter_admin_id'] = $inviterAdmin['id']; - $extend['channel_id'] = $inviterAdmin['channel_id'] ?? null; + $extend['admin_id'] = $inviterAdmin['id']; + $extend['channel_id'] = intval(trim((string) $ch)); } $res = $this->auth->register($params['username'], $params['password'], $params['mobile'], $params['email'], 1, $extend); } @@ -96,6 +100,10 @@ class User extends Frontend 'routePath' => '/user' ]); } + $dup = $this->auth->getRegisterDuplicateKind(); + if ($params['tab'] === 'register' && ($dup === 'username' || $dup === 'email' || $dup === 'phone')) { + return $this->error(__('Account already registered')); + } $msg = $this->auth->getError(); return $this->error($msg ?: __('Check in failed, please try again or contact the website administrator~')); } diff --git a/app/api/lang/en.php b/app/api/lang/en.php index ba9c42f..3b9ffbe 100644 --- a/app/api/lang/en.php +++ b/app/api/lang/en.php @@ -23,6 +23,9 @@ return [ 'Invalid timestamp' => 'Invalid timestamp', 'Invite code does not exist' => 'Invite code does not exist', 'Register only supports phone' => 'Register only supports phone', + 'Invite code required' => 'Invite code is required', + 'Invite code not bound to channel' => 'This invite code is not bound to a valid channel', + 'Account already registered' => 'This phone number is already registered. Please sign in.', 'Please enter the correct mobile number' => 'Please enter the correct mobile number', 'Registered successfully but login failed' => 'Registered successfully but login failed', 'Incorrect account or password' => 'Incorrect account or password', diff --git a/app/api/lang/zh-cn.php b/app/api/lang/zh-cn.php index 115b432..fa2de12 100644 --- a/app/api/lang/zh-cn.php +++ b/app/api/lang/zh-cn.php @@ -55,6 +55,9 @@ return [ 'Invalid timestamp' => '时间戳无效', 'Invite code does not exist' => '邀请码不存在', 'Register only supports phone' => '注册仅支持手机号', + 'Invite code required' => '请填写邀请码', + 'Invite code not bound to channel' => '该邀请码未绑定有效渠道', + 'Account already registered' => '该手机号已注册,请直接登录', 'Please enter the correct mobile number' => '请输入正确的手机号', 'Registered successfully but login failed' => '注册成功但登录失败', 'Incorrect account or password' => '账号或密码错误', diff --git a/app/common/library/Auth.php b/app/common/library/Auth.php index 3a20a67..a7aa808 100644 --- a/app/common/library/Auth.php +++ b/app/common/library/Auth.php @@ -27,7 +27,14 @@ class Auth extends \ba\Auth protected int $keepTime = 86400; protected int $refreshTokenKeepTime = 2592000; - protected array $allowFields = ['id', 'username', 'nickname', 'email', 'mobile', 'avatar', 'gender', 'birthday', 'money', 'score', 'join_time', 'motto', 'last_login_time', 'last_login_ip']; + /** 注册失败原因:`username`/`email` 表示账号已占用,供接口返回友好提示 */ + protected string $registerDuplicateKind = ''; + + protected array $allowFields = [ + 'id', 'username', 'nickname', 'email', 'phone', 'avatar', 'gender', 'birthday', + 'coin', 'channel_id', 'risk_flags', 'uuid', + 'join_time', 'motto', 'last_login_time', 'last_login_ip', + ]; public function __construct(array $config = []) { @@ -73,7 +80,7 @@ class Auth extends \ba\Auth return false; } $this->token = $token; - $this->loginSuccessful(); + $this->loginEd = true; return true; } } @@ -84,6 +91,7 @@ class Auth extends \ba\Auth public function register(string $username, string $password = '', string $phone = '', string $email = '', int $group = 1, array $extend = []): bool { + $this->registerDuplicateKind = ''; $request = function_exists('request') ? request() : null; $ip = $request ? $request->getRealIp() : '0.0.0.0'; @@ -98,13 +106,20 @@ class Auth extends \ba\Auth return false; } if (User::where('email', $email)->find() && $email) { + $this->registerDuplicateKind = 'email'; $this->setError(__('Email') . ' ' . __('already exists')); return false; } if (User::where('username', $username)->find()) { + $this->registerDuplicateKind = 'username'; $this->setError(__('Username') . ' ' . __('already exists')); return false; } + if ($phone !== '' && User::where('phone', $phone)->find()) { + $this->registerDuplicateKind = 'phone'; + $this->setError(__('Mobile') . ' ' . __('already exists')); + return false; + } $nickname = preg_replace_callback('/1[3-9]\d{9}/', fn($m) => substr($m[0], 0, 3) . '****' . substr($m[0], 7), $username); $time = time(); @@ -116,6 +131,8 @@ class Auth extends \ba\Auth 'last_login_ip' => $ip, 'last_login_time' => $time, 'status' => 1, + 'uuid' => User::generateUniquePublicCode10(), + 'remark' => User::formatLoginRemark($time, $ip), ]; $data = array_merge(compact('username', 'password', 'phone', 'email'), $data, $extend); @@ -215,6 +232,7 @@ class Auth extends \ba\Auth $this->model->login_failure = 0; $this->model->last_login_time = time(); $this->model->last_login_ip = $ip; + $this->model->remark = User::formatLoginRemark(time(), $ip); $this->model->save(); $this->loginEd = true; $this->model->commit(); @@ -344,6 +362,11 @@ class Auth extends \ba\Auth return $this->error ? __($this->error) : ''; } + public function getRegisterDuplicateKind(): string + { + return $this->registerDuplicateKind; + } + protected function reset(bool $deleteToken = true): bool { if ($deleteToken && $this->token) { @@ -353,6 +376,7 @@ class Auth extends \ba\Auth $this->loginEd = false; $this->model = null; $this->refreshToken = ''; + $this->registerDuplicateKind = ''; $this->setError(''); $this->setKeepTime((int)config('buildadmin.user_token_keep_time', 86400)); return true; diff --git a/app/common/model/Channel.php b/app/common/model/Channel.php index bb4836f..0484127 100644 --- a/app/common/model/Channel.php +++ b/app/common/model/Channel.php @@ -29,14 +29,4 @@ class Channel extends Model { return is_null($value) ? null : (float)$value; } - - public function adminGroup(): \think\model\relation\BelongsTo - { - return $this->belongsTo(\app\admin\model\AdminGroup::class, 'admin_group_id', 'id'); - } - - public function admin(): \think\model\relation\BelongsTo - { - return $this->belongsTo(\app\admin\model\Admin::class, 'admin_id', 'id'); - } } diff --git a/app/common/model/GameRecord.php b/app/common/model/GameRecord.php index d7be5ad..fd6b5da 100644 --- a/app/common/model/GameRecord.php +++ b/app/common/model/GameRecord.php @@ -11,13 +11,15 @@ class GameRecord extends Model protected $autoWriteTimestamp = true; protected $type = [ - 'create_time' => 'integer', - 'update_time' => 'integer', - 'period_start_at' => 'integer', - 'status' => 'integer', - 'draw_mode' => 'integer', - 'preset_number' => 'integer', - 'result_number' => 'integer', + 'create_time' => 'integer', + 'update_time' => 'integer', + 'period_start_at' => 'integer', + 'status' => 'integer', + 'draw_mode' => 'integer', + 'preset_number' => 'integer', + 'result_number' => 'integer', + 'platform_profit_amount' => 'string', + 'winner_user_count' => 'integer', ]; public function setPeriodStartAtAttr($value, $data = []) diff --git a/app/common/model/User.php b/app/common/model/User.php index 909a91e..159b185 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -13,6 +13,30 @@ class User extends Model protected $autoWriteTimestamp = true; + /** + * 生成 10 位唯一对外标识(大写字母与数字,排除易混淆字符) + */ + public static function generateUniquePublicCode10(): string + { + $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + $len = strlen($chars); + for ($attempt = 0; $attempt < 80; $attempt++) { + $code = ''; + for ($i = 0; $i < 10; $i++) { + $code .= $chars[random_int(0, $len - 1)]; + } + if (!self::where('uuid', $code)->find()) { + return $code; + } + } + throw new \RuntimeException('Failed to generate unique user uuid'); + } + + public static function formatLoginRemark(int $timestamp, string $ip): string + { + return '最后登录:' . date('Y-m-d H:i:s', $timestamp) . ' IP:' . $ip; + } + protected $type = [ 'create_time' => 'integer', 'update_time' => 'integer', diff --git a/app/common/service/GameBetSettleService.php b/app/common/service/GameBetSettleService.php new file mode 100644 index 0000000..823d666 --- /dev/null +++ b/app/common/service/GameBetSettleService.php @@ -0,0 +1,172 @@ +where('period_id', $recordId) + ->where('status', 1) + ->order('id', 'asc') + ->select() + ->toArray(); + + foreach ($bets as $bet) { + $betId = (int) ($bet['id'] ?? 0); + if ($betId <= 0) { + continue; + } + + $win = self::computeWinAmount($bet, $resultNumber); + $jackpot = '0.0000'; + + $affected = Db::name('bet_order') + ->where('id', $betId) + ->where('status', 1) + ->update([ + 'win_amount' => $win, + 'jackpot_extra_amount' => $jackpot, + 'status' => 2, + 'update_time' => $now, + ]); + + if ($affected === 0) { + continue; + } + + if (bccomp($win, '0', 4) <= 0) { + continue; + } + + self::creditUserPayout($bet, $betId, $win, $now); + } + } + + /** + * 补偿:库中已结束局次但注单仍为待开奖的,可重复调用(幂等)。 + */ + public static function settlePendingForEndedRecords(): int + { + $rows = Db::name('game_record') + ->where('status', 4) + ->whereNotNull('result_number') + ->field(['id', 'result_number']) + ->order('id', 'asc') + ->select() + ->toArray(); + + $count = 0; + foreach ($rows as $row) { + $rid = (int) ($row['id'] ?? 0); + $rn = (int) ($row['result_number'] ?? 0); + if ($rid <= 0 || $rn < 1) { + continue; + } + $pending = Db::name('bet_order') + ->where('period_id', $rid) + ->where('status', 1) + ->count(); + if ($pending === 0) { + continue; + } + Db::startTrans(); + try { + self::settleBetsForDraw($rid, $rn); + Db::commit(); + $count++; + } catch (Throwable $e) { + Db::rollback(); + throw $e; + } + } + + return $count; + } + + /** + * 单注应付派彩:命中开奖号码时 unit × (连胜+1) × 33(与 GameLiveService 一致)。 + */ + public static function computeWinAmount(array $bet, int $resultNumber): string + { + $pickNumbers = $bet['pick_numbers'] ?? null; + if (is_string($pickNumbers)) { + $decoded = json_decode($pickNumbers, true); + $pickNumbers = is_array($decoded) ? $decoded : []; + } + if (!is_array($pickNumbers)) { + $pickNumbers = []; + } + if (!in_array($resultNumber, array_map('intval', $pickNumbers), true)) { + return '0.0000'; + } + $unit = (string) ($bet['unit_amount'] ?? '0'); + $streak = (int) ($bet['streak_at_bet'] ?? 0); + $odds = (string) (($streak + 1) * self::BASE_ODDS); + + return bcmul($unit, $odds, 4); + } + + private static function creditUserPayout(array $bet, int $betId, string $winAmount, int $now): void + { + $userId = (int) ($bet['user_id'] ?? 0); + if ($userId <= 0) { + return; + } + + $idem = 'payout_bet_' . $betId; + if (Db::name('user_wallet_record')->where('idempotency_key', $idem)->value('id')) { + return; + } + + $user = Db::name('user')->where('id', $userId)->find(); + if (!$user) { + return; + } + + $before = (string) ($user['coin'] ?? '0'); + $after = bcadd($before, $winAmount, 4); + + Db::name('user_wallet_record')->insert([ + 'user_id' => $userId, + 'channel_id' => $bet['channel_id'] ?? null, + 'biz_type' => 'payout', + 'direction' => 1, + 'amount' => $winAmount, + 'balance_before' => $before, + 'balance_after' => $after, + 'ref_type' => 'bet_order', + 'ref_id' => $betId, + 'idempotency_key' => $idem, + 'operator_admin_id' => null, + 'remark' => '压注派彩', + 'create_time' => $now, + ]); + + Db::name('user')->where('id', $userId)->update([ + 'coin' => $after, + 'update_time' => $now, + ]); + } +} diff --git a/app/common/service/GameLiveService.php b/app/common/service/GameLiveService.php index 7f135e0..c56868b 100644 --- a/app/common/service/GameLiveService.php +++ b/app/common/service/GameLiveService.php @@ -17,6 +17,9 @@ final class GameLiveService private const KEY_BET_SECONDS = 'bet_seconds'; private const KEY_PICK_MAX_NUMBER_COUNT = 'pick_max_number_count'; + /** 开奖结果号码池:1 至此上限(与单注可选号码个数配置无关) */ + private const DRAW_NUMBER_MAX = 36; + public static function buildSnapshot(?int $recordId = null): array { $record = self::resolveRecord($recordId); @@ -30,6 +33,7 @@ final class GameLiveService 'period_seconds' => self::getConfigInt(self::KEY_PERIOD_SECONDS, 30), 'bet_seconds' => self::getConfigInt(self::KEY_BET_SECONDS, 20), 'pick_max_number_count' => self::getPickMaxNumberCount(), + 'draw_number_max' => self::DRAW_NUMBER_MAX, 'remaining_seconds' => 0, 'bet_remaining_seconds' => 0, 'can_calculate' => false, @@ -59,7 +63,7 @@ final class GameLiveService $status = (int) $record['status']; $canCalculate = $elapsed >= $betSeconds && ($status === 0 || $status === 1); if ($canCalculate) { - for ($n = 1; $n <= $pickMax; $n++) { + for ($n = 1; $n <= self::DRAW_NUMBER_MAX; $n++) { $loss = self::estimateLossForNumber($bets, $n); $candidates[] = [ 'number' => $n, @@ -97,6 +101,7 @@ final class GameLiveService 'period_seconds' => $periodSeconds, 'bet_seconds' => $betSeconds, 'pick_max_number_count' => $pickMax, + 'draw_number_max' => self::DRAW_NUMBER_MAX, 'remaining_seconds' => $remaining, 'bet_remaining_seconds' => $betRemaining, 'can_calculate' => $canCalculate, @@ -129,7 +134,7 @@ final class GameLiveService } $pickMax = self::getPickMaxNumberCount(); - if ($manualNumber !== null && ($manualNumber < 1 || $manualNumber > $pickMax)) { + if ($manualNumber !== null && ($manualNumber < 1 || $manualNumber > self::DRAW_NUMBER_MAX)) { return ['ok' => false, 'msg' => '手动开奖号码超出允许范围']; } @@ -138,7 +143,7 @@ final class GameLiveService $bestNumber = null; $bestLoss = null; $bestNumbers = []; - for ($n = 1; $n <= $pickMax; $n++) { + for ($n = 1; $n <= self::DRAW_NUMBER_MAX; $n++) { $loss = self::estimateLossForNumber($bets, $n); $candidates[] = ['number' => $n, 'estimated_loss' => $loss]; if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 4) < 0) { @@ -165,6 +170,7 @@ final class GameLiveService 'period_seconds' => $periodSeconds, 'bet_seconds' => $betSeconds, 'pick_max_number_count' => $pickMax, + 'draw_number_max' => self::DRAW_NUMBER_MAX, 'candidate_numbers' => $candidates, 'ai_default_number' => $bestNumber, 'final_number' => $finalNumber, @@ -189,8 +195,10 @@ final class GameLiveService 'draw_mode' => $manualNumber === null ? 0 : 1, 'update_time' => $now, ]); + GameBetSettleService::settleBetsForDraw((int) $record['id'], $finalNumber); GameRecordService::createNextRecordAfterDraw(); Db::commit(); + GameRecordStatService::refreshForRecordId((int) $record['id']); } catch (Throwable $e) { Db::rollback(); return ['ok' => false, 'msg' => $e->getMessage()]; diff --git a/app/common/service/GameRecordStatService.php b/app/common/service/GameRecordStatService.php new file mode 100644 index 0000000..577a270 --- /dev/null +++ b/app/common/service/GameRecordStatService.php @@ -0,0 +1,106 @@ +where('id', $recordId)->find(); + if (!$record) { + return; + } + $status = (int) ($record['status'] ?? 0); + $now = time(); + + if ($status !== 4) { + Db::name('game_record')->where('id', $recordId)->update([ + 'platform_profit_amount' => '0.0000', + 'winner_user_count' => 0, + 'update_time' => $now, + ]); + + return; + } + + $resultRaw = $record['result_number'] ?? null; + if ($resultRaw === null || $resultRaw === '') { + return; + } + $resultNum = (int) $resultRaw; + + $bets = Db::name('bet_order')->where('period_id', $recordId)->select()->toArray(); + $totalBet = '0.0000'; + $totalPayout = '0.0000'; + $winnerUserIds = []; + + foreach ($bets as $bet) { + $st = (int) ($bet['status'] ?? 0); + if ($st === 3) { + continue; + } + $tb = (string) ($bet['total_amount'] ?? '0'); + $totalBet = bcadd($totalBet, $tb, 4); + + if ($st === 2) { + $payout = bcadd((string) ($bet['win_amount'] ?? '0'), (string) ($bet['jackpot_extra_amount'] ?? '0'), 4); + } else { + $payout = self::estimatePayoutForBet($bet, $resultNum); + } + + $totalPayout = bcadd($totalPayout, $payout, 4); + if (bccomp($payout, '0', 4) > 0) { + $uid = (int) ($bet['user_id'] ?? 0); + if ($uid > 0) { + $winnerUserIds[$uid] = true; + } + } + } + + $profit = bcsub($totalBet, $totalPayout, 4); + + Db::name('game_record')->where('id', $recordId)->update([ + 'platform_profit_amount' => $profit, + 'winner_user_count' => count($winnerUserIds), + 'update_time' => $now, + ]); + } + + /** + * 与 GameLiveService::estimateLossForNumber 中单注派彩一致:命中号码时 unit × (streak+1) × 33。 + */ + private static function estimatePayoutForBet(array $bet, int $resultNumber): string + { + $pickNumbers = $bet['pick_numbers'] ?? null; + if (is_string($pickNumbers)) { + $decoded = json_decode($pickNumbers, true); + $pickNumbers = is_array($decoded) ? $decoded : []; + } + if (!is_array($pickNumbers)) { + $pickNumbers = []; + } + if (!in_array($resultNumber, array_map('intval', $pickNumbers), true)) { + return '0.0000'; + } + $unit = (string) ($bet['unit_amount'] ?? '0'); + $streak = (int) ($bet['streak_at_bet'] ?? 0); + $odds = (string) (($streak + 1) * self::BASE_ODDS); + + return bcmul($unit, $odds, 4); + } +} diff --git a/app/common/validate/Channel.php b/app/common/validate/Channel.php index 7a5517b..e1e4f77 100644 --- a/app/common/validate/Channel.php +++ b/app/common/validate/Channel.php @@ -15,12 +15,11 @@ class Channel extends Validate 'name' => 'require|max:255', 'agent_mode' => 'require|in:turnover,affiliate', 'status' => 'in:0,1', - 'admin_id' => 'require|integer|gt:0', 'remark' => 'max:255', ]; protected $scene = [ - 'add' => ['code', 'name', 'agent_mode', 'status', 'admin_id', 'remark'], - 'edit' => ['name', 'agent_mode', 'status', 'admin_id', 'remark'], + 'add' => ['code', 'name', 'agent_mode', 'status', 'remark'], + 'edit' => ['name', 'agent_mode', 'status', 'remark'], ]; } diff --git a/web/src/lang/backend/en/auth/group.ts b/web/src/lang/backend/en/auth/group.ts index e85b712..3166b51 100644 --- a/web/src/lang/backend/en/auth/group.ts +++ b/web/src/lang/backend/en/auth/group.ts @@ -1,6 +1,13 @@ export default { GroupName: 'Group Name', 'Group name': 'Group Name', + channel_id: 'Channel', + channel_name: 'Channel name', + channel_admin: 'Channel admin', + channel_auto_bind: 'Will bind to the current account channel automatically', + channel_inherit_hint: + 'Sub groups do not pick a channel separately: saving uses the parent group channel; changing parent syncs automatically.', + system_group_no_channel: 'System (no channel)', commission_rate: 'Commission rate (%)', commission_rate_desc_title: 'Group commission notes', commission_rate_desc_1: 'The total group commission rate under the same parent cannot exceed 100%.', diff --git a/web/src/lang/backend/en/channel.ts b/web/src/lang/backend/en/channel.ts index 16cae8a..56715c7 100644 --- a/web/src/lang/backend/en/channel.ts +++ b/web/src/lang/backend/en/channel.ts @@ -58,6 +58,7 @@ export default { admingroup__name: 'name', admin_id: 'admin_id', admin_tree_tip: 'Admins are grouped by channel. Pick a leaf under a channel name. One channel maps to one admin.', + admin_select_tip: 'Only administrators within your data permission scope are listed; search by username.', manual_settle: 'Manual settle', manual_settle_confirm: 'Confirm trigger manual settlement for this channel?', manual_settle_settlement_no: 'Settlement No.', @@ -70,8 +71,8 @@ export default { manual_settle_calc_base: 'Settlement base', manual_settle_commission_amount: 'Commission amount', manual_settle_remark: 'Remark', - admin_id_placeholder: 'Select a channel admin account', - admin__username: 'username', + admin_id_placeholder: 'Select an admin (within your permission scope)', + admin__username: 'Person in charge', create_time: 'create_time', update_time: 'update_time', 'quick Search Fields': 'id,code,name', diff --git a/web/src/lang/backend/en/game/period.ts b/web/src/lang/backend/en/game/period.ts deleted file mode 100644 index eff4eb8..0000000 --- a/web/src/lang/backend/en/game/period.ts +++ /dev/null @@ -1,28 +0,0 @@ -export default { - 'quick Search Fields': 'Period No. / ID', - id: 'ID', - period_no: 'Period No.', - period_start_at: 'Start time', - status: 'Status', - 'status 0': 'Betting open', - 'status 1': 'Closed', - 'status 2': 'Settling', - 'status 3': 'Paying', - 'status 4': 'Ended', - 'status 5': 'Void', - draw_mode: 'Draw mode', - 'draw_mode 0': 'Auto AI', - 'draw_mode 1': 'Manual preset', - preset_number: 'Preset number', - result_number: 'Result number', - void_reason: 'Void reason', - create_time: 'Created', - update_time: 'Updated', - section_auto: 'Auto draw & new period', - auto_create_label: 'Allow auto-create next period', - auto_create_tip: 'When enabled, a background ticker inserts a new period if none is in progress', - manual_create_label: 'Allow manual create next period', - manual_create_tip: 'When enabled, the button below can create the next period', - btn_create_next: 'Create next period (manual)', - saving: 'Saving…', -} diff --git a/web/src/lang/backend/en/game/record.ts b/web/src/lang/backend/en/game/record.ts index 25c9304..0a768b3 100644 --- a/web/src/lang/backend/en/game/record.ts +++ b/web/src/lang/backend/en/game/record.ts @@ -15,6 +15,8 @@ export default { 'draw_mode 1': 'Manual preset', preset_number: 'Preset number', result_number: 'Result number', + platform_profit_amount: 'Round P/L (platform)', + winner_user_count: 'Winners', void_reason: 'Void reason', create_time: 'Created', update_time: 'Updated', diff --git a/web/src/lang/backend/en/record/userWalletRecord.ts b/web/src/lang/backend/en/user/userWalletRecord.ts similarity index 100% rename from web/src/lang/backend/en/record/userWalletRecord.ts rename to web/src/lang/backend/en/user/userWalletRecord.ts diff --git a/web/src/lang/backend/zh-cn/auth/group.ts b/web/src/lang/backend/zh-cn/auth/group.ts index f1190ca..5a2c16c 100644 --- a/web/src/lang/backend/zh-cn/auth/group.ts +++ b/web/src/lang/backend/zh-cn/auth/group.ts @@ -1,6 +1,12 @@ export default { GroupName: '组名', 'Group name': '组别名称', + channel_id: '所属渠道', + channel_name: '渠道名称', + channel_admin: '渠道管理员', + channel_auto_bind: '将自动绑定为当前账号所属渠道', + channel_inherit_hint: '子级不单独选渠道:保存时将使用上级分组对应渠道,变更上级时会自动同步。', + system_group_no_channel: '系统级(未绑定渠道)', commission_rate: '分红比例(%)', commission_rate_desc_title: '角色组分红说明', commission_rate_desc_1: '同一父级下角色组分红比例总和不能超过100%。', diff --git a/web/src/lang/backend/zh-cn/channel.ts b/web/src/lang/backend/zh-cn/channel.ts index 764a86d..518003c 100644 --- a/web/src/lang/backend/zh-cn/channel.ts +++ b/web/src/lang/backend/zh-cn/channel.ts @@ -58,6 +58,7 @@ export default { admingroup__name: '组名', admin_id: '管理员', admin_tree_tip: '按渠道分组展示可关联的管理员账号,请在「渠道名称」下选择具体管理员(叶子节点)。渠道负责人仅对应一名管理员。', + admin_select_tip: '仅列出当前账号数据权限范围内的管理员,支持搜索用户名。', manual_settle: '手动结算', manual_settle_confirm: '确认触发当前渠道手动结算?', manual_settle_settlement_no: '结算单号', @@ -70,8 +71,8 @@ export default { manual_settle_calc_base: '结算基数', manual_settle_commission_amount: '佣金金额', manual_settle_remark: '备注', - admin_id_placeholder: '请选择渠道下的管理员账号', - admin__username: '用户名', + admin_id_placeholder: '请选择管理员(仅当前权限范围内)', + admin__username: '负责人', create_time: '创建时间', update_time: '修改时间', 'quick Search Fields': 'ID、渠道标识、渠道名', diff --git a/web/src/lang/backend/zh-cn/game/period.ts b/web/src/lang/backend/zh-cn/game/period.ts deleted file mode 100644 index 59f0c86..0000000 --- a/web/src/lang/backend/zh-cn/game/period.ts +++ /dev/null @@ -1,28 +0,0 @@ -export default { - 'quick Search Fields': '期号/ID', - id: 'ID', - period_no: '期号', - period_start_at: '开始时间', - status: '状态', - 'status 0': '下注开放', - 'status 1': '已封盘', - 'status 2': '算票中', - 'status 3': '派彩中', - 'status 4': '已结束', - 'status 5': '已作废', - draw_mode: '开奖方式', - 'draw_mode 0': '自动AI', - 'draw_mode 1': '手动预设', - preset_number: '预设号码', - result_number: '开奖号码', - void_reason: '作废原因', - create_time: '创建时间', - update_time: '更新时间', - section_auto: '自动开奖与新建期', - auto_create_label: '允许自动创建下一期', - auto_create_tip: '开启后由后台定时任务在无进行中期号时自动插入新期', - manual_create_label: '允许手动创建下一期', - manual_create_tip: '开启后可在本页使用「手动创建下一期」按钮', - btn_create_next: '手动创建下一期', - saving: '保存中…', -} diff --git a/web/src/lang/backend/zh-cn/game/record.ts b/web/src/lang/backend/zh-cn/game/record.ts index c657aba..11fc0fa 100644 --- a/web/src/lang/backend/zh-cn/game/record.ts +++ b/web/src/lang/backend/zh-cn/game/record.ts @@ -15,6 +15,8 @@ export default { 'draw_mode 1': '手动预设', preset_number: '预设号码', result_number: '开奖号码', + platform_profit_amount: '对局盈亏(平台)', + winner_user_count: '中奖人数', void_reason: '作废原因', create_time: '创建时间', update_time: '更新时间', diff --git a/web/src/lang/backend/zh-cn/game/user.ts b/web/src/lang/backend/zh-cn/game/user.ts index 70f299d..d7398dd 100644 --- a/web/src/lang/backend/zh-cn/game/user.ts +++ b/web/src/lang/backend/zh-cn/game/user.ts @@ -2,7 +2,7 @@ export default { id: 'ID', username: '用户名', password: '密码', - uuid: '用户唯一标识', + uuid: 'uuid', phone: '手机号', email: '邮箱', email_placeholder: '可选,与手机号二选一注册时填写', diff --git a/web/src/lang/backend/zh-cn/user/user.ts b/web/src/lang/backend/zh-cn/user/user.ts index 70f299d..dc0dfae 100644 --- a/web/src/lang/backend/zh-cn/user/user.ts +++ b/web/src/lang/backend/zh-cn/user/user.ts @@ -1,14 +1,14 @@ -export default { +export default { id: 'ID', username: '用户名', password: '密码', - uuid: '用户唯一标识', + uuid: 'uuid', phone: '手机号', email: '邮箱', email_placeholder: '可选,与手机号二选一注册时填写', head_image: '头像', remark: '备注', - coin: '游戏币余额', + coin: '余额', coin_placeholder: 'decimal(18,4),禁止业务用浮点存库', total_deposit_coin: '累计充值(币)', total_valid_bet_coin: '累计有效投注(币)', diff --git a/web/src/lang/backend/zh-cn/record/userWalletRecord.ts b/web/src/lang/backend/zh-cn/user/userWalletRecord.ts similarity index 100% rename from web/src/lang/backend/zh-cn/record/userWalletRecord.ts rename to web/src/lang/backend/zh-cn/user/userWalletRecord.ts diff --git a/web/src/views/backend/auth/admin/index.vue b/web/src/views/backend/auth/admin/index.vue index b897825..b2e7ad5 100644 --- a/web/src/views/backend/auth/admin/index.vue +++ b/web/src/views/backend/auth/admin/index.vue @@ -89,10 +89,16 @@ const baTable = new baTableClass( label: t('State'), prop: 'status', align: 'center', - render: 'tag', - effect: 'dark', - custom: { disable: 'danger', enable: 'success' }, + operator: 'eq', + sortable: false, + render: 'switch', replaceValue: { disable: t('Disable'), enable: t('Enable') }, + customRenderAttr: { + switch: () => ({ + activeValue: 'enable', + inactiveValue: 'disable', + }), + }, }, { label: t('Operate'), diff --git a/web/src/views/backend/auth/group/index.vue b/web/src/views/backend/auth/group/index.vue index 4c8269a..6f4bf9c 100644 --- a/web/src/views/backend/auth/group/index.vue +++ b/web/src/views/backend/auth/group/index.vue @@ -51,7 +51,7 @@ const baTable: baTableClass = new baTableClass( new baTableApi('/admin/auth.Group/'), { expandAll: true, - dblClickNotEditColumn: [undefined], + dblClickNotEditColumn: [undefined, 'status'], column: [ { type: 'selection', align: 'center' }, { @@ -60,15 +60,21 @@ const baTable: baTableClass = new baTableClass( align: 'left', minWidth: '180', }, + { + label: t('auth.group.channel_name'), + prop: 'channel_name', + align: 'center', + minWidth: '140', + }, { label: t('auth.group.commission_rate'), prop: 'commission_rate', align: 'center', formatter: formatRatePercent }, { label: t('auth.group.jurisdiction'), prop: 'rules', align: 'center' }, { label: t('State'), prop: 'status', align: 'center', - render: 'tag', - effect: 'dark', - custom: { 0: 'danger', 1: 'success' }, + operator: 'eq', + sortable: false, + render: 'switch', replaceValue: { 0: t('Disable'), 1: t('Enable') }, }, { label: t('Update time'), prop: 'update_time', align: 'center', width: '160', render: 'datetime' }, @@ -86,6 +92,7 @@ const baTable: baTableClass = new baTableClass( { defaultItems: { status: 1, + channel_id: null, }, } ) @@ -94,9 +101,18 @@ const baTable: baTableClass = new baTableClass( baTable.before.onSubmit = ({ formEl, operate, items }) => { let submitCallback = () => { baTable.form.submitLoading = true + const postItems: anyObj = { ...items } + const pid = Number(postItems.pid ?? 0) + if (pid !== 0) { + delete postItems.channel_id + delete postItems.channel_name + delete postItems.channel_admin_username + } else if (!adminInfo.super) { + delete postItems.channel_id + } baTable.api .postData(operate, { - ...items, + ...postItems, rules: formRef.value?.getCheckeds(), }) .then((res) => { @@ -153,6 +169,10 @@ baTable.after.toggleForm = ({ operate }) => { // 编辑请求完成后钩子 baTable.after.getEditData = () => { + const pid = Number(baTable.form.items?.pid ?? 0) + if (pid !== 0 && baTable.form.items) { + delete baTable.form.items.channel_id + } menuRuleTreeUpdate() } diff --git a/web/src/views/backend/auth/group/popupForm.vue b/web/src/views/backend/auth/group/popupForm.vue index e2b3c5c..99772b2 100644 --- a/web/src/views/backend/auth/group/popupForm.vue +++ b/web/src/views/backend/auth/group/popupForm.vue @@ -41,6 +41,42 @@ valueOnClear: 0, }" /> +

+ {{ t('auth.group.channel_inherit_hint') }} +

+ + + + + + + diff --git a/web/src/views/backend/game/live/index.vue b/web/src/views/backend/game/live/index.vue index e5a9412..e86d5b2 100644 --- a/web/src/views/backend/game/live/index.vue +++ b/web/src/views/backend/game/live/index.vue @@ -11,7 +11,7 @@
{{ t('game.live.countdown') }}: {{ countdownText }}
- + {{ t('game.live.btn_calc') }} @@ -70,6 +70,8 @@ interface Snapshot { period_seconds?: number bet_seconds?: number pick_max_number_count?: number + /** 开奖号码池上限(1–draw_number_max),与单注可选号码上限无关 */ + draw_number_max?: number remaining_seconds?: number bet_remaining_seconds?: number can_calculate?: boolean @@ -87,7 +89,8 @@ const snapshot = reactive({ ai_default_number: null, period_seconds: 30, bet_seconds: 20, - pick_max_number_count: 36, + pick_max_number_count: 10, + draw_number_max: 36, remaining_seconds: 0, bet_remaining_seconds: 0, can_calculate: false, @@ -121,12 +124,14 @@ async function loadSnapshot() { snapshot.ai_default_number = res.data.ai_default_number snapshot.period_seconds = res.data.period_seconds ?? 30 snapshot.bet_seconds = res.data.bet_seconds ?? 20 - snapshot.pick_max_number_count = 36 + snapshot.pick_max_number_count = res.data.pick_max_number_count ?? 10 + snapshot.draw_number_max = res.data.draw_number_max ?? 36 snapshot.remaining_seconds = res.data.remaining_seconds ?? 0 snapshot.bet_remaining_seconds = res.data.bet_remaining_seconds ?? 0 snapshot.can_calculate = !!res.data.can_calculate snapshot.can_draw = !!res.data.can_draw - if (manualNumber.value === null || manualNumber.value < 1 || manualNumber.value > 36) manualNumber.value = 1 + const dmax = res.data.draw_number_max ?? 36 + if (manualNumber.value === null || manualNumber.value < 1 || manualNumber.value > dmax) manualNumber.value = 1 } } finally { loading.value = false @@ -169,7 +174,8 @@ async function initPush() { snapshot.ai_default_number = payload.ai_default_number ?? null snapshot.period_seconds = payload.period_seconds ?? 30 snapshot.bet_seconds = payload.bet_seconds ?? 20 - snapshot.pick_max_number_count = 36 + snapshot.pick_max_number_count = payload.pick_max_number_count ?? 10 + snapshot.draw_number_max = payload.draw_number_max ?? 36 snapshot.remaining_seconds = payload.remaining_seconds ?? 0 snapshot.bet_remaining_seconds = payload.bet_remaining_seconds ?? 0 snapshot.can_calculate = !!payload.can_calculate diff --git a/web/src/views/backend/game/period/index.vue b/web/src/views/backend/game/period/index.vue deleted file mode 100644 index c0d5a43..0000000 --- a/web/src/views/backend/game/period/index.vue +++ /dev/null @@ -1,303 +0,0 @@ - - - - - diff --git a/web/src/views/backend/game/period/popupForm.vue b/web/src/views/backend/game/period/popupForm.vue deleted file mode 100644 index 46559cc..0000000 --- a/web/src/views/backend/game/period/popupForm.vue +++ /dev/null @@ -1,131 +0,0 @@ - - - - - diff --git a/web/src/views/backend/game/record/index.vue b/web/src/views/backend/game/record/index.vue index 08d957e..a5966e2 100644 --- a/web/src/views/backend/game/record/index.vue +++ b/web/src/views/backend/game/record/index.vue @@ -31,6 +31,13 @@ const { t } = useI18n() const tableRef = useTemplateRef('tableRef') const optButtons: OptButton[] = defaultOptButtons(['edit']) +const formatCoin = (_row: any, _column: any, cellValue: number | string | null | undefined) => { + if (cellValue === null || cellValue === undefined || cellValue === '') return '—' + const n = Number(cellValue) + if (Number.isNaN(n)) return '—' + return n.toFixed(4) +} + const baTable = new baTableClass( new baTableApi('/admin/game.Record/'), { @@ -63,6 +70,23 @@ const baTable = new baTableClass( }, { label: t('game.record.preset_number'), prop: 'preset_number', align: 'center', width: 100, operator: 'RANGE' }, { label: t('game.record.result_number'), prop: 'result_number', align: 'center', width: 100, operator: 'RANGE' }, + { + label: t('game.record.platform_profit_amount'), + prop: 'platform_profit_amount', + align: 'center', + minWidth: 130, + operator: 'RANGE', + sortable: false, + formatter: formatCoin, + }, + { + label: t('game.record.winner_user_count'), + prop: 'winner_user_count', + align: 'center', + width: 110, + operator: 'RANGE', + sortable: false, + }, { label: t('game.record.void_reason'), prop: 'void_reason', align: 'center', minWidth: 140, operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', showOverflowTooltip: true }, { label: t('game.record.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 170, timeFormat: 'yyyy-mm-dd hh:MM:ss' }, { label: t('game.record.update_time'), prop: 'update_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 170, timeFormat: 'yyyy-mm-dd hh:MM:ss' }, diff --git a/web/src/views/backend/game/record/popupForm.vue b/web/src/views/backend/game/record/popupForm.vue index a27b1bb..6133ba6 100644 --- a/web/src/views/backend/game/record/popupForm.vue +++ b/web/src/views/backend/game/record/popupForm.vue @@ -26,6 +26,8 @@ /> + +
diff --git a/web/src/views/backend/operation/operationNotice/index.vue b/web/src/views/backend/operation/operationNotice/index.vue index 2dd9408..a775c02 100644 --- a/web/src/views/backend/operation/operationNotice/index.vue +++ b/web/src/views/backend/operation/operationNotice/index.vue @@ -66,9 +66,8 @@ const baTable = new baTableClass( align: 'center', width: 100, operator: 'eq', - render: 'tag', - effect: 'dark', - custom: { 0: 'info', 1: 'success' }, + sortable: false, + render: 'switch', replaceValue: { '0': t('operation.operationNotice.status 0'), '1': t('operation.operationNotice.status 1'), @@ -109,6 +108,7 @@ const baTable = new baTableClass( }, { label: t('Operate'), align: 'center', minWidth: 80, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' }, ], + dblClickNotEditColumn: [undefined, 'status'], }, { defaultItems: { status: 0, notice_type: 0 }, diff --git a/web/src/views/backend/order/betOrder/index.vue b/web/src/views/backend/order/betOrder/index.vue index 07436b4..41ca510 100644 --- a/web/src/views/backend/order/betOrder/index.vue +++ b/web/src/views/backend/order/betOrder/index.vue @@ -58,23 +58,30 @@ const baTable = new baTableClass( pk: 'id', column: [ { label: t('order.betOrder.id'), prop: 'id', align: 'center', width: 100, operator: 'RANGE', sortable: 'custom' }, - { label: t('order.betOrder.period_id'), prop: 'period_id', align: 'center', width: 100, operator: 'RANGE' }, { - label: t('order.betOrder.period_no'), - prop: 'period_no', + label: t('order.betOrder.period_id'), + prop: 'period_id', align: 'center', - minWidth: 160, - operatorPlaceholder: t('Fuzzy query'), - operator: 'LIKE', + show: false, + width: 100, + operator: 'RANGE', }, + // { + // label: t('order.betOrder.period_no'), + // prop: 'period_no', + // align: 'center', + // minWidth: 160, + // operatorPlaceholder: t('Fuzzy query'), + // operator: 'LIKE', + // }, { label: t('order.betOrder.gameRecord_period_no'), prop: 'gameRecord.period_no', align: 'center', - minWidth: 160, + minWidth: 200, operatorPlaceholder: t('Fuzzy query'), operator: 'LIKE', - render: 'tags', + render: 'tag', }, { label: t('order.betOrder.gameRecord_status'), diff --git a/web/src/views/backend/security/dataRecycle/index.vue b/web/src/views/backend/security/dataRecycle/index.vue index 53143e5..b76d1da 100644 --- a/web/src/views/backend/security/dataRecycle/index.vue +++ b/web/src/views/backend/security/dataRecycle/index.vue @@ -75,9 +75,9 @@ const baTable = new baTableClass( label: t('State'), prop: 'status', align: 'center', - render: 'tag', - effect: 'dark', - custom: { 0: 'danger', 1: 'success' }, + operator: 'eq', + sortable: false, + render: 'switch', replaceValue: { 0: t('Disable'), 1: t('security.dataRecycle.Deleting monitoring') }, }, { label: t('Update time'), prop: 'update_time', align: 'center', render: 'datetime', sortable: 'custom', operator: 'RANGE', width: 160 }, diff --git a/web/src/views/backend/security/sensitiveData/index.vue b/web/src/views/backend/security/sensitiveData/index.vue index 9b774c3..e7d0ec1 100644 --- a/web/src/views/backend/security/sensitiveData/index.vue +++ b/web/src/views/backend/security/sensitiveData/index.vue @@ -84,9 +84,9 @@ const baTable = new sensitiveDataClass( label: t('State'), prop: 'status', align: 'center', - render: 'tag', - effect: 'dark', - custom: { 0: 'danger', 1: 'success' }, + operator: 'eq', + sortable: false, + render: 'switch', replaceValue: { 0: t('Disable'), 1: t('security.sensitiveData.Modifying monitoring') }, }, { label: t('Update time'), prop: 'update_time', align: 'center', render: 'datetime', sortable: 'custom', operator: 'RANGE', width: 160 }, @@ -101,7 +101,7 @@ const baTable = new sensitiveDataClass( fixed: 'right', }, ], - dblClickNotEditColumn: [undefined], + dblClickNotEditColumn: [undefined, 'status'], }, { defaultItems: { diff --git a/web/src/views/backend/record/userWalletRecord/index.vue b/web/src/views/backend/user/userWalletRecord/index.vue similarity index 70% rename from web/src/views/backend/record/userWalletRecord/index.vue rename to web/src/views/backend/user/userWalletRecord/index.vue index 1deafd5..7729a56 100644 --- a/web/src/views/backend/record/userWalletRecord/index.vue +++ b/web/src/views/backend/user/userWalletRecord/index.vue @@ -1,10 +1,10 @@ -