Skip to content

大屏数据可视化组件 (拖拽svg)

本文档详细介绍了大屏 echarts 组件的实现方案,这是一个功能丰富的数据可视化大屏,核心是一个高度定制化的 ECharts 2D 地图,并辅以多种交互式弹窗和数据面板。

功能概述

该组件基于 Vue 3 (Composition API), ECharts 和 Element Plus 构建,提供以下主要功能:

  1. 三栏式布局:
    • 左侧面板: 展示各类统计数据,如在岗人数、请假人数等,以及一个可滚动的公告栏。
    • 中间核心区: 包含顶部导航、定制化的 2D 地图以及驻村人员信息滚动列表。
    • 右侧面板: 展示工作动态和资金绩效图表。
  2. 高度定制化的 ECharts 地图:
    • 使用自定义图片作为地图背景和区域填充,而非标准着色。
    • 支持从市级地图下钻到乡镇级子地图。
    • 在子地图上以散点图形式展示村庄点位。
    • 地图带有装饰性动画效果,如旋转光圈和扫描线。
  3. 丰富的弹窗交互:
    • 点击地图乡镇区域,弹出信息窗。
    • 点击子地图村庄点位,弹出村庄详情窗。
    • 弹窗与地图上的点击点之间通过 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 等函数中。

  1. startDrag (on mousedown):

    • 记录拖拽开始。设置 isDragging 标志位为 true
    • 计算并记录鼠标在弹窗内的偏移量 dragOffset (鼠标位置 - 弹窗左上角位置)。
    • window 上注册 mousemovemouseup 事件监听器。
  2. doDrag (on mousemove):

    • 如果 isDraggingtrue,则根据当前鼠标位置和初始偏移量,实时计算弹窗新的 lefttop 值。
    • 更新 popupPosition 这个 ref 变量,该变量通过 style 绑定到弹窗元素上,从而实现视图更新。
    javascript
    popupPosition.value = {
      x: event.clientX - dragOffset.value.x,
      y: event.clientY - dragOffset.value.y,
    };
  3. stopDrag (on mouseup):

    • 设置 isDraggingfalse
    • 移除在 window 上注册的 mousemovemouseup 监听器,停止拖拽。

2.2 SVG 动态连接线

为了在地图上的点击点和弹出的信息框之间建立视觉联系,组件在顶层覆盖了一个全屏的 <svg> 元素,并在其中动态绘制 <path> 路径作为连接线。

实现步骤:

  1. 全屏 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 变量来控制。
  2. 记录点击位置:

    • 在地图的 click 事件处理器中,通过 params.event.event.clientXclientY 获取并存储鼠标在屏幕上的绝对坐标 clickPosition
  3. 计算连接线路径 (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}`;
  4. 响应式更新:

    • 使用 watch 深度监听 popupPosition 的变化。
    • 每当拖拽导致 popupPosition 更新时,watch 回调会触发 updateConnectPath() 函数,重新计算路径并更新 connectPath,从而实现连接线跟随意图拖动而实时变化。
    javascript
    watch(
      popupPosition,
      () => {
        nextTick(() => {
          updateConnectPath();
        });
      },
      { deep: true }
    );

    villagePopupVisible(村庄弹窗)的拖拽和连线逻辑与此完全相同,只是使用了另一套独立的 ref 变量(如 villagePopupPosition, villageConnectPath 等)。

使用方法

  1. 引入组件:

    html
    <script setup>
      import Visualization from "@/docs/frontEnd/pcTerminal/visualization/visualization.vue";
    </script>
    <template>
      <Visualization />
    </template>
  2. 确保依赖和 API:

    • 确保项目中安装了 echarts, element-plus 等依赖。
    • 确保 @/apis/dock.js 中的接口可以正常请求数据。
    • 确保地图 GeoJSON 数据和图片资源路径正确。

注意事项

  • 性能: 大量的 DOM 元素和动画效果可能对性能有一定影响。左侧和底部的滚动列表使用了无缝滚动的技巧(复制列表内容),在数据量极大时可能需要优化。
  • 事件处理: 组件中大量使用了 window 事件监听器(如 resize, mousemove),必须在 onUnmounted 钩子中正确移除,以防内存泄漏。
  • 坐标系: 地图点击事件返回的是 ECharts 内部的坐标,需要通过 params.event.event 获取屏幕坐标,用于计算弹窗和 SVG 连接线的位置。

本内容仅限内部使用,包含机密和专有信息。严禁任何形式的复制、分发或泄露给任何第三方