1. 项目概述
在鸿蒙PC应用开发中,Electron作为跨平台桌面应用开发框架,为开发者提供了强大的能力。这次我们要实现的是一个高度可定制的tab标签页组件,它能够像浏览器标签页一样管理多个内容页面,同时完美适配鸿蒙PC系统的UI风格和交互逻辑。
这个组件最核心的价值在于解决了多文档界面(MDI)应用中的页面管理难题。传统桌面应用往往通过多个窗口或iframe来实现多页面展示,但这会导致资源占用高、状态管理复杂等问题。我们的tab组件采用单窗口多视图架构,通过动态加载和卸载内容区域,既保持了用户体验的一致性,又优化了内存使用效率。
2. 技术选型与架构设计
2.1 为什么选择Electron
Electron结合了Chromium和Node.js,让我们能够使用Web技术开发原生桌面应用。对于鸿蒙PC项目来说,Electron提供了几个关键优势:
- 跨平台一致性:一套代码可以运行在Windows、macOS和鸿蒙PC上,UI表现基本一致
- 开发效率:基于HTML/CSS/JS的技术栈,前端开发者可以快速上手
- 生态丰富:npm海量模块可以直接使用,特别是UI组件库
- 原生能力:通过Node.js集成可以调用系统级API
2.2 组件架构设计
我们的tab标签页组件采用分层架构:
┌─────────────────────────────────┐ │ Main Process │ │ ┌───────────────────────────┐ │ │ │ BrowserWindow │ │ │ └───────────────────────────┘ │ └─────────────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ Renderer Process │ │ ┌─────────────┐ ┌─────────────┐│ │ │ Tab Bar │ │ Content Area││ │ └─────────────┘ └─────────────┘│ └─────────────────────────────────┘主进程负责创建和管理BrowserWindow,渲染进程则处理UI交互和内容展示。这种架构既保证了性能,又实现了良好的隔离性。
3. 核心功能实现
3.1 基础Tab组件实现
首先创建一个基础的Tab组件类:
class TabComponent { constructor(options) { this.tabs = []; this.activeTab = null; this.container = document.getElementById(options.containerId); this.contentArea = document.getElementById(options.contentId); this.initStyles(); this.setupEventListeners(); } initStyles() { const style = document.createElement('style'); style.textContent = ` .tab-bar { display: flex; background: #f5f5f5; } .tab { padding: 8px 16px; cursor: pointer; border-right: 1px solid #ddd; } .tab.active { background: #fff; border-bottom: 2px solid #1890ff; } .tab-content { display: none; height: calc(100% - 40px); } .tab-content.active { display: block; } `; document.head.appendChild(style); } setupEventListeners() { this.container.addEventListener('click', (e) => { if (e.target.classList.contains('tab')) { const tabId = e.target.dataset.tabId; this.activateTab(tabId); } }); } addTab(title, content) { const tabId = `tab-${Date.now()}`; const tabElement = document.createElement('div'); tabElement.className = 'tab'; tabElement.textContent = title; tabElement.dataset.tabId = tabId; const contentElement = document.createElement('div'); contentElement.className = 'tab-content'; contentElement.innerHTML = content; contentElement.id = `content-${tabId}`; this.container.appendChild(tabElement); this.contentArea.appendChild(contentElement); const tab = { id: tabId, title, element: tabElement, content: contentElement }; this.tabs.push(tab); if (!this.activeTab) { this.activateTab(tabId); } return tabId; } activateTab(tabId) { this.tabs.forEach(tab => { const isActive = tab.id === tabId; tab.element.classList.toggle('active', isActive); tab.content.classList.toggle('active', isActive); }); this.activeTab = this.tabs.find(tab => tab.id === tabId); } }3.2 与Electron主进程通信
为了实现更复杂的功能如新建窗口、保存状态等,我们需要与主进程通信:
// renderer.js const { ipcRenderer } = require('electron'); class EnhancedTabComponent extends TabComponent { constructor(options) { super(options); this.setupIpcListeners(); } setupIpcListeners() { ipcRenderer.on('new-tab', () => { this.addTab('新标签', '<div>新标签内容</div>'); }); ipcRenderer.on('close-tab', (_, tabId) => { this.closeTab(tabId); }); } closeTab(tabId) { const tabIndex = this.tabs.findIndex(tab => tab.id === tabId); if (tabIndex === -1) return; const tab = this.tabs[tabIndex]; this.container.removeChild(tab.element); this.contentArea.removeChild(tab.content); this.tabs.splice(tabIndex, 1); if (this.activeTab?.id === tabId) { this.activeTab = this.tabs[Math.min(tabIndex, this.tabs.length - 1)]; if (this.activeTab) { this.activateTab(this.activeTab.id); } } } }3.3 鸿蒙风格适配
为了让组件更符合鸿蒙PC的设计语言,我们需要调整样式和交互:
/* harmony-os-theme.css */ .tab-bar { background: linear-gradient(to right, #f7f7f7, #eaeaea); border-radius: 8px 8px 0 0; padding: 0 10px; height: 42px; } .tab { padding: 10px 20px; margin: 5px 2px 0; border-radius: 8px 8px 0 0; background: rgba(255,255,255,0.7); transition: all 0.3s ease; position: relative; overflow: hidden; } .tab.active { background: white; box-shadow: 0 -2px 10px rgba(0,0,0,0.05); border-bottom: 3px solid #0066ff; } .tab:after { content: ''; position: absolute; bottom: 0; left: 50%; width: 0; height: 2px; background: #0066ff; transition: all 0.3s ease; } .tab:hover:after { left: 0; width: 100%; }4. 高级功能实现
4.1 拖拽排序功能
实现标签页可拖拽排序能极大提升用户体验:
class DraggableTabComponent extends EnhancedTabComponent { constructor(options) { super(options); this.setupDragAndDrop(); } setupDragAndDrop() { this.container.addEventListener('mousedown', (e) => { const tab = e.target.closest('.tab'); if (!tab) return; const tabId = tab.dataset.tabId; const tabIndex = this.tabs.findIndex(t => t.id === tabId); const startX = e.clientX; const startLeft = tab.offsetLeft; const tabWidth = tab.offsetWidth; const moveHandler = (moveEvent) => { const deltaX = moveEvent.clientX - startX; tab.style.position = 'relative'; tab.style.left = `${deltaX}px`; tab.style.zIndex = '100'; // 计算应该交换的位置 const currentLeft = startLeft + deltaX; const newIndex = Math.max(0, Math.min( this.tabs.length - 1, Math.round((currentLeft + tabWidth/2) / tabWidth) - 1 )); if (newIndex !== tabIndex) { // 重新排序DOM元素 const tabs = Array.from(this.container.children); if (newIndex > tabIndex) { tabs[tabIndex].before(tabs[newIndex]); } else { tabs[tabIndex].after(tabs[newIndex]); } // 更新tabs数组 const [movedTab] = this.tabs.splice(tabIndex, 1); this.tabs.splice(newIndex, 0, movedTab); } }; const upHandler = () => { tab.style.position = ''; tab.style.left = ''; tab.style.zIndex = ''; document.removeEventListener('mousemove', moveHandler); document.removeEventListener('mouseup', upHandler); }; document.addEventListener('mousemove', moveHandler); document.addEventListener('mouseup', upHandler); }); } }4.2 状态持久化
为了保证用户体验的连续性,我们需要保存和恢复标签页状态:
class PersistentTabComponent extends DraggableTabComponent { constructor(options) { super(options); this.storageKey = options.storageKey || 'tab-state'; this.restoreState(); window.addEventListener('beforeunload', () => { this.saveState(); }); } saveState() { const state = { tabs: this.tabs.map(tab => ({ id: tab.id, title: tab.title, content: tab.content.innerHTML })), activeTab: this.activeTab?.id }; localStorage.setItem(this.storageKey, JSON.stringify(state)); } restoreState() { const savedState = localStorage.getItem(this.storageKey); if (!savedState) return; try { const state = JSON.parse(savedState); state.tabs.forEach(tab => { this.addTab(tab.title, tab.content); }); if (state.activeTab) { this.activateTab(state.activeTab); } } catch (e) { console.error('Failed to restore tab state:', e); } } }4.3 性能优化
当标签页数量较多时,我们需要考虑性能优化:
class OptimizedTabComponent extends PersistentTabComponent { constructor(options) { super(options); this.maxLoadedTabs = options.maxLoadedTabs || 5; this.unloadedTabs = new Set(); } activateTab(tabId) { const tab = this.tabs.find(t => t.id === tabId); if (!tab) return; // 加载当前激活的标签页内容 if (this.unloadedTabs.has(tabId)) { this.loadTabContent(tab); this.unloadedTabs.delete(tabId); } super.activateTab(tabId); // 卸载非活跃标签页内容以节省内存 this.manageTabLoadState(); } loadTabContent(tab) { // 实际项目中这里可以从缓存或服务器加载内容 tab.content.style.display = 'block'; } unloadTabContent(tab) { // 保存内容到缓存 tab.content.style.display = 'none'; this.unloadedTabs.add(tab.id); } manageTabLoadState() { const activeAndRecent = [ this.activeTab, ...this.tabs .filter(t => t.id !== this.activeTab?.id) .sort((a, b) => b.lastActivated - a.lastActivated) .slice(0, this.maxLoadedTabs - 1) ].filter(Boolean); this.tabs.forEach(tab => { if (!activeAndRecent.includes(tab) && !this.unloadedTabs.has(tab.id)) { this.unloadTabContent(tab); } }); } }5. 鸿蒙PC特定适配
5.1 系统菜单集成
在鸿蒙PC上,我们需要将标签页操作集成到系统菜单中:
// main.js const { app, BrowserWindow, Menu } = require('electron'); function createWindow() { const win = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: true, contextIsolation: false } }); const template = [ { label: '标签页', submenu: [ { label: '新建标签', accelerator: 'CmdOrCtrl+T', click: () => win.webContents.send('new-tab') }, { label: '关闭当前标签', accelerator: 'CmdOrCtrl+W', click: () => win.webContents.send('close-current-tab') }, { type: 'separator' }, { label: '切换到下一个标签', accelerator: 'CmdOrCtrl+Tab', click: () => win.webContents.send('next-tab') }, { label: '切换到上一个标签', accelerator: 'CmdOrCtrl+Shift+Tab', click: () => win.webContents.send('previous-tab') } ] } ]; const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); win.loadFile('index.html'); } app.whenReady().then(createWindow);5.2 鸿蒙主题适配
根据鸿蒙PC的主题设置自动切换组件样式:
class HarmonyThemeTabComponent extends OptimizedTabComponent { constructor(options) { super(options); this.setupThemeListener(); } setupThemeListener() { // 监听系统主题变化 ipcRenderer.on('theme-changed', (_, theme) => { this.applyTheme(theme); }); // 初始主题 this.applyTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); } applyTheme(theme) { const isDark = theme === 'dark'; this.container.style.backgroundColor = isDark ? '#2a2a2a' : '#f5f5f5'; const tabs = this.container.querySelectorAll('.tab'); tabs.forEach(tab => { tab.style.color = isDark ? '#e6e6e6' : '#333'; tab.style.backgroundColor = isDark ? '#3a3a3a' : 'rgba(255,255,255,0.7)'; if (tab.classList.contains('active')) { tab.style.backgroundColor = isDark ? '#1a1a1a' : '#fff'; tab.style.borderBottomColor = isDark ? '#4d8bf0' : '#1890ff'; } }); this.contentArea.style.backgroundColor = isDark ? '#1a1a1a' : '#fff'; this.contentArea.style.color = isDark ? '#e6e6e6' : '#333'; } }6. 测试与调试
6.1 单元测试
为关键功能编写单元测试确保稳定性:
// tab-component.test.js const TabComponent = require('./tab-component'); describe('TabComponent', () => { let tabComponent; beforeEach(() => { document.body.innerHTML = ` <div id="tab-container"></div> <div id="content-area"></div> `; tabComponent = new TabComponent({ containerId: 'tab-container', contentId: 'content-area' }); }); test('should add new tab', () => { const tabId = tabComponent.addTab('Test', '<div>Test Content</div>'); expect(tabId).toBeDefined(); expect(tabComponent.tabs.length).toBe(1); expect(document.querySelectorAll('.tab').length).toBe(1); expect(document.querySelectorAll('.tab-content').length).toBe(1); }); test('should activate tab', () => { const tabId = tabComponent.addTab('Test', '<div>Test Content</div>'); tabComponent.activateTab(tabId); const tab = document.querySelector('.tab'); const content = document.querySelector('.tab-content'); expect(tab.classList.contains('active')).toBe(true); expect(content.classList.contains('active')).toBe(true); expect(tabComponent.activeTab.id).toBe(tabId); }); test('should close tab', () => { const tabId = tabComponent.addTab('Test', '<div>Test Content</div>'); tabComponent.closeTab(tabId); expect(tabComponent.tabs.length).toBe(0); expect(document.querySelectorAll('.tab').length).toBe(0); expect(document.querySelectorAll('.tab-content').length).toBe(0); expect(tabComponent.activeTab).toBeNull(); }); });6.2 性能测试
使用Chrome DevTools进行性能分析:
- 打开开发者工具(Command+Option+I/Ctrl+Shift+I)
- 切换到Performance标签页
- 点击Record按钮开始记录
- 进行典型操作(添加/切换/关闭标签页)
- 停止记录并分析结果
重点关注:
- 脚本执行时间
- 布局重绘次数
- 内存使用情况
- 事件处理耗时
6.3 常见问题排查
问题1:标签页内容闪烁
- 原因:通常是由于CSS加载顺序或样式冲突导致
- 解决方案:确保样式表在组件初始化前加载完成,使用
requestAnimationFrame优化渲染
问题2:内存泄漏
- 症状:长时间使用后内存占用持续增长
- 排查:使用Chrome Memory工具拍摄堆快照,检查DOM节点和事件监听器是否被正确清理
- 修复:在
closeTab方法中确保移除所有相关事件监听器
问题3:拖拽卡顿
- 原因:频繁的DOM操作导致重绘
- 优化:使用
transform代替left/top定位,减少布局抖动
7. 部署与打包
7.1 打包配置
在package.json中添加Electron打包配置:
{ "name": "harmony-tab-component", "version": "1.0.0", "main": "main.js", "scripts": { "start": "electron .", "pack": "electron-builder --dir", "dist": "electron-builder", "build:harmony": "electron-builder --config ./builder-hm.json" }, "dependencies": { "electron": "^25.0.0" }, "devDependencies": { "electron-builder": "^24.0.0" } }鸿蒙PC特定的打包配置(builder-hm.json):
{ "appId": "com.example.harmonytabs", "productName": "Harmony Tab组件", "directories": { "output": "dist/harmony" }, "files": [ "**/*", "!node_modules/{@types,.bin}" ], "harmony": { "target": "pc", "icon": "build/icon.icns", "category": "public.app-category.utilities" } }7.2 构建流程
- 开发环境测试:
npm start- 打包鸿蒙PC版本:
npm run build:harmony- 生成安装包后,使用鸿蒙PC的应用签名工具进行签名:
harmony-signer sign --input dist/harmony/app.dmg --output dist/harmony/app-signed.dmg7.3 持续集成
配置GitHub Actions自动化构建:
name: Build and Deploy on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install dependencies run: npm install - name: Build for Harmony PC run: npm run build:harmony - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: harmony-tab-component path: dist/harmony/8. 扩展与进阶
8.1 多窗口协同
实现跨窗口的标签页拖拽功能:
class MultiWindowTabComponent extends HarmonyThemeTabComponent { constructor(options) { super(options); this.setupWindowDrag(); } setupWindowDrag() { this.container.addEventListener('dragstart', (e) => { const tab = e.target.closest('.tab'); if (!tab) return; e.dataTransfer.setData('application/x-tab-id', tab.dataset.tabId); e.dataTransfer.setData('application/x-window-id', require('electron').remote.getCurrentWindow().id); ipcRenderer.send('drag-tab-start', { tabId: tab.dataset.tabId, windowId: require('electron').remote.getCurrentWindow().id, title: tab.textContent }); }); this.container.addEventListener('dragover', (e) => { e.preventDefault(); // 显示插入位置指示器 }); this.container.addEventListener('drop', (e) => { e.preventDefault(); const tabId = e.dataTransfer.getData('application/x-tab-id'); const sourceWindowId = parseInt(e.dataTransfer.getData('application/x-window-id')); const currentWindowId = require('electron').remote.getCurrentWindow().id; if (sourceWindowId !== currentWindowId) { ipcRenderer.send('move-tab-to-window', { tabId, fromWindow: sourceWindowId, toWindow: currentWindowId, insertAt: this.calculateInsertPosition(e.clientX) }); } }); } calculateInsertPosition(x) { const tabs = Array.from(this.container.children); const containerRect = this.container.getBoundingClientRect(); const relativeX = x - containerRect.left; for (let i = 0; i < tabs.length; i++) { const tabRect = tabs[i].getBoundingClientRect(); const tabCenter = tabRect.left + tabRect.width/2 - containerRect.left; if (relativeX < tabCenter) { return i; } } return tabs.length; } }8.2 插件系统
设计可扩展的插件架构:
class PluginTabComponent extends MultiWindowTabComponent { constructor(options) { super(options); this.plugins = new Map(); this.loadPlugins(); } registerPlugin(name, plugin) { if (this.plugins.has(name)) { console.warn(`Plugin ${name} already registered`); return; } plugin.initialize(this); this.plugins.set(name, plugin); } loadPlugins() { // 从预定义路径加载插件 const pluginPaths = [ './plugins/tab-history.js', './plugins/tab-groups.js', './plugins/tab-search.js' ]; pluginPaths.forEach(path => { try { const plugin = require(path); this.registerPlugin(plugin.name, plugin); } catch (e) { console.error(`Failed to load plugin ${path}:`, e); } }); } // 插件API方法 getActiveTab() { return this.activeTab; } getTabs() { return [...this.tabs]; } addTab(title, content, options = {}) { const tabId = super.addTab(title, content); const tab = this.tabs.find(t => t.id === tabId); // 插件钩子 this.plugins.forEach(plugin => { if (plugin.onTabAdded) { plugin.onTabAdded(tab, options); } }); return tabId; } }8.3 示例插件实现
实现一个简单的标签页历史记录插件:
// plugins/tab-history.js class TabHistoryPlugin { constructor() { this.name = 'tab-history'; this.history = []; this.maxHistoryItems = 50; } initialize(tabComponent) { this.tabComponent = tabComponent; // 监听标签页激活事件 tabComponent.on('tab-activated', (tab) => { this.addToHistory(tab); }); // 添加历史记录面板UI this.setupHistoryUI(); } addToHistory(tab) { this.history.unshift({ id: tab.id, title: tab.title, timestamp: Date.now() }); if (this.history.length > this.maxHistoryItems) { this.history.pop(); } this.updateHistoryUI(); } setupHistoryUI() { this.historyPanel = document.createElement('div'); this.historyPanel.className = 'tab-history-panel'; this.historyPanel.innerHTML = ` <h3>最近访问</h3> <ul class="history-list"></ul> `; document.body.appendChild(this.historyPanel); // 添加样式 const style = document.createElement('style'); style.textContent = ` .tab-history-panel { position: fixed; right: 0; top: 0; width: 250px; height: 100%; background: #f8f8f8; box-shadow: -2px 0 10px rgba(0,0,0,0.1); padding: 10px; overflow-y: auto; } .history-list { list-style: none; padding: 0; } .history-item { padding: 8px; cursor: pointer; border-bottom: 1px solid #eee; } .history-item:hover { background: #eee; } `; document.head.appendChild(style); } updateHistoryUI() { const list = this.historyPanel.querySelector('.history-list'); list.innerHTML = this.history.map(item => ` <li class="history-item">class VirtualizedTabComponent extends PluginTabComponent { constructor(options) { super(options); this.visibleTabCount = options.visibleTabCount || 15; this.tabWidth = options.tabWidth || 120; this.scrollPosition = 0; this.setupVirtualScroll(); } setupVirtualScroll() { this.container.style.overflowX = 'auto'; this.container.style.whiteSpace = 'nowrap'; this.container.style.position = 'relative'; // 创建可视区域 this.viewport = document.createElement('div'); this.viewport.style.width = `${this.visibleTabCount * this.tabWidth}px`; this.viewport.style.overflow = 'hidden'; this.viewport.style.position = 'relative'; this.container.parentNode.insertBefore(this.viewport, this.container); this.viewport.appendChild(this.container); // 设置容器宽度 this.updateContainerWidth(); // 滚动事件 this.viewport.addEventListener('scroll', () => { this.scrollPosition = this.viewport.scrollLeft; this.renderVisibleTabs(); }); // 初始渲染 this.renderVisibleTabs(); } updateContainerWidth() { this.container.style.width = `${this.tabs.length * this.tabWidth}px`; } renderVisibleTabs() { const startIdx = Math.floor(this.scrollPosition / this.tabWidth); const endIdx = Math.min( this.tabs.length - 1, startIdx + this.visibleTabCount + 2 // 缓冲 ); // 隐藏所有标签 this.tabs.forEach(tab => { tab.element.style.display = 'none'; }); // 显示可见区域的标签 for (let i = startIdx; i <= endIdx; i++) { this.tabs[i].element.style.display = 'inline-block'; this.tabs[i].element.style.width = `${this.tabWidth}px`; } } addTab(title, content) { const tabId = super.addTab(title, content); this.updateContainerWidth(); this.renderVisibleTabs(); return tabId; } closeTab(tabId) { super.closeTab(tabId); this.updateContainerWidth(); this.renderVisibleTabs(); } }9.2 Web Workers处理复杂计算
将耗时的标签页搜索功能放到Web Worker中:
// search-worker.js self.onmessage = function(e) { const { tabs, query } = e.data; const results = tabs.filter(tab => { return tab.title.toLowerCase().includes(query.toLowerCase()) || tab.content.toLowerCase().includes(query.toLowerCase()); }).map(tab => tab.id); self.postMessage(results); }; // 在主组件中 class WorkerTabComponent extends VirtualizedTabComponent { constructor(options) { super(options); this.searchWorker = new Worker('./search-worker.js'); this.setupSearch(); } setupSearch() { this.searchWorker.onmessage = (e) => { const matchingTabIds = e.data; this.highlightMatches(matchingTabIds); }; this.searchInput = document.createElement('input'); this.searchInput.type = 'text'; this.searchInput.placeholder = '搜索标签页...'; this.searchInput.style.marginLeft = '10px'; this.searchInput.addEventListener('input', (e) => { const query = e.target.value.trim(); if (query.length > 2) { this.searchWorker.postMessage({ tabs: this.tabs.map(tab => ({ id: tab.id, title: tab.title, content: tab.content.textContent })), query }); } else { this.clearHighlights(); } }); this.container.parentNode.insertBefore(this.searchInput, this.container); } highlightMatches(tabIds) { this.clearHighlights(); tabIds.forEach(id => { const tab = this.tabs.find(t => t.id === id); if (tab) { tab.element.style.backgroundColor = '#fff3cd'; } }); } clearHighlights() { this.tabs.forEach(tab => { tab.element.style.backgroundColor = ''; }); } }9.3 内存管理优化
实现更精细的内存管理策略:
class MemoryOptimizedTabComponent extends WorkerTabComponent { constructor(options) { super(options); this.memoryMonitorInterval = setInterval(() => this.monitorMemory(), 5000); this.memoryThreshold = options.memoryThreshold || 500; // MB } monitorMemory() { const memoryInfo = process.getProcessMemoryInfo(); const usedMB = memoryInfo.privateBytes / 1024 / 1024; if (usedMB > this.memoryThreshold) { this.freeMemory(); } } freeMemory() { // 按最近最少使用顺序排序非活动标签页 const inactiveTabs = this.tabs .filter(tab => tab.id !== this.activeTab?.id) .sort((a, b) => a.lastActivated - b.lastActivated); // 卸载最老的几个标签页内容 const tabsToUnload = Math.min(3, Math.floor(inactiveTabs.length / 2)); for (let i = 0; i < tabsToUnload; i++) { this.unloadTabContent(inactiveTabs[i]); } // 建议GC运行 if (global.gc) { global.gc(); } } unloadTabContent(tab) { if (this.unloadedTabs.has(tab.id)) return; // 保存内容状态 tab.savedState = { scrollPosition: tab.content.scrollTop, formData: this.collectFormData(tab.content) }; // 清空内容 tab.content.innerHTML = '<div class="unloaded-placeholder">内容已卸载以节省内存</div>'; this.unloadedTabs.add(tab.id); console.log(`Unloaded tab ${tab.id} to save memory`); } loadTabContent(tab) { if (!this.unloadedTabs.has(tab.id)) return; // 实际项目中这里应该从缓存或服务器加载原始内容 tab.content.innerHTML = tab.originalContent; // 恢复状态 if (tab.savedState) { tab.content.scrollTop = tab.savedState.scrollPosition; this.restoreFormData(tab.content, tab.savedState.formData); delete tab.savedState; } this.unloadedTabs.delete(tab.id); } collectFormData(contentElement) { const inputs = contentElement.querySelectorAll('input, textarea, select'); return Array.from(inputs).map(input => ({ id: input.id, name: input.name, value: input.value, checked: input.checked, selectedIndex: input.selectedIndex })); } restoreFormData(contentElement, formData) { formData.forEach(data => { const element = contentElement.querySelector(`#${data.id}`) || contentElement.querySelector(`[name="${data.name}"]`); if (!element) return; if (element.type === 'checkbox' || element.type === 'radio') { element.checked = data.checked; } else if (element.tagName === 'SELECT') { element.selectedIndex = data.selectedIndex; } else { element.value = data.value; } }); } }