1.修复角色组不能选择权限的报错
2.修复角色创建子角色报权限不够的问题
This commit is contained in:
@@ -253,28 +253,20 @@ class Admin extends Backend
|
||||
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'));
|
||||
$isSelfEdit = (int) $this->auth->id === (int) $id;
|
||||
if ($isSelfEdit) {
|
||||
unset($data['group_arr'], $data['group_name_arr']);
|
||||
}
|
||||
|
||||
$postedGroups = array_map('intval', $data['group_arr'] ?? []);
|
||||
$rowGroups = array_map('intval', $row->group_arr ?? []);
|
||||
sort($postedGroups);
|
||||
sort($rowGroups);
|
||||
|
||||
// 当前管理员编辑自身时,不允许修改角色组
|
||||
if ((int)$this->auth->id === (int)$id) {
|
||||
$postedGroups = $data['group_arr'] ?? [];
|
||||
if (!is_array($postedGroups)) {
|
||||
$postedGroups = [];
|
||||
}
|
||||
$originGroups = $row->group_arr ?? [];
|
||||
sort($postedGroups);
|
||||
sort($originGroups);
|
||||
if ($postedGroups !== $originGroups) {
|
||||
return $this->error(__('You cannot modify your own management group!'));
|
||||
$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) {
|
||||
@@ -285,8 +277,10 @@ class Admin extends Backend
|
||||
'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,
|
||||
'group_arr' => 'required|array',
|
||||
];
|
||||
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'),
|
||||
@@ -306,10 +300,10 @@ class Admin extends Backend
|
||||
}
|
||||
|
||||
$groupAccess = [];
|
||||
if (!empty($data['group_arr'])) {
|
||||
if (!$isSelfEdit && !empty($editGroupArr)) {
|
||||
$checkGroups = [];
|
||||
$rowGroupArr = $row->group_arr ?? [];
|
||||
foreach ($data['group_arr'] as $datum) {
|
||||
foreach ($editGroupArr as $datum) {
|
||||
if (!in_array($datum, $rowGroupArr)) {
|
||||
$checkGroups[] = $datum;
|
||||
}
|
||||
@@ -323,32 +317,36 @@ class Admin extends Backend
|
||||
}
|
||||
|
||||
$data = $this->excludeFields($data);
|
||||
unset($data['invite_code']);
|
||||
$creatorChannelId = $this->getCreatorChannelId();
|
||||
$groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []);
|
||||
if (!$this->auth->isSuperAdmin()) {
|
||||
if ($creatorChannelId === null || $creatorChannelId === '') {
|
||||
return $this->error(__('You have no permission'));
|
||||
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 ($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;
|
||||
}
|
||||
$result = false;
|
||||
$this->model->startTrans();
|
||||
try {
|
||||
$result = $row->save($data);
|
||||
Db::name('admin_group_access')
|
||||
->where('uid', $id)
|
||||
->delete();
|
||||
if ($groupAccess) {
|
||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||
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) {
|
||||
|
||||
@@ -19,6 +19,11 @@ class Group extends Backend
|
||||
{
|
||||
protected string $authMethod = 'allAuthAndOthers';
|
||||
|
||||
/**
|
||||
* 角色组表单分配权限树(仅需登录 + 具备角色组管理相关权限,不依赖菜单规则管理权限)
|
||||
*/
|
||||
protected array $noNeedPermission = ['rules'];
|
||||
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||
@@ -90,7 +95,7 @@ class Group extends Backend
|
||||
if ($inheritRes !== null) {
|
||||
return $inheritRes;
|
||||
}
|
||||
$rulesRes = $this->handleRules($data);
|
||||
$rulesRes = $this->handleRules($data, $pidInt);
|
||||
if ($rulesRes instanceof Response) return $rulesRes;
|
||||
|
||||
$result = false;
|
||||
@@ -161,7 +166,7 @@ class Group extends Backend
|
||||
if ($inheritRes !== null) {
|
||||
return $inheritRes;
|
||||
}
|
||||
$rulesRes = $this->handleRules($data);
|
||||
$rulesRes = $this->handleRules($data, $pidInt);
|
||||
if ($rulesRes instanceof Response) return $rulesRes;
|
||||
|
||||
$result = false;
|
||||
@@ -296,9 +301,29 @@ class Group extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录管理员可分配给下级角色组的菜单权限树(与 Rule::getMenus 一致,走角色组管理权限)
|
||||
*/
|
||||
public function rules(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (!$this->auth->isSuperAdmin() && !$this->canManageRoleGroups()) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $this->getAssignableMenuRules($request),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pidInt 上级角色组 ID;大于 0 表示创建/编辑的是下级组,可与当前管理员拥有相同菜单权限
|
||||
* @return array|Response
|
||||
*/
|
||||
private function handleRules(array &$data)
|
||||
private function handleRules(array &$data, int $pidInt = 0)
|
||||
{
|
||||
if (!empty($data['rules']) && is_array($data['rules'])) {
|
||||
$superAdmin = true;
|
||||
@@ -310,7 +335,6 @@ class Group extends Backend
|
||||
$checkedRules[] = $postRuleId;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($allRuleIds as $ruleId) {
|
||||
if (!in_array($ruleId, $checkedRules)) {
|
||||
$superAdmin = false;
|
||||
@@ -320,9 +344,15 @@ class Group extends Backend
|
||||
if ($superAdmin && $this->auth->isSuperAdmin()) {
|
||||
$data['rules'] = '*';
|
||||
} else {
|
||||
$ownedRuleIds = $this->auth->getRuleIds();
|
||||
$ownedRuleIds = $this->normalizeRuleIds($this->auth->getRuleIds());
|
||||
$checkedRules = $this->normalizeRuleIds($checkedRules);
|
||||
|
||||
if (!array_diff($ownedRuleIds, $checkedRules)) {
|
||||
// 仅限制「非下级」角色组:防止子管理员新建与自己平级的全权限组;下级组(pid>0)允许授予相同菜单权限
|
||||
if (
|
||||
$pidInt <= 0
|
||||
&& $ownedRuleIds !== []
|
||||
&& !array_diff($ownedRuleIds, $checkedRules)
|
||||
) {
|
||||
return $this->error(__('Role group has all your rights, please contact the upper administrator to add or do not need to add!'));
|
||||
}
|
||||
|
||||
@@ -338,6 +368,23 @@ class Group extends Backend
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string> $ids
|
||||
* @return array<int>
|
||||
*/
|
||||
private function normalizeRuleIds(array $ids): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($ids as $id) {
|
||||
if ($id === '*' || $id === '' || $id === null) {
|
||||
continue;
|
||||
}
|
||||
$result[] = (int) $id;
|
||||
}
|
||||
|
||||
return array_values(array_unique($result));
|
||||
}
|
||||
|
||||
private function getGroups(Request $request, array $where = []): array
|
||||
{
|
||||
$pk = $this->model->getPk();
|
||||
@@ -500,4 +547,82 @@ class Group extends Backend
|
||||
return null;
|
||||
}
|
||||
|
||||
private function canManageRoleGroups(): bool
|
||||
{
|
||||
foreach (['auth/group/index', 'auth/group/add', 'auth/group/edit', 'auth/Group/index', 'auth/Group/add', 'auth/Group/edit'] as $routePath) {
|
||||
if ($this->auth->check($routePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function getAssignableMenuRules(Request $request): array
|
||||
{
|
||||
$ids = $this->auth->getRuleIds();
|
||||
$where = [];
|
||||
if (!in_array('*', $ids, true)) {
|
||||
$where[] = ['id', 'in', $ids ?: [0]];
|
||||
}
|
||||
|
||||
$rules = (new AdminRule())
|
||||
->where($where)
|
||||
->order(['weigh' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$toEnglish = !$this->shouldForceMenuTitleZh($request) && $this->shouldTranslateMenuToEnglish();
|
||||
foreach ($rules as $idx => $rule) {
|
||||
$title = $rule['title'] ?? '';
|
||||
if (is_string($title) && $title !== '') {
|
||||
$rules[$idx]['title'] = $toEnglish ? $this->menuTitleToEn($title) : $this->menuTitleToZh($title);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->tree->assembleChild($rules);
|
||||
}
|
||||
|
||||
private function shouldTranslateMenuToEnglish(): bool
|
||||
{
|
||||
$lang = function_exists('locale') ? locale() : '';
|
||||
$normalized = is_string($lang) ? strtolower(str_replace('_', '-', trim($lang))) : '';
|
||||
|
||||
return str_starts_with($normalized, 'en');
|
||||
}
|
||||
|
||||
private function shouldForceMenuTitleZh(Request $request): bool
|
||||
{
|
||||
$flag = $request->get('force_menu_zh') ?? $request->post('force_menu_zh');
|
||||
|
||||
return in_array($flag, [1, '1', true, 'true', 'yes', 'on'], true);
|
||||
}
|
||||
|
||||
private function menuTitleToZh(string $title): string
|
||||
{
|
||||
static $zhMap = null;
|
||||
if (!is_array($zhMap)) {
|
||||
$mapFile = app_path() . '/common/lang/zh-cn/admin_rule_title.php';
|
||||
$loaded = is_file($mapFile) ? include $mapFile : [];
|
||||
$zhMap = is_array($loaded) ? $loaded : [];
|
||||
}
|
||||
|
||||
return isset($zhMap[$title]) && is_string($zhMap[$title]) ? $zhMap[$title] : $title;
|
||||
}
|
||||
|
||||
private function menuTitleToEn(string $title): string
|
||||
{
|
||||
static $enMap = null;
|
||||
if (!is_array($enMap)) {
|
||||
$mapFile = app_path() . '/common/lang/en/admin_rule_title.php';
|
||||
$loaded = is_file($mapFile) ? include $mapFile : [];
|
||||
$enMap = is_array($loaded) ? $loaded : [];
|
||||
}
|
||||
|
||||
return isset($enMap[$title]) && is_string($enMap[$title]) ? $enMap[$title] : $title;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -208,6 +208,7 @@ Route::post('/admin/auth/group/add', [\app\admin\controller\auth\Group::class, '
|
||||
Route::post('/admin/auth/group/edit', [\app\admin\controller\auth\Group::class, 'edit']);
|
||||
Route::post('/admin/auth/group/del', [\app\admin\controller\auth\Group::class, 'del']);
|
||||
Route::get('/admin/auth/group/select', [\app\admin\controller\auth\Group::class, 'select']);
|
||||
Route::get('/admin/auth/group/rules', [\app\admin\controller\auth\Group::class, 'rules']);
|
||||
|
||||
// admin/auth/rule
|
||||
Route::get('/admin/auth/rule/index', [\app\admin\controller\auth\Rule::class, 'index']);
|
||||
|
||||
@@ -2,7 +2,7 @@ import createAxios from '/@/utils/axios'
|
||||
|
||||
export function getAdminRules() {
|
||||
return createAxios({
|
||||
url: '/admin/auth.Rule/index',
|
||||
url: '/admin/auth.Group/rules',
|
||||
method: 'get',
|
||||
params: {
|
||||
force_menu_zh: 1,
|
||||
|
||||
@@ -104,6 +104,14 @@ const baTable = new baTableClass(
|
||||
}
|
||||
)
|
||||
|
||||
// 编辑自身时不提交角色组,避免与后端「不可修改自己所在管理组」校验冲突
|
||||
baTable.before.onSubmit = ({ operate, items }) => {
|
||||
if (operate === 'edit' && items.id == adminInfo.id) {
|
||||
delete items.group_arr
|
||||
delete items.group_name_arr
|
||||
}
|
||||
}
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
baTable.mount()
|
||||
|
||||
@@ -162,6 +162,9 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: any, val: unknown, callback: Function) => {
|
||||
if (baTable.form.operate === 'Edit' && adminInfo.id == baTable.form.items?.id) {
|
||||
return callback()
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length !== 1) {
|
||||
return callback(new Error(t('auth.admin.Please select exactly one group')))
|
||||
|
||||
@@ -182,9 +182,17 @@ const menuRuleTreeUpdate = () => {
|
||||
if (baTable.form.items!.rules && baTable.form.items!.rules.length) {
|
||||
if (baTable.form.items!.rules.includes('*')) {
|
||||
let arr: number[] = []
|
||||
for (const key in baTable.form.extend!.menuRules) {
|
||||
arr.push(baTable.form.extend!.menuRules[key].id)
|
||||
const walk = (nodes: anyObj[]) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.id) {
|
||||
arr.push(node.id)
|
||||
}
|
||||
if (node.children?.length) {
|
||||
walk(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
walk(res.data.list || [])
|
||||
baTable.form.extend!.defaultCheckedKeys = arr
|
||||
} else {
|
||||
baTable.form.extend!.defaultCheckedKeys = baTable.form.items!.rules
|
||||
|
||||
Reference in New Issue
Block a user