From b0a993458137d6c73b683a2d9f99fc6326a0bf00 Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Wed, 14 Oct 2026 16:25:51 +0800 Subject: [PATCH] feat: add authorized user count to chat user groups and refresh logic --- .../serializers/resource_chat_user_group.py | 12 +++++++++++- .../sql/list_resource_chat_user_group.sql | 11 +++++++++-- ui/src/api/types/chat-user.ts | 2 ++ ui/src/views/VIEW_README.md | 2 +- .../views/application-detail/chat-user/index.vue | 16 ++++++++++++---- .../views/knowledge-detail/chat-user/index.vue | 16 ++++++++++++---- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apps/system_manage/serializers/resource_chat_user_group.py b/apps/system_manage/serializers/resource_chat_user_group.py index be593e5df94..8a3b051a219 100644 --- a/apps/system_manage/serializers/resource_chat_user_group.py +++ b/apps/system_manage/serializers/resource_chat_user_group.py @@ -9,7 +9,7 @@ from common.exception.app_exception import AppApiException from common.utils.common import get_file_content from maxkb.conf import PROJECT_DIR -from system_manage.models import ResourceChatUserGroupAuthorize, UserGroup +from system_manage.models import ResourceChatUserAuthorize, ResourceChatUserGroupAuthorize, UserGroup class ResourceChatUserGroupQuerySerializer(serializers.Serializer): @@ -29,6 +29,9 @@ def get_query_set(self): resource_chat_user_group_authorize_query_set = QuerySet(ResourceChatUserGroupAuthorize).filter( resource_id=resource_id, resource_type=resource_type ) + resource_chat_user_authorize_query_set = QuerySet(ResourceChatUserAuthorize).filter( + resource_id=resource_id, resource_type=resource_type + ) query_set = QuerySet(UserGroup) if user_group_name is not None: query_set = query_set.filter(name__contains=user_group_name) @@ -36,13 +39,20 @@ def get_query_set(self): resource_chat_user_group_authorize_query_set = resource_chat_user_group_authorize_query_set.filter( workspace_id=workspace_id ) + resource_chat_user_authorize_query_set = resource_chat_user_authorize_query_set.filter( + workspace_id=workspace_id + ) else: resource_chat_user_group_authorize_query_set = resource_chat_user_group_authorize_query_set.filter( workspace_id__isnull=True ) + resource_chat_user_authorize_query_set = resource_chat_user_authorize_query_set.filter( + workspace_id__isnull=True + ) return { "default_query_set": query_set, "resource_chat_user_group_authorize_query_set": resource_chat_user_group_authorize_query_set, + "resource_chat_user_authorize_query_set": resource_chat_user_authorize_query_set, } def list(self): diff --git a/apps/system_manage/sql/list_resource_chat_user_group.sql b/apps/system_manage/sql/list_resource_chat_user_group.sql index 09f141e3ba3..00308e2941c 100644 --- a/apps/system_manage/sql/list_resource_chat_user_group.sql +++ b/apps/system_manage/sql/list_resource_chat_user_group.sql @@ -1,5 +1,12 @@ -SELECT user_group.*, COALESCE(resource_chat_user_group_authorize.is_auth,false) as is_auth +SELECT user_group.*, COALESCE(resource_chat_user_group_authorize.is_auth,false) as is_auth, + (SELECT COUNT(DISTINCT resource_chat_user_authorize.user_id) + FROM user_group_relation + JOIN (SELECT * FROM resource_chat_user_authorize ${resource_chat_user_authorize_query_set}) resource_chat_user_authorize + ON resource_chat_user_authorize.user_id = user_group_relation.user_id + AND resource_chat_user_authorize.user_group_id = user_group_relation.group_id + WHERE user_group_relation.group_id = user_group.id + AND resource_chat_user_authorize.is_auth = true) AS authorized_count FROM "user_group" LEFT JOIN (SELECT * FROM resource_chat_user_group_authorize ${resource_chat_user_group_authorize_query_set}) resource_chat_user_group_authorize ON user_group.id = resource_chat_user_group_authorize.user_group_id -${default_query_set} \ No newline at end of file +${default_query_set} diff --git a/ui/src/api/types/chat-user.ts b/ui/src/api/types/chat-user.ts index f84207538c7..4944e6eb3cd 100644 --- a/ui/src/api/types/chat-user.ts +++ b/ui/src/api/types/chat-user.ts @@ -112,6 +112,8 @@ export interface ChatUserAuthorizationGroup { id: string name: string is_auth: boolean + /** 用户组内已授权对话用户数。 */ + authorized_count: number } /** 用户组内的对话用户资源授权状态。 */ diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index ed1c970b1af..f848a814677 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -1389,7 +1389,7 @@ API 地址、调用示例及 MCP 配置只读;后两个展示区参考工具 M 使用对应路由 ID 及 `RESOURCE_TYPE.APPLICATION / KNOWLEDGE`;授权类型必须大写,与后台授权记录及对话端查询一致。 两页通过 `hideHeader: true` 隐藏详情标题栏,使用普通 Flex 双栏容器,不嵌套 `MkViewLayout`。 外层 `ResourceDetailLayout` 统一管理整体滚动;侧栏复用 `w-sidebar-expanded`、`MkSearchList` 的搜索、选中和空态。 -“按用户组”列表通过插槽展示名称及自动授权状态;接口尚未返回组已授权人数,手动授权组显示“已授权 —”,不以当前页勾选数代替。 +“按用户组”列表通过插槽展示名称及自动授权状态;接口返回组已授权人数 `authorized_count`,自动授权组显示“(自动授权)”,手动授权组显示“(已授权 N)”,不以当前页勾选数代替。 右侧第一行展示组名和用户查询总数;下方工具栏左侧为默认按姓名查询的 `MkComplexSearch` 及自动授权开关,右侧为保存。 顶部 `MkComplexSearch` 按姓名、用户名排列;用户来源只在表头使用 `MkTableFilter mode="single"` 筛选,向接口传 `source`。 用户名、姓名搜索与表头来源可组合查询,清空一处条件不影响另一处,不额外重置搜索组件。 diff --git a/ui/src/views/application-detail/chat-user/index.vue b/ui/src/views/application-detail/chat-user/index.vue index e11600916a0..73f21de9d90 100644 --- a/ui/src/views/application-detail/chat-user/index.vue +++ b/ui/src/views/application-detail/chat-user/index.vue @@ -39,6 +39,15 @@ function loadChatUserGroups() { }) } +// 仅刷新用户组列表及已授权人数,保留当前选中的用户组。 +function refreshChatUserGroups() { + return ChatUserApi.getUserGroupList(resource.value).then((groups) => { + const currentId = currentGroup.value?.id + chatUserGroups.value = groups + currentGroup.value = groups.find((group) => group.id === currentId) ?? groups[0] + }) +} + function handleSelectGroup(group: ChatUserAuthorizationGroup) { if (loading.value || currentGroup.value?.id === group.id) return currentGroup.value = group @@ -130,7 +139,7 @@ function handleSave() { // 先记住已保存结果,列表刷新失败时不重复提示未保存。 savedAuthorization.value = Object.fromEntries(payload.map(({ chat_user_id, is_auth }) => [chat_user_id, is_auth])) MsgSuccess('保存成功') - return loadChatUsers() + return loadChatUsers().then(refreshChatUserGroups) }) .finally(() => { loading.value = false @@ -149,7 +158,7 @@ function handleAutoAuthorizationChange(value: string | number | boolean) { chatUsers.value = [] savedAuthorization.value = {} MsgSuccess('保存成功') - return loadChatUsers() + return loadChatUsers().then(refreshChatUserGroups) }) .finally(() => { loading.value = false @@ -170,8 +179,7 @@ onMounted(() => loadChatUserGroups()) diff --git a/ui/src/views/knowledge-detail/chat-user/index.vue b/ui/src/views/knowledge-detail/chat-user/index.vue index 898e02dc570..ec811515a7d 100644 --- a/ui/src/views/knowledge-detail/chat-user/index.vue +++ b/ui/src/views/knowledge-detail/chat-user/index.vue @@ -39,6 +39,15 @@ function loadChatUserGroups() { }) } +// 仅刷新用户组列表及已授权人数,保留当前选中的用户组。 +function refreshChatUserGroups() { + return ChatUserApi.getUserGroupList(resource.value).then((groups) => { + const currentId = currentGroup.value?.id + chatUserGroups.value = groups + currentGroup.value = groups.find((group) => group.id === currentId) ?? groups[0] + }) +} + function handleSelectGroup(group: ChatUserAuthorizationGroup) { if (loading.value || currentGroup.value?.id === group.id) return currentGroup.value = group @@ -130,7 +139,7 @@ function handleSave() { // 先记住已保存结果,列表刷新失败时不重复提示未保存。 savedAuthorization.value = Object.fromEntries(payload.map(({ chat_user_id, is_auth }) => [chat_user_id, is_auth])) MsgSuccess('保存成功') - return loadChatUsers() + return loadChatUsers().then(refreshChatUserGroups) }) .finally(() => { loading.value = false @@ -149,7 +158,7 @@ function handleAutoAuthorizationChange(value: string | number | boolean) { chatUsers.value = [] savedAuthorization.value = {} MsgSuccess('保存成功') - return loadChatUsers() + return loadChatUsers().then(refreshChatUserGroups) }) .finally(() => { loading.value = false @@ -170,8 +179,7 @@ onMounted(() => loadChatUserGroups())