This commit is contained in:
2025-10-23 12:14:03 +08:00
parent 900a72e4c9
commit f9fdb1a6c2
49 changed files with 851 additions and 8129 deletions

View File

@@ -1,4 +1,4 @@
import type { WindowConfig } from '@/services/WindowFormService.ts'
import type { WindowConfig } from '@/services/windowForm/WindowFormService.ts'
/**
* 内置应用清单接口

View File

@@ -2,7 +2,7 @@
* @param clz
* @returns
*/
export function singtonPattern<T extends new (...args: any[]) => any>(clz: T) {
export function SingletonPattern<T extends new (...args: any[]) => any>(clz: T) {
// 添加判断,确保装饰器只能用于类
if (typeof clz !== 'function') {
throw new Error('单例模式装饰器只能修饰在类上')

View File

@@ -1,5 +1,5 @@
import type { IDestroyable } from '@/common/types/IDestroyable'
import type { WindowState } from '@/services/WindowFormService.ts'
import type { WindowState } from '@/services/windowForm/WindowFormService.ts'
import type { ResourceType } from '@/services/ResourceService'
/**
@@ -56,13 +56,13 @@ export interface ISystemBuiltInEventMap extends IEventMap {
*/
export interface IEventBuilder<Events extends IEventMap> extends IDestroyable {
/**
* 添加事件监听
* 订阅事件
* @param eventName 事件名称
* @param handler 事件处理函数
* @param options 配置项 { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
* @returns void
*/
addEventListener<E extends keyof Events, F extends Events[E]>(
subscribe<E extends keyof Events, F extends Events[E]>(
eventName: E,
handler: F,
options?: {
@@ -70,15 +70,15 @@ export interface IEventBuilder<Events extends IEventMap> extends IDestroyable {
immediateArgs?: Parameters<F>
once?: boolean
}
): void
): () => void
/**
* 移除事件监听
* 移除事件
* @param eventName 事件名称
* @param handler 事件处理函数
* @returns void
*/
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F): void
remove<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F): void
/**
* 触发事件
@@ -86,7 +86,7 @@ export interface IEventBuilder<Events extends IEventMap> extends IDestroyable {
* @param args 参数
* @returns void
*/
notifyEvent<E extends keyof Events, F extends Events[E]>(
notify<E extends keyof Events, F extends Events[E]>(
eventName: E,
...args: Parameters<F>
): void

View File

@@ -13,12 +13,13 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
* @param eventName 事件名称
* @param handler 监听器
* @param options { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
* @returns 返回一个 `unsubscribe` 函数,用于移除当前监听
* @example
* eventBus.addEventListener('noArgs', () => {})
* eventBus.addEventListener('greet', name => {}, { immediate: true, immediateArgs: ['abc'] })
* eventBus.addEventListener('onResize', (w, h) => {}, { immediate: true, immediateArgs: [1, 2] })
* eventBus.subscribe('noArgs', () => {})
* eventBus.subscribe('greet', name => {}, { immediate: true, immediateArgs: ['abc'] })
* eventBus.subscribe('onResize', (w, h) => {}, { immediate: true, immediateArgs: [1, 2] })
*/
addEventListener<E extends keyof Events, F extends Events[E]>(
subscribe<E extends keyof Events, F extends Events[E]>(
eventName: E,
handler: F,
options?: {
@@ -26,15 +27,16 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
immediateArgs?: Parameters<F>
once?: boolean
},
) {
if (!handler) return
): () => void {
if (!handler) return () => {}
if (!this._eventHandlers.has(eventName)) {
this._eventHandlers.set(eventName, new Set<HandlerWrapper<F>>())
}
const set = this._eventHandlers.get(eventName)!
const wrapper: HandlerWrapper<F> = { fn: handler, once: options?.once ?? false }
if (![...set].some((wrapper) => wrapper.fn === handler)) {
set.add({ fn: handler, once: options?.once ?? false })
set.add(wrapper)
}
if (options?.immediate) {
@@ -44,6 +46,12 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
console.error(e)
}
}
return () => {
set.delete(wrapper)
// 如果该事件下无监听器,则删除集合
if (set.size === 0) this._eventHandlers.delete(eventName)
}
}
/**
@@ -51,9 +59,9 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
* @param eventName 事件名称
* @param handler 监听器
* @example
* eventBus.removeEventListener('noArgs', () => {})
* eventBus.remove('noArgs', () => {})
*/
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F) {
remove<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F) {
const set = this._eventHandlers.get(eventName)
if (!set) return
@@ -62,6 +70,9 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
set.delete(wrapper)
}
}
if (set.size === 0) {
this._eventHandlers.delete(eventName)
}
}
/**
@@ -69,11 +80,11 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
* @param eventName 事件名称
* @param args 参数
* @example
* eventBus.notifyEvent('noArgs')
* eventBus.notifyEvent('greet', 'Alice')
* eventBus.notifyEvent('onResize', 1, 2)
* eventBus.notify('noArgs')
* eventBus.notify('greet', 'Alice')
* eventBus.notify('onResize', 1, 2)
*/
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>) {
notify<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>) {
if (!this._eventHandlers.has(eventName)) return
const set = this._eventHandlers.get(eventName)!

View File

@@ -191,11 +191,11 @@ class StorageSDKImpl extends SDKBase implements StorageSDK {
}
async get<T = any>(key: string): Promise<APIResponse<T | null>> {
return this.wrapResponse(this.sendToSystem('storage.get', { key }))
return this.wrapResponse(this.sendToSystem('storage.getWindowForm', { key }))
}
async remove(key: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('storage.remove', { key }))
return this.wrapResponse(this.sendToSystem('storage.removeWindowForm', { key }))
}
async clear(): Promise<APIResponse<boolean>> {

View File

@@ -3,7 +3,7 @@ import type { ResourceService } from './ResourceService'
import type { ApplicationSandboxEngine } from './ApplicationSandboxEngine'
import { v4 as uuidv4 } from 'uuid'
import { externalAppDiscovery } from './ExternalAppDiscovery'
import type { WindowFormService } from '@/services/WindowFormService.ts'
import type { WindowFormService } from '@/services/windowForm/WindowFormService.ts'
import { type AppRegistration, appRegistry } from '@/apps'
import windowManager from '@/ui/components/WindowManager.vue'

View File

@@ -148,7 +148,7 @@ export class ResourceService {
}
// 触发权限请求事件UI层处理用户确认
this.eventBus.notifyEvent('onPermissionRequest', request)
this.eventBus.notify('onPermissionRequest', request)
// 根据资源类型的默认策略处理
return this.handlePermissionRequest(request)
@@ -170,7 +170,7 @@ export class ResourceService {
}
this.setPermission(appId, resourceType, request)
this.eventBus.notifyEvent('onPermissionGranted', appId, resourceType)
this.eventBus.notify('onPermissionGranted', appId, resourceType)
return true
} catch (error) {
@@ -191,7 +191,7 @@ export class ResourceService {
request.deniedAt = new Date()
this.setPermission(appId, resourceType, request)
this.eventBus.notifyEvent('onPermissionDenied', appId, resourceType)
this.eventBus.notify('onPermissionDenied', appId, resourceType)
return true
} catch (error) {
@@ -244,7 +244,7 @@ export class ResourceService {
const valueSize = new Blob([serializedValue]).size / (1024 * 1024) // MB
if (usage.usedSpace + valueSize > usage.maxSpace) {
this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.LOCAL_STORAGE)
this.eventBus.notify('onResourceQuotaExceeded', appId, ResourceType.LOCAL_STORAGE)
return false
}
@@ -373,7 +373,7 @@ export class ResourceService {
// 检查请求频率限制
if (!this.checkNetworkRateLimit(appId)) {
this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.NETWORK)
this.eventBus.notify('onResourceQuotaExceeded', appId, ResourceType.NETWORK)
return null
}
@@ -393,7 +393,7 @@ export class ResourceService {
requestRecord.responseSize = parseInt(response.headers.get('content-length') || '0')
this.recordNetworkRequest(requestRecord)
this.eventBus.notifyEvent('onNetworkRequest', requestRecord)
this.eventBus.notify('onNetworkRequest', requestRecord)
return response
} catch (error) {
@@ -657,7 +657,7 @@ export class ResourceService {
usage.usedSpace = usedSpace / (1024 * 1024) // 转换为MB
this.eventBus.notifyEvent('onStorageChange', appId, usage)
this.eventBus.notify('onStorageChange', appId, usage)
}
/**
@@ -668,7 +668,7 @@ export class ResourceService {
usage.usedSpace = 0
usage.lastAccessed = new Date()
this.eventBus.notifyEvent('onStorageChange', appId, usage)
this.eventBus.notify('onStorageChange', appId, usage)
}
/**

View File

@@ -8,7 +8,7 @@ import type {
import type { ResourceType } from './ResourceService'
// 导入所有服务
import { WindowFormService } from './WindowFormService.ts'
import { WindowFormService } from './windowForm/WindowFormService.ts'
import { ResourceService } from './ResourceService'
import { ApplicationSandboxEngine } from './ApplicationSandboxEngine'
import { ApplicationLifecycleManager } from './ApplicationLifecycleManager'
@@ -310,7 +310,7 @@ export class SystemServiceIntegration {
*/
private setupServiceCommunication(): void {
// 监听窗体状态变化(来自 windowFormService 的 onStateChange 事件)
this.eventBus.addEventListener(
this.eventBus.subscribe(
'onWindowStateChanged',
(windowId: string, newState: string, oldState: string) => {
console.log(
@@ -320,7 +320,7 @@ export class SystemServiceIntegration {
)
// 监听窗体关闭事件,自动停止对应的应用
this.eventBus.addEventListener('onWindowClose', async (windowId: string) => {
this.eventBus.subscribe('onWindowClose', async (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体关闭事件: ${windowId}`)
// 查找对应的应用
const runningApps = this.lifecycleManager.getRunningApps()
@@ -338,17 +338,17 @@ export class SystemServiceIntegration {
})
// 监听窗体数据更新事件
this.eventBus.addEventListener('onWindowFormDataUpdate', (data: IWindowFormDataUpdateParams) => {
this.eventBus.subscribe('onWindowFormDataUpdate', (data: IWindowFormDataUpdateParams) => {
console.log(`[SystemIntegration] 接收到窗体数据更新事件:`, data)
})
// 监听窗体调整尺寸开始事件
this.eventBus.addEventListener('onWindowFormResizeStart', (windowId: string) => {
this.eventBus.subscribe('onWindowFormResizeStart', (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸开始事件: ${windowId}`)
})
// 监听窗体调整尺寸过程中事件
this.eventBus.addEventListener(
this.eventBus.subscribe(
'onWindowFormResizing',
(windowId: string, width: number, height: number) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸过程中事件: ${windowId}`, {
@@ -359,7 +359,7 @@ export class SystemServiceIntegration {
)
// 监听窗体调整尺寸结束事件
this.eventBus.addEventListener('onWindowFormResizeEnd', (windowId: string) => {
this.eventBus.subscribe('onWindowFormResizeEnd', (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸结束事件: ${windowId}`)
})
}
@@ -422,7 +422,7 @@ export class SystemServiceIntegration {
try {
switch (type) {
case 'sdk:storage:get':
case 'sdk:storage:getWindowForm':
result = await this.resourceService.getStorage(appId, key)
success = true
break
@@ -432,7 +432,7 @@ export class SystemServiceIntegration {
success = result === true
break
case 'sdk:storage:remove':
case 'sdk:storage:removeWindowForm':
result = await this.resourceService.removeStorage(appId, key)
success = result === true
break
@@ -566,7 +566,7 @@ export class SystemServiceIntegration {
case 'get':
return this.resourceService.getStorage(appId, data.key)
case 'remove':
case 'removeWindowForm':
return this.resourceService.removeStorage(appId, data.key)
case 'clear':

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,430 @@
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
})
}
}

View File

@@ -0,0 +1,79 @@
import type { IEventBuilder } from '@/events/IEventBuilder'
import {
EWindowFormState,
type IWindowFormEvents,
type IWindowFormInstance,
WindowFormDataManager
} from '@/services/windowForm/WindowFormDataManager.ts'
/**
* WindowFormEventBinder
* ----------------------
* 管理拖拽、最小化、关闭等窗口交互事件。
*/
export class WindowFormEventBinder {
private eventBus: IEventBuilder<IWindowFormEvents>
private manager: WindowFormDataManager
constructor(eventBus: IEventBuilder<IWindowFormEvents>, manager: WindowFormDataManager) {
this.eventBus = eventBus
this.manager = manager
}
/** 为窗口绑定交互事件 */
bindWindowEvents(win: IWindowFormInstance) {
const bar = win.element?.querySelector('.window-title-bar') as HTMLElement
const btns = bar?.querySelectorAll('button[data-action]')
if (btns) {
btns.forEach(btn => {
const action = btn.getAttribute('data-action')
if (action === 'min') {
btn.addEventListener('click', () => this.manager.minimize(win.id))
} else if (action === 'max') {
btn.addEventListener('click', () => {
if (win.state === EWindowFormState.MAXIMIZED)
this.manager.restore(win.id)
else
this.manager.maximize(win.id)
})
} else if (action === 'close') {
btn.addEventListener('click', () => {
this.manager.updateState(win.id, EWindowFormState.CLOSING)
})
}
})
}
// 拖动事件
bar?.addEventListener('mousedown', e => this.startDrag(e, win))
}
/** 拖拽实现 */
private startDrag(e: MouseEvent, win: IWindowFormInstance) {
if (!win.element) return
const rect = win.element.getBoundingClientRect()
const startX = e.clientX
const startY = e.clientY
const baseX = rect.left
const baseY = rect.top
const move = (ev: MouseEvent) => {
const dx = ev.clientX - startX
const dy = ev.clientY - startY
const nx = baseX + dx
const ny = baseY + dy
win.element!.style.left = `${nx}px`
win.element!.style.top = `${ny}px`
}
const up = () => {
document.removeEventListener('mousemove', move)
document.removeEventListener('mouseup', up)
}
document.addEventListener('mousemove', move)
document.addEventListener('mouseup', up)
}
dispose() {}
}

View File

@@ -0,0 +1,140 @@
import type { IEventBuilder } from '@/events/IEventBuilder'
import type { IWindowFormEvents, IWindowFormInstance } from './WindowFormDataManager.ts'
import { safeSubscribe } from '@/services/windowForm/utils.ts'
/**
* WindowFormRenderer
* ----------------------
* 负责窗口的DOM创建、销毁与视觉状态更新。
*/
export class WindowFormRenderer {
private eventBus: IEventBuilder<IWindowFormEvents>
constructor(eventBus: IEventBuilder<IWindowFormEvents>) {
this.eventBus = eventBus
}
/** 创建窗口DOM结构 */
async createWindowElement(win: IWindowFormInstance) {
const el = document.createElement('div')
el.className = 'system-window'
el.dataset.id = win.id
el.style.cssText = `
position:absolute;
width:${win.config.width}px;
height:${win.config.height}px;
left:${win.config.x ?? 200}px;
top:${win.config.y ?? 150}px;
background:#fff;
border-radius:8px;
border:1px solid #ccc;
box-shadow:0 4px 20px rgba(0,0,0,.15);
overflow:hidden;
transition:all .15s ease;
`
// 标题栏
const titleBar = this.createTitleBar(win)
const content = document.createElement('div')
content.className = 'window-content'
content.style.cssText = `width:100%;height:calc(100% - 40px);overflow:hidden;`
content.innerHTML = `<div class="loading-state">加载中...</div>`
el.append(titleBar, content)
document.body.appendChild(el)
win.element = el
// 生命周期UI响应
safeSubscribe(win, this.eventBus, 'onLoadStart', id => {
if (id === win.id) this.showLoading(win)
})
safeSubscribe(win, this.eventBus, 'onLoaded', id => {
if (id === win.id) this.hideLoading(win)
})
safeSubscribe(win, this.eventBus, 'onError', (id, err) => {
if (id === win.id) this.showError(win, err)
})
safeSubscribe(win, this.eventBus, 'onDestroy', id => {
if (id === win.id) this.destroy(win)
})
}
/** 创建标题栏 */
private createTitleBar(win: IWindowFormInstance): HTMLElement {
const bar = document.createElement('div')
bar.className = 'window-title-bar'
bar.style.cssText = `
height:40px;
display:flex;
align-items:center;
justify-content:space-between;
background:linear-gradient(to bottom,#f8f9fa,#e9ecef);
border-bottom:1px solid #dee2e6;
padding:0 12px;
user-select:none;
cursor:move;
`
const title = document.createElement('span')
title.textContent = win.config.title ?? ''
title.style.fontWeight = '500'
const ctrl = document.createElement('div')
ctrl.style.display = 'flex'
ctrl.style.gap = '6px'
const makeBtn = (label: string, action: string) => {
const b = document.createElement('button')
b.dataset.action = action
b.textContent = label
b.style.cssText = `
width:24px;height:24px;border:none;
background:transparent;cursor:pointer;
border-radius:4px;font-size:14px;
`
b.addEventListener('mouseenter', () => (b.style.background = '#e9ecef'))
b.addEventListener('mouseleave', () => (b.style.background = 'transparent'))
return b
}
ctrl.append(makeBtn('', 'min'), makeBtn('□', 'max'), makeBtn('×', 'close'))
bar.append(title, ctrl)
return bar
}
/** 显示加载状态 */
private showLoading(win: IWindowFormInstance) {
const el = win.element?.querySelector('.loading-state') as HTMLElement
if (el) el.style.display = 'block'
}
/** 隐藏加载状态 */
private hideLoading(win: IWindowFormInstance) {
const el = win.element?.querySelector('.loading-state') as HTMLElement
if (el) el.style.display = 'none'
}
/** 显示错误UI */
private showError(win: IWindowFormInstance, error: Error) {
const content = win.element?.querySelector('.window-content')
if (content) {
content.innerHTML = `
<div style="padding:20px;color:#d00">
<strong>加载失败:</strong> ${error.message}
</div>
`
}
}
/** 销毁窗口DOM */
destroy(win: IWindowFormInstance) {
// 解绑所有事件
win.subscriptions.forEach(unsub => unsub())
win.subscriptions = []
// 渐隐销毁DOM
if (win.element) {
win.element.style.transition = 'opacity .2s ease'
win.element.style.opacity = '0'
setTimeout(() => win.element?.remove(), 200)
}
}
}

View File

@@ -0,0 +1,119 @@
import type { IEventBuilder } from '@/events/IEventBuilder'
import {
type IWindowFormConfig,
type IWindowFormEvents,
type IWindowFormInstance,
WindowFormDataManager,
EWindowFormState
} from './WindowFormDataManager.ts'
import { WindowFormRenderer } from './WindowFormRenderer'
import { WindowFormEventBinder } from './WindowFormEventBinder'
import { EventBuilderImpl } from '@/events/impl/EventBuilderImpl.ts'
/**
* WindowFormService
* ----------------------
* 框架入口类,整合 Manager、Renderer、EventBinder 三个子模块,
* 对外提供统一的窗口系统接口(创建、销毁、最小化、最大化、聚焦等)。
*/
export class WindowFormService {
private readonly dataManager: WindowFormDataManager
private renderer: WindowFormRenderer
private eventBinder: WindowFormEventBinder
// 窗口内部事件总线
private readonly wfEventBus: IEventBuilder<IWindowFormEvents>
constructor(eventBus: IEventBuilder<IWindowFormEvents>) {
this.wfEventBus = new EventBuilderImpl<IWindowFormEvents>()
// 1 初始化窗口数据管理器
this.dataManager = new WindowFormDataManager(this.wfEventBus)
// 2 初始化窗口渲染器
this.renderer = new WindowFormRenderer(this.wfEventBus)
// 3 初始化事件绑定模块(拖拽 + 调整大小)
this.eventBinder = new WindowFormEventBinder(this.wfEventBus, this.dataManager)
}
/**
* 创建一个窗口实例
* @param appId 应用ID
* @param config 应用窗口配置
*/
async createWindow(appId: string, config: IWindowFormConfig): Promise<IWindowFormInstance> {
// 1 创建数据实例
const instance = await this.dataManager.createWindowForm(appId, config)
// 2 加载阶段
this.dataManager.updateState(instance.id, EWindowFormState.LOADING)
try {
// 3 渲染DOM
await this.renderer.createWindowElement(instance)
// 4 绑定交互事件
this.eventBinder.bindWindowEvents(instance)
// 5 模拟加载完成后激活
this.dataManager.updateState(instance.id, EWindowFormState.ACTIVE)
this.dataManager.focus(instance.id)
this.wfEventBus.notify('onLoaded', instance.id)
return instance
} catch (err) {
// 异常状态
this.dataManager.updateState(instance.id, EWindowFormState.ERROR, err as Error)
throw err
}
}
/** 销毁窗口 */
async destroyWindow(windowId: string): Promise<void> {
const win = this.dataManager.getWindowForm(windowId)
if (!win) return
this.dataManager.updateState(windowId, EWindowFormState.CLOSING)
// 监听 onBeforeClose 的 cancel 回调
let isCanceled = false
this.wfEventBus.notify('onBeforeClose', windowId, () => {
isCanceled = true
})
if (isCanceled) return
// 确保在 DOM 销毁前清理事件
win.subscriptions.forEach(unsub => unsub())
win.subscriptions = []
this.renderer.destroy(win)
this.dataManager.updateState(windowId, EWindowFormState.DESTROYED)
this.dataManager.removeWindowForm(windowId)
}
getWindow(windowId: string): IWindowFormInstance | undefined {
return this.dataManager.getWindowForm(windowId)
}
/** 最小化窗口 */
minimize(windowId: string) {
this.dataManager.minimize(windowId)
}
/** 最大化窗口 */
maximize(windowId: string) {
this.dataManager.maximize(windowId)
}
/** 还原窗口 */
restore(windowId: string) {
this.dataManager.restore(windowId)
}
/** 聚焦窗口 */
focus(windowId: string) {
this.dataManager.focus(windowId)
}
/** 销毁全局监听器(用于系统卸载时) */
dispose() {
this.eventBinder.dispose()
}
}

View File

@@ -0,0 +1,27 @@
import {
EWindowFormState,
type IWindowFormEvents,
type IWindowFormInstance
} from '@/services/windowForm/WindowFormDataManager.ts'
import type { IEventBuilder } from '@/events/IEventBuilder.ts'
/**
* 订阅事件,并自动取消订阅
* @param win
* @param eventBus
* @param event
* @param handler
*/
export function safeSubscribe(
win: IWindowFormInstance,
eventBus: IEventBuilder<IWindowFormEvents>,
event: keyof IWindowFormEvents,
handler: (...args: any[]) => void
) {
const unsubscribe = eventBus.subscribe(event, (...args: any[]) => {
// 若窗口已销毁则不再执行
if (win.state === EWindowFormState.DESTROYED) return
handler(...args)
})
win.subscriptions.push(() => unsubscribe())
}