高效瀏覽器標簽頁管理:Chrome擴展開發完全指南

Hi,我是前端人類學(之前叫布蘭妮甜)!
在信息過載的時代,瀏覽器標簽頁管理已成為提高工作效率的關鍵技能。本文將介紹如何開發一個功能完整的Chrome擴展,幫助用戶高效管理瀏覽器標簽頁,并探討其實現原理和技術細節。


文章目錄

    • 一、為什么需要標簽頁管理工具?
    • 二、擴展功能概述
    • 三、技術實現詳解
      • 1. 分析需求
      • 2. 實現方案
      • 3. 清單文件(manifest.json)配置
      • 4. 彈出窗口腳本 (popup.js)
      • 5. 背景腳本 (background.js)
      • 6. 圖標文件
    • 四、技術實現詳解
      • 1. 用戶界面設計與實現
      • 2. 核心功能JavaScript實現
    • 五、安裝和使用說明
    • 六、功能開發技巧與最佳實踐
      • 1. 異步處理
      • 2. 錯誤處理
      • 3. 內存管理


一、為什么需要標簽頁管理工具?

現代用戶常常同時打開數十個標簽頁,導致:

  • 瀏覽器性能下降,內存占用激增
  • 難以快速找到所需標簽頁
  • 重要工作內容容易被意外關閉
  • 分散注意力,降低工作效率

一個優秀的標簽頁管理擴展可以解決這些問題,讓瀏覽體驗更加高效和愉悅。

二、擴展功能概述

我們開發的標簽頁管理器具有以下核心功能:

  1. 實時標簽頁列表:顯示當前窗口所有標簽頁的標題和圖標
  2. 智能搜索過濾:快速定位特定標簽頁
  3. 批量操作:選擇多個標簽頁進行統一管理
  4. 標簽頁組保存:將相關標簽頁保存為組,方便以后使用
  5. 直觀用戶界面:簡潔設計,流暢交互體驗

三、技術實現詳解

1. 分析需求

一個完整的Chrome擴展需要:

  1. 清單文件(manifest.json) - 定義擴展的基本信息和權限
  2. 彈出界面(popup.html) - 用戶點擊擴展圖標時顯示的界面
  3. 背景腳本(background.js) - 處理擴展的后臺邏輯
  4. 內容腳本(content.js) - 可選,用于與網頁交互

2. 實現方案

下面是完整的Chrome擴展實現代碼,包括所有必要文件:

<!-- 這里是popup.html的完整代碼 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>標簽頁管理器</title><style>* {box-sizing: border-box;margin: 0;padding: 0;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {width: 400px;height: 500px;background: linear-gradient(135deg, #6e8efb, #a777e3);color: #333;overflow: hidden;}.container {display: flex;flex-direction: column;height: 100%;background: rgba(255, 255, 255, 0.95);box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);}.header {padding: 20px;background: #6e8efb;color: white;text-align: center;border-bottom: 1px solid #ddd;}.header h1 {font-size: 20px;font-weight: 600;margin-bottom: 5px;}.header p {font-size: 12px;opacity: 0.9;}.search-box {padding: 15px;border-bottom: 1px solid #eee;}.search-box input {width: 100%;padding: 10px 15px;border: 1px solid #ddd;border-radius: 25px;font-size: 14px;outline: none;transition: all 0.3s;}.search-box input:focus {border-color: #6e8efb;box-shadow: 0 0 0 2px rgba(110, 142, 251, 0.2);}.tabs-container {flex: 1;overflow-y: auto;padding: 10px;}.tab-item {display: flex;align-items: center;padding: 12px 15px;margin-bottom: 8px;background: white;border-radius: 8px;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);cursor: pointer;transition: all 0.2s;border-left: 4px solid #6e8efb;}.tab-item:hover {transform: translateY(-2px);box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);border-left: 4px solid #a777e3;}.tab-item.selected {background: #f0f4ff;border-left: 4px solid #ff7c7c;}.tab-favicon {width: 16px;height: 16px;margin-right: 10px;flex-shrink: 0;}.tab-title {flex: 1;font-size: 14px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}.tab-close {color: #999;padding: 5px;border-radius: 50%;cursor: pointer;transition: all 0.2s;}.tab-close:hover {background: #ff7c7c;color: white;}.actions {display: flex;padding: 15px;border-top: 1px solid #eee;gap: 10px;}.btn {flex: 1;padding: 10px;border: none;border-radius: 5px;cursor: pointer;font-weight: 600;transition: all 0.2s;}.btn-primary {background: #6e8efb;color: white;}.btn-primary:hover {background: #5a7ce2;}.btn-danger {background: #ff7c7c;color: white;}.btn-danger:hover {background: #ff6464;}.btn-secondary {background: #f0f0f0;color: #333;}.btn-secondary:hover {background: #e0e0e0;}.empty-state {text-align: center;padding: 40px 20px;color: #999;}.empty-state i {font-size: 40px;margin-bottom: 15px;display: block;color: #ccc;}.tab-count {background: #ff7c7c;color: white;border-radius: 50%;width: 20px;height: 20px;display: inline-flex;align-items: center;justify-content: center;font-size: 12px;margin-left: 5px;}</style>
</head>
<body><div class="container"><div class="header"><h1>標簽頁管理器</h1><p>高效管理您的瀏覽器標簽頁</p></div><div class="search-box"><input type="text" id="searchInput" placeholder="搜索標簽頁..."></div><div class="tabs-container" id="tabsList"><!-- 標簽頁將動態加載到這里 --><div class="empty-state"><p>正在加載標簽頁...</p></div></div><div class="actions"><button class="btn btn-primary" id="saveGroup">保存組</button><button class="btn btn-danger" id="closeSelected">關閉選中</button><button class="btn btn-secondary" id="refresh">刷新</button></div></div><script src="popup.js"></script>
</body>
</html>

3. 清單文件(manifest.json)配置

清單文件是Chrome擴展的"身份證",定義了擴展的基本信息和權限需求:

{"manifest_version": 3,"name": "標簽頁管理器","version": "1.0","description": "高效管理瀏覽器標簽頁,提高工作效率","permissions": ["tabs","storage"],"action": {"default_popup": "popup.html","default_icon": {"16": "icons/icon16.png","32": "icons/icon32.png","48": "icons/icon48.png","128": "icons/icon128.png"}},"icons": {"16": "icons/icon16.png","32": "icons/icon32.png","48": "icons/icon48.png","128": "icons/icon128.png"},"background": {"service_worker": "background.js"}
}

關鍵配置說明:

  • manifest_version: 3 使用最新Manifest V3規范,更安全高效
  • tabs 權限允許擴展訪問和操作瀏覽器標簽頁
  • storage 權限用于保存用戶創建的標簽頁組
  • action 定義擴展圖標和彈出窗口

4. 彈出窗口腳本 (popup.js)

document.addEventListener('DOMContentLoaded', function() {const tabsList = document.getElementById('tabsList')const searchInput = document.getElementById('searchInput')const saveGroupBtn = document.getElementById('saveGroup')const closeSelectedBtn = document.getElementById('closeSelected')const refreshBtn = document.getElementById('refresh')let currentTabs = []let selectedTabs = new Set()// 加載標簽頁列表function loadTabs() {chrome.tabs.query({ currentWindow: true }, function(tabs) {currentTabs = tabsrenderTabs(tabs)})}// 渲染標簽頁列表function renderTabs(tabs) {if (tabs.length === 0) {tabsList.innerHTML = `<div class="empty-state"><p>沒有打開的標簽頁</p></div>`return}tabsList.innerHTML = ''tabs.forEach(tab => {const isSelected = selectedTabs.has(tab.id)const tabItem = document.createElement('div')tabItem.className = `tab-item ${isSelected ? 'selected' : ''}`tabItem.dataset.tabId = tab.idtabItem.innerHTML = `<img class="tab-favicon" src="${tab.favIconUrl || 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzU1NSI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjUiLz48L3N2Zz4='}"><div class="tab-title">${tab.title}</div><div class="tab-close">?</div>`// 選擇標簽頁tabItem.addEventListener('click', function(e) {if (e.target.classList.contains('tab-close')) returnconst tabId = parseInt(this.dataset.tabId)if (selectedTabs.has(tabId)) {selectedTabs.delete(tabId)this.classList.remove('selected')} else {selectedTabs.add(tabId)this.classList.add('selected')}updateButtonStates()})// 關閉單個標簽頁const closeBtn = tabItem.querySelector('.tab-close')closeBtn.addEventListener('click', function(e) {e.stopPropagation()const tabId = parseInt(tabItem.dataset.tabId)chrome.tabs.remove(tabId, function() {loadTabs()selectedTabs.delete(tabId)updateButtonStates()})})tabsList.appendChild(tabItem)})updateButtonStates()}// 更新按鈕狀態function updateButtonStates() {closeSelectedBtn.innerHTML = selectedTabs.size > 0 ? `關閉選中 <span class="tab-count">${selectedTabs.size}</span>` : '關閉選中'closeSelectedBtn.disabled = selectedTabs.size === 0}// 搜索標簽頁searchInput.addEventListener('input', function() {const searchTerm = this.value.toLowerCase()if (!searchTerm) {renderTabs(currentTabs)return}const filteredTabs = currentTabs.filter(tab => tab.title.toLowerCase().includes(searchTerm) || tab.url.toLowerCase().includes(searchTerm))renderTabs(filteredTabs)})// 保存標簽頁組saveGroupBtn.addEventListener('click', function() {if (selectedTabs.size === 0) {alert('請先選擇要保存的標簽頁')return}const groupName = prompt('請輸入標簽頁組的名稱:')if (!groupName) returnconst tabUrls = currentTabs.filter(tab => selectedTabs.has(tab.id)).map(tab => tab.url)chrome.storage.local.get({ savedGroups: [] }, function(result) {const savedGroups = result.savedGroupssavedGroups.push({name: groupName,urls: tabUrls,date: new Date().toISOString()})chrome.storage.local.set({ savedGroups: savedGroups }, function() {alert(`已保存標簽頁組: ${groupName}`)selectedTabs.clear()renderTabs(currentTabs)})})})// 關閉選中的標簽頁closeSelectedBtn.addEventListener('click', function() {if (selectedTabs.size === 0) returnif (confirm(`確定要關閉 ${selectedTabs.size} 個標簽頁嗎?`)) {chrome.tabs.remove(Array.from(selectedTabs), function() {selectedTabs.clear()loadTabs()})}})// 刷新列表refreshBtn.addEventListener('click', loadTabs)// 初始化加載loadTabs()
})

5. 背景腳本 (background.js)

// 監聽擴展安裝事件
chrome.runtime.onInstalled.addListener(() => {console.log('標簽頁管理器擴展已安裝');
});// 監聽鍵盤快捷鍵
chrome.commands.onCommand.addListener((command) => {if (command === 'open-tab-manager') {// 打開彈出窗口chrome.action.openPopup();}
});// 監聽來自彈出窗口的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {if (request.action === 'getTabs') {chrome.tabs.query({currentWindow: true}, (tabs) => {sendResponse({tabs: tabs});});return true; // 保持消息通道開放用于異步響應}
});

6. 圖標文件

創建以下尺寸的圖標文件并放在icons文件夾中:

  • icon16.png (16x16像素)
  • icon32.png (32x32像素)
  • icon48.png (48x48像素)
  • icon128.png (128x128像素)

可以使用簡單的設計工具或在線圖標生成器創建這些圖標。

四、技術實現詳解

1. 用戶界面設計與實現

視覺設計

  • 漸變背景創造深度感
  • 圓角卡片式布局符合現代UI趨勢
  • 精心設計的交互反饋(懸停效果、選擇狀態)
  • 響應式設計適應不同尺寸

界面結構

<div class="container"><div class="header">...</div><div class="search-box">...</div><div class="tabs-container">...</div><div class="actions">...</div>
</div>

CSS關鍵技術

  • Flexbox布局確保元素靈活排列
  • CSS過渡動畫提升用戶體驗
  • 白色半透明背景保持內容可讀性
  • 精心設計的色彩方案提供視覺層次

2. 核心功能JavaScript實現

標簽頁加載與渲染

function loadTabs() {chrome.tabs.query({currentWindow: true}, function(tabs) {currentTabs = tabs;renderTabs(tabs);});
}

使用Chrome提供的tabs.query API獲取當前窗口所有標簽頁信息,然后動態生成界面元素。

搜索過濾功能

searchInput.addEventListener('input', function() {const searchTerm = this.value.toLowerCase();const filteredTabs = currentTabs.filter(tab => tab.title.toLowerCase().includes(searchTerm) || tab.url.toLowerCase().includes(searchTerm));renderTabs(filteredTabs);
});

通過監聽輸入框的輸入事件,實時過濾顯示匹配的標簽頁。

標簽頁組保存

chrome.storage.local.get({savedGroups: []}, function(result) {const savedGroups = result.savedGroups;savedGroups.push({name: groupName,urls: tabUrls,date: new Date().toISOString()});chrome.storage.local.set({savedGroups: savedGroups}, function() {alert(`已保存標簽頁組: ${groupName}`);});
});

使用Chrome的存儲API將用戶選擇的標簽頁組保存到本地存儲中。

五、安裝和使用說明

  1. 創建以下文件結構:

    tab-manager-extension/
    ├── manifest.json
    ├── popup.html
    ├── popup.js
    ├── background.js
    └── icons/├── icon16.png├── icon32.png├── icon48.png└── icon128.png
    
  2. 在Chrome瀏覽器中打開擴展管理頁面(chrome://extensions/)

  3. 開啟"開發者模式"

  4. 點擊"加載已解壓的擴展程序",選擇包含上述文件的文件夾

  5. 擴展將出現在瀏覽器右上角,點擊圖標即可使用

六、功能開發技巧與最佳實踐

1. 異步處理

Chrome擴展API大量使用回調函數,建議使用Promise包裝以提高代碼可讀性:

function getCurrentTabs() {return new Promise((resolve) => {chrome.tabs.query({currentWindow: true}, (tabs) => {resolve(tabs);});});
}

2. 錯誤處理

始終添加適當的錯誤處理,提高擴展的穩定性:

try {const tabs = await getCurrentTabs();renderTabs(tabs);
} catch (error) {console.error('獲取標簽頁失敗:', error);showErrorMessage('無法加載標簽頁,請重試');
}

3. 內存管理

及時清理不再需要的監聽器和引用,防止內存泄漏:

// 添加事件監聽器時使用命名函數便于移除
element.addEventListener('click', handleClick);// 在適當的時候移除
element.removeEventListener('click', handleClick);

開發Chrome擴展是提升瀏覽器體驗的強大方式。本文介紹的標簽頁管理器不僅解決了實際使用中的痛點,還展示了現代Web開發的最新技術和最佳實踐。
無論是作為生產力工具還是學習項目,這個標簽頁管理器都提供了一個完整的起點,可以根據需要進一步擴展功能或定制樣式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/97650.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97650.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97650.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從 WPF 到 Avalonia 的遷移系列實戰篇3:ResourceDictionary資源與樣式的差異與遷移技巧

從 WPF 到 Avalonia 的遷移系列實戰篇3:ResourceDictionary資源與樣式的差異與遷移技巧 我的GitHub倉庫Avalonia學習項目包含完整的Avalonia實踐案例與代碼對比。 我的gitcode倉庫是Avalonia學習項目。 文中主要示例代碼均可在倉庫中查看&#xff0c;涵蓋核心功能實現與優化方案…

基于Springboot的音樂媒體播放及周邊產品運營平臺(有報告)。Javaee項目,springboot項目。

演示視頻&#xff1a; 基于Springboot的音樂媒體播放及周邊產品運營平臺&#xff08;有報告&#xff09;。Javaee項目&#xff0c;springboot項目。項目介紹&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09…

【項目思維】嵌入式產業鏈與技術生態

這篇文章深入解析嵌入式產業鏈與技術生態上下游關系&#xff0c;輔助建立嵌入式工程師職業發展認知。嵌入式行業并不是“寫單片機程序”那么簡單&#xff0c;而是一個 從芯片設計到系統集成再到最終產品落地 的復雜生態鏈。理解上下游價值鏈&#xff0c;有助于你成為系統型工程…

機器學習(講解)

一、引言&#xff1a;什么是監督學習&#xff1f;監督學習&#xff08;Supervised Learning&#xff09;是機器學習中最基礎且應用最廣泛的范式之一。其核心思想是利用已標記的數據&#xff08;即輸入-輸出對&#xff09;訓練模型&#xff0c;使其能夠對新的、未標記的數據進行…

使用 Bright Data Web Scraper API + Python 高效抓取 Glassdoor 數據:從配置到結構化輸出全流程實戰

使用 Bright Data Web Scraper API Python 高效抓取 Glassdoor 數據&#xff1a;從配置到結構化輸出全流程實戰 摘要 本文詳細介紹了如何使用 Bright Data 的 Web Scraper API 搭配 Python&#xff0c;實現對 Glassdoor 平臺信息的高效抓取。通過 API 請求構建器、反爬機制集成…

Burgan Bank Türkiye 如何借助 Elastic 改造可觀測性和安全性

作者&#xff1a;來自 Elastic Jon Ashley, Ido Friedman, Burak Dz Burgan Bank Trkiye Burgan Bank K.P.S.C. 是科威特項目公司 (KIPCO) 集團的子公司&#xff0c;成立于 1977 年&#xff0c;是中東和北非 (MENA) 地區最大的控股集團和重要銀行集團之一。 該銀行作為客戶的解…

LeetCode 165. 比較版本號 - 優雅Java解決方案

文章目錄LeetCode 165. 比較版本號 - 優雅Java解決方案題目描述示例分析示例 1示例 2示例 3算法思路Java實現方案方案一&#xff1a;雙指針法&#xff08;推薦&#xff09;方案二&#xff1a;優化的單次遍歷法可視化執行過程示例&#xff1a;compareVersion("1.2", &…

基于Kubernetes StatefulSet的有狀態微服務部署與持久化存儲實踐經驗分享

基于Kubernetes StatefulSet的有狀態微服務部署與持久化存儲實踐經驗分享 在傳統微服務架構中&#xff0c;大多數服務都是無狀態的&#xff08;Stateless&#xff09;&#xff0c;可以通過 Deployment、ReplicaSet 等控制器實現水平自動擴縮容。但在生產環境中&#xff0c;仍有…

MySQL編程開發

變量系統變量&#xff1a;MySQL內置變量#查看所有系統變量show variables \G;#通過模糊查詢篩選變量show variables like “%path%”;全局變量&#xff1a;在所有終端中都生效&#xff1b;會話變量&#xff1a;在當前會話&#xff08;本次登錄&#xff09;&#xff1b;#可以通過…

20250830_Oracle 19c CDB+PDB(QMS)默認表空間、臨時表空間、歸檔日志、閃回恢復區巡檢手冊

PDB 關業務,CDB 管底層;每天緊盯 PDB,必要時看 CDB。 一、CDB 與 PDB 的關系 Oracle 12c 以后引入 多租戶架構(Multitenant),分成兩類容器: 層級 名稱 作用 存儲內容 典型操作 CDB CDB$ROOT(容器數據庫) 數據庫實例的根容器 Oracle 元數據、系統表字典、公共用戶、PDB…

什么是MIPS架構?RISC-V架構?有什么區別?【超詳細初學者教程】

什么是MIPS架構&#xff1f;RISC-V架構&#xff1f;有什么區別&#xff1f;【超詳細初學者教程】 關鍵詞&#xff1a;MIPS架構&#xff0c;RISC-V架構&#xff0c;精簡指令集RISC&#xff0c;嵌入式系統&#xff0c;CPU架構對比&#xff0c;指令集架構&#xff0c;開源處理器&…

IDEA Spring屬性注解依賴注入的警告 Field injection is not recommended 異常解決方案

一、異常錯誤 在使用 IntelliJ IDEA 進行 Spring 開發時&#xff0c;當使用 Autowired 注解直接在字段上進行依賴注入時&#xff0c;IDE 會顯示黃色警告&#xff1a; Field injection is not recommended這個警告出現在以下代碼模式中&#xff1a; Service public class UserSe…

智能核心:機器人芯片的科技革新與未來挑戰

在人工智能與機器人技術深度融合的今天&#xff0c;機器人芯片作為驅動智能機器的“大腦”&#xff0c;正成為科技競爭的戰略制高點。這一微小卻至關重要的硬件&#xff0c;決定了機器人的計算能力、響應速度與智能水平&#xff0c;是機器人從“自動化”邁向“自主化”的關鍵所…

經典掃雷游戲實現:從零構建HTML5掃雷游戲

一、引言 掃雷是一款經典的單人益智游戲&#xff0c;起源于20世紀60年代&#xff0c;并在90年代隨著Windows操作系統的普及而風靡全球。本文將詳細介紹如何使用現代網頁技術&#xff08;HTML、CSS和JavaScript&#xff09;從零開始構建一個功能完整的掃雷游戲。我們將涵蓋游戲邏…

ccache編譯加速配置

ccache 介紹 ccache(“compiler cache”的縮寫)是一個編譯器緩存,該工具會高速緩存編譯生成的信息,并在編譯的特定部分使用高速緩存的信息, 比如頭文件,這樣就節省了通常使用 cpp 解析這些信息所需要的時間。 github :https://github.com/ccache/ccache home:https://c…

數據庫主鍵選擇策略分析

為什么不推薦使用數據庫自增主鍵&#xff1f;分庫分表問題&#xff1a;自增ID在分庫分表場景下會導致ID沖突需要額外機制(如步長設置)來保證全局唯一&#xff0c;增加系統復雜度安全性問題&#xff1a;自增ID容易暴露業務量(如訂單號連續)可能被惡意爬取數據分布式系統限制&…

線性代數理論——狀態空間的相關概念以及由系統的輸入輸出導出狀態空間描述

線性代數理論——狀態空間 狀態&#xff1a;動態系統的狀態就是指系統的過去、現在、將來的運動狀況&#xff0c;精確的說就是狀態需要一組必要而充分的數據來表明。 狀態變量&#xff1a;可以表達系統運動狀態的變量都是狀態變量。 狀態變量組&#xff1a;可以完全表征系統在時…

【GaussDB】排查應用高可用切換出現數據庫整體卡頓及報錯自治事務無法創建的問題

【GaussDB】排查應用高可用切換出現數據庫整體卡頓及報錯自治事務無法創建的問題 背景 某客戶在做應用程序的高可用切換測試&#xff0c;在應用程序中&#xff0c;收到了來自數據庫的報錯&#xff0c;不能創建自治事務 ERROR: autonomous transaction failed to create auton…

shell腳本第五階段---shell函數與正則表達式

學習目標掌握case語句的基本語法結構掌握函數的定義以及調用掌握常用的正則表達式元字符含義一、case語句case語句為多選擇語句。可以用case語句匹配一個值與一個模式&#xff0c;如果匹配成功&#xff0c;執行相匹配的命令。case var in 定義變量&#xff1b;var代表變量名…

164.在 Vue3 中使用 OpenLayers 加載 Esri 地圖(多種形式)

適配&#xff1a;Vue 3 Vite TypeScript&#xff08;也兼容 JS&#xff09; 地圖引擎&#xff1a;OpenLayers v10 目標&#xff1a;一次性學會 多種 Esri 底圖加載方式、注記疊加、動態切換、令牌&#xff08;Token&#xff09;鑒權、常見坑位排查。一、效果預覽二、為什么選…