You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

547 lines
13 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
图件的图层树
add by RYG
-->
<template>
<div class="tree-component">
<!-- 加载状态由父组件控制 -->
<!-- <div v-if="loading" class="loading">
加载中……
</div> -->
<div class="tree-container">
<div class="operation" v-if="!props.isPopup">
<el-button v-for="(btn, index) in iconButtons" :key="index" class="square-icon-button"
:icon="getNodeIcon(btn.id)" :type="btn.type" :title="btn.title" text @click="() => setLayerStatus(btn.id)" />
<el-divider direction="vertical" />
<el-button class="square-icon-button" :icon="Delete" type="danger" circle title="删除" text
@click="deleteLayer" />
</div>
<!-- 树形结构(使用外部传入的数据) -->
<el-tree ref="treeRef" v-model="selectedKeys" :data="treeData" show-checkbox node-key="id" default-expand-all
:highlight-current="true" :props="defaultProps" placeholder="点击展开加载子节点..." clearable @check="handleCheck"
class="custom-tree">
<template #default="{ node, data }">
<div class="tree-node">
<span class="icon-container">
<!-- <el-icon :size="treeConfig.iconSize" :class="'icon-orange'">
<component :is="data.icon" />
</el-icon> -->
<el-icon v-if="data.status" :size="treeConfig.iconSize" :class="'icon-orange'">
<component :is="getNodeIcon(data.status)" />
</el-icon>
</span>
<span>{{ node.label }}</span>
<!-- <span v-if="data.count" class="node-count">{{ data.count }}</span> -->
</div>
</template>
</el-tree>
<div class="popup-operation" v-if="props.isPopup && props.showOper">
<el-button :icon="Finished" @click="handleConfirm" type="primary">确定</el-button>
</div>
</div>
<!-- 操作按钮 -->
<!-- <div class="actions" v-if="treeData.length">
<el-button type="primary" @click="getSelectedNodes">获取选中项</el-button>
<el-button @click="clearSelection"></el-button>
</div> -->
<LoadingDialog v-model="loading" ref="loadingRef" :timeout="3000" message="正在加载图层……" @timeout="onTimeout" />
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { emitter } from "@/utils/eventBus";
import { ElTree, ElButton } from "element-plus";
import { Hide, Lock, Edit, Delete, Finished } from "@element-plus/icons-vue";
import { useRoute } from 'vue-router';
import { COMMAND, EMIT_COMMAND } from '~/utils/commandTypes'
import { useWebSocket } from '@/composables/useWebSocket'
// 消息提示(自动隐藏)
const { $toastMessage } = useNuxtApp();
const route = useRoute();
const { messages, onMessage, status, send } = useWebSocket();
const props = defineProps({
isPopup: { type: Boolean, default: false },
showOper: { type: Boolean, defalut: true },
tokenKey: { type: String, default: 'drawerToken' }
// containerHeight: { type: Number, default: window.innerHeight - 75 }
});
// const currentTabTokenKey = computed(() => props.tokenKey);
const currentToken = ref('');
const isPopup = computed(() => {
return props.isPopup;
});
const iconButtons = [
{ icon: "edit", title: "可编辑", type: "warning", id: 1 },
{ icon: "lock", title: "只读", type: "warning", id: 2 },
{ icon: "hide", title: "隐藏", type: "warning", id: 3 },
// { icon: Delete, title: "删除", type: "danger" },
];
// 树配置
const treeConfig = ref({
fontSize: 13,
iconSize: 13,
nodeHeight: 25,
currentHighlight: true,
showIcons: true,
});
// 树的数据
const treeData = ref([]);
// 加载状态
const loading = ref(false);
const loadingRef = ref(null);
// 得到的原始图层数据
const sourceData = ref([]);
// 响应式数据
const treeRef = ref(null);
// 选中的节点 ID 数组
const selectedKeys = ref([]);
// 定义选择节点事件
const emit = defineEmits(['selectedChanged']);
// 树形配置(节点唯一标识、标签、子节点字段)
const defaultProps = {
children: "children",
label: "label",
};
const unsubscribe = onMessage((data) => {
try {
if (!data) return; // 如果没有新消息,直接返回
const json = JSON.parse(data);
const evtType = json.type;
switch (evtType) {
case COMMAND.RELOAD_LAYER:
let layerData = json.data;
showLayers(layerData);
loadingRef.value.close();
break;
}
} finally {
}
})
const onTimeout = () => {
$toastMessage.error("加载图层超时,请稍后重试!");
};
// 组装树的数据
const showLayers = (layerData) => {
if (!layerData) {
return;
}
// 确保layerData是有效的JSON字符串
if (typeof layerData === "string") {
layerData = JSON.parse(layerData);
}
// 确保转换后是数组
if (!Array.isArray(layerData)) {
console.error("Invalid layer data format - expected array");
return;
}
sourceData.value = layerData; // 假设data是树形结构数据
// 创建图层根节点
let layerNodes = [
{
id: "Layer:",
pId: "-1",
label: "图层",
open: true,
halfCheck: false,
status: 1,
icon: getNodeIcon(1), // 默认图标为可编辑
children: [],
},
];
layerNodes[0].children = layerData.map((node) => processNode(node));
treeData.value = layerNodes;
};
const setLayerStatus = (status) => {
let nodes = getSelectedNodes();
nodes = nodes.filter((node) => node.name !== "图层");
let nCount = nodes.length;
if (nCount === 0) {
return;
}
let strStatus = "10";
if (status === 2) {
strStatus = "11";
} else if (status === 3) {
strStatus = "12";
}
let statusData = "";
for (let i = 0; i < nCount; i++) {
// var imgUrl = iconButtons.value[status].icon;
// nodes[i].status = status;
// treeObj.updateNode(nodes[i]);
editNodeById(treeData.value, nodes[i].id, status);
statusData += strStatus + "|Layer:\\" + nodes[i].fullPath + "\r\n";
}
send(COMMAND.SET_LAYER_STATUS, { layerData: statusData }, getToken());
// 处理图层状态设置逻辑
};
const deleteLayer = () => {
let nodes = getSelectedNodes();
nodes = nodes.filter((node) => node.name !== "图层");
let nCount = nodes.length;
if (nCount === 0) {
return;
}
let statusData = "";
for (let i = 0; i < nCount; i++) {
// statusData += (nodes[i].id + ",");
statusData += "Layer:\\" + nodes[i].fullPath + ",";
// treeRef.value.removeNode(nodes[i]);
removeNodeById(treeData.value, nodes[i].id);
}
send(COMMAND.DELETE_LAYER, { layers: statusData, widthChild: 1 }, getToken());
};
// 删除指定ID的节点
const removeNodeById = (tree, id) => {
for (let i = 0; i < tree.length; i++) {
if (tree[i].id === id) {
const removedNode = tree.splice(i, 1)[0];
return { removed: true, removedNode };
}
if (tree[i].children && tree[i].children.length > 0) {
const result = removeNodeById(tree[i].children, id);
if (result.removed) {
return result;
}
}
}
return { removed: false };
};
// 修改节点的状态
const editNodeById = (tree, id, status) => {
for (let i = 0; i < tree.length; i++) {
if (tree[i].id === id) {
// 在这里可以修改节点的属性
tree[i].status = status;
tree[i].icon = getNodeIcon(status); // 更新图标
return true; // 找到并修改节点返回true
}
if (tree[i].children && tree[i].children.length > 0) {
const found = editNodeById(tree[i].children, id, status);
if (found) {
return true; // 如果在子节点中找到并修改返回true
}
}
}
return false; // 没有找到节点返回false
};
// 获取当前图件的token
const getToken = () => {
currentToken.value = localStorage.getItem(props.tokenKey);
return currentToken.value || '';
}
onMounted(() => {
getToken();
clearTree();
// send("GetLayers", null, currentToken.value);
loading.value = true;
sourceChanged();
getLayer();
emitter.on(EMIT_COMMAND.SOURCE_CHANGED, sourceChanged);
emitter.on(EMIT_COMMAND.REFRESH_LAYER, getLayer);
});
const sourceChanged = () => {
clearTree();
}
const getLayer = () => {
send(COMMAND.GET_LAYERS, null, getToken());
}
const getNodeIcon = (status) => {
switch (status) {
case 1:
return Edit;
case 2:
return Lock;
case 3:
return Hide;
default:
return Edit; // 默认图标
}
};
// 递归处理节点和子节点
const processNode = (node) => {
let nodeIcon = getNodeIcon(node.status);
const treeNode = {
id: node.id,
pId: "Layer:", // node.ParentId.toString(),
label: node.name || "",
open: false,
icon: nodeIcon,
status: node.status || 0,
fullPath: node.fullPath || "",
children: [],
};
// 如果有子节点,递归处理
if (node.children && node.children.length > 0) {
treeNode.children = node.children.map((child) => {
const childNode = processNode(child);
// 确保子节点的pId指向父节点id
childNode.pId = treeNode.id;
return childNode;
});
// 有子节点时默认展开
treeNode.open = true;
}
return treeNode;
};
onBeforeUnmount(() => {
// emitter.off("ReloadLayer");
clearTree();
unsubscribe();
emitter.off(EMIT_COMMAND.SOURCE_CHANGED);
emitter.off(EMIT_COMMAND.REFRESH_LAYER)
});
const clearTree = () => {
treeData.value = null;
}
// 选中节点事件处理
const handleCheck = (currentNode, checkStatus) => {
selectedKeys.value = checkStatus.checkedKeys;
if (props.isPopup && !props.showOper) {
let statusData = getSelectedLayers();
emit(EMIT_COMMAND.SELECTED_CHANGED, statusData);
}
};
// 获取选中的节点
const getSelectedNodes = () => {
// 通过 Tree 实例获取选中节点的完整数据
const selectedNodes = treeRef.value?.getCheckedNodes() || [];
return selectedNodes;
};
// 清空选择
const clearSelection = () => {
treeRef.value?.setCheckedKeys([]);
selectedKeys.value = [];
};
// 确定选择按钮事件
const handleConfirm = () => {
let statusData = getSelectedLayers();
if (statusData === '') {
return;
}
emitter.emit(EMIT_COMMAND.SELECTED_LAYER, statusData);
// 得到数据之后清空选择
clearSelection();
};
// 获取选中的图层
const getSelectedLayers = () => {
let nodes = getSelectedNodes();
nodes = nodes.filter((node) => node.name !== "图层");
let nCount = nodes.length;
if (nCount === 0) {
return '';
}
let statusData = "";
for (let i = 0; i < nCount; i++) {
statusData += nodes[i].fullPath + ",";
}
return statusData;
};
onUnmounted(() => {
unsubscribe();
});
</script>
<style scoped>
.tree-component {
width: 100%;
height: 100%;
margin: 0 auto;
padding: 0;
border: 0;
background: #fff;
max-width: 240px;
}
.tree-container {
width: 100%;
/* height: calc(100vh - 75px); */
height: 100%;
overflow: auto;
/* background: lightblue; */
padding: 0;
vertical-align: middle;
/* background: #2c3e50; */
}
.operation {
height: 40px;
vertical-align: middle;
padding-top: 5px;
padding-left: 10px;
border: 1px solid #eaeaea;
}
.popup-operation {
height: 50px;
vertical-align: middle;
padding-top: 10px;
padding-right: 10px;
padding-bottom: 10px;
border-top: 1px solid #eaeaea;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.square-icon-button {
width: 25px;
height: 25px;
padding: 0 !important;
/* 去掉内边距 */
border-radius: 4px;
/* 设置圆角为4px若要直角则设为0 */
display: inline-flex;
justify-content: center;
align-items: center;
}
.square-icon-button:hover {
color: #ec0a0a;
/* 鼠标悬停时的背景色 */
}
.loading {
width: 100%;
height: 100%;
display: flex;
background: #333;
vertical-align: middle;
justify-content: center;
padding: 0;
font-size: 14px;
color: #3498db;
}
.actions {
margin-top: 15px;
display: flex;
gap: 5px;
}
.custom-tree {
/* 自定义文字大小 */
--tree-font-size: 13px;
--tree-icon-size: 13px;
/* overflow: auto; */
font-size: var(--tree-font-size);
}
.custom-tree .el-tree-node__content {
height: 25px !important;
transition: background-color 0.2s;
padding: 0;
border-radius: 6px;
}
.custom-tree .el-tree-node__content:hover {
background-color: rgba(52, 152, 219, 0.1);
}
.custom-tree .el-tree-node__label {
/* font-weight: 300; */
color: #2c3e50;
white-space: nowrap;
margin-left: 0;
}
.custom-tree .el-tree-node__expand-icon {
font-size: var(--tree-icon-size);
transition: transform 0.3s;
}
.custom-tree .el-tree-node__expand-icon.expanded {
transform: rotate(90deg);
}
.custom-tree .el-tree-node.is-current>.el-tree-node__content {
background-color: rgba(52, 152, 219, 0.15);
}
.custom-tree .el-tree-node.is-current>.el-tree-node__content .el-tree-node__label {
color: #2980b9;
/* font-weight: 400; */
}
.icon-container {
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 2px;
width: 16px;
height: 16px;
}
.icon-blue {
color: #3498db;
}
.icon-green {
color: #27ae60;
}
.icon-orange {
color: #e67e22;
}
.icon-purple {
color: #9b59b6;
}
.el-tree {
--el-tree-node-hover-bg-color: rgba(52, 152, 219, 0.1);
--el-tree-text-color: #2c3e50;
--el-tree-expand-icon-color: #e4d61b;
/* background: #333; */
display: flex;
}
</style>