feat: Retrieve the pagination list of resource relationships (#4570)

This commit is contained in:
shaohuzhang1 2025-12-26 14:29:43 +08:00 committed by GitHub
parent 592e429e3b
commit 97ae4e012b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 363 additions and 3 deletions

View File

@ -8886,4 +8886,10 @@ msgid "Role IDs cannot be empty"
msgstr ""
msgid "Some roles do not exist"
msgstr ""
msgid "Authorized pagination list for obtaining resources"
msgstr ""
msgid "Resources mapping"
msgstr ""

View File

@ -9013,3 +9013,9 @@ msgstr "角色 ID 不能为空"
msgid "Some roles do not exist"
msgstr "部分角色不存在"
msgid "Authorized pagination list for obtaining resources"
msgstr "获取资源的关系分页列表"
msgid "Resources mapping"
msgstr "资源映射"

View File

@ -9013,3 +9013,9 @@ msgstr "角色 ID 不能为空"
msgid "Some roles do not exist"
msgstr "部分角色不存在"
msgid "Authorized pagination list for obtaining resources"
msgstr "獲取資源的關係分頁清單"
msgid "Resources mapping"
msgstr "資源映射"

View File

@ -0,0 +1,80 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file resource_mapping.py
@date2025/12/26 14:07
@desc:
"""
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
from rest_framework import serializers
from common.mixins.api_mixin import APIMixin
class ResourceMappingResponse(serializers.Serializer):
id = serializers.UUIDField(required=True, label="主键id")
target_id = serializers.CharField(required=True, label="被关联资源名称")
target_type = serializers.CharField(required=True, label="被关联资源类型")
source_id = serializers.CharField(required=True, label="关联资源Id")
source_type = serializers.CharField(required=True, label="关联资源类型")
name = serializers.CharField(required=True, label="名称")
desc = serializers.CharField(required=False, label="描述")
user_id = serializers.UUIDField(required=True, label="主键id")
class ResourceMappingAPI(APIMixin):
@staticmethod
def get_parameters():
return [
OpenApiParameter(
name="workspace_id",
description="工作空间id",
type=OpenApiTypes.STR,
location='path',
required=True,
),
OpenApiParameter(
name="source",
description="资源类型",
type=OpenApiTypes.STR,
location='path',
required=True,
),
OpenApiParameter(
name="source_id",
description="资源id",
type=OpenApiTypes.STR,
location='path',
required=True,
),
OpenApiParameter(
name="current_page",
description=_("Current page"),
type=OpenApiTypes.INT,
location='path',
required=True,
),
OpenApiParameter(
name="page_size",
description=_("Page size"),
type=OpenApiTypes.INT,
location='path',
required=True,
),
OpenApiParameter(
name="resource_name",
description="名称",
type=OpenApiTypes.STR,
location='query',
required=False
),
]
@staticmethod
def get_response():
return ResourceMappingResponse(many=True)

View File

@ -0,0 +1,44 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file workspace_user_resource_permission.py
@date2025/4/28 17:17
@desc:
"""
import os
from django.db import models
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.db.search import native_page_search, get_dynamics_model
from common.utils.common import get_file_content
from maxkb.conf import PROJECT_DIR
class ResourceMappingSerializer(serializers.Serializer):
resource = serializers.CharField(required=True, label=_('resource'))
resource_id = serializers.UUIDField(required=True, label=_('resource Id'))
resource_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('resource'))
def get_query_set(self):
queryset = QuerySet(model=get_dynamics_model({
'name': models.CharField(),
'target_id': models.CharField(),
"target_type": models.CharField()
}))
queryset = queryset.filter(target_id=self.data.get('resource_id'),
target_type=self.data.get('resource'))
if self.data.get('resource_name'):
queryset = queryset.filter(name__icontains=self.data.get('resource_name'))
return queryset
def page(self, current_page, page_size):
return native_page_search(current_page, page_size, self.get_query_set(), get_file_content(
os.path.join(PROJECT_DIR, "apps", "system_manage",
'sql', 'list_resource_mapping.sql')), with_table_name=False)

View File

@ -0,0 +1,12 @@
WITH source_data_cte AS (
SELECT 'APPLICATION' as source_type, id, "name", "desc","user_id"
FROM application
UNION ALL
SELECT 'KNOWLEDGE' as source_type, id, "name", "desc","user_id"
FROM knowledge)
SELECT rm.*,
sdc.*
FROM resource_mapping rm
LEFT JOIN source_data_cte sdc
ON rm.source_type = sdc.source_type
AND rm.source_id::uuid = sdc.id

View File

@ -9,6 +9,7 @@ urlpatterns = [
path('workspace/<str:workspace_id>/user_resource_permission/user/<str:user_id>/resource/<str:resource>/<int:current_page>/<int:page_size>', views.WorkSpaceUserResourcePermissionView.Page.as_view()),
path('workspace/<str:workspace_id>/resource_user_permission/resource/<str:target>/resource/<str:resource>', views.WorkspaceResourceUserPermissionView.as_view()),
path('workspace/<str:workspace_id>/resource_user_permission/resource/<str:target>/resource/<str:resource>/<int:current_page>/<int:page_size>', views.WorkspaceResourceUserPermissionView.Page.as_view()),
path('workspace/<str:workspace_id>/resource_mapping/<str:resource>/<str:resource_id>/<int:current_page>/<int:page_size>', views.ResourceMappingView.as_view()),
path('email_setting', views.SystemSetting.Email.as_view()),
path('profile', views.SystemProfile.as_view()),
path('valid/<str:valid_type>/<int:valid_count>', views.Valid.as_view())

View File

@ -10,3 +10,4 @@ from .user_resource_permission import *
from .email_setting import *
from .system_profile import *
from .valid import *
from .resource_mapping import *

View File

@ -0,0 +1,37 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file resource_mapping.py
@date2025/12/25 15:28
@desc:
"""
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema
from rest_framework.request import Request
from rest_framework.views import APIView
from common import result
from common.auth import TokenAuth
from system_manage.api.resource_mapping import ResourceMappingAPI
from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer
class ResourceMappingView(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['GET'],
description=_('Retrieve the pagination list of resource relationships'),
operation_id=_('Retrieve the pagination list of resource relationships'), # type: ignore
responses=ResourceMappingAPI.get_response(),
parameters=ResourceMappingAPI.get_parameters(),
tags=[_('Resources mapping')] # type: ignore
)
def get(self, request: Request, workspace_id: str, resource: str, resource_id: str, current_page, page_size):
return result.success(ResourceMappingSerializer({
'resource': resource,
'resource_id': resource_id,
'resource_name': request.query_params.get('resource_name')
}).page(current_page, page_size))

View File

@ -0,0 +1,28 @@
import { Result } from '@/request/Result'
import { get, put, post, del } from '@/request/index'
import type { Ref } from 'vue'
import type { pageRequest } from '@/api/type/common'
const prefix = '/workspace'
/**
*
* @query
*/
const getResourceMapping: (
workspace_id: string,
resource: string,
resource_id: string,
page: pageRequest,
params?: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (workspace_id, resource, resource_id, page, params, loading) => {
return get(
`${prefix}/${workspace_id}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`,
params,
loading,
)
}
export default {
getResourceMapping,
}

View File

@ -0,0 +1,121 @@
<template>
<el-drawer
v-model="visible"
:title="$t('views.system.resourceAuthorization.title')"
size="60%"
:append-to-body="true"
>
<div class="flex-between mb-16">
<div class="flex-between complex-search">
<el-select class="complex-search__left" v-model="searchType" style="width: 100px">
<el-option
:label="$t('views.userManage.userForm.resourceName.label')"
value="resource_name"
/>
</el-select>
<el-input
v-if="searchType === 'resource_name'"
v-model="query.resource_name"
:placeholder="$t('common.search')"
style="width: 220px"
clearable
@keyup.enter="pageResouceMapping()"
/>
</div>
</div>
<app-table
ref="multipleTableRef"
class="mt-16"
:data="tableData"
:pagination-config="paginationConfig"
@sizeChange="handleSizeChange"
@changePage="pageResouceMapping"
:maxTableHeight="200"
:row-key="(row: any) => row.id"
v-loading="loading"
>
<el-table-column
prop="name"
:label="$t('views.userManage.userForm.name.label', '名称')"
min-width="120"
show-overflow-tooltip
/>
<el-table-column
prop="desc"
min-width="120"
show-overflow-tooltip
:label="$t('views.login.loginForm.desc.label', '描述')"
/>
</app-table>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { useRoute } from 'vue-router'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
import useStore from '@/stores'
const route = useRoute()
const { user } = useStore()
const searchType = ref<string>('resource_name')
const query = ref<any>({
resource_name: '',
})
const loading = ref<boolean>(false)
const tableData = ref<Array<any>>()
const visible = ref<boolean>(false)
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
total: 0,
})
const apiType = computed(() => {
if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
}
})
const pageResouceMapping = () => {
const workspaceId = user.getWorkspaceId() || 'default'
const params: any = {}
if (query.value[searchType.value]) {
params[searchType.value] = query.value[searchType.value]
}
loadSharedApi({ type: 'resourceMapping', systemType: apiType.value })
.getResourceMapping(
workspaceId,
currentSource.value,
currentSourceId.value,
paginationConfig,
params,
loading,
)
.then((res: any) => {
tableData.value = res.data.records || []
paginationConfig.total = res.data.total || 0
})
}
function handleSizeChange() {
paginationConfig.current_page = 1
pageResouceMapping()
}
const currentSource = ref<string>()
const currentSourceId = ref<string>()
const open = (source: string, sourceId: string) => {
visible.value = true
currentSource.value = source
currentSourceId.value = sourceId
pageResouceMapping()
}
const close = () => {
visible.value = false
paginationConfig.current_page = 1
}
defineExpose({
open,
close,
})
</script>
<style lang="scss" scoped></style>

View File

@ -2,6 +2,7 @@ import knowledgeWorkspaceApi from '@/api/knowledge/knowledge'
import documentWorkspaceApi from '@/api/knowledge/document'
import paragraphWorkspaceApi from '@/api/knowledge/paragraph'
import problemWorkspaceApi from '@/api/knowledge/problem'
import resourceMappingApi from '@/api/workspace/resource-mapping'
import modelWorkspaceApi from '@/api/model/model'
import toolWorkspaceApi from '@/api/tool/tool'
import chatUserWorkspaceApi from '@/api/chat-user/chat-user'
@ -34,6 +35,7 @@ import workflowVersionResourceApi from '@/api/system-resource-management/workflo
import chatLogResourceApi from '@/api/system-resource-management/chat-log'
import resourceAuthorizationResourceApi from '@/api/system-resource-management/resource-authorization'
import folderResourceApi from '@/api/system-resource-management/folder'
// 普通 API
const workspaceApiMap = {
knowledge: knowledgeWorkspaceApi,
@ -50,6 +52,7 @@ const workspaceApiMap = {
chatLog: chatLogWorkspaceApi,
resourceAuthorization: resourceAuthorizationWorkspaceApi,
folder: folderWorkspaceApi,
resourceMapping: resourceMappingApi,
} as any
// 系统分享 API

View File

@ -104,10 +104,21 @@
<AppIcon iconName="app-setting" class="color-secondary"></AppIcon>
{{ $t('views.model.modelForm.title.paramSetting') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="openAuthorization(model)" v-if="apiType === 'workspace' && permissionPrecise.auth(model.id)">
<el-dropdown-item
@click.stop="openAuthorization(model)"
v-if="apiType === 'workspace' && permissionPrecise.auth(model.id)"
>
<AppIcon iconName="app-resource-authorization" class="color-secondary"></AppIcon>
{{ $t('views.system.resourceAuthorization.title') }}
</el-dropdown-item>
<el-dropdown-item
text
@click.stop="openResourceMappingDrawer(model)"
v-if="permissionPrecise.delete(model.id)"
>
<AppIcon iconName="app-delete" class="color-secondary"></AppIcon>
{{ $t('common.delete') }}
</el-dropdown-item>
<el-dropdown-item
divided
text
@ -132,6 +143,7 @@
ref="ResourceAuthorizationDrawerRef"
v-if="apiType === 'workspace'"
/>
<ResourceMappingDrawer ref="resourceMappingDrawerRef"></ResourceMappingDrawer>
</card-box>
</template>
<script setup lang="ts">
@ -144,13 +156,14 @@ import { modelType } from '@/enums/model'
import ParamSettingDialog from './ParamSettingDialog.vue'
import AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'
import ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'
import ResourceMappingDrawer from '@/components/resource_mapping/index.vue'
import { SourceTypeEnum } from '@/enums/common'
import { t } from '@/locales'
import { i18n_name } from '@/utils/common'
import permissionMap from '@/permission'
import { useRoute } from 'vue-router'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
const resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()
const route = useRoute()
const props = defineProps<{
@ -161,7 +174,9 @@ const props = defineProps<{
isSystemShare?: boolean | undefined
apiType: 'systemShare' | 'workspace' | 'systemManage'
}>()
const openResourceMappingDrawer = (model: any) => {
resourceMappingDrawerRef.value?.open('MODEL', model.id)
}
const isSystemShare = computed(() => {
return props.apiType === 'systemShare'
})