保存
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { WindowConfig } from '@/services/WindowFormService.ts'
|
||||
import type { WindowConfig } from '@/services/windowForm/WindowFormService.ts'
|
||||
|
||||
/**
|
||||
* 内置应用清单接口
|
||||
|
||||
@@ -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('单例模式装饰器只能修饰在类上')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)!
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
430
src/services/windowForm/WindowFormDataManager.ts
Normal file
430
src/services/windowForm/WindowFormDataManager.ts
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
79
src/services/windowForm/WindowFormEventBinder.ts
Normal file
79
src/services/windowForm/WindowFormEventBinder.ts
Normal 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() {}
|
||||
}
|
||||
140
src/services/windowForm/WindowFormRenderer.ts
Normal file
140
src/services/windowForm/WindowFormRenderer.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
119
src/services/windowForm/WindowFormService.ts
Normal file
119
src/services/windowForm/WindowFormService.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
27
src/services/windowForm/utils.ts
Normal file
27
src/services/windowForm/utils.ts
Normal 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())
|
||||
}
|
||||
Reference in New Issue
Block a user