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

1 month ago
<!--
图件的图层树
add by RYG
-->
<template>
<div class="tree-component">
<!-- 加载状态由父组件控制 -->
1 month ago
<!-- <div v-if="loading" class="loading">
1 month ago
加载中
1 month ago
</div> -->
<div class="tree-container">
1 month ago
<div class="operation" v-if="!props.isPopup">
1 month ago
<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)" />
1 month ago
<el-divider direction="vertical" />
1 month ago
<el-button class="square-icon-button" :icon="Delete" type="danger" circle title="删除" text
@click="deleteLayer" />
1 month ago
</div>
<!-- 树形结构使用外部传入的数据 -->
1 month ago
<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">
1 month ago
<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> -->
1 month ago
<el-icon v-if="data.status" :size="treeConfig.iconSize" :class="'icon-orange'">
1 month ago
<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>
1 month ago
<div class="popup-operation" v-if="props.isPopup && props.showOper">
1 month ago
<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> -->
1 month ago
<LoadingDialog v-model="loading" ref="loadingRef" :timeout="3000" message="正在加载图层……" @timeout="onTimeout" />
1 month ago
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { emitter } from "@/utils/eventBus";
1 month ago
import { ElTree, ElButton } from "element-plus";
1 month ago
import { Hide, Lock, Edit, Delete, Finished } from "@element-plus/icons-vue";
import { useRoute } from 'vue-router';
1 month ago
import { COMMAND, EMIT_COMMAND } from '~/utils/commandTypes'
import { useWebSocket } from '@/composables/useWebSocket'
// 消息提示(自动隐藏)
const { $toastMessage } = useNuxtApp();
1 month ago
const route = useRoute();
1 month ago
const { messages, onMessage, status, send } = useWebSocket();
1 month ago
const props = defineProps({
isPopup: { type: Boolean, default: false },
1 month ago
showOper: { type: Boolean, defalut: true },
tokenKey: { type: String, default: 'drawerToken' }
// containerHeight: { type: Number, default: window.innerHeight - 75 }
1 month ago
});
1 month ago
// const currentTabTokenKey = computed(() => props.tokenKey);
const currentToken = ref('');
1 month ago
const isPopup = computed(() => {
return props.isPopup;
});
1 month ago
1 month ago
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([]);
// 加载状态
1 month ago
const loading = ref(false);
const loadingRef = ref(null);
1 month ago
// 得到的原始图层数据
const sourceData = ref([]);
// 响应式数据
const treeRef = ref(null);
// 选中的节点 ID 数组
const selectedKeys = ref([]);
1 month ago
// 定义选择节点事件
const emit = defineEmits(['selectedChanged']);
1 month ago
// 树形配置(节点唯一标识、标签、子节点字段)
const defaultProps = {
children: "children",
label: "label",
};
1 month ago
const unsubscribe = onMessage((data) => {
1 month ago
try {
1 month ago
if (!data) return; // 如果没有新消息,直接返回
const json = JSON.parse(data);
1 month ago
const evtType = json.type;
switch (evtType) {
1 month ago
case COMMAND.RELOAD_LAYER:
1 month ago
let layerData = json.data;
showLayers(layerData);
1 month ago
loadingRef.value.close();
1 month ago
break;
}
} finally {
}
1 month ago
})
const onTimeout = () => {
$toastMessage.error("加载图层超时,请稍后重试!");
};
1 month ago
// 组装树的数据
const showLayers = (layerData) => {
1 month ago
if (!layerData) {
return;
}
1 month ago
// 确保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";
}
1 month ago
send(COMMAND.SET_LAYER_STATUS, { layerData: statusData }, getToken());
1 month ago
// 处理图层状态设置逻辑
};
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);
}
1 month ago
send(COMMAND.DELETE_LAYER, { layers: statusData, widthChild: 1 }, getToken());
1 month ago
};
// 删除指定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
};
1 month ago
// 获取当前图件的token
const getToken = () => {
currentToken.value = localStorage.getItem(props.tokenKey);
return currentToken.value || '';
}
1 month ago
onMounted(() => {
1 month ago
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);
1 month ago
});
1 month ago
const sourceChanged = () => {
clearTree();
}
const getLayer = () => {
send(COMMAND.GET_LAYERS, null, getToken(), currentToken.value);
}
1 month ago
const getNodeIcon = (status) => {
switch (status) {
case 1:
return Edit;
case 2:
return Lock;
case 3:
return Hide;
default:
return Edit; // 默认图标
}
};
// 递归处理节点和子节点
const processNode = (node) => {
1 month ago
let nodeIcon = getNodeIcon(node.status);
1 month ago
const treeNode = {
1 month ago
id: node.id,
1 month ago
pId: "Layer:", // node.ParentId.toString(),
1 month ago
label: node.name || "",
1 month ago
open: false,
icon: nodeIcon,
1 month ago
status: node.status || 0,
fullPath: node.fullPath || "",
1 month ago
children: [],
};
// 如果有子节点,递归处理
1 month ago
if (node.children && node.children.length > 0) {
treeNode.children = node.children.map((child) => {
1 month ago
const childNode = processNode(child);
// 确保子节点的pId指向父节点id
childNode.pId = treeNode.id;
return childNode;
});
// 有子节点时默认展开
treeNode.open = true;
}
return treeNode;
};
onBeforeUnmount(() => {
// emitter.off("ReloadLayer");
1 month ago
clearTree();
unsubscribe();
emitter.off(EMIT_COMMAND.SOURCE_CHANGED);
emitter.off(EMIT_COMMAND.REFRESH_LAYER)
1 month ago
});
1 month ago
const clearTree = () => {
treeData.value = null;
}
1 month ago
// 选中节点事件处理
const handleCheck = (currentNode, checkStatus) => {
selectedKeys.value = checkStatus.checkedKeys;
1 month ago
if (props.isPopup && !props.showOper) {
let statusData = getSelectedLayers();
emit(EMIT_COMMAND.SELECTED_CHANGED, statusData);
}
1 month ago
};
// 获取选中的节点
const getSelectedNodes = () => {
// 通过 Tree 实例获取选中节点的完整数据
const selectedNodes = treeRef.value?.getCheckedNodes() || [];
return selectedNodes;
};
// 清空选择
const clearSelection = () => {
treeRef.value?.setCheckedKeys([]);
selectedKeys.value = [];
};
1 month ago
// 确定选择按钮事件
1 month ago
const handleConfirm = () => {
1 month ago
let statusData = getSelectedLayers();
if (statusData === '') {
return;
}
emitter.emit(EMIT_COMMAND.SELECTED_LAYER, statusData);
// 得到数据之后清空选择
clearSelection();
};
// 获取选中的图层
const getSelectedLayers = () => {
1 month ago
let nodes = getSelectedNodes();
nodes = nodes.filter((node) => node.name !== "图层");
let nCount = nodes.length;
if (nCount === 0) {
1 month ago
return '';
1 month ago
}
let statusData = "";
for (let i = 0; i < nCount; i++) {
statusData += nodes[i].fullPath + ",";
}
1 month ago
return statusData;
1 month ago
};
1 month ago
onUnmounted(() => {
unsubscribe();
});
1 month ago
</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%;
1 month ago
/* height: calc(100vh - 75px); */
height: 100%;
1 month ago
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 {
1 month ago
height: 50px;
1 month ago
vertical-align: middle;
padding-top: 10px;
padding-right: 10px;
1 month ago
padding-bottom: 10px;
1 month ago
border-top: 1px solid #eaeaea;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.square-icon-button {
width: 25px;
height: 25px;
1 month ago
padding: 0 !important;
/* 去掉内边距 */
border-radius: 4px;
/* 设置圆角为4px若要直角则设为0 */
1 month ago
display: inline-flex;
justify-content: center;
align-items: center;
}
.square-icon-button:hover {
1 month ago
color: #ec0a0a;
/* 鼠标悬停时的背景色 */
1 month ago
}
.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);
}
1 month ago
.custom-tree .el-tree-node.is-current>.el-tree-node__content {
1 month ago
background-color: rgba(52, 152, 219, 0.15);
}
1 month ago
.custom-tree .el-tree-node.is-current>.el-tree-node__content .el-tree-node__label {
1 month ago
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>