feat: Folder move
parent
8e3550eb83
commit
eb867daa6c
|
|
@ -57,7 +57,7 @@ def get_folder_tree_serializer(source):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
FOLDER_DEPTH = 2 # Folder 不能超过3层
|
FOLDER_DEPTH = 10000
|
||||||
|
|
||||||
|
|
||||||
def check_depth(source, parent_id, workspace_id, current_depth=0):
|
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:
|
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):
|
def get_max_depth(current_node):
|
||||||
|
|
@ -100,6 +100,12 @@ def get_max_depth(current_node):
|
||||||
return max_depth
|
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):
|
class FolderSerializer(serializers.Serializer):
|
||||||
id = serializers.CharField(required=True, label=_('folder id'))
|
id = serializers.CharField(required=True, label=_('folder id'))
|
||||||
name = serializers.CharField(required=True, label=_('folder name'))
|
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)
|
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:
|
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)
|
source_type = self.data.get('source')
|
||||||
check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth)
|
if has_target_permission(current_node.workspace_id, source_type, self.data.get('user_id'),
|
||||||
parent = Folder.objects.get(id=parent_id)
|
parent_id) or is_workspace_manage(self.data.get('user_id'),
|
||||||
current_node.move_to(parent)
|
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()
|
return self.one()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2207,7 +2207,7 @@ msgid "parent id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:75
|
#: apps/folders/serializers/folder.py:75
|
||||||
msgid "Folder depth cannot exceed 3 levels"
|
msgid "Folder depth cannot exceed 5 levels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:100
|
#: apps/folders/serializers/folder.py:100
|
||||||
|
|
@ -8763,4 +8763,7 @@ msgid "Tag value already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Non-existent id"
|
msgid "Non-existent id"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No permission for the target folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2214,8 +2214,8 @@ msgid "parent id"
|
||||||
msgstr "父级 ID"
|
msgstr "父级 ID"
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:75
|
#: apps/folders/serializers/folder.py:75
|
||||||
msgid "Folder depth cannot exceed 3 levels"
|
msgid "Folder depth cannot exceed 5 levels"
|
||||||
msgstr "文件夹深度不能超过3级"
|
msgstr "文件夹深度不能超过5级"
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:100
|
#: apps/folders/serializers/folder.py:100
|
||||||
msgid "folder user id"
|
msgid "folder user id"
|
||||||
|
|
@ -8890,3 +8890,7 @@ msgstr "标签值已存在"
|
||||||
|
|
||||||
msgid "Non-existent id"
|
msgid "Non-existent id"
|
||||||
msgstr "不存在的ID"
|
msgstr "不存在的ID"
|
||||||
|
|
||||||
|
msgid "No permission for the target folder"
|
||||||
|
msgstr "没有目标文件夹的权限"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2214,8 +2214,8 @@ msgid "parent id"
|
||||||
msgstr "父級 ID"
|
msgstr "父級 ID"
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:75
|
#: apps/folders/serializers/folder.py:75
|
||||||
msgid "Folder depth cannot exceed 3 levels"
|
msgid "Folder depth cannot exceed 5 levels"
|
||||||
msgstr "文件夾深度不能超過3級"
|
msgstr "文件夾深度不能超過5級"
|
||||||
|
|
||||||
#: apps/folders/serializers/folder.py:100
|
#: apps/folders/serializers/folder.py:100
|
||||||
msgid "folder user id"
|
msgid "folder user id"
|
||||||
|
|
@ -8890,3 +8890,7 @@ msgstr "標籤值已存在"
|
||||||
|
|
||||||
msgid "Non-existent id"
|
msgid "Non-existent id"
|
||||||
msgstr "不存在的ID"
|
msgstr "不存在的ID"
|
||||||
|
|
||||||
|
msgid "No permission for the target folder"
|
||||||
|
msgstr "沒有目標資料夾的權限"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, reactive } from 'vue'
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
import folderApi from '@/api/folder'
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message'
|
import { MsgError, MsgSuccess } from '@/utils/message'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
import useStore from '@/stores'
|
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
|
detail.value = data
|
||||||
|
isFolder.value = is_folder
|
||||||
getFolder()
|
getFolder()
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +103,19 @@ const submitHandle = async () => {
|
||||||
...detail.value,
|
...detail.value,
|
||||||
folder_id: selectForderId.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) {
|
if (detail.value.type === 2) {
|
||||||
KnowledgeApi.putLarkKnowledge(detail.value.id, obj, loading).then(() => {
|
KnowledgeApi.putLarkKnowledge(detail.value.id, obj, loading).then(() => {
|
||||||
MsgSuccess(t('common.saveSuccess'))
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@
|
||||||
:current-node-key="currentNodeKey"
|
:current-node-key="currentNodeKey"
|
||||||
highlight-current
|
highlight-current
|
||||||
class="overflow-inherit_node__children"
|
class="overflow-inherit_node__children"
|
||||||
|
draggable
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:allow-drag="allowDrag"
|
||||||
|
@node-drop="handleDrop"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
|
@ -63,7 +67,7 @@
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
@click.stop="openCreateFolder(data)"
|
@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>
|
<AppIcon iconName="app-add-folder" class="color-secondary"></AppIcon>
|
||||||
{{ $t('components.folder.addChildFolder') }}
|
{{ $t('components.folder.addChildFolder') }}
|
||||||
|
|
@ -75,6 +79,13 @@
|
||||||
<AppIcon iconName="app-edit" class="color-secondary"></AppIcon>
|
<AppIcon iconName="app-edit" class="color-secondary"></AppIcon>
|
||||||
{{ $t('common.edit') }}
|
{{ $t('common.edit') }}
|
||||||
</el-dropdown-item>
|
</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
|
<el-dropdown-item
|
||||||
@click.stop="openAuthorization(data)"
|
@click.stop="openAuthorization(data)"
|
||||||
v-if="permissionPrecise.folderAuth(data.id)"
|
v-if="permissionPrecise.folderAuth(data.id)"
|
||||||
|
|
@ -101,6 +112,11 @@
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" :title="title" />
|
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" :title="title" />
|
||||||
|
<MoveToDialog
|
||||||
|
ref="MoveToDialogRef"
|
||||||
|
:source="props.source"
|
||||||
|
@refresh="emit('refreshTree')"
|
||||||
|
/>
|
||||||
<ResourceAuthorizationDrawer
|
<ResourceAuthorizationDrawer
|
||||||
:type="props.source"
|
:type="props.source"
|
||||||
:is-folder="true"
|
:is-folder="true"
|
||||||
|
|
@ -117,13 +133,14 @@ import type { TreeInstance } from 'element-plus'
|
||||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||||
import ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'
|
import ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
|
import MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'
|
||||||
import { i18n_name } from '@/utils/common'
|
import { i18n_name } from '@/utils/common'
|
||||||
import folderApi from '@/api/folder'
|
import folderApi from '@/api/folder'
|
||||||
import { EditionConst } from '@/utils/permission/data'
|
import { EditionConst } from '@/utils/permission/data'
|
||||||
import { hasPermission } from '@/utils/permission/index'
|
import { hasPermission } from '@/utils/permission/index'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
import { TreeToFlatten } from '@/utils/array'
|
import { TreeToFlatten } from '@/utils/array'
|
||||||
import { MsgConfirm } from '@/utils/message'
|
import { MsgConfirm, MsgError, MsgSuccess } from '@/utils/message'
|
||||||
import permissionMap from '@/permission'
|
import permissionMap from '@/permission'
|
||||||
import bus from '@/bus'
|
import bus from '@/bus'
|
||||||
defineOptions({ name: 'FolderTree' })
|
defineOptions({ name: 'FolderTree' })
|
||||||
|
|
@ -177,13 +194,59 @@ const permissionPrecise = computed(() => {
|
||||||
|
|
||||||
const MoreFilledPermission = (node: any, data: any) => {
|
const MoreFilledPermission = (node: any, data: any) => {
|
||||||
return (
|
return (
|
||||||
(node.level !== 3 && permissionPrecise.value.folderCreate(data.id)) ||
|
permissionPrecise.value.folderCreate(data.id) ||
|
||||||
permissionPrecise.value.folderEdit(data.id) ||
|
permissionPrecise.value.folderEdit(data.id) ||
|
||||||
permissionPrecise.value.folderDelete(data.id) ||
|
permissionPrecise.value.folderDelete(data.id) ||
|
||||||
permissionPrecise.value.folderAuth(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()
|
const { folder } = useStore()
|
||||||
onBeforeRouteLeave((to, from) => {
|
onBeforeRouteLeave((to, from) => {
|
||||||
folder.setCurrentFolder({})
|
folder.setCurrentFolder({})
|
||||||
|
|
@ -328,6 +391,19 @@ onUnmounted(() => {
|
||||||
height: calc(100vh - 210px);
|
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) {
|
:deep(.overflow-inherit_node__children) {
|
||||||
.el-tree-node__children {
|
.el-tree-node__children {
|
||||||
overflow: inherit !important;
|
overflow: inherit !important;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue