diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index c651e550554..0c5d24a7cda 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -173,3 +173,13 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 - 流式 POST 请求使用 `postStream` 返回原始 `Response`,由业务组件按具体协议解析数据块; 鉴权、语言请求头和错误状态仍由请求基础设施统一处理。 - 上传、下载和其他特殊请求在真实需求出现时独立设计,不提前塞入普通 JSON 请求客户端。 + +### 资源用户授权 + +`admin/workspace/resource-authorization.ts` 按指定资源查询和更新用户权限,使用 +`resource_user_permission/resource//resource/`;与 System 用户视角的 +`user_resource_permission` 区分。分页使用 `ParamsPage`,直接返回 +`ResponsePage`;提交 `ResourceUserPermissionPayload[]`。 +`ResourceAuthorizationTargetType` 包含资源类型和三个 `_FOLDER` 类型,文件夹类型用于后端 +鉴权。包含子资源时传 `include_children: true` 及经过管理权限筛选的 `folder_ids`, +普通资源或仅当前文件夹不传子文件夹 ID。loading、刷新及成功提示由抽屉负责。 diff --git a/ui/src/api/admin/system/resource-authorization.ts b/ui/src/api/admin/system/resource-authorization.ts index 8962af83355..1e5f3c71240 100644 --- a/ui/src/api/admin/system/resource-authorization.ts +++ b/ui/src/api/admin/system/resource-authorization.ts @@ -13,4 +13,16 @@ const putUserResourcePermissions = (workspaceId: string, userId: string, resourc return put(`${prefix(workspaceId)}/user/${userId}/resource/${resource}`, permissions) } -export default { getUserResourcePermissions, putUserResourcePermissions } + +/** 获取指定空间、指定用户组、指定资源类型的权限列表。 */ +const getUserGroupResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, query?: Dict) => { + return get(`${prefix(workspaceId)}/user-group/${userId}/resource/${resource}`, query) +} + +/** 更新指定空间、指定用户组、指定资源类型的权限列表。 */ +const putUserGroupResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, permissions: ResourcePermissionPayload[]) => { + return put(`${prefix(workspaceId)}/user-group/${userId}/resource/${resource}`, permissions) +} + + +export default { getUserResourcePermissions, putUserResourcePermissions, getUserGroupResourcePermissions, putUserGroupResourcePermissions } diff --git a/ui/src/api/admin/workspace/resource-authorization.ts b/ui/src/api/admin/workspace/resource-authorization.ts new file mode 100644 index 00000000000..f32a37d4fae --- /dev/null +++ b/ui/src/api/admin/workspace/resource-authorization.ts @@ -0,0 +1,29 @@ +import { get, put } from '../core/request' +import type { ParamsPage, ResponsePage } from '../core/types' +import type { Dict, ResourceAuthorizationTargetType, ResourceUserPermission, ResourceUserPermissionPayload } from '@/api/types' + +const prefix = (workspaceId: string, targetId: string, resource: ResourceAuthorizationTargetType) => + `/workspace/${workspaceId}/resource_user_permission/resource/${targetId}/resource/${resource}` + +/** 获取指定资源或文件夹的用户权限分页列表。 */ +const getResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + page: ParamsPage, + query?: Dict, +) => { + return get>(`${prefix(workspaceId, targetId, resource)}/${page.currentPage}/${page.pageSize}`, query) +} + +/** 更新资源的用户权限,可同时应用到有管理权限的子文件夹及资源。 */ +const putResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + permissions: ResourceUserPermissionPayload[], +) => { + return put(prefix(workspaceId, targetId, resource), permissions) +} + +export default { getResourceAuthorization, putResourceAuthorization } diff --git a/ui/src/api/enums/resource-authorization.ts b/ui/src/api/enums/resource-authorization.ts index 1777d4559d1..46f2d80a6dc 100644 --- a/ui/src/api/enums/resource-authorization.ts +++ b/ui/src/api/enums/resource-authorization.ts @@ -1,5 +1,13 @@ /** 后端资源授权的资源类型。 */ export const RESOURCE_TYPE = { APPLICATION: 'APPLICATION', KNOWLEDGE: 'KNOWLEDGE', MODEL: 'MODEL', TOOL: 'TOOL' } as const +/** 用户授权接口额外区分文件夹目标,以匹配后端鉴权入口。 */ +export const RESOURCE_AUTHORIZATION_TARGET_TYPE = { + ...RESOURCE_TYPE, + APPLICATION_FOLDER: 'APPLICATION_FOLDER', + KNOWLEDGE_FOLDER: 'KNOWLEDGE_FOLDER', + TOOL_FOLDER: 'TOOL_FOLDER', +} as const + /** 后端资源授权的权限值。 */ export const RESOURCE_PERMISSION = { MANAGE: 'MANAGE', NOT_AUTH: 'NOT_AUTH', ROLE: 'ROLE', VIEW: 'VIEW' } as const diff --git a/ui/src/api/types/resource-authorization.ts b/ui/src/api/types/resource-authorization.ts index 24d9a61d3c0..a2abcabbf15 100644 --- a/ui/src/api/types/resource-authorization.ts +++ b/ui/src/api/types/resource-authorization.ts @@ -1,12 +1,30 @@ /** 系统资源授权 API 与页面共用的业务类型。 */ -import { RESOURCE_TYPE, RESOURCE_PERMISSION } from '@/api/enums' +import { RESOURCE_TYPE, RESOURCE_PERMISSION, RESOURCE_AUTHORIZATION_TARGET_TYPE } from '@/api/enums' import type { ToolType } from './tool' export type ResourceType = (typeof RESOURCE_TYPE)[keyof typeof RESOURCE_TYPE] export type ResourceAuthorizationType = ResourceType export type ResourcePermission = (typeof RESOURCE_PERMISSION)[keyof typeof RESOURCE_PERMISSION] +export type ResourceAuthorizationTargetType = (typeof RESOURCE_AUTHORIZATION_TARGET_TYPE)[keyof typeof RESOURCE_AUTHORIZATION_TARGET_TYPE] + +/** 指定资源下的用户及其权限,与用户视角的资源列表区分。 */ +export interface ResourceUserPermission { + id: string + nick_name: string + username: string + role_name?: string[] + permission: ResourcePermission +} + +export interface ResourceUserPermissionPayload { + user_id: string + permission: ResourcePermission + include_children?: boolean + folder_ids?: string[] +} + export interface ResourcePermissionItem { auth_target_type: ResourceAuthorizationType children?: ResourcePermissionItem[] diff --git a/ui/src/api/types/system-user-groups.ts b/ui/src/api/types/system-user-groups.ts index 648b2fc164e..127edef9fa0 100644 --- a/ui/src/api/types/system-user-groups.ts +++ b/ui/src/api/types/system-user-groups.ts @@ -7,6 +7,7 @@ export interface SystemUserGroup { export interface SystemUserGroupMember { id: string + roles: string[] username: string email: string phone: string diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 0a18ee96276..08e792e7b3f 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -51,6 +51,9 @@ src/components/ │ │ └── ModelParamsDialog.vue # 模型参数动态表单弹窗 │ ├── workspace-dropdown/ │ │ └── index.vue # 工作空间选择下拉框 +│ ├── resource-authorization-drawer/ # 资源用户授权抽屉与权限配置弹窗 +│ │ ├── index.vue +│ │ └── PermissionConfigDialog.vue │ └── workspace-relation-tags/ │ └── index.vue # 标签及关联工作空间展示 ├── global/ # 高频、稳定的基础组件,自动注册 @@ -1311,3 +1314,18 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags 5. 更新本文档中的目录树和组件说明。 6. 通过正常开发或构建流程刷新自动生成的组件声明。 7. 运行 ESLint、类型检查以及受影响入口的生产构建。 + +### ResourceAuthorizationDrawer + +手动导入 `business/resource-authorization-drawer/index.vue`。调用方传入完整 `api` +(`typeof ResourceAuthorizationApi`)和 `type`(`ResourceAuthorizationTargetType`),不由抽屉 +根据路由路径选择接口。当前后端的资源用户授权统一使用 +`api/admin/workspace/resource-authorization.ts`。通过 `open(id, folder?, workspaceId?)` 打开; +System 场景显式传入资源所属工作空间,Workspace 场景可读取当前工作空间。 +`isFolder`、`isRootFolder` 标记文件夹及根目录,传入文件夹类型也可识别文件夹。 + +支持姓名、用户名、权限及商业版本角色搜索,跨页选择和单人、批量授权;搜索后清空选择。 +根目录隐藏“不授权”,返回的“不授权”按“查看”展示。包含子资源时必须传入完整文件夹子树, +抽屉根据目标工作空间筛选有管理权限的文件夹 ID;没有可管理文件夹时禁用该范围。 +`PermissionConfigDialog` 只收集权限和范围,保存成功才关闭并刷新,失败保留配置。 +保存成功触发 `refresh`,打开和关闭后统一重置临时数据,过期查询不写回。 diff --git a/ui/src/components/business/resource-authorization-drawer/PermissionConfigDialog.vue b/ui/src/components/business/resource-authorization-drawer/PermissionConfigDialog.vue new file mode 100644 index 00000000000..80ce0d21aee --- /dev/null +++ b/ui/src/components/business/resource-authorization-drawer/PermissionConfigDialog.vue @@ -0,0 +1,67 @@ + + + diff --git a/ui/src/components/business/resource-authorization-drawer/index.vue b/ui/src/components/business/resource-authorization-drawer/index.vue new file mode 100644 index 00000000000..7bbad3fc73d --- /dev/null +++ b/ui/src/components/business/resource-authorization-drawer/index.vue @@ -0,0 +1,238 @@ + + + diff --git a/ui/src/constants/CONSTANT_README.md b/ui/src/constants/CONSTANT_README.md index 770590f5455..567b2e6fa19 100644 --- a/ui/src/constants/CONSTANT_README.md +++ b/ui/src/constants/CONSTANT_README.md @@ -30,3 +30,6 @@ const knowledgeType = KNOWLEDGE_TYPE_MAP[knowledge.type] const isWebKnowledge = knowledgeType === KNOWLEDGE_TYPE_KEY.WEB const knowledgeTypeLabel = KNOWLEDGE_TYPE_LABELS[knowledgeType] ``` + +`resource-authorization.ts` 的 `RESOURCE_PERMISSION_OPTIONS` 统一维护权限标签和说明; +资源授权页面与资源用户授权抽屉按版本和根目录约束筛选选项。 diff --git a/ui/src/constants/resource-authorization.ts b/ui/src/constants/resource-authorization.ts new file mode 100644 index 00000000000..d25e5aac5da --- /dev/null +++ b/ui/src/constants/resource-authorization.ts @@ -0,0 +1,10 @@ +import { RESOURCE_PERMISSION } from '@/api/enums' +import type { OptionItem, ResourcePermission } from '@/api/types' + +/** 资源授权页面与抽屉共用的权限展示选项,版本和根目录限制由调用方筛选。 */ +export const RESOURCE_PERMISSION_OPTIONS: (OptionItem & { description: string })[] = [ + { description: '', label: '不授权', value: RESOURCE_PERMISSION.NOT_AUTH }, + { description: '仅能查看和使用该资源', label: '查看', value: RESOURCE_PERMISSION.VIEW }, + { description: '可对该资源进行删改操作', label: '管理', value: RESOURCE_PERMISSION.MANAGE }, + { description: '根据用户角色中的权限授权用户对该资源的操作权限', label: '按用户角色', value: RESOURCE_PERMISSION.ROLE }, +] diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index 0e201aa8dc0..317fd347832 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -67,8 +67,9 @@ h6 { line-height: var(--mk-line-height-base); } -/* dot */ + +/* dot */ // 状态圆点 .mk-dot-danger { border-radius: 50%; @@ -185,6 +186,18 @@ h6 { } } + + +/* link */ +.mk-link { + color: var(--mk-primary); + cursor: pointer; + text-decoration: none; + &:hover { + text-decoration: underline; + } +} + /* required */ /* 必填文字的星号 diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index 327c5560e1e..f85b966f24e 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -240,6 +240,10 @@ src/views/system/identity/users/ └── UserPwdDialog.vue # 修改用户密码弹窗 ``` +资源授权页面的 `UserGroupAuthorizationList` 通过人数链接打开同目录的 +`UserGroupMembersDrawer.vue`,按用户组所属工作空间查询成员,提供用户名、姓名搜索和分页, +并使用 `MkTagGroup` 折叠展示角色;点击人数不切换当前授权对象。 + ## 文件命名 | 文件职责 | 命名格式 | 示例 | diff --git a/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue b/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue index 9eafc5ca3b0..72e2ddc0869 100644 --- a/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue +++ b/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue @@ -89,7 +89,7 @@ function loadResourcePermissions() { loadingPermissions.value = true const permissionRequest = targetType.value === 'user-group' - ? ResourceAuthorizationApi.getUserResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value) + ? ResourceAuthorizationApi.getUserGroupResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value) : ResourceAuthorizationApi.getUserResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value) return permissionRequest @@ -136,7 +136,7 @@ function handlePermissionsSubmit(permissions: ResourcePermissionPayload[]) { loadingPermissions.value = true const permissionRequest = targetType.value === 'user-group' - ? ResourceAuthorizationApi.putUserResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value, permissions) + ? ResourceAuthorizationApi.putUserGroupResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value, permissions) : ResourceAuthorizationApi.putUserResourcePermissions(selectedWorkspaceId.value, targetId, resourceType.value, permissions) permissionRequest diff --git a/ui/src/views/system/identity/resource-authorization/UserGroupMembersDrawer.vue b/ui/src/views/system/identity/resource-authorization/UserGroupMembersDrawer.vue new file mode 100644 index 00000000000..3318e1b74ee --- /dev/null +++ b/ui/src/views/system/identity/resource-authorization/UserGroupMembersDrawer.vue @@ -0,0 +1,90 @@ + + + diff --git a/ui/src/views/system/identity/resource-authorization/components/UserGroupAuthorizationList.vue b/ui/src/views/system/identity/resource-authorization/components/UserGroupAuthorizationList.vue index 1b8b79ea282..b80a0ae8551 100644 --- a/ui/src/views/system/identity/resource-authorization/components/UserGroupAuthorizationList.vue +++ b/ui/src/views/system/identity/resource-authorization/components/UserGroupAuthorizationList.vue @@ -1,6 +1,8 @@ diff --git a/ui/src/views/system/identity/resource-authorization/constants.ts b/ui/src/views/system/identity/resource-authorization/constants.ts index c6731cf3428..1763fcd3be6 100644 --- a/ui/src/views/system/identity/resource-authorization/constants.ts +++ b/ui/src/views/system/identity/resource-authorization/constants.ts @@ -1,6 +1,7 @@ import { RESOURCE_TYPE, RESOURCE_PERMISSION } from '@/api/enums' import type { OptionItem, ResourceAuthorizationType, ResourcePermission } from '@/api/types' import { useStore } from '@/stores' +import { RESOURCE_PERMISSION_OPTIONS } from '@/constants/resource-authorization' const { auth } = useStore() export const RESOURCE_AUTHORIZATION_LABELS: Record = { @@ -15,12 +16,7 @@ interface PermissionOption extends OptionItem { } export function getPermissionOptions(): PermissionOption[] { - const permissionOptions: PermissionOption[] = [ - { description: '', label: '不授权', value: RESOURCE_PERMISSION.NOT_AUTH }, - { description: '仅能查看和使用该资源', label: '查看', value: RESOURCE_PERMISSION.VIEW }, - { description: '可对该资源进行删改操作', label: '管理', value: RESOURCE_PERMISSION.MANAGE }, - { description: '根据用户角色中的权限授权用户对该资源的操作权限', label: '按用户角色', value: RESOURCE_PERMISSION.ROLE }, - ] + const permissionOptions = RESOURCE_PERMISSION_OPTIONS if (auth.isCE) { return permissionOptions.filter((item) => item.value !== RESOURCE_PERMISSION.ROLE) }