Files
vue-desktop/src/services/WindowFormService.ts
2025-10-10 11:25:50 +08:00

1215 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { reactive, ref } from 'vue'
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder'
import { v4 as uuidv4 } from 'uuid'
import type { ResizeDirection, ResizeState } from '@/ui/types/WindowFormTypes'
import { appRegistry } from '@/apps/AppRegistry'
/**
* 窗体状态枚举
*/
export enum WindowState {
CREATING = 'creating',
LOADING = 'loading',
ACTIVE = 'active',
MINIMIZED = 'minimized',
MAXIMIZED = 'maximized',
CLOSING = 'closing',
DESTROYED = 'destroyed',
ERROR = 'error'
}
/**
* 窗体配置接口
*/
/**
* 窗体配置接口
*/
export interface WindowConfig {
/**
* 窗体标题
*/
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 WindowInstance {
/**
* 窗体唯一标识符
*/
id: string
/**
* 关联应用标识符
*/
appId: string
/**
* 窗体配置信息
*/
config: WindowConfig
/**
* 窗体当前状态
*/
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
}
/**
* 窗体管理服务类
*/
export class WindowFormService {
private windowsForm = reactive(new Map<string, WindowInstance>())
private activeWindowId = ref<string | null>(null)
private nextZIndex = 1000
private eventBus: IEventBuilder<WindowEvents>
constructor(eventBus: IEventBuilder<WindowEvents>) {
this.eventBus = eventBus
this.setupGlobalResizeEvents()
}
/**
* 创建新窗体
*/
async createWindow(appId: string, config: WindowConfig): Promise<WindowInstance> {
const windowId = uuidv4()
const now = new Date()
const windowInstance: WindowInstance = {
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<boolean> {
const window = this.windowsForm.get(windowId)
if (!window) return false
try {
this.updateWindowState(windowId, WindowState.CLOSING)
// 清理DOM元素
if (window.element) {
window.element.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.notifyEvent('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.notifyEvent('onResize', windowId, finalWidth, finalHeight)
// 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId)
return true
}
/**
* 获取窗体实例
*/
getWindow(windowId: string): WindowInstance | undefined {
return this.windowsForm.get(windowId)
}
/**
* 获取所有窗体
*/
getAllWindows(): WindowInstance[] {
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.notifyEvent('onFocus', windowId)
// 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId)
return true
}
/**
* 创建窗体DOM元素
*/
private async createWindowElement(windowInstance: WindowInstance): Promise<void> {
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: WindowInstance): 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: WindowInstance): 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.notifyEvent('onMove', windowInstance.id, newLeft, newTop)
// 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowInstance.id)
})
document.addEventListener('mouseup', () => {
isDragging = false
})
}
/**
* 添加窗体调整尺寸功能
*/
private addResizeFunctionality(windowElement: HTMLElement, windowInstance: WindowInstance): 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: WindowInstance
): 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.notifyEvent('onResizeStart', windowInstance.id)
e.preventDefault()
e.stopPropagation()
})
}
/**
* 根据鼠标位置更新光标样式
*/
private updateCursorForResize(
e: MouseEvent,
windowElement: HTMLElement,
windowInstance: WindowInstance
): 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.notifyEvent('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.notifyEvent('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.notifyEvent('onWindowFormDataUpdate', data)
}
/**
* 加载应用
*/
private async loadApplication(windowInstance: WindowInstance): Promise<void> {
// 动态导入 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 = `
<div style="padding: 20px; font-family: sans-serif;">
<h2>应用: ${windowInstance.config.title}</h2>
<p>应用ID: ${windowInstance.appId}</p>
<p>窗体ID: ${windowInstance.id}</p>
<p>这是一个示例应用内容。</p>
</div>
`
}
}
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.notifyEvent('onStateChange', windowId, newState, oldState)
// 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId)
}
}