Skip to content

Commit a393fe6

Browse files
feat: nodes
1 parent 3d49196 commit a393fe6

39 files changed

Lines changed: 486 additions & 225 deletions

ui/src/api/API_README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文
180180
`resource_user_permission/resource/<target>/resource/<resource>`;与 System 用户视角的
181181
`user_resource_permission` 区分。分页使用 `ParamsPage`,直接返回
182182
`ResponsePage<ResourceUserPermission>`;提交 `ResourceUserPermissionPayload[]`
183+
查询和更新接口内部调用 `getWorkspaceId()` 获取路由工作空间,调用方不传工作空间 ID。
183184
`ResourceAuthorizationTargetType` 包含资源类型和三个 `_FOLDER` 类型,文件夹类型用于后端
184185
鉴权。包含子资源时传 `include_children: true` 及经过管理权限筛选的 `folder_ids`
185186
普通资源或仅当前文件夹不传子文件夹 ID。loading、刷新及成功提示由抽屉负责。
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
11
import { get, put } from '../core/request'
22
import type { Dict, ResourceAuthorizationType, ResourcePermissionItem, ResourcePermissionPayload } from '@/api/types'
33

4-
/** 系统管理资源授权 */
4+
/** 系统管理用户资源授权 */
55
const prefix = (workspaceId: string) => `/workspace/${workspaceId}/user_resource_permission`
6-
const groupPrefix = (workspaceId: string) => `/workspace/${workspaceId}/user_group_resource_permission`
6+
77
/** 获取指定空间、指定用户、指定资源类型的权限列表。 */
88
const getUserResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, query?: Dict<unknown>) => {
99
return get<ResourcePermissionItem[]>(`${prefix(workspaceId)}/user/${userId}/resource/${resource}`, query)
1010
}
1111

1212
/** 更新指定空间、指定用户、指定资源类型的权限列表。 */
13-
const putUserResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, permissions: ResourcePermissionPayload[]) => {
13+
const putUserResourcePermissions = (
14+
workspaceId: string,
15+
userId: string,
16+
resource: ResourceAuthorizationType,
17+
permissions: ResourcePermissionPayload[],
18+
) => {
1419
return put<ResourcePermissionPayload[], ResourcePermissionPayload[]>(`${prefix(workspaceId)}/user/${userId}/resource/${resource}`, permissions)
1520
}
1621

22+
/** 系统管理用户组资源授权 */
23+
const groupPrefix = (workspaceId: string) => `/workspace/${workspaceId}/user_group_resource_permission`
1724

1825
/** 获取指定空间、指定用户组、指定资源类型的权限列表。 */
1926
const getUserGroupResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, query?: Dict<unknown>) => {
2027
return get<ResourcePermissionItem[]>(`${groupPrefix(workspaceId)}/user_group/${userId}/resource/${resource}`, query)
2128
}
2229

2330
/** 更新指定空间、指定用户组、指定资源类型的权限列表。 */
24-
const putUserGroupResourcePermissions = (workspaceId: string, userId: string, resource: ResourceAuthorizationType, permissions: ResourcePermissionPayload[]) => {
31+
const putUserGroupResourcePermissions = (
32+
workspaceId: string,
33+
userId: string,
34+
resource: ResourceAuthorizationType,
35+
permissions: ResourcePermissionPayload[],
36+
) => {
2537
return put<ResourcePermissionPayload[], ResourcePermissionPayload[]>(
2638
`${groupPrefix(workspaceId)}/user_group/${userId}/resource/${resource}`,
2739
permissions,
2840
)
2941
}
3042

31-
3243
export default { getUserResourcePermissions, putUserResourcePermissions, getUserGroupResourcePermissions, putUserGroupResourcePermissions }
Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
11
import { get, put } from '../core/request'
22
import type { ParamsPage, ResponsePage } from '../core/types'
33
import type { Dict, ResourceAuthorizationTargetType, ResourceUserPermission, ResourceUserPermissionPayload } from '@/api/types'
4+
import { getWorkspaceId } from '@/utils/resource-context'
45

5-
const prefix = (workspaceId: string, targetId: string, resource: ResourceAuthorizationTargetType) =>
6-
`/workspace/${workspaceId}/resource_user_permission/resource/${targetId}/resource/${resource}`
7-
6+
const getPrefix = () => {
7+
const workspaceId = getWorkspaceId()
8+
return `/workspace/${workspaceId}`
9+
}
810
/** 获取指定资源或文件夹的用户权限分页列表。 */
9-
const getResourceAuthorization = (
10-
workspaceId: string,
11-
targetId: string,
12-
resource: ResourceAuthorizationTargetType,
13-
page: ParamsPage,
14-
query?: Dict<unknown>,
15-
) => {
16-
return get<ResponsePage<ResourceUserPermission>>(`${prefix(workspaceId, targetId, resource)}/${page.currentPage}/${page.pageSize}`, query)
11+
const getResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, page: ParamsPage, query?: Dict<unknown>) => {
12+
return get<ResponsePage<ResourceUserPermission>>(
13+
`${getPrefix()}/resource_user_permission/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`,
14+
query,
15+
)
1716
}
1817

1918
/** 更新资源的用户权限,可同时应用到有管理权限的子文件夹及资源。 */
20-
const putResourceAuthorization = (
21-
workspaceId: string,
22-
targetId: string,
23-
resource: ResourceAuthorizationTargetType,
24-
permissions: ResourceUserPermissionPayload[],
25-
) => {
26-
return put<ResourceUserPermissionPayload[], ResourceUserPermissionPayload[]>(prefix(workspaceId, targetId, resource), permissions)
19+
const putResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, permissions: ResourceUserPermissionPayload[]) => {
20+
return put<ResourceUserPermissionPayload[], ResourceUserPermissionPayload[]>(
21+
`${getPrefix()}/resource_user_permission/resource/${targetId}/resource/${resource}`,
22+
permissions,
23+
)
2724
}
2825

2926
export default { getResourceAuthorization, putResourceAuthorization }

ui/src/components/COMPONENT_README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,10 @@ Workspace 的文件夹虚拟树业务组件。组件根据当前资源上下文
11861186
新 ID 写入路由,但会清除已有的 `folderId`,避免刷新页面后恢复到旧文件夹。隐藏“全部”入口的
11871187
移动目录树继续以调用方传入的 `v-model` 为准。
11881188

1189+
可编辑文件夹的菜单提供“资源授权”,根据对应资源模块的 `folderAuth(folder.id)` 控制入口。
1190+
点击后复用 `ResourceAuthorizationDrawer`,传入 Workspace 资源授权 API、当前 `source`
1191+
原始完整文件夹子树;搜索过滤不缩减子资源授权范围。只读选择场景不挂载授权抽屉。
1192+
11891193
```vue
11901194
<script setup lang="ts">
11911195
import { RESOURCE_TYPE } from '@/api/enums'
@@ -1320,12 +1324,12 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags
13201324
手动导入 `business/resource-authorization-drawer/index.vue`。调用方传入完整 `api`
13211325
`typeof ResourceAuthorizationApi`)和 `type``ResourceAuthorizationTargetType`),不由抽屉
13221326
根据路由路径选择接口。当前后端的资源用户授权统一使用
1323-
`api/admin/workspace/resource-authorization.ts`。通过 `open(id, folder?, workspaceId?)` 打开;
1324-
System 场景显式传入资源所属工作空间,Workspace 场景可读取当前工作空间
1327+
`api/admin/workspace/resource-authorization.ts`。通过 `open(id, folder?)` 打开;
1328+
接口请求与文件夹鉴权均直接通过 `getWorkspaceId()` 读取路由中的工作空间,无需额外传入或保存
13251329
`isFolder``isRootFolder` 标记文件夹及根目录,传入文件夹类型也可识别文件夹。
13261330

13271331
支持姓名、用户名、权限及商业版本角色搜索,跨页选择和单人、批量授权;搜索后清空选择。
13281332
根目录隐藏“不授权”,返回的“不授权”按“查看”展示。包含子资源时必须传入完整文件夹子树,
1329-
抽屉根据目标工作空间筛选有管理权限的文件夹 ID;没有可管理文件夹时禁用该范围。
1333+
抽屉根据当前路由工作空间筛选有管理权限的文件夹 ID;没有可管理文件夹时禁用该范围。
13301334
`PermissionConfigDialog` 只收集权限和范围,保存成功才关闭并刷新,失败保留配置。
1331-
保存成功触发 `refresh`打开和关闭后统一重置临时数据,过期查询不写回
1335+
保存成功触发 `refresh`关闭后统一重置临时数据

ui/src/components/business/folder-tree/VirtualizedTree.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ interface DropTargetInfo {
2525
siblings?: FolderStat[]
2626
}
2727
28-
const props = withDefaults(defineProps<{ canEdit?: boolean; currentNodeKey?: string; data?: FolderTreeNode[]; draggable?: boolean; filterText?: string }>(), {
29-
canEdit: true,
30-
currentNodeKey: '',
31-
data: () => [],
32-
draggable: false,
33-
filterText: '',
34-
})
28+
const props = withDefaults(
29+
defineProps<{ canEdit?: boolean; currentNodeKey?: string; data?: FolderTreeNode[]; draggable?: boolean; filterText?: string }>(),
30+
{
31+
canEdit: true,
32+
currentNodeKey: '',
33+
data: () => [],
34+
draggable: false,
35+
filterText: '',
36+
},
37+
)
3538
3639
const emit = defineEmits<{
3740
create: [folder: FolderItem]
@@ -153,9 +156,9 @@ function handleAfterDrop() {
153156
--el-scrollbar-hover-opacity: 0.5;
154157
--el-scrollbar-hover-bg-color: var(--el-text-color-secondary);
155158
scrollbar-color: transparent transparent;
159+
scrollbar-gutter: stable;
156160
scrollbar-width: thin;
157161
158-
159162
&::-webkit-scrollbar {
160163
background-color: transparent;
161164
height: 6px;

ui/src/components/business/folder-tree/index.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
import { computed, defineAsyncComponent, onMounted, ref, useTemplateRef } from 'vue'
33
import { useRoute, useRouter } from 'vue-router'
44
import FolderApi from '@/api/admin/workspace/folder'
5+
import ResourceAuthorizationApi from '@/api/admin/workspace/resource-authorization'
56
import type { FolderSource, FolderItem } from '@/api/types'
67
import { FOLDER_SORT, type FolderSort } from './types'
78
import { FOLDER_ENTRIES, FOLDER_ENTRY_ID } from '@/constants'
89
import { MsgSuccess, MsgConfirm } from '@/utils/message'
910
import VirtualizedTree from './VirtualizedTree.vue'
1011
import FolderFormDialog from './FolderFormDialog.vue'
12+
import ResourceAuthorizationDrawer from '@/components/business/resource-authorization-drawer/index.vue'
1113
import { useStore } from '@/stores'
1214
import { getWorkspaceId } from '@/utils/resource-context'
1315
@@ -214,6 +216,13 @@ function handleOpenEditFolder(folder: FolderItem) {
214216
folderFormDialogRef.value?.open(folder.id, folder)
215217
}
216218
219+
/* 文件夹资源授权 */
220+
const resourceAuthorizationDrawerRef = useTemplateRef<InstanceType<typeof ResourceAuthorizationDrawer>>('resourceAuthorizationDrawerRef')
221+
222+
function handleOpenResourceAuthorization(folder: FolderItem) {
223+
resourceAuthorizationDrawerRef.value?.open(folder.id, folder)
224+
}
225+
217226
// 创建后选中新文件夹;编辑当前文件夹时同步最新信息。
218227
function handleFolderRefresh(folder: FolderItem, isEdit: boolean) {
219228
return loadFolders().then(() => {
@@ -410,6 +419,10 @@ defineExpose({ refresh: loadFolders, openCreate: handleOpenCreateFolder })
410419
<template #icon><MkIcon name="icon_move2_outlined" /></template>
411420
<span>移动到</span>
412421
</MkDropdownItem>
422+
<MkDropdownItem @click="handleOpenResourceAuthorization(row)">
423+
<template #icon><MkIcon name="icon_passkeys_outlined" /></template>
424+
<span>资源授权</span>
425+
</MkDropdownItem>
413426
<MkDropdownItem divided @click="handleDeleteFolder(row)">
414427
<template #icon><MkIcon name="icon_delete-trash_outlined" /></template>
415428
<span>删除</span>
@@ -420,5 +433,6 @@ defineExpose({ refresh: loadFolders, openCreate: handleOpenCreateFolder })
420433

421434
<FolderFormDialog ref="folderFormDialogRef" :title="formTitle" :source="props.source" @refresh="handleFolderRefresh" />
422435
<MoveToDialog v-if="canEdit" ref="moveToDialogRef" :loading="moveSubmitting" :source="props.source" @submit="handleMoveFolder" />
436+
<ResourceAuthorizationDrawer v-if="canEdit" ref="resourceAuthorizationDrawerRef" :api="ResourceAuthorizationApi" :type="source" is-folder />
423437
</div>
424438
</template>

ui/src/components/business/resource-authorization-drawer/PermissionConfigDialog.vue

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@ const permission = ref<ResourcePermission>()
1616
const includeChildren = ref(false)
1717
const scopeOnly = ref(false)
1818
19-
function resetData() {
20-
permission.value = undefined
21-
includeChildren.value = false
22-
scopeOnly.value = false
23-
}
24-
2519
function open(currentPermission?: ResourcePermission) {
26-
resetData()
2720
permission.value = currentPermission
2821
scopeOnly.value = Boolean(currentPermission)
2922
visible.value = true
@@ -38,11 +31,17 @@ function close() {
3831
visible.value = false
3932
}
4033
34+
function resetData() {
35+
permission.value = undefined
36+
includeChildren.value = false
37+
scopeOnly.value = false
38+
}
39+
4140
defineExpose({ open, close })
4241
</script>
4342

4443
<template>
45-
<MkDialog v-model="visible" :title="scopeOnly ? '生效资源' : '配置权限'" width="500" :show-close="!loading" @closed="resetData">
44+
<MkDialog v-model="visible" :title="scopeOnly ? '生效资源' : '配置权限'" :show-close="!loading" @closed="resetData">
4645
<el-radio-group v-if="!scopeOnly" v-model="permission" class="vertical-radio-group" :disabled="loading">
4746
<el-radio v-for="option in options" :key="option.value" :value="option.value">
4847
<p>{{ option.label }}</p>
@@ -51,12 +50,12 @@ defineExpose({ open, close })
5150
</el-radio-group>
5251
<template v-if="isFolder">
5352
<template v-if="!scopeOnly">
54-
<el-divider />
53+
<el-divider class="my-4!" />
5554
<h6 class="mb-2">生效资源</h6>
5655
</template>
5756
<el-radio-group v-model="includeChildren" class="vertical-radio-group" :disabled="loading">
58-
<el-radio :value="false">仅当前文件夹</el-radio>
59-
<el-radio :value="true" :disabled="!canIncludeChildren">包含所有有管理权限的子文件夹及资源</el-radio>
57+
<el-radio :value="false">仅当前资源</el-radio>
58+
<el-radio :value="true" :disabled="!canIncludeChildren">包含所有子文件夹</el-radio>
6059
</el-radio-group>
6160
</template>
6261
<template #footer>

ui/src/components/business/resource-authorization-drawer/index.vue

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const searchFields = computed(() => [
5252
5353
/* 查询与跨页选择 */
5454
const drawerVisible = ref(false)
55-
const workspaceId = ref('')
5655
const targetId = ref('')
5756
const folderData = ref<FolderItem>()
5857
const loading = ref(false)
@@ -62,25 +61,21 @@ const selectedUsers = ref<ResourceUserPermission[]>([])
6261
const paginationConfig = ref({ currentPage: 1, pageSize: 20, total: 0 })
6362
const searchQuery = ref<Dict<unknown>>()
6463
const tableRef = useTemplateRef('tableRef')
65-
let requestId = 0
66-
let sessionId = 0
6764
6865
function loadPermissions() {
6966
if (!targetId.value) return Promise.resolve()
70-
const currentRequestId = ++requestId
7167
loading.value = true
7268
return props.api
73-
.getResourceAuthorization(workspaceId.value, targetId.value, authorizationType.value, paginationConfig.value, searchQuery.value)
69+
.getResourceAuthorization(targetId.value, authorizationType.value, paginationConfig.value, searchQuery.value)
7470
.then(({ records, total }) => {
75-
if (currentRequestId !== requestId) return
7671
permissionUsers.value = records.map((user) => ({
7772
...user,
7873
permission: props.isRootFolder && user.permission === RESOURCE_PERMISSION.NOT_AUTH ? RESOURCE_PERMISSION.VIEW : user.permission,
7974
}))
8075
paginationConfig.value.total = total
8176
})
8277
.finally(() => {
83-
if (currentRequestId === requestId) loading.value = false
78+
loading.value = false
8479
})
8580
}
8681
@@ -100,17 +95,17 @@ function handleSelectionChange(selection: unknown[]) {
10095
selectedUsers.value = selection as ResourceUserPermission[]
10196
}
10297
103-
/* 子资源范围:使用目标工作空间鉴权,不依赖路由路径推断*/
98+
/* 子资源范围:直接使用路由中的工作空间鉴权*/
10499
const managedFolderIds = computed(() => {
105100
const permissions = folderPermissions.value
106101
if (!folderData.value || !permissions) return []
107102
const canManageAll = hasPermission(
108-
[RoleConstants.ADMIN, new Role(RoleConstants.WORKSPACE_MANAGE.name, workspaceId.value), permissions.auth.getWorkspaceManageFlagPermission()],
103+
[RoleConstants.ADMIN, new Role(RoleConstants.WORKSPACE_MANAGE.name, getWorkspaceId()), permissions.auth.getWorkspaceManageFlagPermission()],
109104
undefined,
110-
{ workspaceId: workspaceId.value },
105+
{ workspaceId: getWorkspaceId() },
111106
)
112107
function collectFolders(folder: FolderItem): string[] {
113-
const canManage = canManageAll || hasPermission(buildBaseResourcePermission(permissions!.edit, folder.id, workspaceId.value))
108+
const canManage = canManageAll || hasPermission(buildBaseResourcePermission(permissions!.edit, folder.id, getWorkspaceId()))
114109
return [...(canManage ? [folder.id] : []), ...(folder.children ?? []).flatMap(collectFolders)]
115110
}
116111
return collectFolders(folderData.value)
@@ -139,7 +134,6 @@ function handlePermissionChange(value: string | number | boolean | undefined, us
139134
140135
function submitPermissions(permission: ResourcePermission, includeChildren: boolean) {
141136
if (submitting.value || !pendingUserIds.value.length || (includeChildren && !managedFolderIds.value.length)) return
142-
const currentSessionId = sessionId
143137
const permissions: ResourceUserPermissionPayload[] = pendingUserIds.value.map((userId) => ({
144138
user_id: userId,
145139
permission,
@@ -148,9 +142,8 @@ function submitPermissions(permission: ResourcePermission, includeChildren: bool
148142
}))
149143
submitting.value = true
150144
return props.api
151-
.putResourceAuthorization(workspaceId.value, targetId.value, authorizationType.value, permissions)
145+
.putResourceAuthorization(targetId.value, authorizationType.value, permissions)
152146
.then(() => {
153-
if (currentSessionId !== sessionId) return
154147
MsgSuccess('提交成功')
155148
configDialogRef.value?.close()
156149
pendingUserIds.value = []
@@ -159,16 +152,19 @@ function submitPermissions(permission: ResourcePermission, includeChildren: bool
159152
return loadPermissions()
160153
})
161154
.finally(() => {
162-
if (currentSessionId === sessionId) submitting.value = false
155+
submitting.value = false
163156
})
164157
}
165158
166-
/* 抽屉生命周期 */
159+
function open(id: string, folder?: FolderItem) {
160+
targetId.value = id
161+
folderData.value = folder ? cloneDeep(folder) : undefined
162+
drawerVisible.value = true
163+
loadPermissions()
164+
}
165+
167166
function resetData() {
168-
sessionId += 1
169-
requestId += 1
170167
targetId.value = ''
171-
workspaceId.value = ''
172168
folderData.value = undefined
173169
permissionUsers.value = []
174170
searchQuery.value = undefined
@@ -179,20 +175,11 @@ function resetData() {
179175
clearSelection()
180176
}
181177
182-
function open(id: string, folder?: FolderItem, resourceWorkspaceId?: string) {
183-
resetData()
184-
targetId.value = id
185-
workspaceId.value = resourceWorkspaceId ?? folder?.workspace_id ?? getWorkspaceId() ?? 'default'
186-
folderData.value = folder ? cloneDeep(folder) : undefined
187-
drawerVisible.value = true
188-
loadPermissions()
189-
}
190-
191178
defineExpose({ open })
192179
</script>
193180

194181
<template>
195-
<MkDrawer v-model="drawerVisible" title="资源授权" size="850" :show-close="!submitting" @closed="resetData">
182+
<MkDrawer v-model="drawerVisible" title="资源授权" :size="920" :show-close="!submitting" @closed="resetData">
196183
<div class="flex-between mb-4 gap-4">
197184
<el-button type="primary" :disabled="!selectedUsers.length || loading || submitting" @click="handleOpenBatchConfig">配置权限</el-button>
198185
<MkComplexSearch :fields="searchFields" @change="handleSearch" />

0 commit comments

Comments
 (0)