From 09082d3b154628425849338fe229a91e400cc1c4 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Mon, 24 Aug 2026 15:25:21 +0800 Subject: [PATCH] fix: codemirror-editor --- ui/package.json | 1 + ui/src/api/admin/system/common.ts | 9 +- .../admin/system/resource-authorization.ts | 38 +++ ui/src/api/enums/folder.ts | 6 - ui/src/api/enums/index.ts | 2 +- ui/src/api/enums/resource-authorization.ts | 15 + ui/src/api/types/common.ts | 7 +- ui/src/api/types/folder.ts | 6 +- ui/src/api/types/index.ts | 1 + ui/src/api/types/resource-authorization.ts | 27 ++ ui/src/components/COMPONENT_README.md | 43 ++- .../business/folder-tree/FolderFormDialog.vue | 12 +- .../components/business/folder-tree/index.vue | 4 +- ui/src/components/codemirror-editor/Json.vue | 128 ++++++++ .../{index.vue => python.vue} | 17 +- ui/src/components/codemirror-editor/types.ts | 9 - ui/src/constants/folder.ts | 8 +- ui/src/router/ROUTE_README.md | 13 + ui/src/router/admin/system/index.ts | 49 ++- ui/src/router/admin/types.ts | 4 + ui/src/views/VIEW_README.md | 1 + ui/src/views/application/ApplicationView.vue | 6 +- .../groups/dialog/CreateGroupMemberDialog.vue | 4 +- .../ResourceAuthorizationView.vue | 226 +++++++++++++ .../components/PermissionTable.vue | 304 ++++++++++++++++++ .../resource-authorization/constants.ts | 55 ++++ .../identity/users/dialog/UserPwdDialog.vue | 15 +- ui/src/views/tool/ToolView.vue | 6 +- .../tool/components/ToolCreateDropdown.vue | 32 +- .../components/python-code/CodeSetting.vue | 11 +- 30 files changed, 958 insertions(+), 101 deletions(-) create mode 100644 ui/src/api/admin/system/resource-authorization.ts delete mode 100644 ui/src/api/enums/folder.ts create mode 100644 ui/src/api/enums/resource-authorization.ts create mode 100644 ui/src/api/types/resource-authorization.ts create mode 100644 ui/src/components/codemirror-editor/Json.vue rename ui/src/components/codemirror-editor/{index.vue => python.vue} (87%) delete mode 100644 ui/src/components/codemirror-editor/types.ts create mode 100644 ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue create mode 100644 ui/src/views/system/identity/resource-authorization/components/PermissionTable.vue create mode 100644 ui/src/views/system/identity/resource-authorization/constants.ts diff --git a/ui/package.json b/ui/package.json index e8df7319bbd..b00ec03117a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@antv/layout": "^2.0.0", + "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-python": "^6.2.1", "@codemirror/lint": "^6.9.7", "@codemirror/state": "^6.7.1", diff --git a/ui/src/api/admin/system/common.ts b/ui/src/api/admin/system/common.ts index 56411bc4e0b..b37e83e3c0b 100644 --- a/ui/src/api/admin/system/common.ts +++ b/ui/src/api/admin/system/common.ts @@ -1,5 +1,5 @@ import { get } from '../core/request' -import type { RequestParams, SystemUserOption } from '@/api/types' +import type { RequestParams, SystemUserOption, WorkspaceMemberOption } from '@/api/types' /** 获取默认密码。 */ const getDefaultPassword = () => { @@ -11,12 +11,9 @@ const getAllUsers = (query?: RequestParams) => { return get('/user/list', query) } -/** 系统中选择空间下的成员用户 - * @param workspaceId 工作空间ID - * 资源授权 和用户组的 用户下拉列表使用 - */ +/** 获取指定工作空间的成员选项。 */ const getWorkspaceMembers = (workspaceId: string, query?: RequestParams) => { - return get(`/workspace/${workspaceId}/user_member`, query) + return get(`/workspace/${workspaceId}/user_member`, query) } export default { diff --git a/ui/src/api/admin/system/resource-authorization.ts b/ui/src/api/admin/system/resource-authorization.ts new file mode 100644 index 00000000000..02a4092c74f --- /dev/null +++ b/ui/src/api/admin/system/resource-authorization.ts @@ -0,0 +1,38 @@ +import { get, put } from '../core/request' +import type { + RequestParams, + ResourceAuthorizationType, + ResourcePermissionItem, + ResourcePermissionPayload, +} from '@/api/types' + +const prefix = (workspaceId: string, userId: string, resource: ResourceAuthorizationType) => + `/workspace/${workspaceId}/user_resource_permission/user/${userId}/resource/${resource}` + +/** 获取工作空间成员对指定类型资源的权限列表。 */ +const getUserResourcePermissions = ( + workspaceId: string, + userId: string, + resource: ResourceAuthorizationType, + query?: RequestParams, +) => { + return get(prefix(workspaceId, userId, resource), query) +} + +/** 更新工作空间成员对指定类型资源的权限。 */ +const putUserResourcePermissions = ( + workspaceId: string, + userId: string, + resource: ResourceAuthorizationType, + permissions: ResourcePermissionPayload[], +) => { + return put( + prefix(workspaceId, userId, resource), + permissions, + ) +} + +export default { + getUserResourcePermissions, + putUserResourcePermissions, +} diff --git a/ui/src/api/enums/folder.ts b/ui/src/api/enums/folder.ts deleted file mode 100644 index 563ab1c3e1f..00000000000 --- a/ui/src/api/enums/folder.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** 后端文件夹资源来源枚举值。 */ -export const FOLDER_SOURCE = { - APPLICATION: 'APPLICATION', - KNOWLEDGE: 'KNOWLEDGE', - TOOL: 'TOOL', -} as const diff --git a/ui/src/api/enums/index.ts b/ui/src/api/enums/index.ts index 923864bcb15..f183e679d7e 100644 --- a/ui/src/api/enums/index.ts +++ b/ui/src/api/enums/index.ts @@ -1,7 +1,7 @@ /** API 枚举值的唯一公共入口。 */ export * from './application' -export * from './folder' export * from './login' export * from './system-role' export * from './model' +export * from './resource-authorization' export * from './tool' diff --git a/ui/src/api/enums/resource-authorization.ts b/ui/src/api/enums/resource-authorization.ts new file mode 100644 index 00000000000..13bdb5c0474 --- /dev/null +++ b/ui/src/api/enums/resource-authorization.ts @@ -0,0 +1,15 @@ +/** 后端资源授权的资源类型。 */ +export const RESOURCE_TYPE = { + APPLICATION: 'APPLICATION', + KNOWLEDGE: 'KNOWLEDGE', + MODEL: 'MODEL', + TOOL: 'TOOL', +} 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/common.ts b/ui/src/api/types/common.ts index ee58c403725..7c8bf5a8179 100644 --- a/ui/src/api/types/common.ts +++ b/ui/src/api/types/common.ts @@ -16,10 +16,15 @@ export interface OptionItem export interface FolderItem { children?: FolderItem[] diff --git a/ui/src/api/types/index.ts b/ui/src/api/types/index.ts index db34fe0d171..db422a0189c 100644 --- a/ui/src/api/types/index.ts +++ b/ui/src/api/types/index.ts @@ -12,5 +12,6 @@ export type * from './system-operate-log' export type * from './system-role' export type * from './system-email' export type * from './model' +export type * from './resource-authorization' export type * from './folder' export type * from './tool' diff --git a/ui/src/api/types/resource-authorization.ts b/ui/src/api/types/resource-authorization.ts new file mode 100644 index 00000000000..24d9a61d3c0 --- /dev/null +++ b/ui/src/api/types/resource-authorization.ts @@ -0,0 +1,27 @@ +/** 系统资源授权 API 与页面共用的业务类型。 */ + +import { RESOURCE_TYPE, RESOURCE_PERMISSION } 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 interface ResourcePermissionItem { + auth_target_type: ResourceAuthorizationType + children?: ResourcePermissionItem[] + folder_id: string | null + icon?: string | null + id: string + name: string + permission: ResourcePermission + resource_type: 'application' | 'folder' | 'knowledge' | 'model' | 'tool' + tool_type?: ToolType | null + user_id: string + workspace_id: string +} + +export interface ResourcePermissionPayload { + permission: ResourcePermission + target_id: string +} diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 58f8f48f3c2..76a04f370e5 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -85,8 +85,8 @@ src/components/ ├── mk-form-list/ │ └── index.vue # 可动态增删的表单行列表,手动导入 ├── codemirror-editor/ -│ ├── index.vue # 支持异步诊断和全屏编辑的代码编辑器 -│ └── types.ts # 编辑器行列诊断类型 +│ ├── python.vue # 内置 pylint 诊断和全屏编辑的 Python 编辑器 +│ └── Json.vue # 支持格式化、语法诊断和全屏编辑的 JSON 输入框 └── mk-source-card/ │ ├── index.vue # 来源资源的统一卡片结构,手动导入 │ ├── mk-source-card-action.vue # 卡片悬浮操作容器 @@ -99,7 +99,8 @@ Vue 模板中使用,不需要手动导入。其他共享组件必须从具体 ```ts import MkSearchList from '@/components/mk-search-list/index.vue' import MkFormList from '@/components/mk-form-list/index.vue' -import CodemirrorEditor from '@/components/codemirror-editor/index.vue' +import PythonCodeEditor from '@/components/codemirror-editor/python.vue' +import JsonInput from '@/components/codemirror-editor/Json.vue' import MkSourceCard from '@/components/mk-source-card/index.vue' import FolderTree from '@/components/business/folder-tree/index.vue' import ModelSelect from '@/components/business/model-select/index.vue' @@ -116,7 +117,8 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags - 跨页面复用且依赖业务类型、固定业务接口或领域交互的组件放入 `business/`, 由使用方手动导入。业务组件不使用 `Mk` 前缀,也不再按 Workspace 等上级领域增加额外目录。 - 不依赖固定业务的共享组合 UI 组件放在 `components/`,由使用方手动导入。 -- 一个公共组件使用一个 kebab-case 目录,入口统一为 `index.vue`。 +- 一个公共组件默认使用一个 kebab-case 目录,入口统一为 `index.vue`;`codemirror-editor` 按语言 + 提供 `python.vue` 和 `Json.vue` 两个专用入口。 - 组件名使用 PascalCase;目录名使用 kebab-case,例如 `MkIcon` 对应 `mk-icon/index.vue`。 - `index.vue` 必须通过 `defineOptions({ name: 'ComponentName' })` 声明多单词组件名,避免 @@ -495,25 +497,36 @@ const paginationConfig = ref({ ## 手动导入组件 -### CodemirrorEditor +### PythonCodeEditor -基于 CodeMirror 6 的代码编辑器,当前内置 Python 语法和 One Dark 主题。通过 `v-model` 管理代码, -通过 `lint` 传入异步检查函数;检查结果使用 `CodeLintIssue` 的行、列、结束位置、严重程度和消息 -生成编辑器诊断,组件最多展示 50 条诊断,并在代码停止输入 500ms 后检查。编辑器提供内置全屏 +基于 CodeMirror 6 的 Python 代码编辑器,通过 `v-model` 管理代码,并在组件内部调用工具 pylint +接口生成诊断。组件最多展示 50 条诊断,并在代码停止输入 500ms 后检查。编辑器提供内置全屏 入口;全屏确认时更新 `v-model` 并触发 `submit-dialog`,`header-extra` 插槽用于添加全屏标题栏操作。 ```vue -function lintCode(sourceCode: string) { - return ToolApi.postToolPylint(sourceCode) -} + +``` + +### JsonInput + +JSON 专用输入框,通过 `v-model` 接收并回传解析后的 JSON 值,内置 JSON 语法诊断、格式化和全屏 +编辑。组件暴露 `validateRules`,可直接接入 Element Plus 表单自定义校验器;无法解析的输入不会 +覆盖最后一次有效的 `v-model` 值。 + +```vue + - + ``` ### MkSourceCard @@ -722,13 +735,13 @@ Workspace 的文件夹虚拟树业务组件。组件根据 `workspaceId` 和 `so ```vue diff --git a/ui/src/components/business/folder-tree/FolderFormDialog.vue b/ui/src/components/business/folder-tree/FolderFormDialog.vue index 094af40f13f..870b98da9bb 100644 --- a/ui/src/components/business/folder-tree/FolderFormDialog.vue +++ b/ui/src/components/business/folder-tree/FolderFormDialog.vue @@ -3,10 +3,13 @@ import { reactive, ref, useTemplateRef } from 'vue' import type { FormInstance, FormRules } from 'element-plus' import FolderApi from '@/api/admin/workspace/folder' import type { FolderItem, FolderSource } from '@/api/types' +import { useStore } from '@/stores' import { MsgSuccess } from '@/utils/message' defineOptions({ name: 'FolderFormDialog' }) +const { user } = useStore() + const props = defineProps<{ title: string source: FolderSource @@ -41,9 +44,12 @@ function handleSubmit() { return request .then((folder) => { - MsgSuccess(isEdit ? '保存成功' : '创建成功') - visible.value = false - emit('refresh', folder, isEdit) + const refreshCurrentUser = isEdit ? Promise.resolve() : user.loadCurrentUser() + return refreshCurrentUser.then(() => { + MsgSuccess(isEdit ? '保存成功' : '创建成功') + visible.value = false + emit('refresh', folder, isEdit) + }) }) .finally(() => { loading.value = false diff --git a/ui/src/components/business/folder-tree/index.vue b/ui/src/components/business/folder-tree/index.vue index c0ed5328f22..0b6c3e8e73d 100644 --- a/ui/src/components/business/folder-tree/index.vue +++ b/ui/src/components/business/folder-tree/index.vue @@ -2,7 +2,7 @@ import { computed, onMounted, ref, useTemplateRef } from 'vue' import FolderApi from '@/api/admin/workspace/folder' import type { FolderSource, FolderItem } from '@/api/types' -import { FOLDER_SOURCE } from '@/api/enums' +import { RESOURCE_TYPE } from '@/api/enums' import { FOLDER_SORT, type FolderSort } from './types' import { FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants' import { MsgSuccess, MsgConfirm } from '@/utils/message' @@ -345,7 +345,7 @@ defineExpose({ refresh: loadFolders, openCreate: handleOpenCreateFolder })
diff --git a/ui/src/components/codemirror-editor/Json.vue b/ui/src/components/codemirror-editor/Json.vue new file mode 100644 index 00000000000..91359f68bbb --- /dev/null +++ b/ui/src/components/codemirror-editor/Json.vue @@ -0,0 +1,128 @@ + + + diff --git a/ui/src/components/codemirror-editor/index.vue b/ui/src/components/codemirror-editor/python.vue similarity index 87% rename from ui/src/components/codemirror-editor/index.vue rename to ui/src/components/codemirror-editor/python.vue index 5aad66481d2..eb4e242448d 100644 --- a/ui/src/components/codemirror-editor/index.vue +++ b/ui/src/components/codemirror-editor/python.vue @@ -4,15 +4,13 @@ import type { EditorState } from '@codemirror/state' import { linter, type Diagnostic } from '@codemirror/lint' import { python } from '@codemirror/lang-python' import { Codemirror } from 'vue-codemirror' -import type { CodeLintIssue } from './types' +import ToolApi from '@/api/admin/workspace/tool/tool' +import type { ToolPylintIssue } from '@/api/types' -defineOptions({ name: 'CodemirrorEditor', inheritAttrs: false }) +defineOptions({ name: 'PythonCodeEditor', inheritAttrs: false }) const code = defineModel({ required: true }) -const props = defineProps<{ - lint?: (code: string) => Promise - title?: string -}>() +defineProps<{ title?: string }>() const emit = defineEmits<{ submitDialog: [code: string] @@ -29,7 +27,7 @@ function getDocumentPosition(state: EditorState, line: number, column: number) { return documentLine.from + safeColumn } -function createDiagnostic(state: EditorState, issue: CodeLintIssue): Diagnostic { +function createDiagnostic(state: EditorState, issue: ToolPylintIssue): Diagnostic { const from = getDocumentPosition(state, issue.line, issue.column) const endPosition = getDocumentPosition( state, @@ -48,10 +46,9 @@ function createDiagnostic(state: EditorState, issue: CodeLintIssue): Diagnostic const codeLinter = linter( async (view) => { const lintSource = view.state.doc.toString() - if (!props.lint || !lintSource.trim()) return [] + if (!lintSource.trim()) return [] - return props - .lint(lintSource) + return ToolApi.postToolPylint(lintSource) .then((issues) => { if (lintSource !== view.state.doc.toString()) return [] return issues.slice(0, 50).map((issue) => createDiagnostic(view.state, issue)) diff --git a/ui/src/components/codemirror-editor/types.ts b/ui/src/components/codemirror-editor/types.ts deleted file mode 100644 index 939874135b5..00000000000 --- a/ui/src/components/codemirror-editor/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** CodeMirror 异步语法检查使用的行列诊断信息。 */ -export interface CodeLintIssue { - column: number - endColumn?: number - endLine?: number - line: number - message: string - type: 'error' | 'warning' -} diff --git a/ui/src/constants/folder.ts b/ui/src/constants/folder.ts index 1774b3501bd..163782e08e2 100644 --- a/ui/src/constants/folder.ts +++ b/ui/src/constants/folder.ts @@ -1,4 +1,4 @@ -import { FOLDER_SOURCE } from '@/api/enums' +import { RESOURCE_TYPE } from '@/api/enums' import type { FolderItem, FolderSource } from '@/api/types' export const FOLDER_ENTRY_ID = { @@ -7,15 +7,15 @@ export const FOLDER_ENTRY_ID = { } as const export const FOLDER_ENTRIES = { - [FOLDER_SOURCE.APPLICATION]: { + [RESOURCE_TYPE.APPLICATION]: { all: { id: FOLDER_ENTRY_ID.ALL, name: '全部智能体' }, shared: { id: FOLDER_ENTRY_ID.SHARED, name: '共享智能体' }, }, - [FOLDER_SOURCE.KNOWLEDGE]: { + [RESOURCE_TYPE.KNOWLEDGE]: { all: { id: FOLDER_ENTRY_ID.ALL, name: '全部知识库' }, shared: { id: FOLDER_ENTRY_ID.SHARED, name: '共享知识库' }, }, - [FOLDER_SOURCE.TOOL]: { + [RESOURCE_TYPE.TOOL]: { all: { id: FOLDER_ENTRY_ID.ALL, name: '全部工具' }, shared: { id: FOLDER_ENTRY_ID.SHARED, name: '共享工具' }, }, diff --git a/ui/src/router/ROUTE_README.md b/ui/src/router/ROUTE_README.md index 5dc3bd7a9c0..c55b111948d 100644 --- a/ui/src/router/ROUTE_README.md +++ b/ui/src/router/ROUTE_README.md @@ -58,6 +58,18 @@ System 根地址为 `/admin/system`,默认进入首页 `/admin/system/home`。 `views/system/settings/AppearanceSettingsView.vue`,并复用 `views/login/components` 中的登录外观组件进行实时主题预览。 +系统资源授权按资源类型使用四个子路由,并复用 +`views/system/identity/resource-authorization/ResourceAuthorizationView.vue`: + +```text +/admin/system/identity/authorization/applications +/admin/system/identity/authorization/knowledge +/admin/system/identity/authorization/tools +/admin/system/identity/authorization/models +``` + +各子路由通过 `meta.resource` 向页面传入对应的后端资源类型。 + 复用路由按模块放在 `admin/system/modules`,文件名与 `admin/workspace/modules` 保持对应;`system/index.ts` 只负责系统导航路由和模块汇总。 有子目录时直接使用 Vue Router 的 `children`,路由层级就是导航层级,不需要额外的分组字段。 @@ -110,6 +122,7 @@ Chat 使用独立入口 `src/chat.ts` 和独立 Router,不要把 Chat 路由 | `icon` | `string` | iconfont Symbol ID,子目录通常可以不配置 | | `order` | `number` | 同级导航排序,数字越小越靠前 | | `hidden` | `boolean` | 设置为 `true` 时不显示在导航中 | +| `resource` | `ResourceAuthorizationType` | 系统资源授权页面当前管理的后端资源类型 | ## 导航生成 diff --git a/ui/src/router/admin/system/index.ts b/ui/src/router/admin/system/index.ts index 800fa70d002..82992ab0d2d 100644 --- a/ui/src/router/admin/system/index.ts +++ b/ui/src/router/admin/system/index.ts @@ -1,4 +1,5 @@ import type { RouteRecordRaw } from 'vue-router' +import { RESOURCE_TYPE } from '@/api/enums' import { systemApplicationRoutes } from './modules/application' import { systemKnowledgeRoutes } from './modules/knowledge' @@ -56,8 +57,54 @@ export const systemRoutes: RouteRecordRaw = { { path: 'authorization', name: 'system-authorization', - component: () => import('@/views/system/SystemView.vue'), + redirect: { name: 'system-authorization-applications' }, meta: { title: '资源授权', order: 50 }, + children: [ + { + path: 'applications', + name: 'system-authorization-applications', + component: () => + import('@/views/system/identity/resource-authorization/ResourceAuthorizationView.vue'), + meta: { + title: '智能体', + order: 10, + resource: RESOURCE_TYPE.APPLICATION, + }, + }, + { + path: 'knowledge', + name: 'system-authorization-knowledge', + component: () => + import('@/views/system/identity/resource-authorization/ResourceAuthorizationView.vue'), + meta: { + title: '知识库', + order: 20, + resource: RESOURCE_TYPE.KNOWLEDGE, + }, + }, + { + path: 'tools', + name: 'system-authorization-tools', + component: () => + import('@/views/system/identity/resource-authorization/ResourceAuthorizationView.vue'), + meta: { + title: '工具', + order: 30, + resource: RESOURCE_TYPE.TOOL, + }, + }, + { + path: 'models', + name: 'system-authorization-models', + component: () => + import('@/views/system/identity/resource-authorization/ResourceAuthorizationView.vue'), + meta: { + title: '模型', + order: 40, + resource: RESOURCE_TYPE.MODEL, + }, + }, + ], }, ], }, diff --git a/ui/src/router/admin/types.ts b/ui/src/router/admin/types.ts index ef94dfb1d11..27ff5a72889 100644 --- a/ui/src/router/admin/types.ts +++ b/ui/src/router/admin/types.ts @@ -1,5 +1,7 @@ /** Admin Router 的业务范围和路由元信息声明。 */ +import type { ResourceAuthorizationType } from '@/api/types' + export type RouteScope = 'workspace' | 'system' declare module 'vue-router' { @@ -13,6 +15,8 @@ declare module 'vue-router' { icon?: string order?: number hidden?: boolean + /** 系统资源授权页面当前管理的资源类型。 */ + resource?: ResourceAuthorizationType title?: string } } diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index 8e26be46300..10b4c98ff68 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -95,6 +95,7 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 | `model/ModelView.vue` | 工作空间模型目录与模型卡片页面 | | `system/SystemView.vue` | System 模块通用占位页面 | | `system/identity/groups/UserGroupListView.vue` | 用户组列表页面 | +| `system/identity/resource-authorization/ResourceAuthorizationView.vue` | 工作空间成员的分类资源授权页面 | | `system/identity/roles/RoleListView.vue` | 角色列表页面 | | `system/identity/users/UserListView.vue` | 用户列表页面 | | `system/identity/workspaces/WorkspaceListView.vue` | 工作空间列表页面 | diff --git a/ui/src/views/application/ApplicationView.vue b/ui/src/views/application/ApplicationView.vue index 2b4bc623368..64f5cd0fafd 100644 --- a/ui/src/views/application/ApplicationView.vue +++ b/ui/src/views/application/ApplicationView.vue @@ -3,13 +3,13 @@ import { computed, ref, useTemplateRef } from 'vue' import CommonApi from '@/api/admin/workspace/common' import ApplicationApi from '@/api/admin/workspace/application' import type { ApplicationDetail, FolderItem, OptionItem, RequestParams } from '@/api/types' -import { FOLDER_SOURCE } from '@/api/enums' +import { RESOURCE_TYPE } from '@/api/enums' import { FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants' import FolderTree from '@/components/business/folder-tree/index.vue' import ApplicationCard from './components/ApplicationCard.vue' /* 当前文件夹 */ -const currentFolder = ref({ ...FOLDER_ENTRIES[FOLDER_SOURCE.APPLICATION].all }) +const currentFolder = ref({ ...FOLDER_ENTRIES[RESOURCE_TYPE.APPLICATION].all }) function handleFolderSelect(folder: FolderItem) { const folderChanged = folder.id !== currentFolder.value.id @@ -78,7 +78,7 @@ function loadApplicationPage(pagination: { currentPage: number; pageSize: number diff --git a/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue b/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue index cdeac84a852..44bc48e758e 100644 --- a/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue +++ b/ui/src/views/system/identity/groups/dialog/CreateGroupMemberDialog.vue @@ -3,7 +3,7 @@ import { reactive, ref } from 'vue' import type { FormInstance, FormRules } from 'element-plus' import UserGroupsApi from '@/api/admin/system/user-groups' import CommonSystemApi from '@/api/admin/system/common' -import type { SystemUserOption } from '@/api/types' +import type { WorkspaceMemberOption } from '@/api/types' import { MsgSuccess } from '@/utils/message' const props = defineProps<{ @@ -22,7 +22,7 @@ const memberForm = reactive<{ userIds: string[] }>({ userIds: [] }) /* 成员选项 */ const optionsLoading = ref(false) -const userOptions = ref([]) +const userOptions = ref([]) let searchTimer: ReturnType | null = null function loadUserOptions(query?: string) { optionsLoading.value = true diff --git a/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue b/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue new file mode 100644 index 00000000000..0b13fd5d09b --- /dev/null +++ b/ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue @@ -0,0 +1,226 @@ + + + diff --git a/ui/src/views/system/identity/resource-authorization/components/PermissionTable.vue b/ui/src/views/system/identity/resource-authorization/components/PermissionTable.vue new file mode 100644 index 00000000000..8ab8f779495 --- /dev/null +++ b/ui/src/views/system/identity/resource-authorization/components/PermissionTable.vue @@ -0,0 +1,304 @@ + + + diff --git a/ui/src/views/system/identity/resource-authorization/constants.ts b/ui/src/views/system/identity/resource-authorization/constants.ts new file mode 100644 index 00000000000..ebb29cef011 --- /dev/null +++ b/ui/src/views/system/identity/resource-authorization/constants.ts @@ -0,0 +1,55 @@ +import { RESOURCE_TYPE, RESOURCE_PERMISSION } from '@/api/enums' +import type { OptionItem, ResourceAuthorizationType, ResourcePermission } from '@/api/types' + +export const RESOURCE_AUTHORIZATION_LABELS: Record = { + [RESOURCE_TYPE.APPLICATION]: '智能体', + [RESOURCE_TYPE.KNOWLEDGE]: '知识库', + [RESOURCE_TYPE.MODEL]: '模型', + [RESOURCE_TYPE.TOOL]: '工具', +} + +interface PermissionOption extends OptionItem { + description: string +} + +export function getPermissionOptions({ + allowRole, + isFolder = false, + isRootFolder = false, +}: { + allowRole: boolean + isFolder?: boolean + isRootFolder?: boolean +}): PermissionOption[] { + const permissionOptions: PermissionOption[] = [ + { + description: '', + label: '不授权', + value: RESOURCE_PERMISSION.NOT_AUTH, + }, + { + description: '仅能查看和使用该资源', + label: '查看', + value: RESOURCE_PERMISSION.VIEW, + }, + { + description: '可对该资源进行修改和删除操作', + label: '管理', + value: RESOURCE_PERMISSION.MANAGE, + }, + ] + + if (isRootFolder) { + return permissionOptions.filter(({ value }) => value !== RESOURCE_PERMISSION.NOT_AUTH) + } + + if (allowRole && !isFolder) { + permissionOptions.push({ + description: '根据用户角色决定对该资源的操作权限', + label: '按用户角色', + value: RESOURCE_PERMISSION.ROLE, + }) + } + + return permissionOptions +} diff --git a/ui/src/views/system/identity/users/dialog/UserPwdDialog.vue b/ui/src/views/system/identity/users/dialog/UserPwdDialog.vue index 40f9b8fb01d..e8ce29f115c 100644 --- a/ui/src/views/system/identity/users/dialog/UserPwdDialog.vue +++ b/ui/src/views/system/identity/users/dialog/UserPwdDialog.vue @@ -2,20 +2,20 @@ import { reactive, ref } from 'vue' import JSEncrypt from 'jsencrypt' import type { FormInstance, FormRules } from 'element-plus' +import { useRouter } from 'vue-router' import UserManageApi from '@/api/admin/system/user-manage' +import type { SystemUser, UpdatePasswordForm } from '@/api/types' import { useStore } from '@/stores' -import type { UpdatePasswordForm,SystemUser } from '@/api/types' -import { MsgSuccess } from '@/utils/message' +import { MsgSuccess } from '@/utils/message' defineOptions({ name: 'UserPwdDialog' }) - - const emit = defineEmits<{ refresh: [] }>() -const { auth } = useStore() +const router = useRouter() +const { auth, user } = useStore() const dialogVisible = ref(false) const passwordSubmitting = ref(false) @@ -70,6 +70,11 @@ async function submitPassword() { return UserManageApi.putUserPassword(userId.value, { encryptedData }) .then(() => { MsgSuccess('密码修改成功') + + if (userId.value === user.userInfo?.id) { + auth.clearToken() + return router.push({ name: 'login' }).then(() => undefined) + } emit('refresh') close() }) diff --git a/ui/src/views/tool/ToolView.vue b/ui/src/views/tool/ToolView.vue index 34331008ea2..813c641c30b 100644 --- a/ui/src/views/tool/ToolView.vue +++ b/ui/src/views/tool/ToolView.vue @@ -5,7 +5,7 @@ import CommonSystemApi from '@/api/admin/system/common' import ToolApi from '@/api/admin/workspace/tool/tool' import SharedApi from '@/api/admin/workspace/shared' import type { OptionItem, RequestParams, ToolType, ToolItem, FolderItem } from '@/api/types' -import { FOLDER_SOURCE, TOOL_SCOPE } from '@/api/enums' +import { RESOURCE_TYPE, TOOL_SCOPE } from '@/api/enums' import { TOOL_TYPE_OPTIONS, FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants' import FolderTree from '@/components/business/folder-tree/index.vue' @@ -14,7 +14,7 @@ import ToolCreateDropdown from './components/ToolCreateDropdown.vue' /* 当前文件夹 */ -const currentFolder = ref({ ...FOLDER_ENTRIES[FOLDER_SOURCE.TOOL].all }) +const currentFolder = ref({ ...FOLDER_ENTRIES[RESOURCE_TYPE.TOOL].all }) const isShared = computed(() => currentFolder.value.id === FOLDER_ENTRY_ID.SHARED) function handleFolderSelect(folder: FolderItem) { @@ -101,7 +101,7 @@ function handleDeleteTool(toolId: string) { diff --git a/ui/src/views/tool/components/ToolCreateDropdown.vue b/ui/src/views/tool/components/ToolCreateDropdown.vue index f2056afae41..f841a3d24d0 100644 --- a/ui/src/views/tool/components/ToolCreateDropdown.vue +++ b/ui/src/views/tool/components/ToolCreateDropdown.vue @@ -3,11 +3,14 @@ import { ref, useTemplateRef } from 'vue' import type { UploadFile, UploadInstance } from 'element-plus' import ToolApi from '@/api/admin/workspace/tool/tool' import { TOOL_TYPE } from '@/api/enums' +import { useStore } from '@/stores' import { MsgSuccess } from '@/utils/message' import ToolFormDrawer from '@/views/tool/create-form/ToolFormDrawer.vue' defineOptions({ name: 'ToolCreateDropdown' }) +const { user } = useStore() + const props = defineProps<{ folderId: string }>() @@ -27,37 +30,26 @@ function handleOpenToolForm() { toolFormDrawerRef.value?.open() } -function handleRefresh() { - emit('refresh') -} - /* 导入创建 */ const elUploadRef = ref() function handleImportCreate(file: UploadFile) { if (!file.raw) return ToolApi.postToolImport(file.raw, props.folderId) .then(() => { - MsgSuccess('导入成功') - // TODO:疑问 - // return user.profile().then(() => { - // getList() - // }) - // .catch((e: any) => { - // if (e.code === 400) { - // MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), { - // cancelButtonText: t('common.confirm'), - // confirmButtonText: t('common.professional'), - // }).then(() => { - // window.open('https://maxkb.cn/pricing.html', '_blank') - // }) - // } - // }) - emit('refresh') + return user.loadCurrentUser().then(() => { + MsgSuccess('导入成功') + handleRefresh() + }) }) .finally(() => { elUploadRef.value?.clearFiles() }) } + +// 发送刷新列表 +function handleRefresh() { + emit('refresh') +}