model = new AdminModel(); $this->tree = Tree::instance(); $isTree = $request->get('isTree') ?? $request->post('isTree') ?? false; $initValue = $request->get('initValue') ?? $request->post('initValue') ?? []; if (is_array($initValue)) { $this->initValue = array_filter($initValue); } elseif ($initValue !== null && $initValue !== '') { $this->initValue = array_filter(explode(',', (string) $initValue)); } else { $this->initValue = []; } $this->keyword = (string) ($request->get('quickSearch') ?? $request->post('quickSearch') ?? ''); $this->assembleTree = ($isTree === true || $isTree === '1' || $isTree === 1) && $this->initValue === []; return null; } public function index(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } if ($request->get('select') ?? $request->post('select')) { $selectRes = $this->select($request); if ($selectRes !== null) { return $selectRes; } } list($where, $alias, $limit, $order) = $this->queryBuilder(); $adminAlias = $alias[strtolower($this->model->getTable())] ?? 'admin'; $query = $this->model ->withoutField('login_failure,password,salt') ->alias($alias) ->where($where); $visibleIds = AdminCommissionDistributionService::getVisibleAdminIdsForOperator( intval($this->auth->id), $this->auth->isSuperAdmin() ); if ($visibleIds !== []) { $query->where($adminAlias . '.id', 'in', $visibleIds); } $topGroup = $request->get('top_group') ?? $request->post('top_group'); if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) { $query = $query ->join('admin_group_access aga', $adminAlias . '.id = aga.uid') ->join('admin_group ag', 'aga.group_id = ag.id') ->where('ag.pid', 0) ->distinct(true); } $rows = $query->order($order)->select()->toArray(); $rows = $this->enrichAdminRows($rows); foreach ($rows as $k => $row) { $parentId = intval($row['parent_admin_id'] ?? 0); $rows[$k]['pid'] = $parentId > 0 ? $parentId : 0; } $tree = Tree::instance()->assembleChild($rows, 'pid', 'id'); return $this->success('', [ 'list' => $tree, 'total' => count($rows), 'remark' => get_route_remark(), ]); } /** * 查询同上级下剩余可分配分红比例(表单提示) */ public function commissionShareRemainder(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } $parentAdminId = intval($request->get('parent_admin_id', 0)); $excludeId = intval($request->get('exclude_id', 0)); $isTopLevelGroup = ($request->get('is_top_level') ?? $request->post('is_top_level')) === '1' || ($request->get('is_top_level') ?? $request->post('is_top_level')) === 1 || ($request->get('is_top_level') ?? $request->post('is_top_level')) === true; $channelId = intval($request->get('channel_id', 0)); if ($isTopLevelGroup) { if ($channelId <= 0) { return $this->success('', [ 'used_rate' => '0.00', 'remaining_rate' => '100.00', 'parent_has_no_share' => false, 'is_top_level' => true, ]); } $stats = AdminCommissionDistributionService::getChannelRootShareRemainder( $channelId, $excludeId > 0 ? $excludeId : null ); return $this->success('', [ 'used_rate' => $stats['used_rate'], 'remaining_rate' => $stats['remaining_rate'], 'parent_has_no_share' => bccomp($stats['remaining_rate'], '0', 2) <= 0, 'is_top_level' => true, ]); } if ($parentAdminId <= 0) { return $this->success('', [ 'used_rate' => '0.00', 'remaining_rate' => '100.00', 'parent_has_no_share' => false, 'is_top_level' => false, ]); } if (!$this->canManageAdminId($parentAdminId)) { return $this->error(__('You have no permission')); } $stats = AdminCommissionDistributionService::getShareRemainder( $parentAdminId, $excludeId > 0 ? $excludeId : null ); return $this->success('', [ 'used_rate' => $stats['used_rate'], 'remaining_rate' => $stats['remaining_rate'], 'parent_has_no_share' => bccomp($stats['remaining_rate'], '0', 2) <= 0, 'is_top_level' => false, ]); } /** * 查询角色组是否为顶级(pid=0),供管理员表单联动 */ public function groupMeta(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } $groupId = intval($request->get('group_id', 0)); if ($groupId <= 0) { return $this->error(__('Invalid parameters')); } $group = Db::name('admin_group')->where('id', $groupId)->field(['id', 'pid', 'channel_id'])->find(); if (!is_array($group)) { return $this->error(__('Record not found')); } if (!$this->auth->isSuperAdmin()) { $authGroups = $this->getManageableGroupIds(); if (!in_array($groupId, $authGroups, true)) { return $this->error(__('You have no permission')); } } $pid = intval($group['pid'] ?? 0); return $this->success('', [ 'is_top_level' => $pid === 0, 'pid' => $pid, 'channel_id' => $group['channel_id'] ?? null, ]); } /** * 远程下拉(重写:支持 top_group=1 仅返回顶级组管理员) */ protected function _select(): Response { if (empty($this->model)) { return $this->success('', [ 'list' => [], 'total' => 0, ]); } $pk = $this->model->getPk(); $fields = [$pk]; $quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', (string) $this->quickSearchField); foreach ($quickSearchArr as $f) { $f = trim((string) $f); if ($f === '') { continue; } $f = str_contains($f, '.') ? substr($f, strrpos($f, '.') + 1) : $f; if ($f !== '' && !in_array($f, $fields, true)) { $fields[] = $f; } } list($where, $alias, $limit, $order) = $this->queryBuilder(); $modelTable = strtolower($this->model->getTable()); $mainAlias = ($alias[$modelTable] ?? $modelTable) . '.'; $selectFields = []; foreach ($fields as $f) { $f = trim((string) $f); if ($f === '') { continue; } $selectFields[] = $mainAlias . $f . ' as ' . $f; } $qualifiedOrder = []; if (is_array($order)) { foreach ($order as $k => $v) { $k = (string) $k; $qualifiedOrder[str_contains($k, '.') ? $k : ($mainAlias . $k)] = $v; } } $query = $this->model ->field($selectFields) ->alias($alias) ->where($where); $visibleIds = AdminCommissionDistributionService::getVisibleAdminIdsForOperator( intval($this->auth->id), $this->auth->isSuperAdmin() ); if ($visibleIds !== []) { $query->where($mainAlias . 'id', 'in', $visibleIds); } $topGroup = $this->request ? ($this->request->get('top_group') ?? $this->request->post('top_group')) : null; if ($topGroup === '1' || $topGroup === 1 || $topGroup === true) { $query = $query ->join('admin_group_access aga', $mainAlias . 'id = aga.uid') ->join('admin_group ag', 'aga.group_id = ag.id') ->where('ag.pid', 0) ->distinct(true); } $res = $query ->order($qualifiedOrder ?: $order) ->paginate($limit); return $this->success('', [ 'list' => $res->items(), 'total' => $res->total(), ]); } public function add(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } if ($request->method() === 'POST') { $data = $request->post(); if (!$data) { return $this->error(__('Parameter %s can not be empty', [''])); } $data = $this->normalizeSingleGroup($data); if (!$this->hasSingleGroup($data['group_arr'] ?? null)) { return $this->error(__('Please select exactly one role group')); } if ($this->modelValidate) { try { $rules = [ 'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username', 'nickname' => 'required|string', 'password' => 'required|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/', 'email' => 'email|unique:admin,email', 'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile', 'group_arr' => 'required|array', ]; $messages = [ 'username.regex' => __('Please input correct username'), 'password.regex' => __('Please input correct password'), ]; Validator::make($data, $rules, $messages)->validate(); } catch (ValidationException $e) { return $this->error($e->getMessage()); } } $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(__('Selected role group is not bound to a channel')); } if ((string) $groupChannelId !== (string) $creatorChannelId) { return $this->error(__('Selected role group channel does not match current account')); } $data['channel_id'] = $creatorChannelId; $data['parent_admin_id'] = $this->auth->id; } else { $postedChannel = $data['channel_id'] ?? null; if ($postedChannel === null || $postedChannel === '') { $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId; } } $parentErr = $this->normalizeParentAndShareFields($data, null, $data['group_arr'] ?? []); if ($parentErr !== null) { return $this->error($parentErr); } $data['invite_code'] = $this->generateUniqueInviteCode(); $result = false; if (!empty($data['group_arr'])) { $authRes = $this->checkGroupAuth($data['group_arr']); if ($authRes !== null) { return $authRes; } } $this->model->startTrans(); try { $result = $this->model->save($data); if (!empty($data['group_arr'])) { $groupAccess = []; foreach ($data['group_arr'] as $datum) { $groupAccess[] = [ 'uid' => $this->model->id, 'group_id' => $datum, ]; } Db::name('admin_group_access')->insertAll($groupAccess); } $this->model->commit(); if (!empty($passwd)) { $this->model->resetPassword($this->model->id, $passwd); } } catch (Throwable $e) { $this->model->rollback(); return $this->error($e->getMessage()); } if ($result !== false) { return $this->success(__('Added successfully')); } return $this->error(__('No rows were added')); } return $this->error(__('Parameter error')); } public function edit(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } $pk = $this->model->getPk(); $id = $request->get($pk) ?? $request->post($pk); $row = $this->model->find($id); if (!$row) { return $this->error(__('Record not found')); } if (!$this->canManageAdminId(intval($row['id']))) { return $this->error(__('You have no permission')); } if ($request->method() === 'POST') { $data = $request->post(); if (!$data) { return $this->error(__('Parameter %s can not be empty', [''])); } $isSelfEdit = (int) $this->auth->id === (int) $id; if ($isSelfEdit) { unset($data['group_arr'], $data['group_name_arr'], $data['parent_admin_id'], $data['commission_share_rate']); } $editGroupArr = null; if (array_key_exists('group_arr', $data)) { $data = $this->normalizeSingleGroup($data); if (!$this->hasSingleGroup($data['group_arr'] ?? null)) { return $this->error(__('Please select exactly one role group')); } $editGroupArr = $data['group_arr']; } elseif (!$isSelfEdit) { return $this->error(__('Please select exactly one role group')); } if ($this->modelValidate) { try { $rules = [ 'username' => 'required|string|regex:/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/|unique:admin,username,' . $id, 'nickname' => 'required|string', 'password' => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/', 'email' => 'email|unique:admin,email,' . $id, 'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile,' . $id, ]; if (array_key_exists('group_arr', $data)) { $rules['group_arr'] = 'required|array'; } $messages = [ 'username.regex' => __('Please input correct username'), 'password.regex' => __('Please input correct password'), ]; Validator::make($data, $rules, $messages)->validate(); } catch (ValidationException $e) { return $this->error($e->getMessage()); } } if ($this->auth->id == $data['id'] && ($data['status'] ?? '') == 'disable') { return $this->error(__('Please use another administrator account to disable the current account!')); } if (!empty($data['password'])) { $this->model->resetPassword($row->id, $data['password']); } $groupAccess = []; if (!$isSelfEdit && !empty($editGroupArr)) { $checkGroups = []; $rowGroupArr = $row->group_arr ?? []; foreach ($editGroupArr as $datum) { if (!in_array($datum, $rowGroupArr)) { $checkGroups[] = $datum; } $groupAccess[] = [ 'uid' => $id, 'group_id' => $datum, ]; } $authRes = $this->checkGroupAuth($checkGroups); if ($authRes !== null) { return $authRes; } } $data = $this->excludeFields($data); unset($data['invite_code'], $data['group_arr'], $data['group_name_arr']); if (!$isSelfEdit && $editGroupArr !== null) { $creatorChannelId = $this->getCreatorChannelId(); $groupChannelId = $this->resolveChannelIdFromPrimaryGroup($editGroupArr); if (!$this->auth->isSuperAdmin()) { if ($creatorChannelId === null || $creatorChannelId === '') { return $this->error(__('You have no permission')); } if ($groupChannelId === null || $groupChannelId === '') { return $this->error(__('Selected role group is not bound to a channel')); } if ((string) $groupChannelId !== (string) $creatorChannelId) { return $this->error(__('Selected role group channel does not match current account')); } $data['channel_id'] = $creatorChannelId; } else { $data['channel_id'] = ($groupChannelId === null || $groupChannelId === '') ? null : $groupChannelId; } } if (!$isSelfEdit) { if (!$this->auth->isSuperAdmin()) { unset($data['parent_admin_id']); } $parentErr = $this->normalizeParentAndShareFields($data, intval($id), $editGroupArr ?? []); if ($parentErr !== null) { return $this->error($parentErr); } } $result = false; $this->model->startTrans(); try { $result = $row->save($data); if (!$isSelfEdit) { Db::name('admin_group_access') ->where('uid', $id) ->delete(); if ($groupAccess) { Db::name('admin_group_access')->insertAll($groupAccess); } } $this->model->commit(); } catch (Throwable $e) { $this->model->rollback(); return $this->error($e->getMessage()); } if ($result !== false) { return $this->success(__('Update successful')); } return $this->error(__('No rows updated')); } unset($row['salt'], $row['login_failure']); $row['password'] = ''; $rowData = $row->toArray(); $enriched = $this->enrichAdminRows([$rowData]); $rowData = $enriched[0] ?? $rowData; $groupArr = is_array($rowData['group_arr'] ?? null) ? $rowData['group_arr'] : []; $rowData['primary_group_is_top_level'] = $this->isPrimaryGroupTopLevel($groupArr); $parentId = intval($rowData['parent_admin_id'] ?? 0); if ($rowData['primary_group_is_top_level']) { $channelId = intval($rowData['channel_id'] ?? 0); if ($channelId > 0) { $rowData['root_share_remainder'] = AdminCommissionDistributionService::getChannelRootShareRemainder( $channelId, intval($id) ); } } elseif ($parentId > 0) { $remainder = AdminCommissionDistributionService::getShareRemainder($parentId, intval($id)); $rowData['share_remainder'] = $remainder; } return $this->success('', [ 'row' => $rowData, ]); } public function del(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } $ids = $request->get('ids') ?? $request->post('ids') ?? []; $ids = is_array($ids) ? $ids : []; if ($ids === []) { return $this->error(__('Parameter error')); } $data = $this->model->where($this->model->getPk(), 'in', $ids)->select(); $count = 0; $this->model->startTrans(); try { foreach ($data as $v) { if ($v->id == $this->auth->id) { continue; } if (!$this->canManageAdminId(intval($v->id))) { continue; } $childCount = Db::name('admin')->where('parent_admin_id', $v->id)->count(); if ($childCount > 0) { $this->model->rollback(); return $this->error(__('Cannot delete administrator with sub-agents')); } $count += $v->delete(); Db::name('admin_group_access') ->where('uid', $v['id']) ->delete(); } $this->model->commit(); } catch (Throwable $e) { $this->model->rollback(); return $this->error($e->getMessage()); } if ($count) { return $this->success(__('Deleted successfully')); } return $this->error(__('No rows were deleted')); } public function select(Request $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } $rows = $this->getAdminsForSelect($request); if ($this->assembleTree) { $treeData = $this->tree->assembleChild($rows, 'pid', 'id'); $rows = $this->tree->assembleTree($this->tree->getTreeArray($treeData, 'username')); } return $this->success('', [ 'options' => $rows, ]); } /** * @return array> */ private function getAdminsForSelect(Request $request): array { list($where, $alias, $limit, $order) = $this->queryBuilder(); $adminAlias = $alias[strtolower($this->model->getTable())] ?? 'admin'; $query = $this->model ->withoutField('login_failure,password,salt') ->alias($alias) ->where($where); $visibleIds = AdminCommissionDistributionService::getVisibleAdminIdsForOperator( intval($this->auth->id), $this->auth->isSuperAdmin() ); if ($visibleIds !== []) { $query->where($adminAlias . '.id', 'in', $visibleIds); } $rows = $query->order($order)->select()->toArray(); foreach ($rows as $k => $row) { $parentId = intval($row['parent_admin_id'] ?? 0); $rows[$k]['pid'] = $parentId > 0 ? $parentId : 0; } return $rows; } private function checkGroupAuth(array $groups): ?Response { if ($this->auth->isSuperAdmin()) { return null; } $authGroups = $this->getManageableGroupIds(); foreach ($groups as $group) { if (!in_array($group, $authGroups)) { return $this->error(__('You have no permission to add an administrator to this group!')); } } return null; } private function getManageableGroupIds(): array { if ($this->auth->isSuperAdmin()) { return Db::name('admin_group')->where('status', 1)->column('id'); } $own = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id'); $children = $this->auth->getAdminChildGroups(); return array_values(array_unique(array_merge($own, $children))); } 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; } /** * @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 canManageAdminId(int $adminId): bool { if ($this->auth->isSuperAdmin()) { return true; } if ($adminId === intval($this->auth->id)) { return true; } $visible = AdminCommissionDistributionService::getVisibleAdminIdsForOperator( intval($this->auth->id), false ); return in_array($adminId, $visible, true); } /** * @param array $groupIds */ private function isPrimaryGroupTopLevel(array $groupIds): bool { if ($groupIds === []) { return false; } $gid = intval($groupIds[0]); if ($gid <= 0) { return false; } $pid = Db::name('admin_group')->where('id', $gid)->value('pid'); return intval($pid ?? 0) === 0; } /** * @param array $data * @param array $groupIds */ private function normalizeParentAndShareFields(array &$data, ?int $editAdminId, array $groupIds = []): ?string { if ($this->isPrimaryGroupTopLevel($groupIds)) { $data['parent_admin_id'] = null; $channelId = $data['channel_id'] ?? null; if ($channelId === null || $channelId === '') { $channelId = $this->resolveChannelIdFromPrimaryGroup($groupIds); if ($channelId !== null && $channelId !== '') { $data['channel_id'] = $channelId; } } $channelIdInt = intval($channelId ?? 0); $shareErr = AdminCommissionDistributionService::validateChannelRootCommissionShareRate( $channelIdInt, $data['commission_share_rate'] ?? null, $editAdminId ); if ($shareErr !== null) { return $shareErr; } $data['commission_share_rate'] = bcadd(strval($data['commission_share_rate'] ?? '0'), '0', 2); return null; } $parentId = isset($data['parent_admin_id']) && $data['parent_admin_id'] !== '' && $data['parent_admin_id'] !== null ? intval($data['parent_admin_id']) : 0; if ($parentId <= 0 && $editAdminId !== null && $editAdminId > 0) { $existingParent = Db::name('admin')->where('id', $editAdminId)->value('parent_admin_id'); $parentId = intval($existingParent ?? 0); } if ($parentId <= 0) { $data['parent_admin_id'] = null; $data['commission_share_rate'] = null; return null; } if ($editAdminId !== null && $parentId === $editAdminId) { return (string) __('Cannot set yourself as parent administrator'); } $parent = Db::name('admin')->where('id', $parentId)->find(); if (!is_array($parent)) { return (string) __('Invalid parent administrator'); } if (!$this->canManageAdminId($parentId) && !$this->auth->isSuperAdmin()) { return (string) __('You have no permission'); } $channelId = $data['channel_id'] ?? null; if ($channelId !== null && $channelId !== '' && (string) ($parent['channel_id'] ?? '') !== (string) $channelId) { return (string) __('Parent administrator must belong to the same channel'); } if (($channelId === null || $channelId === '') && !empty($parent['channel_id'])) { $data['channel_id'] = $parent['channel_id']; } $shareErr = AdminCommissionDistributionService::validateCommissionShareRate( $parentId, $data['commission_share_rate'] ?? null, $editAdminId ); if ($shareErr !== null) { return $shareErr; } $data['commission_share_rate'] = bcadd(strval($data['commission_share_rate'] ?? '0'), '0', 2); $data['parent_admin_id'] = $parentId; return null; } /** * @param array> $rows * @return array> */ private function enrichAdminRows(array $rows): array { if ($rows === []) { return []; } $adminIds = []; $channelIds = []; $parentIds = []; foreach ($rows as $row) { $aid = intval($row['id'] ?? 0); if ($aid > 0) { $adminIds[] = $aid; } $cid = $row['channel_id'] ?? null; if ($cid !== null && $cid !== '') { $channelIds[] = intval($cid); } $pid = intval($row['parent_admin_id'] ?? 0); if ($pid > 0) { $parentIds[] = $pid; } } $channelNames = $channelIds !== [] ? Db::name('channel')->where('id', 'in', array_unique($channelIds))->column('name', 'id') : []; $parentNames = $parentIds !== [] ? Db::name('admin')->where('id', 'in', array_unique($parentIds))->column('username', 'id') : []; $groupMap = []; if ($adminIds !== []) { $accessRows = Db::name('admin_group_access')->where('uid', 'in', $adminIds)->select()->toArray(); $groupIds = array_unique(array_map(static fn(array $r): int => intval($r['group_id'] ?? 0), $accessRows)); $groupNames = $groupIds !== [] ? Db::name('admin_group')->where('id', 'in', $groupIds)->column('name', 'id') : []; foreach ($accessRows as $access) { $uid = intval($access['uid'] ?? 0); $gid = intval($access['group_id'] ?? 0); if ($uid <= 0 || $gid <= 0) { continue; } if (!isset($groupMap[$uid])) { $groupMap[$uid] = []; } $name = $groupNames[$gid] ?? ''; if ($name !== '') { $groupMap[$uid][] = $name; } } } foreach ($rows as $k => $row) { $cid = $row['channel_id'] ?? null; $rows[$k]['channel_name'] = ($cid !== null && $cid !== '') ? strval($channelNames[intval($cid)] ?? '') : ''; $pid = intval($row['parent_admin_id'] ?? 0); $rows[$k]['parent_admin_username'] = $pid > 0 ? strval($parentNames[$pid] ?? '') : ''; $aid = intval($row['id'] ?? 0); $rows[$k]['group_name_arr'] = $groupMap[$aid] ?? []; } return $rows; } private function generateUniqueInviteCode(): string { $tries = 0; do { $tries++; $code = strtoupper(substr(Random::uuid(), 0, 8)); $exists = Db::name('admin')->where('invite_code', $code)->find(); } while ($exists && $tries < 20); return $code; } private function normalizeSingleGroup(array $data): array { if (!array_key_exists('group_arr', $data)) { return $data; } $group = $data['group_arr']; if (is_array($group)) { $data['group_arr'] = array_values(array_filter($group, static function ($item) { return $item !== null && $item !== ''; })); return $data; } if ($group === null || $group === '') { $data['group_arr'] = []; return $data; } $data['group_arr'] = [$group]; return $data; } private function hasSingleGroup(mixed $groups): bool { return is_array($groups) && count($groups) === 1; } }