mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:02:46 +00:00
refactor: toolTree and applicationTree
This commit is contained in:
parent
0b049ada9c
commit
0b06e20527
|
|
@ -18,7 +18,7 @@ Object.defineProperty(prefix, 'value', {
|
|||
* 工具列表带分页(无分页)
|
||||
* @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,
|
||||
loading,
|
||||
) => {
|
||||
|
|
|
|||
|
|
@ -19,17 +19,13 @@
|
|||
<template v-for="(node, index) in filter_menu_nodes" :key="index">
|
||||
<el-text type="info" size="small" class="color-secondary ml-12">{{
|
||||
node.label
|
||||
}}</el-text>
|
||||
}}</el-text>
|
||||
<div class="flex-wrap mt-8">
|
||||
<template v-for="(item, index) in node.list" :key="index">
|
||||
<el-popover placement="right" :width="280">
|
||||
<template #reference>
|
||||
<div
|
||||
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)"
|
||||
>
|
||||
<div class="list-item 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)">
|
||||
<component
|
||||
:is="iconComponent(`${item.type}-icon`)"
|
||||
class="mr-8"
|
||||
|
|
@ -49,7 +45,7 @@
|
|||
</div>
|
||||
<el-text type="info" size="small" class="color-secondary lighter">{{
|
||||
item.text
|
||||
}}</el-text>
|
||||
}}</el-text>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
|
@ -63,92 +59,40 @@
|
|||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('views.tool.title')" name="tool">
|
||||
<el-scrollbar height="400">
|
||||
<div
|
||||
class="workflow-dropdown-item cursor flex p-8-12"
|
||||
@click.stop="clickNodes(toolNode)"
|
||||
@mousedown.stop="onmousedown(toolNode)"
|
||||
>
|
||||
<component :is="iconComponent(`tool-lib-node-icon`)" class="mr-8 mt-4" :size="32" />
|
||||
<div class="pre-wrap">
|
||||
<div class="lighter">{{ toolNode.label }}</div>
|
||||
<el-text type="info" size="small">{{ toolNode.text }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 共享工具 -->
|
||||
<el-collapse expand-icon-position="left">
|
||||
<el-collapse-item name="shared" :icon="CaretRight">
|
||||
<template #title>
|
||||
<div class="flex align-center">
|
||||
<AppIcon iconName="app-shared-active" style="font-size: 20px" class="color-primary"></AppIcon>
|
||||
<span class="ml-8 lighter">{{ $t('views.shared.shared_tool') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<NodeContent :list="sharedToolList" @clickNodes="(val: any) => clickNodes(toolLibNode, val, 'tool')"
|
||||
@onmousedown="(val: any) => onmousedown(toolLibNode, val, 'tool')" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<template v-for="(item, index) in filter_tool_lib_list" :key="index">
|
||||
<div
|
||||
class="workflow-dropdown-item cursor flex p-8-12 align-center"
|
||||
@click.stop="clickNodes(toolLibNode, item, 'tool')"
|
||||
@mousedown.stop="onmousedown(toolLibNode, item, 'tool')"
|
||||
>
|
||||
<component
|
||||
:is="iconComponent(`tool-lib-node-icon`)"
|
||||
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-tree :data="toolTreeData" node-key="id"
|
||||
:props="{ children: 'children', isLeaf: 'isLeaf', class: getNodeClass }" lazy :load="loadNode">
|
||||
<template #default="{ data, node }">
|
||||
<NodeContent v-if="!data._fake" :data="data" :node="node"
|
||||
@clickNodes="(val: any) => clickNodes(toolLibNode, val, 'tool')"
|
||||
@onmousedown="(val: any) => onmousedown(toolLibNode, val, 'tool')" />
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('views.application.title')" name="application">
|
||||
<el-scrollbar height="400">
|
||||
<div v-if="filter_application_list.length > 0">
|
||||
<template v-for="(item, index) in filter_application_list" :key="index">
|
||||
<div
|
||||
class="workflow-dropdown-item cursor flex align-center p-8-12"
|
||||
@click.stop="clickNodes(applicationNode, item, 'application')"
|
||||
@mousedown.stop="onmousedown(applicationNode, item, '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>
|
||||
<el-tree :data="applicationTreeData" node-key="id"
|
||||
:props="{ children: 'children', isLeaf: 'isLeaf', class: getNodeClass }" lazy :load="loadNode">
|
||||
<template #default="{ data, node }">
|
||||
<NodeContent v-if="!data._fake" :data="data" :node="node"
|
||||
@clickNodes="(val: any) => clickNodes(applicationNode, val, 'application')"
|
||||
@onmousedown="(val: any) => onmousedown(applicationNode, val, 'application')" />
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="ml-16 mt-8">
|
||||
<el-text type="info">{{ $t('views.applicationWorkflow.tip.noData') }}</el-text>
|
||||
</div>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
|
@ -156,11 +100,17 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
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 applicationApi from '@/api/application/application'
|
||||
import ToolApi from '@/api/tool/tool'
|
||||
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 props = defineProps({
|
||||
show: {
|
||||
|
|
@ -174,24 +124,12 @@ const props = defineProps({
|
|||
workflowRef: Object,
|
||||
})
|
||||
|
||||
const { folder } = useStore()
|
||||
const emit = defineEmits(['clickNodes', 'onmousedown'])
|
||||
|
||||
const loading = ref(false)
|
||||
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(() => {
|
||||
if (!search_text.value) return menuNodes
|
||||
const searchTerm = search_text.value.toLowerCase()
|
||||
|
|
@ -234,10 +172,10 @@ function clickNodes(item: any, data?: any, type?: string) {
|
|||
...(!fileUploadSetting
|
||||
? {}
|
||||
: {
|
||||
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||
...(fileUploadSetting.audio ? { audio_list: [] } : {}),
|
||||
}),
|
||||
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||
...(fileUploadSetting.audio ? { audio_list: [] } : {}),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
item['properties']['node_data'] = {
|
||||
|
|
@ -279,10 +217,10 @@ function onmousedown(item: any, data?: any, type?: string) {
|
|||
...(!fileUploadSetting
|
||||
? {}
|
||||
: {
|
||||
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||
...(fileUploadSetting.audio ? { audio_list: [] } : {}),
|
||||
}),
|
||||
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||
...(fileUploadSetting.audio ? { audio_list: [] } : {}),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
item['properties']['node_data'] = {
|
||||
|
|
@ -297,17 +235,72 @@ function onmousedown(item: any, data?: any, type?: string) {
|
|||
emit('onmousedown', item)
|
||||
}
|
||||
|
||||
function getList() {
|
||||
// applicationApi.listTool(props.id, loading).then((res: any) => {
|
||||
// toolList.value = res.data
|
||||
// })
|
||||
// applicationApi.getApplicationList(props.id, loading).then((res: any) => {
|
||||
// applicationList.value = res.data
|
||||
// })
|
||||
function getNodeClass(data: any) {
|
||||
return data._fake ? 'tree-node--hidden' : ''
|
||||
}
|
||||
|
||||
const loadNode = async (node: any, resolve: (children: any[]) => void) => {
|
||||
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(() => {
|
||||
// getList()
|
||||
getShareTool()
|
||||
getToolFolder()
|
||||
getApplicationFolder()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -335,5 +328,46 @@ onMounted(() => {
|
|||
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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue