<template>
<div class="tree-catalog-page" :class="attr.class">
<BaseCard :class="{ 'is-hidden': collapse }" padding="0" :border="false" :title="title">
<el-tree
:key="getData.length"
ref="treeRef"
v-bind="props"
:data="getData"
:indent="24"
:node-key="getNodeKey"
:current-node-key="activeNode[getNodeKey] || ''"
:default-expanded-keys="defaultExpandedKeys"
:props="getProps"
title=""
@node-click="handleCurrentChange"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<BaseToolTip placement="top" :rows="1" :content="node.label">{{
node.label
}}</BaseToolTip>
</span>
</template>
</el-tree>
</BaseCard>
<div class="catalog-according" :class="{ 'is-active': collapse }">
<i @click="collapse = !collapse" :title="collapse ? '展开' : '收起'"></i>
</div>
<div><slot></slot></div>
</div>
</template>
<script setup>
import interfaceUrls from '@/api/interfaceUrl';
import request from '@/api';
import { toRefs, ref, computed, nextTick, useAttrs } from 'vue';
defineOptions({
name: 'TreeCategory',
inheritAttrs: false,
});
const props = defineProps({
...ElTree.props,
catalogId: {
type: String,
},
title: {
type: String,
},
});
const emits = defineEmits([...ElTree.emits]);
const attr = useAttrs();
const { data, nodeKey, props: prop, catalogId } = toRefs(props);
const treeRef = ref(null);
const collapse = ref(false);
const defaultData = ref([]);
// 默认展开项
const defaultExpandedKeys = ref([]);
// 当前被选中项
const activeNode = ref({});
const getNodeKey = computed(() => nodeKey.value || 'catalogId');
const getData = computed(() => (data && data.length ? data.value : defaultData.value));
const getActiveNodeData = val =>
activeNode.value?.[getNodeKey.value] &&
activeNode.value?.[getNodeKey.value] === val[getNodeKey.value];
// 自定义数节点样式类
const customNodeClass = (val, node) => {
let str = '';
if (!(val.children && val.children.length)) {
str = str + ' is-leaf';
}
if (getActiveNodeData(val)) {
str = str + ' is-active';
}
return str;
};
const getProps = computed(() => {
return {
...toRefs(prop.value),
label: 'catalogName',
children: 'children',
isLeaf: 'isLeaf',
class: customNodeClass,
};
});
// 重置高亮选择
const resetActiveNode = val => {
activeNode.value = {};
defaultExpandedKeys.value = defaultExpandedKeys.value.filter(
item => item !== val[getNodeKey.value]
);
emits('current-change', { level: 0, isLeaf: false });
};
// 当前选中节点变化时回调事件
const handleCurrentChange = (val, node) => {
if (getActiveNodeData(val)) {
resetActiveNode(val);
return;
}
activeNode.value = val;
if (node?.expanded) {
if (!defaultExpandedKeys.value.includes(val[getNodeKey.value]) && val?.children?.length) {
defaultExpandedKeys.value.push(val[getNodeKey.value]);
}
} else {
defaultExpandedKeys.value = defaultExpandedKeys.value.filter(
item => item !== val[getNodeKey.value]
);
}
emits('current-change', { ...val, level: node.level, isLeaf: node.isLeaf });
nextTick(() => {
treeRef.value.$el
.querySelector('.el-tree-node.is-current.is-active')
.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
});
return;
};
const initTree = async () => {
defaultData.value = [];
try {
let res = await request({
method: 'post',
url: interfaceUrls.assetMgmt.assetCatalogMgmt.queryAssetDomain,
data: catalogId.value ? { catalogId: catalogId.value } : {},
});
defaultData.value = res.datas;
} catch (error) {}
};
// 根据key更新高亮并自动触发选中回调
const updateCurrentKey = (key, reset = false) => {
const node = treeRef.value.getNode(key);
if (node) {
treeRef.value.setCurrentKey(key);
activeNode.value = node.data;
emits('current-change', { ...node.data, level: node.level, isLeaf: node.isLeaf });
} else {
if (reset) {
resetActiveNode(node.data);
}
}
};
// 更新整个树,同时如果当前选中节点还存在,则触发自动选中,否则,根据传入的key选中
const updateTree = async (activeNodeKey = '') => {
await initTree();
await nextTick();
const activeNodeId = activeNode.value[getNodeKey.value];
const node = treeRef.value.getNode(activeNodeId);
if (node) {
updateCurrentKey(activeNodeId);
} else {
if (activeNodeKey) {
updateCurrentKey(activeNodeKey, true);
}
}
};
initTree();
defineExpose({
treeRef,
updateTree,
});
</script>
<style lang="scss">
.tree-catalog-page {
display: flex;
& > .base-card:first-child {
width: 280px;
position: sticky;
top: 20px;
height: calc(100vh - 80px);
.base-card-header {
border: 1px solid #eeeeee;
}
}
& > div:last-child {
.base-form.is-search-form .el-form > .base-card.is-collapse.is-active > .base-card-content {
padding: 20px 0 !important;
.sc-button {
margin-left: 24px;
}
}
& > .base-card.is-custom-table-list {
& > .base-card-header {
position: relative;
p {
text-indent: 16px;
span {
color: var(--el-color-primary);
}
}
.is-custom-table-list-keyword {
width: 260px;
position: absolute;
right: 20px;
top: 7px;
}
}
& > .base-card-content .el-pagination {
justify-content: flex-end;
padding: 20px 0 0;
}
}
}
.catalog-according {
width: 41px;
position: sticky;
top: 20px;
height: calc(100vh - 80px);
&::before {
content: '';
position: absolute;
width: 1px;
top: -20px;
left: 20px;
bottom: -20px;
background: #eee;
}
i {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 78px;
border: 1px solid #eee;
border-left: none;
cursor: pointer;
}
&::after {
pointer-events: none;
content: '';
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-5px);
width: 0;
height: 0;
border: 5px solid transparent;
border-right-color: #999999;
}
&.is-active {
width: 21px;
&::before {
left: 0;
}
i {
left: 0;
}
&::after {
left: 5px;
border-right-color: transparent;
border-left-color: #999999;
}
}
}
& > div:last-child {
flex: 1;
}
}
.el-tree {
padding: 20px 0 0;
max-height: calc(100vh - 125px);
overflow-y: auto;
.el-tree-node__content:hover {
background-color: #e7eff9;
}
.el-tree-node:focus {
> .el-tree-node__content {
background-color: #fff;
}
}
.el-tree-node {
&.is-current.is-active > .el-tree-node__content,
&.is-leaf.is-active > .el-tree-node__content {
color: #2367f1;
background-color: #e7eff9;
}
}
.el-icon.el-tree-node__expand-icon {
width: 16px;
height: 16px;
margin: 0 8px;
color: transparent;
transform: rotate(180deg);
background: url(@/assets/images/icon-tree-accordion.png) center center no-repeat;
&.expanded {
transform: rotate(0);
}
&.is-leaf {
visibility: visible;
transform: rotate(0);
background: url(@/assets/images/icon-tree-title.png) center center no-repeat;
}
}
.custom-tree-node {
flex: 1;
line-height: 32px;
align-items: center;
display: flex;
overflow: hidden;
white-space: normal;
& > span {
&:nth-child(1):not(:last-child) {
display: inline-block;
width: calc(100% - 36px);
vertical-align: middle;
}
&:nth-child(2) {
width: 20px;
height: 20px;
display: inline-block;
padding: 0 8px 0 8px;
vertical-align: middle;
.el-dropdown {
display: none;
width: 20px;
height: 20px;
}
}
}
}
& > .el-tree-node {
& > .el-tree-node__content {
.custom-tree-node {
& > span {
&:nth-child(1) {
font-weight: 700;
}
}
}
}
}
.el-tree-node__content {
height: 32px;
font-size: 14px;
color: #333333;
display: flex;
align-items: center;
cursor: pointer;
}
.el-tree__empty-block {
text-align: center;
font-size: 14px;
color: #999;
}
}
</style>
console