博客 / 詳情

返回

Vue 3 + SVG :打造動態交互式智慧公廁可視化大屏

🚀 Vue 3 + SVG :打造“會呼吸”的智慧可視化大屏

在智慧城市建設的浪潮中,可視化大屏已成為展示數據的核心窗口。而在“智慧公廁”這一細分場景下,如何直觀、實時、高保真地展示每個廁位的佔用狀態(有人/無人),是前端開發中一個既有趣又充滿挑戰的課題。

傳統的做法往往是“切圖一把梭”——使用多張圖片進行絕對定位。但這種方式不僅適配性差(換個分辨率就由於),而且維護成本極高(加個廁位還得找 UI 重新切圖)。

今天,我們將分享一種 基於 Vue 3 + 動態 SVG 的進階方案,拒絕切圖,直接操作矢量路徑,實現一套高性能、任意縮放、毫秒級響應的廁位狀態可視化系統。


💡 為什麼選擇 SVG?

相比於 Canvas 或位圖,SVG 在這種場景下擁有降維打擊般的優勢:

  1. 💎 高保真矢量渲染:無論屏幕是 1080P 還是 4K,線條永遠清晰鋭利,告別鋸齒。
  2. 🎮 動態 DOM 交互:SVG 本質上是 XML,注入頁面後就是 DOM。這意味着我們可以像操作 <div> 一樣,用 CSS 和 JS 直接控制它的顏色、大小甚至形狀。
  3. ⚡ 實時 WebSocket 驅動:後端狀態一推,前端毫秒級變色,無需輪詢,無需刷新。
  4. 🧩 低代碼維護:設計師只需遵循簡單的命名規範(如 ID 命名),前端即可自動識別並綁定數據,新增設備無需改代碼。

🛠️ 技術實現:三步走戰略

我們的實現邏輯非常清晰,分為三步:加載 -> 綁定 -> 驅動

第一步:動態加載 SVG 源碼

為了能夠操作 SVG 內部的節點,我們不能簡單地使用 <img> 標籤,因為 <img> 引入的 SVG 是作為一個整體“黑盒”渲染的,JS 無法觸及其內部靈魂。

目標:將 SVG 文件作為 XML 字符串獲取,並注入到頁面 DOM 中。

代碼實現:

<!-- 容器,用於承載 SVG DOM -->
<div class="toilet">
  <div class="svgObject" v-html="svgContent" ref="svgContainer"></div>
</div>
const svgContent = ref("");
const svgContainer = ref(null);

// 核心方法:加載 SVG
const loadSVG = async (url) => {
  try {
    // 1. 發起 HTTP 請求,獲取 SVG 文件的純文本內容
    // 這裏的 getSvgUrl 是封裝好的 axios 請求
    const response = await getSvgUrl(url);
    const svgText = await response;

    // 2. 利用 v-html 將 SVG 字符串“注入”到 DOM 中
    svgContent.value = svgText;

    // 🌟 關鍵點:等待 Vue 完成 DOM 更新
    // 因為 v-html 的渲染是異步的,必須 await nextTick() 才能確保 DOM 節點已經存在
    await nextTick();

    // 3. SVG 加載完畢,開始初始化路徑綁定
    initSvgPaths();
  } catch (error) {
    console.error("SVG加載失敗:", error);
  }
};

第二步:智能解析與綁定

SVG 注入後,它還只是一堆靜態的標籤。我們需要找到那些代表“廁位”的 path 標籤,把它們提取出來。

約定:設計師在繪製 SVG 時,將每個廁位的 path 元素的 id 設置為對應的業務編號(如 "1", "2", "1-1")。

目標:提取出所有具有有效 ID 的 path 節點,存入數組備用。

代碼實現:

const svgPathList = ref([]);

const initSvgPaths = () => {
  // 1. 就像操作普通 HTML 一樣,獲取所有 path 標籤
  const paths = svgContainer.value.querySelectorAll("path");

  // 2. 篩選出所有“智能廁位”節點
  svgPathList.value = Array.from(paths).filter(
    (path) =>
      // 🛡️ 正則過濾:只保留 ID 為數字或帶連字符的節點
      // 這一步非常重要,能排除掉背景、裝飾線條等無關元素,避免誤操作
      path.id && /^[\d-]+$/.test(path.id)
  );
  console.log("SVG路徑加載完成", svgPathList.value);
};

第三步:WebSocket 實時驅動(含性能優化)

這裏是最核心的部分。當 WebSocket 推送最新的狀態數據時,我們需要實時更新 SVG 的顏色。這裏看似簡單,實則隱藏着性能陷阱。

❌ 初級寫法(踩坑版)

第一次的時候是寫的寫雙重循環:遍歷後端返回的數據,然後針對每一條數據去遍歷 DOM 節點尋找匹配的 ID,找到後然後去更新顏色。

// 🚫 糟糕的實現:O(n*m) 複雜度
// 假設有 50 個廁位,後端推送了 50 條數據,這裏就要執行 2500 次判斷
// 這個方式是接收websocket推送的消息,然後針對每一個消息做處理,大家可以自行封裝一個websocket請求然後綁定處理函數
const handleNewMessage = (data) => {
  if (data.type === "STALL" && data.code == 200) {
    const stallData = data.data;
    for (let i = 0; i < stallData.value.length; i++) {
        // 遍歷後端數據,針對每一個廁位做是否有人判斷,
        // 如果是“2”(有人),則遍歷所有 SVG 路徑,找到匹配 ID 的那個,更新顏色為 "#F98DB1"
      if (stallData.value[i].stallStatus == "2") {
        const targetPath = svgPathList.value.forEach((path) => {
          if (path.id === stallData.value[i].stallNumber) {
            path.style.fill = "#F98DB1";
          } else {
            path.style.fill = "#0FE7FC";
          }
        });
      }
    }
  }
};

這種寫法不僅性能隨着節點數量增加而指數級下降(O(n²)),而且容易出現邏輯漏洞:如果後端數據有重複或順序問題,可能會導致狀態被錯誤覆蓋。

✅ 進階寫法(優化版)

為了實現極致性能,我們採用“空間換時間”的策略,將算法複雜度降維到 O(n)。

核心邏輯步驟:

  1. 建立索引(Mapping)
    首先,將後端返回的數組轉換為 Map 結構。

    • 為什麼? 數組查找元素需要遍歷,時間複雜度是 O(n);而 Map 基於哈希表,查找時間複雜度接近 O(1)。
    • 我們將 stallNumber 作為 Key,stallStatus 作為 Value。
  2. 單次遍歷(Single Pass)
    直接遍歷頁面上的 SVG 路徑節點(svgPathList)。

    • 怎麼做? 對於每一個 Path 節點,直接去 Map 中詢問:“我是 3 號坑位,現在有人嗎?”
    • 結果Map 會瞬間返回狀態,無需再次遍歷數據源。
  3. 狀態驅動視圖
    根據拿到的狀態,動態修改 fill 屬性,並配合 CSS transition 實現絲滑的顏色過渡。

代碼實現:

const handleNewMessage = (data) => {
  if (data.type === "STALL" && data.code == 200) {
    const stallData = data.data;

    // ------------------------------------------------------
    // 步驟 1: 構建高效查找表 (Lookup Table)
    // ------------------------------------------------------
    // 將數組 [ { stallNumber: "1", stallStatus: "2" }, ... ]
    // 轉換為 Map { "1" => "2", ... }
    const statusMap = new Map(
      stallData.map((item) => [item.stallNumber, item.stallStatus])
    );

    // ------------------------------------------------------
    // 步驟 2: 遍歷 DOM 節點,O(1) 讀取狀態
    // ------------------------------------------------------
    svgPathList.value.forEach((path) => {
      // 核心優化:直接通過 ID 從 Map 中取值,無需循環查找
      const status = statusMap.get(path.id);

      // ----------------------------------------------------
      // 步驟 3: 響應式更新視圖
      // ----------------------------------------------------
      if (status === "2") { 
        // 🌸 狀態 2:有人 (粉色)
        path.style.fill = "#F98DB1";
        path.style.transition = "fill 0.5s ease"; // 加上過渡,體驗瞬間提升
      } else {
        // 💧 其他狀態:無人 (青色)
        // 注意:這裏包含了 status 為 undefined 的情況(即數據中未包含該坑位),默認置為無人
        path.style.fill = "#0FE7FC";
      }
    });
  }
};

此優化方案確保了無論有多少個坑位,更新邏輯都像“點名”一樣快,不會隨着數據量增長而卡頓,更加一般化。
説明一下哦,這個需要改變顏色的狀態這裏我跟後端約定的是2,其他狀態默認是無人。


✨ 最終效果

1.識別智能廁位控制枱打印結果如圖!(https://img2024.cnblogs.com/blog/2819675/202601/2819675-20260115135236034-274228499.png)
2.初始公測圖片!(https://img2024.cnblogs.com/blog/2819675/202601/2819675-20260115135527559-1121130856.png)
3.有人進入後圖片!(https://img2024.cnblogs.com/blog/2819675/202601/2819675-20260115140745975-1728210400.png)

通過這套方案,我們實現了一個有生命力的公廁平面圖:

  • 默認狀態:所有廁位靜謐地呈現為 科技青(#0FE7FC)
  • 有人進入:傳感器觸發,WebSocket 消息瞬間抵達,對應的廁位平滑過渡為 醒目粉(#F98DB1)
  • 無損縮放:無論是在 80 寸的指揮中心大屏,還是在 手機端查看,線條永遠清晰,體驗拉滿。

🚀 總結

在 Vue 3 項目中,將 SVG 視為“可編程代碼”而非“靜態圖片”,能極大地拓展前端可視化的邊界,svg圖片跟切圖人員約定號相關圖形編號規則後可以上傳後台,前端根據svg的url獲取,這個項目涉及到100多個公廁,所以涉及到的編號規則一定要統一準確。

這種 “SVG DOM + 數據驅動” 的模式,不僅完美解決了智慧公廁的痛點,還可以廣泛“複製粘貼”到其他領域:

  • 🚗 智慧停車:車位佔用監控
  • 🏭 工業互聯網:流水線設備故障紅綠燈
  • 🏥 智慧醫療:病房牀位管理系統

掌握這一招,讓你的可視化大屏瞬間“活”起來!拒絕死板的切圖,擁抱靈動的 SVG 吧!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.