feat: Folder move

This commit is contained in:
zhangzhanwei 2025-11-04 14:57:03 +08:00 committed by CaptainB
parent e497ce271f
commit ba4e0b77f5
6 changed files with 137 additions and 17 deletions

View File

@ -57,7 +57,7 @@ def get_folder_tree_serializer(source):
return None
FOLDER_DEPTH = 2 # Folder 不能超过3层
FOLDER_DEPTH = 10000
def check_depth(source, parent_id, workspace_id, current_depth=0):
@ -79,7 +79,7 @@ def check_depth(source, parent_id, workspace_id, current_depth=0):
# 验证层级深度
if depth + current_depth > FOLDER_DEPTH:
raise serializers.ValidationError(_('Folder depth cannot exceed 3 levels'))
raise serializers.ValidationError(_('Folder depth cannot exceed 10000 levels'))
def get_max_depth(current_node):
@ -100,6 +100,12 @@ def get_max_depth(current_node):
return max_depth
def has_target_permission(workspace_id, source, user_id, target):
return QuerySet(WorkspaceUserResourcePermission).filter(workspace_id=workspace_id, user_id=user_id,
auth_target_type=source, target=target,
permission_list__contains=['MANAGE']).exists()
class FolderSerializer(serializers.Serializer):
id = serializers.CharField(required=True, label=_('folder id'))
name = serializers.CharField(required=True, label=_('folder name'))
@ -185,11 +191,22 @@ class FolderSerializer(serializers.Serializer):
QuerySet(Folder).filter(id=current_id).update(**edit_dict)
if parent_id is not None and current_id != current_node.workspace_id and current_node.parent_id != parent_id:
# Folder 不能超过3层
current_depth = get_max_depth(current_node)
check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth)
parent = Folder.objects.get(id=parent_id)
current_node.move_to(parent)
source_type = self.data.get('source')
if has_target_permission(current_node.workspace_id, source_type, self.data.get('user_id'),
parent_id) or is_workspace_manage(self.data.get('user_id'),
current_node.workspace_id):
current_depth = get_max_depth(current_node)
check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth)
parent = Folder.objects.get(id=parent_id)
if QuerySet(Folder).filter(name=current_node.name, parent_id=parent_id,
workspace_id=current_node.workspace_id).exists():
raise serializers.ValidationError(_('Folder name already exists'))
current_node.move_to(parent)
else:
raise AppApiException(403, _('No permission for the target folder'))
return self.one()

View File

@ -2207,7 +2207,7 @@ msgid "parent id"
msgstr ""
#: apps/folders/serializers/folder.py:75
msgid "Folder depth cannot exceed 3 levels"
msgid "Folder depth cannot exceed 5 levels"
msgstr ""
#: apps/folders/serializers/folder.py:100
@ -8763,4 +8763,7 @@ msgid "Tag value already exists"
msgstr ""
msgid "Non-existent id"
msgstr ""
msgid "No permission for the target folder"
msgstr ""

View File

@ -2214,8 +2214,8 @@ msgid "parent id"
msgstr "父级 ID"
#: apps/folders/serializers/folder.py:75
msgid "Folder depth cannot exceed 3 levels"
msgstr "文件夹深度不能超过3级"
msgid "Folder depth cannot exceed 5 levels"
msgstr "文件夹深度不能超过5级"
#: apps/folders/serializers/folder.py:100
msgid "folder user id"
@ -8890,3 +8890,7 @@ msgstr "标签值已存在"
msgid "Non-existent id"
msgstr "不存在的ID"
msgid "No permission for the target folder"
msgstr "没有目标文件夹的权限"

View File

@ -2214,8 +2214,8 @@ msgid "parent id"
msgstr "父級 ID"
#: apps/folders/serializers/folder.py:75
msgid "Folder depth cannot exceed 3 levels"
msgstr "文件夾深度不能超過3級"
msgid "Folder depth cannot exceed 5 levels"
msgstr "文件夾深度不能超過5級"
#: apps/folders/serializers/folder.py:100
msgid "folder user id"
@ -8890,3 +8890,7 @@ msgstr "標籤值已存在"
msgid "Non-existent id"
msgstr "不存在的ID"
msgid "No permission for the target folder"
msgstr "沒有目標資料夾的權限"

View File

@ -36,6 +36,7 @@
</template>
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
import folderApi from '@/api/folder'
import { MsgError, MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import useStore from '@/stores'
@ -71,8 +72,11 @@ watch(dialogVisible, (bool) => {
}
})
const open = (data: any) => {
const isFolder = ref<boolean>(false)
const open = (data: any, is_folder?:any) => {
detail.value = data
isFolder.value = is_folder
getFolder()
dialogVisible.value = true
}
@ -99,7 +103,19 @@ const submitHandle = async () => {
...detail.value,
folder_id: selectForderId.value,
}
if (props.source === SourceTypeEnum.KNOWLEDGE) {
if (isFolder.value) {
const folder_obj = {
...detail.value,
parent_id: selectForderId.value,
}
folderApi.putFolder(detail.value.id, detail.value.folder_type, folder_obj, loading)
.then(() => {
MsgSuccess(t('common.saveSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
else if (props.source === SourceTypeEnum.KNOWLEDGE) {
if (detail.value.type === 2) {
KnowledgeApi.putLarkKnowledge(detail.value.id, obj, loading).then(() => {
MsgSuccess(t('common.saveSuccess'))

View File

@ -34,6 +34,10 @@
:current-node-key="currentNodeKey"
highlight-current
class="overflow-inherit_node__children"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
@node-drop="handleDrop"
node-key="id"
v-loading="loading"
v-bind="$attrs"
@ -63,7 +67,7 @@
<el-dropdown-menu>
<el-dropdown-item
@click.stop="openCreateFolder(data)"
v-if="node.level !== 3 && permissionPrecise.folderCreate(data.id)"
v-if="permissionPrecise.folderCreate(data.id)"
>
<AppIcon iconName="app-add-folder" class="color-secondary"></AppIcon>
{{ $t('components.folder.addChildFolder') }}
@ -75,6 +79,13 @@
<AppIcon iconName="app-edit" class="color-secondary"></AppIcon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item
@click.stop="openMoveToDialog(data)"
v-if="node.level !== 1 && permissionPrecise.folderEdit(data.id)"
>
<AppIcon iconName="app-migrate" class="color-secondary"></AppIcon>
{{ $t('common.moveTo') }}
</el-dropdown-item>
<el-dropdown-item
@click.stop="openAuthorization(data)"
v-if="permissionPrecise.folderAuth(data.id)"
@ -101,6 +112,11 @@
</el-scrollbar>
</div>
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" :title="title" />
<MoveToDialog
ref="MoveToDialogRef"
:source="props.source"
@refresh="emit('refreshTree')"
/>
<ResourceAuthorizationDrawer
:type="props.source"
:is-folder="true"
@ -117,13 +133,14 @@ import type { TreeInstance } from 'element-plus'
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
import ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'
import { t } from '@/locales'
import MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'
import { i18n_name } from '@/utils/common'
import folderApi from '@/api/folder'
import { EditionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
import useStore from '@/stores'
import { TreeToFlatten } from '@/utils/array'
import { MsgConfirm } from '@/utils/message'
import { MsgConfirm, MsgError, MsgSuccess } from '@/utils/message'
import permissionMap from '@/permission'
import bus from '@/bus'
defineOptions({ name: 'FolderTree' })
@ -177,13 +194,59 @@ const permissionPrecise = computed(() => {
const MoreFilledPermission = (node: any, data: any) => {
return (
(node.level !== 3 && permissionPrecise.value.folderCreate(data.id)) ||
permissionPrecise.value.folderCreate(data.id) ||
permissionPrecise.value.folderEdit(data.id) ||
permissionPrecise.value.folderDelete(data.id) ||
permissionPrecise.value.folderAuth(data.id)
)
}
const MoveToDialogRef = ref()
function openMoveToDialog(data:any) {
const obj = {
id: data.id,
folder_type: props.source,
}
MoveToDialogRef.value.open(obj, true)
}
const allowDrag = (node: any) => {
return permissionPrecise.value.folderEdit(node.data.id)
}
const allowDrop = (draggingNode: any, dropNode: any, type: string) => {
const dropData = dropNode.data
if (type === 'inner') {
return permissionPrecise.value.folderEdit(dropData.id)
}
return false
}
const handleDrop = (draggingNode: any, dropNode: any, dropType: string, ev: DragEvent) => {
const dragData = draggingNode.data
const dropData = dropNode.data
let newParentId: string
if (dropType === 'inner') {
newParentId = dropData.id
} else {
newParentId = dropData.parent_id
}
const obj = {
...dragData,
parent_id: newParentId
}
folderApi.putFolder(dragData.id, props.source, obj, loading)
.then(() => {
MsgSuccess(t('common.saveSuccess'))
emit('refreshTree')
})
.catch(() => {
MsgError(t('components.folder.requiredMessage'))
emit('refreshTree')
})
}
const { folder } = useStore()
onBeforeRouteLeave((to, from) => {
folder.setCurrentFolder({})
@ -328,6 +391,19 @@ onUnmounted(() => {
height: calc(100vh - 210px);
}
}
:deep(.el-tree) {
.el-tree-node.is-dragging {
opacity: 0.5;
}
.el-tree-node.is-drop-inner > .el-tree-node__content {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary);
border-radius: 4px;
}
.el-tree-node__content {
position: relative;
}
}
:deep(.overflow-inherit_node__children) {
.el-tree-node__children {
overflow: inherit !important;