From bd20d523f0844a3858f51a3f46e14649050029f4 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 24 Sep 2026 17:56:31 +0800 Subject: [PATCH 1/3] feat(ui): add system admin permission enum and i18n Expose GlobalManagement option label for zjrc user center forms. Co-authored-by: Cursor --- packages/base/src/locale/en-US/dmsUserCenter.ts | 5 ++++- packages/base/src/locale/zh-CN/dmsUserCenter.ts | 4 +++- packages/shared/lib/enum/index.ts | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/base/src/locale/en-US/dmsUserCenter.ts b/packages/base/src/locale/en-US/dmsUserCenter.ts index 87c969a9a1..aea92c56c6 100644 --- a/packages/base/src/locale/en-US/dmsUserCenter.ts +++ b/packages/base/src/locale/en-US/dmsUserCenter.ts @@ -28,7 +28,10 @@ export default { opPermissions: 'Platform management permissions', isDisabled: 'Disabled', disabledTips: - 'When the user is disabled, the user will not be able to log in' + 'When the user is disabled, the user will not be able to log in', + globalManagementLockedTips: + 'Only the built-in admin can grant or revoke the System Administrator permission', + manageTargetForbidden: 'You are not allowed to manage this user' }, createUser: { createSuccessTips: 'Add user "{{name}}" successfully' diff --git a/packages/base/src/locale/zh-CN/dmsUserCenter.ts b/packages/base/src/locale/zh-CN/dmsUserCenter.ts index 8c0d4a4473..dcc92ed288 100644 --- a/packages/base/src/locale/zh-CN/dmsUserCenter.ts +++ b/packages/base/src/locale/zh-CN/dmsUserCenter.ts @@ -25,7 +25,9 @@ export default { userGroups: '所属用户组', opPermissions: '平台管理权限', isDisabled: '是否禁用', - disabledTips: '当用户被禁用,该用户将无法登录' + disabledTips: '当用户被禁用,该用户将无法登录', + globalManagementLockedTips: '仅内置管理员可授予或收回「系统管理员」权限', + manageTargetForbidden: '无权管理该用户' }, createUser: { createSuccessTips: '添加用户 "{{name}}" 成功' diff --git a/packages/shared/lib/enum/index.ts b/packages/shared/lib/enum/index.ts index 1e56f3c9df..7378fe724e 100644 --- a/packages/shared/lib/enum/index.ts +++ b/packages/shared/lib/enum/index.ts @@ -62,7 +62,8 @@ export enum OpPermissionTypeUid { 'view_others_workflow' = '700007', // 查看其他工单权限 700007 'view_other_audit_plan' = '700008', // 查看其他扫描任务权限 700008 'save_audit_plan' = '700009', // 创建扫描任务权限;拥有该权限的用户可以创建/更新扫描任务 700009 - 'sql_query' = '700010' //SQL查询;SQL查询权限 700010 + 'sql_query' = '700010', //SQL查询;SQL查询权限 700010 + 'global_management' = '700017' // 系统管理员;可进行系统配置、用户管理等,权限低于内置 admin 700017 } export enum CompanyNoticeDisplayStatusEnum { From b7b8a5ea99bdd2f5883877528b7e7c43fcbcd048 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 24 Sep 2026 17:56:31 +0800 Subject: [PATCH 2/3] feat(ui): wire system admin form options and edit boundary User create/update shows system admin; non-built-in admins cannot grant it or edit other system admins; list displays the permission. Co-authored-by: Cursor --- .../Modal/User/AddUser/index.test.tsx | 2 + .../UserCenter/Modal/User/AddUser/index.tsx | 40 ++++++++++++++-- .../Modal/User/UpdateUser/index.test.tsx | 3 ++ .../Modal/User/UpdateUser/index.tsx | 42 +++++++++++++++-- .../UserCenter/Modal/User/UserForm/index.tsx | 34 ++++++++++++-- .../Modal/User/UserForm/index.type.ts | 3 ++ .../__snapshots__/index.test.tsx.snap | 33 ++----------- .../UserCenter/components/UserList/List.tsx | 12 ++++- .../UserList/__tests__/UserList.test.tsx | 6 ++- .../__snapshots__/UserList.test.tsx.snap | 11 ++++- .../UserCenter/components/UserList/column.tsx | 18 +++++--- .../UserCenter/utils/systemAdminBoundary.ts | 46 +++++++++++++++++++ 12 files changed, 199 insertions(+), 51 deletions(-) create mode 100644 packages/base/src/page/UserCenter/utils/systemAdminBoundary.ts diff --git a/packages/base/src/page/UserCenter/Modal/User/AddUser/index.test.tsx b/packages/base/src/page/UserCenter/Modal/User/AddUser/index.test.tsx index 2667fbced7..e5c98f2094 100644 --- a/packages/base/src/page/UserCenter/Modal/User/AddUser/index.test.tsx +++ b/packages/base/src/page/UserCenter/Modal/User/AddUser/index.test.tsx @@ -8,6 +8,7 @@ import { ModalName } from '../../../../../data/ModalName'; import { selectOptionByIndex } from '@actiontech/shared/lib/testUtil/customQuery'; import EmitterKey from '../../../../../data/EmitterKey'; import { queryBySelector } from '@actiontech/shared/lib/testUtil/customQuery'; +import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; jest.mock('react-redux', () => { return { @@ -23,6 +24,7 @@ describe('base/UserCenter/Modal/AddUser', () => { let addUserSpy: jest.SpyInstance; beforeEach(() => { jest.useFakeTimers(); + mockUseCurrentUser({ username: 'admin', uid: '700200' }); addUserSpy = userCenter.addUser(); opPermissionListSpy = userCenter.getOpPermissionsList(); diff --git a/packages/base/src/page/UserCenter/Modal/User/AddUser/index.tsx b/packages/base/src/page/UserCenter/Modal/User/AddUser/index.tsx index 6986d20adc..66aefe7549 100644 --- a/packages/base/src/page/UserCenter/Modal/User/AddUser/index.tsx +++ b/packages/base/src/page/UserCenter/Modal/User/AddUser/index.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useBoolean } from 'ahooks'; import { Form, message, Space } from 'antd'; import { useTranslation } from 'react-i18next'; @@ -6,13 +6,15 @@ import { useDispatch, useSelector } from 'react-redux'; import { ModalName } from '../../../../../data/ModalName'; import { IReduxState } from '../../../../../store'; import { updateUserManageModalStatus } from '../../../../../store/userCenter'; -import { ResponseCode } from '@actiontech/shared/lib/enum'; +import { OpPermissionTypeUid, ResponseCode } from '@actiontech/shared/lib/enum'; import EmitterKey from '../../../../../data/EmitterKey'; import UserForm from '../UserForm'; import { IUserFormFields } from '../UserForm/index.type'; import EventEmitter from '../../../../../utils/EventEmitter'; import { BasicDrawer, BasicButton } from '@actiontech/shared'; import User from '@actiontech/shared/lib/api/base/service/User'; +import { useCurrentUser } from '@actiontech/shared/lib/global'; +import { canEditGlobalManagement } from '../../../utils/systemAdminBoundary'; const AddUser = () => { const [form] = Form.useForm(); @@ -27,6 +29,12 @@ const AddUser = () => { (state) => !!state.userCenter.modalStatus[ModalName.DMS_Add_User] ); + const { username, uid } = useCurrentUser(); + const allowEditGlobalManagement = useMemo( + () => canEditGlobalManagement({ name: username, uid }), + [username, uid] + ); + const [messageApi, contextHolder] = message.useMessage(); const onClose = useCallback(() => { @@ -41,6 +49,16 @@ const AddUser = () => { const addUser = useCallback(async () => { const values = await form.validateFields(); + const opPermissionUids = values.opPermissionUids ?? []; + if ( + !allowEditGlobalManagement && + opPermissionUids.includes(OpPermissionTypeUid.global_management) + ) { + messageApi.error( + t('dmsUserCenter.user.userForm.globalManagementLockedTips') + ); + return; + } setTrue(); User.AddUser({ user: { @@ -49,7 +67,7 @@ const AddUser = () => { email: values.email ?? '', phone: values.phone ?? '', wxid: values.wxid ?? '', - op_permission_uids: values.opPermissionUids ?? [] + op_permission_uids: opPermissionUids } }) .then((res) => { @@ -66,7 +84,15 @@ const AddUser = () => { .finally(() => { setFalse(); }); - }, [onClose, form, setFalse, setTrue, t, messageApi]); + }, [ + allowEditGlobalManagement, + onClose, + form, + setFalse, + setTrue, + t, + messageApi + ]); return ( { } > {contextHolder} - + ); }; diff --git a/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.test.tsx b/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.test.tsx index d62df9904c..97652d196a 100644 --- a/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.test.tsx +++ b/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.test.tsx @@ -8,6 +8,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { ModalName } from '../../../../../data/ModalName'; import EmitterKey from '../../../../../data/EmitterKey'; import { queryBySelector } from '@actiontech/shared/lib/testUtil/customQuery'; +import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -22,6 +23,8 @@ describe('base/UserCenter/Modal/UpdateUser', () => { const mockUserData = userList[0]; beforeEach(() => { jest.useFakeTimers(); + // UpdateUser 经 useCurrentUser 读当前操作者;需内置 admin 才能编辑系统管理员边界 + mockUseCurrentUser({ username: 'admin', uid: '700200' }); updateUserSpy = userCenter.updateUser(); opPermissionListSpy = userCenter.getOpPermissionsList(); diff --git a/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.tsx b/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.tsx index 447c09796d..a38976c5c9 100644 --- a/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.tsx +++ b/packages/base/src/page/UserCenter/Modal/User/UpdateUser/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useCallback } from 'react'; +import { useEffect, useCallback, useMemo } from 'react'; import { Form, message, Space } from 'antd'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -9,7 +9,7 @@ import UserForm from '../UserForm'; import { IUserFormFields } from '../UserForm/index.type'; import { ModalName } from '../../../../../data/ModalName'; import { updateUserManageModalStatus } from '../../../../../store/userCenter'; -import { ResponseCode } from '@actiontech/shared/lib/enum'; +import { OpPermissionTypeUid, ResponseCode } from '@actiontech/shared/lib/enum'; import EventEmitter from '../../../../../utils/EventEmitter'; import { IListUser, @@ -19,6 +19,11 @@ import User from '@actiontech/shared/lib/api/base/service/User'; import { ListUserStatEnum } from '@actiontech/shared/lib/api/base/service/common.enum'; import { BasicDrawer, BasicButton } from '@actiontech/shared'; import { SystemRole } from '@actiontech/shared/lib/enum'; +import { useCurrentUser } from '@actiontech/shared/lib/global'; +import { + canEditGlobalManagement, + canManageTarget +} from '../../../utils/systemAdminBoundary'; const UpdateUser = () => { const [form] = Form.useForm(); @@ -37,6 +42,16 @@ const UpdateUser = () => { (state) => state.userCenter.selectUser ); + const { username, uid } = useCurrentUser(); + const currentOperator = useMemo( + () => ({ name: username, uid }), + [username, uid] + ); + const allowEditGlobalManagement = useMemo( + () => canEditGlobalManagement(currentOperator), + [currentOperator] + ); + const [messageApi, contextHolder] = message.useMessage(); const onClose = useCallback(() => { @@ -52,12 +67,32 @@ const UpdateUser = () => { const updateUser = async () => { const values = await form.validateFields(); + if (!canManageTarget(currentOperator, currentUser)) { + messageApi.error(t('dmsUserCenter.user.userForm.manageTargetForbidden')); + return; + } + + const nextOpUids = values.opPermissionUids ?? []; + const prevHadGlobal = + currentUser?.op_permissions?.some( + (p) => p.uid === OpPermissionTypeUid.global_management + ) ?? false; + const nextHasGlobal = nextOpUids.includes( + OpPermissionTypeUid.global_management + ); + if (!allowEditGlobalManagement && prevHadGlobal !== nextHasGlobal) { + messageApi.error( + t('dmsUserCenter.user.userForm.globalManagementLockedTips') + ); + return; + } + const userParams: IUpdateUser = { password: values.passwordConfirm, email: values.email ?? '', phone: values.phone ?? '', wxid: values.wxid ?? '', - op_permission_uids: values.opPermissionUids ?? [], + op_permission_uids: nextOpUids, is_disabled: values.username !== 'admin' ? !!values.isDisabled : false }; setTrue(); @@ -124,6 +159,7 @@ const UpdateUser = () => { visible={visible} isUpdate={true} isAdmin={currentUser?.name === SystemRole.admin} + canEditGlobalManagement={allowEditGlobalManagement} /> ); diff --git a/packages/base/src/page/UserCenter/Modal/User/UserForm/index.tsx b/packages/base/src/page/UserCenter/Modal/User/UserForm/index.tsx index 1e457aaf7a..d20ceeb290 100644 --- a/packages/base/src/page/UserCenter/Modal/User/UserForm/index.tsx +++ b/packages/base/src/page/UserCenter/Modal/User/UserForm/index.tsx @@ -1,7 +1,7 @@ import { IUserFormProps } from './index.type'; import { Form, Switch } from 'antd'; import { BasicInput, BasicSelect } from '@actiontech/shared'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Rule } from 'antd/es/form'; import { nameRule, phoneRule } from '@actiontech/shared/lib/utils/FormRule'; @@ -9,9 +9,11 @@ import EmptyBox from '@actiontech/shared/lib/components/EmptyBox'; import { BasicToolTips } from '@actiontech/shared'; import useOpPermission from '../../../../../hooks/useOpPermission'; import { ListOpPermissionsFilterByTargetEnum } from '@actiontech/shared/lib/api/base/service/OpPermission/index.enum'; +import { OpPermissionTypeUid } from '@actiontech/shared/lib/enum'; const UserForm: React.FC = (props) => { const { t } = useTranslation(); + const canEditGlobalManagement = props.canEditGlobalManagement !== false; const { loading: getOpPermissionListLoading, @@ -19,6 +21,18 @@ const UserForm: React.FC = (props) => { updateOpPermissionList } = useOpPermission(); + const permissionOptions = useMemo(() => { + if (canEditGlobalManagement) { + return opPermissionOptions; + } + return opPermissionOptions.map((opt) => { + if (opt.value === OpPermissionTypeUid.global_management) { + return { ...opt, disabled: true }; + } + return opt; + }); + }, [canEditGlobalManagement, opPermissionOptions]); + const userNameRules = (): Rule[] => { const baseRules = [ { @@ -164,7 +178,21 @@ const UserForm: React.FC = (props) => { + {t('dmsUserCenter.user.userForm.opPermissions')} + + ) + } > = (props) => { placeholder={t('common.form.placeholder.select', { name: t('dmsUserCenter.user.userForm.opPermissions') })} - options={opPermissionOptions} + options={permissionOptions} optionFilterProp="label" /> diff --git a/packages/base/src/page/UserCenter/Modal/User/UserForm/index.type.ts b/packages/base/src/page/UserCenter/Modal/User/UserForm/index.type.ts index 89b5b67a0a..d67c1a601f 100644 --- a/packages/base/src/page/UserCenter/Modal/User/UserForm/index.type.ts +++ b/packages/base/src/page/UserCenter/Modal/User/UserForm/index.type.ts @@ -16,5 +16,8 @@ export interface IUserFormProps { form: FormInstance; visible: boolean; isUpdate?: boolean; + /** 目标用户是否为 admin(控制禁用开关展示;勿与「当前操作者是否内置」混淆) */ isAdmin?: boolean; + /** 当前操作者是否可授予/收回「系统管理员」;false 时锁定 700017 选项 */ + canEditGlobalManagement?: boolean; } diff --git a/packages/base/src/page/UserCenter/__tests__/__snapshots__/index.test.tsx.snap b/packages/base/src/page/UserCenter/__tests__/__snapshots__/index.test.tsx.snap index 05c7174356..5c5a3a6a3b 100644 --- a/packages/base/src/page/UserCenter/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/base/src/page/UserCenter/__tests__/__snapshots__/index.test.tsx.snap @@ -650,16 +650,7 @@ exports[`base/UserCenter should render user list when it first entered the user
- -
+ />
@@ -1483,16 +1474,7 @@ exports[`base/UserCenter switch to operate permission list 1`] = `
- -
+ />
@@ -2614,16 +2596,7 @@ exports[`base/UserCenter switch to role list 1`] = `
- -
+ />
diff --git a/packages/base/src/page/UserCenter/components/UserList/List.tsx b/packages/base/src/page/UserCenter/components/UserList/List.tsx index fde1364b72..0b2d43c873 100644 --- a/packages/base/src/page/UserCenter/components/UserList/List.tsx +++ b/packages/base/src/page/UserCenter/components/UserList/List.tsx @@ -13,6 +13,7 @@ import { ResponseCode } from '@actiontech/shared/lib/enum'; import { IListUser } from '@actiontech/shared/lib/api/base/service/common'; import { IListUsersParams } from '@actiontech/shared/lib/api/base/service/User/index.d'; import User from '@actiontech/shared/lib/api/base/service/User'; +import { useCurrentUser } from '@actiontech/shared/lib/global'; import { UserCenterListEnum } from '../../index.enum'; import { updateSelectUser, @@ -32,6 +33,8 @@ const UserList: React.FC<{ activePage: UserCenterListEnum }> = ({ const dispatch = useDispatch(); + const { username, uid } = useCurrentUser(); + const { requestErrorMessage, handleTableRequestError } = useTableRequestError(); @@ -97,9 +100,14 @@ const UserList: React.FC<{ activePage: UserCenterListEnum }> = ({ [refresh, t, messageApi] ); + const currentOperator = useMemo( + () => ({ name: username, uid }), + [username, uid] + ); + const actions = useMemo(() => { - return UserListActions(onEditUser, onDeleteUser); - }, [onEditUser, onDeleteUser]); + return UserListActions(onEditUser, onDeleteUser, currentOperator); + }, [onEditUser, onDeleteUser, currentOperator]); useEffect(() => { const { unsubscribe } = EventEmitter.subscribe( diff --git a/packages/base/src/page/UserCenter/components/UserList/__tests__/UserList.test.tsx b/packages/base/src/page/UserCenter/components/UserList/__tests__/UserList.test.tsx index 9ae1558a28..da2bda6be3 100644 --- a/packages/base/src/page/UserCenter/components/UserList/__tests__/UserList.test.tsx +++ b/packages/base/src/page/UserCenter/components/UserList/__tests__/UserList.test.tsx @@ -17,6 +17,7 @@ import { ModalName } from '../../../../../data/ModalName'; import EventEmitter from '../../../../../utils/EventEmitter'; import EmitterKey from '../../../../../data/EmitterKey'; import { UserCenterListEnum } from '../../../index.enum'; +import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; jest.mock('react-redux', () => { return { @@ -30,6 +31,8 @@ describe('base/UserCenter/UserList', () => { const dispatchSpy = jest.fn(); beforeEach(() => { jest.useFakeTimers(); + // 内置 admin:可管理列表内含 admin 在内的全部用户行 + mockUseCurrentUser({ username: 'admin', uid: '700200' }); userListSpy = userCenter.getUserList(); (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); }); @@ -64,7 +67,8 @@ describe('base/UserCenter/UserList', () => { expect(screen.getByText('test')).toBeInTheDocument(); expect(screen.getByText('test666')).toBeInTheDocument(); expect(screen.getAllByText('dms')).toHaveLength(3); - expect(screen.getAllByText('删 除')).toHaveLength(2); + // 内置 admin 操作者:可管理/删除含 admin 在内的全部行(canManageTarget) + expect(screen.getAllByText('删 除')).toHaveLength(3); expect(screen.getAllByText('管 理')).toHaveLength(3); }); diff --git a/packages/base/src/page/UserCenter/components/UserList/__tests__/__snapshots__/UserList.test.tsx.snap b/packages/base/src/page/UserCenter/components/UserList/__tests__/__snapshots__/UserList.test.tsx.snap index 95bb555b23..342fb2ce62 100644 --- a/packages/base/src/page/UserCenter/components/UserList/__tests__/__snapshots__/UserList.test.tsx.snap +++ b/packages/base/src/page/UserCenter/components/UserList/__tests__/__snapshots__/UserList.test.tsx.snap @@ -495,7 +495,16 @@ exports[`base/UserCenter/UserList render user table 1`] = `
+ > + +
diff --git a/packages/base/src/page/UserCenter/components/UserList/column.tsx b/packages/base/src/page/UserCenter/components/UserList/column.tsx index 9b6f3737bb..34578169ae 100644 --- a/packages/base/src/page/UserCenter/components/UserList/column.tsx +++ b/packages/base/src/page/UserCenter/components/UserList/column.tsx @@ -7,8 +7,11 @@ import { ListUserStatEnum } from '@actiontech/shared/lib/api/base/service/common import { orderBy } from 'lodash'; import { t } from '../../../../locale'; import { TableColumnWithIconStyleWrapper } from '@actiontech/shared/lib/styleWrapper/element'; -import { SystemRole } from '@actiontech/shared/lib/enum'; import { CheckHexagonOutlined, CloseHexagonOutlined } from '@actiontech/icons'; +import { + canManageTarget, + UserIdentityLike +} from '../../utils/systemAdminBoundary'; export const UserListColumns: () => ActiontechTableColumn = () => [ { @@ -65,8 +68,12 @@ export const UserListColumns: () => ActiontechTableColumn = () => [ export const UserListActions = ( onEditUser: (record?: IListUser) => void, - onDeleteUser: (record?: IListUser) => void + onDeleteUser: (record?: IListUser) => void, + currentOperator?: UserIdentityLike | null ): ActiontechTableActionMeta[] => { + const canOperate = (record?: IListUser) => + canManageTarget(currentOperator, record); + return [ { text: t('common.manage'), @@ -77,7 +84,8 @@ export const UserListActions = ( onEditUser(record); } }; - } + }, + permissions: (record) => canOperate(record) }, { text: t('common.delete'), @@ -95,9 +103,7 @@ export const UserListActions = ( } }; }, - permissions: (record) => { - return record?.name !== SystemRole.admin; - } + permissions: (record) => canOperate(record) } ]; }; diff --git a/packages/base/src/page/UserCenter/utils/systemAdminBoundary.ts b/packages/base/src/page/UserCenter/utils/systemAdminBoundary.ts new file mode 100644 index 0000000000..65740de8ab --- /dev/null +++ b/packages/base/src/page/UserCenter/utils/systemAdminBoundary.ts @@ -0,0 +1,46 @@ +import { OpPermissionTypeUid } from '@actiontech/shared/lib/enum'; + +export type UserIdentityLike = { + name?: string; + username?: string; + uid?: string; + op_permissions?: Array<{ uid?: string } | null> | null; +}; + +const BUILTIN_ADMIN_NAMES = new Set(['admin', 'sys']); +const BUILTIN_ADMIN_UIDS = new Set(['700200', '700201']); + +/** 内置 admin / sys(对齐后端 IsUserDMSAdmin);禁止用 isAdmin 推断 */ +export const isBuiltInAdminUser = (user?: UserIdentityLike | null): boolean => { + const name = user?.name ?? user?.username ?? ''; + const uid = user?.uid ?? ''; + return BUILTIN_ADMIN_NAMES.has(name) || BUILTIN_ADMIN_UIDS.has(uid); +}; + +/** 目标用户是否持有系统管理员(700017);看 op_permissions,不用 is_admin */ +export const isSystemAdminUser = (user?: UserIdentityLike | null): boolean => { + return ( + user?.op_permissions?.some( + (p) => p?.uid === OpPermissionTypeUid.global_management + ) ?? false + ); +}; + +/** 当前操作者能否管理目标用户(管理/删除) */ +export const canManageTarget = ( + current?: UserIdentityLike | null, + target?: UserIdentityLike | null +): boolean => { + if (isBuiltInAdminUser(current)) { + return true; + } + if (!target) { + return false; + } + return !isBuiltInAdminUser(target) && !isSystemAdminUser(target); +}; + +/** 当前操作者能否授予/收回「系统管理员」 */ +export const canEditGlobalManagement = ( + current?: UserIdentityLike | null +): boolean => isBuiltInAdminUser(current); From b8b9b23f8b6f64d076c6bae34d791c0cfc1ff055 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Thu, 24 Sep 2026 17:56:31 +0800 Subject: [PATCH 3/3] chore(ui): refresh branch version stamp and VersionModal snapshots Align version string and EE/CE VersionModal snaps with local CI. Co-authored-by: Cursor --- .../test/__snapshots__/BasicVersionModal.test.tsx.snap | 4 ++-- .../VersionModal/test/__snapshots__/index.ce.test.tsx.snap | 4 ++-- .../Modal/VersionModal/test/__snapshots__/index.test.tsx.snap | 4 ++-- packages/base/src/scripts/version.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap b/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap index b30049a529..3168ec2d4c 100644 --- a/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap +++ b/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap @@ -110,7 +110,7 @@ exports[`base/Nav/SideMenu/UserMenu/BasicVersionModal render snap when open is t class="ant-typography css-dev-only-do-not-override-txh9fw" > UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02
UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02
UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02
UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02
UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02
UI: - dev-zjrc-error-p0p1 93c9fa7e2 + dev-zjrc-user-system-admin f4fab7b02