Appearance
大屏数据可视化组件 (拖拽svg)
本文档详细介绍了大屏 echarts 组件的实现方案,这是一个功能丰富的数据可视化大屏,核心是一个高度定制化的 ECharts 2D 地图,并辅以多种交互式弹窗和数据面板。
功能概述
该组件基于 Vue 3 (Composition API), ECharts 和 Element Plus 构建,提供以下主要功能:
- 三栏式布局:
- 左侧面板: 展示各类统计数据,如在岗人数、请假人数等,以及一个可滚动的公告栏。
- 中间核心区: 包含顶部导航、定制化的 2D 地图以及驻村人员信息滚动列表。
- 右侧面板: 展示工作动态和资金绩效图表。
- 高度定制化的 ECharts 地图:
- 使用自定义图片作为地图背景和区域填充,而非标准着色。
- 支持从市级地图下钻到乡镇级子地图。
- 在子地图上以散点图形式展示村庄点位。
- 地图带有装饰性动画效果,如旋转光圈和扫描线。
- 丰富的弹窗交互:
- 点击地图乡镇区域,弹出信息窗。
- 点击子地图村庄点位,弹出村庄详情窗。
- 弹窗与地图上的点击点之间通过 SVG 动态连接线 连接。
- 所有弹窗均支持 自由拖拽。
- 顶部导航栏可以打开全屏覆盖的通用弹窗,用于展示资料库、工作动态、培训学习和公告栏等详细内容。
依赖项
javascript
// 主要NPM库
import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from "vue";
import * as echarts from "echarts";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
// 本地模块/数据
import { MiLinData, MiLinGeoJSON } from "@/utils/map-data";
import { townPoints } from "@/utils/point";
import {
infoNoticeList,
userStatList,
workDynamicLists,
infoAllFundsStat,
infoStayUserList,
infoFilterTownList,
resourcelist,
noticelist,
workDynamiclist,
getFilterTownList,
getFilterTownVillageList,
resourceDetail,
workDynamicDetail,
noticeDetail,
} from "@/apis/dock.js"; // 各类API接口
import RelaodTable from "@/components/RelaodTable.vue";
import Meet from "@/views/components/meet.vue";核心组件结构
html
<template>
<div class="visualization-content">
<!-- 主内容区 -->
<div class="main-content">
<!-- 左边面板 -->
<div class="left-panel">...</div>
<!-- 地图核心区 -->
<div class="map-container">
<!-- 顶部导航 -->
<div class="nav-title">...</div>
<!-- 地图框架 -->
<div class="map-frame">
<div id="mapChart"></div>
<!-- 装饰元素 -->
</div>
<!-- 地图下方信息 -->
<div class="map-info">...</div>
</div>
<!-- 右边面板 -->
<div class="right-panel">...</div>
</div>
<!-- 区域信息弹窗 (乡镇) -->
<div class="area-info-popup" v-if="filterTownList">
<!-- SVG 连接线 -->
<svg class="popup-connector">
<path :d="connectPath" ...></path>
</svg>
<!-- 弹窗内容 -->
<div class="popup-content" ref="popupContentRef" @mousedown="startDrag">
...
</div>
</div>
<!-- 村庄信息弹窗 -->
<div class="area-info-popup" v-if="villagePopupVisible">
<!-- SVG 连接线 -->
<svg class="popup-connector">
<path :d="villageConnectPath" ...></path>
</svg>
<!-- 弹窗内容 -->
<div
class="popup-content"
ref="villagePopupContentRef"
@mousedown="startVillageDrag"
>
...
</div>
</div>
<!-- 通用全屏弹窗 -->
<transition name="fade">
<div class="document-library-popup" v-show="isAnyPopupVisible">...</div>
</transition>
</div>
</template>主要功能实现
1. 定制化 2D 地图
该组件采用高度定制的 2D 地图方案,以获得更好的视觉效果和性能。
地图配置 (createMiLinMapOption):
- 背景透明:
backgroundColor: "transparent",以便下方的 DOM 动画元素可以显示出来。 - 自定义区域样式:javascript
itemStyle: { borderColor: "#AD7205", borderWidth: 2, areaColor: { type: "image", image: ngsImg, // 使用自定义图片填充 repeat: "repeat", }, }, emphasis: { // 悬浮/选中样式 itemStyle: { areaColor: { type: "image", image: nbsImg, // 切换为另一张图片 }, }, }, - 下钻与返回: 通过双击事件
handleDoubleClick切换到子地图视图,并显示返回按钮。子地图renderSonsMap的实现方式类似,但只加载被点击区域的 GeoJSON feature。
2. 弹窗交互详解
2.1 弹窗拖拽
弹窗的拖拽功能是通过监听鼠标事件实现的,逻辑封装在 startDrag, doDrag, stopDrag 等函数中。
startDrag(onmousedown):- 记录拖拽开始。设置
isDragging标志位为true。 - 计算并记录鼠标在弹窗内的偏移量
dragOffset(鼠标位置 - 弹窗左上角位置)。 - 在
window上注册mousemove和mouseup事件监听器。
- 记录拖拽开始。设置
doDrag(onmousemove):- 如果
isDragging为true,则根据当前鼠标位置和初始偏移量,实时计算弹窗新的left和top值。 - 更新
popupPosition这个ref变量,该变量通过style绑定到弹窗元素上,从而实现视图更新。
javascriptpopupPosition.value = { x: event.clientX - dragOffset.value.x, y: event.clientY - dragOffset.value.y, };- 如果
stopDrag(onmouseup):- 设置
isDragging为false。 - 移除在
window上注册的mousemove和mouseup监听器,停止拖拽。
- 设置
2.2 SVG 动态连接线
为了在地图上的点击点和弹出的信息框之间建立视觉联系,组件在顶层覆盖了一个全屏的 <svg> 元素,并在其中动态绘制 <path> 路径作为连接线。
实现步骤:
全屏 SVG 容器:
html<svg class="popup-connector" :width="svgWidth" :height="svgHeight"> <path :d="connectPath" fill="none" stroke="#FF4815" stroke-width="3" ></path> <!-- 还有一些装饰性的 circle 元素 --> </svg>- 这个 SVG 覆盖整个屏幕 (
position: fixed),但pointer-events: none确保它不影响下层元素的交互。 - 路径的形状由
:d属性绑定connectPath这个ref变量来控制。
- 这个 SVG 覆盖整个屏幕 (
记录点击位置:
- 在地图的
click事件处理器中,通过params.event.event.clientX和clientY获取并存储鼠标在屏幕上的绝对坐标clickPosition。
- 在地图的
计算连接线路径 (
updateConnectPath):- 此函数在弹窗位置
popupPosition变化时被watch自动调用。 - 起点: 线的起点
(startX, startY)就是之前记录的clickPosition。 - 终点: 为了让连接线看起来更自然,终点不是弹窗的左上角,而是弹窗四条边中离起点最近的中点。
- 首先获取弹窗的实时矩形区域
popupRect = popupContentRef.value.getBoundingClientRect()。 - 计算四条边中点的坐标,并找到离起点最近的一个作为
targetX, targetY。
- 首先获取弹窗的实时矩形区域
- 拐点: 为了避免斜线,路径采用两段正交的折线。根据起点和终点的位置关系,计算出一个 "肘部" 拐点
(elbowX, elbowY)。 - 生成路径字符串: 将起点、拐点、终点组合成 SVG 路径指令字符串。
javascript// M = Move To, L = Line To connectPath.value = `M ${startX} ${startY} L ${elbowX} ${elbowY} L ${targetX} ${targetY}`;- 此函数在弹窗位置
响应式更新:
- 使用
watch深度监听popupPosition的变化。 - 每当拖拽导致
popupPosition更新时,watch回调会触发updateConnectPath()函数,重新计算路径并更新connectPath,从而实现连接线跟随意图拖动而实时变化。
javascriptwatch( popupPosition, () => { nextTick(() => { updateConnectPath(); }); }, { deep: true } );villagePopupVisible(村庄弹窗)的拖拽和连线逻辑与此完全相同,只是使用了另一套独立的 ref 变量(如villagePopupPosition,villageConnectPath等)。- 使用
使用方法
引入组件:
html<script setup> import Visualization from "@/docs/frontEnd/pcTerminal/visualization/visualization.vue"; </script> <template> <Visualization /> </template>确保依赖和 API:
- 确保项目中安装了
echarts,element-plus等依赖。 - 确保
@/apis/dock.js中的接口可以正常请求数据。 - 确保地图 GeoJSON 数据和图片资源路径正确。
- 确保项目中安装了
注意事项
- 性能: 大量的 DOM 元素和动画效果可能对性能有一定影响。左侧和底部的滚动列表使用了无缝滚动的技巧(复制列表内容),在数据量极大时可能需要优化。
- 事件处理: 组件中大量使用了
window事件监听器(如resize,mousemove),必须在onUnmounted钩子中正确移除,以防内存泄漏。 - 坐标系: 地图点击事件返回的是 ECharts 内部的坐标,需要通过
params.event.event获取屏幕坐标,用于计算弹窗和 SVG 连接线的位置。
