431 lines
9.5 KiB
TypeScript
431 lines
9.5 KiB
TypeScript
|
|
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
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|