refactor: toolTree and applicationTree

This commit is contained in:
teukkk 2025-07-03 19:34:36 +08:00
parent 0b049ada9c
commit 0b06e20527
3 changed files with 245 additions and 121 deletions

View File

@ -18,7 +18,7 @@ Object.defineProperty(prefix, 'value', {
* *
* @params {folder_id: string} * @params {folder_id: string}
*/ */
const getToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = ( const getToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<{tools: any[], folders: any[]}>> = (
data, data,
loading, loading,
) => { ) => {

View File

@ -19,17 +19,13 @@
<template v-for="(node, index) in filter_menu_nodes" :key="index"> <template v-for="(node, index) in filter_menu_nodes" :key="index">
<el-text type="info" size="small" class="color-secondary ml-12">{{ <el-text type="info" size="small" class="color-secondary ml-12">{{
node.label node.label
}}</el-text> }}</el-text>
<div class="flex-wrap mt-8"> <div class="flex-wrap mt-8">
<template v-for="(item, index) in node.list" :key="index"> <template v-for="(item, index) in node.list" :key="index">
<el-popover placement="right" :width="280"> <el-popover placement="right" :width="280">
<template #reference> <template #reference>
<div <div class="list-item flex align-center border border-r-6 mb-12 p-8-12 cursor ml-12"
class="flex align-center border border-r-6 mb-12 p-8-12 cursor ml-12" style="width: 39%" @click.stop="clickNodes(item)" @mousedown.stop="onmousedown(item)">
style="width: 39%"
@click.stop="clickNodes(item)"
@mousedown.stop="onmousedown(item)"
>
<component <component
:is="iconComponent(`${item.type}-icon`)" :is="iconComponent(`${item.type}-icon`)"
class="mr-8" class="mr-8"
@ -49,7 +45,7 @@
</div> </div>
<el-text type="info" size="small" class="color-secondary lighter">{{ <el-text type="info" size="small" class="color-secondary lighter">{{
item.text item.text
}}</el-text> }}</el-text>
</template> </template>
</el-popover> </el-popover>
</template> </template>
@ -63,92 +59,40 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('views.tool.title')" name="tool"> <el-tab-pane :label="$t('views.tool.title')" name="tool">
<el-scrollbar height="400"> <el-scrollbar height="400">
<div <!-- 共享工具 -->
class="workflow-dropdown-item cursor flex p-8-12" <el-collapse expand-icon-position="left">
@click.stop="clickNodes(toolNode)" <el-collapse-item name="shared" :icon="CaretRight">
@mousedown.stop="onmousedown(toolNode)" <template #title>
> <div class="flex align-center">
<component :is="iconComponent(`tool-lib-node-icon`)" class="mr-8 mt-4" :size="32" /> <AppIcon iconName="app-shared-active" style="font-size: 20px" class="color-primary"></AppIcon>
<div class="pre-wrap"> <span class="ml-8 lighter">{{ $t('views.shared.shared_tool') }}</span>
<div class="lighter">{{ toolNode.label }}</div> </div>
<el-text type="info" size="small">{{ toolNode.text }}</el-text> </template>
</div> <NodeContent :list="sharedToolList" @clickNodes="(val: any) => clickNodes(toolLibNode, val, 'tool')"
</div> @onmousedown="(val: any) => onmousedown(toolLibNode, val, 'tool')" />
</el-collapse-item>
</el-collapse>
<template v-for="(item, index) in filter_tool_lib_list" :key="index"> <el-tree :data="toolTreeData" node-key="id"
<div :props="{ children: 'children', isLeaf: 'isLeaf', class: getNodeClass }" lazy :load="loadNode">
class="workflow-dropdown-item cursor flex p-8-12 align-center" <template #default="{ data, node }">
@click.stop="clickNodes(toolLibNode, item, 'tool')" <NodeContent v-if="!data._fake" :data="data" :node="node"
@mousedown.stop="onmousedown(toolLibNode, item, 'tool')" @clickNodes="(val: any) => clickNodes(toolLibNode, val, 'tool')"
> @onmousedown="(val: any) => onmousedown(toolLibNode, val, 'tool')" />
<component </template>
:is="iconComponent(`tool-lib-node-icon`)" </el-tree>
class="mr-8"
:size="32"
:item="item"
/>
<div class="pre-wrap">
<div class="lighter ellipsis-1" :title="item.name">{{ item.name }}</div>
<p>
<el-text
class="ellipsis-1"
type="info"
size="small"
:title="item.desc"
v-if="item.desc"
>{{ item.desc }}</el-text
>
</p>
</div>
</div>
</template>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('views.application.title')" name="application"> <el-tab-pane :label="$t('views.application.title')" name="application">
<el-scrollbar height="400"> <el-scrollbar height="400">
<div v-if="filter_application_list.length > 0"> <el-tree :data="applicationTreeData" node-key="id"
<template v-for="(item, index) in filter_application_list" :key="index"> :props="{ children: 'children', isLeaf: 'isLeaf', class: getNodeClass }" lazy :load="loadNode">
<div <template #default="{ data, node }">
class="workflow-dropdown-item cursor flex align-center p-8-12" <NodeContent v-if="!data._fake" :data="data" :node="node"
@click.stop="clickNodes(applicationNode, item, 'application')" @clickNodes="(val: any) => clickNodes(applicationNode, val, 'application')"
@mousedown.stop="onmousedown(applicationNode, item, 'application')" @onmousedown="(val: any) => onmousedown(applicationNode, val, 'application')" />
>
<component
:is="iconComponent(`application-node-icon`)"
class="mr-8"
:size="32"
:item="item"
/>
<div class="pre-wrap" style="width: 60%">
<div class="lighter ellipsis" :title="item.name">
{{ item.name }}
</div>
<p>
<el-text
class="ellipsis"
type="info"
size="small"
:title="item.desc"
v-if="item.desc"
>{{ item.desc }}</el-text
>
</p>
</div>
<div class="status-tag" style="margin-left: auto">
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px">
{{ $t('views.application.workflow') }}</el-tag
>
<el-tag class="blue-tag" v-else style="height: 22px">{{
$t('views.application.simple')
}}</el-tag>
</div>
</div>
</template> </template>
</div> </el-tree>
<div v-else class="ml-16 mt-8">
<el-text type="info">{{ $t('views.applicationWorkflow.tip.noData') }}</el-text>
</div>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -156,11 +100,17 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { menuNodes, toolNode, toolLibNode, applicationNode } from '@/workflow/common/data' import { menuNodes, toolLibNode, applicationNode } from '@/workflow/common/data'
import { iconComponent } from '@/workflow/icons/utils' import { iconComponent } from '@/workflow/icons/utils'
import applicationApi from '@/api/application/application' import ToolApi from '@/api/tool/tool'
import { isWorkFlow } from '@/utils/application' import { isWorkFlow } from '@/utils/application'
import { isAppIcon } from '@/utils/common' import useStore from '@/stores'
import NodeContent from './NodeContent.vue'
import { SourceTypeEnum } from '@/enums/common'
import sharedWorkspaceApi from '@/api/shared-workspace'
import { CaretRight } from '@element-plus/icons-vue'
import ApplicationApi from '@/api/application/application'
const search_text = ref<string>('') const search_text = ref<string>('')
const props = defineProps({ const props = defineProps({
show: { show: {
@ -174,24 +124,12 @@ const props = defineProps({
workflowRef: Object, workflowRef: Object,
}) })
const { folder } = useStore()
const emit = defineEmits(['clickNodes', 'onmousedown']) const emit = defineEmits(['clickNodes', 'onmousedown'])
const loading = ref(false) const loading = ref(false)
const activeName = ref('base') const activeName = ref('base')
const toolList = ref<any[]>([])
const filter_tool_lib_list = computed(() => {
return toolList.value.filter((item: any) =>
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase()),
)
})
const applicationList = ref<any[]>([])
const filter_application_list = computed(() => {
return applicationList.value.filter((item: any) =>
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase()),
)
})
const filter_menu_nodes = computed(() => { const filter_menu_nodes = computed(() => {
if (!search_text.value) return menuNodes if (!search_text.value) return menuNodes
const searchTerm = search_text.value.toLowerCase() const searchTerm = search_text.value.toLowerCase()
@ -234,10 +172,10 @@ function clickNodes(item: any, data?: any, type?: string) {
...(!fileUploadSetting ...(!fileUploadSetting
? {} ? {}
: { : {
...(fileUploadSetting.document ? { document_list: [] } : {}), ...(fileUploadSetting.document ? { document_list: [] } : {}),
...(fileUploadSetting.image ? { image_list: [] } : {}), ...(fileUploadSetting.image ? { image_list: [] } : {}),
...(fileUploadSetting.audio ? { audio_list: [] } : {}), ...(fileUploadSetting.audio ? { audio_list: [] } : {}),
}), }),
} }
} else { } else {
item['properties']['node_data'] = { item['properties']['node_data'] = {
@ -279,10 +217,10 @@ function onmousedown(item: any, data?: any, type?: string) {
...(!fileUploadSetting ...(!fileUploadSetting
? {} ? {}
: { : {
...(fileUploadSetting.document ? { document_list: [] } : {}), ...(fileUploadSetting.document ? { document_list: [] } : {}),
...(fileUploadSetting.image ? { image_list: [] } : {}), ...(fileUploadSetting.image ? { image_list: [] } : {}),
...(fileUploadSetting.audio ? { audio_list: [] } : {}), ...(fileUploadSetting.audio ? { audio_list: [] } : {}),
}), }),
} }
} else { } else {
item['properties']['node_data'] = { item['properties']['node_data'] = {
@ -297,17 +235,72 @@ function onmousedown(item: any, data?: any, type?: string) {
emit('onmousedown', item) emit('onmousedown', item)
} }
function getList() { function getNodeClass(data: any) {
// applicationApi.listTool(props.id, loading).then((res: any) => { return data._fake ? 'tree-node--hidden' : ''
// toolList.value = res.data }
// })
// applicationApi.getApplicationList(props.id, loading).then((res: any) => { const loadNode = async (node: any, resolve: (children: any[]) => void) => {
// applicationList.value = res.data if (node.level === 0) return resolve([])
// }) try {
let folders
if (activeName.value === 'tool') {
const res = await ToolApi.getToolList({ folder_id: node.data.id })
node.data.cardList = res.data.tools
folders = res.data?.folders
} else {
const res = await ApplicationApi.getAllApplication({ folder_id: node.data.id })
node.data.cardList = res.data.filter(item => item.resource_type === "application")
folders = res.data.filter(item => item.resource_type === "folder")
}
const children = folders.map(f => ({
...f,
children: [],
isLeaf: false,
}))
if (folders.length === 0 && node.data.cardList.length > 0) {
//
children.push({
id: `__placeholder__${node.data.id}`,
isLeaf: true,
_fake: true,
})
}
resolve(children)
} catch (e: any) {
resolve([]) // resolve
}
}
const toolTreeData = ref<any[]>([])
function getToolFolder() {
folder.asyncGetFolder(SourceTypeEnum.TOOL, {}, loading).then((res: any) => {
toolTreeData.value = res.data
})
}
const sharedToolList = ref<any[]>([])
async function getShareTool() {
try {
const res = await sharedWorkspaceApi.getToolList(loading)
sharedToolList.value = res.data
} catch (error: any) {
console.error(error)
}
}
const applicationTreeData = ref<any[]>([])
function getApplicationFolder() {
folder.asyncGetFolder(SourceTypeEnum.APPLICATION, {}, loading).then((res: any) => {
applicationTreeData.value = res.data
})
} }
onMounted(() => { onMounted(() => {
// getList() getShareTool()
getToolFolder()
getApplicationFolder()
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -335,5 +328,46 @@ onMounted(() => {
background: var(--app-text-color-light-1); background: var(--app-text-color-light-1);
} }
} }
.list-item {
&:hover {
border-color: var(--el-color-primary);
}
}
:deep(.el-collapse) {
border-top-width: 0;
.el-collapse-item__header {
height: 40px;
gap: 0;
.el-collapse-item__arrow {
font-size: 16px;
color: var(--app-text-color-secondary);
padding: 6px;
}
}
.el-collapse-item__content {
padding: 0 12px 16px 12px;
.list {
margin-top: 0;
transform: none;
}
}
}
:deep(.el-tree-node):focus>.el-tree-node__content {
background: transparent;
}
:deep(.el-tree-node__content) {
height: auto;
align-items: baseline;
&:hover {
background: transparent;
}
}
:deep(.tree-node--hidden) {
display: none !important;
}
} }
</style> </style>

View File

@ -0,0 +1,90 @@
<template>
<div class="w-full">
<div v-if="data" class="flex align-center">
<AppIcon iconName="app-folder" style="font-size: 20px"></AppIcon>
<span class="ml-8 ellipsis color-text-primary lighter" style="max-width: 110px" :title="data.name">
{{ data.name }}
</span>
</div>
<transition name="el-fade-in-linear">
<div v-if="props.list?.length || (props.node?.expanded && toolList.length)"
class="list border-r-4 layout-bg flex-wrap" @click.stop>
<el-popover v-for="item in toolList" :key="item.id" placement="right" :width="280">
<template #reference>
<div class="list-item flex align-center border border-r-6 p-8-12 cursor" style="width: 39%"
@click.stop="emit('clickNodes', item)" @mousedown.stop="emit('onmousedown', item)">
<el-avatar v-if="isAppIcon(item?.icon)" shape="square" :size="32" style="background: none">
<img :src="item?.icon" alt="" />
</el-avatar>
<el-avatar v-else class="avatar-green" shape="square" :size="32">
<img src="@/assets/node/icon_tool.svg" style="width: 58%" alt="" />
</el-avatar>
<span class="ml-8 ellipsis">{{ item.name }}</span>
</div>
</template>
<template #default>
<div class="flex-between mb-8">
<div class="flex align-center">
<el-avatar v-if="isAppIcon(item?.icon)" shape="square" :size="32" style="background: none">
<img :src="item?.icon" alt="" />
</el-avatar>
<el-avatar v-else class="avatar-green" shape="square" :size="32">
<img src="@/assets/node/icon_tool.svg" style="width: 58%" alt="" />
</el-avatar>
<span class="font-medium ml-8">{{ item.name }}</span>
</div>
<div v-if="item.type" class="status-tag" style="margin-left: auto">
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px">
{{ $t('views.application.workflow') }}
</el-tag>
<el-tag class="blue-tag" v-else style="height: 22px">
{{ $t('views.application.simple') }}
</el-tag>
</div>
</div>
<el-text type="info" size="small">{{ item.desc }}</el-text>
</template>
</el-popover>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { isAppIcon } from '@/utils/common'
import { isWorkFlow } from '@/utils/application'
const props = defineProps<{
data?: any
node?: any
list?: any[]
}>()
const emit = defineEmits<{
(e: 'clickNodes', item: any): void;
(e: 'onmousedown', item: any): void;
}>();
const toolList = computed(() => props.list ?? props.data?.cardList ?? [])
</script>
<style lang="scss" scoped>
.list {
cursor: default;
padding: 12px;
gap: 12px;
margin-top: 12px;
transform: translate(-16px, 0);
.list-item {
background-color: #ffffff;
&:hover {
border-color: var(--el-color-primary);
}
}
}
</style>