Files
vue-desktop/src/services/windowForm/WindowFormDataManager.ts
2025-10-23 12:14:03 +08:00

431 lines
9.5 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 { v4 as uuidv4 } from 'uuid'
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder'
/** 窗口状态枚举 */
export enum EWindowFormState {
/** 创建中 */
CREATING = 'creating',
/** 加载中 */
LOADING = 'loading',
/** 激活 */
ACTIVE = 'active',
/** 未激活 - 在后台,失去焦点 */
INACTIVE = 'inactive',
/** 最小化 */
MINIMIZED = 'minimized',
/** 最大化状态 */
MAXIMIZED = 'maximized',
/** 关闭中 */
CLOSING = 'closing',
/** 销毁 */
DESTROYED = 'destroyed',
/** 错误 */
ERROR = 'error'
}
/** 窗口系统事件接口 */
export interface IWindowFormEvents extends IEventMap {
onWindowFormDataUpdate: (data: {
id: string
state: EWindowFormState
width: number
height: number
x: number
y: number
}) => void
/**
* 窗体创建事件
*/
onCreating: (wid: string) => void
/**
* 窗体加载事件
*/
onLoading: (wid: string) => void
/**
* 窗体加载完成事件
*/
onLoaded: (wid: string) => void
/**
* 窗体聚焦事件
*/
onFocus: (wid: string) => void
/**
* 窗体失焦事件
*/
onBlur: (wid: string) => void
/**
* 窗体激活事件
*/
onActivate: (wid: string) => void
/**
* 窗体失活事件
*/
onDeactivate: (wid: string) => void
/**
* 窗体最小化事件
*/
onMinimize: (wid: string) => void
/**
* 窗体最大化事件
*/
onMaximize: (wid: string) => void
/**
* 窗体还原、恢复事件
*/
onRestore: (wid: string) => void
/**
* 窗体关闭前事件
* @param id 窗体ID
* @param cancel
*/
onBeforeClose: (id: string, cancel: () => void) => void
/**
* 窗体关闭事件
*/
onClose: (wid: string) => void
/**
* 窗体销毁事件
*/
onDestroy: (wid: string) => void
/**
* 窗体错误事件
*/
onError: (wid: string, error: Error) => void
}
/** 窗口配置参数 */
export interface IWindowFormConfig {
/**
* 窗体标题
*/
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 IWindowFormData {
/**
* 窗体标题
*/
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 IWindowFormInstance {
/** 窗口ID */
id: string
/** 应用ID */
appId: string
/** 窗口参数 */
config: IWindowFormData
/** 窗口状态 */
state: EWindowFormState
/** 窗口ZIndex */
zIndex: number
/** 创建时间 - 时间戳 */
createdAt: number
/** 更新时间 - 时间戳 */
updatedAt: number
/** 窗口DOM元素 */
element?: HTMLElement
/** 窗口iframe元素 */
iframe?: HTMLIFrameElement
/** 记录事件解绑函数 */
subscriptions: (() => void)[]
}
/**
* WindowFormDataManager
* -------------------
* 窗口数据与状态的中心管理器。
* - 管理 Map<id, IWindowFormInstance>
* - 控制 ZIndex
* - 管理状态变更与事件通知
*/
export class WindowFormDataManager {
private windowForms = new Map<string, IWindowFormInstance>()
private activeWindowId: string | null = null
private nextZIndex = 1000
private wfEventBus: IEventBuilder<IWindowFormEvents>
constructor(wfEventBus: IEventBuilder<IWindowFormEvents>) {
this.wfEventBus = wfEventBus
}
/** 创建窗口实例数据对象不含DOM */
async createWindowForm(appId: string, config: IWindowFormConfig): Promise<IWindowFormInstance> {
const id = uuidv4()
const now = new Date().getTime()
const mergeConfig: IWindowFormData = {
title: config.title ?? '窗口',
width: config.width ?? 300,
height: config.height ?? 300,
minWidth: config.minWidth ?? 0,
minHeight: config.minHeight ?? 0,
maxWidth: config.maxWidth ?? window.innerWidth,
maxHeight: config.maxHeight ?? window.innerHeight,
resizable: config.resizable ?? true,
movable: config.movable ?? true,
closable: config.closable ?? true,
minimizable: config.minimizable ?? true,
maximizable: config.maximizable ?? true,
modal: config.modal ?? false,
alwaysOnTop: config.alwaysOnTop ?? false,
x: config.x ?? 0,
y: config.y ?? 0
}
const instance: IWindowFormInstance = {
id,
appId,
config: mergeConfig,
state: EWindowFormState.CREATING,
zIndex: this.nextZIndex++,
createdAt: now,
updatedAt: now,
subscriptions: []
}
this.windowForms.set(id, instance)
return instance
}
/** 获取窗口实例 */
getWindowForm(windowId: string): IWindowFormInstance | undefined {
return this.windowForms.get(windowId)
}
/** 删除窗口实例 */
removeWindowForm(windowId: string) {
this.windowForms.delete(windowId)
}
/** 更新窗口状态 */
updateState(windowId: string, newState: EWindowFormState, error?: Error) {
const win = this.windowForms.get(windowId)
if (!win) return
const old = win.state
if (old === newState) return
win.state = newState
this.wfEventBus.notify('onStateChange', windowId, newState, old)
this.notifyUpdate(win)
this.transition(win, newState, error)
}
/** 生命周期状态分发器 */
private transition(win: IWindowFormInstance, newState: EWindowFormState, error?: Error) {
const id = win.id
switch (newState) {
case EWindowFormState.CREATING:
this.wfEventBus.notify('onCreating', id)
break
case EWindowFormState.LOADING:
this.wfEventBus.notify('onLoading', id)
break
case EWindowFormState.ACTIVE:
this.wfEventBus.notify('onActivate', id)
break
case EWindowFormState.INACTIVE:
this.wfEventBus.notify('onDeactivate', id)
break
case EWindowFormState.MINIMIZED:
this.wfEventBus.notify('onMinimize', id)
break
case EWindowFormState.MAXIMIZED:
this.wfEventBus.notify('onMaximize', id)
break
case EWindowFormState.CLOSING:
this.wfEventBus.notify('onClose', id)
break
case EWindowFormState.DESTROYED:
this.wfEventBus.notify('onDestroy', id)
break
case EWindowFormState.ERROR:
this.wfEventBus.notify('onError', id, error ?? new Error('未知错误'))
break
}
}
/** 聚焦窗口 */
focus(windowId: string) {
const win = this.windowForms.get(windowId)
if (!win) return
this.activeWindowId = windowId
win.zIndex = this.nextZIndex++
if (win.element) win.element.style.zIndex = `${win.zIndex}`
this.wfEventBus.notify('onFocus', windowId)
this.notifyUpdate(win)
}
/** 最小化窗口 */
minimize(windowId: string) {
const win = this.windowForms.get(windowId)
if (!win || !win.element) return
win.element.style.display = 'none'
this.updateState(windowId, EWindowFormState.MINIMIZED)
}
/** 最大化窗口 */
maximize(windowId: string) {
const win = this.windowForms.get(windowId)
if (!win || !win.element) return
win.element.dataset.originalWidth = win.element.style.width
win.element.dataset.originalHeight = win.element.style.height
win.element.style.position = 'fixed'
Object.assign(win.element.style, {
top: '0',
left: '0',
width: '100vw',
height: '100vh'
})
this.updateState(windowId, EWindowFormState.MAXIMIZED)
}
/** 还原窗口 */
restore(windowId: string) {
const win = this.windowForms.get(windowId)
if (!win || !win.element) return
Object.assign(win.element.style, {
position: 'absolute',
width: win.element.dataset.originalWidth,
height: win.element.dataset.originalHeight
})
this.updateState(windowId, EWindowFormState.ACTIVE)
}
/** 通知窗口数据更新 */
private notifyUpdate(win: IWindowFormInstance) {
const rect = win.element?.getBoundingClientRect()
if (!rect) return
this.wfEventBus.notify('onWindowFormDataUpdate', {
id: win.id,
state: win.state,
width: rect.width,
height: rect.height,
x: rect.left,
y: rect.top
})
}
}