import { Injectable } from '@angular/core'; /** * 浏览器窗口管理服务 * 封装 window.open 操作,提供项目标签页管理功能 * * 功能: * 1. 生成正确的绝对 URL * 2. 管理标签页状态(localStorage 存储) * 3. 实现窗口复用(已打开则聚焦) * 4. 标签页数量限制(最多7个,自动关闭最旧) * 5. 错误处理和诊断日志 */ @Injectable({ providedIn: 'root' }) export class WindowService { private readonly MAX_TABS = 7; private readonly TAB_STORAGE_KEY = 'browser_tabs'; private windowReferences = new Map(); /** * 打开项目标签页 * @param projectId 项目ID * @returns 是否成功打开或聚焦窗口 */ openProjectTab(projectId: string): boolean { console.log('🔍 [WindowService] 打开项目标签页:', projectId); // 生成窗口名称和URL const windowName = this.generateWindowName(projectId); const url = this.generateProjectUrl(projectId); console.log('🔍 [WindowService] 参数:', { windowName, url }); // 检查内存中是否已跟踪此窗口 const existingWindow = this.windowReferences.get(windowName); if (existingWindow && !existingWindow.closed) { console.log('🔍 [WindowService] 窗口已在内存中跟踪,聚焦:', windowName); existingWindow.focus(); return true; } // 清理无效的窗口引用 this.cleanupWindowReferences(); // 检查标签页数量限制 const openTabCount = this.windowReferences.size; console.log('🔍 [WindowService] 当前跟踪的标签页数量:', openTabCount, '限制:', this.MAX_TABS); if (openTabCount >= this.MAX_TABS) { console.error('🔍 [WindowService] 标签页数量超限'); this.showError('已达到最大标签页数量(7个),请手动关闭一个标签页后再试'); return false; } // 使用 _blank 目标打开新窗口 console.log('🔍 [WindowService] 使用 _blank 目标打开新窗口'); const newWindow = window.open(url, '_blank'); console.log('🔍 [WindowService] window.open 结果:', !!newWindow); if (!newWindow) { console.error('🔍 [WindowService] 无法打开新标签页'); this.showError('无法打开新标签页,可能被浏览器阻止,请检查浏览器弹出窗口设置'); return false; } // 在内存中跟踪此窗口 this.windowReferences.set(windowName, newWindow); // 设置关闭监听器 const checkClosed = () => { if (newWindow.closed) { console.log('🔍 [WindowService] 检测到窗口已关闭:', windowName); this.windowReferences.delete(windowName); } else { // 继续检查 setTimeout(checkClosed, 1000); } }; setTimeout(checkClosed, 1000); console.log('🔍 [WindowService] 窗口已打开并跟踪'); return true; } /** * 生成窗口名称 */ private generateWindowName(projectId: string): string { return `project-${projectId}`; } /** * 生成项目URL(绝对路径) */ private generateProjectUrl(projectId: string): string { // 生成绝对URL确保在新标签页中可靠加载 const origin = window.location.origin; const url = `${origin}/project/${projectId}`; console.log('🔍 [WindowService] 生成的URL:', { origin, url }); return url; } /** * 清理无效的窗口引用 */ private cleanupWindowReferences(): void { for (const [name, win] of this.windowReferences.entries()) { if (win.closed) { console.log('🔍 [WindowService] 清理已关闭的窗口引用:', name); this.windowReferences.delete(name); } } } /** * 根据名称获取已存在的窗口(简化版,不依赖window.open('', name)) */ private getWindowByName(name: string): Window | null { // 只从内存引用中获取 const win = this.windowReferences.get(name); if (win && !win.closed) { return win; } return null; } /** * 获取标签页数据 */ private getTabData(): Record { try { const tabsJson = localStorage.getItem(this.TAB_STORAGE_KEY); return tabsJson ? JSON.parse(tabsJson) : {}; } catch (e) { console.error('🔍 [WindowService] 解析标签页数据失败:', e); return {}; } } /** * 保存标签页数据 */ private saveTabData(tabData: Record): void { try { localStorage.setItem(this.TAB_STORAGE_KEY, JSON.stringify(tabData)); } catch (e) { console.error('🔍 [WindowService] 保存标签页数据失败:', e); } } /** * 获取当前应用的基URL(用于诊断) */ getBaseUrl(): string { return window.location.origin; } /** * 显示错误提示给用户 */ private showError(message: string): void { console.error('🔍 [WindowService] 错误:', message); // 使用alert简单提示用户,实际项目中可替换为toast等UI组件 alert(`打开标签页失败: ${message}`); } /** * 诊断方法:输出当前窗口状态信息 */ diagnose(): { baseUrl: string; openTabCount: number; maxTabs: number; trackedWindows: string[]; windowLocation: string; } { return { baseUrl: this.getBaseUrl(), openTabCount: this.windowReferences.size, maxTabs: this.MAX_TABS, trackedWindows: Array.from(this.windowReferences.keys()), windowLocation: window.location.href }; } }