feat: Folder directories support moving or dragging.

v3.2
wangdan-fit2cloud 2025-11-06 21:02:47 +08:00
parent 6df1e19eaa
commit 1263592221
10 changed files with 166 additions and 139 deletions

View File

@ -3,14 +3,34 @@
{{ breadcrumbData[0]?.name }} {{ breadcrumbData[0]?.name }}
</h2> </h2>
<el-breadcrumb separator-icon="ArrowRight" style="line-height: normal" class="mt-4" v-else> <el-breadcrumb separator-icon="ArrowRight" style="line-height: normal" class="mt-4" v-else>
<el-breadcrumb-item v-for="(item, index) in breadcrumbData" :key="index"> <template v-if="breadcrumbData.length > 3">
<h5 class="ml-4 ellipsis" v-if="index === breadcrumbData.length - 1" :title="item.name"> <el-breadcrumb-item>
{{ item.name }} <el-button link @click="handleClick(breadcrumbData[0])" :title="breadcrumbData[0].name">
</h5> <span class="ellipsis"> {{ breadcrumbData[0].name }}</span>
<el-button v-else link @click="handleClick(item)" :title="item.name"> </el-button>
<span class="ellipsis"> {{ item.name }}</span> </el-breadcrumb-item>
</el-button> <el-breadcrumb-item>
</el-breadcrumb-item> <el-button link @click="handleClick(breadcrumbData[breadcrumbData.length - 2])">
<el-icon><MoreFilled /></el-icon>
</el-button>
</el-breadcrumb-item>
<el-breadcrumb-item>
<h5 class="ml-4 ellipsis" :title="breadcrumbData[breadcrumbData.length - 1].name">
{{ breadcrumbData[breadcrumbData.length - 1].name }}
</h5>
</el-breadcrumb-item>
</template>
<template v-else>
<el-breadcrumb-item v-for="(item, index) in breadcrumbData" :key="index">
<h5 class="ml-4 ellipsis" v-if="index === breadcrumbData.length - 1" :title="item.name">
{{ item.name }}
</h5>
<el-button v-else link @click="handleClick(item)" :title="item.name">
<span class="ellipsis"> {{ item.name }}</span>
</el-button>
</el-breadcrumb-item>
</template>
</el-breadcrumb> </el-breadcrumb>
</template> </template>

View File

@ -11,12 +11,6 @@
ref="treeRef" ref="treeRef"
:source="source" :source="source"
:data="folderList" :data="folderList"
:treeStyle="{
height: 'calc(100vh - 320px)',
border: '1px solid #ebeef5',
borderRadius: '6px',
padding: '8px',
}"
:default-expanded-keys="[currentNodeKey]" :default-expanded-keys="[currentNodeKey]"
:canOperation="false" :canOperation="false"
class="move-to-dialog-tree" class="move-to-dialog-tree"
@ -74,7 +68,7 @@ watch(dialogVisible, (bool) => {
const isFolder = ref<boolean>(false) const isFolder = ref<boolean>(false)
const open = (data: any, is_folder?:any) => { const open = (data: any, is_folder?: any) => {
detail.value = data detail.value = data
isFolder.value = is_folder isFolder.value = is_folder
getFolder() getFolder()
@ -105,17 +99,17 @@ const submitHandle = async () => {
} }
if (isFolder.value) { if (isFolder.value) {
const folder_obj = { const folder_obj = {
...detail.value, ...detail.value,
parent_id: selectForderId.value, parent_id: selectForderId.value,
} }
folderApi.putFolder(detail.value.id, detail.value.folder_type, folder_obj, loading) folderApi
.putFolder(detail.value.id, detail.value.folder_type, folder_obj, loading)
.then(() => { .then(() => {
MsgSuccess(t('common.saveSuccess')) MsgSuccess(t('common.saveSuccess'))
emit('refresh') emit('refresh')
dialogVisible.value = false dialogVisible.value = false
}) })
} } else if (props.source === SourceTypeEnum.KNOWLEDGE) {
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'))
@ -155,8 +149,12 @@ defineExpose({ open })
padding: 0 !important; padding: 0 !important;
margin-bottom: 8px; margin-bottom: 8px;
} }
:deep(.tree-label) { :deep(.el-scrollbar) {
max-width: 100% !important; border: 1px solid var(--el-border-color-light);
border-radius: 6px;
}
:deep(.el-tree) {
height: calc(100vh - 320px)!important;
} }
} }
</style> </style>

View File

@ -5,27 +5,28 @@
:placeholder="$t('common.search')" :placeholder="$t('common.search')"
prefix-icon="Search" prefix-icon="Search"
clearable clearable
class="p-8" class="p-16 pb-0"
/> />
<div class="p-8 pb-0" v-if="showShared && hasPermission(EditionConst.IS_EE, 'OR')">
<div v-if="showShared && hasPermission(EditionConst.IS_EE, 'OR')" class="border-b mb-4"> <div class="border-b">
<div <div
@click="handleSharedNodeClick" @click="handleSharedNodeClick"
class="shared-button flex cursor" class="shared-button flex cursor border-r-6"
:class="currentNodeKey === 'share' && 'active'" :class="currentNodeKey === 'share' && 'active'"
> >
<AppIcon <AppIcon
iconName="app-shared-active" iconName="app-shared-active"
style="font-size: 18px" style="font-size: 18px"
class="color-primary" class="color-primary"
></AppIcon> ></AppIcon>
<span class="ml-8">{{ shareTitle }}</span> <span class="ml-8">{{ shareTitle }}</span>
</div>
</div> </div>
</div> </div>
<el-scrollbar> <el-scrollbar>
<el-tree <el-tree
class="overflow-inherit_node__children" class="folder-tree__main p-8"
:class=" :class="
showShared && hasPermission(EditionConst.IS_EE, 'OR') showShared && hasPermission(EditionConst.IS_EE, 'OR')
? 'tree-height-shared' ? 'tree-height-shared'
@ -40,7 +41,7 @@
:default-expanded-keys="[currentNodeKey]" :default-expanded-keys="[currentNodeKey]"
:current-node-key="currentNodeKey" :current-node-key="currentNodeKey"
highlight-current highlight-current
draggable :draggable="draggable"
:allow-drop="allowDrop" :allow-drop="allowDrop"
:allow-drag="allowDrag" :allow-drag="allowDrag"
@node-drop="handleDrop" @node-drop="handleDrop"
@ -49,13 +50,12 @@
v-bind="$attrs" v-bind="$attrs"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="flex-between w-full" @mouseenter.stop="handleMouseEnter(data)"> <div
<div class="flex align-center"> @mouseenter.stop="handleMouseEnter(data)"
<AppIcon iconName="app-folder" style="font-size: 20px"></AppIcon> class="flex align-center w-full custom-tree-node"
<span class="ml-8 ellipsis tree-label" style="max-width: 110px" :title="node.label">{{ >
i18n_name(node.label) <AppIcon iconName="app-folder" style="font-size: 20px"></AppIcon>
}}</span> <span class="tree-label ml-8" :title="node.label">{{ i18n_name(node.label) }}</span>
</div>
<div <div
v-if="canOperation && MoreFilledPermission(node, data)" v-if="canOperation && MoreFilledPermission(node, data)"
@ -63,7 +63,7 @@
v-show="hoverNodeId === data.id" v-show="hoverNodeId === data.id"
@mouseenter.stop="handleMouseEnter(data)" @mouseenter.stop="handleMouseEnter(data)"
@mouseleave.stop="handleMouseleave" @mouseleave.stop="handleMouseleave"
class="mr-16" class="mr-8 tree-operation-button"
> >
<el-dropdown trigger="click" :teleported="false"> <el-dropdown trigger="click" :teleported="false">
<el-button text class="w-full" v-if="MoreFilledPermission(node, data)"> <el-button text class="w-full" v-if="MoreFilledPermission(node, data)">
@ -178,6 +178,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
draggable: {
type: Boolean,
default: false,
},
}) })
const resourceType = computed(() => { const resourceType = computed(() => {
if (props.source === 'APPLICATION') { if (props.source === 'APPLICATION') {
@ -399,7 +403,7 @@ onUnmounted(() => {
padding-top: 4px; padding-top: 4px;
height: calc(100vh - 180px); height: calc(100vh - 180px);
} }
:deep(.el-tree) { :deep(.folder-tree__main) {
.el-tree-node.is-dragging { .el-tree-node.is-dragging {
opacity: 0.5; opacity: 0.5;
} }
@ -408,11 +412,19 @@ onUnmounted(() => {
border: 2px dashed var(--el-color-primary); border: 2px dashed var(--el-color-primary);
border-radius: 4px; border-radius: 4px;
} }
.custom-tree-node {
box-sizing: content-box;
width: calc(100% - 27px);
}
.tree-label {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.el-tree-node__content { .el-tree-node__content {
position: relative; position: relative;
} }
}
:deep(.overflow-inherit_node__children) {
.el-tree-node__children { .el-tree-node__children {
overflow: inherit !important; overflow: inherit !important;
} }

View File

@ -14,6 +14,9 @@
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
} }
.el-icon {
flex-shrink: 0;
}
// card // card
.el-card { .el-card {

View File

@ -75,18 +75,16 @@
<el-tab-pane :label="$t('views.tool.title')" name="tool"> <el-tab-pane :label="$t('views.tool.title')" name="tool">
<LayoutContainer> <LayoutContainer>
<template #left> <template #left>
<div class="p-8"> <folder-tree
<folder-tree :source="SourceTypeEnum.TOOL"
:source="SourceTypeEnum.TOOL" :data="toolTreeData"
:data="toolTreeData" :currentNodeKey="folder.currentFolder?.id"
:currentNodeKey="folder.currentFolder?.id" @handleNodeClick="folderClickHandle"
@handleNodeClick="folderClickHandle" :shareTitle="$t('views.shared.shared_tool')"
:shareTitle="$t('views.shared.shared_tool')" :showShared="permissionPrecise['is_share']()"
:showShared="permissionPrecise['is_share']()" :canOperation="false"
:canOperation="false" :treeStyle="{ height: '400px' }"
:treeStyle="{ height: '400px' }" />
/>
</div>
</template> </template>
<el-scrollbar height="450"> <el-scrollbar height="450">
<NodeContent <NodeContent
@ -101,16 +99,14 @@
<el-tab-pane :label="$t('views.application.title')" name="application"> <el-tab-pane :label="$t('views.application.title')" name="application">
<LayoutContainer> <LayoutContainer>
<template #left> <template #left>
<div class="p-8"> <folder-tree
<folder-tree :source="SourceTypeEnum.APPLICATION"
:source="SourceTypeEnum.APPLICATION" :data="applicationTreeData"
:data="applicationTreeData" :currentNodeKey="folder.currentFolder?.id"
:currentNodeKey="folder.currentFolder?.id" @handleNodeClick="folderClickHandle"
@handleNodeClick="folderClickHandle" :canOperation="false"
:canOperation="false" :treeStyle="{ height: '400px' }"
:treeStyle="{ height: '400px' }" />
/>
</div>
</template> </template>
<el-scrollbar height="450"> <el-scrollbar height="450">
<NodeContent <NodeContent

View File

@ -27,18 +27,16 @@
</template> </template>
<LayoutContainer class="application-manage"> <LayoutContainer class="application-manage">
<template #left> <template #left>
<div class="p-8"> <folder-tree
<folder-tree :data="folderList"
:data="folderList" :currentNodeKey="currentFolder?.id"
:currentNodeKey="currentFolder?.id" @handleNodeClick="folderClickHandle"
@handleNodeClick="folderClickHandle" v-loading="folderLoading"
v-loading="folderLoading" :canOperation="false"
:canOperation="false" showShared
showShared :shareTitle="$t('views.shared.shared_knowledge')"
:shareTitle="$t('views.shared.shared_knowledge')" :treeStyle="{ height: 'calc(100vh - 240px)' }"
:treeStyle="{ height: 'calc(100vh - 240px)' }" />
/>
</div>
</template> </template>
<div class="layout-bg"> <div class="layout-bg">
<div class="flex-between p-16 ml-8"> <div class="flex-between p-16 ml-8">

View File

@ -23,18 +23,16 @@
</template> </template>
<LayoutContainer class="application-manage"> <LayoutContainer class="application-manage">
<template #left> <template #left>
<div class="p-8"> <folder-tree
<folder-tree :data="folderList"
:data="folderList" :currentNodeKey="currentFolder?.id"
:currentNodeKey="currentFolder?.id" @handleNodeClick="folderClickHandle"
@handleNodeClick="folderClickHandle" v-loading="folderLoading"
v-loading="folderLoading" :canOperation="false"
:canOperation="false" showShared
showShared :shareTitle="$t('views.shared.shared_tool')"
:shareTitle="$t('views.shared.shared_tool')" :treeStyle="{ height: 'calc(100vh - 240px)' }"
:treeStyle="{ height: 'calc(100vh - 240px)' }" />
/>
</div>
</template> </template>
<div class="layout-bg"> <div class="layout-bg">
<div class="flex-between p-16 ml-8"> <div class="flex-between p-16 ml-8">
@ -198,15 +196,17 @@ function getList() {
type: 'tool', type: 'tool',
isShared: folder_id === 'share', isShared: folder_id === 'share',
systemType: 'workspace', systemType: 'workspace',
}).getToolList({
folder_id: folder_id,
tool_type: 'CUSTOM'
}).then((res: any) => {
toolList.value = res.data?.tools || res.data || []
toolList.value = toolList.value?.filter((item: any) => item.is_active)
searchData.value = res.data.tools || res.data
searchData.value = searchData.value?.filter((item: any) => item.is_active)
}) })
.getToolList({
folder_id: folder_id,
tool_type: 'CUSTOM',
})
.then((res: any) => {
toolList.value = res.data?.tools || res.data || []
toolList.value = toolList.value?.filter((item: any) => item.is_active)
searchData.value = res.data.tools || res.data
searchData.value = searchData.value?.filter((item: any) => item.is_active)
})
} }
defineExpose({ open }) defineExpose({ open })

View File

@ -2,15 +2,15 @@
<LayoutContainer showCollapse class="application-manage"> <LayoutContainer showCollapse class="application-manage">
<template #left> <template #left>
<h4 class="p-12-16 pb-0 mt-12">{{ $t('views.application.title') }}</h4> <h4 class="p-12-16 pb-0 mt-12">{{ $t('views.application.title') }}</h4>
<div class="p-8">
<folder-tree <folder-tree
:source="SourceTypeEnum.APPLICATION" :source="SourceTypeEnum.APPLICATION"
:data="folderList" :data="folderList"
:currentNodeKey="folder.currentFolder?.id" :currentNodeKey="folder.currentFolder?.id"
@handleNodeClick="folderClickHandle" @handleNodeClick="folderClickHandle"
@refreshTree="refreshFolder" @refreshTree="refreshFolder"
/> :draggable="true"
</div> />
</template> </template>
<ContentContainer> <ContentContainer>
<template #header> <template #header>

View File

@ -2,17 +2,17 @@
<LayoutContainer showCollapse class="knowledge-manage"> <LayoutContainer showCollapse class="knowledge-manage">
<template #left> <template #left>
<h4 class="p-12-16 pb-0 mt-12">{{ $t('views.knowledge.title') }}</h4> <h4 class="p-12-16 pb-0 mt-12">{{ $t('views.knowledge.title') }}</h4>
<div class="p-8">
<folder-tree <folder-tree
:source="SourceTypeEnum.KNOWLEDGE" :source="SourceTypeEnum.KNOWLEDGE"
:data="folderList" :data="folderList"
:currentNodeKey="folder.currentFolder?.id" :currentNodeKey="folder.currentFolder?.id"
@handleNodeClick="folderClickHandle" @handleNodeClick="folderClickHandle"
:shareTitle="$t('views.shared.shared_knowledge')" :shareTitle="$t('views.shared.shared_knowledge')"
:showShared="permissionPrecise['is_share']()" :showShared="permissionPrecise['is_share']()"
@refreshTree="refreshFolder" @refreshTree="refreshFolder"
/> :draggable="true"
</div> />
</template> </template>
<KnowledgeListContainer @refreshFolder="refreshFolder"> <KnowledgeListContainer @refreshFolder="refreshFolder">
<template #header> <template #header>

View File

@ -2,17 +2,17 @@
<LayoutContainer showCollapse class="tool-manage"> <LayoutContainer showCollapse class="tool-manage">
<template #left> <template #left>
<h4 class="p-12-16 pb-0 mt-12">{{ $t('views.tool.title') }}</h4> <h4 class="p-12-16 pb-0 mt-12">{{ $t('views.tool.title') }}</h4>
<div class="p-8">
<folder-tree <folder-tree
:source="SourceTypeEnum.TOOL" :source="SourceTypeEnum.TOOL"
:data="folderList" :data="folderList"
:currentNodeKey="folder.currentFolder?.id" :currentNodeKey="folder.currentFolder?.id"
@handleNodeClick="folderClickHandle" @handleNodeClick="folderClickHandle"
@refreshTree="refreshFolder" @refreshTree="refreshFolder"
:shareTitle="$t('views.shared.shared_tool')" :shareTitle="$t('views.shared.shared_tool')"
:showShared="permissionPrecise['is_share']()" :showShared="permissionPrecise['is_share']()"
/> :draggable="true"
</div> />
</template> </template>
<ToolListContainer @refreshFolder="refreshFolder"> <ToolListContainer @refreshFolder="refreshFolder">
<template #header> <template #header>
@ -40,7 +40,7 @@ import { SourceTypeEnum } from '@/enums/common'
import permissionMap from '@/permission' import permissionMap from '@/permission'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import useStore from '@/stores' import useStore from '@/stores'
import bus from "@/bus" import bus from '@/bus'
const route = useRoute() const route = useRoute()
const { folder, tool } = useStore() const { folder, tool } = useStore()