1.修复角色组不能选择权限的报错
2.修复角色创建子角色报权限不够的问题
This commit is contained in:
@@ -253,28 +253,20 @@ class Admin extends Backend
|
|||||||
if (!$data) {
|
if (!$data) {
|
||||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||||
}
|
}
|
||||||
$data = $this->normalizeSingleGroup($data);
|
$isSelfEdit = (int) $this->auth->id === (int) $id;
|
||||||
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
|
if ($isSelfEdit) {
|
||||||
return $this->error(__('Please select exactly one role group'));
|
unset($data['group_arr'], $data['group_name_arr']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$postedGroups = array_map('intval', $data['group_arr'] ?? []);
|
$editGroupArr = null;
|
||||||
$rowGroups = array_map('intval', $row->group_arr ?? []);
|
if (array_key_exists('group_arr', $data)) {
|
||||||
sort($postedGroups);
|
$data = $this->normalizeSingleGroup($data);
|
||||||
sort($rowGroups);
|
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
|
||||||
|
return $this->error(__('Please select exactly one role group'));
|
||||||
// 当前管理员编辑自身时,不允许修改角色组
|
|
||||||
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 = $data['group_arr'];
|
||||||
|
} elseif (!$isSelfEdit) {
|
||||||
|
return $this->error(__('Please select exactly one role group'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->modelValidate) {
|
if ($this->modelValidate) {
|
||||||
@@ -285,8 +277,10 @@ class Admin extends Backend
|
|||||||
'password' => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
'password' => 'nullable|string|regex:/^(?!.*[&<>"\'\n\r]).{6,32}$/',
|
||||||
'email' => 'email|unique:admin,email,' . $id,
|
'email' => 'email|unique:admin,email,' . $id,
|
||||||
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile,' . $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 = [
|
$messages = [
|
||||||
'username.regex' => __('Please input correct username'),
|
'username.regex' => __('Please input correct username'),
|
||||||
'password.regex' => __('Please input correct password'),
|
'password.regex' => __('Please input correct password'),
|
||||||
@@ -306,10 +300,10 @@ class Admin extends Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
$groupAccess = [];
|
$groupAccess = [];
|
||||||
if (!empty($data['group_arr'])) {
|
if (!$isSelfEdit && !empty($editGroupArr)) {
|
||||||
$checkGroups = [];
|
$checkGroups = [];
|
||||||
$rowGroupArr = $row->group_arr ?? [];
|
$rowGroupArr = $row->group_arr ?? [];
|
||||||
foreach ($data['group_arr'] as $datum) {
|
foreach ($editGroupArr as $datum) {
|
||||||
if (!in_array($datum, $rowGroupArr)) {
|
if (!in_array($datum, $rowGroupArr)) {
|
||||||
$checkGroups[] = $datum;
|
$checkGroups[] = $datum;
|
||||||
}
|
}
|
||||||
@@ -323,32 +317,36 @@ class Admin extends Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->excludeFields($data);
|
$data = $this->excludeFields($data);
|
||||||
unset($data['invite_code']);
|
unset($data['invite_code'], $data['group_arr'], $data['group_name_arr']);
|
||||||
$creatorChannelId = $this->getCreatorChannelId();
|
if (!$isSelfEdit && $editGroupArr !== null) {
|
||||||
$groupChannelId = $this->resolveChannelIdFromPrimaryGroup($data['group_arr'] ?? []);
|
$creatorChannelId = $this->getCreatorChannelId();
|
||||||
if (!$this->auth->isSuperAdmin()) {
|
$groupChannelId = $this->resolveChannelIdFromPrimaryGroup($editGroupArr);
|
||||||
if ($creatorChannelId === null || $creatorChannelId === '') {
|
if (!$this->auth->isSuperAdmin()) {
|
||||||
return $this->error(__('You have no permission'));
|
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;
|
$result = false;
|
||||||
$this->model->startTrans();
|
$this->model->startTrans();
|
||||||
try {
|
try {
|
||||||
$result = $row->save($data);
|
$result = $row->save($data);
|
||||||
Db::name('admin_group_access')
|
if (!$isSelfEdit) {
|
||||||
->where('uid', $id)
|
Db::name('admin_group_access')
|
||||||
->delete();
|
->where('uid', $id)
|
||||||
if ($groupAccess) {
|
->delete();
|
||||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
if ($groupAccess) {
|
||||||
|
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->model->commit();
|
$this->model->commit();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ class Group extends Backend
|
|||||||
{
|
{
|
||||||
protected string $authMethod = 'allAuthAndOthers';
|
protected string $authMethod = 'allAuthAndOthers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色组表单分配权限树(仅需登录 + 具备角色组管理相关权限,不依赖菜单规则管理权限)
|
||||||
|
*/
|
||||||
|
protected array $noNeedPermission = ['rules'];
|
||||||
|
|
||||||
protected ?object $model = null;
|
protected ?object $model = null;
|
||||||
|
|
||||||
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
protected string|array $preExcludeFields = ['create_time', 'update_time'];
|
||||||
@@ -90,7 +95,7 @@ class Group extends Backend
|
|||||||
if ($inheritRes !== null) {
|
if ($inheritRes !== null) {
|
||||||
return $inheritRes;
|
return $inheritRes;
|
||||||
}
|
}
|
||||||
$rulesRes = $this->handleRules($data);
|
$rulesRes = $this->handleRules($data, $pidInt);
|
||||||
if ($rulesRes instanceof Response) return $rulesRes;
|
if ($rulesRes instanceof Response) return $rulesRes;
|
||||||
|
|
||||||
$result = false;
|
$result = false;
|
||||||
@@ -161,7 +166,7 @@ class Group extends Backend
|
|||||||
if ($inheritRes !== null) {
|
if ($inheritRes !== null) {
|
||||||
return $inheritRes;
|
return $inheritRes;
|
||||||
}
|
}
|
||||||
$rulesRes = $this->handleRules($data);
|
$rulesRes = $this->handleRules($data, $pidInt);
|
||||||
if ($rulesRes instanceof Response) return $rulesRes;
|
if ($rulesRes instanceof Response) return $rulesRes;
|
||||||
|
|
||||||
$result = false;
|
$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
|
* @return array|Response
|
||||||
*/
|
*/
|
||||||
private function handleRules(array &$data)
|
private function handleRules(array &$data, int $pidInt = 0)
|
||||||
{
|
{
|
||||||
if (!empty($data['rules']) && is_array($data['rules'])) {
|
if (!empty($data['rules']) && is_array($data['rules'])) {
|
||||||
$superAdmin = true;
|
$superAdmin = true;
|
||||||
@@ -310,7 +335,6 @@ class Group extends Backend
|
|||||||
$checkedRules[] = $postRuleId;
|
$checkedRules[] = $postRuleId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($allRuleIds as $ruleId) {
|
foreach ($allRuleIds as $ruleId) {
|
||||||
if (!in_array($ruleId, $checkedRules)) {
|
if (!in_array($ruleId, $checkedRules)) {
|
||||||
$superAdmin = false;
|
$superAdmin = false;
|
||||||
@@ -320,9 +344,15 @@ class Group extends Backend
|
|||||||
if ($superAdmin && $this->auth->isSuperAdmin()) {
|
if ($superAdmin && $this->auth->isSuperAdmin()) {
|
||||||
$data['rules'] = '*';
|
$data['rules'] = '*';
|
||||||
} else {
|
} 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!'));
|
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;
|
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
|
private function getGroups(Request $request, array $where = []): array
|
||||||
{
|
{
|
||||||
$pk = $this->model->getPk();
|
$pk = $this->model->getPk();
|
||||||
@@ -500,4 +547,82 @@ class Group extends Backend
|
|||||||
return null;
|
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/edit', [\app\admin\controller\auth\Group::class, 'edit']);
|
||||||
Route::post('/admin/auth/group/del', [\app\admin\controller\auth\Group::class, 'del']);
|
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/select', [\app\admin\controller\auth\Group::class, 'select']);
|
||||||
|
Route::get('/admin/auth/group/rules', [\app\admin\controller\auth\Group::class, 'rules']);
|
||||||
|
|
||||||
// admin/auth/rule
|
// admin/auth/rule
|
||||||
Route::get('/admin/auth/rule/index', [\app\admin\controller\auth\Rule::class, 'index']);
|
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() {
|
export function getAdminRules() {
|
||||||
return createAxios({
|
return createAxios({
|
||||||
url: '/admin/auth.Rule/index',
|
url: '/admin/auth.Group/rules',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
force_menu_zh: 1,
|
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)
|
provide('baTable', baTable)
|
||||||
|
|
||||||
baTable.mount()
|
baTable.mount()
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
|||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: (_rule: any, val: unknown, callback: Function) => {
|
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 (Array.isArray(val)) {
|
||||||
if (val.length !== 1) {
|
if (val.length !== 1) {
|
||||||
return callback(new Error(t('auth.admin.Please select exactly one group')))
|
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 && baTable.form.items!.rules.length) {
|
||||||
if (baTable.form.items!.rules.includes('*')) {
|
if (baTable.form.items!.rules.includes('*')) {
|
||||||
let arr: number[] = []
|
let arr: number[] = []
|
||||||
for (const key in baTable.form.extend!.menuRules) {
|
const walk = (nodes: anyObj[]) => {
|
||||||
arr.push(baTable.form.extend!.menuRules[key].id)
|
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
|
baTable.form.extend!.defaultCheckedKeys = arr
|
||||||
} else {
|
} else {
|
||||||
baTable.form.extend!.defaultCheckedKeys = baTable.form.items!.rules
|
baTable.form.extend!.defaultCheckedKeys = baTable.form.items!.rules
|
||||||
|
|||||||
Reference in New Issue
Block a user