From f2b12fbaf57f9dcfcb75ef3caa77bedc9c830d3d Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Thu, 23 Oct 2025 12:14:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../windowForm/WindowFormServiceOld.ts | 1225 +++++++++++++++++ 1 file changed, 1225 insertions(+) create mode 100644 src/services/windowForm/WindowFormServiceOld.ts diff --git a/src/services/windowForm/WindowFormServiceOld.ts b/src/services/windowForm/WindowFormServiceOld.ts new file mode 100644 index 0000000..d48f416 --- /dev/null +++ b/src/services/windowForm/WindowFormServiceOld.ts @@ -0,0 +1,1225 @@ +import { reactive, ref } from 'vue' +import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder.ts' +import { v4 as uuidv4 } from 'uuid' +import type { ResizeDirection, ResizeState } from '@/ui/types/WindowFormTypes.ts' +import { appRegistry } from '@/apps/AppRegistry.ts' + +/** + * 窗体状态枚举 + */ +export enum WindowState { + CREATING = 'creating', + LOADING = 'loading', + ACTIVE = 'active', + MINIMIZED = 'minimized', + MAXIMIZED = 'maximized', + CLOSING = 'closing', + DESTROYED = 'destroyed', + ERROR = 'error' +} + +/** + * 窗体配置接口 + */ +export interface IWindowConfig { + /** + * 窗体标题 + */ + title?: string + /** + * 窗体宽度(像素) + */ + width: number + /** + * 窗体高度(像素) + */ + height: number + /** + * 窗体最小宽度(像素) + */ + minWidth?: number + /** + * 窗体最小高度(像素) + */ + minHeight?: number + /** + * 窗体最大宽度(像素) + */ + maxWidth?: number + /** + * 窗体最大高度(像素) + */ + maxHeight?: number + /** + * 是否可调整大小 + */ + resizable?: boolean + /** + * 是否可移动 + */ + movable?: boolean + /** + * 是否可关闭 + */ + closable?: boolean + /** + * 是否可最小化 + */ + minimizable?: boolean + /** + * 是否可最大化 + */ + maximizable?: boolean + /** + * 是否为模态窗体 + */ + modal?: boolean + /** + * 是否始终置顶 + */ + alwaysOnTop?: boolean + /** + * 窗体X坐标位置 + */ + x?: number + /** + * 窗体Y坐标位置 + */ + y?: number +} + +/** + * 窗体实例接口 + */ +export interface WindowFormInstance { + /** + * 窗体唯一标识符 + */ + id: string + /** + * 关联应用标识符 + */ + appId: string + /** + * 窗体配置信息 + */ + config: IWindowConfig + /** + * 窗体当前状态 + */ + state: WindowState + /** + * 窗体DOM元素 + */ + element?: HTMLElement + /** + * 窗体内嵌iframe元素 + */ + iframe?: HTMLIFrameElement + /** + * 窗体层级索引 + */ + zIndex: number + /** + * 窗体创建时间 + */ + createdAt: Date + /** + * 窗体更新时间 + */ + updatedAt: Date + /** + * 拖拽调整尺寸状态 + */ + resizeState?: ResizeState +} + +/** + * 窗体事件接口 + */ +export interface WindowEvents extends IEventMap { + onStateChange: (windowId: string, newState: WindowState, oldState: WindowState) => void + onResize: (windowId: string, width: number, height: number) => void + onMove: (windowId: string, x: number, y: number) => void + onFocus: (windowId: string) => void + onBlur: (windowId: string) => void + onClose: (windowId: string) => void + onResizeStart: (windowId: string) => void + onResizing: (windowId: string, width: number, height: number) => void + onResizeEnd: (windowId: string) => void + onWindowFormDataUpdate: (data: { + id: string + state: WindowState + width: number + height: number + x: number + y: number + }) => void +} + +/** + * 窗体管理服务类 + */ +export class WindowFormService { + private windowsForm = reactive(new Map()) + private activeWindowId = ref(null) + private nextZIndex = 1000 + private eventBus: IEventBuilder + + constructor(eventBus: IEventBuilder) { + this.eventBus = eventBus + this.setupGlobalResizeEvents() + } + + /** + * 创建新窗体 + */ + async createWindow(appId: string, config: IWindowConfig): Promise { + const windowId = uuidv4() + const now = new Date() + + const windowInstance: WindowFormInstance = { + id: windowId, + appId, + config, + state: WindowState.CREATING, + zIndex: this.nextZIndex++, + createdAt: now, + updatedAt: now + } + + this.windowsForm.set(windowId, windowInstance) + + try { + // 创建窗体DOM元素 + await this.createWindowElement(windowInstance) + + // 更新状态为加载中 + this.updateWindowState(windowId, WindowState.LOADING) + + // 模拟应用加载过程 + await this.loadApplication(windowInstance) + + // 激活窗体 + this.updateWindowState(windowId, WindowState.ACTIVE) + this.setActiveWindow(windowId) + + return windowInstance + } catch (error) { + this.updateWindowState(windowId, WindowState.ERROR) + throw error + } + } + + /** + * 销毁窗体 + */ + async destroyWindow(windowId: string): Promise { + const window = this.windowsForm.get(windowId) + if (!window) return false + + try { + this.updateWindowState(windowId, WindowState.CLOSING) + + // 清理DOM元素 + if (window.element) { + window.element.remove() + } + if (window.iframe) { + window.iframe.remove() + } + + // 从集合中移除 + this.windowsForm.delete(windowId) + + // 更新活跃窗体 + if (this.activeWindowId.value === windowId) { + this.activeWindowId.value = null + // 激活最后一个窗体 + const lastWindow = Array.from(this.windowsForm.values()).pop() + if (lastWindow) { + this.setActiveWindow(lastWindow.id) + } + } + + this.eventBus.notify('onClose', windowId) + return true + } catch (error) { + console.error('销毁窗体失败:', error) + return false + } + } + + /** + * 最小化窗体 + */ + minimizeWindow(windowId: string): boolean { + const window = this.windowsForm.get(windowId) + if (!window || window.state === WindowState.MINIMIZED) return false + + this.updateWindowState(windowId, WindowState.MINIMIZED) + + if (window.element) { + window.element.style.display = 'none' + } + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 最大化窗体 + */ + maximizeWindow(windowId: string): boolean { + const window = this.windowsForm.get(windowId) + if (!window || window.state === WindowState.MAXIMIZED) return false + + const oldState = window.state + this.updateWindowState(windowId, WindowState.MAXIMIZED) + + if (window.element) { + // 保存原始尺寸和位置 + window.element.dataset.originalWidth = window.config.width.toString() + window.element.dataset.originalHeight = window.config.height.toString() + window.element.dataset.originalX = (window.config.x || 0).toString() + window.element.dataset.originalY = (window.config.y || 0).toString() + + // 设置最大化样式 + Object.assign(window.element.style, { + position: 'fixed', + top: '0', + left: '0', + width: '100vw', + height: 'calc(100vh - 40px)', // 减去任务栏高度 + display: 'block', + transform: 'none' // 确保移除transform + }) + } + + this.setActiveWindow(windowId) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 还原窗体 + */ + restoreWindow(windowId: string): boolean { + const window = this.windowsForm.get(windowId) + if (!window) return false + + const targetState = + window.state === WindowState.MINIMIZED + ? WindowState.ACTIVE + : window.state === WindowState.MAXIMIZED + ? WindowState.ACTIVE + : window.state + + this.updateWindowState(windowId, targetState) + + if (window.element) { + if (window.state === WindowState.MINIMIZED) { + window.element.style.display = 'block' + } else if (window.state === WindowState.MAXIMIZED) { + // 恢复原始尺寸和位置 + const originalWidth = window.element.dataset.originalWidth + const originalHeight = window.element.dataset.originalHeight + const originalX = window.element.dataset.originalX + const originalY = window.element.dataset.originalY + + Object.assign(window.element.style, { + width: originalWidth ? `${originalWidth}px` : `${window.config.width}px`, + height: originalHeight ? `${originalHeight}px` : `${window.config.height}px`, + left: originalX ? `${originalX}px` : '0px', + top: originalY ? `${originalY}px` : '0px', + transform: 'none' // 确保移除transform + }) + + // 更新配置中的位置 + if (originalX) window.config.x = parseFloat(originalX) + if (originalY) window.config.y = parseFloat(originalY) + } + } + + this.setActiveWindow(windowId) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 设置窗体标题 + */ + setWindowTitle(windowId: string, title: string): boolean { + const window = this.windowsForm.get(windowId) + if (!window) return false + + window.config.title = title + window.updatedAt = new Date() + + // 更新DOM元素标题 + if (window.element) { + const titleElement = window.element.querySelector('.window-title') + if (titleElement) { + titleElement.textContent = title + } + } + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 设置窗体尺寸 + */ + setWindowSize(windowId: string, width: number, height: number): boolean { + const window = this.windowsForm.get(windowId) + if (!window) return false + + // 检查尺寸限制 + const finalWidth = this.clampDimension(width, window.config.minWidth, window.config.maxWidth) + const finalHeight = this.clampDimension( + height, + window.config.minHeight, + window.config.maxHeight + ) + + window.config.width = finalWidth + window.config.height = finalHeight + window.updatedAt = new Date() + + if (window.element) { + window.element.style.width = `${finalWidth}px` + window.element.style.height = `${finalHeight}px` + } + + this.eventBus.notify('onResize', windowId, finalWidth, finalHeight) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 获取窗体实例 + */ + getWindow(windowId: string): WindowFormInstance | undefined { + return this.windowsForm.get(windowId) + } + + /** + * 获取所有窗体 + */ + getAllWindows(): WindowFormInstance[] { + return Array.from(this.windowsForm.values()) + } + + /** + * 获取活跃窗体ID + */ + getActiveWindowId(): string | null { + return this.activeWindowId.value + } + + /** + * 设置活跃窗体 + */ + setActiveWindow(windowId: string): boolean { + const window = this.windowsForm.get(windowId) + if (!window) return false + + this.activeWindowId.value = windowId + window.zIndex = this.nextZIndex++ + + if (window.element) { + window.element.style.zIndex = window.zIndex.toString() + } + + this.eventBus.notify('onFocus', windowId) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + + return true + } + + /** + * 创建窗体DOM元素 + */ + private async createWindowElement(windowInstance: WindowFormInstance): Promise { + const { id, config, appId } = windowInstance + + // 创建窗体容器 + const windowElement = document.createElement('div') + windowElement.className = 'system-window' + windowElement.id = `window-${id}` + + // 计算初始位置 + let left = config.x + let top = config.y + + // 如果没有指定位置,则居中显示 + if (left === undefined || top === undefined) { + const centerX = Math.max(0, (window.innerWidth - config.width) / 2) + const centerY = Math.max(0, (window.innerHeight - config.height) / 2) + left = left !== undefined ? left : centerX + top = top !== undefined ? top : centerY + } + + // 设置基本样式 + Object.assign(windowElement.style, { + position: 'absolute', + width: `${config.width}px`, + height: `${config.height}px`, + left: `${left}px`, + top: `${top}px`, + zIndex: windowInstance.zIndex.toString(), + backgroundColor: '#fff', + border: '1px solid #ccc', + borderRadius: '8px', + boxShadow: '0 4px 20px rgba(0,0,0,0.15)', + overflow: 'hidden' + }) + + // 保存初始位置到配置中 + windowInstance.config.x = left + windowInstance.config.y = top + + // 创建窗体标题栏 + const titleBar = this.createTitleBar(windowInstance) + windowElement.appendChild(titleBar) + + // 创建窗体内容区域 + const contentArea = document.createElement('div') + contentArea.className = 'window-content' + contentArea.style.cssText = ` + width: 100%; + height: calc(100% - 40px); + overflow: hidden; + ` + + // 检查是否为内置应用 + if (appRegistry.hasApp(appId)) { + // 内置应用:创建普通 div 容器,AppRenderer 组件会在这里渲染内容 + const appContainer = document.createElement('div') + appContainer.className = 'built-in-app-container' + appContainer.id = `app-container-${appId}` + appContainer.style.cssText = ` + width: 100%; + height: 100%; + background: #fff; + ` + contentArea.appendChild(appContainer) + + console.log(`[WindowService] 为内置应用 ${appId} 创建了普通容器`) + } else { + // 外部应用:创建 iframe 容器 + const iframe = document.createElement('iframe') + iframe.style.cssText = ` + width: 100%; + height: 100%; + border: none; + background: #fff; + ` + iframe.sandbox = 'allow-scripts allow-forms allow-popups' // 移除allow-same-origin以提高安全性 + contentArea.appendChild(iframe) + + // 保存 iframe 引用(仅对外部应用) + windowInstance.iframe = iframe + + console.log(`[WindowService] 为外部应用 ${appId} 创建了 iframe 容器`) + } + + windowElement.appendChild(contentArea) + + // 添加拖拽调整尺寸功能 + if (config.resizable !== false) { + this.addResizeFunctionality(windowElement, windowInstance) + } + + // 添加到页面 + document.body.appendChild(windowElement) + + // 保存引用 + windowInstance.element = windowElement + } + + /** + * 创建窗体标题栏 + */ + private createTitleBar(windowInstance: WindowFormInstance): HTMLElement { + const titleBar = document.createElement('div') + titleBar.className = 'window-title-bar' + titleBar.style.cssText = ` + height: 40px; + background: linear-gradient(to bottom, #f8f9fa, #e9ecef); + border-bottom: 1px solid #dee2e6; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 12px; + cursor: move; + user-select: none; + ` + + // 窗体标题 + const title = document.createElement('span') + title.className = 'window-title' + title.textContent = windowInstance.config.title || '' + title.style.cssText = ` + font-size: 14px; + font-weight: 500; + color: #333; + ` + + // 控制按钮组 + const controls = document.createElement('div') + controls.className = 'window-controls' + controls.style.cssText = ` + display: flex; + gap: 8px; + ` + + // 最小化按钮 + if (windowInstance.config.minimizable !== false) { + const minimizeBtn = this.createControlButton('−', () => { + this.minimizeWindow(windowInstance.id) + }) + controls.appendChild(minimizeBtn) + } + + // 最大化按钮 + if (windowInstance.config.maximizable !== false) { + const maximizeBtn = this.createControlButton('□', () => { + if (windowInstance.state === WindowState.MAXIMIZED) { + this.restoreWindow(windowInstance.id) + } else { + this.maximizeWindow(windowInstance.id) + } + }) + controls.appendChild(maximizeBtn) + } + + // 关闭按钮 + if (windowInstance.config.closable !== false) { + const closeBtn = this.createControlButton('×', () => { + this.destroyWindow(windowInstance.id) + }) + closeBtn.style.color = '#dc3545' + controls.appendChild(closeBtn) + } + + titleBar.appendChild(title) + titleBar.appendChild(controls) + + // 添加拖拽功能 + if (windowInstance.config.movable !== false) { + this.addDragFunctionality(titleBar, windowInstance) + } + + return titleBar + } + + /** + * 创建控制按钮 + */ + private createControlButton(text: string, onClick: () => void): HTMLElement { + const button = document.createElement('button') + button.textContent = text + button.style.cssText = ` + width: 24px; + height: 24px; + border: none; + background: transparent; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + font-size: 16px; + line-height: 1; + ` + + button.addEventListener('click', onClick) + + // 添加悬停效果 + button.addEventListener('mouseenter', () => { + button.style.backgroundColor = '#e9ecef' + }) + + button.addEventListener('mouseleave', () => { + button.style.backgroundColor = 'transparent' + }) + + return button + } + + /** + * 添加窗体拖拽功能 + */ + private addDragFunctionality(titleBar: HTMLElement, windowInstance: WindowFormInstance): void { + let isDragging = false + let startX = 0 + let startY = 0 + let startLeft = 0 + let startTop = 0 + + titleBar.addEventListener('mousedown', (e) => { + // 检查是否正在调整尺寸,如果是则不处理拖拽 + if (windowInstance.resizeState?.isResizing) { + return + } + + // 检查是否点击在调整尺寸手柄上,如果是则不处理拖拽 + const target = e.target as HTMLElement + if (target.classList.contains('resize-handle')) { + return + } + + if (!windowInstance.element) return + + isDragging = true + startX = e.clientX + startY = e.clientY + + const rect = windowInstance.element.getBoundingClientRect() + + // 如果使用了transform,需要转换为实际坐标 + if ( + windowInstance.element.style.transform && + windowInstance.element.style.transform.includes('translate') + ) { + // 移除transform并设置实际的left/top值 + windowInstance.element.style.transform = 'none' + windowInstance.config.x = rect.left + windowInstance.config.y = rect.top + windowInstance.element.style.left = `${rect.left}px` + windowInstance.element.style.top = `${rect.top}px` + } + + startLeft = rect.left + startTop = rect.top + + // 设置为活跃窗体 + this.setActiveWindow(windowInstance.id) + + e.preventDefault() + e.stopPropagation() + }) + + document.addEventListener('mousemove', (e) => { + // 检查是否正在调整尺寸,如果是则不处理拖拽 + if (windowInstance.resizeState?.isResizing) { + return + } + + if (!isDragging || !windowInstance.element) return + + const deltaX = e.clientX - startX + const deltaY = e.clientY - startY + + const newLeft = startLeft + deltaX + const newTop = startTop + deltaY + + windowInstance.element.style.left = `${newLeft}px` + windowInstance.element.style.top = `${newTop}px` + + // 更新配置 + windowInstance.config.x = newLeft + windowInstance.config.y = newTop + + this.eventBus.notify('onMove', windowInstance.id, newLeft, newTop) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowInstance.id) + }) + + document.addEventListener('mouseup', () => { + isDragging = false + }) + } + + /** + * 添加窗体调整尺寸功能 + */ + private addResizeFunctionality( + windowElement: HTMLElement, + windowInstance: WindowFormInstance + ): void { + // 初始化调整尺寸状态 + windowInstance.resizeState = { + isResizing: false, + direction: 'none', + startX: 0, + startY: 0, + startWidth: 0, + startHeight: 0, + startXPosition: 0, + startYPosition: 0 + } + + // 创建8个调整尺寸的手柄 + const resizeHandles = this.createResizeHandles(windowElement) + + // 添加鼠标事件监听器 + resizeHandles.forEach((handle) => { + this.addResizeHandleEvents(handle, windowElement, windowInstance) + }) + + // 添加窗口边缘检测 + windowElement.addEventListener('mousemove', (e) => { + if (!windowInstance.resizeState || windowInstance.resizeState.isResizing) return + this.updateCursorForResize(e, windowElement, windowInstance) + }) + + windowElement.addEventListener('mouseleave', () => { + if (!windowInstance.resizeState || windowInstance.resizeState.isResizing) return + windowElement.style.cursor = 'default' + }) + } + + /** + * 创建调整尺寸的手柄 + */ + private createResizeHandles(windowElement: HTMLElement): HTMLElement[] { + const handles: HTMLElement[] = [] + const directions: ResizeDirection[] = [ + 'topLeft', + 'top', + 'topRight', + 'right', + 'bottomRight', + 'bottom', + 'bottomLeft', + 'left' + ] + + directions.forEach((direction) => { + const handle = document.createElement('div') + handle.className = `resize-handle resize-handle-${direction}` + + // 设置手柄样式 + handle.style.position = 'absolute' + handle.style.zIndex = '1001' + + // 根据方向设置位置和光标 + switch (direction) { + case 'topLeft': + handle.style.top = '-6px' + handle.style.left = '-6px' + handle.style.cursor = 'nw-resize' + break + case 'top': + handle.style.top = '-4px' + handle.style.left = '6px' + handle.style.right = '6px' + handle.style.cursor = 'n-resize' + break + case 'topRight': + handle.style.top = '-6px' + handle.style.right = '-6px' + handle.style.cursor = 'ne-resize' + break + case 'right': + handle.style.top = '6px' + handle.style.bottom = '6px' + handle.style.right = '-4px' + handle.style.cursor = 'e-resize' + break + case 'bottomRight': + handle.style.bottom = '-6px' + handle.style.right = '-6px' + handle.style.cursor = 'se-resize' + break + case 'bottom': + handle.style.bottom = '-4px' + handle.style.left = '6px' + handle.style.right = '6px' + handle.style.cursor = 's-resize' + break + case 'bottomLeft': + handle.style.bottom = '-6px' + handle.style.left = '-6px' + handle.style.cursor = 'sw-resize' + break + case 'left': + handle.style.top = '6px' + handle.style.bottom = '6px' + handle.style.left = '-4px' + handle.style.cursor = 'w-resize' + break + } + + // 设置手柄尺寸 + if (direction === 'top' || direction === 'bottom') { + handle.style.height = '8px' + } else if (direction === 'left' || direction === 'right') { + handle.style.width = '8px' + } else { + // 对角方向的手柄需要更大的点击区域 + handle.style.width = '12px' + handle.style.height = '12px' + } + + windowElement.appendChild(handle) + handles.push(handle) + }) + + return handles + } + + /** + * 添加调整尺寸手柄的事件监听器 + */ + private addResizeHandleEvents( + handle: HTMLElement, + windowElement: HTMLElement, + windowInstance: WindowFormInstance + ): void { + const direction = handle.className + .split(' ') + .find((cls) => cls.startsWith('resize-handle-')) + ?.split('-')[2] as ResizeDirection + + handle.addEventListener('mousedown', (e) => { + if (!windowInstance.resizeState) return + + e.preventDefault() + e.stopPropagation() + + // 确保窗体位置是最新的 + if (windowInstance.element) { + const rect = windowInstance.element.getBoundingClientRect() + // 如果使用了transform,需要转换为实际坐标 + if ( + windowInstance.element.style.transform && + windowInstance.element.style.transform.includes('translate') + ) { + // 移除transform并设置实际的left/top值 + windowInstance.element.style.transform = 'none' + windowInstance.config.x = rect.left + windowInstance.config.y = rect.top + windowInstance.element.style.left = `${rect.left}px` + windowInstance.element.style.top = `${rect.top}px` + } + } + + // 开始调整尺寸 + windowInstance.resizeState.isResizing = true + windowInstance.resizeState.direction = direction + windowInstance.resizeState.startX = e.clientX + windowInstance.resizeState.startY = e.clientY + windowInstance.resizeState.startWidth = windowInstance.config.width + windowInstance.resizeState.startHeight = windowInstance.config.height + windowInstance.resizeState.startXPosition = windowInstance.config.x || 0 + windowInstance.resizeState.startYPosition = windowInstance.config.y || 0 + + // 添加半透明遮罩效果 + windowElement.style.opacity = '0.8' + + // 触发开始调整尺寸事件 + this.eventBus.notify('onResizeStart', windowInstance.id) + + e.preventDefault() + e.stopPropagation() + }) + } + + /** + * 根据鼠标位置更新光标样式 + */ + private updateCursorForResize( + e: MouseEvent, + windowElement: HTMLElement, + windowInstance: WindowFormInstance + ): void { + if (!windowInstance.resizeState) return + + const rect = windowElement.getBoundingClientRect() + const x = e.clientX - rect.left + const y = e.clientY - rect.top + const edgeSize = 8 + + // 检查鼠标位置确定调整方向 + let direction: ResizeDirection = 'none' + + // 优先检查角落区域,使用更精确的检测 + if (x >= 0 && x < edgeSize && y >= 0 && y < edgeSize) { + direction = 'topLeft' + } else if (x > rect.width - edgeSize && x <= rect.width && y >= 0 && y < edgeSize) { + direction = 'topRight' + } else if (x >= 0 && x < edgeSize && y > rect.height - edgeSize && y <= rect.height) { + direction = 'bottomLeft' + } else if ( + x > rect.width - edgeSize && + x <= rect.width && + y > rect.height - edgeSize && + y <= rect.height + ) { + direction = 'bottomRight' + } + // 然后检查边缘区域 + else if (x >= 0 && x < edgeSize && y >= edgeSize && y <= rect.height - edgeSize) { + direction = 'left' + } else if ( + x > rect.width - edgeSize && + x <= rect.width && + y >= edgeSize && + y <= rect.height - edgeSize + ) { + direction = 'right' + } else if (y >= 0 && y < edgeSize && x >= edgeSize && x <= rect.width - edgeSize) { + direction = 'top' + } else if ( + y > rect.height - edgeSize && + y <= rect.height && + x >= edgeSize && + x <= rect.width - edgeSize + ) { + direction = 'bottom' + } + + // 更新光标样式 + windowElement.style.cursor = + direction === 'none' + ? 'default' + : `${direction.replace(/([A-Z])/g, '-$1').toLowerCase()}-resize` + } + + /** + * 设置全局调整尺寸事件监听器 + */ + private setupGlobalResizeEvents(): void { + document.addEventListener('mousemove', (e) => { + // 处理调整尺寸过程中的鼠标移动 + this.handleResizeMouseMove(e) + }) + + document.addEventListener('mouseup', () => { + // 处理调整尺寸结束 + this.handleResizeMouseUp() + }) + } + + /** + * 处理调整尺寸过程中的鼠标移动 + */ + private handleResizeMouseMove(e: MouseEvent): void { + // 找到正在调整尺寸的窗体 + const resizingWindow = Array.from(this.windowsForm.values()).find( + (window) => window.resizeState?.isResizing + ) + + // 如果没有正在调整尺寸的窗体,直接返回 + if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return + + const { direction, startX, startY, startWidth, startHeight, startXPosition, startYPosition } = + resizingWindow.resizeState + + const deltaX = e.clientX - startX + const deltaY = e.clientY - startY + + let newWidth = startWidth + let newHeight = startHeight + let newX = startXPosition + let newY = startYPosition + + // 根据调整方向计算新尺寸和位置 + switch (direction) { + case 'topLeft': + newWidth = Math.max(200, startWidth - deltaX) + newHeight = Math.max(150, startHeight - deltaY) + newX = startXPosition + (startWidth - newWidth) + newY = startYPosition + (startHeight - newHeight) + break + case 'top': + newHeight = Math.max(150, startHeight - deltaY) + newY = startYPosition + (startHeight - newHeight) + break + case 'topRight': + newWidth = Math.max(200, startWidth + deltaX) + newHeight = Math.max(150, startHeight - deltaY) + newY = startYPosition + (startHeight - newHeight) + break + case 'right': + newWidth = Math.max(200, startWidth + deltaX) + break + case 'bottomRight': + newWidth = Math.max(200, startWidth + deltaX) + newHeight = Math.max(150, startHeight + deltaY) + break + case 'bottom': + newHeight = Math.max(150, startHeight + deltaY) + break + case 'bottomLeft': + newWidth = Math.max(200, startWidth - deltaX) + newHeight = Math.max(150, startHeight + deltaY) + newX = startXPosition + (startWidth - newWidth) + break + case 'left': + newWidth = Math.max(200, startWidth - deltaX) + newX = startXPosition + (startWidth - newWidth) + break + } + + // 应用尺寸限制 + newWidth = this.clampDimension( + newWidth, + resizingWindow.config.minWidth, + resizingWindow.config.maxWidth + ) + newHeight = this.clampDimension( + newHeight, + resizingWindow.config.minHeight, + resizingWindow.config.maxHeight + ) + + // 应用新尺寸和位置 + resizingWindow.config.width = newWidth + resizingWindow.config.height = newHeight + resizingWindow.config.x = newX + resizingWindow.config.y = newY + + if (resizingWindow.element) { + resizingWindow.element.style.width = `${newWidth}px` + resizingWindow.element.style.height = `${newHeight}px` + resizingWindow.element.style.left = `${newX}px` + resizingWindow.element.style.top = `${newY}px` + resizingWindow.element.style.transform = 'none' + } + + // 触发调整尺寸事件 + this.eventBus.notify('onResizing', resizingWindow.id, newWidth, newHeight) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(resizingWindow.id) + } + + /** + * 处理调整尺寸结束 + */ + private handleResizeMouseUp(): void { + // 找到正在调整尺寸的窗体 + const resizingWindow = Array.from(this.windowsForm.values()).find( + (window) => window.resizeState?.isResizing + ) + + if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return + + // 结束调整尺寸 + resizingWindow.resizeState.isResizing = false + + // 移除半透明遮罩效果 + resizingWindow.element.style.opacity = '1' + + // 触发调整尺寸结束事件 + this.eventBus.notify('onResizeEnd', resizingWindow.id) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(resizingWindow.id) + } + + /** + * 限制尺寸在最小值和最大值之间 + */ + private clampDimension(value: number, min: number = 0, max: number = Infinity): number { + return Math.max(min, Math.min(max, value)) + } + + /** + * 发送窗体数据更新事件 + */ + private notifyWindowFormDataUpdate(windowId: string): void { + const window = this.windowsForm.get(windowId) + if (!window || !window.element) return + + // 获取窗体数据 + const rect = window.element.getBoundingClientRect() + const data = { + id: windowId, + state: window.state, + width: window.config.width, + height: window.config.height, + x: window.config.x !== undefined ? window.config.x : rect.left, + y: window.config.y !== undefined ? window.config.y : rect.top + } + + // 发送事件到事件总线 + this.eventBus.notify('onWindowFormDataUpdate', data) + } + + /** + * 加载应用 + */ + private async loadApplication(windowInstance: WindowFormInstance): Promise { + // 动态导入 AppRegistry 检查是否为内置应用 + try { + // 如果是内置应用,直接返回,不需要等待 + if (appRegistry.hasApp(windowInstance.appId)) { + console.log(`[WindowService] 内置应用 ${windowInstance.appId} 无需等待加载`) + return Promise.resolve() + } + } catch (error) { + console.warn('无法导入 AppRegistry,使用传统加载方式') + } + + // 对于外部应用,保持原有的加载逻辑 + return new Promise((resolve) => { + console.log(`[WindowService] 开始加载外部应用 ${windowInstance.appId}`) + setTimeout(() => { + if (windowInstance.iframe) { + // 这里可以设置 iframe 的 src 来加载具体应用 + windowInstance.iframe.src = 'about:blank' + + // 添加一些示例内容 + const doc = windowInstance.iframe.contentDocument + if (doc) { + doc.body.innerHTML = ` +
+

应用: ${windowInstance.config.title}

+

应用ID: ${windowInstance.appId}

+

窗体ID: ${windowInstance.id}

+

这是一个示例应用内容。

+
+ ` + } + } + console.log(`[WindowService] 外部应用 ${windowInstance.appId} 加载完成`) + resolve() + }, 200) + }) + } + + /** + * 更新窗体状态 + */ + private updateWindowState(windowId: string, newState: WindowState): void { + const window = this.windowsForm.get(windowId) + if (!window) return + + const oldState = window.state + + // 只有状态真正发生变化时才触发事件 + if (oldState === newState) return + + window.state = newState + window.updatedAt = new Date() + + // 所有状态变化都应该触发事件,这是正常的系统行为 + console.log(`[WindowService] 窗体状态变化: ${windowId} ${oldState} -> ${newState}`) + this.eventBus.notify('onStateChange', windowId, newState, oldState) + + // 发送窗体数据更新事件 + this.notifyWindowFormDataUpdate(windowId) + } +}