2025-10-30 19:30:06 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="comp-content">
|
|
|
|
|
<div class="content">
|
2025-11-06 10:51:33 +08:00
|
|
|
<div v-if="!hideTree" class="left-content" :class="{ hide: !isToggleShow }">
|
2025-10-30 19:30:06 +08:00
|
|
|
<div class="options">
|
|
|
|
|
<slot name="options" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="tree-data">
|
|
|
|
|
<el-tree
|
|
|
|
|
v-if="visible"
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
node-key="id"
|
|
|
|
|
highlight-current
|
|
|
|
|
:load="loadMoreFun"
|
|
|
|
|
lazy
|
|
|
|
|
:props="{
|
2025-11-12 15:16:46 +08:00
|
|
|
label: 'originalName',
|
2025-10-30 19:30:06 +08:00
|
|
|
children: 'children',
|
|
|
|
|
isLeaf: 'leaf',
|
|
|
|
|
}"
|
2025-11-11 20:29:34 +08:00
|
|
|
v-bind="$attrs"
|
2025-10-30 19:30:06 +08:00
|
|
|
@node-click="nodeClickFun"
|
|
|
|
|
@current-change="currentChangeFun"
|
|
|
|
|
>
|
|
|
|
|
<template #default="{ data, node }">
|
|
|
|
|
<div class="tree-item">
|
|
|
|
|
<div class="name">
|
|
|
|
|
<div class="icon">
|
2025-11-26 18:11:50 +08:00
|
|
|
<el-icon :size="18">
|
|
|
|
|
<MessageBox v-if="data.relatedResourceUuidOwnType === NODE_TYPE.PROJECT" />
|
|
|
|
|
<Share v-else-if="data.relatedResourceUuidOwnType === NODE_TYPE.PHASE" />
|
|
|
|
|
<Document v-else-if="data.relatedResourceUuidOwnType === NODE_TYPE.TASK"/>
|
|
|
|
|
<Folder v-else />
|
2025-11-07 18:42:09 +08:00
|
|
|
</el-icon>
|
2025-10-30 19:30:06 +08:00
|
|
|
</div>
|
2025-11-12 15:16:46 +08:00
|
|
|
<div class="label">{{ data.originalName }}</div>
|
2025-10-30 19:30:06 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="actions">
|
|
|
|
|
<slot name="treeAction" :data="data" :node="node" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-tree>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-11-06 10:51:33 +08:00
|
|
|
<div class="toggle-btn" :class="{ hide: !isToggleShow }" @click="toggleFun">
|
|
|
|
|
<el-icon :size="12">
|
|
|
|
|
<DArrowLeft />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</div>
|
2025-10-30 19:30:06 +08:00
|
|
|
<div class="right-content">
|
2025-11-12 15:16:46 +08:00
|
|
|
<div class="top-content">
|
|
|
|
|
<div class="menu">
|
|
|
|
|
<el-breadcrumb separator="/">
|
|
|
|
|
<el-breadcrumb-item v-for="(item) in navList" :key="item.id" @click="openDirFun(item.id)">
|
|
|
|
|
{{ item.originalName }}
|
|
|
|
|
</el-breadcrumb-item>
|
|
|
|
|
</el-breadcrumb>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="search" @click="searchFun">
|
2025-11-12 16:00:01 +08:00
|
|
|
<el-input v-model="searchData" :suffix-icon="Search" placeholder="搜索" size="small" clearable />
|
2025-11-12 15:16:46 +08:00
|
|
|
</div>
|
2025-10-30 19:30:06 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="table">
|
|
|
|
|
<slot name="table" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { ref, watch } from 'vue';
|
2025-11-26 18:11:50 +08:00
|
|
|
import { MessageBox, Share, Folder, DArrowLeft, Search, Document } from '@element-plus/icons-vue';
|
2025-11-07 18:42:09 +08:00
|
|
|
import { NODE_TYPE } from '@/utils/enum/node';
|
2025-10-30 19:30:06 +08:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
api: any;
|
|
|
|
|
hideTree?: boolean;
|
2025-11-07 09:16:43 +08:00
|
|
|
templateId: number;
|
2025-10-30 19:30:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
api: null,
|
|
|
|
|
hideTree: false,
|
2025-11-07 09:16:43 +08:00
|
|
|
templateId: 0,
|
2025-10-30 19:30:06 +08:00
|
|
|
});
|
|
|
|
|
|
2025-11-12 15:16:46 +08:00
|
|
|
const emit = defineEmits(['choseNode', 'updateNav', 'search']);
|
2025-10-30 19:30:06 +08:00
|
|
|
|
|
|
|
|
const treeRef = ref<any>();
|
|
|
|
|
const navList = ref<any>([]);
|
|
|
|
|
const visible = ref(true);
|
2025-11-06 10:51:33 +08:00
|
|
|
const isToggleShow = ref(true);
|
2025-11-12 16:00:01 +08:00
|
|
|
const searchData = ref<any>('');
|
2025-10-30 19:30:06 +08:00
|
|
|
|
2025-11-07 09:16:43 +08:00
|
|
|
watch(() => props.templateId, () => {
|
|
|
|
|
reloadFun();
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-30 19:30:06 +08:00
|
|
|
// 加载节点
|
|
|
|
|
const loadMoreFun = async(node: any, resolve: any) => {
|
2025-11-07 09:16:43 +08:00
|
|
|
const listData: any = await getDataFun(node.data);
|
2025-10-30 19:30:06 +08:00
|
|
|
if (node.level >= 1) {
|
2025-11-12 15:16:46 +08:00
|
|
|
listData.forEach((item: any) => {
|
|
|
|
|
item.relatedParentUuid = node.data.relatedResourceUuid;
|
|
|
|
|
});
|
2025-10-30 19:30:06 +08:00
|
|
|
node.data.children = listData;
|
|
|
|
|
choseNodeFun(node.data);
|
|
|
|
|
}
|
|
|
|
|
return resolve(listData);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取节点数据
|
2025-11-07 09:16:43 +08:00
|
|
|
const getDataFun = async(data: any) => {
|
2025-10-30 19:30:06 +08:00
|
|
|
if (props.api) {
|
2025-11-07 09:16:43 +08:00
|
|
|
const params = {
|
|
|
|
|
dimensionTemplateId: props.templateId,
|
2025-11-12 15:16:46 +08:00
|
|
|
fileId: data.id || '',
|
2025-11-07 09:16:43 +08:00
|
|
|
};
|
|
|
|
|
const res: any = await props.api(params);
|
2025-10-30 19:30:06 +08:00
|
|
|
if (res.code === 200) {
|
|
|
|
|
return res.data;
|
|
|
|
|
} else {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 树形节点点击
|
|
|
|
|
const nodeClickFun = (data: any, node: any) => {
|
|
|
|
|
updateNavFun([]);
|
|
|
|
|
getTreeMenuFun(node);
|
|
|
|
|
choseNodeFun(data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 循环获取父节点
|
|
|
|
|
const getTreeMenuFun = (node: any) => {
|
|
|
|
|
if (node.label && node.parent) {
|
|
|
|
|
const data = navList.value;
|
|
|
|
|
data.unshift(node.data);
|
|
|
|
|
updateNavFun(data);
|
|
|
|
|
getTreeMenuFun(node.parent);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 打开目录
|
|
|
|
|
const openDirFun = (id: any) => {
|
|
|
|
|
const node = treeRef.value.getNode(String(id));
|
|
|
|
|
if (node) {
|
|
|
|
|
node.loaded = false;
|
|
|
|
|
node.expand();
|
|
|
|
|
treeRef.value.setCurrentKey(id);
|
|
|
|
|
choseNodeFun(node.data);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 返回上一级
|
|
|
|
|
const backFun = () => {
|
|
|
|
|
const data = navList.value[navList.value.length - 2];
|
|
|
|
|
openDirFun(data.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 当前节点更改
|
|
|
|
|
const currentChangeFun = (data: any, node: any) => {
|
|
|
|
|
updateNavFun([]);
|
|
|
|
|
getTreeMenuFun(node);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 选中节点
|
|
|
|
|
const choseNodeFun = (data: any) => {
|
|
|
|
|
emit('choseNode', data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 导航变更
|
|
|
|
|
const updateNavFun = (data: any) => {
|
|
|
|
|
navList.value = data;
|
|
|
|
|
emit('updateNav', data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const reloadFun = () => {
|
2025-11-12 18:12:04 +08:00
|
|
|
navList.value = [];
|
2025-10-30 19:30:06 +08:00
|
|
|
visible.value = false;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
visible.value = true;
|
|
|
|
|
}, 0);
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-06 10:51:33 +08:00
|
|
|
const toggleFun = () => {
|
|
|
|
|
isToggleShow.value = !isToggleShow.value;
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-12 15:16:46 +08:00
|
|
|
const searchFun = () => {
|
|
|
|
|
emit('search');
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-30 19:30:06 +08:00
|
|
|
defineExpose({
|
|
|
|
|
backFun,
|
|
|
|
|
openDirFun,
|
|
|
|
|
reloadFun,
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.comp-content {
|
|
|
|
|
height: 100%;
|
|
|
|
|
.content {
|
2025-11-06 10:51:33 +08:00
|
|
|
position: relative;
|
2025-10-30 19:30:06 +08:00
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
2025-11-06 10:51:33 +08:00
|
|
|
.toggle-btn {
|
|
|
|
|
position: absolute;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
top: 50%;
|
|
|
|
|
left: 400px;
|
|
|
|
|
margin-top: -20px;
|
|
|
|
|
background-color: var(--el-bg-color);
|
|
|
|
|
border-radius: 0 4px 4px 0;
|
|
|
|
|
width: 14px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
border: solid 1px var(--el-border-color-darker);
|
|
|
|
|
border-left: 0;
|
|
|
|
|
color: var(--el-text-color-regular);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
&.hide {
|
|
|
|
|
left: -14px;
|
|
|
|
|
transform: rotateZ(180deg);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-30 19:30:06 +08:00
|
|
|
.left-content {
|
|
|
|
|
width: 400px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
margin-right: var(--margin-normal);
|
|
|
|
|
background-color: var(--el-bg-color);
|
|
|
|
|
border-radius: var(--border-radius-normal);
|
|
|
|
|
padding: var(--padding-normal);
|
2025-11-06 10:51:33 +08:00
|
|
|
border-right: solid 1px var(--el-border-color-darker);
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
&.hide {
|
|
|
|
|
width: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
2025-10-30 19:30:06 +08:00
|
|
|
.tree-item {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
width: 0;
|
|
|
|
|
.name {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
.icon {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
margin-right: var(--margin-tiny);
|
|
|
|
|
}
|
|
|
|
|
.label {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.actions {
|
|
|
|
|
padding-left: var(--padding-normal);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.right-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
background-color: var(--el-bg-color);
|
|
|
|
|
border-radius: var(--border-radius-normal);
|
2025-11-12 15:16:46 +08:00
|
|
|
padding: 0 var(--padding-normal) var(--padding-normal);
|
|
|
|
|
.top-content {
|
2025-10-30 19:30:06 +08:00
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
2025-11-12 15:16:46 +08:00
|
|
|
padding: 10px 0;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
border-bottom: solid 1px var(--el-border-color-light);
|
|
|
|
|
.menu {
|
|
|
|
|
flex: 1;
|
2025-11-12 16:00:01 +08:00
|
|
|
height: 24px;
|
2025-11-12 15:16:46 +08:00
|
|
|
background-color: var(--el-bg-color-page);
|
|
|
|
|
margin-right: 20px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
}
|
|
|
|
|
.search {
|
2025-11-12 16:00:01 +08:00
|
|
|
height: 24px;
|
2025-11-12 15:16:46 +08:00
|
|
|
background-color: var(--el-bg-color-page);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
2025-10-30 19:30:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|