From 9e9adc880c7a9029ff04292f1aaab404c4ea2d5a Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Mon, 10 Aug 2026 18:25:33 +0800 Subject: [PATCH] feat: update user group management with workspace-specific APIs and improve member search functionality --- apps/common/log/log.py | 3 +- apps/users/serializers/user_group.py | 1 + ui/src/api/admin/system/user-groups.ts | 108 +++++++++---- ui/src/api/admin/system/user-manage.ts | 9 ++ .../system/identity/groups/GroupsListView.vue | 151 ++++++++++-------- .../groups/dialog/CreateGroupMemberDialog.vue | 42 +++-- .../dialog/CreateOrUpdateGroupDialog.vue | 9 +- 7 files changed, 209 insertions(+), 114 deletions(-) diff --git a/apps/common/log/log.py b/apps/common/log/log.py index 502cc3b05f6..272acef689d 100644 --- a/apps/common/log/log.py +++ b/apps/common/log/log.py @@ -33,6 +33,8 @@ def _get_user(request): user = request.user if user is None: return {} + if hasattr(user, 'profile') and user.profile is not None: + user = user.profile user_info = { "id": str(user.id), "email": user.email, @@ -40,7 +42,6 @@ def _get_user(request): "nick_name": user.nick_name, "username": user.username, } - # 如果是 User 模型且有 role 属性 if hasattr(user, "role"): user_info["role"] = user.role return user_info diff --git a/apps/users/serializers/user_group.py b/apps/users/serializers/user_group.py index 5fbadc6b940..6c67404f343 100644 --- a/apps/users/serializers/user_group.py +++ b/apps/users/serializers/user_group.py @@ -144,6 +144,7 @@ def validate_normal_users(self, workspace_id: str, user_ids: list[str]): mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping") valid_user_ids = set( + str(uid) for uid in mapping_model.objects.filter( workspace_id=workspace_id, user_id__in=user_id_set, diff --git a/ui/src/api/admin/system/user-groups.ts b/ui/src/api/admin/system/user-groups.ts index 81e05918d46..6b3989ef9e2 100644 --- a/ui/src/api/admin/system/user-groups.ts +++ b/ui/src/api/admin/system/user-groups.ts @@ -1,44 +1,82 @@ -import type { ListItem } from '@/api/types' +import { del, get, post } from '../core/request' +import type { ParamsPage, ResponsePage } from '../core/types' +import type { ListItem, RequestParams } from '@/api/types' -export interface SystemUserGroupRequest { - id?: string +export interface SystemUserGroup { + id: string name: string - workspaceId: string -} - -/** - * 创建或重命名系统用户组。 - * TODO 后端接口就绪后,在此接入 Admin 请求层并移除本地占位返回。 - */ -function postSystemUserGroup(group: SystemUserGroupRequest) { - return Promise.resolve({ - id: group.id ?? crypto.randomUUID(), - name: group.name, - }) -} - -/** - * 删除系统用户组。 - * TODO 后端接口就绪后,在此接入 Admin 请求层并移除本地占位返回。 - */ -function deleteSystemUserGroup(_workspaceId: string, _groupId: string) { - return Promise.resolve(true) -} - -/** - * 移除系统用户组成员。 - * TODO 后端接口就绪后,在此接入 Admin 请求层并移除本地占位返回。 - */ -function postRemoveSystemUserGroupMembers( - _workspaceId: string, - _groupId: string, - _memberIds: Array, + workspace_id: string + count: number +} + +export interface SystemUserGroupMember { + id: string + username: string + email: string + phone: string + is_active: boolean + role: string + nick_name: string + create_time: string + update_time: string + source: string + system_user_group_relation_id: string +} + +const prefix = (workspaceId: string) => `/system/workspace/${workspaceId}/user_group` + +export function getSystemUserGroups(workspaceId: string) { + return get(prefix(workspaceId)) +} + +export function postSystemUserGroup(workspaceId: string, group: { id?: string; name: string }) { + return post<{ id?: string; name: string }, SystemUserGroup>(prefix(workspaceId), group) +} + +export function deleteSystemUserGroup(workspaceId: string, groupId: string) { + return del(`${prefix(workspaceId)}/${groupId}`) +} + +export function getSystemUserGroupMembers( + workspaceId: string, + groupId: string, + page: ParamsPage, + query?: RequestParams, ) { - return Promise.resolve(true) + return get>( + `${prefix(workspaceId)}/${groupId}/user_list/${page.currentPage}/${page.pageSize}`, + query, + ) +} + +export function postSystemUserGroupMembers( + workspaceId: string, + groupId: string, + userIds: string[], +) { + return post<{ user_ids: string[] }, boolean>( + `${prefix(workspaceId)}/${groupId}/add_member`, + { user_ids: userIds }, + ) +} + +export function postRemoveSystemUserGroupMembers( + workspaceId: string, + groupId: string, + relationIds: string[], +) { + return del<{ group_relation_ids: string[] }, boolean>( + `${prefix(workspaceId)}/${groupId}/remove_member`, + undefined, + { group_relation_ids: relationIds }, + ) } export default { deleteSystemUserGroup, - postSystemUserGroup, + getSystemUserGroupMembers, + getSystemUserGroups, postRemoveSystemUserGroupMembers, + postSystemUserGroup, + postSystemUserGroupMembers, } diff --git a/ui/src/api/admin/system/user-manage.ts b/ui/src/api/admin/system/user-manage.ts index e81a9cd2d2a..ba1822c416b 100644 --- a/ui/src/api/admin/system/user-manage.ts +++ b/ui/src/api/admin/system/user-manage.ts @@ -57,6 +57,14 @@ function getAllUsers(query?: RequestParams) { return get('/user/list', query) } +/** 获取空间下的成员用户 + * @param workspaceId 工作空间ID + * 资源授权 和用户组的 用户下拉列表使用 + */ +function getWorkspaceMembers(workspaceId: string, query?: RequestParams) { + return get(`/workspace/${workspaceId}/user_member`, query) +} + export default { deleteUser, getUserManagePage, @@ -67,4 +75,5 @@ export default { putUser, putUserPassword, getAllUsers, + getWorkspaceMembers, } diff --git a/ui/src/views/system/identity/groups/GroupsListView.vue b/ui/src/views/system/identity/groups/GroupsListView.vue index 6e76e13fbe3..193218722b3 100644 --- a/ui/src/views/system/identity/groups/GroupsListView.vue +++ b/ui/src/views/system/identity/groups/GroupsListView.vue @@ -2,6 +2,7 @@ import { onMounted, ref, useTemplateRef } from 'vue' import WorkspaceApi from '@/api/admin/system/workspace' import UserGroupsApi from '@/api/admin/system/user-groups' +import type { SystemUserGroup, SystemUserGroupMember } from '@/api/admin/system/user-groups' import type { RequestParams, OptionItem, WorkspaceItem } from '@/api/types' import { MsgConfirm, MsgSuccess } from '@/utils/message' import MkWorkspaceDropdown from '@/components/mk-workspace-dropdown/index.vue' @@ -10,136 +11,154 @@ import MkWorkspaceRelationTags from '@/components/mk-workspace-relation-tags/ind import CreateOrUpdateGroupDialog from './dialog/CreateOrUpdateGroupDialog.vue' import CreateGroupMemberDialog from './dialog/CreateGroupMemberDialog.vue' -interface UserGroup { - id: string - memberCount: number - name: string -} - -interface UserGroupMember { - id: number - name: string - roles: string[] - source: string - username: string -} - /* 成员列表相关 */ +const loadingMembers = ref(false) const paginationConfig = ref({ - currentPage: 2, + currentPage: 1, pageSize: 10, - total: 20, + total: 0, }) const memberSearchQuery = ref() const memberSearchFields: OptionItem[] = [ { label: '用户名', value: 'username' }, - { label: '姓名', value: 'name' }, + { label: '姓名', value: 'nick_name' }, ] -const userGroupMembers = ref([ - { id: 1, name: 'test-w', username: 'test-w', roles: ['工作空间管理员'], source: '系统用户' }, - { id: 2, name: 'Eira1', username: 'Eira1', roles: ['普通用户', '管理员'], source: '钉钉' }, - { id: 3, name: '司马图南', username: 'simatunan', roles: ['普通用户'], source: '钉钉' }, - { id: 4, name: '吕晓', username: 'lvxiao', roles: ['usso-工作空间管理员'], source: 'CAS' }, - { id: 5, name: '涂晓', username: 'tuixao', roles: ['普通用户'], source: 'LDAP' }, - { id: 6, name: '裴尔', username: 'peier', roles: ['普通用户'], source: 'OIDC' }, - { id: 7, name: '裴尔尔', username: 'peierer', roles: ['普通用户'], source: 'OAuth2' }, - { id: 8, name: '裴晓尔', username: 'peixiaoer', roles: ['普通用户'], source: '企业微信' }, - { id: 9, name: 'shaohu', username: 'shaohu', roles: ['普通用户'], source: '企业微信' }, - { id: 10, name: '白新', username: 'baixin', roles: ['普通用户'], source: '飞书' }, -]) +const userGroupMembers = ref([]) function handleMemberSearch(query?: RequestParams) { memberSearchQuery.value = query paginationConfig.value.currentPage = 1 - return + loadUserGroupMembers() } -const selectedGroupMembers = ref([]) +const selectedGroupMembers = ref([]) function handleMemberSelectionChange(selection: unknown[]) { - selectedGroupMembers.value = selection as UserGroupMember[] + selectedGroupMembers.value = selection as SystemUserGroupMember[] } /* 添加成员drawer */ const memberDialogRef = useTemplateRef>('memberDialogRef') -function handleRemoveMembers(member?: UserGroupMember) { +function handleRemoveMembers(member?: SystemUserGroupMember) { const group = currentGroup.value const members = member ? [member] : selectedGroupMembers.value if (!group || !members.length) return + const targetName = member ? member.nick_name || member.username : '' const title = member - ? `是否移除成员:${member.name}?` + ? `是否移除成员:${targetName}?` : `是否移除选中的 ${members.length} 个成员?` MsgConfirm(title, '', { confirmButtonText: '移除' }) .then(() => { return UserGroupsApi.postRemoveSystemUserGroupMembers( selectedWorkspaceId.value, group.id, - members.map(({ id }) => id), + members.map(({ system_user_group_relation_id }) => system_user_group_relation_id), ).then(() => { MsgSuccess('移除成功') - const removedMemberIds = new Set(members.map(({ id }) => id)) + const removedRelationIds = new Set( + members.map(({ system_user_group_relation_id }) => system_user_group_relation_id), + ) userGroupMembers.value = userGroupMembers.value.filter( - ({ id }) => !removedMemberIds.has(id), + ({ system_user_group_relation_id }) => !removedRelationIds.has(system_user_group_relation_id), ) paginationConfig.value.total = userGroupMembers.value.length - group.memberCount = Math.max(0, group.memberCount - members.length) selectedGroupMembers.value = [] + loadUserGroups() }) }) .catch(() => {}) } /* 选择用户组列表 */ -const currentGroup = ref() -const userGroups = ref([ - { id: 'delivery', name: '交付', memberCount: 18 }, - { id: 'finance', name: '财务', memberCount: 20 }, - { id: 'development', name: '研发', memberCount: 36 }, - { id: 'marketing', name: '市场', memberCount: 24 }, - { id: 'sales', name: '销售', memberCount: 28 }, -]) -function handleGroupSelect(group: UserGroup) { +const loadingGroups = ref(false) +const currentGroup = ref() +const userGroups = ref([]) + +function loadUserGroups() { + loadingGroups.value = true + UserGroupsApi.getSystemUserGroups(selectedWorkspaceId.value) + .then((groups) => { + userGroups.value = groups + if (!currentGroup.value || !groups.some(({ id }) => id === currentGroup.value?.id)) { + currentGroup.value = groups[0] + loadUserGroupMembers() + } + }) + .finally(() => { + loadingGroups.value = false + }) +} + +function handleGroupSelect(group: SystemUserGroup) { currentGroup.value = group paginationConfig.value.currentPage = 1 - return + loadUserGroupMembers() } /* 新增、重命名与删除用户组 */ const groupDialogRef = useTemplateRef>('groupDialogRef') -function handleGroupSaved(group: { id: string; name: string }) { - const currentGroupIndex = userGroups.value.findIndex(({ id }) => id === group.id) - if (currentGroupIndex >= 0) { - userGroups.value[currentGroupIndex] = { - ...userGroups.value[currentGroupIndex]!, - name: group.name, - } +function handleGroupSaved(group: SystemUserGroup) { + const existingIndex = userGroups.value.findIndex(({ id }) => id === group.id) + if (existingIndex >= 0) { + userGroups.value[existingIndex] = group } else { - userGroups.value.push({ ...group, memberCount: 0 }) + userGroups.value.push(group) } currentGroup.value = userGroups.value.find(({ id }) => id === group.id) + if (currentGroup.value?.id === group.id) { + loadUserGroupMembers() + } } -function deleteGroup(group: UserGroup) { - MsgConfirm(`确定删除用户组“${group.name}”吗?`, '删除后,组内成员不会被删除。') +function deleteGroup(group: SystemUserGroup) { + MsgConfirm(`确定删除用户组"${group.name}"吗?`, '删除后,组内成员不会被删除。') .then(() => { return UserGroupsApi.deleteSystemUserGroup(selectedWorkspaceId.value, group.id).then(() => { MsgSuccess('删除成功') userGroups.value = userGroups.value.filter(({ id }) => id !== group.id) - if (currentGroup.value?.id === group.id) currentGroup.value = userGroups.value[0] + if (currentGroup.value?.id === group.id) { + currentGroup.value = userGroups.value[0] + loadUserGroupMembers() + } }) }) .catch(() => {}) } +/* 加载成员列表 */ +function loadUserGroupMembers(reset = false) { + if (!currentGroup.value) return + if (reset) { + paginationConfig.value.currentPage = 1 + } + + const { currentPage, pageSize } = paginationConfig.value + loadingMembers.value = true + UserGroupsApi.getSystemUserGroupMembers( + selectedWorkspaceId.value, + currentGroup.value.id, + { currentPage, pageSize }, + memberSearchQuery.value, + ) + .then(({ total, records }) => { + userGroupMembers.value = records + paginationConfig.value.total = total + }) + .finally(() => { + loadingMembers.value = false + }) +} + /* 选择工作空间列表 */ const selectedWorkspaceId = ref('default') const workspaceOptions = ref([]) function handleWorkspaceSelect(workspace: WorkspaceItem) { selectedWorkspaceId.value = workspace.id ?? 'default' + loadUserGroups() } function loadWorkspaceOptions() { @@ -149,6 +168,8 @@ function loadWorkspaceOptions() { if (!workspaceOptions.value.some(({ id }) => id === selectedWorkspaceId.value)) { selectedWorkspaceId.value = workspaceOptions.value[0]?.id ?? 'default' } + + loadUserGroups() }) } @@ -174,6 +195,7 @@ onMounted(() => loadWorkspaceOptions()) loadWorkspaceOptions()) - {{ currentGroup?.memberCount }} + {{ currentGroup?.count }} @@ -215,20 +237,20 @@ onMounted(() => loadWorkspaceOptions()) - + @@ -256,8 +278,9 @@ onMounted(() => loadWorkspaceOptions()) /> diff --git a/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue b/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue index 61b0d4e7549..1132e6fabef 100644 --- a/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue +++ b/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue @@ -1,13 +1,14 @@