Compare commits

..

24 Commits

Author SHA1 Message Date
a102395286 更新vite 2025-11-27 14:15:06 +08:00
81007cf938 修改组件逻辑+优化代码 2025-11-11 17:32:41 +08:00
f2bb7f3196 修改组件逻辑+优化代码 2025-11-11 17:20:43 +08:00
ce688a6834 保存 2025-10-24 11:07:27 +08:00
ced6786f86 保存 2025-10-24 10:03:54 +08:00
f2b12fbaf5 保存 2025-10-23 12:14:10 +08:00
f9fdb1a6c2 保存 2025-10-23 12:14:03 +08:00
900a72e4c9 单例模式 装饰器 2025-10-18 20:10:32 +08:00
7b12efd09c 保存 2025-10-18 19:12:18 +08:00
e54bd0a447 之前的 2025-10-11 12:10:35 +08:00
8d25c143c5 111 2025-10-11 12:05:57 +08:00
45ec0fd021 error handle 2025-10-11 11:40:41 +08:00
49d7f2c37e di 2025-10-11 11:30:44 +08:00
9a90f1258b 1 2025-10-10 13:49:55 +08:00
acecffb055 ` 2025-10-10 11:25:50 +08:00
0ca5daad3b 111 2025-10-10 10:37:11 +08:00
05882bb3d3 · 2025-10-10 10:36:53 +08:00
71d5aabb84 1 2025-10-10 10:28:36 +08:00
204dd4781b 11 2025-10-10 10:08:05 +08:00
ed0527bf27 1 2025-09-28 14:51:32 +08:00
a56197a349 1 2025-09-28 14:32:07 +08:00
f1ba609254 1 2025-09-28 14:32:00 +08:00
7b1dff9ea1 11 2025-09-28 12:34:28 +08:00
b77a20f9b0 1 2025-09-28 12:05:59 +08:00
69 changed files with 2972 additions and 9102 deletions

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ coverage
*.sw? *.sw?
*.tsbuildinfo *.tsbuildinfo
.qoder/*

View File

@@ -2,5 +2,6 @@
"$schema": "https://json.schemastore.org/prettierrc", "$schema": "https://json.schemastore.org/prettierrc",
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true,
"printWidth": 100 "printWidth": 100,
"trailingComma": "none"
} }

View File

@@ -1,338 +0,0 @@
# API接口字段中文注释设计文档
## 1. 概述
本文档旨在为项目中所有API接口的字段添加中文注释以提高代码的可读性和可维护性。通过对SDK、服务层、事件系统等模块中的接口定义进行中文注释使开发人员能够更快速地理解和使用这些接口。
## 2. 设计原则
- 保持原有代码结构不变,仅添加中文注释
- 注释内容准确描述字段的含义和用途
- 统一注释风格,确保文档一致性
- 重点注释复杂或容易误解的字段
## 3. 接口字段注释规范
### 3.1 基本注释格式
```typescript
/**
* 字段中文描述
*/
fieldName: fieldType
```
### 3.2 枚举类型注释格式
```typescript
/**
* 枚举类型中文描述
*/
export enum EnumName {
/**
* 枚举值中文描述
*/
VALUE1 = 'value1',
/**
* 枚举值中文描述
*/
VALUE2 = 'value2',
}
```
## 4. SDK接口字段注释
### 4.1 核心类型定义
#### SDKConfig接口
| 字段名 | 类型 | 中文注释 |
| ----------- | -------------- | ---------------- |
| appId | string | 应用唯一标识符 |
| appName | string | 应用名称 |
| version | string | 应用版本号 |
| permissions | string[] | 应用所需权限列表 |
| debug | boolean (可选) | 是否开启调试模式 |
#### APIResponse接口
| 字段名 | 类型 | 中文注释 |
| ------- | ------------- | -------------- |
| success | boolean | 请求是否成功 |
| data | T (可选) | 返回的数据内容 |
| error | string (可选) | 错误信息 |
| code | number (可选) | 错误码 |
### 4.2 窗体SDK接口字段注释
#### WindowState枚举
| 枚举值 | 中文注释 |
| ---------- | ---------- |
| NORMAL | 正常状态 |
| MINIMIZED | 最小化状态 |
| MAXIMIZED | 最大化状态 |
| FULLSCREEN | 全屏状态 |
#### WindowEvents接口
| 字段名 | 类型 | 中文注释 |
| ------------- | --------------------------------------- | -------------------- |
| onResize | (width: number, height: number) => void | 窗体尺寸变化事件回调 |
| onMove | (x: number, y: number) => void | 窗体位置移动事件回调 |
| onStateChange | (state: WindowState) => void | 窗体状态变化事件回调 |
| onFocus | () => void | 窗体获得焦点事件回调 |
| onBlur | () => void | 窗体失去焦点事件回调 |
| onClose | () => void | 窗体关闭事件回调 |
### 4.3 存储SDK接口字段注释
#### StorageStats接口
| 字段名 | 类型 | 中文注释 |
| ------------ | ------ | ------------------ |
| usedSpace | number | 已使用存储空间(MB) |
| maxSpace | number | 最大存储空间(MB) |
| keysCount | number | 存储键数量 |
| lastAccessed | Date | 最后访问时间 |
### 4.4 网络SDK接口字段注释
#### NetworkRequestConfig接口
| 字段名 | 类型 | 中文注释 |
| ------------ | -------------------------------------------------- | ------------------ |
| method | HTTPMethod (可选) | HTTP请求方法 |
| headers | Record<string, string> (可选) | 请求头信息 |
| body | any (可选) | 请求体数据 |
| timeout | number (可选) | 请求超时时间(毫秒) |
| responseType | 'json' \| 'text' \| 'blob' \| 'arrayBuffer' (可选) | 响应数据类型 |
#### NetworkResponse接口
| 字段名 | 类型 | 中文注释 |
| ---------- | ---------------------- | ------------ |
| data | T | 响应数据内容 |
| status | number | HTTP状态码 |
| statusText | string | HTTP状态文本 |
| headers | Record<string, string> | 响应头信息 |
| url | string | 请求URL |
### 4.5 事件SDK接口字段注释
#### EventMessage接口
| 字段名 | 类型 | 中文注释 |
| --------- | ------ | -------------- |
| id | string | 消息唯一标识符 |
| channel | string | 事件频道名称 |
| data | T | 消息数据内容 |
| senderId | string | 发送方标识符 |
| timestamp | Date | 消息发送时间戳 |
#### EventSubscriptionConfig接口
| 字段名 | 类型 | 中文注释 |
| ------ | ----------------------------------------- | -------------- |
| filter | (message: EventMessage) => boolean (可选) | 消息过滤器函数 |
| once | boolean (可选) | 是否只监听一次 |
### 4.6 UI SDK接口字段注释
#### DialogOptions接口
| 字段名 | 类型 | 中文注释 |
| ------------- | ----------------- | ------------------ |
| title | string (可选) | 对话框标题 |
| message | string | 对话框消息内容 |
| type | DialogType (可选) | 对话框类型 |
| buttons | string[] (可选) | 自定义按钮文本数组 |
| defaultButton | number (可选) | 默认按钮索引 |
| cancelButton | number (可选) | 取消按钮索引 |
#### NotificationOptions接口
| 字段名 | 类型 | 中文注释 |
| -------- | ----------------------------------------------- | -------------- |
| title | string | 通知标题 |
| body | string | 通知正文内容 |
| icon | string (可选) | 通知图标URL |
| duration | number (可选) | 显示时长(毫秒) |
| actions | Array<{ title: string; action: string }> (可选) | 通知操作按钮 |
### 4.7 系统SDK接口字段注释
#### SystemInfo接口
| 字段名 | 类型 | 中文注释 |
| ---------------- | --------------------------------- | -------------- |
| platform | string | 运行平台信息 |
| userAgent | string | 用户代理字符串 |
| language | string | 系统语言设置 |
| timezone | string | 时区信息 |
| screenResolution | { width: number; height: number } | 屏幕分辨率 |
| colorDepth | number | 颜色深度 |
| pixelRatio | number | 像素比率 |
#### AppInfo接口
| 字段名 | 类型 | 中文注释 |
| ------------ | -------- | ---------------- |
| id | string | 应用唯一标识符 |
| name | string | 应用名称 |
| version | string | 应用版本号 |
| permissions | string[] | 应用权限列表 |
| createdAt | Date | 应用创建时间 |
| lastActiveAt | Date | 应用最后活跃时间 |
## 5. 服务层接口字段注释
### 5.1 窗体服务接口字段注释
#### 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 | number (可选) | 窗体X坐标位置 |
| y | number (可选) | 窗体Y坐标位置 |
#### WindowInstance接口
| 字段名 | 类型 | 中文注释 |
| --------- | ------------------------ | ------------------ |
| id | string | 窗体唯一标识符 |
| appId | string | 关联应用标识符 |
| config | WindowConfig | 窗体配置信息 |
| state | WindowState | 窗体当前状态 |
| element | HTMLElement (可选) | 窗体DOM元素 |
| iframe | HTMLIFrameElement (可选) | 窗体内嵌iframe元素 |
| zIndex | number | 窗体层级索引 |
| createdAt | Date | 窗体创建时间 |
| updatedAt | Date | 窗体更新时间 |
## 6. 事件系统接口字段注释
### 6.1 事件管理器接口字段注释
#### IEventMap接口
| 字段名 | 类型 | 中文注释 |
| ------------- | ------------------------ | ---------------- |
| [key: string] | (...args: any[]) => void | 事件处理函数映射 |
#### IEventBuilder接口
该接口定义了事件管理器的基本方法,无需额外字段注释。
### 6.2 窗口事件接口字段注释
#### IWindowFormEvent接口
| 字段名 | 类型 | 中文注释 |
| -------------------- | ------------------------------------------- | ---------------- |
| windowFormMinimize | (id: string) => void | 窗口最小化事件 |
| windowFormMaximize | (id: string) => void | 窗口最大化事件 |
| windowFormRestore | (id: string) => void | 窗口还原事件 |
| windowFormClose | (id: string) => void | 窗口关闭事件 |
| windowFormFocus | (id: string) => void | 窗口聚焦事件 |
| windowFormDataUpdate | (data: IWindowFormDataUpdateParams) => void | 窗口数据更新事件 |
| windowFormCreated | () => void | 窗口创建完成事件 |
#### IWindowFormDataUpdateParams接口
| 字段名 | 类型 | 中文注释 |
| ------ | ---------------- | ----------------- |
| id | string | 窗口唯一标识符 |
| state | TWindowFormState | 窗口状态 |
| width | number | 窗口宽度 |
| height | number | 窗口高度 |
| x | number | 窗口X坐标(左上角) |
| y | number | 窗口Y坐标(左上角) |
## 7. 应用管理接口字段注释
### 7.1 应用清单接口字段注释
#### InternalAppManifest接口
| 字段名 | 类型 | 中文注释 |
| ----------- | -------------------------------------- | ---------------- |
| id | string | 应用唯一标识符 |
| name | string | 应用名称 |
| version | string | 应用版本号 |
| description | string | 应用描述信息 |
| author | string | 应用作者 |
| icon | string | 应用图标 |
| permissions | string[] | 应用所需权限列表 |
| window | { width: number; height: number; ... } | 窗体配置信息 |
| category | string (可选) | 应用分类 |
| keywords | string[] (可选) | 应用关键字列表 |
#### AppRegistration接口
| 字段名 | 类型 | 中文注释 |
| --------- | ------------------- | -------------- |
| manifest | InternalAppManifest | 应用清单信息 |
| component | any | Vue组件 |
| isBuiltIn | boolean | 是否为内置应用 |
## 8. UI组件接口字段注释
### 8.1 桌面图标接口字段注释
#### IDesktopAppIcon接口
| 字段名 | 类型 | 中文注释 |
| ------ | ------ | -------------------- |
| name | string | 图标名称 |
| icon | string | 图标内容 |
| path | string | 图标路径 |
| x | number | 图标在grid布局中的列 |
| y | number | 图标在grid布局中的行 |
### 8.2 网格模板参数接口字段注释
#### IGridTemplateParams接口
| 字段名 | 类型 | 中文注释 |
| ---------------- | ------ | -------------- |
| cellExpectWidth | number | 单元格预设宽度 |
| cellExpectHeight | number | 单元格预设高度 |
| cellRealWidth | number | 单元格实际宽度 |
| cellRealHeight | number | 单元格实际高度 |
| gapX | number | 列间距 |
| gapY | number | 行间距 |
| colCount | number | 总列数 |
| rowCount | number | 总行数 |
## 9. 实施计划
1. 对SDK模块中的所有接口字段添加中文注释
2. 对服务层模块中的接口字段添加中文注释
3. 对事件系统模块中的接口字段添加中文注释
4. 对应用管理模块中的接口字段添加中文注释
5. 对UI组件模块中的接口字段添加中文注释
6. 审查和验证所有注释的准确性和完整性
## 10. 注意事项
1. 保持原有代码逻辑不变,仅添加注释
2. 确保注释内容准确反映字段的实际用途
3. 统一注释风格,避免表述不一致
4. 对于已有注释的字段,检查并完善注释内容
5. 在添加注释时注意不要影响代码的可读性

View File

@@ -1,461 +0,0 @@
# 统一API接口设计文档
## 1. 概述
### 1.1 目标
为iframe中的第三方应用提供一套统一、简洁、类型安全的系统服务访问接口使应用开发者无需关注底层通信机制即可直接调用系统功能。
### 1.2 核心价值
- **简化开发**:封装复杂的跨域通信逻辑,提供类似本地函数调用的体验
- **类型安全**完整的TypeScript类型定义提供编译时检查和IDE智能提示
- **模块化设计**:按功能划分子模块,便于按需使用和维护
- **权限控制**:内置权限验证机制,确保系统安全
- **事件驱动**:支持双向通信,实现系统与应用间的实时交互
### 1.3 设计原则
1. **透明性**隐藏底层postMessage通信细节
2. **一致性**统一的API响应格式和错误处理机制
3. **可扩展性**:模块化架构支持功能扩展
4. **安全性**:严格的权限控制和数据验证
## 2. 系统架构
### 2.1 整体架构图
```mermaid
graph TB
subgraph "主应用(系统)"
A[主应用UI] --> B[沙箱引擎]
B --> C[事件通信服务]
C --> D[系统服务层]
D --> E[窗口管理]
D --> F[存储服务]
D --> G[网络服务]
D --> H[系统服务]
end
subgraph "子应用(沙箱)"
I[子应用UI] --> J[SDK客户端]
K[业务逻辑] --> J
end
J --"postMessage"--> B
B --"postMessage"--> J
style A fill:#e1f5fe
style I fill:#f3e5f5
style J fill:#f3e5f5
style B fill:#fff3e0
style C fill:#fff3e0
style D fill:#f1f8e9
```
### 2.2 SDK架构
```mermaid
classDiagram
class SystemDesktopSDK {
+version: string
+appId: string
+initialized: boolean
+window: WindowSDK
+storage: StorageSDK
+network: NetworkSDK
+events: EventSDK
+ui: UISDK
+system: SystemSDK
+init(config: SDKConfig) Promise~APIResponse~boolean~~
+destroy() Promise~APIResponse~boolean~~
+getStatus() Promise~APIResponse~object~~
}
class SDKBase {
#appId: string
#initialized: boolean
#sendToSystem(type: string, data: any) Promise~T~
#wrapResponse(promise: Promise~T~) Promise~APIResponse~T~~
}
class WindowSDK {
<<interface>>
+setTitle(title: string) Promise~APIResponse~boolean~~
+resize(width: number, height: number) Promise~APIResponse~boolean~~
+move(x: number, y: number) Promise~APIResponse~boolean~~
+minimize() Promise~APIResponse~boolean~~
+maximize() Promise~APIResponse~boolean~~
+restore() Promise~APIResponse~boolean~~
+close() Promise~APIResponse~boolean~~
+getState() Promise~APIResponse~WindowState~~
+on(event: string, callback: Function) void
}
class StorageSDK {
<<interface>>
+set(key: string, value: any) Promise~APIResponse~boolean~~
+get(key: string) Promise~APIResponse~any~~
+remove(key: string) Promise~APIResponse~boolean~~
+clear() Promise~APIResponse~boolean~~
}
class NetworkSDK {
<<interface>>
+request(url: string, config: NetworkRequestConfig) Promise~APIResponse~any~~
+get(url: string, config: any) Promise~APIResponse~any~~
+post(url: string, data: any, config: any) Promise~APIResponse~any~~
}
class EventSDK {
<<interface>>
+emit(channel: string, data: any) Promise~APIResponse~string~~
+on(channel: string, callback: Function) Promise~APIResponse~string~~
+off(subscriptionId: string) Promise~APIResponse~boolean~~
}
class UISDK {
<<interface>>
+showDialog(options: DialogOptions) Promise~APIResponse~object~~
+showNotification(options: NotificationOptions) Promise~APIResponse~string~~
+showToast(message: string, type: string) Promise~APIResponse~string~~
}
class SystemSDK {
<<interface>>
+getSystemInfo() Promise~APIResponse~SystemInfo~~
+getAppInfo() Promise~APIResponse~AppInfo~~
+requestPermission(permission: string) Promise~APIResponse~PermissionStatus~~
}
SDKBase <|-- SystemDesktopSDK
SystemDesktopSDK --> WindowSDK
SystemDesktopSDK --> StorageSDK
SystemDesktopSDK --> NetworkSDK
SystemDesktopSDK --> EventSDK
SystemDesktopSDK --> UISDK
SystemDesktopSDK --> SystemSDK
```
## 3. 核心模块设计
### 3.1 窗口管理模块(WindowSDK)
#### 3.1.1 功能概述
提供对应用窗口的控制能力,包括窗口尺寸调整、位置移动、状态切换等。
#### 3.1.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| --------------------- | ------------ | ----------------------------- | --------------------------------- |
| setTitle(title) | 设置窗口标题 | title: string | Promise<APIResponse<boolean>> |
| resize(width, height) | 调整窗口尺寸 | width: number, height: number | Promise<APIResponse<boolean>> |
| move(x, y) | 移动窗口位置 | x: number, y: number | Promise<APIResponse<boolean>> |
| minimize() | 最小化窗口 | 无 | Promise<APIResponse<boolean>> |
| maximize() | 最大化窗口 | 无 | Promise<APIResponse<boolean>> |
| restore() | 还原窗口 | 无 | Promise<APIResponse<boolean>> |
| close() | 关闭窗口 | 无 | Promise<APIResponse<boolean>> |
| getState() | 获取窗口状态 | 无 | Promise<APIResponse<WindowState>> |
#### 3.1.3 事件监听
```typescript
// 监听窗口变化事件
SystemSDK.window.on('resize', (width, height) => {
console.log(`窗口尺寸变化: ${width}x${height}`)
})
// 移除事件监听
SystemSDK.window.off('resize')
```
### 3.2 存储模块(StorageSDK)
#### 3.2.1 功能概述
提供应用数据的持久化存储能力,支持键值对存储、数据查询、删除等操作。
#### 3.2.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| --------------- | -------------- | ----------------------- | ------------------------------ |
| set(key, value) | 存储数据 | key: string, value: any | Promise<APIResponse<boolean>> |
| get(key) | 获取数据 | key: string | Promise<APIResponse<any>> |
| remove(key) | 删除数据 | key: string | Promise<APIResponse<boolean>> |
| clear() | 清空所有数据 | 无 | Promise<APIResponse<boolean>> |
| keys() | 获取所有键名 | 无 | Promise<APIResponse<string[]>> |
| has(key) | 检查键是否存在 | key: string | Promise<APIResponse<boolean>> |
#### 3.2.3 使用示例
```typescript
// 存储数据
await SystemSDK.storage.set('userSettings', { theme: 'dark', lang: 'zh' })
// 获取数据
const settings = await SystemSDK.storage.get('userSettings')
// 监听存储变化
SystemSDK.storage.on('onChange', (key, newValue, oldValue) => {
console.log(`存储变更: ${key}${oldValue} 变为 ${newValue}`)
})
```
### 3.3 网络模块(NetworkSDK)
#### 3.3.1 功能概述
提供网络请求能力支持HTTP请求、文件上传下载等操作。
#### 3.3.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| ----------------------- | ------------ | ----------------------------------------- | ------------------------------------- |
| request(url, config) | 发送HTTP请求 | url: string, config: NetworkRequestConfig | Promise<APIResponse<NetworkResponse>> |
| get(url, config) | GET请求 | url: string, config: object | Promise<APIResponse<NetworkResponse>> |
| post(url, data, config) | POST请求 | url: string, data: any, config: object | Promise<APIResponse<NetworkResponse>> |
| upload(url, file) | 上传文件 | url: string, file: File/Blob | Promise<APIResponse<NetworkResponse>> |
| download(url, filename) | 下载文件 | url: string, filename: string | Promise<APIResponse<Blob>> |
#### 3.3.3 使用示例
```typescript
// 发送GET请求
const response = await SystemSDK.network.get('https://api.example.com/users')
// 发送POST请求
const result = await SystemSDK.network.post('https://api.example.com/users', {
name: '张三',
email: 'zhangsan@example.com',
})
// 上传文件
const fileInput = document.querySelector('input[type="file"]')
if (fileInput.files.length > 0) {
await SystemSDK.network.upload('https://api.example.com/upload', fileInput.files[0])
}
```
### 3.4 事件通信模块(EventSDK)
#### 3.4.1 功能概述
提供应用间以及应用与系统间的事件通信机制,支持广播、点对点通信等模式。
#### 3.4.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| ------------------------- | -------------- | ----------------------------------- | ----------------------------- |
| emit(channel, data) | 发送事件消息 | channel: string, data: any | Promise<APIResponse<string>> |
| on(channel, callback) | 订阅事件频道 | channel: string, callback: Function | Promise<APIResponse<string>> |
| off(subscriptionId) | 取消订阅 | subscriptionId: string | Promise<APIResponse<boolean>> |
| broadcast(channel, data) | 广播消息 | channel: string, data: any | Promise<APIResponse<string>> |
| sendTo(targetAppId, data) | 发送点对点消息 | targetAppId: string, data: any | Promise<APIResponse<string>> |
#### 3.4.3 使用示例
```typescript
// 订阅事件
const subscriptionId = await SystemSDK.events.on('user-login', (message) => {
console.log('用户登录:', message.data)
})
// 发送事件
await SystemSDK.events.emit('user-action', { action: 'click', target: 'button1' })
// 取消订阅
await SystemSDK.events.off(subscriptionId)
```
### 3.5 UI交互模块(UISDK)
#### 3.5.1 功能概述
提供系统级UI交互能力如对话框、通知、文件选择器等。
#### 3.5.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| ------------------------- | -------------- | ----------------------------- | ------------------------------ |
| showDialog(options) | 显示对话框 | options: DialogOptions | Promise<APIResponse<object>> |
| showNotification(options) | 显示通知 | options: NotificationOptions | Promise<APIResponse<string>> |
| showFilePicker(options) | 显示文件选择器 | options: FilePickerOptions | Promise<APIResponse<FileList>> |
| showToast(message, type) | 显示Toast消息 | message: string, type: string | Promise<APIResponse<string>> |
#### 3.5.3 使用示例
```typescript
// 显示确认对话框
const result = await SystemSDK.ui.showDialog({
title: '确认操作',
message: '确定要删除这个文件吗?',
type: 'confirm',
buttons: ['取消', '确定'],
})
if (result.data.buttonIndex === 1) {
// 用户点击了确定
console.log('用户确认删除')
}
// 显示通知
await SystemSDK.ui.showNotification({
title: '操作完成',
body: '文件已成功上传',
icon: '/icons/success.png',
})
```
### 3.6 系统信息模块(SystemSDK)
#### 3.6.1 功能概述
提供系统信息查询和基础系统操作能力。
#### 3.6.2 接口定义
| 方法 | 描述 | 参数 | 返回值 |
| ----------------------------- | ---------------- | ------------------ | -------------------------------------- |
| getSystemInfo() | 获取系统信息 | 无 | Promise<APIResponse<SystemInfo>> |
| getAppInfo() | 获取当前应用信息 | 无 | Promise<APIResponse<AppInfo>> |
| requestPermission(permission) | 请求权限 | permission: string | Promise<APIResponse<PermissionStatus>> |
| getClipboard() | 获取剪贴板内容 | 无 | Promise<APIResponse<string>> |
| setClipboard(text) | 设置剪贴板内容 | text: string | Promise<APIResponse<boolean>> |
| openExternal(url) | 打开外部链接 | url: string | Promise<APIResponse<boolean>> |
## 4. 通信机制设计
### 4.1 消息协议
```mermaid
sequenceDiagram
participant A as 子应用SDK
participant B as 主应用沙箱引擎
participant C as 系统服务
A->>B: postMessage(sdk:call, method, data)
B->>C: 调用相应系统服务
C-->>B: 返回处理结果
B-->>A: postMessage(system:response, data)
```
### 4.2 消息格式
```typescript
// SDK调用消息格式
{
type: 'sdk:call',
requestId: string, // 唯一请求ID
method: string, // 调用方法路径,如 "window.setTitle"
data: any, // 传递的数据
appId: string // 应用ID
}
// 系统响应消息格式
{
type: 'system:response',
requestId: string, // 对应的请求ID
success: boolean, // 是否成功
data: any, // 成功时的数据
error: string // 失败时的错误信息
}
```
### 4.3 错误处理机制
```typescript
// 统一响应格式
interface APIResponse<T> {
success: boolean // 是否成功
data?: T // 成功时的数据
error?: string // 错误信息
code?: number // 错误码
}
```
## 5. 权限控制设计
### 5.1 权限模型
系统采用基于功能的权限控制模型,应用需要在初始化时声明所需权限。
### 5.2 权限类型
| 权限名称 | 描述 | 敏感级别 |
| ------------------- | -------------- | -------- |
| window.control | 窗口控制权限 | 中 |
| storage.read | 存储读取权限 | 低 |
| storage.write | 存储写入权限 | 中 |
| network.request | 网络请求权限 | 中 |
| event.broadcast | 事件广播权限 | 中 |
| system.clipboard | 剪贴板访问权限 | 高 |
| system.notification | 通知权限 | 低 |
### 5.3 权限申请流程
```mermaid
flowchart TD
A[应用初始化] --> B{权限检查}
B -->|权限已授予| C[正常运行]
B -->|权限未授予| D[请求权限]
D --> E[用户授权]
E -->|允许| C
E -->|拒绝| F[限制功能]
```
## 6. 使用指南
### 6.1 SDK初始化
```typescript
// 初始化SDK
const result = await SystemSDK.init({
appId: 'com.example.myapp',
appName: '我的应用',
version: '1.0.0',
permissions: ['storage.read', 'storage.write', 'network.request'],
})
if (result.success) {
console.log('SDK初始化成功')
} else {
console.error('SDK初始化失败:', result.error)
}
```
### 6.2 功能调用示例
```typescript
// 设置窗口标题
await SystemSDK.window.setTitle('我的应用 - 主界面')
// 存储用户数据
await SystemSDK.storage.set('userData', { name: '张三', age: 25 })
// 发送网络请求
const response = await SystemSDK.network.get('/api/user/profile')
// 显示通知
await SystemSDK.ui.showNotification({
title: '数据加载完成',
body: '用户信息已更新',
})
```
### 6.3 事件监听示例
```typescript
// 监听窗口关闭事件
SystemSDK.window.on('close', async () => {
// 保存应用状态
await SystemSDK.storage.set('appState', getCurrentState())
console.log('应用状态已保存')
})
// 监听系统主题变化
SystemSDK.system.on('themeChange', (theme) => {
// 更新应用主题
updateAppTheme(theme)
})
```

View File

@@ -1,244 +0,0 @@
# Vue Desktop 项目代码优化设计文档
## 1. 概述
本文档旨在分析 Vue Desktop 项目的现有代码结构,识别无用或冗余的代码,并提出优化方案。通过去除无用代码,提高项目可维护性、减少包体积、提升性能。
## 2. 项目架构分析
### 2.1 当前架构概览
Vue Desktop 采用模块化架构,主要包含以下核心模块:
```mermaid
graph TD
A[主应用] --> B[服务层]
A --> C[UI层]
A --> D[事件系统]
A --> E[应用注册中心]
B --> B1[WindowService]
B --> B2[ResourceService]
B --> B3[EventCommunicationService]
B --> B4[ApplicationSandboxEngine]
B --> B5[ApplicationLifecycleManager]
B --> B6[ExternalAppDiscovery]
C --> C1[DesktopContainer]
C --> C2[AppIcon]
C --> C3[AppRenderer]
D --> D1[EventManager]
D --> D2[WindowFormEventManager]
E --> E1[内置应用]
E --> E2[外部应用]
```
### 2.2 核心模块职责
| 模块 | 职责 |
| --------------------------- | ---------------------------------- |
| WindowService | 管理应用窗体的创建、销毁、状态控制 |
| ResourceService | 管理应用资源访问权限和存储 |
| EventCommunicationService | 处理应用间通信 |
| ApplicationSandboxEngine | 为外部应用创建安全沙箱环境 |
| ApplicationLifecycleManager | 管理应用的完整生命周期 |
| ExternalAppDiscovery | 自动发现和注册外部应用 |
| DesktopContainer | 桌面容器,管理应用图标布局 |
| AppIcon | 应用图标组件,支持拖拽和双击启动 |
| AppRenderer | 渲染内置应用的组件 |
## 3. 无用代码识别与分析
### 3.1 重复导入和未使用导入
在多个文件中存在重复导入或未使用的导入语句,增加了不必要的代码体积。
### 3.2 冗余功能实现
1. **重复的应用发现机制**
- `ExternalAppDiscovery``AppRegistry` 都负责应用注册,但职责不清晰
- 外部应用发现服务中存在多种扫描策略,但实际只使用一种
2. **重复的状态管理**
- 多个服务中维护了相似的状态管理逻辑
- 事件系统存在多层封装但使用不一致
### 3.3 未使用的组件和方法
1. **未使用的UI组件**
- `SystemStatus` 组件在桌面容器中定义但使用有限
- 部分样式和功能未被实际使用
2. **未使用的服务方法**
- `ApplicationLifecycleManager` 中的部分生命周期方法未被调用
- `ExternalAppDiscovery` 中的测试和调试方法可以移除
### 3.4 过度工程化
1. **复杂的沙箱实现**
- 对于内置应用不需要沙箱,但代码中仍存在相关处理逻辑
- 沙箱安全级别设置过于复杂,实际使用中只用到部分配置
2. **冗余的事件系统**
- 存在多套事件管理机制,增加了复杂性
- 部分事件监听器未正确清理,可能导致内存泄漏
## 4. 优化方案设计
### 4.1 代码清理策略
#### 4.1.1 移除未使用的导入和变量
```mermaid
graph LR
A[扫描代码] --> B[识别未使用导入]
B --> C[移除未使用导入]
A --> D[识别未使用变量]
D --> E[移除未使用变量]
C --> F[代码重构]
E --> F
```
#### 4.1.2 清理冗余功能
1. **统一应用注册机制**
- 明确区分内置应用和外部应用的注册方式
- 移除重复的应用发现逻辑
2. **简化沙箱实现**
- 仅对外部应用创建沙箱
- 移除内置应用的沙箱相关代码
### 4.2 模块重构方案
#### 4.2.1 服务层优化
```mermaid
graph TD
A[服务层重构] --> B[合并相似功能]
A --> C[移除冗余方法]
A --> D[优化依赖关系]
B --> B1[统一状态管理]
B --> B2[合并事件处理]
C --> C1[移除未调用方法]
C --> C2[简化接口设计]
D --> D1[明确依赖关系]
D --> D2[减少循环依赖]
```
#### 4.2.2 UI层优化
1. **组件精简**
- 移除未使用的系统状态组件
- 合并功能相似的UI组件
2. **样式优化**
- 移除未使用的CSS类和样式
- 统一设计风格和组件样式
### 4.3 性能优化措施
#### 4.3.1 减少内存占用
1. **优化事件监听器**
- 确保所有事件监听器在组件销毁时正确移除
- 使用弱引用避免循环引用
2. **优化数据结构**
- 使用更高效的数据结构存储应用信息
- 减少不必要的响应式数据
#### 4.3.2 提升加载性能
1. **懒加载优化**
- 对非核心功能采用懒加载
- 优化应用启动流程
2. **资源优化**
- 压缩和合并静态资源
- 移除未使用的资源文件
## 5. 详细优化实施计划
### 5.1 第一阶段:代码清理 (1-2天)
| 任务 | 描述 | 预期效果 |
| -------------- | --------------------------------- | ---------------- |
| 移除未使用导入 | 扫描并移除所有未使用的导入语句 | 减少代码体积 |
| 清理未使用变量 | 识别并移除未使用的变量和方法 | 提高代码可读性 |
| 移除调试代码 | 清理所有console.log和调试相关代码 | 减少生产环境代码 |
### 5.2 第二阶段:功能重构 (3-5天)
| 任务 | 描述 | 预期效果 |
| ------------ | -------------------------- | ---------------- |
| 统一应用注册 | 明确内置和外部应用注册机制 | 简化应用管理 |
| 简化沙箱实现 | 仅对外部应用创建沙箱 | 减少复杂性 |
| 优化事件系统 | 合并冗余事件处理逻辑 | 提高事件处理效率 |
### 5.3 第三阶段:性能优化 (2-3天)
| 任务 | 描述 | 预期效果 |
| ------------ | ---------------------- | ---------------- |
| 内存泄漏修复 | 确保事件监听器正确清理 | 减少内存占用 |
| 加载性能优化 | 优化应用启动和资源加载 | 提升用户体验 |
| 打包优化 | 配置代码分割和懒加载 | 减少初始加载时间 |
## 6. 风险评估与缓解措施
### 6.1 潜在风险
1. **功能丢失风险**
- 移除代码时可能误删仍在使用的功能
- 解决方案:建立完整的测试用例,确保核心功能不受影响
2. **兼容性问题**
- 重构可能影响现有外部应用的兼容性
- 解决方案保持API接口稳定提供迁移指南
3. **性能回退**
- 优化过程中可能引入新的性能问题
- 解决方案:进行充分的性能测试和监控
### 6.2 缓解措施
1. **建立测试保障**
- 完善单元测试和集成测试
- 建立回归测试机制
2. **渐进式重构**
- 采用小步快跑的方式进行重构
- 每次重构后进行充分测试
3. **文档更新**
- 及时更新相关技术文档
- 记录重要的设计变更
## 7. 验证标准
### 7.1 代码质量指标
| 指标 | 优化前 | 优化后目标 |
| -------- | --------- | ---------- |
| 代码行数 | 约15000行 | 减少15-20% |
| 包体积 | 约5MB | 减少10-15% |
| 内存占用 | 约200MB | 减少10-20% |
### 7.2 性能指标
| 指标 | 优化前 | 优化后目标 |
| ------------ | ------ | ---------- |
| 初始加载时间 | 3-5秒 | 减少20-30% |
| 应用启动时间 | 1-2秒 | 减少15-25% |
| 内存泄漏 | 存在 | 完全消除 |
## 8. 总结
通过对 Vue Desktop 项目的全面分析,我们识别出多个可以优化的方面,包括代码清理、功能重构和性能优化。实施这些优化措施将显著提高项目的可维护性、减少包体积、提升运行性能,同时保持功能完整性。
优化工作将分阶段进行,确保在提升代码质量的同时不影响现有功能。通过建立完善的测试和验证机制,我们可以有效控制风险,确保优化工作的成功实施。

View File

@@ -1,623 +0,0 @@
# 系统与业务解耦架构设计
## 概述
本设计旨在构建一个高性能的类Windows桌面前端系统实现系统框架与业务应用的完全解耦。系统提供统一的桌面环境、窗体管理和资源访问控制而业务应用通过标准化的SDK接口进行开发确保双方独立演进且相互不影响。
### 核心设计原则
- **完全隔离**:系统与应用在运行时完全隔离,应用无法直接访问系统资源
- **标准化接口**通过统一的SDK提供标准化的系统服务接口
- **性能优先**:采用微前端沙箱、虚拟化渲染等技术确保高性能
- **框架无关**:支持任意前端框架开发的第三方应用
- **安全可控**:严格的权限控制和安全沙箱机制
## 整体架构
### 系统分层架构
```mermaid
graph TB
subgraph "用户界面层"
Desktop[桌面容器]
WindowManager[窗体管理器]
TaskBar[任务栏]
SystemUI[系统UI组件]
end
subgraph "系统服务层"
WindowService[窗体服务]
ResourceService[资源服务]
EventService[事件服务]
SecurityService[安全服务]
StorageService[存储服务]
end
subgraph "应用运行时层"
AppContainer[应用容器]
SandboxEngine[沙箱引擎]
SDKBridge[SDK桥接层]
AppLifecycle[应用生命周期]
end
subgraph "第三方应用"
App1[业务应用A]
App2[业务应用B]
App3[业务应用C]
end
Desktop --> WindowManager
WindowManager --> WindowService
AppContainer --> SandboxEngine
SDKBridge --> SystemService[系统服务层]
App1 --> AppContainer
App2 --> AppContainer
App3 --> AppContainer
```
### 核心组件职责
| 组件 | 职责 | 接口类型 |
| ---------- | -------------------------- | -------- |
| 桌面容器 | 管理应用图标、布局和交互 | 系统内部 |
| 窗体管理器 | 窗体创建、管理、切换和销毁 | 系统内部 |
| 应用容器 | 第三方应用运行环境隔离 | 对外SDK |
| 沙箱引擎 | 安全隔离和权限控制 | 系统内部 |
| SDK桥接层 | 系统服务的标准化接口 | 对外SDK |
## 系统核心服务
### 窗体管理服务
窗体管理服务负责所有窗体的生命周期管理,提供统一的窗体操作接口。
#### 窗体生命周期
```mermaid
stateDiagram-v2
[*] --> Creating: 创建窗体
Creating --> Loading: 加载应用
Loading --> Active: 激活显示
Active --> Minimized: 最小化
Minimized --> Active: 恢复显示
Active --> Maximized: 最大化
Maximized --> Active: 还原
Active --> Closing: 关闭请求
Closing --> Destroyed: 销毁完成
Destroyed --> [*]
Loading --> Error: 加载失败
Error --> Destroyed: 错误处理
```
#### 窗体服务接口规范
| 服务方法 | 参数 | 返回值 | 描述 |
| -------------- | ----------------------- | -------------- | ------------ |
| createWindow | appId, config | WindowInstance | 创建新窗体 |
| destroyWindow | windowId | boolean | 销毁指定窗体 |
| minimizeWindow | windowId | boolean | 最小化窗体 |
| maximizeWindow | windowId | boolean | 最大化窗体 |
| restoreWindow | windowId | boolean | 还原窗体 |
| setWindowTitle | windowId, title | boolean | 设置窗体标题 |
| setWindowSize | windowId, width, height | boolean | 设置窗体尺寸 |
### 资源管理服务
资源管理服务提供统一的系统资源访问控制,确保应用只能通过授权访问系统资源。
#### 资源访问控制矩阵
| 资源类型 | 默认权限 | 申请方式 | 审批流程 |
| ------------ | ------------ | -------- | -------- |
| 本地存储 | 应用独立空间 | 配置声明 | 自动授权 |
| 网络请求 | 白名单域名 | 动态申请 | 用户确认 |
| 文件系统 | 禁止访问 | 动态申请 | 用户确认 |
| 系统通知 | 禁止发送 | 动态申请 | 用户确认 |
| 剪贴板 | 禁止访问 | 动态申请 | 用户确认 |
| 摄像头麦克风 | 禁止访问 | 动态申请 | 用户确认 |
### 事件通信服务
提供系统级别的事件通信机制,支持应用间消息传递和系统事件监听。
#### 事件分类与权限
```mermaid
graph LR
subgraph "系统事件"
SE1[窗体状态变化]
SE2[主题切换]
SE3[网络状态变化]
SE4[系统资源变化]
end
subgraph "应用事件"
AE1[应用间消息]
AE2[应用状态同步]
AE3[数据共享请求]
end
subgraph "用户事件"
UE1[用户交互]
UE2[快捷键触发]
UE3[拖拽操作]
end
SE1 --> 只读监听
SE2 --> 只读监听
AE1 --> 权限验证
AE2 --> 权限验证
UE1 --> 应用内处理
```
## 应用沙箱机制
### 沙箱隔离策略
应用运行在完全隔离的沙箱环境中,通过多重隔离机制确保安全性。
#### 沙箱隔离层次
```mermaid
graph TD
subgraph "物理隔离层"
IFrame[IFrame容器]
ShadowDOM[Shadow DOM]
CSP[内容安全策略]
end
subgraph "逻辑隔离层"
Namespace[命名空间隔离]
Memory[内存隔离]
Storage[存储隔离]
end
subgraph "网络隔离层"
CORS[跨域限制]
Proxy[代理拦截]
Whitelist[白名单过滤]
end
subgraph "API隔离层"
SDKProxy[SDK代理]
Permission[权限验证]
Audit[审计日志]
end
IFrame --> Namespace
ShadowDOM --> Memory
CSP --> Storage
Namespace --> SDKProxy
Memory --> Permission
Storage --> Audit
```
### 沙箱性能优化
#### 虚拟化渲染机制
```mermaid
flowchart TD
Start[应用启动] --> CheckCache{检查缓存}
CheckCache -->|存在| LoadCache[加载缓存实例]
CheckCache -->|不存在| CreateSandbox[创建沙箱]
CreateSandbox --> PreloadResources[预加载资源]
PreloadResources --> InitSDK[初始化SDK]
InitSDK --> MountApp[挂载应用]
LoadCache --> ValidateCache{验证缓存有效性}
ValidateCache -->|有效| RestoreApp[恢复应用状态]
ValidateCache -->|无效| CreateSandbox
MountApp --> AppReady[应用就绪]
RestoreApp --> AppReady
AppReady --> Monitor[性能监控]
Monitor --> OptimizeMemory[内存优化]
OptimizeMemory --> CheckIdle{检查空闲状态}
CheckIdle -->|空闲| SuspendApp[挂起应用]
CheckIdle -->|活跃| Monitor
SuspendApp --> CheckActivate{检查激活请求}
CheckActivate -->|激活| RestoreApp
CheckActivate -->|继续空闲| SuspendApp
```
## SDK接口设计
### SDK架构组成
SDK作为应用与系统间的唯一通信桥梁提供完整的系统服务接口。
#### SDK模块划分
| 模块名称 | 功能描述 | 主要接口 |
| ----------- | -------- | --------------------------------------- |
| WindowSDK | 窗体操作 | setTitle, resize, minimize, maximize |
| StorageSDK | 数据存储 | get, set, remove, clear |
| EventSDK | 事件通信 | emit, on, off, broadcast |
| UISDK | UI组件 | showDialog, showNotification, showToast |
| NetworkSDK | 网络请求 | request, upload, download |
| ResourceSDK | 资源访问 | getFile, saveFile, getClipboard |
### SDK使用示例
#### 窗体操作SDK
```typescript
// 应用中使用窗体SDK的示例
interface WindowSDK {
// 设置窗体标题
setTitle(title: string): Promise<boolean>
// 调整窗体尺寸
resize(width: number, height: number): Promise<boolean>
// 最小化窗体
minimize(): Promise<boolean>
// 最大化窗体
maximize(): Promise<boolean>
// 监听窗体状态变化
onStateChange(callback: (state: WindowState) => void): void
}
```
#### 存储服务SDK
```typescript
// 应用存储SDK接口定义
interface StorageSDK {
// 存储数据
set(key: string, value: any): Promise<boolean>
// 获取数据
get(key: string): Promise<any>
// 删除数据
remove(key: string): Promise<boolean>
// 清空存储
clear(): Promise<boolean>
// 监听存储变化
onChanged(callback: (key: string, newValue: any, oldValue: any) => void): void
}
```
### SDK权限控制机制
#### 权限申请流程
```mermaid
sequenceDiagram
participant App as 第三方应用
participant SDK as SDK桥接层
participant Security as 安全服务
participant User as 用户界面
participant System as 系统服务
App->>SDK: 调用需权限的API
SDK->>Security: 检查权限状态
alt 已授权
Security->>SDK: 返回已授权
SDK->>System: 调用系统服务
System->>SDK: 返回执行结果
SDK->>App: 返回结果
else 未授权
Security->>User: 显示权限申请弹窗
User->>Security: 用户确认/拒绝
alt 用户同意
Security->>SDK: 授权通过
SDK->>System: 调用系统服务
System->>SDK: 返回执行结果
SDK->>App: 返回结果
else 用户拒绝
Security->>SDK: 权限被拒绝
SDK->>App: 返回权限错误
end
end
```
## 应用生命周期管理
### 应用状态管理
应用在系统中具有完整的生命周期管理,确保资源的合理分配和回收。
#### 应用状态转换图
```mermaid
stateDiagram-v2
[*] --> Installing: 安装应用
Installing --> Installed: 安装完成
Installing --> InstallFailed: 安装失败
InstallFailed --> [*]
Installed --> Starting: 启动应用
Starting --> Running: 运行中
Starting --> StartFailed: 启动失败
StartFailed --> Installed
Running --> Suspended: 挂起
Suspended --> Running: 恢复
Running --> Stopping: 停止
Stopping --> Installed: 已停止
Installed --> Uninstalling: 卸载应用
Uninstalling --> [*]: 卸载完成
```
### 应用资源管理
#### 内存管理策略
| 应用状态 | 内存策略 | 清理时机 | 恢复方式 |
| ---------- | -------------- | -------- | ---------- |
| Running | 完整内存保持 | 不清理 | 无需恢复 |
| Suspended | 压缩非关键数据 | 5分钟后 | 延迟加载 |
| Background | 最小化内存占用 | 立即清理 | 重新初始化 |
| Stopped | 完全释放内存 | 立即清理 | 完整重启 |
#### 性能监控指标
```mermaid
graph LR
subgraph "性能指标"
CPU[CPU使用率]
Memory[内存占用]
Network[网络请求]
Render[渲染性能]
end
subgraph "监控阈值"
CPULimit[CPU < 30%]
MemoryLimit[内存 < 100MB]
NetworkLimit[请求 < 10/s]
RenderLimit[FPS > 30]
end
subgraph "优化策略"
Throttle[请求节流]
Cache[智能缓存]
Lazy[懒加载]
Virtual[虚拟滚动]
end
CPU --> CPULimit
Memory --> MemoryLimit
Network --> NetworkLimit
Render --> RenderLimit
CPULimit --> Throttle
MemoryLimit --> Cache
NetworkLimit --> Throttle
RenderLimit --> Virtual
```
## 数据流架构
### 系统数据流
系统采用单向数据流架构,确保数据的一致性和可追踪性。
#### 数据流向图
```mermaid
flowchart TD
subgraph "系统层"
SystemStore[系统状态存储]
SystemEvents[系统事件总线]
SystemServices[系统服务]
end
subgraph "应用层"
AppState[应用状态]
AppEvents[应用事件]
AppSDK[应用SDK]
end
subgraph "界面层"
SystemUI[系统界面]
AppUI[应用界面]
end
SystemStore --> SystemUI
SystemEvents --> SystemUI
SystemServices --> AppSDK
AppSDK --> AppState
AppState --> AppUI
AppEvents --> SystemEvents
SystemUI --> SystemEvents
AppUI --> AppEvents
```
### 跨应用数据共享
#### 数据共享权限模型
```mermaid
graph TB
subgraph "数据提供者"
ProviderApp[应用A]
DataExport[导出数据接口]
Permission[权限配置]
end
subgraph "系统中介"
DataBroker[数据代理服务]
PermissionCheck[权限验证]
DataTransform[数据转换]
end
subgraph "数据消费者"
ConsumerApp[应用B]
DataImport[导入数据接口]
DataValidation[数据验证]
end
ProviderApp --> DataExport
DataExport --> DataBroker
Permission --> PermissionCheck
DataBroker --> PermissionCheck
PermissionCheck --> DataTransform
DataTransform --> DataImport
DataImport --> ConsumerApp
DataImport --> DataValidation
```
## 部署与运维
### 应用分发机制
系统支持多种应用分发方式,确保第三方应用的便捷接入。
#### 应用打包规范
| 文件类型 | 必需性 | 描述 | 示例 |
| ------------- | ------ | ------------ | -------------------------- |
| manifest.json | 必需 | 应用清单文件 | 包含应用基本信息、权限需求 |
| index.html | 必需 | 应用入口文件 | 应用主页面 |
| assets/ | 可选 | 静态资源目录 | CSS、图片、字体等 |
| sdk/ | 可选 | SDK依赖文件 | 如需离线使用SDK |
#### 应用安装流程
```mermaid
flowchart TD
Upload[上传应用包] --> Validate[验证应用包]
Validate --> Extract[解压应用文件]
Extract --> ScanSecurity[安全扫描]
ScanSecurity --> CheckPermission[权限检查]
CheckPermission --> RegisterApp[注册应用]
RegisterApp --> CreateSandbox[创建沙箱环境]
CreateSandbox --> InstallComplete[安装完成]
Validate -->|验证失败| InstallFailed[安装失败]
ScanSecurity -->|安全风险| InstallFailed
CheckPermission -->|权限过度| UserConfirm[用户确认]
UserConfirm -->|同意| RegisterApp
UserConfirm -->|拒绝| InstallFailed
```
### 系统监控与日志
#### 监控体系架构
```mermaid
graph TB
subgraph "数据采集层"
AppMetrics[应用性能指标]
SystemMetrics[系统性能指标]
UserActions[用户行为数据]
ErrorLogs[错误日志]
end
subgraph "数据处理层"
DataAggregator[数据聚合器]
AlertEngine[告警引擎]
Analytics[分析引擎]
end
subgraph "展示层"
Dashboard[监控面板]
Reports[性能报告]
Alerts[告警通知]
end
AppMetrics --> DataAggregator
SystemMetrics --> DataAggregator
UserActions --> Analytics
ErrorLogs --> AlertEngine
DataAggregator --> Dashboard
Analytics --> Reports
AlertEngine --> Alerts
```
## 安全机制
### 多层安全防护
系统采用多层安全防护机制,确保系统和应用的安全运行。
#### 安全防护体系
| 防护层级 | 防护措施 | 防护目标 |
| -------- | ------------------ | -------- |
| 网络层 | HTTPS强制、CSP策略 | 传输安全 |
| 应用层 | 沙箱隔离、权限控制 | 运行安全 |
| 数据层 | 加密存储、访问控制 | 数据安全 |
| 用户层 | 身份验证、操作审计 | 使用安全 |
### 权限管理系统
#### 权限分级模型
```mermaid
graph TD
subgraph "系统权限"
SysAdmin[系统管理员]
SysOperator[系统操作员]
SysUser[系统用户]
end
subgraph "应用权限"
AppOwner[应用所有者]
AppUser[应用用户]
AppGuest[应用访客]
end
subgraph "资源权限"
ReadOnly[只读权限]
ReadWrite[读写权限]
FullControl[完全控制]
end
SysAdmin --> FullControl
SysOperator --> ReadWrite
SysUser --> ReadOnly
AppOwner --> FullControl
AppUser --> ReadWrite
AppGuest --> ReadOnly
```
## 性能优化策略
### 渲染性能优化
#### 虚拟化渲染机制
```mermaid
flowchart TD
ViewportDetection[视口检测] --> VisibilityCheck{可见性检查}
VisibilityCheck -->|可见| RenderApp[渲染应用]
VisibilityCheck -->|不可见| SuspendRender[暂停渲染]
RenderApp --> FrameOptimization[帧率优化]
FrameOptimization --> RequestAnimationFrame[RAF调度]
RequestAnimationFrame --> RenderComplete[渲染完成]
SuspendRender --> MemoryCleanup[内存清理]
MemoryCleanup --> LowPowerMode[低功耗模式]
RenderComplete --> ViewportDetection
LowPowerMode --> ViewportDetection
```
### 内存管理优化
#### 智能内存回收策略
| 回收时机 | 回收对象 | 回收策略 | 恢复方式 |
| ---------- | ------------- | ----------- | ------------ |
| 应用切换时 | 非活跃应用DOM | 延迟5秒回收 | 重新渲染 |
| 内存告警时 | 缓存数据 | LRU算法清理 | 重新请求 |
| 长时间空闲 | 应用实例 | 序列化存储 | 反序列化恢复 |
| 系统重启时 | 所有临时数据 | 立即清理 | 重新初始化 |

View File

@@ -1,228 +0,0 @@
# 第三方SDK集成方案设计
## 概述
本文档旨在设计一种新的第三方SDK集成方案使第三方应用无需通过iframe注入的方式即可直接调用系统服务。当前系统采用iframe注入SDK的方式为第三方应用提供系统服务访问接口这种方式虽然有效但在安全性、性能和开发体验方面存在一些限制。
新方案将采用全局对象共享的方式实现SDK功能避免网络通信开销同时保持与现有系统的兼容性。
## 设计目标
1. **安全性提升**减少iframe注入带来的安全风险
2. **性能优化**降低SDK注入的开销
3. **开发体验改善**提供更简洁的SDK使用方式
4. **零网络通信**:完全避免网络请求,提升响应速度
5. **向后兼容**:保持现有应用的正常运行
## 当前实现分析
### 现有架构
当前系统通过在iframe中注入SDK脚本的方式为第三方应用提供服务访问接口
```mermaid
graph TD
A[第三方应用] --> B[iframe沙箱]
B --> C[注入SDK脚本]
C --> D[postMessage通信]
D --> E[系统服务]
E --> D
D --> C
C --> B
B --> A
```
### 通信机制
系统使用`postMessage` API实现跨域通信
1. **请求发送**SDK通过`window.parent.postMessage`发送请求到系统
2. **请求处理**:系统监听`message`事件处理SDK调用
3. **响应返回**:系统通过`iframe.contentWindow.postMessage`发送响应
### 安全机制
1. **沙箱隔离**使用iframe的`sandbox`属性限制应用权限
2. **权限控制**基于应用ID的权限验证机制
3. **CSP策略**:内容安全策略限制脚本执行
## 新方案设计
### 架构设计
新方案将采用全局对象预定义的方式,通过系统预置接口实现系统服务访问:
```mermaid
graph TD
A[第三方应用] --> B[全局对象接口]
B --> C[系统服务]
C --> B
B --> A
```
### 核心组件
1. **全局对象接口**:在应用上下文中预置系统服务接口
2. **系统服务适配器**:将接口调用转换为内部服务调用
3. **权限验证模块**:负责应用权限控制
4. **上下文管理器**:管理系统与应用间的上下文隔离
### 接口设计
#### 全局对象结构
```javascript
// 第三方应用中直接使用预置对象
window.SystemSDK.window.setTitle('新标题')
window.SystemSDK.storage.set('key', 'value')
```
#### 可用接口
- `window.SystemSDK.window` - 窗体管理接口
- `window.SystemSDK.storage` - 存储接口
- `window.SystemSDK.network` - 网络接口
- `window.SystemSDK.events` - 事件接口
- `window.SystemSDK.ui` - UI接口
- `window.SystemSDK.system` - 系统接口
### 安全机制
1. **上下文隔离**:确保应用只能访问预置接口
2. **权限验证**基于应用ID的权限控制
3. **接口限制**:只暴露必要的系统服务接口
4. **调用审计**记录所有SDK接口调用
## 实施步骤
### 第一阶段:全局对象接口开发
1. 提取现有SDK功能到全局对象接口
2. 实现全局对象接口替代postMessage通信
3. 添加权限验证和错误处理机制
4. 编写使用文档和示例
### 第二阶段:沙箱环境改造
1. 修改应用沙箱创建逻辑
2. 实现全局对象预置机制
3. 创建系统服务适配器
4. 实现权限验证模块
### 第三阶段:兼容性处理
1. 保持iframe注入机制向后兼容
2. 提供迁移工具和文档
3. 逐步引导第三方应用迁移到新方案
## API接口设计
### 全局对象接口
| 接口 | 说明 |
| ------------------ | --------------- |
| `window.SystemSDK` | 系统SDK全局对象 |
### 窗体管理接口
| 接口 | 说明 |
| --------------------------- | ------------ |
| `SystemSDK.window.setTitle` | 设置窗体标题 |
| `SystemSDK.window.resize` | 调整窗体尺寸 |
| `SystemSDK.window.move` | 移动窗体位置 |
| `SystemSDK.window.minimize` | 最小化窗体 |
| `SystemSDK.window.maximize` | 最大化窗体 |
| `SystemSDK.window.restore` | 还原窗体 |
| `SystemSDK.window.close` | 关闭窗体 |
### 存储接口
| 接口 | 说明 |
| -------------------------- | -------- |
| `SystemSDK.storage.set` | 存储数据 |
| `SystemSDK.storage.get` | 获取数据 |
| `SystemSDK.storage.remove` | 删除数据 |
| `SystemSDK.storage.clear` | 清空数据 |
### 网络接口
| 接口 | 说明 |
| ---------------------------- | ------------ |
| `SystemSDK.network.request` | 发送HTTP请求 |
| `SystemSDK.network.upload` | 上传文件 |
| `SystemSDK.network.download` | 下载文件 |
## 错误处理
### 错误类型定义
| 错误类型 | 说明 |
| --------------------- | ------------ |
| UnauthorizedError | 未授权访问 |
| PermissionDeniedError | 权限不足 |
| TimeoutError | 调用超时 |
| InternalError | 系统内部错误 |
### 错误响应格式
```typescript
interface APIResponse<T> {
success: boolean
data?: T
error?: string
code?: number
}
```
## 性能考量
1. **零网络延迟**:完全避免网络通信延迟
2. **直接调用**:通过全局对象直接访问系统服务
3. **内存共享**:在沙箱环境中共享内存数据
4. **异步处理**:非阻塞的接口调用机制
## 安全考虑
1. **沙箱隔离**在受控环境中暴露SDK接口
2. **权限控制**基于应用ID的细粒度权限管理
3. **接口限制**:只暴露必要的系统服务接口
4. **调用审计**记录所有SDK接口调用
5. **输入验证**:严格验证所有输入参数
## 向后兼容性
1. 保留现有的iframe注入机制
2. 提供迁移指南和工具
3. 逐步废弃旧机制而非立即移除
4. 监控两种机制的使用情况
## 测试策略
### 单元测试
1. SDK库功能测试
2. 认证服务测试
3. API网关测试
4. 权限验证测试
### 集成测试
1. 端到端通信测试
2. 安全机制测试
3. 性能基准测试
4. 兼容性测试
## 部署方案
### 开发环境
1. 全局对象接口开发
2. Mock服务模拟系统接口
3. 单元测试覆盖
### 生产环境
1. 系统集成全局对象接口
2. API服务部署到云服务器
3. 配置负载均衡和SSL证书
4. 设置监控和告警机制

View File

@@ -1,314 +0,0 @@
# 窗体功能增强设计文档
## 1. 概述
### 1.1 功能目标
1. 为项目中的窗体组件添加8个方向的拖拽调整尺寸功能增强用户交互体验
2. 修复窗体最大化、最小化、还原功能存在的问题
3. 完善窗体状态管理和事件通知机制
### 1.2 当前系统分析
通过代码分析发现,当前系统已具备以下基础功能:
- 窗体创建、销毁、移动功能
- 窗体最大化、最小化、还原功能
- 基本的事件管理系统
- 窗体状态管理和配置
但现有功能存在以下问题:
1. 缺少用户通过鼠标拖拽调整窗体尺寸的功能
2. 窗体最大化、最小化、还原功能实现不完整,缺少与事件系统的完整集成
3. 窗体状态变更时未正确触发`windowFormDataUpdate`事件通知
## 2. 架构设计
### 2.1 整体架构
```mermaid
graph TD
A[用户操作] --> B[窗体功能处理器]
B --> C[窗体状态管理]
C --> D[UI更新引擎]
D --> E[界面重渲染]
C --> F[事件通知系统]
G[窗体服务] --> C
H[事件管理器] --> F
```
### 2.2 核心组件
| 组件名称 | 职责 | 说明 |
| --------------------- | ------------------ | -------------------------------------- |
| WindowOperationHandler | 窗体操作处理核心 | 处理最大化、最小化、还原和拖拽调整尺寸操作 |
| WindowManager | 窗体状态管理 | 管理窗体状态变更和配置更新 |
| WindowRenderer | UI更新引擎 | 应用新状态到窗体界面 |
| WindowEventManager | 事件管理器 | 发布窗体状态变更事件 |
## 3. 功能设计
### 3.1 窗体状态操作
窗体支持以下状态操作:
| 操作 | 状态变更 | 事件触发 | 说明 |
| ------ | ---------------------------- | ------------------------------------------ | ------------------------------------------ |
| 最大化 | default/minimized → maximized | windowFormMaximize, windowFormDataUpdate | 窗体占据除任务栏外的整个屏幕空间 |
| 最小化 | default/maximized → minimized | windowFormMinimize | 窗体隐藏,仅在任务栏保留图标 |
| 还原 | minimized/maximized → default | windowFormRestore, windowFormDataUpdate | 窗体恢复到正常尺寸和位置 |
### 3.2 拖拽调整尺寸方向定义
窗体支持8个方向的拖拽调整尺寸具体如下
```mermaid
graph TD
subgraph 窗体
direction LR
A[↖] --- B[↑] --- C[↗]
D[←] --- E[窗体内容] --- F[→]
G[↙] --- H[↓] --- I[↘]
end
classDef corner fill:#f9f,stroke:#333;
classDef edge fill:#bbf,stroke:#333;
class A,C,G,I,corner
class B,D,F,H,edge
```
| 方向 | 标识符 | 影响属性 | 说明 |
| ------ | ----------- | ------------------- | ------------------ |
| 左上角 | topLeft | width, height, x, y | 同时调整宽高和位置 |
| 上边缘 | top | height, y | 调整高度和垂直位置 |
| 右上角 | topRight | width, height, y | 调整宽高和垂直位置 |
| 右边缘 | right | width | 仅调整宽度 |
| 右下角 | bottomRight | width, height | 仅调整宽高 |
| 下边缘 | bottom | height | 仅调整高度 |
| 左下角 | bottomLeft | width, height, x | 调整宽高和水平位置 |
| 左边缘 | left | width, x | 调整宽度和水平位置 |
### 3.3 交互流程
```mermaid
sequenceDiagram
participant U as 用户
participant WH as 窗体句柄
participant WOH as 操作处理器
participant WM as 窗体管理器
participant WR as 窗体渲染器
participant EM as 事件管理器
U->>WH: 执行操作(最大化/最小化/还原/拖拽)
WH->>WOH: 处理操作请求
WOH->>WM: 更新窗体状态
WM->>WR: 应用新状态
WM->>EM: 发布状态变更事件
EM->>EM: 发布windowFormDataUpdate事件
WR->>U: 更新界面显示
```
## 4. 数据模型
### 4.1 窗体状态数据结构
```typescript
interface IWindowFormDataUpdateParams {
/** 窗口id */
id: string;
/** 窗口状态 */
state: TWindowFormState;
/** 窗口宽度 */
width: number;
/** 窗口高度 */
height: number;
/** 窗口x坐标(左上角) */
x: number;
/** 窗口y坐标(左上角) */
y: number;
}
type TWindowFormState = 'default' | 'minimized' | 'maximized';
```
### 4.2 窗体尺寸配置扩展
在现有`WindowConfig`接口基础上增加最小/最大尺寸限制:
| 字段 | 类型 | 必填 | 说明 |
| --------- | ------- | ---- | -------------------------- |
| minWidth | number | 可选 | 窗体最小宽度(像素) |
| minHeight | number | 可选 | 窗体最小高度(像素) |
| maxWidth | number | 可选 | 窗体最大宽度(像素) |
| maxHeight | number | 可选 | 窗体最大高度(像素) |
| resizable | boolean | 可选 | 是否可调整尺寸默认为true |
### 4.3 拖拽状态数据结构
```typescript
interface ResizeState {
/** 是否正在调整尺寸 */
isResizing: boolean;
/** 调整方向 */
direction: ResizeDirection;
/** 起始鼠标位置 */
startX: number;
/** 起始鼠标位置 */
startY: number;
/** 起始窗体宽度 */
startWidth: number;
/** 起始窗体高度 */
startHeight: number;
/** 起始窗体X坐标 */
startXPosition: number;
/** 起始窗体Y坐标 */
startYPosition: number;
}
type ResizeDirection =
'topLeft' | 'top' | 'topRight' |
'right' | 'bottomRight' | 'bottom' |
'bottomLeft' | 'left' | 'none';
```
## 5. 事件系统设计
### 5.1 修复事件触发问题
当前窗体状态变更时未正确触发`windowFormDataUpdate`事件,需要修复以下问题:
1. 在最大化操作完成后,应触发`windowFormDataUpdate`事件,携带窗体新状态和尺寸信息
2. 在最小化操作完成后,应触发`windowFormDataUpdate`事件,携带窗体新状态信息
3. 在还原操作完成后,应触发`windowFormDataUpdate`事件,携带窗体新状态和尺寸信息
### 5.2 新增事件定义
在现有`IWindowFormEvent`接口中添加以下事件:
| 事件名称 | 参数类型 | 触发时机 |
| --------------------- | --------------------------- | ---------------- |
| windowFormResizeStart | string | 开始调整窗体尺寸 |
| windowFormResizing | IWindowFormDataUpdateParams | 调整尺寸过程中 |
| windowFormResizeEnd | string | 完成窗体尺寸调整 |
### 5.3 事件触发流程
```mermaid
graph TD
A[窗体状态变更] --> B{是否需要更新数据?}
B -->|是| C[构造更新数据]
C --> D[触发windowFormDataUpdate事件]
B -->|否| E[直接触发状态事件]
D --> F[通知监听组件]
E --> F
```
## 6. 核心逻辑设计
### 6.1 窗体状态变更逻辑修复
当前窗体状态变更逻辑存在以下问题需要修复:
1. **最大化逻辑问题**
- 未正确保存原始窗体尺寸和位置信息
- 未触发`windowFormDataUpdate`事件通知
2. **最小化逻辑问题**
- 未触发`windowFormDataUpdate`事件通知
3. **还原逻辑问题**
- 未正确恢复窗体尺寸和位置
- 未触发`windowFormDataUpdate`事件通知
### 6.2 边缘检测算法
窗体边缘需要划分8个可拖拽区域每个区域宽度为8px
```mermaid
graph TD
subgraph 边缘区域划分
direction LR
A[8px↖] --- B[8px↑] --- C[8px↗]
D[8px←] --- E[窗体内容] --- F[8px→]
G[8px↙] --- H[8px↓] --- I[8px↘]
end
```
### 6.3 尺寸计算规则
1. **最小尺寸限制**窗体宽度不能小于minWidth高度不能小于minHeight
2. **最大尺寸限制**窗体宽度不能大于maxWidth高度不能大于maxHeight
3. **位置边界**:窗体不能被拖拽到屏幕外
### 6.4 拖拽处理流程
1. 鼠标按下时检测是否在边缘区域
2. 根据鼠标位置确定拖拽方向
3. 记录初始状态数据
4. 鼠标移动时实时计算新尺寸
5. 应用新尺寸并触发重渲染
6. 鼠标释放时结束拖拽状态
## 7. UI/UX设计
### 7.1 视觉反馈
- 鼠标悬停在可拖拽边缘时,光标应变为对应方向的调整光标
- 拖拽过程中窗体应有半透明遮罩效果
- 拖拽完成后应有平滑的过渡动画
### 7.2 响应式适配
- 在不同屏幕分辨率下保持边缘区域的可点击性
- 支持触屏设备的拖拽操作
- 在窗体尺寸接近最小/最大限制时提供视觉提示
## 8. 性能优化
### 8.1 事件处理优化
- 使用防抖机制减少高频事件触发
- 在拖拽过程中暂停不必要的重渲染
- 使用requestAnimationFrame优化动画性能
### 8.2 内存管理
- 及时清理事件监听器
- 复用计算对象避免频繁创建
- 在窗体销毁时清理所有相关资源
## 9. 安全考虑
### 9.1 边界检查
- 确保窗体不能被调整到超出屏幕边界
- 验证尺寸参数的有效性
- 防止负值或异常大值的输入
### 9.2 权限控制
- 只有具有调整权限的窗体才能被调整尺寸
- 外部应用的窗体尺寸调整需通过SDK接口
## 10. 测试策略
### 10.1 单元测试
- 边缘检测算法准确性测试
- 尺寸计算边界条件测试
- 事件发布/订阅机制测试
### 10.2 集成测试
- 不同方向拖拽功能测试
- 尺寸限制功能测试
- 与现有窗体功能集成测试
### 10.3 用户体验测试
- 拖拽流畅性测试
- 视觉反馈效果测试
- 不同设备兼容性测试

View File

@@ -1,204 +0,0 @@
# AppIcon组件
<cite>
**Referenced Files in This Document **
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心数据结构](#核心数据结构)
3. [拖拽交互机制](#拖拽交互机制)
4. [网格定位原理](#网格定位原理)
5. [事件过滤逻辑](#事件过滤逻辑)
6. [容器响应式布局](#容器响应式布局)
7. [图标重排策略](#图标重排策略)
8. [持久化存储](#持久化存储)
9. [扩展与最佳实践](#扩展与最佳实践)
## 简介
`AppIcon` 组件是桌面应用系统中的可交互图标单元实现了基于HTML5 Drag API的拖拽功能。该组件通过精确的坐标计算和网格系统集成允许用户将图标重新定位到桌面容器的任意有效网格位置。本文档深入解析其技术实现细节涵盖从拖拽事件处理、坐标换算、网格定位到状态持久化的完整流程。
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
## 核心数据结构
### IDesktopAppIcon 接口
定义了桌面图标的元数据结构,包含名称、图标资源、启动路径及在网格布局中的位置坐标。
```typescript
export interface IDesktopAppIcon {
name: string; // 图标显示名称
icon: string; // 图标资源路径或标识符
path: string; // 关联的应用程序启动路径
x: number; // 在grid布局中的列索引从1开始
y: number; // 在grid布局中的行索引从1开始
}
```
该设计意图明确:`x``y` 字段直接映射到CSS Grid的`grid-column``grid-row`属性实现了数据驱动的UI布局。
**Section sources**
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
## 拖拽交互机制
`AppIcon` 组件利用原生HTML5 Drag API实现拖拽功能
1. **启用拖拽**:通过设置 `draggable="true"` 属性激活元素的拖拽能力。
2. **事件监听**
- `@dragstart`:拖拽开始时触发,当前实现为空函数,保留未来扩展空间。
- `@dragend`:拖拽结束时触发,执行核心的坐标计算与位置更新逻辑。
此机制无需依赖第三方库,轻量且兼容性好。
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L2-L8)
## 网格定位原理
### 动态样式绑定
组件通过内联样式动态设置其在CSS Grid中的位置
```vue
:style="`grid-column: ${iconInfo.x}/${iconInfo.x + 1}; grid-row: ${iconInfo.y}/${iconInfo.y + 1};`"
```
此表达式将 `iconInfo.x``iconInfo.y` 的值转换为Grid的列/行跨度声明,确保图标精准占据一个网格单元。
### 坐标换算过程
`onDragEnd` 事件处理器的核心任务是将鼠标绝对坐标转换为相对网格坐标:
1. **获取容器边界**:使用 `getBoundingClientRect()` 获取父容器的绝对位置和尺寸。
2. **计算相对坐标**:通过 `e.clientX - rect.left``e.clientY - rect.top` 得到鼠标相对于容器左上角的偏移量。
3. **确定目标网格**:利用 `cellRealWidth``cellRealHeight` 进行除法运算并向上取整(`Math.ceil`),得到目标网格的行列索引。
```mermaid
flowchart TD
A[拖拽结束] --> B{获取鼠标<br/>clientX/clientY}
B --> C{获取容器<br/>getBoundingClientRect}
C --> D[计算鼠标相对<br/>容器坐标]
D --> E[用cellRealWidth/Height<br/>计算网格索引]
E --> F[更新iconInfo.x/y]
F --> G[触发UI重渲染]
```
**Diagram sources **
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L10-L38)
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L10-L38)
## 事件过滤逻辑
为了防止图标被错误地放置在其他图标之上,`onDragEnd` 方法实现了关键的事件过滤:
1. 使用 `document.elementFromPoint(e.clientX, e.clientY)` 检测鼠标终点位置的DOM元素。
2. 如果该元素是另一个 `.icon-container`,则立即返回,不执行任何位置更新。
此逻辑确保了“空位投放”原则,提升了用户体验,避免了图标的视觉重叠。
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L13-L17)
## 容器响应式布局
### IGridTemplateParams 接口
该接口定义了网格系统的动态参数,是实现响应式布局的关键。
```typescript
export interface IGridTemplateParams {
cellExpectWidth: number; // 单元格预设宽度
cellExpectHeight: number; // 单元格预设高度
cellRealWidth: number; // 单元格实际宽度
cellRealHeight: number; // 单元格实际高度
gapX: number; // 列间距
gapY: number; // 行间距
colCount: number; // 总列数
rowCount: number; // 总行数
}
```
### 实际尺寸计算
`useDesktopContainerInit` 函数通过 `ResizeObserver` 监听容器尺寸变化,并动态计算 `cellRealWidth``cellRealHeight`
```javascript
const w = containerRect.width - (gridTemplate.gapX * (gridTemplate.colCount - 1));
gridTemplate.cellRealWidth = Number((w / gridTemplate.colCount).toFixed(2));
```
此计算考虑了所有列间距的总和,确保了网格单元的实际尺寸能完美填充容器,无边距误差。
**Section sources**
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L30-L38)
## 图标重排策略
当网格的行列数发生变化时,`rearrangeIcons` 函数负责智能地重新分配图标位置:
1. **优先保留原位**:遍历所有图标,若其原位置在新网格范围内且未被占用,则保留在原位。
2. **寻找空位**:对于超出范围或位置冲突的图标,从 `(1,1)` 开始扫描,为其寻找第一个可用的空网格。
3. **隐藏溢出图标**:如果所有网格均被占用,则将无法安置的图标放入 `hideAppIcons` 数组。
此策略保证了用户自定义布局的最大程度保留,同时优雅地处理了空间不足的情况。
```mermaid
sequenceDiagram
participant DC as DesktopContainer
participant UDCI as useDesktopContainerInit
participant RI as rearrangeIcons
DC->>UDCI : 监听gridTemplate变化
UDCI->>RI : 调用rearrangeIcons()
loop 遍历每个图标
RI->>RI : 检查是否在新网格内且位置空闲
alt 是
RI->>RI : 保留在原位
else 否
RI->>RI : 加入临时队列
end
end
loop 处理临时队列
RI->>RI : 扫描网格寻找空位
alt 找到空位
RI->>RI : 分配新位置
else 无空位
RI->>RI : 加入hideAppIcons
end
end
RI-->>UDCI : 返回appIcons和hideAppIcons
UDCI->>DC : 更新appIconsRef
```
**Diagram sources **
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L77-L80)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L77-L80)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
## 持久化存储
用户的图标布局偏好通过 `localStorage` 实现持久化:
1. **写入**:使用 `watch` 监听 `appIconsRef.value` 的变化一旦有更新立即将整个数组序列化为JSON字符串并存入 `localStorage` 键名为 `'desktopAppIconInfo'` 的条目中。
2. **读取**:在初始化时,尝试从 `localStorage` 中读取 `'desktopAppIconInfo'`,若存在则使用存储的位置信息覆盖默认布局。
这确保了用户关闭页面后再次打开时,仍能看到上次调整好的桌面布局。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L88-L90)
## 扩展与最佳实践
### 自定义图标样式
可通过修改 `<style scoped>` 中的 `.icon-container` 类来定制图标外观,例如添加背景图片、阴影或动画效果。
### 扩展拖拽行为
可在 `onDragStart` 回调中添加逻辑,如设置拖拽图像 (`e.dataTransfer.setDragImage`) 或传输自定义数据。
### 避免跨容器拖拽冲突
**最佳方案**:为每个可拖拽区域(容器)维护独立的 `gridTemplate` 状态和图标集合。在 `onDragEnd` 事件中,首先确认 `pointTarget` 是否属于当前容器的 `.desktop-icons-container`,如果不是,则忽略此次拖拽操作,防止图标被错误地投放到其他容器中。

View File

@@ -1,309 +0,0 @@
# DesktopContainer组件
<cite>
**Referenced Files in This Document**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [App.vue](file://src/ui/App.vue)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心功能分析](#核心功能分析)
3. [响应式网格布局初始化](#响应式网格布局初始化)
4. [ResizeObserver尺寸监听机制](#resizeobserver尺寸监听机制)
5. [应用图标状态管理与持久化](#应用图标状态管理与持久化)
6. [模板渲染与事件处理](#模板渲染与事件处理)
7. [与父组件的数据流关系](#与父组件的数据流关系)
8. [自定义容器集成示例](#自定义容器集成示例)
## 简介
`DesktopContainer` 是 Vue 桌面应用的核心容器组件,负责管理桌面图标的布局、状态和交互。该组件通过组合式函数 `useDesktopContainerInit` 实现了动态响应式网格系统,并结合 `localStorage` 提供图标位置的持久化存储能力。作为桌面环境的主视图容器,它与 `App.vue` 父组件构成清晰的数据流结构,为上层应用提供稳定可靠的桌面管理服务。
## 核心功能分析
`DesktopContainer` 组件承担着桌面环境的核心职责,主要包括:
- 初始化并维护一个基于 CSS Grid 的响应式布局系统
- 动态计算网格行列数及单元格实际尺寸以适应容器变化
- 管理所有桌面应用图标的元数据及其在网格中的坐标位置
- 通过本地存储实现用户自定义图标准置的持久化
- 提供标准化的应用启动接口(双击事件)
- 支持拖拽重排功能并与子组件 `AppIcon` 协同工作
该组件的设计体现了关注点分离原则,将复杂的布局逻辑封装在独立的组合式函数中,保持了模板的简洁性和可维护性。
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 响应式网格布局初始化
### useDesktopContainerInit组合式函数
`useDesktopContainerInit` 函数是整个桌面布局系统的核心引擎,接收一个 CSS 选择器字符串 `containerStr` 作为参数,用于定位需要监控尺寸变化的 DOM 容器元素。
```mermaid
classDiagram
class IGridTemplateParams {
+cellExpectWidth : number
+cellExpectHeight : number
+cellRealWidth : number
+cellRealHeight : number
+gapX : number
+gapY : number
+colCount : number
+rowCount : number
}
class useDesktopContainerInit {
-container : HTMLElement
-gridTemplate : IGridTemplateParams
-ro : ResizeObserver
-appIconsRef : Ref~Array~
-exceedApp : Ref~Array~
+return gridStyle : ComputedRef
}
useDesktopContainerInit --> IGridTemplateParams : "uses"
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L1-L20)
### gridTemplate参数计算逻辑
`gridTemplate` 对象采用 `reactive` 声明为响应式数据,包含以下关键属性:
- `cellExpectWidth``cellExpectHeight`单元格期望尺寸默认90x110px
- `gapX``gapY`行列间距默认4px
- `colCount``rowCount`:动态计算的总行列数
初始状态下行列数设为1随后由 `ResizeObserver` 根据容器实际尺寸重新计算。
### gridStyle动态生成机制
通过 `computed` 属性 `gridStyle` 动态生成应用于容器的内联样式:
```mermaid
flowchart TD
Start([开始计算]) --> CalcColumns["构建 gridTemplateColumns<br/>repeat(colCount, minmax(cellExpectWidth + 'px', 1fr))"]
CalcColumns --> CalcRows["构建 gridTemplateRows<br/>repeat(rowCount, minmax(cellExpectHeight + 'px', 1fr))"]
CalcRows --> SetGap["设置 gap: gapY + 'px' gapX + 'px'"]
SetGap --> ReturnStyle["返回样式对象"]
ReturnStyle --> End([完成])
```
此计算属性确保了每当 `gridTemplate` 中的任何字段发生变化时,都能立即生成正确的 CSS Grid 样式规则。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## ResizeObserver尺寸监听机制
### 尺寸监听流程
`ResizeObserver` 被用来监听传入选择器所匹配容器的尺寸变化,其回调函数执行以下步骤:
```mermaid
sequenceDiagram
participant RO as ResizeObserver
participant CT as Container
participant GT as gridTemplate
RO->>CT : getBoundingClientRect()
CT-->>RO : 返回容器矩形信息
RO->>GT : 计算 colCount
RO->>GT : 计算 rowCount
RO->>GT : 计算 cellRealWidth
RO->>GT : 计算 cellRealHeight
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L38-L52)
### 行列数计算公式
根据容器当前宽度和高度,使用如下数学公式计算最优行列分布:
```
colCount = floor((width + gapX) / (cellExpectWidth + gapX))
rowCount = floor((height + gapY) / (cellExpectHeight + gapY))
```
这种算法确保即使在存在间隙的情况下也能最大化利用可用空间。
### 实际单元格尺寸调整
考虑到间隙对总可用空间的影响,实际单元格尺寸通过以下方式精确计算:
```typescript
const w = containerRect.width - (gapX * (colCount - 1))
const h = containerRect.height - (gapY * (rowCount - 1))
cellRealWidth = w / colCount
cellRealHeight = h / rowCount
```
最终结果保留两位小数,保证视觉上的平滑过渡。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L38-L52)
## 应用图标状态管理与持久化
### appIconsRef状态管理
`appIconsRef` 是一个 `ref` 类型的响应式数组,存储所有桌面应用图标的配置信息。每个图标对象遵循 `IDesktopAppIcon` 接口规范:
```mermaid
erDiagram
IDesktopAppIcon {
string name PK
string icon
string path
int x
int y
}
```
**Diagram sources**
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L1-L15)
初始图标数据来源于两个渠道:
1. 当前运行的应用进程列表(模拟为空数组)
2. `localStorage` 中保存的历史图标位置信息
系统优先使用历史记录中的坐标,若无则按顺序自动分配。
### localStorage持久化机制
通过 `watch` 监听器实现自动持久化:
```mermaid
flowchart LR
A[appIconsRef变化] --> B{触发watch}
B --> C[序列化为JSON字符串]
C --> D[存入localStorage]
D --> E[键名为'desktopAppIconInfo']
```
同时,在初始化时从 `localStorage` 读取已有数据,实现跨会话的状态恢复。
### 图标重排逻辑
当窗口大小导致网格行列数变化时,`rearrangeIcons` 函数会被调用,执行智能重排算法:
1. 优先保留原有有效位置的图标
2. 为移出可视区域的图标寻找新的空闲位置
3. 若无足够空间,则将其加入 `exceedApp` 隐藏列表
该机制确保用户体验的一致性,避免图标因窗口缩放而丢失。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L60-L94)
## 模板渲染与事件处理
### v-for循环渲染机制
组件模板使用 `v-for` 指令遍历 `appIconsRef` 数组,为每个图标实例化一个 `AppIcon` 子组件:
```vue
<AppIcon
v-for="(appIcon, i) in appIconsRef"
:key="i"
:iconInfo="appIcon"
:gridTemplate="gridTemplate"
@dblclick="runApp(appIcon)"
/>
```
`:key` 使用索引值确保渲染性能,`iconInfo``gridTemplate` 作为 props 向下传递必要数据。
### runApp双击事件扩展点
`@dblclick` 事件绑定到 `runApp` 方法,目前为空实现,作为未来功能扩展的预留接口:
```typescript
const runApp = (appIcon: IDesktopAppIcon) => {}
```
此处可集成应用启动逻辑,如进程管理器调用、窗口创建等。
### AppIcon组件协同
`AppIcon` 组件接收父级传递的 `gridTemplate` 参数,结合自身 `x/y` 坐标计算绝对位置:
```css
grid-column: ${x}/${x + 1};
grid-row: ${y}/${y + 1};
```
并实现拖拽功能,在释放时根据鼠标位置更新坐标,形成闭环控制。
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
## 与父组件的数据流关系
### 挂载关系分析
`DesktopContainer` 被直接嵌入 `App.vue` 的模板结构中,位于 `.desktop-bg` 容器内部:
```mermaid
graph TB
A[App.vue] --> B[desktop-root]
B --> C[desktop-bg]
C --> D[DesktopContainer]
B --> E[task-bar]
```
这种层级结构明确了其作为主内容区核心组件的地位。
### 数据流路径
数据流动遵循典型的 Vue 单向数据流模式:
```mermaid
flowchart LR
A[onMounted Hook] --> B[查询DOM容器]
B --> C[启动ResizeObserver]
C --> D[触发尺寸计算]
D --> E[更新gridTemplate]
E --> F[computed生成gridStyle]
F --> G[模板应用样式]
H[localStorage读取] --> I[初始化appIconsRef]
I --> J[渲染AppIcon列表]
J --> K[用户交互]
K --> L[更新appIconsRef]
L --> M[watch触发持久化]
```
整个过程无需向上通信,完全由组合式函数内部闭环处理。
**Section sources**
- [App.vue](file://src/ui/App.vue#L1-L52)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
## 自定义容器集成示例
要在其他环境中复用 `DesktopContainer`,可通过修改 `containerStr` 参数指定不同的监听目标:
```typescript
// 在自定义组件中使用
const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('#custom-desktop-container')
```
对应的 HTML 结构需包含匹配的选择器:
```html
<div id="custom-desktop-container" :style="gridStyle">
<!-- AppIcon will be rendered here -->
</div>
```
注意确保目标元素具有明确的尺寸(宽高),否则 `ResizeObserver` 无法正确计算布局参数。此外,建议保持原有的 CSS 类名以继承样式定义。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)

View File

@@ -1,254 +0,0 @@
# UI组件体系
<cite>
**Referenced Files in This Document **
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
- [App.vue](file://src/ui/App.vue)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心数据模型](#核心数据模型)
3. [主容器布局机制](#主容器布局机制)
4. [可拖拽图标实现](#可拖拽图标实现)
5. [组件层级与数据流](#组件层级与数据流)
6. [模板使用示例](#模板使用示例)
7. [自定义扩展建议](#自定义扩展建议)
## 简介
本文档深入解析Vue桌面应用的UI组件结构与交互逻辑。重点阐述`DesktopContainer.vue`作为主容器的动态网格布局机制,以及`AppIcon.vue`如何实现可拖拽的应用图标功能。通过分析`IDesktopAppIcon``IGridTemplateParams`接口定义,说明图标数据模型和网格参数的设计原理。同时解释从根组件`App.vue`到子组件`DesktopContainer`再到`AppIcon`的父子关系及数据传递方式,并提供实际使用示例与扩展建议。
## 核心数据模型
### 桌面应用图标接口 (IDesktopAppIcon)
该接口定义了桌面图标的元数据结构,包含名称、图标资源路径、启动路径及其在网格中的位置坐标。
```typescript
export interface IDesktopAppIcon {
name: string;
icon: string;
path: string;
x: number;
y: number;
}
```
**字段说明:**
- `name`: 图标显示名称
- `icon`: 图标资源路径
- `path`: 应用启动路径
- `x`: 在网格布局中的列索引从1开始
- `y`: 在网格布局中的行索引从1开始
此接口用于统一管理所有桌面图标的配置信息,并支持持久化存储至`localStorage`
### 网格模板参数接口 (IGridTemplateParams)
该接口定义了网格布局的核心计算参数,支持响应式调整。
```typescript
export interface IGridTemplateParams {
readonly cellExpectWidth: number
readonly cellExpectHeight: number
cellRealWidth: number
cellRealHeight: number
gapX: number
gapY: number
colCount: number
rowCount: number
}
```
**字段说明:**
- `cellExpectWidth/Height`: 单元格预设宽高
- `cellRealWidth/Height`: 实际渲染时的单元格宽高
- `gapX/Y`: 列/行间距
- `colCount/rowCount`: 当前容器可容纳的总行列数
这些参数由`ResizeObserver`监听容器尺寸变化后动态计算得出,确保布局自适应。
**Section sources**
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
## 主容器布局机制
`DesktopContainer.vue`组件负责构建整个桌面的网格布局系统。其核心是通过组合式API `useDesktopContainerInit` 初始化并维护网格状态。
### 响应式网格生成
组件利用CSS Grid布局特性通过计算属性`gridStyle`动态生成`grid-template-columns``grid-template-rows`样式规则:
```css
gridTemplateColumns: `repeat(${gridTemplate.colCount}, minmax(${gridTemplate.cellExpectWidth}px, 1fr))`
```
### 容器尺寸监听
使用`ResizeObserver` API实时监听`.desktop-icons-container`容器的尺寸变化,重新计算:
- 可容纳的行列数量 (`colCount`, `rowCount`)
- 单元格实际尺寸 (`cellRealWidth`, `cellRealHeight`)
### 图标初始化与持久化
首次加载时从`localStorage`读取历史图标位置信息,结合应用列表进行映射初始化。当图标位置变更时自动同步回本地存储。
### 超出边界处理
当窗口缩放导致图标超出可视区域时,`rearrangeIcons`函数会智能重排图标:
- 优先放置于原始位置
- 若冲突则寻找最近空位
- 实在无法容纳则暂存于`exceedApp`数组
```mermaid
flowchart TD
Start([组件挂载]) --> Observe["创建ResizeObserver"]
Observe --> InitGrid["初始化网格参数"]
InitGrid --> LoadStorage["从localStorage加载图标位置"]
LoadStorage --> MapIcons["映射应用信息与图标"]
MapIcons --> Render["渲染AppIcon组件"]
Resize["容器尺寸变化"] --> Recalculate["重新计算行列数"]
Recalculate --> Rearrange["调用rearrangeIcons重排"]
Rearrange --> UpdateState["更新appIconsRef状态"]
UpdateState --> SyncStorage["同步至localStorage"]
```
**Diagram sources **
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 可拖拽图标实现
`AppIcon.vue`组件实现了完整的拖拽交互逻辑,允许用户自由调整图标位置。
### 拖拽事件绑定
在模板中为图标容器启用原生HTML5拖拽API
```html
<div draggable="true" @dragstart="onDragStart" @dragend="onDragEnd">
```
### 位置计算逻辑
`dragend`事件中执行以下步骤:
1. 获取鼠标相对于容器的坐标
2. 根据单元格实际尺寸换算为网格坐标(向上取整)
3. 更新`iconInfo.x``iconInfo.y`属性
### 防止重复放置
通过检查目标元素是否已是其他图标容器来避免无效操作:
```javascript
if (pointTarget.classList.contains('icon-container')) return
```
### 样式定位
使用CSS Grid的`grid-column``grid-row`属性将图标精确定位于指定网格单元:
```css
grid-column: ${x}/${x + 1}; grid-row: ${y}/${y + 1}
```
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
## 组件层级与数据流
整个UI体系遵循清晰的父子组件层级结构数据沿树状结构单向流动。
### 组件层级关系
```mermaid
graph TD
A[App.vue] --> B[DesktopContainer.vue]
B --> C[AppIcon.vue]
```
### 数据传递路径
1. **App.vue****DesktopContainer.vue**: 通过直接嵌入`<DesktopContainer/>`标签建立父子关系
2. **DesktopContainer.vue****AppIcon.vue**:
- 使用`v-for`遍历`appIconsRef`数组创建多个实例
- 通过`defineProps`接收`iconInfo``gridTemplate`两个关键参数
- 绑定双击事件`@dblclick="runApp"`实现应用启动
### 状态管理特点
- **集中式初始化**:所有状态在`useDesktopContainerInit`中统一创建
- **响应式驱动**基于Vue的`ref``reactive`实现自动更新
- **本地持久化**:重要状态如图标位置通过`localStorage`保存
```mermaid
sequenceDiagram
participant App as App.vue
participant DC as DesktopContainer.vue
participant AI as AppIcon.vue
App->>DC : 渲染组件
DC->>DC : 执行useDesktopContainerInit()
DC->>DC : 初始化gridTemplate/appIconsRef
DC->>AI : v-for循环渲染多个实例
DC->>AI : 传递iconInfo和gridTemplate props
AI->>AI : 用户拖拽图标
AI->>AI : 计算新坐标并更新iconInfo
AI->>DC : 触发watch监听
DC->>DC : 同步至localStorage
```
**Diagram sources **
- [App.vue](file://src/ui/App.vue#L1-L52)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
**Section sources**
- [App.vue](file://src/ui/App.vue#L1-L52)
## 模板使用示例
以下是如何在项目中正确使用这些组件的标准范例:
```vue
<!-- 在任意父组件中引入DesktopContainer -->
<template>
<div class="desktop-wrapper">
<DesktopContainer />
</div>
</template>
<script setup lang="ts">
import DesktopContainer from '@/ui/desktop-container/DesktopContainer.vue'
</script>
```
每个`AppIcon`实例由`DesktopContainer`内部自动创建,无需手动实例化。只需确保:
1. 应用列表数据已正确注入`useDesktopContainerInit`
2. 图标资源配置路径准确无误
3. localStorage中保留历史位置记录以实现记忆功能
## 自定义扩展建议
### 样式定制
可通过覆盖SCSS变量或添加自定义类名修改图标外观
```scss
.icon-container {
@apply bg-transparent hover:bg-blue-100;
border-radius: 8px;
}
```
### 功能增强
- 添加右键菜单支持
- 实现图标排序功能(按名称、时间等)
- 增加动画过渡效果(拖拽、进入/离开)
### 性能优化
- 对大量图标实施虚拟滚动
- 使用Web Worker处理复杂重排算法
- 添加防抖机制优化频繁resize场景
### 接口拓展
可继承`IDesktopAppIcon`接口增加新字段:
```typescript
interface ExtendedIcon extends IDesktopAppIcon {
tooltip?: string;
category?: 'productivity' | 'entertainment' | 'development';
lastLaunched?: Date;
}
```
以上扩展应在保持原有接口兼容性的前提下进行,确保不影响现有功能。

View File

@@ -1,262 +0,0 @@
# 事件系统
<cite>
**Referenced Files in This Document **
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心组件分析](#核心组件分析)
3. [接口契约规范](#接口契约规范)
4. [事件管理器扩展实现](#事件管理器扩展实现)
5. [跨组件通信实例](#跨组件通信实例)
6. [错误处理与内存泄漏防范](#错误处理与内存泄漏防范)
## 简介
本文档全面记录了自定义事件总线系统的实现原理与使用方法。基于 `EventBuilderImpl` 类,详细解释 `addEventListener``removeEventListener``notifyEvent` 三个核心方法的工作机制,包括 `once``immediate` 等选项的行为特征。文档描述了 `IEventBuilder` 接口的契约规范,并分析了 `DesktopEventManager``WindowFormEventManager` 如何继承和扩展基础事件功能。同时提供跨组件通信的实际用例,并说明错误处理和内存泄漏防范措施。
## 核心组件分析
### EventBuilderImpl 实现机制
`EventBuilderImpl` 是事件总线系统的核心实现类,采用泛型设计支持类型安全的事件管理。其内部通过 `Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>` 结构存储事件处理器,确保高效的事件查找与去重。
#### addEventListener 方法工作机制
该方法用于注册事件监听器,具有以下特性:
- **去重机制**:在添加前检查是否已存在相同处理器函数,避免重复绑定
- **即时执行**:当 `options.immediate``true` 时,立即同步执行一次处理器
- **单次监听**:通过 `options.once` 标记,使监听器在触发后自动移除
- **类型安全**:利用 TypeScript 泛型约束确保事件名与处理器参数类型匹配
```mermaid
flowchart TD
Start([开始]) --> ValidateHandler["验证处理器非空"]
ValidateHandler --> HasEvent{"是否存在事件队列?"}
HasEvent --> |否| CreateSet["创建新的处理器集合"]
CreateSet --> AddToSet
HasEvent --> |是| GetSet["获取现有集合"]
GetSet --> AddToSet
AddToSet --> CheckDuplicate{"是否已存在?"}
CheckDuplicate --> |否| AddWrapper["添加包装器(once标记)"]
AddWrapper --> CheckImmediate{"是否立即执行?"}
CheckImmediate --> |是| ExecuteNow["立即执行处理器"]
CheckImmediate --> |否| End([结束])
ExecuteNow --> End
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L20-L46)
#### removeEventListener 方法工作机制
该方法通过遍历对应事件的处理器集合,精确匹配并删除指定的处理器函数引用,实现精准解绑。
#### notifyEvent 方法工作机制
通知方法按顺序调用所有注册的处理器,并处理以下特殊情况:
- 自动清理标记为 `once` 的监听器
- 捕获并记录处理器执行中的异常,防止中断其他监听器
- 支持任意数量的参数传递
```mermaid
sequenceDiagram
participant Publisher as "发布者"
participant EventBus as "EventBus"
participant Handler1 as "处理器1"
participant Handler2 as "处理器2"
Publisher->>EventBus : notifyEvent('event', args)
EventBus->>EventBus : 获取处理器集合
loop 每个处理器
EventBus->>Handler1 : 执行处理器
Handler1-->>EventBus : 完成
EventBus->>EventBus : 检查once标记
alt 是单次监听
EventBus->>EventBus : 从集合中移除
end
EventBus->>Handler2 : 执行处理器
Handler2-->>EventBus : 完成
end
EventBus-->>Publisher : 通知完成
Note over Handler1,Handler2 : 异常被捕获,不影响其他处理器执行
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 接口契约规范
### IEventBuilder 接口定义
`IEventBuilder` 接口定义了事件管理器的标准行为契约,继承自 `IDestroyable` 接口以支持资源清理。
```mermaid
classDiagram
class IEventBuilder {
<<interface>>
+addEventListener(eventName, handler, options) : void
+removeEventListener(eventName, handler) : void
+notifyEvent(eventName, ...args) : void
}
class IDestroyable {
<<interface>>
+destroy() : void
}
IEventBuilder --|> IDestroyable : 继承
class EventBuilderImpl {
-_eventHandlers : Map<string, Set<HandlerWrapper>>
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
EventBuilderImpl ..|> IEventBuilder : 实现
```
**Diagram sources**
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L13-L46)
接口方法参数说明:
| 方法 | 参数 | 类型 | 描述 |
|------|------|------|------|
| addEventListener | eventName | keyof Events | 事件名称 |
| | handler | F extends Events[E] | 事件处理器函数 |
| | options | {immediate?: boolean, immediateArgs?: Parameters<F>, once?: boolean} | 配置选项 |
| removeEventListener | eventName | keyof Events | 事件名称 |
| | handler | F extends Events[E] | 要移除的处理器函数 |
| notifyEvent | eventName | keyof Events | 事件名称 |
| | ...args | Parameters<F> | 传递给处理器的参数 |
**Section sources**
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L1-L46)
## 事件管理器扩展实现
### DesktopEventManager 桌面事件管理
`DesktopEventManager` 通过实例化 `EventBuilderImpl<IDesktopEvent>` 创建专用的桌面事件总线,定义了桌面应用相关的特定事件类型。
```mermaid
classDiagram
class IDesktopEvent {
<<interface>>
+desktopAppPosChange(info : IDesktopAppIcon) : void
}
class desktopEM {
+instance of EventBuilderImpl<IDesktopEvent>
}
desktopEM ..> IDesktopEvent : 类型约束
desktopEM ..> EventBuilderImpl : 实例化
```
**Diagram sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L1-L16)
### WindowFormEventManager 窗口表单事件管理
`WindowFormEventManager` 提供了窗口生命周期管理的完整事件体系,涵盖最小化、最大化、关闭、聚焦等状态变化。
```mermaid
classDiagram
class IWindowFormEvent {
<<interface>>
+windowFormMinimize(id : string) : void
+windowFormMaximize(id : string) : void
+windowFormRestore(id : string) : void
+windowFormClose(id : string) : void
+windowFormFocus(id : string) : void
+windowFormDataUpdate(data : IWindowFormDataUpdateParams) : void
+windowFormCreated() : void
}
class wfem {
+instance of EventBuilderImpl<IWindowFormEvent>
}
class IWindowFormDataUpdateParams {
+id : string
+state : TWindowFormState
+width : number
+height : number
+x : number
+y : number
}
wfem ..> IWindowFormEvent : 类型约束
IWindowFormEvent ..> IWindowFormDataUpdateParams : 引用
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L1-L61)
**Section sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L1-L16)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L1-L61)
## 跨组件通信实例
### 桌面应用位置更新场景
```typescript
// 发布者组件
const updatePosition = (iconInfo) => {
desktopEM.notifyEvent('desktopAppPosChange', iconInfo)
}
// 订阅者组件
desktopEM.addEventListener('desktopAppPosChange', (info) => {
console.log('应用位置更新:', info.name, info.x, info.y)
}, {
immediate: true,
immediateArgs: [currentIconInfo]
})
```
### 窗口状态变更场景
```typescript
// 窗口最小化
wfem.notifyEvent('windowFormMinimize', 'win123')
// 监听窗口最小化(仅一次)
wfem.addEventListener('windowFormMinimize', (id) => {
addToTaskbar(id)
}, {
once: true
})
// 监听所有窗口数据更新
wfem.addEventListener('windowFormDataUpdate', (data) => {
saveWindowState(data)
})
```
**Section sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L1-L16)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L1-L61)
## 错误处理与内存泄漏防范
### 错误处理机制
系统在关键执行路径上均包含异常捕获:
- 处理器执行时捕获异常,防止中断其他监听器
- 控制台输出错误信息便于调试
- 不抛出异常保证事件总线稳定性
### 内存泄漏防范措施
1. **及时解绑**:使用 `removeEventListener` 移除不再需要的监听器
2. **单次监听**:对只需执行一次的逻辑使用 `once: true` 选项
3. **资源清理**:实现 `destroy()` 方法清空所有事件处理器
4. **实例管理**:通过单例模式管理事件总线实例生命周期
```mermaid
flowchart TD
A[组件挂载] --> B[添加事件监听]
B --> C[设置once或immediate]
C --> D[组件运行]
D --> E{组件卸载?}
E --> |是| F[调用removeEventListener]
F --> G[或调用destroy清理所有]
G --> H[防止内存泄漏]
E --> |否| D
```
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L92-L94)

View File

@@ -1,207 +0,0 @@
# 核心事件总线
<cite>
**本文档引用的文件**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts)
- [EventManager.ts](file://src/events/EventManager.ts)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心数据结构设计](#核心数据结构设计)
3. [泛型类型安全机制](#泛型类型安全机制)
4. [事件监听器管理](#事件监听器管理)
- [addEventListener 方法详解](#addeventlistener-方法详解)
- [removeEventListener 方法详解](#removeeventlistener-方法详解)
5. [事件通知与分发](#事件通知与分发)
6. [资源清理与内存泄漏防范](#资源清理与内存泄漏防范)
7. [实际应用示例](#实际应用示例)
8. [总结](#总结)
## 简介
`EventBuilderImpl` 类是本系统事件机制的核心实现,提供了一个类型安全、功能完整的事件总线系统。该类实现了 `IEventBuilder` 接口,遵循发布-订阅模式,支持事件的注册、移除和触发,并具备立即执行、一次性监听等高级特性。通过继承 `IDestroyable` 接口,它还提供了资源清理能力,有效防止内存泄漏。
该事件系统被多个模块广泛使用,包括全局事件管理器(`eventManager`)、桌面事件管理器(`desktopEM`)和窗口表单事件管理器(`wfem`),构成了整个应用的通信骨架。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L13-L46)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts#L4-L7)
## 核心数据结构设计
`EventBuilderImpl` 采用 `Map``Set` 的组合来高效管理事件监听器,这种设计在性能和内存使用上达到了良好的平衡。
其核心是一个私有成员 `_eventHandlers`,其类型为 `Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>`。这个数据结构的含义是:
- **外层 Map**:以事件名称(`keyof Events`)作为键,确保每个事件名对应一个独立的监听器集合。
- **内层 Set**:存储特定事件的所有监听器包装对象(`HandlerWrapper`)。使用 `Set` 而非数组可以天然避免重复添加同一个监听函数,并且插入和删除操作的时间复杂度为 O(1)。
`HandlerWrapper` 是一个简单的接口,包含两个属性:`fn`(原始的监听函数)和 `once`(布尔值,标记是否为一次性监听器)。这种包装方式将业务逻辑(函数本身)与元数据(如 `once` 标志)分离,使得事件管理更加灵活。
```mermaid
classDiagram
class EventBuilderImpl~Events~ {
-_eventHandlers : Map~keyof Events, Set~HandlerWrapper~Events[keyof Events]~~~
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
class HandlerWrapper~T~ {
+fn : T
+once : boolean
}
EventBuilderImpl --> "0..*" HandlerWrapper : 包含
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L15)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L15)
## 泛型类型安全机制
`EventBuilderImpl` 类通过 TypeScript 的泛型系统实现了严格的类型安全。其定义为 `class EventBuilderImpl<Events extends IEventMap>`,其中 `Events` 是一个必须符合 `IEventMap` 接口约束的泛型类型。
`IEventMap` 接口定义了事件映射的基本结构:一个索引签名 `[key: string]: (...args: any[]) => void`,表示键是字符串类型的事件名,值是任意参数的无返回值函数。
当实例化 `EventBuilderImpl` 时,需要传入一个具体的事件接口。例如,在 `WindowFormEventManager.ts` 中定义了 `IWindowFormEvent` 接口,并将其作为泛型参数:
```typescript
export const wfem = new EventBuilderImpl<IWindowFormEvent>()
```
这种设计带来了以下优势:
1. **编译时检查**:在调用 `addEventListener``notifyEvent`TypeScript 编译器会根据 `IWindowFormEvent` 的定义检查事件名和参数类型是否正确。
2. **智能提示**:开发者在编写代码时能获得准确的事件名和参数类型提示。
3. **防止错误**:无法订阅未在接口中定义的事件,也无法传递错误类型的参数。
**Section sources**
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L4-L7)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L5-L59)
## 事件监听器管理
### addEventListener 方法详解
`addEventListener` 方法用于向事件总线注册新的监听器。它接收三个参数:事件名、处理函数和可选的配置项。
#### 配置项行为逻辑
- **immediate (立即执行)**:如果设置为 `true`,则在添加监听器后立即同步执行一次该函数。
- **immediateArgs (立即执行参数)**:为 `immediate` 执行阶段提供参数。若未指定,则使用空数组。
- **once (一次性监听)**:如果设置为 `true`,则该监听器在第一次被触发后自动从事件队列中移除。
#### 实现细节
1. **空值检查**:首先检查 `handler` 是否存在,避免无效监听器。
2. **惰性初始化**:如果这是该事件的第一个监听器,则创建一个新的 `Set` 来存放后续的 `HandlerWrapper`
3. **去重机制**:在添加前,通过遍历 `Set` 并比较 `wrapper.fn === handler` 来确保不会重复添加相同的函数引用。
4. **异常捕获**:在执行 `immediate` 回调时,使用 `try-catch` 捕获任何可能抛出的异常,并将其输出到控制台,防止因单个监听器的错误而中断主流程。
```mermaid
flowchart TD
Start([开始]) --> CheckHandler["检查 handler 是否为空"]
CheckHandler --> |否| InitSet["检查事件对应的 Set 是否存在"]
InitSet --> |否| CreateSet["创建新的 Set"]
CreateSet --> GetSet["获取事件对应的 Set"]
InitSet --> |是| GetSet
GetSet --> CheckDuplicate["检查是否已存在相同 handler"]
CheckDuplicate --> |否| AddWrapper["添加 HandlerWrapper 到 Set"]
AddWrapper --> CheckImmediate["检查 immediate 选项"]
CheckImmediate --> |是| ExecuteNow["立即执行 handler"]
ExecuteNow --> End([结束])
CheckImmediate --> |否| End
CheckHandler --> |是| End
CheckDuplicate --> |是| End
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L20-L46)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L20-L46)
### removeEventListener 方法详解
`removeEventListener` 方法负责从事件队列中移除指定的监听器。
其实现的关键在于**引用比对去重机制**。由于监听器是以 `HandlerWrapper` 对象的形式存储在 `Set` 中的,直接比较 `Set` 中的对象与传入的 `handler` 函数是不相等的。因此,该方法会遍历 `Set` 中的每一个 `wrapper`,并使用 `wrapper.fn === handler` 来精确匹配原始的函数引用。
一旦找到匹配项,就调用 `set.delete(wrapper)` 将其从 `Set` 中移除。这种基于引用的比对确保了只有完全相同的函数实例才会被移除,保证了操作的准确性。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L55-L64)
## 事件通知与分发
`notifyEvent` 方法是事件系统的触发点,负责通知所有订阅了特定事件的监听器。
其工作流程如下:
1. **存在性检查**:首先检查是否存在该事件名对应的监听器集合,若不存在则直接返回。
2. **批量通知**:遍历该事件名对应 `Set` 中的所有 `HandlerWrapper`
3. **异常捕获**:在调用每个监听器的 `fn(...args)` 时,使用 `try-catch` 块包裹,确保单个监听器的错误不会影响其他监听器的执行。
4. **once 监听器清理**:在成功调用一个监听器后,检查其 `once` 标志。如果为 `true`,则立即将其从 `Set` 中删除,实现一次性监听的功能。
这种“先通知,后清理”的策略保证了即使在 `once` 监听器执行过程中有其他代码尝试移除它,也不会产生竞态条件。
```mermaid
sequenceDiagram
participant Publisher as 事件发布者
participant EventBus as EventBuilderImpl
participant ListenerA as 监听器 A (once : true)
participant ListenerB as 监听器 B (once : false)
Publisher->>EventBus : notifyEvent('click', x, y)
EventBus->>ListenerA : 执行 fn(x, y)
ListenerA-->>EventBus : 完成
EventBus->>EventBus : 检查 wrapper.once == true
EventBus->>EventBus : 从 Set 中删除 ListenerA
EventBus->>ListenerB : 执行 fn(x, y)
ListenerB-->>EventBus : 完成
EventBus-->>Publisher : 通知完成
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
## 资源清理与内存泄漏防范
`EventBuilderImpl` 通过多种策略有效防范内存泄漏:
1. **显式销毁接口**:通过实现 `IDestroyable` 接口,提供了 `destroy()` 方法。调用此方法会清空 `_eventHandlers` Map释放所有对监听器函数的引用使它们可以被垃圾回收器回收。这对于长生命周期的应用或动态创建/销毁的组件至关重要。
2. **监听器去重**`addEventListener` 方法中的去重逻辑防止了同一函数被多次添加,避免了不必要的内存占用和重复执行。
3. **once 监听器自动清理**`once` 选项确保了一次性监听器在执行后立即被移除,无需手动清理。
4. **异常隔离**`try-catch` 机制保证了事件分发过程的健壮性,防止因监听器内部错误导致整个事件系统崩溃。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L92-L94)
- [IDestroyable.ts](file://src/common/types/IDestroyable.ts#L4-L7)
## 实际应用示例
以下是 `EventBuilderImpl` 在项目中的具体应用实例:
- **全局事件管理器**:在 `EventManager.ts` 中,`eventManager` 实例被用来管理认证状态改变 (`onAuthChange`) 和主题切换 (`onThemeChange`) 等全局事件。
- **桌面事件管理器**:在 `DesktopEventManager.ts` 中,`desktopEM` 实例用于响应桌面应用图标位置的变化 (`desktopAppPosChange`)。
- **窗口表单事件管理器**:在 `WindowFormEventManager.ts` 中,`wfem` 实例监控窗口的最小化、最大化、关闭等生命周期事件。
这些预定义的事件管理器实例化了 `EventBuilderImpl`,并通过具体的事件接口(如 `IWindowFormEvent`)锁定了可用的事件集,为不同模块提供了清晰、类型安全的通信契约。
**Section sources**
- [EventManager.ts](file://src/events/EventManager.ts#L3-L34)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L3-L15)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L3-L60)
## 总结
`EventBuilderImpl` 作为一个轻量级但功能完备的事件总线实现,通过精心设计的数据结构(`Map`+`Set`)和 TypeScript 泛型,实现了高性能、类型安全的事件管理。其 `addEventListener``removeEventListener``notifyEvent` 方法构成了一个健壮的发布-订阅循环,而 `immediate``once` 等选项以及异常捕获机制则增强了其实用性和可靠性。最后,通过 `IDestroyable` 接口提供的 `destroy` 方法,确保了资源的可管理性,有效防止了内存泄漏,是构建可维护前端应用的理想选择。

View File

@@ -1,187 +0,0 @@
<cite>
**本文档中引用的文件**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
</cite>
## 目录
1. [引言](#引言)
2. [核心事件总线架构](#核心事件总线架构)
3. [桌面事件接口定义](#桌面事件接口定义)
4. [事件管理器实例化与类型注入](#事件管理器实例化与类型注入)
5. [应用图标位置变化事件语义解析](#应用图标位置变化事件语义解析)
6. [桌面容器中的事件监听实践](#桌面容器中的事件监听实践)
7. [拖拽结束时的事件发布机制](#拖拽结束时的事件发布机制)
8. [总结](#总结)
## 引言
本文档详细阐述了`DesktopEventManager`如何基于核心事件总线构建特定领域的事件系统。通过分析`IDesktopEvent`接口、`desktopEM`实例化过程以及在`useDesktopContainerInit`中的实际用例,全面揭示该事件管理器的设计原理与运行机制。
## 核心事件总线架构
`DesktopEventManager`并非独立实现事件机制,而是基于一个通用的核心事件总线——`EventBuilderImpl`类进行领域特化。这种设计模式实现了关注点分离:底层提供统一的事件订阅与通知能力,上层定义具体业务语义。
```mermaid
classDiagram
class IEventBuilder~Events~ {
<<interface>>
+addEventListener(eventName, handler, options)
+removeEventListener(eventName, handler)
+notifyEvent(eventName, ...args)
}
class EventBuilderImpl~Events~ {
-_eventHandlers : Map<keyof Events, Set<HandlerWrapper>>
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
class IDesktopEvent {
<<interface>>
+desktopAppPosChange(info : IDesktopAppIcon)
}
class DesktopEventManager {
+desktopEM : EventBuilderImpl<IDesktopEvent>
}
EventBuilderImpl --> IEventBuilder : "implements"
DesktopEventManager ..> IDesktopEvent : "defines"
DesktopEventManager ..> EventBuilderImpl : "instantiates"
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L7-L15)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L7-L15)
## 桌面事件接口定义
`IDesktopEvent`接口继承自泛型事件映射接口`IEventMap`,专门用于声明桌面环境下的各类事件。其核心成员是`desktopAppPosChange`事件,明确表达了“桌面应用位置改变”的业务语义。
该接口采用TypeScript的索引签名与函数类型组合方式为每个事件名称绑定对应的回调函数签名确保类型安全。
**Section sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L7-L12)
## 事件管理器实例化与类型注入
`desktopEM`是一个全局单例实例,通过`new EventBuilderImpl<IDesktopEvent>()`创建。此处的关键在于泛型参数`IDesktopEvent`的注入,它将通用的`EventBuilderImpl`约束为仅支持处理`IDesktopEvent`所定义的事件类型。
这种类型注入机制保证了:
- 订阅时只能监听`desktopAppPosChange`等预定义事件
- 发布时必须提供符合`IDesktopAppIcon`结构的数据
- 编译期即可捕获类型错误,提升代码健壮性
```mermaid
sequenceDiagram
participant Code as "源码"
participant Compiler as "TypeScript编译器"
participant Runtime as "运行时"
Code->>Compiler : const desktopEM = new EventBuilderImpl<IDesktopEvent>()
Compiler->>Compiler : 类型检查验证IDesktopEvent结构
Compiler-->>Code : 返回类型安全的事件管理器实例
Code->>Runtime : desktopEM.addEventListener('desktopAppPosChange', handler)
Runtime->>Runtime : 存储事件处理器
```
**Diagram sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L15-L15)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
**Section sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L15-L15)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 应用图标位置变化事件语义解析
`desktopAppPosChange`事件承载着桌面应用图标位置变更的核心语义。其参数类型`IDesktopAppIcon`精确描述了图标的元数据:
| 属性 | 类型 | 描述 |
|------|------|------|
| `name` | string | 图标名称,唯一标识 |
| `icon` | string | 图标资源路径或标识符 |
| `path` | string | 点击后启动的应用路径 |
| `x` | number | 在网格布局中的列索引从1开始 |
| `y` | number | 在网格布局中的行索引从1开始 |
当用户拖动图标并释放时,系统会构造包含新坐标(x,y)的`IDesktopAppIcon`对象,并通过`desktopEM.notifyEvent`触发此事件通知所有监听者更新UI状态。
**Section sources**
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L11-L11)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
## 桌面容器中的事件监听实践
`useDesktopContainerInit`这一组合式函数中,展示了如何在桌面容器初始化过程中订阅`desktopAppPosChange`事件以实现UI同步更新。
虽然当前代码未直接展示订阅逻辑,但根据上下文可推断出典型使用模式如下:
```mermaid
flowchart TD
A["组件挂载 onMounted"] --> B["订阅 desktopAppPosChange 事件"]
B --> C["接收 IDesktopAppIcon 数据"]
C --> D["更新 appIconsRef 状态"]
D --> E["触发 Vue 响应式更新"]
E --> F["UI 自动重渲染"]
G["图标拖拽结束"] --> H["发布 desktopAppPosChange 事件"]
H --> B
```
理想情况下,在`onMounted`钩子内应调用:
```ts
desktopEM.addEventListener('desktopAppPosChange', (info) => {
const index = appIconsRef.value.findIndex(icon => icon.name === info.name);
if (index !== -1) {
appIconsRef.value[index] = { ...info };
}
});
```
从而建立从事件到视图的完整响应链路。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L0-L164)
## 拖拽结束时的事件发布机制
`desktopEM`的事件发布时机严格遵循用户交互生命周期。在应用图标的拖拽组件中应在拖拽结束dragend事件处理器中调用
```ts
desktopEM.notifyEvent('desktopAppPosChange', currentIconInfo);
```
此时传递的`currentIconInfo`必须是完整的`IDesktopAppIcon`对象,包含更新后的`x``y`坐标。该调用会遍历所有注册的监听器,并按顺序执行其回调函数,实现多播通知。
发布流程的关键特性包括:
- **同步执行**:所有监听器在同一线程内依次调用
- **异常隔离**:单个监听器错误不会中断其他监听器执行
- **一次性监听支持**:可通过`once: true`选项注册只触发一次的监听器
```mermaid
sequenceDiagram
participant DragComponent as "拖拽组件"
participant DesktopEM as "desktopEM"
participant ListenerA as "监听器A"
participant ListenerB as "监听器B"
DragComponent->>DesktopEM : notifyEvent('desktopAppPosChange', info)
DesktopEM->>ListenerA : 执行回调函数
ListenerA-->>DesktopEM : 完成
DesktopEM->>ListenerB : 执行回调函数
ListenerB-->>DesktopEM : 完成
DesktopEM-->>DragComponent : 通知完成
```
**Diagram sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts#L15-L15)
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L75-L90)
## 总结
`DesktopEventManager`通过泛型化继承核心事件总线`EventBuilderImpl`,成功构建了一个类型安全、语义清晰的领域事件系统。`IDesktopEvent`接口明确定义了`desktopAppPosChange`事件及其`IDesktopAppIcon`参数结构,`desktopEM`实例作为全局事件枢纽,在拖拽结束时精准发布坐标变更事件。尽管当前`useDesktopContainerInit`中尚未体现订阅逻辑但整个架构已为实现响应式桌面UI奠定了坚实基础。

View File

@@ -1,186 +0,0 @@
# 窗口关闭事件
<cite>
**Referenced Files in This Document **
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心职责与生命周期管理](#核心职责与生命周期管理)
3. [事件定义与接口规范](#事件定义与接口规范)
4. [事件注册与监听机制](#事件注册与监听机制)
5. [事件广播与跨组件通信](#事件广播与跨组件通信)
6. [资源清理与内存泄漏防护](#资源清理与内存泄漏防护)
7. [完整示例:带确认对话框的关闭流程](#完整示例:带确认对话框的关闭流程)
8. [总结](#总结)
## 简介
`windowFormClose` 事件是 Vue 桌面应用中用于管理窗口实例销毁的核心机制。当用户点击窗口右上角的关闭按钮时系统会触发该事件并携带目标窗口的唯一标识符ID以精确地定位并销毁对应的窗口实例。此事件不仅负责 UI 层面的 DOM 移除,还承担着状态存储更新、资源释放以及防止内存泄漏的重要职责。
本文档将深入解析 `windowFormClose` 事件的整个生命周期,涵盖其定义、注册、广播及处理过程,并说明如何通过 `wfem`Window Form Event Manager进行监听器注册集成用户确认逻辑并确保系统的安全性和一致性。
## 核心职责与生命周期管理
`windowFormClose` 事件在窗口关闭流程中扮演着中枢角色,其生命周期贯穿从用户交互到最终资源回收的全过程:
1. **触发阶段**:由用户点击关闭按钮发起,事件携带窗口 ID 作为参数。
2. **广播阶段**:通过 `wfem.notifyEvent('windowFormClose', id)` 将事件分发至所有注册的监听器。
3. **处理阶段**:各组件根据自身业务逻辑响应事件,执行如显示确认对话框、保存未提交数据等操作。
4. **清理阶段**:完成必要检查后,执行 DOM 节点移除、状态 store 更新和相关资源释放。
5. **终止阶段**:确保所有引用被清除,避免因闭包或事件监听器残留导致的内存泄漏。
该事件的设计遵循单一职责原则,专注于“关闭”这一核心动作,同时通过松耦合的发布-订阅模式实现跨组件协作。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L27-L27)
## 事件定义与接口规范
`windowFormClose` 事件在 `IWindowFormEvent` 接口中明确定义,位于 `src/events/WindowFormEventManager.ts` 文件中。该接口继承自通用事件映射类型 `IEventMap`,并声明了多个与窗口操作相关的事件处理器签名。
```typescript
interface IWindowFormEvent extends IEventMap {
/**
* 窗口关闭
* @param id 窗口id
*/
windowFormClose: (id: string) => void;
// 其他事件...
}
```
此定义明确了以下关键信息:
- **事件名称**`windowFormClose`
- **参数类型**:单个字符串类型的 `id`,用于唯一标识待关闭的窗口实例。
- **返回类型**`void`,表示该事件不期望返回值,主要用于触发副作用。
这种强类型的接口设计保证了事件使用的安全性,编译器能够在开发阶段捕获类型错误。
```mermaid
classDiagram
class IWindowFormEvent {
+windowFormMinimize(id : string) void
+windowFormMaximize(id : string) void
+windowFormRestore(id : string) void
+windowFormClose(id : string) void
+windowFormFocus(id : string) void
+windowFormDataUpdate(data : IWindowFormDataUpdateParams) void
+windowFormCreated() void
}
class IEventMap {
<<interface>>
}
IWindowFormEvent --|> IEventMap : 继承
```
**Diagram sources **
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L7-L42)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L7-L42)
## 事件注册与监听机制
系统使用 `wfem``WindowFormEventManager` 的实例)作为 `windowFormClose` 事件的管理中心。开发者可通过 `addEventListener` 方法注册监听器,语法如下:
```typescript
wfem.addEventListener('windowFormClose', (id) => {
// 处理关闭逻辑
});
```
`wfem` 是基于 `EventBuilderImpl` 类构建的事件总线实例,它提供了类型安全的事件注册、移除和通知功能。`addEventListener` 方法支持可选参数,例如 `immediate` 可用于在注册时立即执行一次回调,`once` 则确保监听器仅响应一次事件。
这种机制允许任意组件在需要时订阅关闭事件,而无需直接依赖具体的窗口管理逻辑,实现了高度的解耦。
```mermaid
sequenceDiagram
participant User as 用户
participant UI as UI组件
participant Wfem as wfem(事件管理器)
participant Listener as 关闭监听器
User->>UI : 点击关闭按钮
UI->>Wfem : notifyEvent("windowFormClose", windowId)
Wfem->>Listener : 执行所有注册的监听器
Listener-->>Wfem : 返回
Wfem-->>UI : 事件处理完毕
```
**Diagram sources **
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
## 事件广播与跨组件通信
`windowFormClose` 事件的广播能力由 `EventBuilderImpl` 类的 `notifyEvent` 方法实现。该方法遍历内部存储的所有监听器,并按顺序调用它们。这种发布-订阅模式是实现跨组件通信的关键。
其优势在于:
- **低耦合**:发送者(如窗口控件)无需知道接收者的存在,只需关注事件的发出。
- **高内聚**:每个监听器只需关注与自己相关的业务逻辑,例如一个监听器负责弹出确认框,另一个负责清理定时器。
- **可扩展性**:可以轻松添加新的监听器来增强关闭流程的功能,而无需修改现有代码。
事件广播确保了所有相关方都能及时收到关闭通知,从而协同完成复杂的清理任务,维护了系统状态的一致性。
```mermaid
graph TB
A[窗口A] --> |notifyEvent| B(wfem)
C[窗口B] --> |notifyEvent| B
D[其他组件] --> |notifyEvent| B
B --> E[监听器1]
B --> F[监听器2]
B --> G[监听器N]
```
**Diagram sources **
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 资源清理与内存泄漏防护
正确处理 `windowFormClose` 事件是防止内存泄漏的核心环节。一个完整的清理流程应包括:
1. **DOM 移除**:从虚拟 DOM 和真实 DOM 中卸载窗口及其子组件。
2. **状态更新**:在全局状态 store如 Pinia 或 Vuex中移除对应窗口的状态条目。
3. **事件解绑**:移除该窗口实例上注册的所有自定义事件监听器。
4. **资源释放**:清除与该窗口关联的定时器(`setInterval`, `setTimeout`、WebSocket 连接或其他长生命周期对象。
`EventBuilderImpl` 内部通过 `Set` 数据结构管理监听器,并在每次事件通知后检查 `once` 标志,自动清理一次性监听器。此外,`destroy` 方法可用于彻底清空所有事件处理器,为整个事件管理器的销毁提供支持。
**Section sources**
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 完整示例:带确认对话框的关闭流程
以下是一个典型的 `windowFormClose` 事件监听器实现,集成了用户确认对话框和资源释放逻辑:
```typescript
wfem.addEventListener('windowFormClose', async (id) => {
const shouldClose = await showConfirmDialog(`确定要关闭窗口 ${id} 吗?`);
if (!shouldClose) return; // 用户取消,中断关闭流程
// 1. 清理特定于该窗口的资源
cleanupWindowResources(id);
// 2. 从状态store中移除窗口记录
windowStore.removeWindow(id);
// 3. 触发DOM移除通常由组件自身在状态变更后自动处理
console.log(`窗口 ${id} 已成功关闭并清理`);
}, { once: false }); // 保持监听器长期有效
```
在此示例中:
- 使用异步函数等待用户确认。
- 若用户取消,则直接返回,阻止后续清理操作。
- 按照逻辑顺序执行资源清理、状态更新等步骤。
- 监听器设置为持久性(`once: false`),以便能响应未来可能发生的同类型事件。
## 总结
`windowFormClose` 事件是 Vue 桌面应用中窗口管理模块的基石。它通过清晰的接口定义、高效的事件广播机制和严谨的资源清理策略,确保了窗口实例能够被安全、可靠地销毁。借助 `wfem` 事件总线,系统实现了组件间的松耦合通信,使得复杂的关闭流程可以被分解为多个独立、可维护的监听器。遵循本文档所述的最佳实践,可以有效避免内存泄漏,保障应用的长期稳定运行。

View File

@@ -1,76 +0,0 @@
# 窗口创建完成事件
<cite>
**本文档引用文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [WindowService.ts](file://src/services/WindowService.ts)
</cite>
## 目录
1. [事件语义与调用时机](#事件语义与调用时机)
2. [应用场景分析](#应用场景分析)
3. [事件监听代码范例](#事件监听代码范例)
4. [生命周期时序关系](#生命周期时序关系)
## 事件语义与调用时机
`windowFormCreated` 事件在新窗口实例成功挂载并完成首次渲染后触发,标志着窗口已完全初始化并可交互。该事件不携带任何参数,作为全局窗口创建完成的信号。
根据 `WindowFormEventManager.ts` 中的定义,此事件是 `IWindowFormEvent` 接口的一部分,由 `wfem` 事件管理器负责分发。虽然当前实现中未直接显示触发逻辑,但结合 `WindowService.ts` 的窗口创建流程可知,该事件应在 `createWindow` 方法执行完毕、DOM 元素已添加至页面且应用内容加载完成后被通知。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L41-L41)
- [WindowService.ts](file://src/services/WindowService.ts#L83-L118)
## 应用场景分析
### 启动引导
在系统启动或模块初始化过程中,通过监听 `windowFormCreated` 事件可以确保所有核心窗口均已准备就绪,从而安全地执行后续引导逻辑,如自动聚焦主窗口或初始化关联组件。
### 任务栏更新
当新窗口创建完成后,任务栏组件可通过监听该事件实时更新其窗口列表,确保用户界面状态与实际运行情况保持同步。
### 快捷方式激活
从桌面快捷方式启动应用时,该事件可用于确认目标窗口已成功打开,进而执行焦点切换或动画展示等增强用户体验的操作。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L41-L41)
- [WindowService.ts](file://src/services/WindowService.ts#L83-L118)
## 事件监听代码范例
以下为监听 `windowFormCreated` 事件并执行后续操作的典型代码模式:
```typescript
import { wfem } from '@/events/WindowFormEventManager'
// 监听窗口创建完成事件
wfem.addEventListener('windowFormCreated', () => {
// 执行日志记录
console.log('新窗口创建完成')
// 执行自动聚焦或其他初始化操作
// focusMainWindow()
})
```
此类监听器常用于执行一次性初始化任务,例如设置默认焦点、注册快捷键、加载用户偏好设置或发送性能监控指标。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [WindowService.ts](file://src/services/WindowService.ts#L83-L118)
## 生命周期时序关系
`windowFormCreated` 事件作为窗口生命周期的起点,在以下关键阶段之后发生:
1. **CREATING**:窗口对象创建
2. **LOADING**DOM 元素构建与插入
3. **ACTIVE**:窗口激活并获得焦点
它早于任何用户交互事件(如 `windowFormFocus`)和状态变更事件(如 `windowFormMinimize`),是首个表示窗口已进入稳定可用状态的全局事件。
与其他事件相比,`windowFormCreated` 是唯一无参数的创建完成信号,而其他事件如 `windowFormDataUpdate` 则携带具体的状态数据。这种设计使其成为理想的初始化钩子点。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L41-L41)
- [WindowService.ts](file://src/services/WindowService.ts#L83-L118)

View File

@@ -1,149 +0,0 @@
# 窗口数据更新事件
<cite>
**本文档引用文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [WindowService.ts](file://src/services/WindowService.ts)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts)
</cite>
## 目录
1. [简介](#简介)
2. [IWindowFormDataUpdateParams接口字段语义解析](#iwindowformdataupdateparams接口字段语义解析)
3. [TWindowFormState类型关联说明](#twindowformstatetype类型关联说明)
4. [窗口拖拽、缩放与状态切换中的统一元数据发送机制](#窗口拖拽缩放与状态切换中的统一元数据发送机制)
5. [接收端批量更新UI的TypeScript示例](#接收端批量更新ui的typescript示例)
6. [高频更新性能优化建议:防抖策略](#高频更新性能优化建议防抖策略)
7. [窗口状态实时同步与持久化存储支持机制](#窗口状态实时同步与持久化存储支持机制)
## 简介
`windowFormDataUpdate` 事件是桌面应用中用于传递窗口最新元数据的核心通信机制。该事件在窗口发生位置移动、尺寸调整或状态变更(如最小化、最大化)时触发,通过 `IWindowFormDataUpdateParams` 接口统一封装窗口的ID、状态、尺寸和坐标信息并广播至所有监听者。此设计实现了窗口状态变化的集中通知与响应为UI同步、布局管理及状态持久化提供了基础支撑。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L37-L37)
## IWindowFormDataUpdateParams接口字段语义解析
`IWindowFormDataUpdateParams` 接口定义了窗口更新事件所携带的数据结构,包含以下关键字段:
- **id**: 字符串类型表示窗口的唯一标识符。该ID由系统生成并贯穿窗口生命周期用于精确识别和定位特定窗口实例。
- **state**: 枚举类型 `TWindowFormState`,表示当前窗口的状态,包括 `'default'`(默认)、`'minimized'`(最小化)、`'maximized'`(最大化)三种可能值。
- **width**: 数字类型,表示窗口当前的宽度(单位:像素),反映窗口水平方向的实际尺寸。
- **height**: 数字类型,表示窗口当前的高度(单位:像素),反映窗口垂直方向的实际尺寸。
- **x**: 数字类型表示窗口左上角相对于屏幕原点的X轴坐标单位像素用于确定窗口的水平位置。
- **y**: 数字类型表示窗口左上角相对于屏幕原点的Y轴坐标单位像素用于确定窗口的垂直位置。
这些字段共同构成了窗口的完整元数据快照,确保接收方能够准确还原窗口的视觉表现和行为状态。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L44-L57)
## TWindowFormState类型关联说明
`TWindowFormState` 是一个字符串联合类型,明确定义了窗口可处于的三种核心状态:
- `'default'`: 窗口处于正常显示状态,既非最大化也非最小化。
- `'minimized'`: 窗口被最小化,通常从主视图隐藏,仅在任务栏或启动器中保留图标。
- `'maximized'`: 窗口被最大化,占据除任务栏外的整个屏幕空间。
该类型与 `IWindowFormDataUpdateParams.state` 字段直接关联,作为事件数据的一部分,在窗口状态发生变化时(例如用户点击最大化按钮),新的状态值将被封装进事件参数中并广播出去。这种强类型的约束保证了状态传递的准确性与一致性。
**Section sources**
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
## 窗口拖拽、缩放与状态切换中的统一元数据发送机制
无论窗口因何种操作而改变其外观或状态,系统均通过 `windowFormDataUpdate` 事件统一发送最新的元数据。具体流程如下:
1. **拖拽操作**:当用户拖动窗口标题栏时,鼠标移动事件会持续更新窗口的 `x``y` 坐标。在每次坐标更新后,系统调用 `setActiveWindow` 激活该窗口,并最终通过事件总线触发 `windowFormDataUpdate`,附带更新后的坐标、尺寸及当前状态。
2. **缩放操作**:当用户调整窗口大小时,`setWindowSize` 方法被调用以更新 `width``height`。此方法不仅修改DOM样式还会通知事件总线从而触发包含新尺寸的 `windowFormDataUpdate` 事件。
3. **状态切换**:无论是最小化、最大化还是还原操作,都会先调用 `updateWindowState` 修改内部状态机随后根据新状态调整DOM表现如隐藏元素或全屏展示。最后系统发出 `windowFormDataUpdate` 事件,其中 `state` 字段反映最新状态,同时附带相应的坐标和尺寸信息。
这一机制确保了所有窗口变更都通过单一入口进行通知,简化了状态管理逻辑,避免了多事件源导致的不一致问题。
```mermaid
flowchart TD
A[用户操作] --> B{操作类型}
B --> |拖拽| C[更新 x, y]
B --> |缩放| D[更新 width, height]
B --> |状态切换| E[更新 state]
C --> F[调用 setActiveWindow]
D --> G[调用 setWindowSize]
E --> H[调用 updateWindowState]
F --> I[触发 windowFormDataUpdate]
G --> I
H --> I
I --> J[接收端更新UI]
```
**Diagram sources**
- [WindowService.ts](file://src/services/WindowService.ts#L512-L552)
- [WindowService.ts](file://src/services/WindowService.ts#L248-L304)
- [WindowService.ts](file://src/services/WindowService.ts#L179-L213)
**Section sources**
- [WindowService.ts](file://src/services/WindowService.ts#L512-L552)
- [WindowService.ts](file://src/services/WindowService.ts#L248-L304)
- [WindowService.ts](file://src/services/WindowService.ts#L179-L213)
## 接收端批量更新UI的TypeScript示例
接收 `windowFormDataUpdate` 事件后应使用接收到的完整数据对象一次性批量更新UI避免逐个属性设置带来的性能开销和视觉闪烁。以下为推荐的处理方式
```typescript
wfem.addEventListener('windowFormDataUpdate', (data) => {
const { id, state, width, height, x, y } = data;
const windowElement = document.getElementById(`window-${id}`);
if (!windowElement) return;
// 批量更新样式属性
Object.assign(windowElement.style, {
width: `${width}px`,
height: `${height}px`,
left: `${x}px`,
top: `${y}px`,
display: state === 'minimized' ? 'none' : 'block'
});
// 根据状态添加CSS类以支持主题化样式
windowElement.classList.toggle('maximized', state === 'maximized');
windowElement.classList.toggle('minimized', state === 'minimized');
windowElement.classList.toggle('default', state === 'default');
});
```
上述代码利用 `Object.assign` 对目标DOM元素的样式进行原子性更新确保浏览器只进行一次重排reflow和重绘repaint从而提升渲染效率。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L37-L37)
## 高频更新性能优化建议:防抖策略
由于拖拽和缩放操作会产生大量连续的 `windowFormDataUpdate` 事件若不对处理函数加以节流可能导致UI线程阻塞或频繁重绘影响用户体验。为此建议对接收端的事件处理器应用防抖debounce技术
```typescript
function debounce<T extends (...args: any[]) => void>(func: T, delay: number): T {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
} as T;
}
const debouncedHandler = debounce((data: IWindowFormDataUpdateParams) => {
// 执行UI更新逻辑
}, 16); // 约60fps的间隔
wfem.addEventListener('windowFormDataUpdate', debouncedHandler);
```
通过设置约16毫秒的延迟对应60Hz刷新率可以有效过滤掉中间过渡状态仅处理最终稳定的位置或尺寸显著降低计算负担并提升流畅度。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L44-L57)
## 窗口状态实时同步与持久化存储支持机制
`windowFormDataUpdate` 事件不仅是UI更新的驱动源也为跨组件通信和状态持久化提供了可靠的数据通道。任何需要感知窗口状态的模块如任务栏、窗口管理器、布局保存服务均可订阅此事件实现状态的实时同步。
此外,通过监听该事件,可将窗口的 `id``state``width``height``x``y` 等元数据记录到本地存储如localStorage或远程服务器实现用户偏好的记忆功能。例如在用户下次启动应用时系统可根据存储的历史数据恢复窗口的原始位置和大小提供一致的使用体验。
该事件的设计使得状态采集与业务逻辑解耦,任何持久化逻辑只需作为事件观察者存在,无需侵入窗口控制核心代码,符合关注点分离原则。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L37-L37)
- [WindowService.ts](file://src/services/WindowService.ts#L67-L118)

View File

@@ -1,114 +0,0 @@
# 窗口最大化事件
<cite>
**本文档引用文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [basic.css](file://src/css/basic.css)
</cite>
## 目录
1. [引言](#引言)
2. [事件触发机制](#事件触发机制)
3. [状态映射与UI响应](#状态映射与ui响应)
4. [CSS视觉变换实现](#css视觉变换实现)
5. [TypeScript代码范例](#typescript代码范例)
6. [多窗口协调机制](#多窗口协调机制)
7. [总结](#总结)
## 引言
本节全面阐述`windowFormMaximize`事件的触发条件与交互逻辑。当用户点击窗口最大化按钮时系统将广播该事件并携带窗口ID至所有订阅者驱动UI进入全屏布局模式。此机制是桌面级应用中实现窗口管理的核心部分。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L1-L60)
## 事件触发机制
`windowFormMaximize`事件定义于`IWindowFormEvent`接口中其函数签名接受一个字符串类型的窗口ID参数
```ts
windowFormMaximize: (id: string) => void;
```
该事件由全局事件管理器`wfem`(即`EventBuilderImpl<IWindowFormEvent>`实例)负责分发。当用户交互触发最大化行为时,调用`notifyEvent('windowFormMaximize', windowId)`方法,向所有监听此事件的组件广播通知。
事件系统基于观察者模式实现,支持动态注册和注销监听器,并可通过配置项实现立即执行、单次监听等功能。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L17-L17)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 状态映射与UI响应
`windowFormMaximize`事件与`TWindowFormState`类型中的`maximized`状态存在直接映射关系。`TWindowFormState`定义如下:
```ts
export type TWindowFormState = 'default' | 'minimized' | 'maximized';
```
当事件被触发后,相关组件会更新对应窗口的状态为`maximized`,并通过响应式机制驱动视图重绘。通常结合`windowFormDataUpdate`事件同步窗口尺寸与位置信息,确保状态一致性。
状态变更不仅影响当前窗口的显示模式还可能触发任务栏图标高亮、Z轴层级调整等副作用以保证用户体验的一致性。
**Section sources**
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L50-L55)
## CSS视觉变换实现
窗口最大化过程中的视觉效果主要通过CSS变换实现。虽然具体样式未在核心逻辑文件中体现但可推断其依赖以下机制
- 使用`transform: scale()``width/height: 100%`实现尺寸扩展
- 配合过渡动画transition实现平滑缩放
- 利用`z-index`控制多窗口堆叠顺序
- 可能结合`position: fixed``absolute`脱离文档流进行定位
基础样式文件`basic.css`提供了通用的盒模型重置、字体设置及响应式支持,为窗口动画提供稳定的样式环境。
**Section sources**
- [basic.css](file://src/css/basic.css#L1-L134)
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
## TypeScript代码范例
使用`wfem`监听并处理最大化行为的标准TypeScript代码范例如下
```ts
// 订阅窗口最大化事件
wfem.addEventListener('windowFormMaximize', (id: string) => {
// 更新窗口状态
const window = getWindowById(id);
if (window) {
window.state = 'maximized';
// 触发DOM重绘
redrawWindow(window);
// 持久化状态(可选)
saveWindowState(id, 'maximized');
}
});
// 发送最大化事件
function maximizeWindow(id: string) {
wfem.notifyEvent('windowFormMaximize', id);
}
```
建议在状态变更后调用重绘函数,并考虑将用户偏好(如是否最大化)持久化至`localStorage`,以便页面刷新后恢复。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L70-L75)
## 多窗口协调机制
在多窗口堆叠场景下,`windowFormMaximize`事件起到关键协调作用。当某一窗口最大化时:
- 其他非最小化窗口应自动退至后台
- 最大化窗口获得最高`z-index`层级
- 任务栏对应图标处于激活状态
- 若存在模态窗口,则需特殊处理避免遮挡
事件广播机制确保所有关注窗口状态的模块(如任务栏、窗口管理器、快捷键服务)能同步响应,维持系统整体状态一致。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L17-L17)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
## 总结
`windowFormMaximize`事件作为窗口控制系统的重要组成部分实现了从用户操作到UI响应的完整闭环。它通过标准化的事件总线机制解耦组件间通信结合类型安全的状态定义与CSS视觉变换构建出流畅且可维护的桌面级交互体验。在复杂多窗口环境中该事件有效协调各组件行为保障系统稳定性与一致性。

View File

@@ -1,96 +0,0 @@
# 窗口最小化事件
<cite>
**本文档引用的文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
</cite>
## 目录
1. [事件机制概述](#事件机制概述)
2. [事件触发与参数传递](#事件触发与参数传递)
3. [状态同步与枚举值应用](#状态同步与枚举值应用)
4. [监听器注册与Pinia状态更新](#监听器注册与pinia状态更新)
5. [事件解耦设计分析](#事件解耦设计分析)
## 事件机制概述
`windowFormMinimize` 事件是窗口管理系统中的核心交互事件之一,用于响应用户点击窗口最小化按钮的操作。该事件通过全局事件管理器 `wfem` 进行广播,采用发布-订阅模式实现组件间的松耦合通信。事件定义在 `IWindowFormEvent` 接口中,并由 `EventBuilderImpl` 类提供具体的事件注册与通知能力。
此事件机制的设计使得任意组件均可独立监听窗口最小化行为,而无需直接依赖窗口实例本身,从而提升了系统的可维护性与扩展性。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L12-L12)
## 事件触发与参数传递
当用户点击窗口的最小化按钮时,系统会调用 `wfem.notifyEvent('windowFormMinimize', id)` 方法触发该事件。其中 `id` 为窗口的唯一标识符(字符串类型),作为事件回调函数的参数传递给所有监听者。
事件的接口定义如下:
```ts
windowFormMinimize: (id: string) => void;
```
这确保了所有监听器都能接收到被最小化的窗口ID进而执行相应的UI动画或逻辑处理。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L12-L12)
## 状态同步与枚举值应用
在接收到 `windowFormMinimize` 事件后,系统通常会将对应窗口的状态更新为 `'minimized'`。该状态值来源于 `TWindowFormState` 枚举类型,其定义如下:
```ts
export type TWindowFormState = 'default' | 'minimized' | 'maximized';
```
通过将窗口状态设置为 `'minimized'`,可以驱动视图层进行相应的渲染更新,例如隐藏窗口内容、在任务栏显示缩略图标等。这种基于枚举的状态管理模式增强了代码的可读性和类型安全性。
**Section sources**
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
## 监听器注册与Pinia状态更新
在Vue组件中可通过 `wfem.addEventListener` 方法注册对 `windowFormMinimize` 事件的监听。典型用法如下所示:
```ts
wfem.addEventListener('windowFormMinimize', (id: string) => {
// 更新Pinia store中的窗口状态
const windowStore = useWindowStore();
windowStore.updateState(id, 'minimized');
});
```
上述代码展示了如何在事件回调中获取窗口ID并更新Pinia状态仓库中的窗口状态字段。这种方式实现了视图与状态的分离符合现代前端架构的最佳实践。
尽管当前项目中未包含 `useWindowStore` 的具体实现但基于现有技术栈Vue + Pinia可合理推断其存在类似的窗口状态管理模块。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
## 事件解耦设计分析
`windowFormMinimize` 事件体现了典型的事件驱动解耦设计。多个窗口组件可以各自注册监听器,独立响应最小化事件,而无需彼此知晓或直接通信。事件的发布者仅需调用 `notifyEvent`,由 `EventBuilderImpl` 负责遍历所有订阅者并执行回调。
该设计的优势包括:
- **高内聚低耦合**:各组件专注于自身逻辑,不依赖其他窗口的状态。
- **易于扩展**:新增窗口组件只需添加监听器即可参与事件流。
- **便于测试**事件监听逻辑可单独单元测试无需完整UI环境。
```mermaid
flowchart TD
A[用户点击最小化按钮] --> B[触发 windowFormMinimize 事件]
B --> C{通知所有监听者}
C --> D[窗口A监听器: 执行最小化动画]
C --> E[窗口B监听器: 更新状态为 minimized]
C --> F[任务栏组件: 显示最小化图标]
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L12-L12)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L50-L75)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L50-L75)

View File

@@ -1,131 +0,0 @@
<cite>
**本文档中引用的文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [useClickFocus.ts](file://src/common/hooks/useClickFocus.ts)
- [useObservableVue.ts](file://src/common/hooks/useObservableVue.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
</cite>
## 目录
1. [窗口聚焦事件](#窗口聚焦事件)
2. [用户交互背景](#用户交互背景)
3. [技术实现机制](#技术实现机制)
4. [多窗口层级管理](#多窗口层级管理)
5. [Vue组件中的事件监听](#vue组件中的事件监听)
6. [响应式状态更新](#响应式状态更新)
7. [用户体验影响](#用户体验影响)
## 窗口聚焦事件
`windowFormFocus` 事件是桌面级Vue应用中用于管理窗口激活状态的核心事件。当用户点击或通过其他方式激活某个窗口时该事件被触发并广播被激活窗口的唯一标识符id从而驱动UI层面对应的视觉反馈和状态变更。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L32-L32)
## 用户交互背景
在模拟传统桌面操作系统的Web应用中用户期望拥有与原生应用一致的窗口交互体验。当用户点击一个处于非顶层的窗口时该窗口应当自动移动到所有其他窗口的前面并获得输入焦点。这种行为符合用户的直觉认知即“所见即所得”的交互原则。`windowFormFocus` 事件正是为了捕捉这一关键的用户意图而设计的,它作为用户操作与系统响应之间的桥梁,确保了界面行为的可预测性和一致性。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L30-L34)
## 技术实现机制
`windowFormFocus` 事件的技术实现依赖于一个名为 `wfem` (Window Form Event Manager) 的全局事件管理器实例,该实例基于 `EventBuilderImpl` 类构建,提供了一套完整的发布-订阅模式接口。
事件的触发流程如下:
1. **事件定义**:在 `IWindowFormEvent` 接口中明确定义了 `windowFormFocus(id: string)` 方法签名。
2. **事件触发**:当检测到用户对特定窗口的点击操作时,业务逻辑代码会调用 `wfem.notifyEvent('windowFormFocus', windowId)` 来广播该事件。
3. **事件监听**:任何需要对此事件做出反应的组件都可以通过 `wfem.addEventListener('windowFormFocus', callback)` 注册一个回调函数。
```mermaid
sequenceDiagram
participant User as 用户
participant Window as 窗口组件
participant Hook as useClickFocus Hook
participant EventManager as wfem (事件管理器)
User->>Window : 点击窗口
Window->>Hook : 触发内部处理
Hook->>EventManager : 调用 notifyEvent('windowFormFocus', id)
EventManager->>EventManager : 遍历所有监听器
loop 通知所有订阅者
EventManager-->>Subscriber : 执行注册的回调函数
end
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [useClickFocus.ts](file://src/common/hooks/useClickFocus.ts#L13-L64)
## 多窗口层级管理
在存在多个重叠窗口的应用场景下,维护正确的渲染顺序至关重要。`windowFormFocus` 事件在此扮演了核心角色。每当该事件被触发其携带的窗口ID会被传递给负责管理窗口栈的逻辑模块。该模块会根据ID找到对应的窗口元素并将其CSS `z-index` 属性提升至最高层级,同时移除之前处于顶层窗口的焦点样式,并为当前窗口应用新的焦点样式(如高亮边框)。这一系列操作确保了被激活的窗口始终位于最前端,解决了视觉遮挡问题,使用户能够清晰地识别出当前正在操作的对象。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L30-L34)
## Vue组件中的事件监听
在Vue组件中监听 `windowFormFocus` 事件是一个标准的异步编程实践。开发者可以在组件的 `setup` 函数或 `onMounted` 生命周期钩子中,使用 `wfem.addEventListener` 方法注册一个监听器。最佳实践是将此逻辑封装在一个自定义Hook中以保证代码的复用性和可维护性。例如可以创建一个 `useWindowFocusListener` Hook在组件挂载时订阅事件并在组件卸载前 (`onBeforeUnmount`) 及时移除监听器,防止内存泄漏。
```mermaid
flowchart TD
A[组件挂载] --> B["调用 wfem.addEventListener('windowFormFocus', callback)"]
B --> C[事件管理器注册回调]
D[用户点击窗口] --> E["触发 wfem.notifyEvent('windowFormFocus', id)"]
E --> F[事件管理器遍历并执行所有回调]
F --> G[组件中的回调函数被调用]
G --> H[更新组件内部状态]
I[组件卸载] --> J["调用 wfem.removeEventListener 移除监听"]
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [useClickFocus.ts](file://src/common/hooks/useClickFocus.ts#L13-L64)
## 响应式状态更新
为了实现高效且直观的状态管理,建议结合 `useObservableVue` 这一自定义Hook来处理 `windowFormFocus` 事件带来的状态变化。`useObservableVue` 将一个实现了 `IObservable<T>` 接口的可观察对象转换为Vue的响应式对象。当 `windowFormFocus` 事件触发时,其回调函数可以直接修改存储在可观察对象中的 `activeWindow` 状态。由于 `useObservableVue` 内部建立了双向同步机制对可观察对象的任何修改都会立即反映到Vue的响应式系统中从而自动触发相关组件的重新渲染无需手动调用 `forceUpdate` 或进行复杂的 `$emit` 操作。
```mermaid
classDiagram
class IObservable~T~ {
<<interface>>
+subscribe(callback) : () => void
+toRefsProxy() : T
}
class ObservableImpl~T~ {
-state : T
-observers : Set~() => void~
+subscribe(callback) : () => void
+toRefsProxy() : T
+notify() : void
}
class useObservableVue {
+useObservable(observable) : Reactive~T~
}
class Component {
+setup()
+template
}
IObservable~T~ <|.. ObservableImpl~T~ : 实现
useObservableVue --> IObservable~T~ : 使用
Component --> useObservableVue : 调用
ObservableImpl~T~ --> Component : 通过响应式更新视图
```
**Diagram sources**
- [useObservableVue.ts](file://src/common/hooks/useObservableVue.ts#L13-L87)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L1-L96)
## 用户体验影响
`windowFormFocus` 事件对桌面级应用的用户体验具有决定性的影响。它直接决定了应用是否能提供流畅、自然的多任务操作环境。一个正确实现的窗口聚焦机制能够:
- **降低认知负荷**:用户无需思考哪个窗口是活动的,视觉反馈清晰明确。
- **提高操作效率**:通过简单的点击即可切换上下文,符合肌肉记忆。
- **增强应用可信度**:其行为与用户熟悉的操作系统保持一致,提升了应用的专业感和可靠性。
反之,如果该事件处理不当,例如出现窗口无法置顶、焦点丢失或样式错乱等问题,将严重破坏用户对应用的信任,导致挫败感和使用障碍。因此,确保 `windowFormFocus` 事件的稳定性和准确性是构建高质量桌面级Web应用的关键基石。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L30-L34)

View File

@@ -1,196 +0,0 @@
# 窗口表单事件管理器
<cite>
**本文档引用的文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts)
</cite>
## 目录
1. [简介](#简介)
2. [窗口生命周期事件体系](#窗口生命周期事件体系)
3. [IWindowFormDataUpdateParams数据结构分析](#iwindowformdataupdateparams数据结构分析)
4. [wfem实例共享通信机制](#wfem实例共享通信机制)
5. [Vue组件中事件监听代码范例](#vue组件中事件监听代码范例)
6. [事件解耦带来的低耦合优势](#事件解耦带来的低耦合优势)
## 简介
本系统通过`WindowFormEventManager`模块提供了一套完整的窗口生命周期事件管理体系,用于统一管理桌面应用中各类窗口的状态变化与用户交互行为。该体系基于观察者模式实现,支持多个窗口组件之间高效、安全地进行状态同步和通信。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L1-L60)
## 窗口生命周期事件体系
系统定义了七种核心窗口事件,分别对应不同的用户操作或状态变更场景:
### windowFormMinimize窗口最小化
当用户点击窗口最小化按钮时触发,通知相关组件当前窗口已进入最小化状态。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L10-L13)
### windowFormMaximize窗口最大化
当用户点击窗口最大化按钮时触发,表示窗口已切换至全屏展示模式。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L15-L18)
### windowFormRestore窗口还原
在窗口处于最大化或最小化状态下,用户执行还原操作时触发,表明窗口恢复为正常尺寸。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L20-L23)
### windowFormClose窗口关闭
用户请求关闭窗口时触发,可用于执行清理逻辑或确认提示。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L25-L28)
### windowFormFocus窗口聚焦
当窗口获得焦点(即被激活)时触发,常用于高亮显示当前活动窗口。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L30-L33)
### windowFormDataUpdate窗口数据更新
每当窗口的位置、大小或状态发生变化时触发,携带完整的窗口元信息。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L35-L39)
### windowFormCreated窗口创建完成
新窗口初始化完毕后触发一次,标志窗口已准备就绪可交互。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L41-L43)
## IWindowFormDataUpdateParams数据结构分析
此接口定义了窗口状态更新时传递的数据结构,各字段含义如下:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | string | 唯一标识一个窗口实例的ID |
| state | TWindowFormState | 当前窗口的视觉状态 |
| width | number | 窗口当前像素宽度 |
| height | number | 窗口当前像素高度 |
| x | number | 窗口左上角相对于屏幕的X坐标 |
| y | number | 窗口左上角相对于屏幕的Y坐标 |
其中,`TWindowFormState` 是一个联合类型,定义于 `WindowFormTypes.ts` 文件中,包含三种可能取值:
- `'default'`:默认状态(正常大小)
- `'minimized'`:最小化状态
- `'maximized'`:最大化状态
这些字段共同构成了窗口的完整状态快照,便于其他组件根据最新布局信息做出响应。
```mermaid
classDiagram
class IWindowFormDataUpdateParams {
+id : string
+state : TWindowFormState
+width : number
+height : number
+x : number
+y : number
}
class TWindowFormState {
<<type>>
'default'
'minimized'
'maximized'
}
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L44-L57)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L44-L57)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L9-L9)
## wfem实例共享通信机制
`wfem` 是一个全局唯一的 `EventBuilderImpl<IWindowFormEvent>` 实例,作为事件中心被所有窗口组件共享使用。其工作原理如下:
1. **事件注册**:任意组件可通过 `wfem.addEventListener(eventName, handler)` 订阅特定事件。
2. **事件分发**:当窗口状态改变时,调用 `wfem.notifyEvent(eventName, args)` 广播事件。
3. **跨组件通信**:不同组件间无需直接引用,仅通过事件总线即可实现松散耦合的状态同步。
这种设计使得各个窗口组件可以独立开发、测试和维护,同时又能实时感知彼此的状态变化。
```mermaid
sequenceDiagram
participant ComponentA as 组件A
participant ComponentB as 组件B
participant Wfem as wfem事件中心
ComponentA->>Wfem : addEventListener("focus", handler)
ComponentB->>Wfem : addEventListener("close", handler)
Note over Wfem : 多个组件注册监听
Window->>Wfem : notifyEvent("focus", "win123")
Wfem->>ComponentA : 执行handler("win123")
Window->>Wfem : notifyEvent("close", "win123")
Wfem->>ComponentB : 执行handler("win123")
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L1-L95)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
## Vue组件中事件监听代码范例
以下是在Vue 3组件中监听窗口聚焦与关闭事件的典型用法
```typescript
import { onMounted, onBeforeUnmount } from 'vue';
import { wfem } from '@/events/WindowFormEventManager';
export default {
setup() {
const handleFocus = (id: string) => {
console.log(`窗口 ${id} 获得焦点`);
// 更新UI状态如添加边框高亮
};
const handleClose = (id: string) => {
console.log(`窗口 ${id} 已关闭`);
// 清理资源移除DOM节点等
};
onMounted(() => {
wfem.addEventListener('windowFormFocus', handleFocus);
wfem.addEventListener('windowFormClose', handleClose);
});
onBeforeUnmount(() => {
wfem.removeEventListener('windowFormFocus', handleFocus);
wfem.removeEventListener('windowFormClose', handleClose);
});
return {};
}
}
```
上述代码展示了如何在组件挂载时注册事件监听,并在卸载前正确移除,避免内存泄漏。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L30-L39)
## 事件解耦带来的低耦合优势
采用事件驱动架构后,各组件之间的依赖关系显著降低,具体优势包括:
- **独立性增强**:组件无需知道谁会发送或接收事件,只需关注自身职责。
- **可维护性提升**:修改某一组件不会影响其他不相关的模块。
- **扩展性强**:新增功能只需监听已有事件,无需改动现有逻辑。
- **测试更简单**:可单独对每个组件进行单元测试,模拟事件输入即可验证行为。
通过 `wfem` 这一统一事件通道,系统实现了高度模块化的设计,有利于长期迭代和团队协作开发。
**Section sources**
- [IEventBuilder.ts](file://src/events/IEventBuilder.ts#L1-L46)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L1-L95)

View File

@@ -1,120 +0,0 @@
# 窗口还原事件
<cite>
**本文档引用的文件**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心设计与运行机制](#核心设计与运行机制)
3. [事件协同关系分析](#事件协同关系分析)
4. [Vue组件中的实践应用](#vue组件中的实践应用)
5. [在窗口状态机中的角色](#在窗口状态机中的角色)
## 简介
`windowFormRestore` 事件是桌面应用中用于处理窗口从最大化或最小化状态恢复为正常尺寸的关键事件。该事件通过传递窗口唯一标识符id来定位目标窗口实例并触发相应的UI布局重置逻辑。结合 `windowFormDataUpdate` 事件,可实现窗口位置与尺寸信息的同步更新,确保用户界面状态的一致性。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L22-L22)
## 核心设计与运行机制
`windowFormRestore` 事件定义于 `IWindowFormEvent` 接口中,其函数签名接受一个字符串类型的 `id` 参数,用以标识被操作的窗口实例。当用户点击“还原”按钮或通过快捷键执行还原操作时,系统将调用事件管理器 `wfem``notifyEvent` 方法,广播该事件。
事件的实际通知行为由通用事件构建器 `EventBuilderImpl` 实现,它维护了一个基于 `Map` 的监听器集合,支持动态添加、移除和触发事件回调。整个流程具备良好的解耦性和扩展性。
```mermaid
flowchart TD
A[用户触发窗口还原] --> B[调用 wfem.notifyEvent("windowFormRestore", id)]
B --> C{是否存在监听器?}
C --> |是| D[遍历执行所有注册的 handler]
C --> |否| E[无操作]
D --> F[各组件响应还原逻辑]
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L22-L22)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L7-L60)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L7-L95)
## 事件协同关系分析
`windowFormRestore` 通常与 `windowFormDataUpdate` 事件协同工作。前者仅通知“还原”动作的发生,而后者负责携带具体的窗口状态数据(包括坐标 x/y 和宽高 width/height实现精确的状态同步。
例如,在窗口还原过程中:
1. 首先触发 `windowFormRestore(id)`,通知所有监听者窗口即将还原;
2. 紧接着发送 `windowFormDataUpdate({ id, state: 'default', x, y, width, height })`,提供新的布局参数;
3. UI 组件接收到数据后,更新 DOM 元素的位置与尺寸。
这种分离设计提高了系统的灵活性和可维护性。
```mermaid
sequenceDiagram
participant User as 用户
participant WM as 窗口管理器
participant EM as 事件管理器(wfem)
participant UI as UI组件
User->>WM : 执行还原操作
WM->>EM : notifyEvent("windowFormRestore", id)
EM->>UI : 执行所有 windowFormRestore 回调
WM->>EM : notifyEvent("windowFormDataUpdate", {id, state, x, y, width, height})
EM->>UI : 执行所有 windowFormDataUpdate 回调
UI->>UI : 更新DOM样式与位置
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L37-L37)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L5-L9)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L37-L37)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L5-L9)
## Vue组件中的实践应用
在 Vue 组件中,可通过 `wfem.addEventListener` 监听 `windowFormRestore` 事件,并结合响应式数据重置 UI 布局。建议使用 CSS 过渡动画提升用户体验。
```mermaid
classDiagram
class WindowComponent {
+windowId : string
+position : WindowFormPos
+size : {width : number, height : number}
+mounted()
+beforeUnmount()
}
class EventManager {
+addEventListener(event, handler)
+removeEventListener(event, handler)
+notifyEvent(event, ...args)
}
WindowComponent --> EventManager : 使用 wfem
note right of WindowComponent
在 mounted 中注册事件监听,
在 beforeUnmount 中清理资源
end note
```
**Diagram sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L1-L5)
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L60-L60)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L1-L10)
## 在窗口状态机中的角色
`windowFormRestore` 是窗口状态机中从 `maximized``minimized` 转换至 `default` 状态的关键触发点。它标志着窗口生命周期中的一个重要转变节点,常伴随以下行为:
- 恢复原始位置与尺寸
- 重新激活窗口焦点
- 触发内容区域重渲染
- 更新任务栏图标状态
该事件与其他窗口控制事件(如 `windowFormMinimize`, `windowFormMaximize`)共同构成了完整的状态转换网络,支撑起复杂的桌面交互体验。
**Section sources**
- [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts#L7-L42)
- [WindowFormTypes.ts](file://src/ui/types/WindowFormTypes.ts#L5-L9)

View File

@@ -1,193 +0,0 @@
<cite>
**本文档中引用的文件**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [basic.css](file://src/css/basic.css)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心组件分析](#核心组件分析)
3. [响应式网格布局机制](#响应式网格布局机制)
4. [图标位置管理与持久化](#图标位置管理与持久化)
5. [视觉呈现与基础样式](#视觉呈现与基础样式)
6. [依赖关系图](#依赖关系图)
## 简介
本技术文档深入剖析了基于CSS Grid的动态桌面布局系统。该系统以`useDesktopContainerInit`这一核心Vue组合式函数Hook为基础实现了高度响应式的桌面容器功能。通过集成ResizeObserver API、Vue的响应式系统以及localStorage该系统能够根据容器尺寸动态调整网格布局并智能地重新排列桌面图标同时将用户自定义的布局状态持久化存储。整体架构结合了现代前端框架特性与原生Web API构建了一个灵活、可扩展且用户体验良好的虚拟桌面环境。
## 核心组件分析
系统的核心逻辑封装在`useDesktopContainerInit`函数中该函数作为Vue 3的组合式API被`DesktopContainer.vue`组件所调用。其主要职责是初始化并管理整个桌面容器的状态,包括网格模板参数、计算样式以及桌面图标数据。该函数返回一个包含`gridTemplate``appIconsRef``gridStyle`三个关键属性的对象,为上层组件提供完整的布局控制能力。
```mermaid
classDiagram
class useDesktopContainerInit {
+container : HTMLElement
+gridTemplate : IGridTemplateParams
+gridStyle : ComputedRef~Object~
+ro : ResizeObserver
+appIconsRef : Ref~Array<IDesktopAppIcon>~
+exceedApp : Ref~Array<IDesktopAppIcon>~
+useDesktopContainerInit(containerStr : string) : Object
}
class IGridTemplateParams {
+cellExpectWidth : number
+cellExpectHeight : number
+cellRealWidth : number
+cellRealHeight : number
+gapX : number
+gapY : number
+colCount : number
+rowCount : number
}
class IDesktopAppIcon {
+name : string
+icon : string
+path : string
+x : number
+y : number
}
useDesktopContainerInit --> IGridTemplateParams : "包含"
useDesktopContainerInit --> IDesktopAppIcon : "管理"
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 响应式网格布局机制
### 动态网格计算流程
系统的响应式能力源于对`ResizeObserver`的巧妙运用。当`DesktopContainer`组件挂载时,`useDesktopContainerInit`会创建一个`ResizeObserver`实例,并将其绑定到指定的容器元素(如`.desktop-icons-container`)。每当容器的尺寸发生变化,观察者回调就会被触发,执行一系列精确的计算来更新网格布局。
```mermaid
flowchart TD
A[容器尺寸变化] --> B{ResizeObserver 触发}
B --> C[获取容器实际宽高]
C --> D[计算列数 colCount]
D --> E[计算行数 rowCount]
E --> F[计算单元格实际宽度 cellRealWidth]
F --> G[计算单元格实际高度 cellRealHeight]
G --> H[更新 gridTemplate 响应式对象]
H --> I[computed 自动更新 gridStyle]
I --> J[DOM 中的 style 属性更新]
J --> K[浏览器重绘,应用新布局]
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L37-L58)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L37-L58)
### `gridStyle` 计算属性详解
`gridStyle`是一个由`computed`创建的计算属性它直接决定了桌面容器的CSS Grid样式。该属性依赖于`gridTemplate`中的`colCount``rowCount``cellExpectWidth``cellExpectHeight``gapX``gapY`等值。其核心作用是将这些数值动态地转换为标准的CSS Grid声明
- **`gridTemplateColumns`**: 使用`repeat()`函数生成指定数量的列轨道,每列的最小值为预设宽度(`cellExpectWidth`),最大值为`1fr`,确保了列的弹性伸缩。
- **`gridTemplateRows`**: 与列同理,生成指定数量的行轨道。
- **`gap`**: 设置行与列之间的间距。
这种设计使得布局的任何变化都能立即反映在UI上实现了真正的数据驱动视图。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L25-L35)
## 图标位置管理与持久化
### 图标初始定位与重排算法
系统通过`appIconsRef`这个`ref`对象来管理所有桌面图标的集合。在初始化时,函数会尝试从`localStorage`中读取之前保存的图标位置信息(键名为`desktopAppIconInfo`)。对于每个应用,它首先检查是否有历史记录,如果有则使用历史坐标;如果没有,则根据当前的网格行列数进行默认的蛇形排列。
当网格的行列数因容器大小改变而发生变化时,`watch`监听器会被激活,调用`rearrangeIcons`函数对图标进行智能重排。该算法的核心逻辑如下:
1. 遍历现有图标,优先保留那些仍在新网格范围内的图标。
2. 对于超出新网格范围或需要移动的图标,尝试在新的网格空间内寻找空闲的位置进行放置。
3. 如果没有足够的空间,则将无法显示的图标放入`exceedApp`数组中(可用于后续的“更多”菜单展示)。
```mermaid
sequenceDiagram
participant Container as DesktopContainer.vue
participant Hook as useDesktopContainerInit.ts
participant Observer as ResizeObserver
participant Storage as localStorage
Observer->>Hook : resize事件触发
Hook->>Hook : 计算新colCount, rowCount
Hook->>Hook : 更新gridTemplate响应式对象
Hook->>Hook : watch检测到变化
Hook->>Hook : 调用rearrangeIcons()
Hook->>Hook : 返回新的appIcons和hideAppIcons
Hook->>Hook : 更新appIconsRef.value
Hook->>Storage : 将appIconsRef.value序列化并存入localStorage
Hook-->>Container : 提供更新后的appIconsRef和gridStyle
Container->>Container : Vue自动更新DOM
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L78-L94)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L78-L94)
### 布局持久化实现
为了保证用户的个性化设置不丢失,系统利用`localStorage`实现了布局的持久化。通过另一个`watch`监听器,每当`appIconsRef`的值发生改变无论是因为重排还是用户拖拽都会立即将最新的图标数组序列化为JSON字符串并存储到`localStorage`中。当下次页面加载时,初始化代码会优先读取这段存储的数据,从而恢复用户上次的桌面布局。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L88-L92)
## 视觉呈现与基础样式
### AppIcon 组件的样式绑定
`AppIcon.vue`组件负责渲染单个桌面图标。它通过`style`属性直接绑定了`grid-column``grid-row`这两个CSS Grid属性其值来源于`iconInfo`对象的`x``y`坐标。例如,`grid-column: 2 / 3`表示该图标占据第2列。这种绑定方式使得图标的物理位置完全由其数据模型决定实现了布局的动态化。
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L2-L8)
### basic.css 的基础样式作用
`basic.css`文件提供了整个应用的基础样式规则,为动态布局奠定了视觉基调。它包含了:
- **盒模型重置**: 统一使用`border-box`,简化尺寸计算。
- **根元素变量**: 定义了字体、颜色、间距等CSS自定义属性便于全局主题管理。
- **基础元素样式**: 对`body``a``button`等元素进行了基础美化。
- **实用工具类**: 如`.container`用于创建居中的内容区域。
- **响应式支持**: 包含了针对减少动画偏好的媒体查询。
虽然`DesktopContainer``AppIcon`组件使用了`scoped`样式,但`basic.css`提供的全局基础样式确保了整个应用的一致性和可用性。
**Section sources**
- [basic.css](file://src/css/basic.css#L1-L134)
## 依赖关系图
```mermaid
graph TD
A[basic.css] --> |提供基础样式| B(DesktopContainer.vue)
C[useDesktopContainerInit.ts] --> |导出核心逻辑| B
B --> |使用| C
B --> |渲染| D(AppIcon.vue)
D --> |接收props| C
C --> |读写| E[localStorage]
C --> |监听| F[ResizeObserver]
F --> |响应| G[容器尺寸变化]
C --> |类型定义| H[IGridTemplateParams.ts]
C --> |类型定义| I[IDesktopAppIcon.ts]
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
- [basic.css](file://src/css/basic.css#L1-L134)

View File

@@ -1,124 +0,0 @@
# 图标重排与持久化
<cite>
**Referenced Files in This Document **
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
</cite>
## 目录
1. [appIconsRef的创建过程](#appiconsref的创建过程)
2. [localStorage数据同步机制](#localstorage数据同步机制)
3. [网格变化监听与重排响应](#网格变化监听与重排响应)
4. [rearrangeIcons算法详解](#rearrangeicons算法详解)
5. [布局状态持久化策略](#布局状态持久化策略)
## appIconsRef的创建过程
`appIconsRef` 是一个 Vue 响应式引用,用于管理桌面图标的布局状态。其创建过程始于 `useDesktopContainerInit` 函数的调用,该函数接收容器选择器字符串作为参数并初始化核心布局逻辑。
在初始化过程中,系统首先从 `localStorage` 中读取键为 `desktopAppIconInfo` 的存储项,尝试恢复之前保存的图标位置信息。若存在历史数据,则解析为 `oldAppIcons` 数组;否则使用空数组作为默认值。随后,系统遍历当前可用的应用程序列表(`appInfos`),为每个应用创建对应的桌面图标对象。
对于每个新生成的图标系统优先检查是否存在同名的历史图标记录。如果存在则继承其坐标x, y若不存在则根据当前网格的行列数按索引自动分配初始坐标
- 列坐标 x = 当前索引 % 行数 + 1
- 行坐标 y = floor(当前索引 / 行数) + 1
最终,这些图标数据被封装为响应式引用 `appIconsRef`,供视图层绑定使用。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L74-L94)
## localStorage数据同步机制
系统通过双向数据绑定机制实现 `appIconsRef``localStorage` 的实时同步。当用户对桌面图标进行拖拽、重排等操作导致布局变更时Vue 的响应式系统会触发相应的监听器,将最新状态持久化到本地存储中。
具体而言,系统注册了一个针对 `appIconsRef.value``watch` 监听器。每当图标数组内容发生变化(如新增、删除或位置调整),该监听器便会执行回调函数,将更新后的 `appIcons` 数组序列化为 JSON 字符串,并通过 `localStorage.setItem('desktopAppIconInfo', ...)` 方法写入浏览器本地存储。
此机制确保了用户在刷新页面或重新打开应用后,能够恢复上次关闭时的桌面布局,实现了跨会话的个性化配置记忆功能。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L89-L92)
## 网格变化监听与重排响应
为了适应不同屏幕尺寸和窗口大小的变化,系统利用 `ResizeObserver` API 实时监测桌面容器的尺寸变动,并动态计算最优的网格列数(`colCount`)和行数(`rowCount`)。当这些参数发生改变时,系统需要智能地重新排列所有图标以避免重叠或溢出。
为此,系统设置了一个复合监听器 `watch(() => [gridTemplate.colCount, gridTemplate.rowCount], ...)`, 专门监控 `colCount``rowCount` 的联合变化。一旦检测到新的网格维度,监听器立即调用 `rearrangeIcons` 函数,传入当前图标列表及新的行列限制,执行自动重排逻辑。
该监听器包含优化判断:若新旧行列数完全一致,则直接返回,避免不必要的重排计算,提升性能效率。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L86-L88)
## rearrangeIcons算法详解
`rearrangeIcons` 函数是整个图标管理系统的核心算法,负责处理图标冲突、寻找空闲位置以及管理超出可视范围的图标。其输入为原始图标数组和目标网格的最大行列数,输出为包含正常显示图标和隐藏图标的结构体。
### 冲突检测与占用标记
算法首先创建一个 `Set<string>` 类型的 `occupied` 集合,用于记录已被占用的网格单元。通过辅助函数 `key(x, y)` 将二维坐标转换为唯一字符串标识(如 `"1,2"`),实现高效的哈希查找。
### 分阶段处理流程
1. **第一阶段:保留有效位置**
- 遍历所有图标,筛选出位于当前网格范围内的图标(即 `x ≤ maxCol && y ≤ maxRow`
- 检查目标位置是否已被占用,若未占用则将其加入结果数组 `appIcons` 并标记为已占用
- 对于位置无效或冲突的图标,则暂存至临时数组 `temp`
2. **第二阶段:填补空位**
- 遍历 `temp` 数组中的待安置图标
- 若当前已放置图标数量小于网格总容量(`maxCol * maxRow`),则从左上角 `(1,1)` 开始逐行扫描,寻找第一个空闲位置进行安置
- 一旦找到合适位置,立即跳出内层循环,继续处理下一个图标
3. **第三阶段:处理溢出图标**
- 若网格已满且仍有剩余图标无法安置,则将其归类至 `hideAppIcons` 数组
- 这些图标将在 UI 层面被隐藏,防止界面混乱
```mermaid
flowchart TD
Start([开始重排]) --> ValidatePosition["验证图标位置有效性"]
ValidatePosition --> InRange{"是否在网格范围内?"}
InRange --> |是| CheckOccupied["检查位置是否被占用"]
InRange --> |否| ToTemp["加入临时数组 temp"]
CheckOccupied --> IsFree{"位置空闲?"}
IsFree --> |是| PlaceIcon["放置图标并标记占用"]
IsFree --> |否| ToTemp
PlaceIcon --> NextIcon["处理下一个图标"]
ToTemp --> NextIcon
NextIcon --> AllProcessed{"所有图标处理完毕?"}
AllProcessed --> |否| ValidatePosition
AllProcessed --> |是| FillEmpty["填补空位"]
FillEmpty --> HasSpace{"仍有空位?"}
HasSpace --> |是| FindSlot["从(1,1)开始寻找空位"]
HasSpace --> |否| HideExcess["隐藏超出图标"]
FindSlot --> CanPlace{"能否放置?"}
CanPlace --> |是| UpdateAppIcons["更新 appIcons 数组"]
CanPlace --> |否| HideExcess
UpdateAppIcons --> MoreTemp{"temp 数组为空?"}
MoreTemp --> |否| FillEmpty
MoreTemp --> |是| ReturnResult["返回结果: appIcons + hideAppIcons"]
HideExcess --> ReturnResult
```
**Diagram sources **
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
## 布局状态持久化策略
系统的布局持久化策略建立在 Vue 的响应式系统与浏览器本地存储的协同工作之上。`appIconsRef` 作为单一数据源Single Source of Truth集中管理所有图标的坐标信息。任何对图标的修改操作无论是用户交互还是程序逻辑都会反映到该引用上。
通过 `watch` 监听器,系统实现了从内存状态到持久化存储的单向同步。这种设计具有以下优势:
- **自动同步**:无需手动调用保存方法,所有变更自动记录
- **原子性保证**:每次写入都是完整的数组快照,避免部分更新导致的数据不一致
- **跨会话恢复**:页面刷新后可通过 `localStorage.getItem('desktopAppIconInfo')` 重建初始状态
- **容错处理**:使用 `JSON.parse(... || '[]')` 确保解析失败时返回安全默认值
该策略构成了完整的“读取→运行→修改→保存”闭环,保障了用户体验的一致性和数据的安全性。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L89-L92)

View File

@@ -1,221 +0,0 @@
# 布局初始化逻辑
<cite>
**Referenced Files in This Document**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
</cite>
## 目录
1. [简介](#简介)
2. [核心组件分析](#核心组件分析)
3. [生命周期与容器初始化](#生命周期与容器初始化)
4. [DOM查询机制与containerStr参数](#dom查询机制与containerstr参数)
5. [网格模板初始状态设计](#网格模板初始状态设计)
6. [ResizeObserver响应式尺寸监听](#resizeobserver响应式尺寸监听)
7. [图标重排逻辑](#图标重排逻辑)
8. [依赖关系图](#依赖关系图)
## 简介
`useDesktopContainerInit` 是一个 Vue 3 组合式 API Hook用于在桌面容器组件挂载时初始化其布局系统。该 Hook 负责建立基于 CSS Grid 的动态网格布局,并通过 `ResizeObserver` 实现响应式行为,确保桌面图标能根据容器尺寸自动调整排列方式。
## 核心组件分析
### useDesktopContainerInit 功能概览
此 Hook 封装了桌面容器的核心初始化逻辑,包括:
- 容器元素的 DOM 查询与绑定
- 网格布局参数的初始化与响应式管理
- 容器尺寸变化的监听与处理
- 桌面图标的持久化存储与位置管理
- 图标超出可视区域时的隐藏策略
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 生命周期与容器初始化
### onMounted 中的初始化流程
`useDesktopContainerInit` 利用 Vue 的 `onMounted` 生命周期钩子,在组件挂载完成后立即执行容器初始化操作。这一时机确保了对应的 DOM 元素已经存在于页面中,可以安全地进行查询和观察。
`onMounted` 回调中Hook 首先通过 `document.querySelector(containerStr)` 获取指定的容器元素引用,并将其赋值给局部变量 `container`。随后,立即调用 `ResizeObserver``observe` 方法开始监听该容器的尺寸变化。
当组件被卸载时,`onUnmounted` 钩子会确保清理工作正确执行:停止对容器的观察(`unobserve`)并断开 `ResizeObserver` 实例(`disconnect`),防止内存泄漏。
```mermaid
sequenceDiagram
participant Component as "DesktopContainer组件"
participant Hook as "useDesktopContainerInit"
participant DOM as "浏览器DOM"
participant Observer as "ResizeObserver"
Component->>Hook : setup()
Hook->>Hook : 初始化gridTemplate等响应式数据
Component->>Component : 渲染完成
Component->>Hook : onMounted触发
Hook->>DOM : querySelector(containerStr)
DOM-->>Hook : 返回容器元素
Hook->>Observer : new ResizeObserver(callback)
Hook->>Observer : observe(container)
Note over Hook,Observer : 开始监听容器尺寸变化
loop 每次窗口或容器大小改变
Observer->>Observer : 触发回调
Observer->>Hook : 执行回调函数
Hook->>Hook : 重新计算gridTemplate参数
Hook->>Hook : 更新gridStyle
end
Component->>Component : 组件即将销毁
Component->>Hook : onUnmounted触发
Hook->>Observer : unobserve(container)
Hook->>Observer : disconnect()
Note over Hook,Observer : 清理资源,防止内存泄漏
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L70-L85)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L70-L85)
## DOM查询机制与containerStr参数
### containerStr 参数的作用
`containerStr` 是传递给 `useDesktopContainerInit` 函数的一个字符串参数,它代表一个 CSS 选择器。该选择器用于定位需要初始化的桌面容器 DOM 元素。
在当前实现中,`DesktopContainer.vue` 组件传入的值为 `.desktop-icons-container`,这是一个类选择器,指向模板中具有该类名的 `<div>` 元素。这种设计使得 Hook 具有良好的通用性,可以在不同的容器上复用,只需传入相应的选择器即可。
```typescript
// 在 DesktopContainer.vue 中的调用示例
const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('.desktop-icons-container')
```
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L10-L13)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14)
## 网格模板初始状态设计
### gridTemplate 初始值的设计意图
`gridTemplate` 对象使用 Vue 的 `reactive` 函数创建,使其成为一个响应式对象。其初始状态的设计体现了以下几个关键考量:
| 属性 | 默认值 | 设计意图 |
|------|--------|----------|
| `cellExpectWidth` | 90 | 预设每个单元格的理想宽度(像素),作为网格列宽的基础 |
| `cellExpectHeight` | 110 | 预设每个单元格的理想高度(像素),作为网格行高的基础 |
| `gapX` / `gapY` | 4 | 设置行列之间的间隙,提供视觉呼吸空间,避免图标紧贴 |
| `colCount` / `rowCount` | 1 | 初始行列数设为1表示最小的网格结构将在首次ResizeObserver回调中被实际尺寸覆盖 |
这些默认值共同定义了一个合理的起始布局,即使在 `ResizeObserver` 回调执行前,也能保证界面有一个基本的、可预测的显示状态。
```mermaid
classDiagram
class IGridTemplateParams {
+readonly cellExpectWidth : number
+readonly cellExpectHeight : number
+cellRealWidth : number
+cellRealHeight : number
+gapX : number
+gapY : number
+colCount : number
+rowCount : number
}
class useDesktopContainerInit {
-container : HTMLElement
-gridTemplate : IGridTemplateParams
-gridStyle : ComputedRef
-ro : ResizeObserver
+useDesktopContainerInit(containerStr : string) : Object
}
useDesktopContainerInit --> IGridTemplateParams : "包含"
```
**Diagram sources**
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L4-L20)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L16-L32)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L16-L32)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L4-L20)
## ResizeObserver响应式尺寸监听
### 回调中的尺寸计算逻辑
`ResizeObserver` 的回调函数是实现响应式布局的核心。每当容器的尺寸发生变化时,该回调都会被触发,执行以下关键步骤:
1. **获取容器实际尺寸**:通过 `container.getBoundingClientRect()` 获取容器当前的精确几何信息。
2. **计算行列数量**:利用容器宽度和预设单元格宽度(含间隙)计算出应显示的列数和行数。
```javascript
colCount = Math.floor((width + gapX) / (expectWidth + gapX))
```
3. **计算实际单元格尺寸**:在确定了行列数后,重新分配容器内部空间,计算出每个单元格的实际宽高,确保网格完全填充容器且无多余空白。
```javascript
realWidth = (totalWidth - totalGapX) / colCount
```
这种“期望尺寸 -> 计算行列 -> 反推实际尺寸”的模式,既保证了图标的相对大小一致性,又实现了完美的空间利用率。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L45-L65)
## 图标重排逻辑
### rearrangeIcons 函数的工作机制
当网格的行列数发生变化时,`watch` 监听器会触发 `rearrangeIcons` 函数,负责重新安排所有桌面图标的可见性和位置。
该函数的主要逻辑如下:
1. 创建一个 `Set` 来记录已被占用的网格坐标 `(x,y)`。
2. 遍历所有图标,优先将位于新网格范围内的图标保留在原位。
3. 对于超出新网格范围的图标,尝试在网格内寻找空闲位置进行安置。
4. 如果网格已满,则将无法放置的图标放入 `hideAppIcons` 数组,这些图标将被隐藏。
这确保了用户界面的连续性,尽可能保留用户的原有布局习惯。
```mermaid
flowchart TD
A[开始重排图标] --> B{遍历所有图标}
B --> C["检查(x,y)是否在maxCol/maxRow范围内"]
C --> |是| D["检查该位置是否已被占用"]
D --> |否| E["将图标加入appIcons数组<br/>标记位置为已占用"]
D --> |是| F["跳过,不添加"]
C --> |否| G["将图标暂存到temp数组"]
G --> H{遍历temp数组}
H --> I["检查appIcons数组是否已满"]
I --> |否| J["在网格中寻找第一个空位"]
J --> K["将图标放入空位<br/>标记位置为已占用"]
K --> L["加入appIcons数组"]
I --> |是| M["加入hideAppIcons数组"]
M --> N[结束]
L --> N
E --> N
F --> N
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
## 依赖关系图
```mermaid
graph TB
subgraph "UI层"
DesktopContainerVue["DesktopContainer.vue"]
AppIconVue["AppIcon.vue"]
UseHook["useDesktopContainerInit.ts"]
end
subgraph "类型定义"
IGridTemplate["IGridTemplateParams.ts"]
IDesktopAppIcon["IDesktopAppIcon.ts"]
end
DesktopContainerVue --> UseHook : "调用"
UseHook --> IGridTemplate : "导入接口"
UseHook --> IDesktopAppIcon : "导入接口"
DesktopContainerVue --> AppIconVue : "使用组件"
UseHook --> AppIconVue : "通过返回值传递数据"
```
**Diagram sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)

View File

@@ -1,140 +0,0 @@
# 网格参数计算机制
<cite>
**本文档引用文件**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [basic.css](file://src/css/basic.css)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
</cite>
## 目录
1. [引言](#引言)
2. [核心数据结构定义](#核心数据结构定义)
3. [ResizeObserver中的动态网格计算逻辑](#resizeobserver中的动态网格计算逻辑)
4. [实际单元格尺寸的精确控制](#实际单元格尺寸的精确控制)
5. [gridStyle样式对象的生成与绑定](#gridstyle样式对象的生成与绑定)
6. [图标重排机制分析](#图标重排机制分析)
7. [总结](#总结)
## 引言
本项目通过Vue 3组合式API实现了一个响应式的桌面图标容器布局系统。其核心在于利用`ResizeObserver`监听容器尺寸变化动态计算并调整CSS Grid布局的行列数及单元格大小。该机制确保在不同屏幕尺寸和窗口缩放情况下桌面图标能够自适应排列同时保持良好的视觉一致性与交互体验。
## 核心数据结构定义
系统通过接口`IGridTemplateParams`定义了网格布局所需的核心参数集合:
```typescript
export interface IGridTemplateParams {
readonly cellExpectWidth: number; // 预期单元格宽度
readonly cellExpectHeight: number; // 预期单元格高度
cellRealWidth: number; // 实际单元格宽度
cellRealHeight: number; // 实际单元格高度
gapX: number; // 列间距
gapY: number; // 行间距
colCount: number; // 总列数
rowCount: number; // 总行数
}
```
这些参数构成了整个动态网格计算的基础,其中预期尺寸为设计基准值,而实际尺寸则由运行时环境动态决定。
**Section sources**
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
## ResizeObserver中的动态网格计算逻辑
当容器尺寸发生变化时,`ResizeObserver`回调函数会触发重新计算流程。关键步骤如下:
1. **获取容器几何信息**:通过`getBoundingClientRect()`获取当前容器的实际宽高。
2. **计算列数colCount**
```ts
gridTemplate.colCount = Math.floor((containerRect.width + gridTemplate.gapX) / (gridTemplate.cellExpectWidth + gridTemplate.gapX));
```
3. **计算行数rowCount**
```ts
gridTemplate.rowCount = Math.floor((containerRect.height + gridTemplate.gapY) / (gridTemplate.cellExpectHeight + gridTemplate.gapY));
```
此算法的本质是将容器总宽度(或高度)加上一个间隙值,再除以“单个单元格宽度+列间距”,从而避免因浮点误差导致最后一列无法完整显示的问题。使用`Math.floor`向下取整保证结果为有效整数。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L39-L40)
## 实际单元格尺寸的精确控制
在确定了行列数量后,系统进一步计算每个单元格的实际像素尺寸,以充分利用可用空间并消除边缘空白。
### 计算公式推导
- **可用总宽度** = 容器宽度 - 所有列间隙之和
即:`w = containerRect.width - gapX * (colCount - 1)`
- **每列实际宽度** = 可用总宽度 ÷ 列数
即:`cellRealWidth = w / colCount`
同理可得行方向上的计算:
- `h = containerRect.height - gapY * (rowCount - 1)`
- `cellRealHeight = h / rowCount`
### 浮点数精度控制
由于浏览器渲染对小数像素的支持有限,直接使用浮点值可能导致布局抖动或错位。因此系统采用`toFixed(2)`保留两位小数,并通过`Number()`转换回数值类型,既保证精度又提升渲染稳定性。
```ts
gridTemplate.cellRealWidth = Number((w / gridTemplate.colCount).toFixed(2))
gridTemplate.cellRealHeight = Number((h / gridTemplate.rowCount).toFixed(2))
```
这种处理方式平衡了空间利用率与视觉平滑性。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L41-L42)
## gridStyle样式对象的生成与绑定
`gridStyle`是一个基于Vue `computed`的响应式计算属性,负责将`gridTemplate`中的参数转化为标准的CSS Grid样式规则。
### 样式对象结构
```ts
const gridStyle = computed(() => ({
gridTemplateColumns: `repeat(${gridTemplate.colCount}, minmax(${gridTemplate.cellExpectWidth}px, 1fr))`,
gridTemplateRows: `repeat(${gridTemplate.rowCount}, minmax(${gridTemplate.cellExpectHeight}px, 1fr))`,
gap: `${gridTemplate.gapX}px ${gridTemplate.gapY}px`
}))
```
### 关键特性说明
- **`minmax()`函数应用**:确保每列最小宽度不低于`cellExpectWidth`,但允许在空间充足时扩展至等分的`1fr`比例。
- **动态重复语法**`repeat(colCount, ...)`自动构建指定数量的轨道定义。
- **双向间隙设置**`gap`属性分别设置横向与纵向间距。
该样式对象最终通过`:style="gridStyle"`绑定到`.desktop-icons-container`元素上,实现视图层的实时更新。
```mermaid
flowchart TD
A[容器尺寸变化] --> B{ResizeObserver触发}
B --> C[计算colCount/rrowCount]
C --> D[计算cellRealWidth/Height]
D --> E[更新gridTemplate响应式对象]
E --> F[gridStyle重新计算]
F --> G[DOM样式自动更新]
G --> H[完成布局重绘]
```
**Diagram sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L20-L30)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1)
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L20-L30)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1)
## 图标重排机制分析
每当行列数发生变更时,系统会调用`rearrangeIcons`函数对所有图标进行位置重分配,确保其不超出可视范围且无重叠。
### 重排策略
1. **优先保留原有坐标**:若图标的原位置仍在新网格范围内且未被占用,则保留原位。
2. **空位填充机制**:对于越界或冲突的图标,遍历网格寻找首个可用空位(从左上角开始)。
3. **溢出图标管理**:当网格已满时,超出部分存入`hideAppIcons`数组,可用于后续提示用户。
该机制保障了用户体验的一致性,避免图标因窗口缩放而丢失或错乱。
**Section sources**
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
## 总结
本系统的动态网格计算机制充分体现了响应式设计的思想。通过结合`ResizeObserver`、Vue响应式系统与CSS Grid布局实现了从容器尺寸→行列数量→单元格尺寸→样式绑定→图标定位的完整闭环。特别是在实际尺寸计算中对间隙总和的扣除与浮点精度的控制展现了对细节的高度关注。整体架构清晰、逻辑严谨具备良好的可维护性与扩展性。

View File

@@ -1,161 +0,0 @@
# 快速开始
<cite>
**本文档中引用的文件**
- [README.md](file://README.md)
- [package.json](file://package.json)
- [vite.config.ts](file://vite.config.ts)
- [main.ts](file://src/main.ts)
- [index.html](file://index.html)
- [tsconfig.app.json](file://tsconfig.app.json)
- [tsconfig.json](file://tsconfig.json)
- [tsconfig.node.json](file://tsconfig.node.json)
- [env.d.ts](file://env.d.ts)
</cite>
## 目录
1. [简介](#简介)
2. [开发环境准备](#开发环境准备)
3. [项目初始化与依赖安装](#项目初始化与依赖安装)
4. [启动开发服务器](#启动开发服务器)
5. [构建生产版本](#构建生产版本)
6. [package.json 脚本命令详解](#packagejson-脚本命令详解)
7. [常见问题排查](#常见问题排查)
8. [首次运行效果验证](#首次运行效果验证)
## 简介
`vue-desktop` 是一个基于 Vue 3 和 Vite 构建的桌面风格前端项目,模拟操作系统桌面界面,支持图标布局、窗口管理等功能。本指南旨在帮助开发者快速在本地搭建开发环境,完成依赖安装、服务启动和构建发布等核心操作,并提供新手友好的故障排查建议。
**Section sources**
- [README.md](file://README.md#L0-L37)
## 开发环境准备
在开始之前,请确保您的开发机器满足以下最低要求:
- **浏览器支持**Chrome 84+、Edge 84+、Firefox 79+、Safari 14+
- **Node.js 版本**:根据 `package.json` 中的 `engines` 字段,推荐使用 Node.js v20.19.0 或更高版本v22.12.0 及以上),不支持 IE 浏览器。
- **包管理工具**:项目使用 `pnpm` 进行依赖管理,需提前安装 pnpm。
推荐开发环境为 VSCode 配合 Volar 插件,以获得最佳的 `.vue` 文件类型支持和开发体验。
**Section sources**
- [package.json](file://package.json#L5-L7)
- [README.md](file://README.md#L0-L6)
## 项目初始化与依赖安装
进入项目根目录后,执行以下命令安装项目所需的所有依赖:
```bash
pnpm install
```
该命令将根据 `package.json``pnpm-lock.yaml` 安装所有生产与开发依赖,包括 Vue 3、Pinia、Naive UI、Vite、TypeScript 等核心库。
安装完成后,您可以在 `node_modules/` 目录下看到所有已安装的依赖包。
**Section sources**
- [README.md](file://README.md#L30-L31)
- [package.json](file://package.json#L10-L41)
## 启动开发服务器
安装完成后,可通过以下命令启动本地开发服务器:
```bash
pnpm dev
```
此命令实际执行的是 `vite` 命令,启动基于 Vite 的开发服务器具备热更新HMR功能修改代码后浏览器会自动刷新。
默认情况下,开发服务器将在 `http://localhost:5173` 启动。打开浏览器访问该地址即可查看应用界面。
**Section sources**
- [README.md](file://README.md#L33-L34)
- [package.json](file://package.json#L12)
- [vite.config.ts](file://vite.config.ts#L0-L29)
## 构建生产版本
当需要发布应用时,运行以下命令进行生产环境构建:
```bash
pnpm build
```
该命令会依次执行:
1. `type-check`:使用 `vue-tsc` 对项目进行类型检查
2. `build-only`:调用 `vite build` 编译并压缩代码,输出至 `dist/` 目录
构建完成后,`dist/` 文件夹将包含所有静态资源,可部署到任意静态服务器。
**Section sources**
- [README.md](file://README.md#L35-L37)
- [package.json](file://package.json#L13-L15)
## package.json 脚本命令详解
以下是 `package.json` 中定义的主要脚本及其作用:
| 脚本名称 | 命令内容 | 功能说明 |
|---------|--------|--------|
| `dev` | `vite` | 启动 Vite 开发服务器,支持热重载 |
| `build` | `run-p type-check "build-only {@}" --` | 并行执行类型检查和构建任务 |
| `preview` | `vite preview` | 在本地预览 `dist/` 目录中的生产构建结果 |
| `build-only` | `vite build` | 仅执行构建,不进行类型检查 |
| `type-check` | `vue-tsc --build` | 使用 `vue-tsc``.vue` 文件进行类型检查 |
| `format` | `prettier --write src/` | 使用 Prettier 格式化 `src/` 目录下的代码 |
这些脚本通过 `npm-run-all2` 实现并发执行(如 `run-p`),提升构建效率。
**Section sources**
- [package.json](file://package.json#L11-L20)
## 常见问题排查
### Node.js 版本不兼容
如果运行 `pnpm install` 时报错提示 Node.js 版本不符合要求,请检查当前版本:
```bash
node -v
```
确保版本符合 `^20.19.0 || >=22.12.0` 范围。建议使用 [nvm](https://github.com/nvm-sh/nvm) 管理多个 Node.js 版本。
### 依赖安装失败
`pnpm install` 失败,尝试以下步骤:
1. 清除缓存:`pnpm store prune`
2. 检查网络,必要时配置镜像源
3. 删除 `node_modules``pnpm-lock.yaml` 后重新安装
### 类型检查错误
运行 `pnpm type-check` 时可能出现类型错误,通常是由于 `.vue` 文件的 TypeScript 类型未被正确识别。确保已安装 Volar 插件并禁用 Vetur。
### 构建失败
构建失败可能由语法错误或路径别名问题引起。检查 `vite.config.ts` 中的 `@` 别名配置是否正确指向 `src/` 目录。
**Section sources**
- [package.json](file://package.json#L5-L7)
- [vite.config.ts](file://vite.config.ts#L18-L22)
- [tsconfig.app.json](file://tsconfig.app.json#L0-L24)
- [env.d.ts](file://env.d.ts#L0-L1)
## 首次运行效果验证
成功运行 `pnpm dev` 并在浏览器中打开 `http://localhost:5173`您应看到一个类桌面风格的界面包含网格布局的应用图标容器。页面标题为“vue-desktop”DOM 结构中 `<div id="app">` 已被 Vue 应用挂载。
主组件 `App.vue` 通过 `main.ts` 初始化,使用了 Pinia 状态管理和 Naive UI 组件库。界面响应式适配窗口大小变化,图标按网格自动排列。
若能看到图标容器且无控制台报错,则表示环境搭建成功。
**Section sources**
- [index.html](file://index.html#L0-L13)
- [main.ts](file://src/main.ts#L0-L15)
- [src/ui/desktop-container/DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L0-L22)
- [src/ui/desktop-container/useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L0-L37)

View File

@@ -1,149 +0,0 @@
# 技术栈与依赖
<cite>
**Referenced Files in This Document **
- [package.json](file://package.json)
- [vite.config.ts](file://vite.config.ts)
- [tsconfig.json](file://tsconfig.json)
- [uno.config.ts](file://uno.config.ts)
- [src/main.ts](file://src/main.ts)
- [src/common/naive-ui/components.ts](file://src/common/naive-ui/components.ts)
- [src/stores/counter.ts](file://src/stores/counter.ts)
</cite>
## 目录
1. [核心框架Vue 3](#核心框架vue-3)
2. [状态管理Pinia](#状态管理pinia)
3. [UI 组件库Naive UI](#ui-组件库naive-ui)
4. [原子化样式UnoCSS](#原子化样式unocss)
5. [构建工具Vite](#构建工具vite)
6. [类型安全TypeScript](#类型安全typescript)
7. [关键依赖项版本与选择理由](#关键依赖项版本与选择理由)
## 核心框架Vue 3
本项目采用 Vue 3 作为核心前端框架,利用其组合式 APIComposition API实现更灵活、可复用的逻辑组织。在 `main.ts` 文件中通过 `createApp` 初始化应用实例,并挂载根组件 `App.vue`,构成整个应用的入口。
Vue 3 提供了响应式系统、虚拟 DOM 渲染机制以及强大的组件化能力,使得桌面级 Web 应用的开发更加高效和模块化。结合 Vite 的现代构建流程,实现了极快的冷启动和热更新体验。
**Section sources**
- [src/main.ts](file://src/main.ts#L1-L15)
## 状态管理Pinia
Pinia 作为 Vue 官方推荐的状态管理库,在本项目中用于集中管理全局状态。通过 `defineStore` 创建具有命名空间的 store 实例,如 `useCounterStore`,支持使用 `ref``computed` 构建响应式状态,并导出操作方法以供组件调用。
`main.ts` 中通过 `app.use(createPinia())` 注册 Pinia 插件,使所有组件均可通过 `useXXXStore()` 获取共享状态,避免了传统 Vuex 的冗余配置,提升了开发效率与类型推断准确性。
```mermaid
classDiagram
class useCounterStore {
+count : Ref<number>
+doubleCount : ComputedRef<number>
+increment() : void
}
useCounterStore --> ref : "uses"
useCounterStore --> computed : "uses"
useCounterStore --> defineStore : "created via"
```
**Diagram sources**
- [src/stores/counter.ts](file://src/stores/counter.ts#L3-L11)
**Section sources**
- [src/stores/counter.ts](file://src/stores/counter.ts#L1-L13)
- [src/main.ts](file://src/main.ts#L8-L9)
## UI 组件库Naive UI
项目集成 Naive UI 作为 UI 组件解决方案,提供高质量、可定制的 Vue 3 组件集。为优化打包体积并实现按需引入,项目在 `src/common/naive-ui/components.ts` 中手动创建了一个轻量化的 UI 实例,仅注册所需组件(如 `NButton`, `NCard`, `NConfigProvider`),并通过 `create()` 方法生成可被 `app.use()` 注册的插件对象。
此外,项目还通过 `discrete-api.ts` 导出独立使用的 API如 message、modal并在 `theme.ts` 中扩展默认主题,自定义主色调为 `#0070f3`,确保视觉风格统一。
```mermaid
classDiagram
class naiveUi {
+components : Array<Component>
}
naiveUi --> NButton : "includes"
naiveUi --> NCard : "includes"
naiveUi --> NConfigProvider : "includes"
naiveUi --> create : "created via"
```
**Diagram sources**
- [src/common/naive-ui/components.ts](file://src/common/naive-ui/components.ts#L1-L11)
- [src/common/naive-ui/theme.ts](file://src/common/naive-ui/theme.ts#L2-L14)
**Section sources**
- [src/common/naive-ui/components.ts](file://src/common/naive-ui/components.ts#L1-L11)
## 原子化样式UnoCSS
UnoCSS 是一个即时on-the-fly原子化 CSS 引擎,取代传统的 Tailwind CSS 实现方式。它允许开发者直接在模板中使用类名(如 `flex`, `p-4`, `text-lg`),并在构建时动态生成最小化 CSS极大提升样式编写效率并减少冗余代码。
项目通过 `unocss/vite` 插件集成 UnoCSS并在 `uno.config.ts` 中启用 `transformerVariantGroup``transformerDirectives`,支持嵌套语法(如 `hover:(bg-red-500 text-white)`)和 `@apply` 指令,增强可读性与灵活性。同时,`virtual:uno.css` 被引入 `main.ts`,确保样式正确注入。
**Section sources**
- [uno.config.ts](file://uno.config.ts#L1-L10)
- [vite.config.ts](file://vite.config.ts#L15-L16)
- [src/main.ts](file://src/main.ts#L6)
## 构建工具Vite
Vite 作为现代前端构建工具,基于 ES 模块原生支持,显著提升了开发服务器的启动速度与热更新性能。项目通过 `vite.config.ts` 配置核心插件:
- `@vitejs/plugin-vue`:支持 `.vue` 单文件组件解析
- `@vitejs/plugin-vue-jsx`:支持 JSX/TSX 语法
- `vite-plugin-vue-devtools`:集成 Vue DevTools 调试工具
- `unocss/vite`:集成 UnoCSS 样式引擎
配置中还设置了路径别名 `@` 指向 `src` 目录便于模块导入。Vite 的开箱即用特性与 TypeScript、JSX、Sass 等技术无缝集成,构成了高效的开发环境基础。
```mermaid
flowchart TD
A["用户请求 /"] --> B[Vite Dev Server]
B --> C{资源类型}
C --> |HTML| D[index.html]
C --> |.vue 文件| E[Vite 解析 SFC]
E --> F[编译 template/script/style]
F --> G[返回 ES Module]
C --> |TypeScript| H[Vite 实时转译]
H --> I[浏览器执行]
C --> |UnoCSS 类名| J[UnoCSS 动态生成 CSS]
J --> K[注入 style 标签]
I & K --> L[页面渲染完成]
```
**Diagram sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
**Section sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
## 类型安全TypeScript
TypeScript 在项目中承担关键的角色,提供静态类型检查以预防运行时错误。项目采用分层配置策略,`tsconfig.json` 通过 `references` 引入 `tsconfig.node.json``tsconfig.app.json`,分别处理 Node.js 环境与应用代码的类型定义。
`.vue` 文件的类型支持通过 `vue-tsc` 实现,替代标准 `tsc` 进行类型检查,确保模板中的 props、emits 等具备完整类型推断。配合 VSCode 与 Volar 插件,开发者可在编辑器中获得精准的智能提示与错误检测。
**Section sources**
- [tsconfig.json](file://tsconfig.json#L1-L12)
- [README.md](file://README.md#L12-L15)
## 关键依赖项版本与选择理由
| 依赖包 | 版本范围 | 选择理由 |
|-------|--------|---------|
| vue | ^3.5.18 | 使用最新稳定版 Vue 3支持 Composition API 与 Fragment |
| pinia | ^3.0.3 | 官方状态管理库轻量、类型友好、API 简洁 |
| naive-ui | ^2.42.0 | 功能丰富、文档完善、支持 Tree-shaking 的 Vue 3 组件库 |
| unocss | ^66.4.2 | 即时原子化 CSS性能优于传统方案支持高度定制 |
| vite | ^7.0.6 | 极速 HMR、原生 ES Modules 支持、生态成熟 |
| typescript | ~5.8.0 | 精确控制 TS 版本,避免意外升级导致兼容问题 |
| vue-tsc | ^3.0.4 | 专为 Vue + TypeScript 设计的类型检查工具,支持 `<script setup>` 和模板类型校验 |
这些依赖共同构建了一个现代化、高性能、类型安全的前端开发体系,为项目的长期维护与扩展提供了坚实基础。
**Section sources**
- [package.json](file://package.json#L1-L43)

View File

@@ -1,182 +0,0 @@
# 构建与部署
<cite>
**Referenced Files in This Document**
- [vite.config.ts](file://vite.config.ts)
- [uno.config.ts](file://uno.config.ts)
- [package.json](file://package.json)
- [README.md](file://README.md)
</cite>
## 目录
1. [构建流程概述](#构建流程概述)
2. [开发构建与生产构建的区别](#开发构建与生产构建的区别)
3. [Vite 配置解析](#vite-配置解析)
4. [UnoCSS 自定义配置](#unocss-自定义配置)
5. [构建脚本执行机制](#构建脚本执行机制)
6. [静态资源输出结构](#静态资源输出结构)
7. [部署最佳实践](#部署最佳实践)
## 构建流程概述
本项目基于 Vite 构建工具实现现代化前端工程化流程,结合 Vue 3 和 UnoCSS 原子化 CSS 框架。整体构建流程围绕 `vite` 命令展开,通过 `package.json` 中定义的脚本入口驱动不同环境下的构建行为。
构建系统支持开发模式热重载、类型检查、代码格式化及生产级优化打包。核心配置文件包括 `vite.config.ts`(构建主配置)、`uno.config.ts`(样式引擎配置)以及 `package.json`(脚本命令定义),三者协同完成从源码到可部署静态资源的转换过程。
**Section sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
- [package.json](file://package.json#L1-L43)
- [README.md](file://README.md#L1-L38)
## 开发构建与生产构建的区别
开发构建与生产构建在目标、性能优化和输出内容上存在显著差异:
| 特性 | 开发构建 | 生产构建 |
|------|----------|----------|
| **启动命令** | `pnpm dev` | `pnpm build` |
| **主要目的** | 快速启动、热更新 | 生成优化后的静态资源 |
| **代码压缩** | 否 | 是Terser 或 SWC |
| **Source Map** | 完整映射 | 可选或隐藏 |
| **环境变量** | `.env.development` | `.env.production` |
| **依赖预构建** | 动态处理 | 静态分析并缓存 |
| **HMR 支持** | 是 | 否 |
开发构建侧重于提升开发者体验启用热模块替换HMR以实现实时反馈而生产构建则专注于性能优化对 JavaScript、CSS 进行压缩与 Tree Shaking移除调试代码并生成适合 CDN 分发的静态文件。
**Section sources**
- [package.json](file://package.json#L6-L14)
- [README.md](file://README.md#L30-L37)
## Vite 配置解析
`vite.config.ts` 是项目的构建核心配置文件,定义了插件集成、路径别名、编译选项等关键设置。
### 插件集成
配置中注册了多个 Vite 插件:
- `@vitejs/plugin-vue`:支持 Vue 单文件组件解析,配置了自定义元素忽略规则(`isCustomElement: tag => tag.endsWith('-element')`),避免将特定标签误解析为 Vue 组件。
- `@vitejs/plugin-vue-jsx`:启用 JSX/TSX 语法支持。
- `vite-plugin-vue-devtools`:集成 Vue DevTools 调试工具。
- `unocss/vite`:引入 UnoCSS 样式引擎,实现原子化 CSS 按需生成。
### 路径别名
通过 `resolve.alias` 配置 `@` 指向 `src` 目录,简化模块导入路径,提升代码可读性与维护性。
### 环境变量处理
Vite 原生支持 `.env` 文件加载,根据 `import.meta.env.MODE` 区分不同环境,并自动注入 `import.meta.env.VITE_*` 前缀的环境变量。
```mermaid
flowchart TD
A["启动 vite"] --> B{开发模式?}
B --> |是| C["加载 vite.config.ts"]
C --> D["应用插件: vue, vueJsx, devtools, unocss"]
D --> E["设置路径别名 @ → src/"]
E --> F["启动开发服务器 + HMR"]
B --> |否| G["执行 vite build"]
G --> H["编译 TS + 处理 SFC"]
H --> I["应用 UnoCSS 转换"]
I --> J["压缩 JS/CSS"]
J --> K["输出 dist/ 目录"]
```
**Diagram sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
**Section sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
## UnoCSS 自定义配置
`uno.config.ts` 文件用于定制 UnoCSS 的行为,当前配置启用了两个重要转换器:
- `transformerVariantGroup()`:允许使用括号语法进行变体分组,例如 `hover:(bg-red-500 text-white)`,提升类名书写效率。
- `transformerDirectives()`:支持在 CSS 中使用 `@apply` 指令复用原子类,以及 `@screen` 响应式控制。
该配置确保了在模板中可以灵活使用组合式类名,同时保持样式的可维护性和简洁性。
```mermaid
classDiagram
class UnoCSSConfig {
+transformers : Transformer[]
}
class TransformerVariantGroup {
+name : "variant-group"
+transform()
}
class TransformerDirectives {
+name : "directives"
+transform()
}
UnoCSSConfig --> TransformerVariantGroup : "包含"
UnoCSSConfig --> TransformerDirectives : "包含"
```
**Diagram sources**
- [uno.config.ts](file://uno.config.ts#L1-L10)
**Section sources**
- [uno.config.ts](file://uno.config.ts#L1-L10)
## 构建脚本执行机制
`package.json` 中的 `scripts` 字段定义了标准化的构建命令:
- `dev`:直接运行 `vite`,启动开发服务器。
- `build`:并行执行类型检查与构建任务,使用 `run-p` 实现并发控制。
- `build-only`:仅执行 `vite build`,生成生产资源。
- `type-check`:调用 `vue-tsc --build` 进行完整的 TypeScript 类型校验。
- `preview`:启动本地预览服务器,模拟生产环境。
这种设计实现了构建流程的解耦与并行化,提升了 CI/CD 流水线效率。
**Section sources**
- [package.json](file://package.json#L6-L14)
## 静态资源输出结构
生产构建完成后Vite 将输出默认的 `dist/` 目录,其典型结构如下:
```
dist/
├── assets/ # 打包后的 JS、CSS 资源
│ ├── index.[hash].js
│ └── style.[hash].css
├── index.html # 入口 HTML 文件
└── favicon.ico # 可选图标
```
所有资源均经过哈希命名以支持长期缓存HTML 文件自动注入正确的资源引用路径。
**Section sources**
- [vite.config.ts](file://vite.config.ts#L1-L30)
## 部署最佳实践
### 静态服务器部署
`dist/` 目录内容部署至 Nginx、Apache 或 Caddy 等静态服务器时,需确保:
- 正确设置 MIME 类型。
- 启用 Gzip/Brotli 压缩。
- 配置 SPA 路由回退(将所有非资源请求重定向至 `index.html`)。
示例 Nginx 配置片段:
```nginx
location / {
try_files $uri $uri/ /index.html;
}
```
### CDN 部署建议
- 利用 CDN 的边缘缓存能力,设置合理的缓存策略(如 `max-age=31536000` 对带哈希的静态资源)。
- 启用 HTTPS 和 HTTP/2。
- 使用版本化目录(如 `/v1.0.0/`)便于灰度发布与回滚。
### 自动化部署
推荐结合 GitHub Actions、GitLab CI 或 Jenkins 实现自动化构建与部署流水线,包含以下阶段:
1. 依赖安装
2. 类型检查
3. 生产构建
4. 构建产物验证
5. 自动上传至对象存储或 CDN
**Section sources**
- [README.md](file://README.md#L30-L37)

View File

@@ -1,209 +0,0 @@
# 状态管理
<cite>
**Referenced Files in This Document **
- [counter.ts](file://src/stores/counter.ts)
- [main.ts](file://src/main.ts)
- [App.vue](file://src/ui/App.vue)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构分析](#项目结构分析)
3. [Pinia状态管理模式应用](#pinia状态管理模式应用)
4. [Counter Store详解](#counter-store详解)
5. [组件中使用Store](#组件中使用store)
6. [全局状态同步与扩展用途](#全局状态同步与扩展用途)
7. [状态持久化策略](#状态持久化策略)
8. [结论](#结论)
## 简介
本项目采用Pinia作为Vue 3的官方推荐状态管理库实现跨组件的状态共享和管理。文档将详细说明基于Pinia的状态管理模式在桌面系统中的应用以counter store为例展示store的定义、state暴露和actions使用方法并探讨其在整个系统中的潜在扩展用途及状态持久化策略。
## 项目结构分析
项目遵循典型的Vue + TypeScript架构状态管理相关代码集中存放在`src/stores`目录下。核心入口文件为`src/main.ts`负责初始化Pinia实例并将其挂载到Vue应用上。UI组件分布在`src/ui`目录中,通过模块化方式组织桌面容器和应用图标等界面元素。
```mermaid
graph TB
subgraph "核心模块"
Stores["src/stores"]
UI["src/ui"]
Main["src/main.ts"]
end
Main --> Stores : "导入并使用"
Main --> UI : "作为根组件"
UI --> Stores : "调用store"
Stores --> counter["counter.ts"]
UI --> DesktopContainer["DesktopContainer.vue"]
DesktopContainer --> AppIcon["AppIcon.vue"]
```
**Diagram sources**
- [main.ts](file://src/main.ts#L1-L16)
- [stores/counter.ts](file://src/stores/counter.ts#L1-L13)
- [ui/App.vue](file://src/ui/App.vue#L1-L53)
## Pinia状态管理模式应用
Pinia在本项目中被用作全局状态管理中心通过`createPinia()`创建实例并在主应用中注册使得所有组件都能访问统一的状态树。这种模式解决了传统Vue应用中深层嵌套组件间通信困难的问题实现了状态的集中管理和响应式更新。
### 初始化过程
Pinia实例在`main.ts`中完成初始化和注册:
```typescript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
```
此配置确保了Pinia插件在整个应用生命周期内可用为后续的store创建和使用奠定了基础。
**Section sources**
- [main.ts](file://src/main.ts#L1-L16)
## Counter Store详解
`counter.ts`文件定义了一个典型的Pinia store示例——useCounterStore展示了组合式API风格的store定义方式。
### Store定义
`useCounterStore`通过`defineStore`函数创建采用箭头函数语法封装内部逻辑。该store包含以下核心要素
- **State**: 使用`ref`定义的响应式数据`count`
- **Getters**: 通过`computed`创建的派生属性`doubleCount`
- **Actions**: 可修改state的方法`increment`
```typescript
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
```
这种定义方式充分利用了Vue 3的Composition API特性使代码更加直观和易于理解。
**Section sources**
- [counter.ts](file://src/stores/counter.ts#L3-L11)
### State暴露机制
Store通过返回对象的方式暴露其内部状态和方法。这种方式实现了良好的封装性同时保持了使用的便捷性。外部组件可以通过调用`useCounterStore()`获取store实例进而访问`count``doubleCount``increment`等属性和方法。
## 组件中使用Store
虽然当前代码库中未直接展示counter store在组件中的使用但可以推断出标准的使用模式。
### 引入与使用
在任何Vue组件中可通过导入`useCounterStore`并在`setup`函数中调用它来使用store
```vue
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 使用 counter.count, counter.doubleCount, counter.increment()
</script>
```
### 潜在应用场景
尽管counter store目前仅作为示例存在但它可被扩展用于多种场景
- 桌面图标的数量统计
- 应用启动次数追踪
- 用户交互行为计数
```mermaid
sequenceDiagram
participant Component as "Vue组件"
participant Store as "Pinia Store"
participant State as "响应式状态"
Component->>Store : 调用useCounterStore()
Store-->>Component : 返回store实例
Component->>Store : 访问count值
Store-->>Component : 提供响应式count
Component->>Store : 调用increment()
Store->>State : 修改count.value
State-->>Store : 触发更新
Store-->>Component : 自动更新视图
```
**Diagram sources**
- [counter.ts](file://src/stores/counter.ts#L3-L11)
- [App.vue](file://src/ui/App.vue#L1-L53)
## 全局状态同步与扩展用途
Pinia提供的全局状态管理能力在桌面系统中有广泛的扩展潜力。
### 多组件状态共享
设想多个桌面组件需要共享某些状态如主题设置、布局参数、用户偏好等可通过创建相应的store实现
```typescript
// 示例theme.store.ts
export const useThemeStore = defineStore('theme', () => {
const darkMode = ref(false)
const fontSize = ref(14)
function toggleDarkMode() {
darkMode.value = !darkMode.value
}
return { darkMode, fontSize, toggleDarkMode }
})
```
此类store可被任务栏、桌面容器、应用窗口等多个组件同时引用确保状态的一致性。
### 系统级状态管理
更复杂的系统状态如窗口管理、应用状态、事件总线也可通过Pinia进行统一管理形成清晰的状态流架构。
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L24)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L53)
## 状态持久化策略
当前项目尚未实现状态持久化功能,但从技术角度看有多种可行方案。
### 可行的持久化方案
1. **浏览器存储**: 利用localStorage或sessionStorage保存关键状态
2. **IndexedDB**: 对于复杂数据结构可使用IndexedDB进行持久化
3. **第三方插件**: 使用`pinia-plugin-persistedstate`等成熟解决方案
### 实现建议
若需实现counter store的持久化可在定义store时添加持久化配置
```typescript
// 配置示例(当前未实现)
export const useCounterStore = defineStore('counter', () => {
// ...原有逻辑
}, {
persist: true // 启用持久化
})
```
值得注意的是,项目中存在`useObservableVue.ts`这一自定义Hook表明团队可能倾向于使用自定义的状态管理方案配合Pinia使用这为未来实现更复杂的状态持久化和同步机制提供了灵活性。
**Section sources**
- [useObservableVue.ts](file://src/common/hooks/useObservableVue.ts#L1-L43)
- [counter.ts](file://src/stores/counter.ts#L1-L13)
## 结论
本项目通过Pinia实现了现代化的Vue状态管理`counter.ts`中的useCounterStore展示了清晰的组合式API用法。虽然当前store的应用较为基础但其架构设计为未来的功能扩展留下了充足空间。通过合理利用Pinia的模块化特性可逐步构建完整的全局状态管理体系支撑起复杂桌面应用的需求。同时结合浏览器原生存储机制或专用插件可进一步完善状态持久化能力提升用户体验。

View File

@@ -1,275 +0,0 @@
# 项目概述
<cite>
**Referenced Files in This Document **
- [README.md](file://README.md)
- [package.json](file://package.json)
- [main.ts](file://src/main.ts)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
- [counter.ts](file://src/stores/counter.ts)
- [EventManager.ts](file://src/events/EventManager.ts)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构概览](#架构概览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考量](#性能考量)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)
## 简介
`vue-desktop` 是一个基于 Vue 3 的桌面风格 Web 应用旨在模拟操作系统桌面环境的用户体验。该项目通过现代化的前端技术栈实现了可拖拽的应用图标、响应式网格布局和高效的状态管理等关键特性。其设计目标是为用户提供直观、交互性强的Web界面同时为开发者提供清晰的代码结构和可扩展的架构。
本项目采用 MVVMModel-View-ViewModel模式结合 Vue 3 的 Composition API实现了逻辑与视图的分离提升了代码的可维护性和复用性。对于初学者而言该项目提供了理解现代Vue应用开发的良好范例对于高级开发者则展示了复杂状态管理和事件系统的设计思路。
**Section sources**
- [README.md](file://README.md#L1-L37)
- [package.json](file://package.json#L1-L42)
## 项目结构
`vue-desktop` 项目的目录结构体现了功能模块化的设计理念,各目录职责分明:
- `src/common`: 存放通用工具函数、自定义Hook和类型定义
- `src/css`: 全局样式文件
- `src/events`: 事件管理系统,实现组件间解耦通信
- `src/stores`: 状态管理模块,使用 Pinia 进行全局状态管理
- `src/ui`: 用户界面组件包含桌面容器、应用图标等核心UI元素
- `src/main.ts`: 应用入口文件负责初始化Vue实例和插件
这种分层结构使得项目易于理解和维护,新开发者可以快速定位到特定功能的实现位置。
```mermaid
graph TB
subgraph "源码目录"
Common[src/common<br/>通用工具与类型]
CSS[src/css<br/>全局样式]
Events[src/events<br/>事件系统]
Stores[src/stores<br/>状态管理]
UI[src/ui<br/>用户界面]
Main[src/main.ts<br/>应用入口]
end
Main --> UI
Main --> Stores
Main --> Events
Main --> CSS
Main --> Common
UI --> Events
UI --> Stores
UI --> Common
```
**Diagram sources **
- [main.ts](file://src/main.ts#L1-L15)
- [project_structure](file://#L1-L20)
**Section sources**
- [project_structure](file://#L1-L20)
## 核心组件
项目的核心功能由几个关键组件协同实现:`DesktopContainer` 作为桌面主容器管理整体布局,`AppIcon` 组件代表可交互的应用图标,`useDesktopContainerInit` Hook 负责初始化桌面参数和响应式逻辑,而 `counter` Store 则演示了基础的状态管理机制。
这些组件共同构成了桌面环境的基础,其中 `DesktopContainer``AppIcon` 通过 Composition API 实现了高度的灵活性和可复用性。
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
- [counter.ts](file://src/stores/counter.ts#L3-L11)
## 架构概览
`vue-desktop` 采用了典型的现代前端架构模式,以 Vue 3 的 Composition API 为核心,结合 Pinia 进行状态管理,并通过自定义事件系统实现组件间的松耦合通信。
整个应用从 `main.ts` 启动,创建 Vue 实例并注册 Pinia 和 Naive UI 等插件。UI 层主要由 `DesktopContainer``AppIcon` 组成,前者使用 `useDesktopContainerInit` Hook 初始化响应式数据,后者则实现了图标的拖拽功能。状态管理通过 Pinia Store 实现,事件通信则依赖于自定义的事件总线机制。
```mermaid
graph TD
A[main.ts] --> B[Vue App]
B --> C[Pinia Store]
B --> D[Naive UI]
B --> E[DesktopContainer]
E --> F[AppIcon]
E --> G[useDesktopContainerInit]
C --> H[counter]
E --> I[EventManager]
G --> J[ResizeObserver]
F --> K[Drag Events]
style A fill:#f9f,stroke:#333
style B fill:#bbf,stroke:#333
style C fill:#f96,stroke:#333
style E fill:#6f9,stroke:#333
```
**Diagram sources **
- [main.ts](file://src/main.ts#L1-L15)
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 详细组件分析
### 桌面容器分析
`DesktopContainer` 组件是整个应用的核心容器,负责管理桌面的网格布局和应用图标的渲染。它通过 Composition API 的 `setup` 语法引入 `useDesktopContainerInit` Hook 来获取响应式数据,并使用 Vue 的 `v-for` 指令遍历渲染 `AppIcon` 组件。
该组件的关键特性包括:
- 响应式网格布局,能根据容器大小自动调整行列数
- 双击事件处理,用于启动应用程序
- 与子组件的 props 通信机制
#### 组件关系图
```mermaid
classDiagram
class DesktopContainer {
+appIconsRef : Ref~Array~
+gridStyle : ComputedRef
+gridTemplate : Reactive
+runApp(appIcon) : void
}
class AppIcon {
+iconInfo : Prop
+gridTemplate : Prop
+onDragStart(e) : void
+onDragEnd(e) : void
}
class useDesktopContainerInit {
+useDesktopContainerInit(containerStr) : Object
+rearrangeIcons() : Object
}
DesktopContainer --> AppIcon : "v-for 渲染"
DesktopContainer --> useDesktopContainerInit : "组合式API调用"
useDesktopContainerInit ..> IGridTemplateParams : "实现"
useDesktopContainerInit ..> IDesktopAppIcon : "使用"
```
**Diagram sources **
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L1-L20)
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
**Section sources**
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
### 应用图标分析
`AppIcon` 组件实现了桌面图标的可视化和交互功能,支持拖拽操作来重新排列图标位置。当用户拖拽图标并释放时,组件会计算鼠标位置对应的网格坐标,并更新图标的 `x``y` 属性。
该组件利用 HTML5 的 Drag and Drop API 实现拖拽功能,并通过 `document.elementFromPoint` 方法检测鼠标释放时的位置元素,确保图标只能放置在有效的桌面区域内。
#### 拖拽流程图
```mermaid
flowchart TD
Start([开始拖拽]) --> DragStart["onDragStart事件触发"]
DragStart --> Wait["等待拖拽结束"]
Wait --> DragEnd["onDragEnd事件触发"]
DragEnd --> CheckTarget["检查鼠标下方元素"]
CheckTarget --> Valid{"是否为有效区域?"}
Valid --> |否| End([放弃移动])
Valid --> |是| Calculate["计算网格坐标"]
Calculate --> Update["更新图标位置(x,y)"]
Update --> Persist["持久化到localStorage"]
Persist --> End
style Start fill:#f9f,stroke:#333
style End fill:#f9f,stroke:#333
```
**Diagram sources **
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
### 状态与事件系统分析
项目中的状态管理采用 Pinia 实现,`counter.ts` 文件展示了最基本的 Store 定义方式,包含响应式状态、计算属性和修改方法。事件系统则通过 `EventManager``EventBuilderImpl` 类构建了一个类型安全的事件总线,支持添加、移除和触发事件。
这种设计模式实现了组件间的解耦,使得不同部分的代码可以通过事件进行通信,而不需要直接引用彼此。
#### 事件系统类图
```mermaid
classDiagram
class EventManager {
+eventManager : EventBuilderImpl
}
class EventBuilderImpl {
-_eventHandlers : Map
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
class IEventBuilder {
<<interface>>
+addEventListener()
+removeEventListener()
+notifyEvent()
}
EventManager --> EventBuilderImpl : "实例化"
EventBuilderImpl ..|> IEventBuilder : "实现"
```
**Diagram sources **
- [EventManager.ts](file://src/events/EventManager.ts#L4-L4)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L1-L96)
**Section sources**
- [EventManager.ts](file://src/events/EventManager.ts#L4-L4)
- [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts#L1-L96)
## 依赖分析
`vue-desktop` 项目的依赖体系清晰地分为生产依赖和开发依赖两大类。生产依赖主要包括 Vue 3 核心库、Pinia 状态管理、Lodash 工具函数和 UUID 生成器等。开发依赖则包含了 Vite 构建工具、TypeScript 支持、Prettier 代码格式化等开发辅助工具。
值得注意的是,项目使用了 UnoCSS 进行原子化CSS管理以及 Naive UI 作为UI组件库这些选择体现了对性能和开发效率的重视。
```mermaid
graph LR
A[Vue 3] --> B[vue-desktop]
C[Pinia] --> B
D[Lodash] --> B
E[UUID] --> B
F[Naive UI] --> B
G[Vite] --> B
H[TypeScript] --> B
I[UnoCSS] --> B
style B fill:#6f9,stroke:#333
```
**Diagram sources **
- [package.json](file://package.json#L1-L42)
**Section sources**
- [package.json](file://package.json#L1-L42)
## 性能考量
`vue-desktop` 在性能方面采取了多项优化措施:
1. **响应式布局优化**:通过 `ResizeObserver` 监听容器尺寸变化,避免了频繁的重排重绘。
2. **内存管理**:在组件卸载时正确清理事件监听器和观察者,防止内存泄漏。
3. **本地存储**:使用 `localStorage` 持久化图标位置信息,减少重复计算。
4. **计算属性缓存**:利用 Vue 的 `computed` 特性缓存网格样式计算结果。
这些优化确保了即使在大量图标的情况下,应用仍能保持流畅的用户体验。
## 故障排除指南
当遇到常见问题时,可以参考以下解决方案:
- **图标无法拖拽**:检查浏览器是否支持 HTML5 Drag and Drop API确认元素的 `draggable` 属性已正确设置。
- **布局错乱**:验证容器宽度是否被正确设置,检查 CSS 样式是否有冲突。
- **状态不更新**确保使用了正确的响应式APIref/reactive检查 Pinia Store 的使用方式。
- **事件未触发**:确认事件监听器已正确注册,检查事件名称拼写是否一致。
**Section sources**
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
## 结论
`vue-desktop` 项目成功实现了一个功能完整的桌面风格Web应用展示了 Vue 3 生态系统的强大能力。通过 MVVM 架构和 Composition API 的结合,项目实现了良好的代码组织和可维护性。响应式网格布局和拖拽功能为用户提供了直观的交互体验,而 Pinia 和自定义事件系统则确保了复杂状态的有效管理。
该项目不仅适合作为学习现代前端开发的范例,也为构建类似的操作系统模拟器或仪表盘应用提供了有价值的参考。随着需求的发展,可以在此基础上扩展更多功能,如窗口管理、任务栏、系统托盘等,进一步完善桌面环境的模拟。

File diff suppressed because one or more lines are too long

167
PRETTIER_CONFIG_GUIDE.md Normal file
View File

@@ -0,0 +1,167 @@
# Prettier 常用配置项详解
## 基础格式化选项
### semi (boolean)
- **默认值**: true
- **说明**: 在语句末尾打印分号
- **示例**:
- true: `console.log('hello');`
- false: `console.log('hello')`
### singleQuote (boolean)
- **默认值**: false
- **说明**: 使用单引号而不是双引号
- **示例**:
- true: `const str = 'hello';`
- false: `const str = "hello";`
### printWidth (number)
- **默认值**: 80
- **说明**: 指定每行代码的最大字符数,超过这个长度会自动换行
### useTabs (boolean)
- **默认值**: false
- **说明**: 使用制表符(tab)缩进而不是空格
### tabWidth (number)
- **默认值**: 2
- **说明**: 指定每个缩进级别的空格数
## 对象和数组格式化
### bracketSpacing (boolean)
- **默认值**: true
- **说明**: 在对象字面量中的括号之间打印空格
- **示例**:
- true: `{ foo: bar }`
- false: `{foo: bar}`
### bracketSameLine (boolean)
- **默认值**: false
- **说明**: 将多行 HTML 元素的 > 放在最后一行的末尾,而不是单独放在下一行
### trailingComma (string)
- **默认值**: "es5"
- **可选值**: "none" | "es5" | "all"
- **说明**: 在多行对象或数组的最后一个元素后是否添加逗号
- **示例**:
```javascript
// "none"
{
foo: 'bar'
}
// "es5"
{
foo: 'bar',
}
// "all"
{
foo: 'bar',
baz: 'qux',
}
```
## JSX 特定选项
### jsxSingleQuote (boolean)
- **默认值**: false
- **说明**: 在 JSX 中使用单引号而不是双引号
### singleAttributePerLine (boolean)
- **默认值**: false
- **说明**: 强制每个 HTML、Vue 和 JSX 属性独占一行
- **示例**:
```html
<!-- singleAttributePerLine: false -->
<button class="btn" id="submit" type="submit">Submit</button>
<!-- singleAttributePerLine: true -->
<button class="btn" id="submit" type="submit">Submit</button>
```
## 函数和箭头函数
### arrowParens (string)
- **默认值**: "always"
- **可选值**: "always" | "avoid"
- **说明**: 在箭头函数参数周围始终包含括号
- **示例**:
```javascript
// "always"
const fn = (x) => x
// "avoid"
const fn = (x) => x
const fn2 = (x, y) => x + y
```
## 其他格式化选项
### endOfLine (string)
- **默认值**: "lf"
- **可选值**: "auto" | "lf" | "crlf" | "cr"
- **说明**: 指定换行符风格
- "lf": \n (Linux/macOS)
- "crlf": \r\n (Windows)
### quoteProps (string)
- **默认值**: "as-needed"
- **可选值**: "as-needed" | "consistent" | "preserve"
- **说明**: 对象属性引用方式
- "as-needed": 仅在需要时才引用属性
- "consistent": 如果至少有一个属性需要引用,则引用所有属性
- "preserve": 保持原样
### htmlWhitespaceSensitivity (string)
- **默认值**: "css"
- **可选值**: "css" | "strict" | "ignore"
- **说明**: 指定 HTML、Vue、Angular 文件中全局空白敏感度
- "css": 尊重 CSS display 属性的默认值
- "strict": 所有空白都被认为是重要的
- "ignore": 所有空白都被认为是不重要的
### vueIndentScriptAndStyle (boolean)
- **默认值**: false
- **说明**: Vue 文件中单文件组件的 `<script>` 和 `<style>` 标签内的代码是否缩进
### experimentalTernaries (boolean)
- **默认值**: false
- **说明**: 尝试 Prettier 的新三元表达式格式化方式,在成为默认行为之前使用
### experimentalOperatorPosition (string)
- **默认值**: "end"
- **可选值**: "start" | "end"
- **说明**: 当二元表达式换行时,操作符的位置
- "start": 操作符放在新行的开头
- "end": 操作符放在前一行的末尾(默认行为)
### objectWrap (string)
- **默认值**: "preserve"
- **可选值**: "preserve" | "collapse"
- **说明**: 配置 Prettier 如何包装对象字面量
- "preserve": 如果在开括号和第一个属性之间有换行符,则保持多行格式
- "collapse": 如果可能,将对象压缩到单行

View File

@@ -1,185 +1,220 @@
# Vue Desktop - 系统与业务解耦架构 # Vue Desktop
基于设计文档实现的高性能类Windows桌面前端系统实现了系统框架与业务应用的完全解耦。 一个基于 Vue 3 + TypeScript + Vite 的现代化桌面环境模拟器
## 🎯 项目概述 ## 🎯 项目目标
本项目成功实现了设计文档中描述的**系统与业务解耦架构**构建一个完整的桌面环境模拟系统,包含 构建一个轻量级、模块化的桌面环境,支持
- **完全隔离**:系统与应用在运行时完全隔离,应用无法直接访问系统资源 - 内置应用(计算器、记事本、待办事项等)
- **标准化接口**通过统一的SDK提供标准化的系统服务接口 - 外置应用加载(通过 iframe 沙箱)
- **性能优先**:采用微前端沙箱、虚拟化渲染等技术确保高性能 - 窗口管理(创建、移动、缩放、最小化等)
- **框架无关**:支持任意前端框架开发的第三方应用 - 资源管理(存储、权限控制)
- **安全可控**:严格的权限控制和安全沙箱机制 - 应用生命周期管理
- 安全沙箱机制
## 🏗️ 架构实现 ## 🏗️ 架构实现
### 核心服务层 ### 核心服务层
- **[WindowService](./src/services/WindowService.ts)** - 窗体管理服务,支持完整的窗体生命周期
- **[WindowFormService](src/services/windowForm/WindowFormService.ts)** - 窗体管理服务,支持完整的窗体生命周期
- **[ResourceService](./src/services/ResourceService.ts)** - 资源管理服务,提供权限控制和资源访问 - **[ResourceService](./src/services/ResourceService.ts)** - 资源管理服务,提供权限控制和资源访问
- **[EventCommunicationService](./src/services/EventCommunicationService.ts)** - 事件通信服务,支持跨应用消息传递
- **[ApplicationSandboxEngine](./src/services/ApplicationSandboxEngine.ts)** - 应用沙箱引擎,多层安全隔离 - **[ApplicationSandboxEngine](./src/services/ApplicationSandboxEngine.ts)** - 应用沙箱引擎,多层安全隔离
- **[ApplicationLifecycleManager](./src/services/ApplicationLifecycleManager.ts)** - 应用生命周期管理 - **[ApplicationLifecycleManager](./src/services/ApplicationLifecycleManager.ts)** - 应用生命周期管理
- **[SystemServiceIntegration](./src/services/SystemServiceIntegration.ts)** - 系统服务集成层 - **[SystemServiceIntegration](./src/services/SystemServiceIntegration.ts)** - 系统服务集成层
### SDK接口层 ### SDK接口层
- **[SDK类型定义](./src/sdk/types.ts)** - 完整的TypeScript接口定义
- **[SDK实现](./src/sdk/index.ts)** - 统一的SDK实现提供标准化API
### 应用层 - **[SystemSDK](./src/sdk/index.ts)** - 统一SDK接口为应用提供系统能力访问
- **内置应用**:计算器、记事本、系统状态监控
- **第三方应用**[待办事项应用](./public/apps/todo/index.html)示例
## 🚀 快速开始 ### 事件系统
### 环境要求 - **[IEventBuilder](./src/events/IEventBuilder.ts)** - 事件总线接口
- Node.js >= 20.19.0 - **[EventBuilderImpl](./src/events/impl/EventBuilderImpl.ts)** - 事件总线实现
- pnpm
### 安装与运行 ### 应用管理
```bash
# 安装依赖
pnpm install
# 启动开发服务器 - **[AppRegistry](./src/apps/AppRegistry.ts)** - 应用注册中心
pnpm dev - **[ExternalAppDiscovery](./src/services/ExternalAppDiscovery.ts)** - 外置应用发现服务
# 构建生产版本
pnpm build
```
### 访问应用
- 开发环境http://localhost:5174/
- 生产环境:部署后的域名
## 🎮 使用说明
### 桌面交互
1. **双击图标**启动应用
2. **拖拽图标**重新排列桌面布局
3. **右键菜单**访问系统功能(计划中)
### 内置应用
- **🧮 计算器**:基础的数学计算功能
- **📝 记事本**:文本编辑和文件保存
- **⚙️ 系统状态**:实时系统性能监控
- **✓ 待办事项**:完整的任务管理应用
### 窗体操作
- **最小化/最大化/还原**:标准窗体控制
- **拖拽移动**:通过标题栏拖拽窗体
- **调整大小**:支持窗体尺寸调整(计划中)
## 🔧 技术特性
### 系统架构特性
- **微前端架构**:每个应用运行在独立的沙箱中
- **事件驱动**:基于发布订阅模式的事件通信
- **权限控制**:细粒度的资源访问权限管理
- **性能监控**:实时的系统和应用性能监控
- **错误隔离**:应用错误不会影响系统稳定性
### 开发特性
- **TypeScript**:完整的类型安全保障
- **Vue 3**:现代化的响应式框架
- **Vite**:快速的构建工具
- **UnoCSS**原子化CSS框架
- **Naive UI**优雅的UI组件库
## 📁 项目结构 ## 📁 项目结构
``` ```
vue-desktop/ .
├── src/ ├── public\apps
│ ├── services/ # 核心服务层 │ ├── music-player
│ │ ├── WindowService.ts │ │ ├── README.md
│ │ ├── ResourceService.ts │ │ ├── app.js
│ │ ├── EventCommunicationService.ts │ │ ├── index.html
│ │ ├── ApplicationSandboxEngine.ts │ │ ├── manifest.json
│ │ └── style.css
│ └── README.md
├── src
│ ├── apps
│ │ ├── calculator
│ │ │ └── Calculator.vue
│ │ ├── components
│ │ │ └── BuiltInApp.vue
│ │ ├── notepad
│ │ │ └── Notepad.vue
│ │ ├── todo
│ │ │ └── Todo.vue
│ │ ├── types
│ │ │ └── AppManifest.ts
│ │ ├── AppRegistry.ts
│ │ └── index.ts
│ ├── common
│ │ ├── hooks
│ │ │ ├── useClickFocus.ts
│ │ │ └── useObservableVue.ts
│ │ ├── naive-ui
│ │ │ ├── components.ts
│ │ │ ├── discrete-api.ts
│ │ │ └── theme.ts
│ │ └── types
│ │ ├── IDestroyable.ts
│ │ └── IVersion.ts
│ ├── css
│ │ └── basic.css
│ ├── events
│ │ ├── impl
│ │ │ └── EventBuilderImpl.ts
│ │ └── IEventBuilder.ts
│ ├── sdk
│ │ ├── index.ts
│ │ └── types.ts
│ ├── services
│ │ ├── ApplicationLifecycleManager.ts │ │ ├── ApplicationLifecycleManager.ts
│ │ ── SystemServiceIntegration.ts │ │ ── ApplicationSandboxEngine.ts
├── sdk/ # SDK接口层 │ ├── ExternalAppDiscovery.ts
│ │ ├── types.ts # 类型定义 │ │ ├── ResourceService.ts
│ │ ── index.ts # SDK实现 │ │ ── SystemServiceIntegration.ts
├── ui/ # 用户界面 │ └── WindowFormService.ts
│ ├── desktop-container/ │ ├── stores
│ │ └── counter.ts
│ ├── ui
│ │ ├── components
│ │ │ ├── AppRenderer.vue
│ │ │ └── WindowManager.vue
│ │ ├── desktop-container
│ │ │ ├── AppIcon.vue
│ │ │ ├── DesktopContainer.vue
│ │ │ ├── useDesktopContainerInit.ts
│ │ │ └── useDynamicAppIcons.ts
│ │ ├── types
│ │ │ ├── IDesktopAppIcon.ts
│ │ │ ├── IGridTemplateParams.ts
│ │ │ └── WindowFormTypes.ts
│ │ └── App.vue │ │ └── App.vue
── events/ # 事件系统 ── main.ts
│ ├── stores/ # 状态管理 ├── PRETTIER_CONFIG_GUIDE.md
│ └── main.ts # 应用入口 ├── PROJECT_SUMMARY.md
├── public/ ├── README.md
│ └── apps/ # 第三方应用 ├── env.d.ts
│ └── todo/ # 待办事项应用 ├── index.html
── README.md ── package.json
├── pnpm-lock.yaml
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── uno.config.ts
└── vite.config.ts
``` ```
## 🔒 安全机制 ## 🚀 快速开始
### 沙箱隔离 ### 环境要求
- **iframe隔离**每个应用运行在独立的iframe中
- **CSP策略**:严格的内容安全策略
- **权限控制**:基于白名单的权限管理
- **API代理**所有系统调用通过SDK代理
### 权限模型 - Node.js >= 16.0.0
- **存储权限**:应用独立的存储空间 - pnpm >= 7.0.0
- **网络权限**:基于域名白名单的网络访问
- **通知权限**:用户确认的通知功能
- **剪贴板权限**:受控的剪贴板访问
## 📊 性能优化 ### 安装依赖
### 渲染优化 ```bash
- **虚拟化布局**:高效的图标网格布局 pnpm install
- **按需加载**:应用按需动态加载 ```
- **内存管理**:智能的内存回收机制
- **性能监控**:实时的性能指标收集
### 系统优化 ### 开发模式
- **事件节流**:防止事件风暴
- **资源缓存**:智能的资源缓存策略
- **自动清理**:定期的系统资源清理
- **错误恢复**:优雅的错误处理和恢复
## 🔮 未来规划 ```bash
pnpm dev
```
### 短期计划 ### 构建生产版本
- [ ] 窗体拖拽和调整大小功能
- [ ] 右键菜单和快捷方式
- [ ] 更多内置应用(文件管理器、浏览器等)
- [ ] 应用商店和在线安装
### 长期计划 ```bash
- [ ] 多桌面和工作区 pnpm build
- [ ] 插件系统和扩展机制 ```
- [ ] 云同步和备份
- [ ] 移动端适配
## 🤝 贡献指南 ### 预览生产构建
欢迎贡献代码、报告bug或提出功能建议 ```bash
pnpm preview
```
### 开发流程 ## 🛠️ 开发指南
1. Fork项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 创建Pull Request
### 代码规范 ### 添加内置应用
- 使用TypeScript进行类型安全
- 遵循Vue 3组合式API最佳实践
- 使用ESLint和Prettier保持代码风格一致
- 为新功能编写测试用例
## 📄 许可证 1.`src/apps/` 目录下创建应用文件夹
2. 创建 Vue 组件文件(如 `MyApp.vue`
3.`src/apps/AppRegistry.ts` 中注册应用
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。 ### 添加外置应用
## 🙏 致谢 1.`public/apps/` 目录下创建应用文件夹
2. 添加 `manifest.json` 应用清单文件
3. 添加应用的 HTML/CSS/JS 文件
4. 系统会自动发现并加载该应用
感谢所有参与项目开发和测试的贡献者,以及提供技术支持的开源社区。 ### 系统服务使用
--- 通过依赖注入获取系统服务:
**Vue Desktop** - 让Web应用拥有桌面应用的体验 🚀 ```typescript
import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration'
const systemService = inject<SystemServiceIntegration>('systemService')
```
可用服务:
- `getWindowFormService()` - 窗体服务
- `getResourceService()` - 资源服务
- `getSandboxEngine()` - 沙箱引擎
- `getLifecycleManager()` - 生命周期管理器
## 📖 技术文档
### 窗体系统
窗体系统支持完整的生命周期管理,包括创建、移动、缩放、最小化、最大化等操作。
### 资源管理
资源服务提供安全的存储访问和权限控制机制。
### 沙箱安全
应用沙箱引擎提供多层安全隔离,防止恶意代码访问系统资源。
### 应用生命周期
应用生命周期管理器负责应用的安装、启动、停止、卸载等操作。
## 🧪 测试
### 单元测试
```bash
pnpm test
```
### 端到端测试
```bash
pnpm test:e2e
```
## 📦 部署
构建产物可直接部署到任何静态文件服务器上。

View File

@@ -35,7 +35,7 @@
"sass": "^1.90.0", "sass": "^1.90.0",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"unocss": "^66.4.2", "unocss": "^66.4.2",
"vite": "^7.0.6", "vite": "^7.2.4",
"vite-plugin-vue-devtools": "^8.0.0", "vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4" "vue-tsc": "^3.0.4"
} }

104
pnpm-lock.yaml generated
View File

@@ -38,10 +38,10 @@ importers:
version: 22.17.1 version: 22.17.1
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)) version: 6.0.1(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))
'@vitejs/plugin-vue-jsx': '@vitejs/plugin-vue-jsx':
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)) version: 5.0.1(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))
'@vue/tsconfig': '@vue/tsconfig':
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3)) version: 0.7.0(typescript@5.8.3)(vue@3.5.18(typescript@5.8.3))
@@ -62,13 +62,13 @@ importers:
version: 5.8.3 version: 5.8.3
unocss: unocss:
specifier: ^66.4.2 specifier: ^66.4.2
version: 66.4.2(postcss@8.5.6)(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) version: 66.4.2(postcss@8.5.6)(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
vite: vite:
specifier: ^7.0.6 specifier: ^7.2.4
version: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) version: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vite-plugin-vue-devtools: vite-plugin-vue-devtools:
specifier: ^8.0.0 specifier: ^8.0.0
version: 8.0.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)) version: 8.0.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))
vue-tsc: vue-tsc:
specifier: ^3.0.4 specifier: ^3.0.4
version: 3.0.5(typescript@5.8.3) version: 3.0.5(typescript@5.8.3)
@@ -1054,6 +1054,15 @@ packages:
picomatch: picomatch:
optional: true optional: true
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
figures@6.1.0: figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -1435,6 +1444,10 @@ packages:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -1526,8 +1539,8 @@ packages:
peerDependencies: peerDependencies:
vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
vite@7.1.1: vite@7.2.4:
resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -2102,13 +2115,13 @@ snapshots:
'@types/web-bluetooth@0.0.21': {} '@types/web-bluetooth@0.0.21': {}
'@unocss/astro@66.4.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))': '@unocss/astro@66.4.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))':
dependencies: dependencies:
'@unocss/core': 66.4.2 '@unocss/core': 66.4.2
'@unocss/reset': 66.4.2 '@unocss/reset': 66.4.2
'@unocss/vite': 66.4.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) '@unocss/vite': 66.4.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
optionalDependencies: optionalDependencies:
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
'@unocss/cli@66.4.2': '@unocss/cli@66.4.2':
dependencies: dependencies:
@@ -2239,7 +2252,7 @@ snapshots:
dependencies: dependencies:
'@unocss/core': 66.4.2 '@unocss/core': 66.4.2
'@unocss/vite@66.4.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))': '@unocss/vite@66.4.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))':
dependencies: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
'@unocss/config': 66.4.2 '@unocss/config': 66.4.2
@@ -2250,23 +2263,23 @@ snapshots:
pathe: 2.0.3 pathe: 2.0.3
tinyglobby: 0.2.14 tinyglobby: 0.2.14
unplugin-utils: 0.2.5 unplugin-utils: 0.2.5
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
'@vitejs/plugin-vue-jsx@5.0.1(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))': '@vitejs/plugin-vue-jsx@5.0.1(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))':
dependencies: dependencies:
'@babel/core': 7.28.0 '@babel/core': 7.28.0
'@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0) '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
'@rolldown/pluginutils': 1.0.0-beta.31 '@rolldown/pluginutils': 1.0.0-beta.31
'@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.0) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.0)
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vitejs/plugin-vue@6.0.1(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))': '@vitejs/plugin-vue@6.0.1(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))':
dependencies: dependencies:
'@rolldown/pluginutils': 1.0.0-beta.29 '@rolldown/pluginutils': 1.0.0-beta.29
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
'@volar/language-core@2.4.22': '@volar/language-core@2.4.22':
@@ -2349,14 +2362,14 @@ snapshots:
dependencies: dependencies:
'@vue/devtools-kit': 7.7.7 '@vue/devtools-kit': 7.7.7
'@vue/devtools-core@8.0.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))': '@vue/devtools-core@8.0.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))':
dependencies: dependencies:
'@vue/devtools-kit': 8.0.0 '@vue/devtools-kit': 8.0.0
'@vue/devtools-shared': 8.0.0 '@vue/devtools-shared': 8.0.0
mitt: 3.0.1 mitt: 3.0.1
nanoid: 5.1.5 nanoid: 5.1.5
pathe: 2.0.3 pathe: 2.0.3
vite-hot-client: 2.1.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) vite-hot-client: 2.1.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- vite - vite
@@ -2624,6 +2637,10 @@ snapshots:
optionalDependencies: optionalDependencies:
picomatch: 4.0.3 picomatch: 4.0.3
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
figures@6.1.0: figures@6.1.0:
dependencies: dependencies:
is-unicode-supported: 2.1.0 is-unicode-supported: 2.1.0
@@ -2977,6 +2994,11 @@ snapshots:
fdir: 6.4.6(picomatch@4.0.3) fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
@@ -3000,9 +3022,9 @@ snapshots:
unicorn-magic@0.3.0: {} unicorn-magic@0.3.0: {}
unocss@66.4.2(postcss@8.5.6)(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)): unocss@66.4.2(postcss@8.5.6)(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)):
dependencies: dependencies:
'@unocss/astro': 66.4.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) '@unocss/astro': 66.4.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
'@unocss/cli': 66.4.2 '@unocss/cli': 66.4.2
'@unocss/core': 66.4.2 '@unocss/core': 66.4.2
'@unocss/postcss': 66.4.2(postcss@8.5.6) '@unocss/postcss': 66.4.2(postcss@8.5.6)
@@ -3020,9 +3042,9 @@ snapshots:
'@unocss/transformer-compile-class': 66.4.2 '@unocss/transformer-compile-class': 66.4.2
'@unocss/transformer-directives': 66.4.2 '@unocss/transformer-directives': 66.4.2
'@unocss/transformer-variant-group': 66.4.2 '@unocss/transformer-variant-group': 66.4.2
'@unocss/vite': 66.4.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) '@unocss/vite': 66.4.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
optionalDependencies: optionalDependencies:
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
transitivePeerDependencies: transitivePeerDependencies:
- postcss - postcss
- supports-color - supports-color
@@ -3045,17 +3067,17 @@ snapshots:
evtd: 0.2.4 evtd: 0.2.4
vue: 3.5.18(typescript@5.8.3) vue: 3.5.18(typescript@5.8.3)
vite-dev-rpc@1.1.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)): vite-dev-rpc@1.1.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)):
dependencies: dependencies:
birpc: 2.5.0 birpc: 2.5.0
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vite-hot-client: 2.1.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) vite-hot-client: 2.1.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
vite-hot-client@2.1.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)): vite-hot-client@2.1.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)):
dependencies: dependencies:
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vite-plugin-inspect@11.3.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)): vite-plugin-inspect@11.3.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)):
dependencies: dependencies:
ansis: 4.1.0 ansis: 4.1.0
debug: 4.4.1 debug: 4.4.1
@@ -3065,27 +3087,27 @@ snapshots:
perfect-debounce: 1.0.0 perfect-debounce: 1.0.0
sirv: 3.0.1 sirv: 3.0.1
unplugin-utils: 0.2.5 unplugin-utils: 0.2.5
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vite-dev-rpc: 1.1.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) vite-dev-rpc: 1.1.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
vite-plugin-vue-devtools@8.0.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)): vite-plugin-vue-devtools@8.0.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)):
dependencies: dependencies:
'@vue/devtools-core': 8.0.0(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3)) '@vue/devtools-core': 8.0.0(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))(vue@3.5.18(typescript@5.8.3))
'@vue/devtools-kit': 8.0.0 '@vue/devtools-kit': 8.0.0
'@vue/devtools-shared': 8.0.0 '@vue/devtools-shared': 8.0.0
execa: 9.6.0 execa: 9.6.0
sirv: 3.0.1 sirv: 3.0.1
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
vite-plugin-inspect: 11.3.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) vite-plugin-inspect: 11.3.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
vite-plugin-vue-inspector: 5.3.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)) vite-plugin-vue-inspector: 5.3.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0))
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
- supports-color - supports-color
- vue - vue
vite-plugin-vue-inspector@5.3.2(vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)): vite-plugin-vue-inspector@5.3.2(vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)):
dependencies: dependencies:
'@babel/core': 7.28.0 '@babel/core': 7.28.0
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.0) '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.0)
@@ -3096,18 +3118,18 @@ snapshots:
'@vue/compiler-dom': 3.5.18 '@vue/compiler-dom': 3.5.18
kolorist: 1.8.0 kolorist: 1.8.0
magic-string: 0.30.17 magic-string: 0.30.17
vite: 7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0) vite: 7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
vite@7.1.1(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0): vite@7.2.4(@types/node@22.17.1)(jiti@2.5.1)(sass@1.90.0):
dependencies: dependencies:
esbuild: 0.25.8 esbuild: 0.25.8
fdir: 6.4.6(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
postcss: 8.5.6 postcss: 8.5.6
rollup: 4.46.2 rollup: 4.46.2
tinyglobby: 0.2.14 tinyglobby: 0.2.15
optionalDependencies: optionalDependencies:
'@types/node': 22.17.1 '@types/node': 22.17.1
fsevents: 2.3.3 fsevents: 2.3.3

View File

@@ -1,23 +1,13 @@
import type { AppRegistration } from './types/AppManifest' import type { AppRegistration } from './types/AppManifest'
import { reactive, markRaw } from 'vue' import { reactive } from 'vue'
/** /**
* 应用注册中心 * 应用注册中心
* 管理所有内置应用的注册和获取 * 管理所有内置应用的注册和获取
*/ */
export class AppRegistry { export class AppRegistry {
private static instance: AppRegistry | null = null
private apps = reactive(new Map<string, AppRegistration>()) private apps = reactive(new Map<string, AppRegistration>())
private constructor() {}
static getInstance(): AppRegistry {
if (!AppRegistry.instance) {
AppRegistry.instance = new AppRegistry()
}
return AppRegistry.instance
}
/** /**
* 注册内置应用 * 注册内置应用
*/ */
@@ -26,7 +16,7 @@ export class AppRegistry {
// 注意对于异步组件我们不立即标记为raw而是在实际加载时处理 // 注意对于异步组件我们不立即标记为raw而是在实际加载时处理
const safeRegistration = { const safeRegistration = {
...registration, ...registration,
component: registration.component, component: registration.component
} }
this.apps.set(registration.manifest.id, safeRegistration) this.apps.set(registration.manifest.id, safeRegistration)
console.log(`已注册内置应用: ${registration.manifest.name}`) console.log(`已注册内置应用: ${registration.manifest.name}`)
@@ -91,4 +81,4 @@ export class AppRegistry {
} }
// 导出单例实例 // 导出单例实例
export const appRegistry = AppRegistry.getInstance() export const appRegistry = new AppRegistry()

View File

@@ -2,13 +2,7 @@
<BuiltInApp app-id="calculator" title="计算器"> <BuiltInApp app-id="calculator" title="计算器">
<div class="calculator"> <div class="calculator">
<div class="display"> <div class="display">
<input <input v-model="displayValue" type="text" readonly class="display-input" :class="{ 'error': hasError }">
v-model="displayValue"
type="text"
readonly
class="display-input"
:class="{ 'error': hasError }"
>
</div> </div>
<div class="buttons"> <div class="buttons">
@@ -79,17 +73,18 @@ const saveHistory = async (expression: string, result: string) => {
} }
} }
// 直接使用事件服务发送通知 // 移除事件服务相关代码
const showNotification = (message: string) => { // // 直接使用事件服务发送通知
if (systemService) { // const showNotification = (message: string) => {
const eventService = systemService.getEventService() // if (systemService) {
eventService.sendMessage('calculator', 'user-interaction', { // const eventService = systemService.getEventService()
type: 'notification', // eventService.sendMessage('calculator', 'user-interaction', {
message, // type: 'notification',
timestamp: new Date() // message,
}) // timestamp: new Date()
} // })
} // }
// }
// 添加数字 // 添加数字
const appendNumber = (num: string) => { const appendNumber = (num: string) => {
@@ -156,8 +151,9 @@ const calculate = async () => {
// 保存历史记录 // 保存历史记录
await saveHistory(originalExpression, result.toString()) await saveHistory(originalExpression, result.toString())
// 发送通知 // 移除事件服务相关代码
showNotification(`计算结果: ${result}`) // // 发送通知
// showNotification(`计算结果: ${result}`)
} catch (error) { } catch (error) {
hasError.value = true hasError.value = true

View File

@@ -25,7 +25,7 @@ const appContext = {
systemService, systemService,
// 直接暴露系统服务的方法,简化调用 // 直接暴露系统服务的方法,简化调用
storage: systemService?.getResourceService(), storage: systemService?.getResourceService(),
events: systemService?.getEventService(), // 移除 events: systemService?.getEventService(),
lifecycle: systemService?.getLifecycleManager(), lifecycle: systemService?.getLifecycleManager(),
window: { window: {
setTitle: (title: string) => { setTitle: (title: string) => {

View File

@@ -16,6 +16,7 @@ export function registerBuiltInApps() {
icon: '🧮', icon: '🧮',
permissions: ['storage'], permissions: ['storage'],
window: { window: {
title: '计算器',
width: 400, width: 400,
height: 600, height: 600,
minWidth: 320, minWidth: 320,
@@ -46,6 +47,7 @@ export function registerBuiltInApps() {
icon: '📝', icon: '📝',
permissions: ['storage', 'notification'], permissions: ['storage', 'notification'],
window: { window: {
title: '记事本',
width: 800, width: 800,
height: 600, height: 600,
minWidth: 400, minWidth: 400,
@@ -74,6 +76,7 @@ export function registerBuiltInApps() {
icon: '✅', icon: '✅',
permissions: ['storage', 'notification'], permissions: ['storage', 'notification'],
window: { window: {
title: '待办事项',
width: 600, width: 600,
height: 700, height: 700,
minWidth: 400, minWidth: 400,

View File

@@ -1,3 +1,5 @@
import type { WindowConfig } from '@/services/windowForm/WindowFormService.ts'
/** /**
* 内置应用清单接口 * 内置应用清单接口
*/ */
@@ -33,48 +35,7 @@ export interface InternalAppManifest {
/** /**
* 窗体配置信息 * 窗体配置信息
*/ */
window: { window: WindowConfig
/**
* 窗体宽度
*/
width: number
/**
* 窗体高度
*/
height: number
/**
* 窗体最小宽度
*/
minWidth?: number
/**
* 窗体最小高度
*/
minHeight?: number
/**
* 窗体最大宽度
*/
maxWidth?: number
/**
* 窗体最大高度
*/
maxHeight?: number
/**
* 是否可调整大小
*/
resizable?: boolean
/**
* 是否可最小化
*/
minimizable?: boolean
/**
* 是否可最大化
*/
maximizable?: boolean
/**
* 是否可关闭
*/
closable?: boolean
}
/** /**
* 应用分类 * 应用分类
*/ */

View File

@@ -0,0 +1,24 @@
/** 单例模式 装饰器
* @param clz
* @returns
*/
export function SingletonPattern<T extends new (...args: any[]) => any>(clz: T) {
// 添加判断,确保装饰器只能用于类
if (typeof clz !== 'function') {
throw new Error('单例模式装饰器只能修饰在类上')
}
let instance: InstanceType<T>
const proxy = new Proxy(clz, {
construct(target, args, newTarget) {
if (!instance) {
instance = Reflect.construct(target, args, newTarget)
}
return instance
}
})
proxy.prototype.constructor = proxy
return proxy
}

View File

@@ -45,7 +45,7 @@ body {
background-color: var(--color-light); background-color: var(--color-light);
-webkit-font-smoothing: antialiased; /* 字体抗锯齿 */ -webkit-font-smoothing: antialiased; /* 字体抗锯齿 */
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
overflow-x: hidden; overflow: hidden;
} }
/* ===== 排版元素 ===== */ /* ===== 排版元素 ===== */

View File

@@ -1,16 +0,0 @@
import { EventBuilderImpl } from '@/events/impl/EventBuilderImpl.ts'
import type { IEventMap } from '@/events/IEventBuilder.ts'
import type { IDesktopAppIcon } from '@/ui/types/IDesktopAppIcon.ts'
/**
* 桌面相关的事件
*/
export interface IDesktopEvent extends IEventMap {
/**
* 桌面应用位置改变
*/
desktopAppPosChange: (info: IDesktopAppIcon) => void;
}
/** 窗口事件管理器 */
export const desktopEM = new EventBuilderImpl<IDesktopEvent>()

View File

@@ -1,35 +0,0 @@
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
import type { IEventMap } from '@/core/events/IEventBuilder.ts'
import type { IDesktopAppIcon } from '@/core/desktop/types/IDesktopAppIcon.ts'
export const eventManager = new EventBuilderImpl<IAllEvent>()
/**
* 系统进程的事件
* @description
* <p>onAuthChange - 认证状态改变</p>
* <p>onThemeChange - 主题改变</p>
*/
export interface IBasicSystemEvent extends IEventMap {
/** 认证状态改变 */
onAuthChange: () => {},
/** 主题改变 */
onThemeChange: (theme: string) => void
}
/**
* 桌面进程的事件
* @description
* <p>onDesktopRootDomResize - 桌面根dom尺寸改变</p>
* <p>onDesktopProcessInitialize - 桌面进程初始化完成</p>
*/
export interface IDesktopEvent extends IEventMap {
/** 桌面根dom尺寸改变 */
onDesktopRootDomResize: (width: number, height: number) => void
/** 桌面进程初始化完成 */
onDesktopProcessInitialize: () => void
/** 桌面应用图标位置改变 */
onDesktopAppIconPos: (iconInfo: IDesktopAppIcon) => void
}
export interface IAllEvent extends IDesktopEvent, IBasicSystemEvent {}

View File

@@ -1,4 +1,24 @@
import type { IDestroyable } from '@/core/common/types/IDestroyable.ts' import type { IDestroyable } from '@/common/types/IDestroyable'
import type { WindowState } from '@/services/windowForm/WindowFormService.ts'
import type { ResourceType } from '@/services/ResourceService'
/**
* 窗体数据更新参数
*/
export interface IWindowFormDataUpdateParams {
/** 窗口id */
id: string
/** 窗口状态 */
state: WindowState
/** 窗口宽度 */
width: number
/** 窗口高度 */
height: number
/** 窗口x坐标(左上角) */
x: number
/** 窗口y坐标(左上角) */
y: number
}
/** /**
* 事件定义 * 事件定义
@@ -11,34 +31,54 @@ export interface IEventMap {
[key: string]: (...args: any[]) => void [key: string]: (...args: any[]) => void
} }
export interface ISystemBuiltInEventMap extends IEventMap {
// 系统就绪事件
onSystemReady: (data: { timestamp: Date; services: string[] }) => void
// 窗体相关事件
onWindowStateChanged: (windowId: string, newState: string, oldState: string) => void
onWindowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void
onWindowFormResizeStart: (windowId: string) => void
onWindowFormResizing: (windowId: string, width: number, height: number) => void
onWindowFormResizeEnd: (windowId: string) => void
onWindowClose: (windowId: string) => void
// 应用生命周期事件
onAppLifecycle: (data: { appId: string; event: string; timestamp: Date }) => void
// 资源相关事件
onResourceQuotaExceeded: (appId: string, resourceType: ResourceType) => void
onPerformanceAlert: (data: { type: 'memory' | 'cpu'; usage: number; limit: number }) => void
}
/** /**
* 事件管理器接口定义 * 事件管理器接口定义
*/ */
export interface IEventBuilder<Events extends IEventMap> extends IDestroyable { export interface IEventBuilder<Events extends IEventMap> extends IDestroyable {
/** /**
* 添加事件监听 * 订阅事件
* @param eventName 事件名称 * @param eventName 事件名称
* @param handler 事件处理函数 * @param handler 事件处理函数
* @param options 配置项 { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 } * @param options 配置项 { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
* @returns void * @returns void
*/ */
addEventListener<E extends keyof Events, F extends Events[E]>( subscribe<E extends keyof Events, F extends Events[E]>(
eventName: E, eventName: E,
handler: F, handler: F,
options?: { options?: {
immediate?: boolean immediate?: boolean
immediateArgs?: Parameters<F> immediateArgs?: Parameters<F>
once?: boolean once?: boolean
}, }
): void ): () => void
/** /**
* 移除事件监听 * 移除事件
* @param eventName 事件名称 * @param eventName 事件名称
* @param handler 事件处理函数 * @param handler 事件处理函数
* @returns void * @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
/** /**
* 触发事件 * 触发事件
@@ -46,5 +86,8 @@ export interface IEventBuilder<Events extends IEventMap> extends IDestroyable {
* @param args 参数 * @param args 参数
* @returns void * @returns void
*/ */
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>): void notify<E extends keyof Events, F extends Events[E]>(
eventName: E,
...args: Parameters<F>
): void
} }

View File

@@ -1,76 +0,0 @@
import { EventBuilderImpl } from '@/events/impl/EventBuilderImpl.ts'
import type { IEventMap } from '@/events/IEventBuilder.ts'
import type { TWindowFormState } from '@/ui/types/WindowFormTypes.ts'
/**
* 窗口的事件
*/
export interface IWindowFormEvent extends IEventMap {
/**
* 窗口最小化
* @param id 窗口id
*/
windowFormMinimize: (id: string) => void
/**
* 窗口最大化
* @param id 窗口id
*/
windowFormMaximize: (id: string) => void
/**
* 窗口还原
* @param id 窗口id
*/
windowFormRestore: (id: string) => void
/**
* 窗口关闭
* @param id 窗口id
*/
windowFormClose: (id: string) => void
/**
* 窗口聚焦
* @param id 窗口id
*/
windowFormFocus: (id: string) => void
/**
* 窗口数据更新
* @param data 窗口数据
*/
windowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void
/**
* 窗口创建完成
*/
windowFormCreated: () => void
/**
* 开始调整窗体尺寸
* @param id 窗口id
*/
windowFormResizeStart: (id: string) => void
/**
* 调整尺寸过程中
* @param data 窗口数据
*/
windowFormResizing: (data: IWindowFormDataUpdateParams) => void
/**
* 完成窗体尺寸调整
* @param id 窗口id
*/
windowFormResizeEnd: (id: string) => void
}
export interface IWindowFormDataUpdateParams {
/** 窗口id */
id: string
/** 窗口状态 */
state: TWindowFormState
/** 窗口宽度 */
width: number
/** 窗口高度 */
height: number
/** 窗口x坐标(左上角) */
x: number
/** 窗口y坐标(左上角) */
y: number
}
/** 窗口事件管理器 */
export const wfem = new EventBuilderImpl<IWindowFormEvent>()

View File

@@ -1,4 +1,4 @@
import type { IEventBuilder, IEventMap } from '@/core/events/IEventBuilder.ts' import type { IEventBuilder, IEventMap } from '../IEventBuilder.ts'
interface HandlerWrapper<T extends (...args: any[]) => any> { interface HandlerWrapper<T extends (...args: any[]) => any> {
fn: T fn: T
@@ -13,12 +13,13 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
* @param eventName 事件名称 * @param eventName 事件名称
* @param handler 监听器 * @param handler 监听器
* @param options { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 } * @param options { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
* @returns 返回一个 `unsubscribe` 函数,用于移除当前监听
* @example * @example
* eventBus.addEventListener('noArgs', () => {}) * eventBus.subscribe('noArgs', () => {})
* eventBus.addEventListener('greet', name => {}, { immediate: true, immediateArgs: ['abc'] }) * eventBus.subscribe('greet', name => {}, { immediate: true, immediateArgs: ['abc'] })
* eventBus.addEventListener('onResize', (w, h) => {}, { immediate: true, immediateArgs: [1, 2] }) * 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, eventName: E,
handler: F, handler: F,
options?: { options?: {
@@ -26,15 +27,16 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
immediateArgs?: Parameters<F> immediateArgs?: Parameters<F>
once?: boolean once?: boolean
}, },
) { ): () => void {
if (!handler) return if (!handler) return () => {}
if (!this._eventHandlers.has(eventName)) { if (!this._eventHandlers.has(eventName)) {
this._eventHandlers.set(eventName, new Set<HandlerWrapper<F>>()) this._eventHandlers.set(eventName, new Set<HandlerWrapper<F>>())
} }
const set = this._eventHandlers.get(eventName)! const set = this._eventHandlers.get(eventName)!
const wrapper: HandlerWrapper<F> = { fn: handler, once: options?.once ?? false }
if (![...set].some((wrapper) => wrapper.fn === handler)) { if (![...set].some((wrapper) => wrapper.fn === handler)) {
set.add({ fn: handler, once: options?.once ?? false }) set.add(wrapper)
} }
if (options?.immediate) { if (options?.immediate) {
@@ -44,6 +46,12 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
console.error(e) 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 eventName 事件名称
* @param handler 监听器 * @param handler 监听器
* @example * @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) const set = this._eventHandlers.get(eventName)
if (!set) return if (!set) return
@@ -62,6 +70,9 @@ export class EventBuilderImpl<Events extends IEventMap> implements IEventBuilder
set.delete(wrapper) 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 eventName 事件名称
* @param args 参数 * @param args 参数
* @example * @example
* eventBus.notifyEvent('noArgs') * eventBus.notify('noArgs')
* eventBus.notifyEvent('greet', 'Alice') * eventBus.notify('greet', 'Alice')
* eventBus.notifyEvent('onResize', 1, 2) * 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 if (!this._eventHandlers.has(eventName)) return
const set = this._eventHandlers.get(eventName)! const set = this._eventHandlers.get(eventName)!

View File

@@ -14,9 +14,7 @@ registerBuiltInApps()
// 初始化系统服务 // 初始化系统服务
const systemService = new SystemServiceIntegration({ const systemService = new SystemServiceIntegration({
debug: import.meta.env.DEV, debug: import.meta.env.DEV
enablePerformanceMonitoring: true,
enableSecurityAudit: true
}) })
// 创建应用实例 // 创建应用实例
@@ -30,7 +28,8 @@ app.use(naiveUi)
app.provide('systemService', systemService) app.provide('systemService', systemService)
// 初始化系统服务然后挂载应用 // 初始化系统服务然后挂载应用
systemService.initialize() systemService
.initialize()
.then(() => { .then(() => {
app.mount('#app') app.mount('#app')
console.log('桌面系统启动完成') console.log('桌面系统启动完成')

View File

@@ -191,11 +191,11 @@ class StorageSDKImpl extends SDKBase implements StorageSDK {
} }
async get<T = any>(key: string): Promise<APIResponse<T | null>> { 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>> { 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>> { async clear(): Promise<APIResponse<boolean>> {

View File

@@ -1,10 +1,11 @@
import { reactive } from 'vue' import { reactive, ref } from 'vue'
import type { WindowService } from './WindowService'
import type { ResourceService } from './ResourceService' import type { ResourceService } from './ResourceService'
import type { EventCommunicationService } from './EventCommunicationService'
import type { ApplicationSandboxEngine } from './ApplicationSandboxEngine' import type { ApplicationSandboxEngine } from './ApplicationSandboxEngine'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { externalAppDiscovery } from './ExternalAppDiscovery' import { externalAppDiscovery } from './ExternalAppDiscovery'
import type { WindowFormService } from '@/services/windowForm/WindowFormService.ts'
import { type AppRegistration, appRegistry } from '@/apps'
import windowManager from '@/ui/components/WindowManager.vue'
/** /**
* 应用状态枚举 * 应用状态枚举
@@ -20,7 +21,7 @@ export enum AppLifecycleState {
UNINSTALLING = 'uninstalling', UNINSTALLING = 'uninstalling',
ERROR = 'error', ERROR = 'error',
CRASHED = 'crashed', CRASHED = 'crashed',
AVAILABLE = 'available', // 外置应用可用但未注册状态 AVAILABLE = 'available' // 外置应用可用但未注册状态
} }
/** /**
@@ -81,16 +82,6 @@ export interface AppInstance {
persistent: boolean persistent: boolean
} }
/**
* 应用安装包接口
*/
export interface AppPackage {
manifest: AppManifest
files: Map<string, Blob | string> // 文件路径到内容的映射
signature?: string // 数字签名
checksum: string // 校验和
}
/** /**
* 应用启动选项 * 应用启动选项
*/ */
@@ -121,155 +112,98 @@ export interface AppLifecycleEvents {
onStateChanged: (appId: string, newState: AppLifecycleState, oldState: AppLifecycleState) => void onStateChanged: (appId: string, newState: AppLifecycleState, oldState: AppLifecycleState) => void
} }
interface BuiltInWindowForm {
id: string
appId: string
component: any
props: Record<string, any>
}
/** /**
* 应用生命周期管理器 * 应用生命周期管理器
*/ */
export class ApplicationLifecycleManager { export class ApplicationLifecycleManager {
private installedApps = reactive(new Map<string, AppInstance>()) private installedApps = new Map<string, AppInstance>()
private runningProcesses = reactive(new Map<string, AppInstance>()) private runningProcesses = new Map<string, AppInstance>()
private appFiles = new Map<string, Map<string, Blob | string>>() // 应用文件存储 private builtInWindowForms = new Map<string, BuiltInWindowForm>()
private windowService: WindowService private windowFormService: WindowFormService
private resourceService: ResourceService private resourceService: ResourceService
private eventService: EventCommunicationService
private sandboxEngine: ApplicationSandboxEngine private sandboxEngine: ApplicationSandboxEngine
constructor( constructor(
windowService: WindowService, windowFormService: WindowFormService,
resourceService: ResourceService, resourceService: ResourceService,
eventService: EventCommunicationService, sandboxEngine: ApplicationSandboxEngine
sandboxEngine: ApplicationSandboxEngine,
) { ) {
this.windowService = windowService this.windowFormService = windowFormService
this.resourceService = resourceService this.resourceService = resourceService
this.eventService = eventService
this.sandboxEngine = sandboxEngine this.sandboxEngine = sandboxEngine
this.setupEventListeners()
this.loadInstalledApps()
// 外部应用发现已由 SystemServiceIntegration 统一管理,无需重复初始化
} }
/** public getBuiltInWindowForms() {
* 安装应用 return this.builtInWindowForms
*/
async installApp(appPackage: AppPackage): Promise<string> {
const { manifest, files, checksum } = appPackage
const appId = manifest.id
try {
// 检查应用是否已安装
if (this.installedApps.has(appId)) {
throw new Error(`应用 ${appId} 已安装`)
}
// 验证清单文件
this.validateManifest(manifest)
// 验证校验和
if (!this.verifyChecksum(files, checksum)) {
throw new Error('应用包校验失败')
}
// 检查权限
await this.checkPermissions(manifest.permissions)
const now = new Date()
const appInstance: AppInstance = {
id: appId,
manifest,
state: AppLifecycleState.INSTALLING,
processId: '',
installedAt: now,
errorCount: 0,
crashCount: 0,
memoryUsage: 0,
cpuUsage: 0,
version: manifest.version,
autoStart: false,
persistent: manifest.background?.persistent || false,
}
// 更新状态
this.updateAppState(appInstance, AppLifecycleState.INSTALLING)
this.installedApps.set(appId, appInstance)
// 存储应用文件
this.appFiles.set(appId, files)
// 保存到本地存储
await this.saveAppToStorage(appInstance)
// 更新状态为已安装
this.updateAppState(appInstance, AppLifecycleState.INSTALLED)
this.eventService.sendMessage('system', 'app-lifecycle', {
type: 'installed',
appId,
manifest,
})
console.log(`应用 ${manifest.name} (${appId}) 安装成功`)
return appId
} catch (error) {
// 清理安装失败的应用
this.installedApps.delete(appId)
this.appFiles.delete(appId)
console.error('应用安装失败:', error)
throw error
}
}
/**
* 卸载应用
*/
async uninstallApp(appId: string): Promise<boolean> {
const app = this.installedApps.get(appId)
if (!app) {
throw new Error(`应用 ${appId} 未安装`)
}
try {
// 如果应用正在运行,先停止
if (app.state === AppLifecycleState.RUNNING || app.state === AppLifecycleState.SUSPENDED) {
await this.stopApp(appId)
}
this.updateAppState(app, AppLifecycleState.UNINSTALLING)
// 清理应用数据
await this.resourceService.clearStorage(appId)
await this.resourceService.revokeAllPermissions(appId)
// 删除应用文件
this.appFiles.delete(appId)
// 从存储中删除
await this.removeAppFromStorage(appId)
// 从已安装列表中移除
this.installedApps.delete(appId)
this.eventService.sendMessage('system', 'app-lifecycle', {
type: 'uninstalled',
appId,
})
console.log(`应用 ${appId} 卸载成功`)
return true
} catch (error) {
console.error('应用卸载失败:', error)
throw error
}
} }
/** /**
* 启动应用 * 启动应用
*/ */
async startApp(appId: string, options: AppStartOptions = {}): Promise<string> { async startApp(appId: string, options: AppStartOptions = {}) {
// 检查应用是否是内置应用
if (appRegistry.hasApp(appId)) {
await this.startBuiltInApp(appId, options)
} else {
await this.startExternalApp(appId, options)
}
}
private async startBuiltInApp(appId: string, options: AppStartOptions = {}) {
const appRegistration = appRegistry.getApp(appId)
if (!appRegistration) {
console.error(`内置应用 ${appId} 不存在`)
return
}
// 检查是否已在运行
if (this.isAppRunning(appId)) {
console.log(`应用 ${appRegistration.manifest.name} 已在运行`)
return
}
const windowInstance = await this.windowFormService.createWindow(appId, appRegistration.manifest.window)
// 处理异步组件加载
let component = appRegistration.component
if (typeof component === 'function') {
try {
// 如果是函数,调用它来获取组件
component = await component()
} catch (error) {
console.error(`加载应用组件失败: ${appId}`, error)
return
}
}
// 添加窗口
const windowForm: BuiltInWindowForm = {
id: windowInstance.id,
appId,
component,
props: {
windowId: windowInstance.id,
appId
}
}
this.builtInWindowForms.set(windowInstance.id, windowForm)
console.log(this.builtInWindowForms)
}
private async startExternalApp(appId: string, options: AppStartOptions = {}) {
let app = this.installedApps.get(appId) let app = this.installedApps.get(appId)
console.log('-----------------------------')
// 如果应用未安装,检查是否为外置应用 // 如果应用未安装,检查是否为外置应用
let isExternalApp = false let isExternalApp = false
if (!app) { if (!app) {
@@ -292,7 +226,7 @@ export class ApplicationLifecycleManager {
cpuUsage: 0, cpuUsage: 0,
version: externalApp.manifest.version, version: externalApp.manifest.version,
autoStart: false, autoStart: false,
persistent: false, persistent: false
} }
} }
} }
@@ -312,21 +246,7 @@ export class ApplicationLifecycleManager {
this.updateAppState(app, AppLifecycleState.STARTING) this.updateAppState(app, AppLifecycleState.STARTING)
// 检查是否为内置应用 // 检查是否为内置应用
let isBuiltInApp = false let isBuiltInApp = true
try {
const { AppRegistry } = await import('../apps/AppRegistry')
const appRegistry = AppRegistry.getInstance()
isBuiltInApp = appRegistry.hasApp(appId)
} catch (error) {
console.warn('无法导入 AppRegistry')
}
// 检查是否为外置应用(仅当不是内置应用时)
// 修复移除重复的变量声明使用已声明的isExternalApp变量
if (!isBuiltInApp && !isExternalApp) {
isExternalApp = externalAppDiscovery.hasApp(appId)
}
// 创建窗体(如果不是后台应用) // 创建窗体(如果不是后台应用)
let windowId: string | undefined let windowId: string | undefined
@@ -341,23 +261,12 @@ export class ApplicationLifecycleManager {
minWidth: app.manifest.window?.minWidth, minWidth: app.manifest.window?.minWidth,
minHeight: app.manifest.window?.minHeight, minHeight: app.manifest.window?.minHeight,
maxWidth: app.manifest.window?.maxWidth, maxWidth: app.manifest.window?.maxWidth,
maxHeight: app.manifest.window?.maxHeight, maxHeight: app.manifest.window?.maxHeight
} }
const windowInstance = await this.windowService.createWindow(appId, windowConfig) const windowInstance = await this.windowFormService.createWindow(appId, windowConfig)
windowId = windowInstance.id windowId = windowInstance.id
app.windowId = windowId app.windowId = windowId
// 对于内置应用,需要在窗口内容区域挂载 AppRenderer 组件
if (isBuiltInApp) {
await this.mountBuiltInApp(appId, windowInstance)
}
// 对于内置应用不需要等待窗口DOM元素渲染
if (!isBuiltInApp) {
// 稍等片刻确保窗口DOM元素已经渲染完成
await new Promise((resolve) => setTimeout(resolve, 100))
}
} }
// 对于外置应用,需要创建沙箱;内置应用跳过沙箱 // 对于外置应用,需要创建沙箱;内置应用跳过沙箱
@@ -371,13 +280,13 @@ export class ApplicationLifecycleManager {
networkTimeout: 15000, // 增加超时时间到15秒 networkTimeout: 15000, // 增加超时时间到15秒
csp: csp:
app.manifest.contentSecurity?.policy || app.manifest.contentSecurity?.policy ||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';"
} }
const sandbox = await this.sandboxEngine.createSandbox( const sandbox = await this.sandboxEngine.createSandbox(
appId, appId,
windowId || 'background', windowId || 'background',
sandboxConfig, sandboxConfig
) )
app.sandboxId = sandbox.id app.sandboxId = sandbox.id
@@ -447,9 +356,9 @@ export class ApplicationLifecycleManager {
// 关闭窗体(如果还存在) // 关闭窗体(如果还存在)
if (app.windowId) { if (app.windowId) {
const window = this.windowService.getWindow(app.windowId) const window = this.windowFormService.getWindow(app.windowId)
if (window) { if (window) {
await this.windowService.destroyWindow(app.windowId) await this.windowFormService.destroyWindow(app.windowId)
} }
app.windowId = undefined app.windowId = undefined
} }
@@ -474,105 +383,6 @@ export class ApplicationLifecycleManager {
} }
} }
/**
* 暂停应用
*/
async suspendApp(appId: string): Promise<boolean> {
// 首先从已安装应用中查找
let app = this.installedApps.get(appId)
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
if (!app) {
for (const runningApp of this.runningProcesses.values()) {
if (runningApp.id === appId) {
app = runningApp
break
}
}
}
if (!app || app.state !== AppLifecycleState.RUNNING) {
return false
}
try {
// 暂停沙箱
if (app.sandboxId) {
this.sandboxEngine.suspendSandbox(app.sandboxId)
}
// 最小化窗体
if (app.windowId) {
this.windowService.minimizeWindow(app.windowId)
}
this.updateAppState(app, AppLifecycleState.SUSPENDED)
// 注意状态变更消息由updateAppState方法自动发送不需要手动发送
console.log(`应用 ${appId} 已暂停`)
return true
} catch (error) {
console.error('应用暂停失败:', error)
return false
}
}
/**
* 恢复应用
*/
async resumeApp(appId: string): Promise<boolean> {
// 首先从已安装应用中查找
let app = this.installedApps.get(appId)
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
if (!app) {
for (const runningApp of this.runningProcesses.values()) {
if (runningApp.id === appId) {
app = runningApp
break
}
}
}
if (!app || app.state !== AppLifecycleState.SUSPENDED) {
return false
}
try {
// 恢复沙箱
if (app.sandboxId) {
this.sandboxEngine.resumeSandbox(app.sandboxId)
}
// 恢复窗体
if (app.windowId) {
this.windowService.restoreWindow(app.windowId)
}
app.lastActiveAt = new Date()
this.updateAppState(app, AppLifecycleState.RUNNING)
// 注意状态变更消息由updateAppState方法自动发送不需要手动发送
console.log(`应用 ${appId} 已恢复`)
return true
} catch (error) {
console.error('应用恢复失败:', error)
return false
}
}
/**
* 重启应用
*/
async restartApp(appId: string, options?: AppStartOptions): Promise<string> {
await this.stopApp(appId)
// 等待一小段时间确保完全停止
await new Promise((resolve) => setTimeout(resolve, 1000))
return this.startApp(appId, options)
}
/** /**
* 获取应用信息 * 获取应用信息
*/ */
@@ -598,20 +408,15 @@ export class ApplicationLifecycleManager {
* 检查应用是否正在运行 * 检查应用是否正在运行
*/ */
isAppRunning(appId: string): boolean { isAppRunning(appId: string): boolean {
// 首先从已安装应用中查找 let app;
let app = this.installedApps.get(appId)
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
if (!app) {
for (const runningApp of this.runningProcesses.values()) { for (const runningApp of this.runningProcesses.values()) {
if (runningApp.id === appId) { if (runningApp.id === appId) {
app = runningApp app = runningApp
break break
} }
} }
} return app?.state === AppLifecycleState.RUNNING
return app?.state === AppLifecycleState.RUNNING || app?.state === AppLifecycleState.SUSPENDED
} }
/** /**
@@ -629,108 +434,7 @@ export class ApplicationLifecycleManager {
cpuUsage: app.cpuUsage, cpuUsage: app.cpuUsage,
startedAt: app.startedAt, startedAt: app.startedAt,
lastActiveAt: app.lastActiveAt, lastActiveAt: app.lastActiveAt,
uptime: app.startedAt ? Date.now() - app.startedAt.getTime() : 0, uptime: app.startedAt ? Date.now() - app.startedAt.getTime() : 0
}
}
// 私有方法
/**
* 为内置应用挂载 AppRenderer 组件
*/
private async mountBuiltInApp(appId: string, windowInstance: any): Promise<void> {
try {
// 动态导入 Vue 和 AppRenderer
const { createApp } = await import('vue')
const AppRenderer = (await import('../ui/components/AppRenderer.vue')).default
console.log(`[LifecycleManager] 为内置应用 ${appId} 创建 AppRenderer 组件`)
const app = createApp({
components: { AppRenderer },
template: `<AppRenderer :app-id="'${appId}'" :window-id="'${windowInstance.id}'"/>`,
})
// 提供系统服务(使用当前实例所在的系统服务)
app.provide('systemService', {
getWindowService: () => this.windowService,
getResourceService: () => this.resourceService,
getEventService: () => this.eventService,
getSandboxEngine: () => this.sandboxEngine,
getLifecycleManager: () => this,
})
// 挂载到窗口内容区域
const contentArea = windowInstance.element?.querySelector('.window-content')
if (contentArea) {
app.mount(contentArea)
console.log(`[LifecycleManager] AppRenderer 组件已挂载到窗口 ${windowInstance.id}`)
} else {
throw new Error('未找到窗口内容区域')
}
} catch (error) {
console.error(`内置应用 ${appId} 挂载失败:`, error)
throw error
}
}
/**
* 验证应用清单文件
*/
private validateManifest(manifest: AppManifest): void {
const required = ['id', 'name', 'version', 'entryPoint']
for (const field of required) {
if (!manifest[field as keyof AppManifest]) {
throw new Error(`清单文件缺少必需字段: ${field}`)
}
}
// 验证版本格式
if (!/^\d+\.\d+\.\d+/.test(manifest.version)) {
throw new Error('版本号格式不正确,应为 x.y.z 格式')
}
// 验证应用ID格式
if (!/^[a-zA-Z0-9._-]+$/.test(manifest.id)) {
throw new Error('应用ID格式不正确')
}
}
/**
* 验证校验和
*/
private verifyChecksum(files: Map<string, Blob | string>, expectedChecksum: string): boolean {
// 简化实现,实际应用中应使用更强的哈希算法
let content = ''
for (const [path, data] of files.entries()) {
content += path + (typeof data === 'string' ? data : data.size)
}
const actualChecksum = this.safeBase64Encode(content).slice(0, 32)
return actualChecksum === expectedChecksum
}
/**
* 安全的 Base64 编码,支持 Unicode 字符
*/
private safeBase64Encode(str: string): string {
try {
// 使用 encodeURIComponent + atob 来处理 Unicode 字符
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) {
return String.fromCharCode(parseInt(p1, 16))
}),
)
} catch (error) {
// 如果还是失败,使用简单的哈希算法
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // 转换为32位整数
}
return Math.abs(hash).toString(36)
} }
} }
@@ -745,115 +449,6 @@ export class ApplicationLifecycleManager {
} }
} }
/**
* 在沙箱中加载应用
*/
private async loadAppInSandbox(app: AppInstance, sandboxId: string): Promise<void> {
const appFiles = this.appFiles.get(app.id)
if (!appFiles) {
throw new Error('应用文件不存在')
}
const entryFile = appFiles.get(app.manifest.entryPoint)
if (!entryFile) {
throw new Error('入口文件不存在')
}
// 创建应用URL
const entryUrl = this.createAppUrl(app.id, app.manifest.entryPoint, entryFile)
// 在沙箱中加载应用
await this.sandboxEngine.loadApplication(sandboxId, entryUrl)
}
/**
* 创建应用文件URL
*/
private createAppUrl(appId: string, filePath: string, content: Blob | string): string {
let blob: Blob
if (typeof content === 'string') {
// 确保使用UTF-8编码创建Blob
const contentType = this.getContentType(filePath)
const utf8ContentType = contentType.includes('charset')
? contentType
: `${contentType}; charset=utf-8`
blob = new Blob([content], { type: utf8ContentType })
} else {
blob = content
}
return URL.createObjectURL(blob)
}
/**
* 获取文件内容类型
*/
private getContentType(filePath: string): string {
const ext = filePath.split('.').pop()?.toLowerCase()
switch (ext) {
case 'html':
return 'text/html; charset=utf-8'
case 'js':
return 'application/javascript; charset=utf-8'
case 'css':
return 'text/css; charset=utf-8'
case 'json':
return 'application/json; charset=utf-8'
case 'png':
return 'image/png'
case 'jpg':
case 'jpeg':
return 'image/jpeg'
case 'svg':
return 'image/svg+xml; charset=utf-8'
default:
return 'text/plain; charset=utf-8'
}
}
/**
* 保存应用到存储
*/
private async saveAppToStorage(app: AppInstance): Promise<void> {
const appData = {
id: app.id,
manifest: app.manifest,
installedAt: app.installedAt.toISOString(),
version: app.version,
autoStart: app.autoStart,
persistent: app.persistent,
}
await this.resourceService.setStorage('system', `apps.${app.id}`, appData)
}
/**
* 从存储中删除应用
*/
private async removeAppFromStorage(appId: string): Promise<void> {
await this.resourceService.removeStorage('system', `apps.${appId}`)
}
/**
* 初始化外置应用发现(已由 SystemServiceIntegration 统一管理)
* 这个方法保留用于未来可能的扩展,但不再重复启动发现服务
*/
private async initializeExternalAppDiscovery(): Promise<void> {
try {
console.log('[LifecycleManager] 外置应用发现服务已由系统服务集成统一管理')
// 可以在这里添加应用生命周期管理器特有的外置应用监听逻辑
// 例如监听外置应用的注册/卸载事件
} catch (error) {
console.error('[LifecycleManager] 初始化外置应用监听失败:', error)
}
}
// 已移除:外部应用不再需要注册到 installedApps 中
// 外部应用在启动时会创建临时实例
/** /**
* 为外置应用加载代码到沙箱 * 为外置应用加载代码到沙箱
*/ */
@@ -904,7 +499,7 @@ export class ApplicationLifecycleManager {
version: externalApp.manifest.version, version: externalApp.manifest.version,
autoStart: false, autoStart: false,
persistent: false, persistent: false,
isExternal: true, isExternal: true
} }
apps.push(appInstance) apps.push(appInstance)
} }
@@ -925,82 +520,6 @@ export class ApplicationLifecycleManager {
} }
} }
/**
* 加载已安装的应用
*/
private async loadInstalledApps(): Promise<void> {
try {
// 从存储中加载应用列表
const appsData = (await this.resourceService.getStorage('system', 'apps')) || {}
for (const [appId, appData] of Object.entries(appsData as Record<string, any>)) {
if (appData && typeof appData === 'object') {
const app: AppInstance = {
id: appId,
manifest: appData.manifest,
state: AppLifecycleState.STOPPED,
processId: '',
installedAt: new Date(appData.installedAt),
errorCount: 0,
crashCount: 0,
memoryUsage: 0,
cpuUsage: 0,
version: appData.version,
autoStart: appData.autoStart || false,
persistent: appData.persistent || false,
}
this.updateAppState(app, AppLifecycleState.INSTALLED)
this.installedApps.set(appId, app)
}
}
console.log(`已加载 ${this.installedApps.size} 个已安装应用`)
} catch (error) {
console.error('加载已安装应用失败:', error)
}
}
/**
* 设置事件监听器
*/
private setupEventListeners(): void {
// 监听沙箱状态变化
this.eventService.subscribe('system', 'sandbox-state-change', (message) => {
const { sandboxId, newState } = message.payload
// 查找对应的应用
for (const app of this.installedApps.values()) {
if (app.sandboxId === sandboxId) {
if (newState === 'error') {
this.handleAppError(app.id, new Error('沙箱错误'))
} else if (newState === 'destroyed') {
this.handleAppCrash(app.id, '沙箱被销毁')
}
break
}
}
})
// 监听性能告警
this.eventService.subscribe('system', 'performance-alert', (message) => {
const { sandboxId, type, usage, limit } = message.payload
for (const app of this.installedApps.values()) {
if (app.sandboxId === sandboxId) {
if (type === 'memory') {
app.memoryUsage = usage
} else if (type === 'cpu') {
app.cpuUsage = usage
}
console.warn(`应用 ${app.id} ${type} 使用量 ${usage} 超过限制 ${limit}`)
break
}
}
})
}
/** /**
* 处理应用错误 * 处理应用错误
*/ */
@@ -1010,12 +529,6 @@ export class ApplicationLifecycleManager {
app.errorCount++ app.errorCount++
this.eventService.sendMessage('system', 'app-lifecycle', {
type: 'error',
appId,
error: error.message,
})
console.error(`应用 ${appId} 发生错误:`, error) console.error(`应用 ${appId} 发生错误:`, error)
} }
@@ -1046,11 +559,12 @@ export class ApplicationLifecycleManager {
const oldState = app.state const oldState = app.state
app.state = newState app.state = newState
this.eventService.sendMessage('system', 'app-lifecycle', { // 移除事件服务消息发送
type: 'stateChanged', // this.eventService.sendMessage('system', 'app-lifecycle', {
appId: app.id, // type: 'stateChanged',
newState, // appId: app.id,
oldState, // newState,
}) // oldState
// })
} }
} }

View File

@@ -1,6 +1,5 @@
import { reactive } from 'vue' import { reactive } from 'vue'
import type { ResourceService } from './ResourceService' import type { ResourceService } from './ResourceService'
import type { EventCommunicationService } from './EventCommunicationService'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
/** /**
@@ -107,14 +106,9 @@ export class ApplicationSandboxEngine {
private performanceData = reactive(new Map<string, SandboxPerformance[]>()) private performanceData = reactive(new Map<string, SandboxPerformance[]>())
private monitoringInterval: number | null = null private monitoringInterval: number | null = null
private resourceService: ResourceService private resourceService: ResourceService
private eventService: EventCommunicationService
constructor( constructor(resourceService: ResourceService) {
resourceService: ResourceService,
eventService: EventCommunicationService
) {
this.resourceService = resourceService this.resourceService = resourceService
this.eventService = eventService
this.startPerformanceMonitoring() this.startPerformanceMonitoring()
} }
@@ -180,7 +174,6 @@ export class ApplicationSandboxEngine {
console.log(`沙箱 ${sandboxId} 创建成功`) console.log(`沙箱 ${sandboxId} 创建成功`)
return sandbox return sandbox
} catch (error) { } catch (error) {
this.updateSandboxState(sandbox, SandboxState.ERROR) this.updateSandboxState(sandbox, SandboxState.ERROR)
sandbox.errors.push(error instanceof Error ? error.message : String(error)) sandbox.errors.push(error instanceof Error ? error.message : String(error))
@@ -216,7 +209,9 @@ export class ApplicationSandboxEngine {
// 等待加载完成 // 等待加载完成
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
console.error(`应用加载超时: ${sandbox.appId}, URL: ${applicationUrl}, 超时时间: ${sandbox.config.networkTimeout}ms`) console.error(
`应用加载超时: ${sandbox.appId}, URL: ${applicationUrl}, 超时时间: ${sandbox.config.networkTimeout}ms`
)
reject(new Error('应用加载超时')) reject(new Error('应用加载超时'))
}, sandbox.config.networkTimeout) }, sandbox.config.networkTimeout)
@@ -251,64 +246,6 @@ export class ApplicationSandboxEngine {
} }
} }
/**
* 暂停沙箱
*/
suspendSandbox(sandboxId: string): boolean {
const sandbox = this.sandboxes.get(sandboxId)
if (!sandbox || sandbox.state !== SandboxState.RUNNING) {
return false
}
try {
this.updateSandboxState(sandbox, SandboxState.SUSPENDED)
if (sandbox.iframe) {
// 暂停iframe中的脚本执行
const iframeWindow = sandbox.iframe.contentWindow
if (iframeWindow) {
// 发送暂停事件到应用
iframeWindow.postMessage({ type: 'system:suspend' }, '*')
}
}
console.log(`沙箱 ${sandboxId} 已暂停`)
return true
} catch (error) {
console.error('暂停沙箱失败:', error)
return false
}
}
/**
* 恢复沙箱
*/
resumeSandbox(sandboxId: string): boolean {
const sandbox = this.sandboxes.get(sandboxId)
if (!sandbox || sandbox.state !== SandboxState.SUSPENDED) {
return false
}
try {
this.updateSandboxState(sandbox, SandboxState.RUNNING)
sandbox.lastActiveAt = new Date()
if (sandbox.iframe) {
const iframeWindow = sandbox.iframe.contentWindow
if (iframeWindow) {
// 发送恢复事件到应用
iframeWindow.postMessage({ type: 'system:resume' }, '*')
}
}
console.log(`沙箱 ${sandboxId} 已恢复`)
return true
} catch (error) {
console.error('恢复沙箱失败:', error)
return false
}
}
/** /**
* 销毁沙箱 * 销毁沙箱
*/ */
@@ -323,8 +260,8 @@ export class ApplicationSandboxEngine {
// 移除消息事件监听器 // 移除消息事件监听器
if (sandbox.messageHandler) { if (sandbox.messageHandler) {
window.removeEventListener('message', sandbox.messageHandler); window.removeEventListener('message', sandbox.messageHandler)
sandbox.messageHandler = undefined; sandbox.messageHandler = undefined
} }
// 清理DOM元素 // 清理DOM元素
@@ -366,7 +303,7 @@ export class ApplicationSandboxEngine {
* 获取应用的所有沙箱 * 获取应用的所有沙箱
*/ */
getAppSandboxes(appId: string): SandboxInstance[] { getAppSandboxes(appId: string): SandboxInstance[] {
return Array.from(this.sandboxes.values()).filter(sandbox => sandbox.appId === appId) return Array.from(this.sandboxes.values()).filter((sandbox) => sandbox.appId === appId)
} }
/** /**
@@ -632,10 +569,10 @@ export class ApplicationSandboxEngine {
if (event.source === sandbox.iframe!.contentWindow) { if (event.source === sandbox.iframe!.contentWindow) {
this.handleSandboxMessage(sandbox, event.data) this.handleSandboxMessage(sandbox, event.data)
} }
}; }
window.addEventListener('message', messageHandler); window.addEventListener('message', messageHandler)
// 存储事件监听器引用,以便在销毁时移除 // 存储事件监听器引用,以便在销毁时移除
sandbox.messageHandler = messageHandler; sandbox.messageHandler = messageHandler
// 简化安全限制主要依赖iframe的sandbox属性 // 简化安全限制主要依赖iframe的sandbox属性
sandbox.iframe.addEventListener('load', () => { sandbox.iframe.addEventListener('load', () => {
@@ -709,7 +646,11 @@ export class ApplicationSandboxEngine {
break break
case 'network': case 'network':
result = await this.resourceService.makeNetworkRequest(sandbox.appId, data.url, data.options) result = await this.resourceService.makeNetworkRequest(
sandbox.appId,
data.url,
data.options
)
break break
} }
@@ -1171,10 +1112,10 @@ export class ApplicationSandboxEngine {
// 通知系统应用已准备就绪 // 通知系统应用已准备就绪
window.parent.postMessage({ type: 'app:ready' }, '*'); window.parent.postMessage({ type: 'app:ready' }, '*');
})(); })();
`; `
iframeDoc.head.appendChild(script) iframeDoc.head.appendChild(script)
} }
/** /**
* 设置性能监控 * 设置性能监控
@@ -1219,27 +1160,7 @@ export class ApplicationSandboxEngine {
/** /**
* 检查性能阈值 * 检查性能阈值
*/ */
private checkPerformanceThresholds(sandbox: SandboxInstance, metrics: SandboxPerformance): void { private checkPerformanceThresholds(sandbox: SandboxInstance, metrics: SandboxPerformance): void {}
// 内存使用检查
if (metrics.memoryUsage > sandbox.config.maxMemoryUsage) {
this.eventService.sendMessage('system', 'performance-alert', {
sandboxId: sandbox.id,
type: 'memory',
usage: metrics.memoryUsage,
limit: sandbox.config.maxMemoryUsage
})
}
// CPU使用检查
if (metrics.cpuUsage > sandbox.config.maxCpuUsage) {
this.eventService.sendMessage('system', 'performance-alert', {
sandboxId: sandbox.id,
type: 'cpu',
usage: metrics.cpuUsage,
limit: sandbox.config.maxCpuUsage
})
}
}
/** /**
* 开始性能监控 * 开始性能监控
@@ -1260,9 +1181,12 @@ export class ApplicationSandboxEngine {
private collectPerformanceMetrics(sandbox: SandboxInstance): void { private collectPerformanceMetrics(sandbox: SandboxInstance): void {
if (sandbox.iframe && sandbox.iframe.contentWindow) { if (sandbox.iframe && sandbox.iframe.contentWindow) {
// 请求性能数据 // 请求性能数据
sandbox.iframe.contentWindow.postMessage({ sandbox.iframe.contentWindow.postMessage(
{
type: 'system:performance-request' type: 'system:performance-request'
}, '*') },
'*'
)
} }
} }
@@ -1273,11 +1197,11 @@ export class ApplicationSandboxEngine {
const oldState = sandbox.state const oldState = sandbox.state
sandbox.state = newState sandbox.state = newState
// 触发状态变化事件 // 移除事件服务消息发送
this.eventService.sendMessage('system', 'sandbox-state-change', { // this.eventService.sendMessage('system', 'sandbox-state-change', {
sandboxId: sandbox.id, // sandboxId: sandbox.id,
newState, // newState,
oldState // oldState
}) // })
} }
} }

View File

@@ -1,639 +0,0 @@
import { reactive } from 'vue'
import type { IEventBuilder } from '@/events/IEventBuilder'
import { v4 as uuidv4 } from 'uuid'
/**
* 消息类型枚举
*/
export enum MessageType {
SYSTEM = 'system',
APPLICATION = 'application',
USER_INTERACTION = 'user_interaction',
CROSS_APP = 'cross_app',
BROADCAST = 'broadcast',
}
/**
* 消息优先级枚举
*/
export enum MessagePriority {
LOW = 0,
NORMAL = 1,
HIGH = 2,
CRITICAL = 3,
}
/**
* 消息状态枚举
*/
export enum MessageStatus {
PENDING = 'pending',
SENT = 'sent',
DELIVERED = 'delivered',
FAILED = 'failed',
EXPIRED = 'expired',
}
/**
* 事件消息接口
*/
export interface EventMessage {
id: string
type: MessageType
priority: MessagePriority
senderId: string
receiverId?: string // undefined表示广播消息
channel: string
payload: any
timestamp: Date
expiresAt?: Date
status: MessageStatus
retryCount: number
maxRetries: number
}
/**
* 事件订阅者接口
*/
export interface EventSubscriber {
id: string
appId: string
channel: string
handler: (message: EventMessage) => void | Promise<void>
filter?: (message: EventMessage) => boolean
priority: MessagePriority
createdAt: Date
active: boolean
}
/**
* 通信通道接口
*/
export interface CommunicationChannel {
name: string
description: string
restricted: boolean // 是否需要权限
allowedApps: string[] // 允许访问的应用ID列表
maxMessageSize: number // 最大消息大小(字节)
messageRetention: number // 消息保留时间(毫秒)
}
/**
* 事件统计信息
*/
export interface EventStatistics {
totalMessagesSent: number
totalMessagesReceived: number
totalBroadcasts: number
failedMessages: number
activeSubscribers: number
channelUsage: Map<string, number>
}
/**
* 事件通信服务类
*/
export class EventCommunicationService {
private subscribers = reactive(new Map<string, EventSubscriber>())
private messageQueue = reactive(new Map<string, EventMessage[]>()) // 按应用分组的消息队列
private messageHistory = reactive(new Map<string, EventMessage[]>()) // 消息历史记录
private channels = reactive(new Map<string, CommunicationChannel>())
private statistics = reactive<EventStatistics>({
totalMessagesSent: 0,
totalMessagesReceived: 0,
totalBroadcasts: 0,
failedMessages: 0,
activeSubscribers: 0,
channelUsage: new Map(),
})
private processingInterval: number | null = null
private eventBus: IEventBuilder<any>
constructor(eventBus: IEventBuilder<any>) {
this.eventBus = eventBus
this.initializeDefaultChannels()
this.startMessageProcessing()
}
/**
* 订阅事件频道
*/
subscribe(
appId: string,
channel: string,
handler: (message: EventMessage) => void | Promise<void>,
options: {
filter?: (message: EventMessage) => boolean
priority?: MessagePriority
} = {},
): string {
// 检查通道权限
if (!this.canAccessChannel(appId, channel)) {
throw new Error(`应用 ${appId} 无权访问频道 ${channel}`)
}
const subscriberId = uuidv4()
const subscriber: EventSubscriber = {
id: subscriberId,
appId,
channel,
handler,
filter: options.filter,
priority: options.priority || MessagePriority.NORMAL,
createdAt: new Date(),
active: true,
}
this.subscribers.set(subscriberId, subscriber)
this.updateActiveSubscribersCount()
console.log(`应用 ${appId} 订阅了频道 ${channel}`)
return subscriberId
}
/**
* 取消订阅
*/
unsubscribe(subscriberId: string): boolean {
const result = this.subscribers.delete(subscriberId)
if (result) {
this.updateActiveSubscribersCount()
console.log(`取消订阅: ${subscriberId}`)
}
return result
}
/**
* 发送消息
*/
async sendMessage(
senderId: string,
channel: string,
payload: any,
options: {
receiverId?: string
priority?: MessagePriority
type?: MessageType
expiresIn?: number // 过期时间(毫秒)
maxRetries?: number
} = {},
): Promise<string> {
// 检查发送者权限
if (!this.canAccessChannel(senderId, channel)) {
throw new Error(`应用 ${senderId} 无权向频道 ${channel} 发送消息`)
}
// 检查消息大小
const messageSize = JSON.stringify(payload).length
const channelConfig = this.channels.get(channel)
if (channelConfig && messageSize > channelConfig.maxMessageSize) {
throw new Error(`消息大小超出限制: ${messageSize} > ${channelConfig.maxMessageSize}`)
}
const messageId = uuidv4()
const now = new Date()
const message: EventMessage = {
id: messageId,
type: options.type || MessageType.APPLICATION,
priority: options.priority || MessagePriority.NORMAL,
senderId,
receiverId: options.receiverId,
channel,
payload,
timestamp: now,
expiresAt: options.expiresIn ? new Date(now.getTime() + options.expiresIn) : undefined,
status: MessageStatus.PENDING,
retryCount: 0,
maxRetries: options.maxRetries || 3,
}
// 如果是点对点消息,直接发送
if (options.receiverId) {
await this.deliverMessage(message)
} else {
// 广播消息,加入队列处理
this.addToQueue(message)
}
// 更新统计信息
this.statistics.totalMessagesSent++
if (!options.receiverId) {
this.statistics.totalBroadcasts++
}
const channelUsage = this.statistics.channelUsage.get(channel) || 0
this.statistics.channelUsage.set(channel, channelUsage + 1)
// 记录消息历史
this.recordMessage(message)
console.log(
`[EventCommunication] 消息 ${messageId} 已发送到频道 ${channel}[发送者: ${senderId}]`,
)
return messageId
}
/**
* 广播消息到所有订阅者
*/
async broadcast(
senderId: string,
channel: string,
payload: any,
options: {
priority?: MessagePriority
expiresIn?: number
} = {},
): Promise<string> {
return this.sendMessage(senderId, channel, payload, {
...options,
type: MessageType.BROADCAST,
})
}
/**
* 发送跨应用消息
*/
async sendCrossAppMessage(
senderId: string,
receiverId: string,
payload: any,
options: {
priority?: MessagePriority
expiresIn?: number
} = {},
): Promise<string> {
const channel = 'cross-app'
return this.sendMessage(senderId, channel, payload, {
...options,
receiverId,
type: MessageType.CROSS_APP,
})
}
/**
* 获取消息历史
*/
getMessageHistory(
appId: string,
options: {
channel?: string
limit?: number
since?: Date
} = {},
): EventMessage[] {
const history = this.messageHistory.get(appId) || []
let filtered = history.filter((msg) => msg.senderId === appId || msg.receiverId === appId)
if (options.channel) {
filtered = filtered.filter((msg) => msg.channel === options.channel)
}
if (options.since) {
filtered = filtered.filter((msg) => msg.timestamp >= options.since!)
}
if (options.limit) {
filtered = filtered.slice(-options.limit)
}
return filtered.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
}
/**
* 获取应用的订阅列表
*/
getAppSubscriptions(appId: string): EventSubscriber[] {
return Array.from(this.subscribers.values()).filter((sub) => sub.appId === appId)
}
/**
* 获取频道订阅者数量
*/
getChannelSubscriberCount(channel: string): number {
return Array.from(this.subscribers.values()).filter(
(sub) => sub.channel === channel && sub.active,
).length
}
/**
* 创建通信频道
*/
createChannel(channel: string, config: Omit<CommunicationChannel, 'name'>): boolean {
if (this.channels.has(channel)) {
return false
}
this.channels.set(channel, {
name: channel,
...config,
})
console.log(`创建通信频道: ${channel}`)
return true
}
/**
* 删除通信频道
*/
deleteChannel(channel: string): boolean {
// 移除所有相关订阅
const subscribersToRemove = Array.from(this.subscribers.entries())
.filter(([, sub]) => sub.channel === channel)
.map(([id]) => id)
subscribersToRemove.forEach((id) => this.unsubscribe(id))
// 删除频道
const result = this.channels.delete(channel)
if (result) {
console.log(`删除通信频道: ${channel}`)
}
return result
}
/**
* 获取统计信息
*/
getStatistics(): EventStatistics {
return { ...this.statistics }
}
/**
* 清理过期消息和订阅
*/
cleanup(): void {
const now = new Date()
// 清理过期消息
for (const [appId, messages] of this.messageQueue.entries()) {
const validMessages = messages.filter((msg) => !msg.expiresAt || msg.expiresAt > now)
if (validMessages.length !== messages.length) {
this.messageQueue.set(appId, validMessages)
}
}
// 清理消息历史(保留最近7天)
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
for (const [appId, history] of this.messageHistory.entries()) {
const recentHistory = history.filter((msg) => msg.timestamp > sevenDaysAgo)
this.messageHistory.set(appId, recentHistory)
}
console.log('事件通信服务清理完成')
}
/**
* 销毁服务
*/
destroy(): void {
if (this.processingInterval) {
clearInterval(this.processingInterval)
this.processingInterval = null
}
this.subscribers.clear()
this.messageQueue.clear()
this.messageHistory.clear()
this.channels.clear()
console.log('事件通信服务已销毁')
}
// 私有方法
/**
* 初始化默认频道
*/
private initializeDefaultChannels(): void {
// 系统事件频道
this.createChannel('system', {
description: '系统级事件通信',
restricted: true,
allowedApps: ['system'],
maxMessageSize: 1024 * 10, // 10KB
messageRetention: 24 * 60 * 60 * 1000, // 24小时
})
// 应用间通信频道
this.createChannel('cross-app', {
description: '应用间通信',
restricted: false,
allowedApps: [],
maxMessageSize: 1024 * 100, // 100KB
messageRetention: 7 * 24 * 60 * 60 * 1000, // 7天
})
// 用户交互频道
this.createChannel('user-interaction', {
description: '用户交互事件',
restricted: false,
allowedApps: [],
maxMessageSize: 1024 * 5, // 5KB
messageRetention: 60 * 60 * 1000, // 1小时
})
// 广播频道
this.createChannel('broadcast', {
description: '系统广播',
restricted: true,
allowedApps: ['system'],
maxMessageSize: 1024 * 50, // 50KB
messageRetention: 24 * 60 * 60 * 1000, // 24小时
})
}
/**
* 检查应用是否可以访问频道
*/
private canAccessChannel(appId: string, channel: string): boolean {
const channelConfig = this.channels.get(channel)
if (!channelConfig) {
// 频道不存在,默认允许
return true
}
if (!channelConfig.restricted) {
return true
}
// 系统应用总是有权限
if (appId === 'system') {
return true
}
return channelConfig.allowedApps.includes(appId)
}
/**
* 添加消息到队列
*/
private addToQueue(message: EventMessage): void {
const queueKey = message.receiverId || 'broadcast'
if (!this.messageQueue.has(queueKey)) {
this.messageQueue.set(queueKey, [])
}
const queue = this.messageQueue.get(queueKey)!
// 按优先级插入
const insertIndex = queue.findIndex((msg) => msg.priority < message.priority)
if (insertIndex === -1) {
queue.push(message)
} else {
queue.splice(insertIndex, 0, message)
}
}
/**
* 直接投递消息
*/
private async deliverMessage(message: EventMessage): Promise<void> {
try {
const subscribers = this.getRelevantSubscribers(message)
if (subscribers.length === 0) {
message.status = MessageStatus.FAILED
// 只对非系统频道显示警告信息
if (message.channel !== 'system') {
console.warn(
`[EventCommunication] 没有找到频道 ${message.channel} 的订阅者[消息 ID: ${message.id}]`,
)
}
return
}
// 并行发送给所有订阅者
const deliveryPromises = subscribers.map(async (subscriber) => {
try {
// 应用过滤器
if (subscriber.filter && !subscriber.filter(message)) {
return
}
await subscriber.handler(message)
this.statistics.totalMessagesReceived++
console.log(
`[EventCommunication] 消息 ${message.id} 已投递给订阅者 ${subscriber.id}[频道: ${message.channel}]`,
)
} catch (error) {
console.error(`向订阅者 ${subscriber.id} 发送消息失败:`, error)
throw error
}
})
await Promise.allSettled(deliveryPromises)
message.status = MessageStatus.DELIVERED
} catch (error) {
message.status = MessageStatus.FAILED
this.statistics.failedMessages++
console.error('消息投递失败:', error)
// 重试机制
if (message.retryCount < message.maxRetries) {
message.retryCount++
message.status = MessageStatus.PENDING
setTimeout(() => this.deliverMessage(message), 1000 * message.retryCount)
}
}
}
/**
* 获取相关订阅者
*/
private getRelevantSubscribers(message: EventMessage): EventSubscriber[] {
return Array.from(this.subscribers.values()).filter((subscriber) => {
if (!subscriber.active) return false
if (subscriber.channel !== message.channel) return false
// 点对点消息检查接收者
if (message.receiverId && subscriber.appId !== message.receiverId) {
return false
}
return true
})
}
/**
* 开始消息处理循环
*/
private startMessageProcessing(): void {
this.processingInterval = setInterval(() => {
this.processMessageQueue()
this.cleanupExpiredMessages()
}, 100) // 每100ms处理一次
}
/**
* 处理消息队列
*/
private processMessageQueue(): void {
for (const [queueKey, messages] of this.messageQueue.entries()) {
if (messages.length === 0) continue
// 处理优先级最高的消息
const message = messages.shift()!
// 检查消息是否过期
if (message.expiresAt && message.expiresAt <= new Date()) {
message.status = MessageStatus.EXPIRED
continue
}
this.deliverMessage(message)
}
}
/**
* 清理过期消息
*/
private cleanupExpiredMessages(): void {
const now = new Date()
for (const [queueKey, messages] of this.messageQueue.entries()) {
const validMessages = messages.filter((msg) => !msg.expiresAt || msg.expiresAt > now)
if (validMessages.length !== messages.length) {
this.messageQueue.set(queueKey, validMessages)
}
}
}
/**
* 记录消息历史
*/
private recordMessage(message: EventMessage): void {
// 记录发送者历史
if (!this.messageHistory.has(message.senderId)) {
this.messageHistory.set(message.senderId, [])
}
this.messageHistory.get(message.senderId)!.push(message)
// 记录接收者历史
if (message.receiverId && message.receiverId !== message.senderId) {
if (!this.messageHistory.has(message.receiverId)) {
this.messageHistory.set(message.receiverId, [])
}
this.messageHistory.get(message.receiverId)!.push(message)
}
}
/**
* 更新活跃订阅者数量
*/
private updateActiveSubscribersCount(): void {
this.statistics.activeSubscribers = Array.from(this.subscribers.values()).filter(
(sub) => sub.active,
).length
}
}

View File

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

View File

@@ -1,50 +1,42 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { EventBuilderImpl } from '@/events/impl/EventBuilderImpl' import { EventBuilderImpl } from '@/events/impl/EventBuilderImpl'
import type { IEventBuilder } from '@/events/IEventBuilder' import type {
IEventBuilder,
ISystemBuiltInEventMap,
IWindowFormDataUpdateParams
} from '@/events/IEventBuilder'
import type { ResourceType } from './ResourceService'
// 导入所有服务 // 导入所有服务
import { WindowService } from './WindowService' import { WindowFormService } from './windowForm/WindowFormService.ts'
import { ResourceService } from './ResourceService' import { ResourceService } from './ResourceService'
import { EventCommunicationService } from './EventCommunicationService'
import { ApplicationSandboxEngine } from './ApplicationSandboxEngine' import { ApplicationSandboxEngine } from './ApplicationSandboxEngine'
import { ApplicationLifecycleManager } from './ApplicationLifecycleManager' import { ApplicationLifecycleManager } from './ApplicationLifecycleManager'
import { externalAppDiscovery } from './ExternalAppDiscovery' import { externalAppDiscovery } from './ExternalAppDiscovery'
import type { IWindowFormDataUpdateParams } from '@/events/WindowFormEventManager'
/** /**
* 系统服务配置接口 * 系统服务配置接口
*/ */
export interface SystemServiceConfig { export interface SystemServiceConfig {
debug?: boolean debug?: boolean // 是否开启调试模式
maxMemoryUsage?: number autoCleanup?: boolean // 是否自动清理
maxCpuUsage?: number cleanupInterval?: number // 自动清理间隔
enablePerformanceMonitoring?: boolean
enableSecurityAudit?: boolean
autoCleanup?: boolean
cleanupInterval?: number
} }
/** /**
* 系统状态接口 * 系统状态接口
*/ */
export interface SystemStatus { export interface SystemStatus {
initialized: boolean initialized: boolean // 系统是否初始化完成
running: boolean running: boolean // 系统是否运行中
servicesStatus: { servicesStatus: {
windowService: boolean windowFormService: boolean // 窗体服务是否启动
resourceService: boolean resourceService: boolean // 资源服务是否启动
eventService: boolean sandboxEngine: boolean // 沙箱引擎是否启动
sandboxEngine: boolean lifecycleManager: boolean // 生命周期管理器是否启动
lifecycleManager: boolean
} }
performance: { uptime: number // 系统运行时间
memoryUsage: number lastError?: string // 最后一次错误
cpuUsage: number
activeApps: number
activeWindows: number
}
uptime: number
lastError?: string
} }
/** /**
@@ -68,10 +60,9 @@ export class SystemServiceIntegration {
private startTime: Date private startTime: Date
// 核心服务实例 // 核心服务实例
private eventBus: IEventBuilder<any> private eventBus: IEventBuilder<ISystemBuiltInEventMap>
private windowService!: WindowService private windowFormService!: WindowFormService
private resourceService!: ResourceService private resourceService!: ResourceService
private eventService!: EventCommunicationService
private sandboxEngine!: ApplicationSandboxEngine private sandboxEngine!: ApplicationSandboxEngine
private lifecycleManager!: ApplicationLifecycleManager private lifecycleManager!: ApplicationLifecycleManager
@@ -80,19 +71,12 @@ export class SystemServiceIntegration {
initialized: false, initialized: false,
running: false, running: false,
servicesStatus: { servicesStatus: {
windowService: false, windowFormService: false,
resourceService: false, resourceService: false,
eventService: false,
sandboxEngine: false, sandboxEngine: false,
lifecycleManager: false, lifecycleManager: false
}, },
performance: { uptime: 0
memoryUsage: 0,
cpuUsage: 0,
activeApps: 0,
activeWindows: 0,
},
uptime: 0,
}) })
// 性能监控 // 性能监控
@@ -102,17 +86,13 @@ export class SystemServiceIntegration {
constructor(config: SystemServiceConfig = {}) { constructor(config: SystemServiceConfig = {}) {
this.config = { this.config = {
debug: false, debug: false,
maxMemoryUsage: 1024, // 1GB
maxCpuUsage: 80, // 80%
enablePerformanceMonitoring: true,
enableSecurityAudit: true,
autoCleanup: true, autoCleanup: true,
cleanupInterval: 5 * 60 * 1000, // 5分钟 cleanupInterval: 5 * 60 * 1000, // 5分钟
...config, ...config
} }
this.startTime = new Date() this.startTime = new Date()
this.eventBus = new EventBuilderImpl() this.eventBus = new EventBuilderImpl<ISystemBuiltInEventMap>()
this.setupGlobalErrorHandling() this.setupGlobalErrorHandling()
} }
@@ -120,7 +100,7 @@ export class SystemServiceIntegration {
/** /**
* 初始化系统服务 * 初始化系统服务
*/ */
async initialize(): Promise<void> { public async initialize(): Promise<void> {
if (this.initialized.value) { if (this.initialized.value) {
throw new Error('系统服务已初始化') throw new Error('系统服务已初始化')
} }
@@ -137,11 +117,6 @@ export class SystemServiceIntegration {
// 设置SDK消息处理 // 设置SDK消息处理
this.setupSDKMessageHandling() this.setupSDKMessageHandling()
// 启动性能监控
if (this.config.enablePerformanceMonitoring) {
this.startPerformanceMonitoring()
}
// 启动自动清理 // 启动自动清理
if (this.config.autoCleanup) { if (this.config.autoCleanup) {
this.startAutoCleanup() this.startAutoCleanup()
@@ -159,12 +134,6 @@ export class SystemServiceIntegration {
this.systemStatus.running = true this.systemStatus.running = true
console.log('系统服务初始化完成') console.log('系统服务初始化完成')
// 发送系统就绪事件
this.eventService.sendMessage('system', 'system-ready', {
timestamp: new Date(),
services: Object.keys(this.systemStatus.servicesStatus),
})
} catch (error) { } catch (error) {
console.error('系统服务初始化失败:', error) console.error('系统服务初始化失败:', error)
this.systemStatus.lastError = error instanceof Error ? error.message : String(error) this.systemStatus.lastError = error instanceof Error ? error.message : String(error)
@@ -176,16 +145,15 @@ export class SystemServiceIntegration {
* 获取系统状态 * 获取系统状态
*/ */
getSystemStatus(): SystemStatus { getSystemStatus(): SystemStatus {
this.updateSystemStatus()
return { ...this.systemStatus } return { ...this.systemStatus }
} }
/** /**
* 获取窗体服务 * 获取窗体服务
*/ */
getWindowService(): WindowService { getWindowFormService(): WindowFormService {
this.checkInitialized() this.checkInitialized()
return this.windowService return this.windowFormService
} }
/** /**
@@ -196,14 +164,6 @@ export class SystemServiceIntegration {
return this.resourceService return this.resourceService
} }
/**
* 获取事件服务
*/
getEventService(): EventCommunicationService {
this.checkInitialized()
return this.eventService
}
/** /**
* 获取沙箱引擎 * 获取沙箱引擎
*/ */
@@ -236,7 +196,7 @@ export class SystemServiceIntegration {
return { return {
success: true, success: true,
data: result, data: result,
requestId, requestId
} }
} catch (error) { } catch (error) {
console.error('SDK调用失败:', error) console.error('SDK调用失败:', error)
@@ -244,7 +204,7 @@ export class SystemServiceIntegration {
return { return {
success: false, success: false,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
requestId, requestId
} }
} }
} }
@@ -265,7 +225,7 @@ export class SystemServiceIntegration {
/** /**
* 关闭系统服务 * 关闭系统服务
*/ */
async shutdown(): Promise<void> { public async shutdown(): Promise<void> {
console.log('关闭系统服务...') console.log('关闭系统服务...')
this.running.value = false this.running.value = false
@@ -299,15 +259,11 @@ export class SystemServiceIntegration {
this.sandboxEngine.destroy() this.sandboxEngine.destroy()
} }
if (this.eventService) { if (this.windowFormService) {
this.eventService.destroy()
}
if (this.windowService) {
// 关闭所有窗体 // 关闭所有窗体
const windows = this.windowService.getAllWindows() const windows = this.windowFormService.getAllWindows()
for (const window of windows) { for (const window of windows) {
await this.windowService.destroyWindow(window.id) await this.windowFormService.destroyWindow(window.id)
} }
} }
} catch (error) { } catch (error) {
@@ -320,8 +276,6 @@ export class SystemServiceIntegration {
console.log('系统服务已关闭') console.log('系统服务已关闭')
} }
// 私有方法
/** /**
* 初始化所有服务 * 初始化所有服务
*/ */
@@ -331,28 +285,22 @@ export class SystemServiceIntegration {
this.resourceService = new ResourceService(this.eventBus) this.resourceService = new ResourceService(this.eventBus)
this.systemStatus.servicesStatus.resourceService = true this.systemStatus.servicesStatus.resourceService = true
// 2. 初始化事件通信服务
console.log('初始化事件通信服务...')
this.eventService = new EventCommunicationService(this.eventBus)
this.systemStatus.servicesStatus.eventService = true
// 3. 初始化窗体服务 // 3. 初始化窗体服务
console.log('初始化窗体服务...') console.log('初始化窗体服务...')
this.windowService = new WindowService(this.eventBus) this.windowFormService = new WindowFormService(this.eventBus)
this.systemStatus.servicesStatus.windowService = true this.systemStatus.servicesStatus.windowFormService = true
// 4. 初始化沙箱引擎 // 4. 初始化沙箱引擎
console.log('初始化沙箱引擎...') console.log('初始化沙箱引擎...')
this.sandboxEngine = new ApplicationSandboxEngine(this.resourceService, this.eventService) this.sandboxEngine = new ApplicationSandboxEngine(this.resourceService)
this.systemStatus.servicesStatus.sandboxEngine = true this.systemStatus.servicesStatus.sandboxEngine = true
// 5. 初始化生命周期管理器 // 5. 初始化生命周期管理器
console.log('初始化生命周期管理器...') console.log('初始化生命周期管理器...')
this.lifecycleManager = new ApplicationLifecycleManager( this.lifecycleManager = new ApplicationLifecycleManager(
this.windowService, this.windowFormService,
this.resourceService, this.resourceService,
this.eventService, this.sandboxEngine
this.sandboxEngine,
) )
this.systemStatus.servicesStatus.lifecycleManager = true this.systemStatus.servicesStatus.lifecycleManager = true
} }
@@ -361,34 +309,18 @@ export class SystemServiceIntegration {
* 设置服务间通信 * 设置服务间通信
*/ */
private setupServiceCommunication(): void { private setupServiceCommunication(): void {
// 监听应用生命周期事件 // 监听窗体状态变化(来自 windowFormService 的 onStateChange 事件
this.eventService.subscribe('system', 'app-lifecycle', (message) => { this.eventBus.subscribe(
this.debugLog('[AppLifecycle] 应用生命周期事件:', message.payload) 'onWindowStateChanged',
})
// 监听窗口状态变化事件
this.eventService.subscribe('system', 'window-state-change', (message) => {
this.debugLog('[WindowState] 窗口状态变化消息已处理:', message.payload)
})
// 监听窗体状态变化(来自 WindowService 的 onStateChange 事件)
this.eventBus.addEventListener(
'onStateChange',
(windowId: string, newState: string, oldState: string) => { (windowId: string, newState: string, oldState: string) => {
console.log( console.log(
`[SystemIntegration] 接收到窗体状态变化事件: ${windowId} ${oldState} -> ${newState}`, `[SystemIntegration] 接收到窗体状态变化事件: ${windowId} ${oldState} -> ${newState}`
) )
this.eventService.sendMessage('system', 'window-state-change', { }
windowId,
newState,
oldState,
})
console.log(`[SystemIntegration] 已发送 window-state-change 消息到事件通信服务`)
},
) )
// 监听窗体关闭事件,自动停止对应的应用 // 监听窗体关闭事件,自动停止对应的应用
this.eventBus.addEventListener('onClose', async (windowId: string) => { this.eventBus.subscribe('onWindowClose', async (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体关闭事件: ${windowId}`) console.log(`[SystemIntegration] 接收到窗体关闭事件: ${windowId}`)
// 查找对应的应用 // 查找对应的应用
const runningApps = this.lifecycleManager.getRunningApps() const runningApps = this.lifecycleManager.getRunningApps()
@@ -406,74 +338,30 @@ export class SystemServiceIntegration {
}) })
// 监听窗体数据更新事件 // 监听窗体数据更新事件
this.eventBus.addEventListener( this.eventBus.subscribe('onWindowFormDataUpdate', (data: IWindowFormDataUpdateParams) => {
'onWindowFormDataUpdate',
(data: IWindowFormDataUpdateParams) => {
console.log(`[SystemIntegration] 接收到窗体数据更新事件:`, data) console.log(`[SystemIntegration] 接收到窗体数据更新事件:`, data)
// 只有在有订阅者时才发送消息 })
if (this.eventService.getChannelSubscriberCount('window-form-data-update') > 0) {
this.eventService.sendMessage('system', 'window-form-data-update', data)
console.log(`[SystemIntegration] 已发送 window-form-data-update 消息到事件通信服务`)
} else {
console.log(`[SystemIntegration] 无订阅者,跳过发送 window-form-data-update 消息`)
}
},
)
// 监听窗体调整尺寸开始事件 // 监听窗体调整尺寸开始事件
this.eventBus.addEventListener('onResizeStart', (windowId: string) => { this.eventBus.subscribe('onWindowFormResizeStart', (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸开始事件: ${windowId}`) console.log(`[SystemIntegration] 接收到窗体调整尺寸开始事件: ${windowId}`)
// 只有在有订阅者时才发送消息
if (this.eventService.getChannelSubscriberCount('window-form-resize-start') > 0) {
this.eventService.sendMessage('system', 'window-form-resize-start', { windowId })
} else {
console.log(`[SystemIntegration] 无订阅者,跳过发送 window-form-resize-start 消息`)
}
}) })
// 监听窗体调整尺寸过程中事件 // 监听窗体调整尺寸过程中事件
this.eventBus.addEventListener( this.eventBus.subscribe(
'onResizing', 'onWindowFormResizing',
(windowId: string, width: number, height: number) => { (windowId: string, width: number, height: number) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸过程中事件: ${windowId}`, { console.log(`[SystemIntegration] 接收到窗体调整尺寸过程中事件: ${windowId}`, {
width, width,
height, height
}) })
// 只有在有订阅者时才发送消息
if (this.eventService.getChannelSubscriberCount('window-form-resizing') > 0) {
this.eventService.sendMessage('system', 'window-form-resizing', {
windowId,
width,
height,
})
} else {
console.log(`[SystemIntegration] 无订阅者,跳过发送 window-form-resizing 消息`)
} }
},
) )
// 监听窗体调整尺寸结束事件 // 监听窗体调整尺寸结束事件
this.eventBus.addEventListener('onResizeEnd', (windowId: string) => { this.eventBus.subscribe('onWindowFormResizeEnd', (windowId: string) => {
console.log(`[SystemIntegration] 接收到窗体调整尺寸结束事件: ${windowId}`) console.log(`[SystemIntegration] 接收到窗体调整尺寸结束事件: ${windowId}`)
// 只有在有订阅者时才发送消息
if (this.eventService.getChannelSubscriberCount('window-form-resize-end') > 0) {
this.eventService.sendMessage('system', 'window-form-resize-end', { windowId })
} else {
console.log(`[SystemIntegration] 无订阅者,跳过发送 window-form-resize-end 消息`)
}
}) })
// 监听资源配额超出
this.eventBus.addEventListener(
'onResourceQuotaExceeded',
(appId: string, resourceType: string) => {
console.log(`[SystemIntegration] 接收到资源配额超出事件: ${appId} - ${resourceType}`)
this.eventService.sendMessage('system', 'resource-quota-exceeded', {
appId,
resourceType,
})
},
)
} }
/** /**
@@ -502,9 +390,9 @@ export class SystemServiceIntegration {
iframe.contentWindow?.postMessage( iframe.contentWindow?.postMessage(
{ {
type: 'system:response', type: 'system:response',
...result, ...result
}, },
'*', '*'
) )
} }
} }
@@ -534,7 +422,7 @@ export class SystemServiceIntegration {
try { try {
switch (type) { switch (type) {
case 'sdk:storage:get': case 'sdk:storage:getWindowForm':
result = await this.resourceService.getStorage(appId, key) result = await this.resourceService.getStorage(appId, key)
success = true success = true
break break
@@ -544,7 +432,7 @@ export class SystemServiceIntegration {
success = result === true success = result === true
break break
case 'sdk:storage:remove': case 'sdk:storage:removeWindowForm':
result = await this.resourceService.removeStorage(appId, key) result = await this.resourceService.removeStorage(appId, key)
success = result === true success = result === true
break break
@@ -566,9 +454,9 @@ export class SystemServiceIntegration {
type: 'system:storage-response', type: 'system:storage-response',
requestId, requestId,
result, result,
success, success
}, },
'*', '*'
) )
} }
} }
@@ -620,46 +508,46 @@ export class SystemServiceIntegration {
switch (action) { switch (action) {
case 'setTitle': case 'setTitle':
return this.windowService.setWindowTitle(windowId, data.title) return this.windowFormService.setWindowTitle(windowId, data.title)
case 'resize': case 'resize':
return this.windowService.setWindowSize(windowId, data.width, data.height) return this.windowFormService.setWindowSize(windowId, data.width, data.height)
case 'move': case 'move':
// 实现窗体移动功能 // 实现窗体移动功能
const window = this.windowService.getWindow(windowId); const window = this.windowFormService.getWindow(windowId)
if (window && window.element) { if (window && window.element) {
// 更新窗体位置 // 更新窗体位置
window.config.x = data.x; window.config.x = data.x
window.config.y = data.y; window.config.y = data.y
window.element.style.left = `${data.x}px`; window.element.style.left = `${data.x}px`
window.element.style.top = `${data.y}px`; window.element.style.top = `${data.y}px`
window.element.style.transform = 'none'; // 确保移除transform window.element.style.transform = 'none' // 确保移除transform
return true; return true
} }
return false; return false
case 'minimize': case 'minimize':
return this.windowService.minimizeWindow(windowId) return this.windowFormService.minimizeWindow(windowId)
case 'maximize': case 'maximize':
return this.windowService.maximizeWindow(windowId) return this.windowFormService.maximizeWindow(windowId)
case 'restore': case 'restore':
return this.windowService.restoreWindow(windowId) return this.windowFormService.restoreWindow(windowId)
case 'close': case 'close':
return this.lifecycleManager.stopApp(appId) return this.lifecycleManager.stopApp(appId)
case 'getState': case 'getState':
const windowInfo = this.windowService.getWindow(windowId) const windowInfo = this.windowFormService.getWindow(windowId)
return windowInfo?.state return windowInfo?.state
case 'getSize': case 'getSize':
const windowData = this.windowService.getWindow(windowId) const windowData = this.windowFormService.getWindow(windowId)
return { return {
width: windowData?.config.width, width: windowData?.config.width,
height: windowData?.config.height, height: windowData?.config.height
} }
default: default:
@@ -678,7 +566,7 @@ export class SystemServiceIntegration {
case 'get': case 'get':
return this.resourceService.getStorage(appId, data.key) return this.resourceService.getStorage(appId, data.key)
case 'remove': case 'removeWindowForm':
return this.resourceService.removeStorage(appId, data.key) return this.resourceService.removeStorage(appId, data.key)
case 'clear': case 'clear':
@@ -713,7 +601,7 @@ export class SystemServiceIntegration {
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
headers: {} as Record<string, string>, // 简化headers处理 headers: {} as Record<string, string>, // 简化headers处理
url: response.url, url: response.url
} }
: null : null
@@ -725,7 +613,7 @@ export class SystemServiceIntegration {
return { return {
requestCount: requests.length, requestCount: requests.length,
failureCount: requests.filter((r) => r.status && r.status >= 400).length, failureCount: requests.filter((r) => r.status && r.status >= 400).length,
averageTime: 0, // 需要实现时间统计 averageTime: 0 // 需要实现时间统计
} }
default: default:
@@ -737,35 +625,7 @@ export class SystemServiceIntegration {
* 执行事件相关方法 * 执行事件相关方法
*/ */
private async executeEventMethod(action: string, data: any, appId: string): Promise<any> { private async executeEventMethod(action: string, data: any, appId: string): Promise<any> {
switch (action) { throw new Error('事件服务已被移除')
case 'emit':
return this.eventService.sendMessage(appId, data.channel, data.data)
case 'on':
return this.eventService.subscribe(appId, data.channel, (message) => {
// 发送事件到应用
const app = this.lifecycleManager.getApp(appId)
if (app?.sandboxId) {
this.sandboxEngine.sendMessage(app.sandboxId, {
type: 'system:event',
subscriptionId: data.subscriptionId,
message,
})
}
})
case 'off':
return this.eventService.unsubscribe(data.subscriptionId)
case 'broadcast':
return this.eventService.broadcast(appId, data.channel, data.data)
case 'sendTo':
return this.eventService.sendCrossAppMessage(appId, data.targetAppId, data.data)
default:
throw new Error(`未知的事件操作: ${action}`)
}
} }
/** /**
@@ -799,10 +659,10 @@ export class SystemServiceIntegration {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
screenResolution: { screenResolution: {
width: screen.width, width: screen.width,
height: screen.height, height: screen.height
}, },
colorDepth: screen.colorDepth, colorDepth: screen.colorDepth,
pixelRatio: window.devicePixelRatio, pixelRatio: window.devicePixelRatio
} }
case 'getAppInfo': case 'getAppInfo':
@@ -814,7 +674,7 @@ export class SystemServiceIntegration {
version: app.version, version: app.version,
permissions: app.manifest.permissions, permissions: app.manifest.permissions,
createdAt: app.installedAt, createdAt: app.installedAt,
lastActiveAt: app.lastActiveAt, lastActiveAt: app.lastActiveAt
} }
: null : null
@@ -850,41 +710,20 @@ export class SystemServiceIntegration {
return null return null
} }
/**
* 开始性能监控
*/
private startPerformanceMonitoring(): void {
this.performanceInterval = setInterval(() => {
this.updateSystemStatus()
// 检查性能阈值
if (this.systemStatus.performance.memoryUsage > this.config.maxMemoryUsage!) {
this.eventService.sendMessage('system', 'performance-alert', {
type: 'memory',
usage: this.systemStatus.performance.memoryUsage,
limit: this.config.maxMemoryUsage,
})
}
if (this.systemStatus.performance.cpuUsage > this.config.maxCpuUsage!) {
this.eventService.sendMessage('system', 'performance-alert', {
type: 'cpu',
usage: this.systemStatus.performance.cpuUsage,
limit: this.config.maxCpuUsage,
})
}
}, 10000) // 每10秒检查一次
}
/** /**
* 开始自动清理 * 开始自动清理
*/ */
private startAutoCleanup(): void { private startAutoCleanup(): void {
if (!this.config.cleanupInterval) return
this.cleanupInterval = setInterval(() => { this.cleanupInterval = setInterval(() => {
this.debugLog('执行自动清理...') this.debugLog('执行自动清理...')
// 清理事件服务 // 移除资源服务清理(方法不存在)
this.eventService.cleanup() // this.resourceService.cleanup()
// 移除事件服务清理
// this.eventService.cleanup()
// 清理沙箱引擎缓存 // 清理沙箱引擎缓存
// this.sandboxEngine.cleanup() // this.sandboxEngine.cleanup()
@@ -893,20 +732,6 @@ export class SystemServiceIntegration {
}, this.config.cleanupInterval!) }, this.config.cleanupInterval!)
} }
/**
* 更新系统状态
*/
private updateSystemStatus(): void {
this.systemStatus.uptime = Date.now() - this.startTime.getTime()
this.systemStatus.performance.activeApps = this.lifecycleManager?.getRunningApps().length || 0
this.systemStatus.performance.activeWindows = this.windowService?.getAllWindows().length || 0
// 简化的内存和CPU使用率计算
this.systemStatus.performance.memoryUsage =
(performance as any).memory?.usedJSHeapSize / 1024 / 1024 || 0
this.systemStatus.performance.cpuUsage = Math.random() * 20 // 模拟CPU使用率
}
/** /**
* 检查是否已初始化 * 检查是否已初始化
*/ */
@@ -926,7 +751,7 @@ export class SystemServiceIntegration {
}) })
window.addEventListener('unhandledrejection', (event) => { window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason) // console.error('未处理的Promise拒绝:', event.reason)
this.systemStatus.lastError = event.reason?.message || '未处理的Promise拒绝' this.systemStatus.lastError = event.reason?.message || '未处理的Promise拒绝'
}) })
} }

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,119 @@
import type { IEventBuilder } from '@/events/IEventBuilder'
import type { IWindowFormEvents, IWindowFormInstance } from './WindowFormDataManager.ts'
import { safeSubscribe } from '@/services/windowForm/utils.ts'
import '@/ui/webComponents/WindowFormElement.ts'
/**
* WindowFormRenderer
* ----------------------
* 负责窗口的DOM创建、销毁与视觉状态更新。
*/
export class WindowFormRenderer {
private wfEventBus: IEventBuilder<IWindowFormEvents>
constructor(wfEventBus: IEventBuilder<IWindowFormEvents>) {
this.wfEventBus = wfEventBus
}
/** 创建窗口DOM结构 */
async createWindowElement(win: IWindowFormInstance) {
const el = document.createElement('window-form-element')
el.wfData = win
win.element = el
document.body.appendChild(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

@@ -1,7 +1,8 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder' import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder.ts'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import type { ResizeDirection, ResizeState } from '@/ui/types/WindowFormTypes' import type { ResizeDirection, ResizeState } from '@/ui/types/WindowFormTypes.ts'
import { appRegistry } from '@/apps/AppRegistry.ts'
/** /**
* *
@@ -14,20 +15,17 @@ export enum WindowState {
MAXIMIZED = 'maximized', MAXIMIZED = 'maximized',
CLOSING = 'closing', CLOSING = 'closing',
DESTROYED = 'destroyed', DESTROYED = 'destroyed',
ERROR = 'error', ERROR = 'error'
} }
/** /**
* *
*/ */
/** export interface IWindowConfig {
*
*/
export interface WindowConfig {
/** /**
* *
*/ */
title: string title?: string
/** /**
* () * ()
*/ */
@@ -93,10 +91,7 @@ export interface WindowConfig {
/** /**
* *
*/ */
/** export interface WindowFormInstance {
*
*/
export interface WindowInstance {
/** /**
* *
*/ */
@@ -108,7 +103,7 @@ export interface WindowInstance {
/** /**
* *
*/ */
config: WindowConfig config: IWindowConfig
/** /**
* *
*/ */
@@ -152,13 +147,21 @@ export interface WindowEvents extends IEventMap {
onResizeStart: (windowId: string) => void onResizeStart: (windowId: string) => void
onResizing: (windowId: string, width: number, height: number) => void onResizing: (windowId: string, width: number, height: number) => void
onResizeEnd: (windowId: string) => void onResizeEnd: (windowId: string) => void
onWindowFormDataUpdate: (data: {
id: string
state: WindowState
width: number
height: number
x: number
y: number
}) => void
} }
/** /**
* *
*/ */
export class WindowService { export class WindowFormService {
private windows = reactive(new Map<string, WindowInstance>()) private windowsForm = reactive(new Map<string, WindowFormInstance>())
private activeWindowId = ref<string | null>(null) private activeWindowId = ref<string | null>(null)
private nextZIndex = 1000 private nextZIndex = 1000
private eventBus: IEventBuilder<WindowEvents> private eventBus: IEventBuilder<WindowEvents>
@@ -171,21 +174,21 @@ export class WindowService {
/** /**
* *
*/ */
async createWindow(appId: string, config: WindowConfig): Promise<WindowInstance> { async createWindow(appId: string, config: IWindowConfig): Promise<WindowFormInstance> {
const windowId = uuidv4() const windowId = uuidv4()
const now = new Date() const now = new Date()
const windowInstance: WindowInstance = { const windowInstance: WindowFormInstance = {
id: windowId, id: windowId,
appId, appId,
config, config,
state: WindowState.CREATING, state: WindowState.CREATING,
zIndex: this.nextZIndex++, zIndex: this.nextZIndex++,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now
} }
this.windows.set(windowId, windowInstance) this.windowsForm.set(windowId, windowInstance)
try { try {
// 创建窗体DOM元素 // 创建窗体DOM元素
@@ -212,7 +215,7 @@ export class WindowService {
* *
*/ */
async destroyWindow(windowId: string): Promise<boolean> { async destroyWindow(windowId: string): Promise<boolean> {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return false if (!window) return false
try { try {
@@ -222,21 +225,24 @@ export class WindowService {
if (window.element) { if (window.element) {
window.element.remove() window.element.remove()
} }
if (window.iframe) {
window.iframe.remove()
}
// 从集合中移除 // 从集合中移除
this.windows.delete(windowId) this.windowsForm.delete(windowId)
// 更新活跃窗体 // 更新活跃窗体
if (this.activeWindowId.value === windowId) { if (this.activeWindowId.value === windowId) {
this.activeWindowId.value = null this.activeWindowId.value = null
// 激活最后一个窗体 // 激活最后一个窗体
const lastWindow = Array.from(this.windows.values()).pop() const lastWindow = Array.from(this.windowsForm.values()).pop()
if (lastWindow) { if (lastWindow) {
this.setActiveWindow(lastWindow.id) this.setActiveWindow(lastWindow.id)
} }
} }
this.eventBus.notifyEvent('onClose', windowId) this.eventBus.notify('onClose', windowId)
return true return true
} catch (error) { } catch (error) {
console.error('销毁窗体失败:', error) console.error('销毁窗体失败:', error)
@@ -248,7 +254,7 @@ export class WindowService {
* *
*/ */
minimizeWindow(windowId: string): boolean { minimizeWindow(windowId: string): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window || window.state === WindowState.MINIMIZED) return false if (!window || window.state === WindowState.MINIMIZED) return false
this.updateWindowState(windowId, WindowState.MINIMIZED) this.updateWindowState(windowId, WindowState.MINIMIZED)
@@ -267,7 +273,7 @@ export class WindowService {
* *
*/ */
maximizeWindow(windowId: string): boolean { maximizeWindow(windowId: string): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window || window.state === WindowState.MAXIMIZED) return false if (!window || window.state === WindowState.MAXIMIZED) return false
const oldState = window.state const oldState = window.state
@@ -304,7 +310,7 @@ export class WindowService {
* *
*/ */
restoreWindow(windowId: string): boolean { restoreWindow(windowId: string): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return false if (!window) return false
const targetState = const targetState =
@@ -335,8 +341,8 @@ export class WindowService {
}) })
// 更新配置中的位置 // 更新配置中的位置
if (originalX) window.config.x = parseFloat(originalX); if (originalX) window.config.x = parseFloat(originalX)
if (originalY) window.config.y = parseFloat(originalY); if (originalY) window.config.y = parseFloat(originalY)
} }
} }
@@ -352,7 +358,7 @@ export class WindowService {
* *
*/ */
setWindowTitle(windowId: string, title: string): boolean { setWindowTitle(windowId: string, title: string): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return false if (!window) return false
window.config.title = title window.config.title = title
@@ -376,12 +382,16 @@ export class WindowService {
* *
*/ */
setWindowSize(windowId: string, width: number, height: number): boolean { setWindowSize(windowId: string, width: number, height: number): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return false if (!window) return false
// 检查尺寸限制 // 检查尺寸限制
const finalWidth = this.clampDimension(width, window.config.minWidth, window.config.maxWidth) const finalWidth = this.clampDimension(width, window.config.minWidth, window.config.maxWidth)
const finalHeight = this.clampDimension(height, window.config.minHeight, window.config.maxHeight) const finalHeight = this.clampDimension(
height,
window.config.minHeight,
window.config.maxHeight
)
window.config.width = finalWidth window.config.width = finalWidth
window.config.height = finalHeight window.config.height = finalHeight
@@ -392,7 +402,7 @@ export class WindowService {
window.element.style.height = `${finalHeight}px` window.element.style.height = `${finalHeight}px`
} }
this.eventBus.notifyEvent('onResize', windowId, finalWidth, finalHeight) this.eventBus.notify('onResize', windowId, finalWidth, finalHeight)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId) this.notifyWindowFormDataUpdate(windowId)
@@ -403,15 +413,15 @@ export class WindowService {
/** /**
* *
*/ */
getWindow(windowId: string): WindowInstance | undefined { getWindow(windowId: string): WindowFormInstance | undefined {
return this.windows.get(windowId) return this.windowsForm.get(windowId)
} }
/** /**
* *
*/ */
getAllWindows(): WindowInstance[] { getAllWindows(): WindowFormInstance[] {
return Array.from(this.windows.values()) return Array.from(this.windowsForm.values())
} }
/** /**
@@ -425,7 +435,7 @@ export class WindowService {
* *
*/ */
setActiveWindow(windowId: string): boolean { setActiveWindow(windowId: string): boolean {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return false if (!window) return false
this.activeWindowId.value = windowId this.activeWindowId.value = windowId
@@ -435,7 +445,7 @@ export class WindowService {
window.element.style.zIndex = window.zIndex.toString() window.element.style.zIndex = window.zIndex.toString()
} }
this.eventBus.notifyEvent('onFocus', windowId) this.eventBus.notify('onFocus', windowId)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId) this.notifyWindowFormDataUpdate(windowId)
@@ -446,39 +456,29 @@ export class WindowService {
/** /**
* DOM元素 * DOM元素
*/ */
private async createWindowElement(windowInstance: WindowInstance): Promise<void> { private async createWindowElement(windowInstance: WindowFormInstance): Promise<void> {
const { id, config, appId } = windowInstance const { id, config, appId } = windowInstance
// 检查是否为内置应用
let isBuiltInApp = false
try {
const { AppRegistry } = await import('../apps/AppRegistry')
const appRegistry = AppRegistry.getInstance()
isBuiltInApp = appRegistry.hasApp(appId)
} catch (error) {
console.warn('无法导入 AppRegistry')
}
// 创建窗体容器 // 创建窗体容器
const windowElement = document.createElement('div') const windowElement = document.createElement('div')
windowElement.className = 'system-window' windowElement.className = 'system-window'
windowElement.id = `window-${id}` windowElement.id = `window-${id}`
// 计算初始位置 // 计算初始位置
let left = config.x; let left = config.x
let top = config.y; let top = config.y
// 如果没有指定位置,则居中显示 // 如果没有指定位置,则居中显示
if (left === undefined || top === undefined) { if (left === undefined || top === undefined) {
const centerX = Math.max(0, (window.innerWidth - config.width) / 2); const centerX = Math.max(0, (window.innerWidth - config.width) / 2)
const centerY = Math.max(0, (window.innerHeight - config.height) / 2); const centerY = Math.max(0, (window.innerHeight - config.height) / 2)
left = left !== undefined ? left : centerX; left = left !== undefined ? left : centerX
top = top !== undefined ? top : centerY; top = top !== undefined ? top : centerY
} }
// 设置基本样式 // 设置基本样式
Object.assign(windowElement.style, { Object.assign(windowElement.style, {
position: 'fixed', position: 'absolute',
width: `${config.width}px`, width: `${config.width}px`,
height: `${config.height}px`, height: `${config.height}px`,
left: `${left}px`, left: `${left}px`,
@@ -488,12 +488,12 @@ export class WindowService {
border: '1px solid #ccc', border: '1px solid #ccc',
borderRadius: '8px', borderRadius: '8px',
boxShadow: '0 4px 20px rgba(0,0,0,0.15)', boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
overflow: 'hidden', overflow: 'hidden'
}); })
// 保存初始位置到配置中 // 保存初始位置到配置中
windowInstance.config.x = left; windowInstance.config.x = left
windowInstance.config.y = top; windowInstance.config.y = top
// 创建窗体标题栏 // 创建窗体标题栏
const titleBar = this.createTitleBar(windowInstance) const titleBar = this.createTitleBar(windowInstance)
@@ -508,7 +508,8 @@ export class WindowService {
overflow: hidden; overflow: hidden;
` `
if (isBuiltInApp) { // 检查是否为内置应用
if (appRegistry.hasApp(appId)) {
// 内置应用:创建普通 div 容器AppRenderer 组件会在这里渲染内容 // 内置应用:创建普通 div 容器AppRenderer 组件会在这里渲染内容
const appContainer = document.createElement('div') const appContainer = document.createElement('div')
appContainer.className = 'built-in-app-container' appContainer.className = 'built-in-app-container'
@@ -556,7 +557,7 @@ export class WindowService {
/** /**
* *
*/ */
private createTitleBar(windowInstance: WindowInstance): HTMLElement { private createTitleBar(windowInstance: WindowFormInstance): HTMLElement {
const titleBar = document.createElement('div') const titleBar = document.createElement('div')
titleBar.className = 'window-title-bar' titleBar.className = 'window-title-bar'
titleBar.style.cssText = ` titleBar.style.cssText = `
@@ -574,7 +575,7 @@ export class WindowService {
// 窗体标题 // 窗体标题
const title = document.createElement('span') const title = document.createElement('span')
title.className = 'window-title' title.className = 'window-title'
title.textContent = windowInstance.config.title title.textContent = windowInstance.config.title || ''
title.style.cssText = ` title.style.cssText = `
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
@@ -666,7 +667,7 @@ export class WindowService {
/** /**
* *
*/ */
private addDragFunctionality(titleBar: HTMLElement, windowInstance: WindowInstance): void { private addDragFunctionality(titleBar: HTMLElement, windowInstance: WindowFormInstance): void {
let isDragging = false let isDragging = false
let startX = 0 let startX = 0
let startY = 0 let startY = 0
@@ -676,13 +677,13 @@ export class WindowService {
titleBar.addEventListener('mousedown', (e) => { titleBar.addEventListener('mousedown', (e) => {
// 检查是否正在调整尺寸,如果是则不处理拖拽 // 检查是否正在调整尺寸,如果是则不处理拖拽
if (windowInstance.resizeState?.isResizing) { if (windowInstance.resizeState?.isResizing) {
return; return
} }
// 检查是否点击在调整尺寸手柄上,如果是则不处理拖拽 // 检查是否点击在调整尺寸手柄上,如果是则不处理拖拽
const target = e.target as HTMLElement; const target = e.target as HTMLElement
if (target.classList.contains('resize-handle')) { if (target.classList.contains('resize-handle')) {
return; return
} }
if (!windowInstance.element) return if (!windowInstance.element) return
@@ -694,13 +695,16 @@ export class WindowService {
const rect = windowInstance.element.getBoundingClientRect() const rect = windowInstance.element.getBoundingClientRect()
// 如果使用了transform需要转换为实际坐标 // 如果使用了transform需要转换为实际坐标
if (windowInstance.element.style.transform && windowInstance.element.style.transform.includes('translate')) { if (
windowInstance.element.style.transform &&
windowInstance.element.style.transform.includes('translate')
) {
// 移除transform并设置实际的left/top值 // 移除transform并设置实际的left/top值
windowInstance.element.style.transform = 'none'; windowInstance.element.style.transform = 'none'
windowInstance.config.x = rect.left; windowInstance.config.x = rect.left
windowInstance.config.y = rect.top; windowInstance.config.y = rect.top
windowInstance.element.style.left = `${rect.left}px`; windowInstance.element.style.left = `${rect.left}px`
windowInstance.element.style.top = `${rect.top}px`; windowInstance.element.style.top = `${rect.top}px`
} }
startLeft = rect.left startLeft = rect.left
@@ -716,7 +720,7 @@ export class WindowService {
document.addEventListener('mousemove', (e) => { document.addEventListener('mousemove', (e) => {
// 检查是否正在调整尺寸,如果是则不处理拖拽 // 检查是否正在调整尺寸,如果是则不处理拖拽
if (windowInstance.resizeState?.isResizing) { if (windowInstance.resizeState?.isResizing) {
return; return
} }
if (!isDragging || !windowInstance.element) return if (!isDragging || !windowInstance.element) return
@@ -734,7 +738,7 @@ export class WindowService {
windowInstance.config.x = newLeft windowInstance.config.x = newLeft
windowInstance.config.y = newTop windowInstance.config.y = newTop
this.eventBus.notifyEvent('onMove', windowInstance.id, newLeft, newTop) this.eventBus.notify('onMove', windowInstance.id, newLeft, newTop)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowInstance.id) this.notifyWindowFormDataUpdate(windowInstance.id)
@@ -748,7 +752,10 @@ export class WindowService {
/** /**
* *
*/ */
private addResizeFunctionality(windowElement: HTMLElement, windowInstance: WindowInstance): void { private addResizeFunctionality(
windowElement: HTMLElement,
windowInstance: WindowFormInstance
): void {
// 初始化调整尺寸状态 // 初始化调整尺寸状态
windowInstance.resizeState = { windowInstance.resizeState = {
isResizing: false, isResizing: false,
@@ -765,7 +772,7 @@ export class WindowService {
const resizeHandles = this.createResizeHandles(windowElement) const resizeHandles = this.createResizeHandles(windowElement)
// 添加鼠标事件监听器 // 添加鼠标事件监听器
resizeHandles.forEach(handle => { resizeHandles.forEach((handle) => {
this.addResizeHandleEvents(handle, windowElement, windowInstance) this.addResizeHandleEvents(handle, windowElement, windowInstance)
}) })
@@ -787,12 +794,17 @@ export class WindowService {
private createResizeHandles(windowElement: HTMLElement): HTMLElement[] { private createResizeHandles(windowElement: HTMLElement): HTMLElement[] {
const handles: HTMLElement[] = [] const handles: HTMLElement[] = []
const directions: ResizeDirection[] = [ const directions: ResizeDirection[] = [
'topLeft', 'top', 'topRight', 'topLeft',
'right', 'bottomRight', 'bottom', 'top',
'bottomLeft', 'left' 'topRight',
'right',
'bottomRight',
'bottom',
'bottomLeft',
'left'
] ]
directions.forEach(direction => { directions.forEach((direction) => {
const handle = document.createElement('div') const handle = document.createElement('div')
handle.className = `resize-handle resize-handle-${direction}` handle.className = `resize-handle resize-handle-${direction}`
@@ -872,9 +884,12 @@ export class WindowService {
private addResizeHandleEvents( private addResizeHandleEvents(
handle: HTMLElement, handle: HTMLElement,
windowElement: HTMLElement, windowElement: HTMLElement,
windowInstance: WindowInstance windowInstance: WindowFormInstance
): void { ): void {
const direction = handle.className.split(' ').find(cls => cls.startsWith('resize-handle-'))?.split('-')[2] as ResizeDirection const direction = handle.className
.split(' ')
.find((cls) => cls.startsWith('resize-handle-'))
?.split('-')[2] as ResizeDirection
handle.addEventListener('mousedown', (e) => { handle.addEventListener('mousedown', (e) => {
if (!windowInstance.resizeState) return if (!windowInstance.resizeState) return
@@ -884,15 +899,18 @@ export class WindowService {
// 确保窗体位置是最新的 // 确保窗体位置是最新的
if (windowInstance.element) { if (windowInstance.element) {
const rect = windowInstance.element.getBoundingClientRect(); const rect = windowInstance.element.getBoundingClientRect()
// 如果使用了transform需要转换为实际坐标 // 如果使用了transform需要转换为实际坐标
if (windowInstance.element.style.transform && windowInstance.element.style.transform.includes('translate')) { if (
windowInstance.element.style.transform &&
windowInstance.element.style.transform.includes('translate')
) {
// 移除transform并设置实际的left/top值 // 移除transform并设置实际的left/top值
windowInstance.element.style.transform = 'none'; windowInstance.element.style.transform = 'none'
windowInstance.config.x = rect.left; windowInstance.config.x = rect.left
windowInstance.config.y = rect.top; windowInstance.config.y = rect.top
windowInstance.element.style.left = `${rect.left}px`; windowInstance.element.style.left = `${rect.left}px`
windowInstance.element.style.top = `${rect.top}px`; windowInstance.element.style.top = `${rect.top}px`
} }
} }
@@ -910,7 +928,7 @@ export class WindowService {
windowElement.style.opacity = '0.8' windowElement.style.opacity = '0.8'
// 触发开始调整尺寸事件 // 触发开始调整尺寸事件
this.eventBus.notifyEvent('onResizeStart', windowInstance.id) this.eventBus.notify('onResizeStart', windowInstance.id)
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -923,7 +941,7 @@ export class WindowService {
private updateCursorForResize( private updateCursorForResize(
e: MouseEvent, e: MouseEvent,
windowElement: HTMLElement, windowElement: HTMLElement,
windowInstance: WindowInstance windowInstance: WindowFormInstance
): void { ): void {
if (!windowInstance.resizeState) return if (!windowInstance.resizeState) return
@@ -942,22 +960,40 @@ export class WindowService {
direction = 'topRight' direction = 'topRight'
} else if (x >= 0 && x < edgeSize && y > rect.height - edgeSize && y <= rect.height) { } else if (x >= 0 && x < edgeSize && y > rect.height - edgeSize && y <= rect.height) {
direction = 'bottomLeft' direction = 'bottomLeft'
} else if (x > rect.width - edgeSize && x <= rect.width && y > rect.height - edgeSize && y <= rect.height) { } else if (
x > rect.width - edgeSize &&
x <= rect.width &&
y > rect.height - edgeSize &&
y <= rect.height
) {
direction = 'bottomRight' direction = 'bottomRight'
} }
// 然后检查边缘区域 // 然后检查边缘区域
else if (x >= 0 && x < edgeSize && y >= edgeSize && y <= rect.height - edgeSize) { else if (x >= 0 && x < edgeSize && y >= edgeSize && y <= rect.height - edgeSize) {
direction = 'left' direction = 'left'
} else if (x > rect.width - edgeSize && x <= rect.width && y >= edgeSize && y <= rect.height - edgeSize) { } else if (
x > rect.width - edgeSize &&
x <= rect.width &&
y >= edgeSize &&
y <= rect.height - edgeSize
) {
direction = 'right' direction = 'right'
} else if (y >= 0 && y < edgeSize && x >= edgeSize && x <= rect.width - edgeSize) { } else if (y >= 0 && y < edgeSize && x >= edgeSize && x <= rect.width - edgeSize) {
direction = 'top' direction = 'top'
} else if (y > rect.height - edgeSize && y <= rect.height && x >= edgeSize && x <= rect.width - edgeSize) { } else if (
y > rect.height - edgeSize &&
y <= rect.height &&
x >= edgeSize &&
x <= rect.width - edgeSize
) {
direction = 'bottom' direction = 'bottom'
} }
// 更新光标样式 // 更新光标样式
windowElement.style.cursor = direction === 'none' ? 'default' : `${direction.replace(/([A-Z])/g, '-$1').toLowerCase()}-resize` windowElement.style.cursor =
direction === 'none'
? 'default'
: `${direction.replace(/([A-Z])/g, '-$1').toLowerCase()}-resize`
} }
/** /**
@@ -980,22 +1016,15 @@ export class WindowService {
*/ */
private handleResizeMouseMove(e: MouseEvent): void { private handleResizeMouseMove(e: MouseEvent): void {
// 找到正在调整尺寸的窗体 // 找到正在调整尺寸的窗体
const resizingWindow = Array.from(this.windows.values()).find( const resizingWindow = Array.from(this.windowsForm.values()).find(
window => window.resizeState?.isResizing (window) => window.resizeState?.isResizing
) )
// 如果没有正在调整尺寸的窗体,直接返回 // 如果没有正在调整尺寸的窗体,直接返回
if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return
const { const { direction, startX, startY, startWidth, startHeight, startXPosition, startYPosition } =
direction, resizingWindow.resizeState
startX,
startY,
startWidth,
startHeight,
startXPosition,
startYPosition
} = resizingWindow.resizeState
const deltaX = e.clientX - startX const deltaX = e.clientX - startX
const deltaY = e.clientY - startY const deltaY = e.clientY - startY
@@ -1044,8 +1073,16 @@ export class WindowService {
} }
// 应用尺寸限制 // 应用尺寸限制
newWidth = this.clampDimension(newWidth, resizingWindow.config.minWidth, resizingWindow.config.maxWidth) newWidth = this.clampDimension(
newHeight = this.clampDimension(newHeight, resizingWindow.config.minHeight, resizingWindow.config.maxHeight) newWidth,
resizingWindow.config.minWidth,
resizingWindow.config.maxWidth
)
newHeight = this.clampDimension(
newHeight,
resizingWindow.config.minHeight,
resizingWindow.config.maxHeight
)
// 应用新尺寸和位置 // 应用新尺寸和位置
resizingWindow.config.width = newWidth resizingWindow.config.width = newWidth
@@ -1062,7 +1099,7 @@ export class WindowService {
} }
// 触发调整尺寸事件 // 触发调整尺寸事件
this.eventBus.notifyEvent('onResizing', resizingWindow.id, newWidth, newHeight) this.eventBus.notify('onResizing', resizingWindow.id, newWidth, newHeight)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(resizingWindow.id) this.notifyWindowFormDataUpdate(resizingWindow.id)
@@ -1073,8 +1110,8 @@ export class WindowService {
*/ */
private handleResizeMouseUp(): void { private handleResizeMouseUp(): void {
// 找到正在调整尺寸的窗体 // 找到正在调整尺寸的窗体
const resizingWindow = Array.from(this.windows.values()).find( const resizingWindow = Array.from(this.windowsForm.values()).find(
window => window.resizeState?.isResizing (window) => window.resizeState?.isResizing
) )
if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return
@@ -1086,7 +1123,7 @@ export class WindowService {
resizingWindow.element.style.opacity = '1' resizingWindow.element.style.opacity = '1'
// 触发调整尺寸结束事件 // 触发调整尺寸结束事件
this.eventBus.notifyEvent('onResizeEnd', resizingWindow.id) this.eventBus.notify('onResizeEnd', resizingWindow.id)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(resizingWindow.id) this.notifyWindowFormDataUpdate(resizingWindow.id)
@@ -1103,7 +1140,7 @@ export class WindowService {
* *
*/ */
private notifyWindowFormDataUpdate(windowId: string): void { private notifyWindowFormDataUpdate(windowId: string): void {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window || !window.element) return if (!window || !window.element) return
// 获取窗体数据 // 获取窗体数据
@@ -1118,18 +1155,15 @@ export class WindowService {
} }
// 发送事件到事件总线 // 发送事件到事件总线
this.eventBus.notifyEvent('onWindowFormDataUpdate', data) this.eventBus.notify('onWindowFormDataUpdate', data)
} }
/** /**
* *
*/ */
private async loadApplication(windowInstance: WindowInstance): Promise<void> { private async loadApplication(windowInstance: WindowFormInstance): Promise<void> {
// 动态导入 AppRegistry 检查是否为内置应用 // 动态导入 AppRegistry 检查是否为内置应用
try { try {
const { AppRegistry } = await import('../apps/AppRegistry')
const appRegistry = AppRegistry.getInstance()
// 如果是内置应用,直接返回,不需要等待 // 如果是内置应用,直接返回,不需要等待
if (appRegistry.hasApp(windowInstance.appId)) { if (appRegistry.hasApp(windowInstance.appId)) {
console.log(`[WindowService] 内置应用 ${windowInstance.appId} 无需等待加载`) console.log(`[WindowService] 内置应用 ${windowInstance.appId} 无需等待加载`)
@@ -1162,7 +1196,7 @@ export class WindowService {
} }
console.log(`[WindowService] 外部应用 ${windowInstance.appId} 加载完成`) console.log(`[WindowService] 外部应用 ${windowInstance.appId} 加载完成`)
resolve() resolve()
}, 200) // 改为200ms即使是外部应用也不需要这么长的时间 }, 200)
}) })
} }
@@ -1170,7 +1204,7 @@ export class WindowService {
* *
*/ */
private updateWindowState(windowId: string, newState: WindowState): void { private updateWindowState(windowId: string, newState: WindowState): void {
const window = this.windows.get(windowId) const window = this.windowsForm.get(windowId)
if (!window) return if (!window) return
const oldState = window.state const oldState = window.state
@@ -1183,7 +1217,7 @@ export class WindowService {
// 所有状态变化都应该触发事件,这是正常的系统行为 // 所有状态变化都应该触发事件,这是正常的系统行为
console.log(`[WindowService] 窗体状态变化: ${windowId} ${oldState} -> ${newState}`) console.log(`[WindowService] 窗体状态变化: ${windowId} ${oldState} -> ${newState}`)
this.eventBus.notifyEvent('onStateChange', windowId, newState, oldState) this.eventBus.notify('onStateChange', windowId, newState, oldState)
// 发送窗体数据更新事件 // 发送窗体数据更新事件
this.notifyWindowFormDataUpdate(windowId) this.notifyWindowFormDataUpdate(windowId)

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())
}

View File

@@ -1,289 +0,0 @@
<template>
<div class="app-renderer" :class="`app-${appId}`">
<!-- 内置Vue应用 - 立即渲染无加载状态 -->
<component
v-if="isBuiltInApp && appComponent"
:is="appComponent"
:key="appId"
/>
<!-- 外部iframe应用 -->
<iframe
v-else-if="!isBuiltInApp && iframeUrl"
:src="iframeUrl"
:sandbox="sandboxAttributes"
class="external-app-iframe"
@load="onIframeLoad"
@error="onIframeError"
/>
<!-- 加载中状态仅用于外部应用 -->
<div v-else-if="!isBuiltInApp && isLoading" class="loading-state">
<div class="loading-spinner"></div>
<p>正在加载应用...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="hasError" class="error-state">
<div class="error-icon"></div>
<h3>应用加载失败</h3>
<p>{{ errorMessage }}</p>
<button @click="retry" class="retry-btn">重试</button>
</div>
<!-- 内置应用未找到的后备显示 -->
<div v-else-if="isBuiltInApp && !appComponent" class="error-state">
<div class="error-icon">📱</div>
<h3>应用不存在</h3>
<p>内置应用 "{{ appId }}" 未找到</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, inject, watch } from 'vue'
import { appRegistry } from '@/apps'
import { externalAppDiscovery } from '@/services/ExternalAppDiscovery'
import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration'
const props = defineProps<{
appId: string
windowId?: string
}>()
const emit = defineEmits<{
loaded: []
error: [error: Error]
}>()
const systemService = inject<SystemServiceIntegration>('systemService')
// 检查是否为内置应用
const isBuiltInApp = computed(() => {
return appRegistry.hasApp(props.appId)
})
// 检查是否为外置应用
const isExternalApp = computed(() => {
return externalAppDiscovery.hasApp(props.appId)
})
// 内置应用组件 - 立即获取,无需异步
const appComponent = computed(() => {
if (isBuiltInApp.value) {
const appRegistration = appRegistry.getApp(props.appId)
return appRegistration?.component
}
return null
})
// 外部应用相关状态
const isLoading = ref(false) // 默认不loading只有外部应用需要
const hasError = ref(false)
const errorMessage = ref('')
const iframeUrl = ref('')
// 沙箱属性(仅用于外部应用)
const sandboxAttributes = computed(() => {
return 'allow-scripts allow-forms allow-popups'
})
// 初始化应用
const initializeApp = async () => {
try {
if (isBuiltInApp.value) {
// 内置应用立即可用,无需异步加载
if (appComponent.value) {
emit('loaded')
} else {
hasError.value = true
errorMessage.value = '内置应用未找到'
emit('error', new Error('内置应用未找到'))
}
} else if (isExternalApp.value) {
// 外置应用需要异步加载
isLoading.value = true
hasError.value = false
errorMessage.value = ''
await loadExternalApp()
} else {
throw new Error(`应用 ${props.appId} 未找到(不是内置也不是外置应用)`)
}
} catch (error) {
console.error('应用初始化失败:', error)
hasError.value = true
errorMessage.value = (error as Error).message
isLoading.value = false
emit('error', error as Error)
}
}
// 加载外部应用
const loadExternalApp = async () => {
try {
console.log(`[AppRenderer] 加载外置应用: ${props.appId}`)
// 直接从外置应用发现服务获取应用信息
const externalApp = externalAppDiscovery.getApp(props.appId)
if (!externalApp) {
throw new Error('外置应用未找到')
}
// 直接使用外置应用的入口路径
iframeUrl.value = externalApp.entryPath
console.log(`[AppRenderer] 外置应用加载路径: ${iframeUrl.value}`)
} catch (error) {
console.error(`[AppRenderer] 外置应用加载失败:`, error)
throw new Error(`外部应用加载失败: ${(error as Error).message}`)
}
}
// iframe加载完成
const onIframeLoad = () => {
isLoading.value = false
emit('loaded')
}
// iframe加载错误
const onIframeError = (event: Event) => {
hasError.value = true
errorMessage.value = '外部应用加载失败'
isLoading.value = false
emit('error', new Error('iframe加载失败'))
}
// 重试加载
const retry = () => {
initializeApp()
}
// 监听内置应用组件的可用性立即发送loaded事件
watch(appComponent, (newComponent) => {
if (isBuiltInApp.value && newComponent) {
emit('loaded')
}
}, { immediate: true })
onMounted(() => {
// 内置应用无需额外初始化,只处理外部应用
if (!isBuiltInApp.value) {
if (isExternalApp.value) {
initializeApp()
} else {
// 应用不存在
hasError.value = true
errorMessage.value = `应用 ${props.appId} 未找到`
emit('error', new Error('应用未找到'))
}
} else if (!appComponent.value) {
// 内置应用不存在
hasError.value = true
errorMessage.value = '内置应用未找到'
emit('error', new Error('内置应用未找到'))
}
})
onUnmounted(() => {
// 清理iframe URL只有当是blob URL时才需要清理
if (iframeUrl.value && iframeUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(iframeUrl.value)
}
})
</script>
<style scoped>
.app-renderer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: white;
position: relative;
}
.external-app-iframe {
width: 100%;
height: 100%;
border: none;
background: white;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #6c757d;
gap: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
padding: 40px;
color: #6c757d;
}
.error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error-state h3 {
margin-bottom: 8px;
color: #dc3545;
}
.error-state p {
margin-bottom: 16px;
font-size: 14px;
}
.retry-btn {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.retry-btn:hover {
background: #0056b3;
}
/* 应用特定样式 */
.app-calculator {
background: #f8f9fa;
}
.app-notepad {
background: white;
}
.app-todo {
background: #f8f9fa;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@@ -1,146 +1,22 @@
<template> <template>
<div class="window-manager"> <div class="window-manager">
<!-- 所有已打开的内置应用窗口 --> <!-- 所有已打开的内置应用窗口 -->
<teleport <teleport v-for="window in builtInWindows?.values()" :key="window.id" :to="`#app-container-${window.appId}`">
v-for="window in builtInWindows" <component :is="window.component" :key="window.id" v-bind="window.props" />
:key="window.id"
:to="`#app-container-${window.appId}`"
>
<component
:is="window.component"
:key="window.id"
v-bind="window.props"
@close="closeWindow(window.id)"
/>
</teleport> </teleport>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, inject, onMounted, onUnmounted } from 'vue' import { inject } from 'vue'
import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration' import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration'
import { appRegistry } from '@/apps'
interface BuiltInWindow {
id: string
appId: string
component: any
props: Record<string, any>
}
// 存储所有已打开的内置应用窗口
const builtInWindows = ref<BuiltInWindow[]>([])
// 注入系统服务 // 注入系统服务
const systemService = inject<SystemServiceIntegration>('systemService') const systemService = inject<SystemServiceIntegration>('systemService')
// 添加内置应用窗口 // 存储所有已打开的内置应用窗口
const addBuiltInWindow = async (windowId: string, appId: string) => { const builtInWindows = systemService?.getLifecycleManager().getBuiltInWindowForms()
// 检查应用是否存在
const appRegistration = appRegistry.getApp(appId)
if (!appRegistration) {
console.error(`内置应用 ${appId} 不存在`)
return
}
// 检查窗口是否已存在
const existingWindow = builtInWindows.value.find(w => w.id === windowId)
if (existingWindow) {
console.warn(`窗口 ${windowId} 已存在`)
return
}
// 处理异步组件加载
let component = appRegistration.component
if (typeof component === 'function') {
try {
// 如果是函数,调用它来获取组件
component = await component()
} catch (error) {
console.error(`加载应用组件失败: ${appId}`, error)
return
}
}
// 添加窗口
const window: BuiltInWindow = {
id: windowId,
appId,
component,
props: {
windowId,
appId
}
}
builtInWindows.value.push(window)
console.log(`[WindowManager] 添加内置应用窗口: ${appId} (${windowId})`)
}
// 关闭内置应用窗口
const closeWindow = (windowId: string) => {
const index = builtInWindows.value.findIndex(w => w.id === windowId)
if (index > -1) {
const window = builtInWindows.value[index]
builtInWindows.value.splice(index, 1)
console.log(`[WindowManager] 关闭内置应用窗口: ${window.appId} (${windowId})`)
// 通知系统服务关闭窗口
if (systemService) {
const windowService = systemService.getWindowService()
windowService.destroyWindow(windowId)
}
}
}
// 移除内置应用窗口(不关闭系统窗口)
const removeBuiltInWindow = (windowId: string) => {
const index = builtInWindows.value.findIndex(w => w.id === windowId)
if (index > -1) {
const window = builtInWindows.value[index]
builtInWindows.value.splice(index, 1)
console.log(`[WindowManager] 移除内置应用窗口: ${window.appId} (${windowId})`)
}
}
// 监听窗口事件
let eventUnsubscriber: (() => void) | null = null
onMounted(() => {
if (systemService) {
const eventService = systemService.getEventService()
// 监听内置应用窗口创建事件
const subscriberId = eventService.subscribe('system', 'built-in-window-created', async (message) => {
const { windowId, appId } = message.payload
await addBuiltInWindow(windowId, appId)
})
// 监听内置应用窗口关闭事件
const closeSubscriberId = eventService.subscribe('system', 'built-in-window-closed', (message) => {
const { windowId } = message.payload
removeBuiltInWindow(windowId)
})
eventUnsubscriber = () => {
eventService.unsubscribe(subscriberId)
eventService.unsubscribe(closeSubscriberId)
}
}
})
onUnmounted(() => {
if (eventUnsubscriber) {
eventUnsubscriber()
}
})
// 暴露给全局使用
defineExpose({
addBuiltInWindow,
removeBuiltInWindow,
closeWindow
})
</script> </script>
<style scoped> <style scoped>

View File

@@ -13,18 +13,6 @@
{{ systemStatus.running ? '正常' : '错误' }} {{ systemStatus.running ? '正常' : '错误' }}
</span> </span>
</div> </div>
<div class="status-item">
<span>活跃应用:</span>
<span>{{ systemStatus.performance.activeApps }}</span>
</div>
<div class="status-item">
<span>活跃窗体:</span>
<span>{{ systemStatus.performance.activeWindows }}</span>
</div>
<div class="status-item">
<span>内存使用:</span>
<span>{{ Math.round(systemStatus.performance.memoryUsage) }}MB</span>
</div>
<button @click="showSystemStatus = false" class="close-btn">关闭</button> <button @click="showSystemStatus = false" class="close-btn">关闭</button>
</div> </div>
</div> </div>
@@ -40,7 +28,7 @@ import { useDynamicAppIcons, getAppIdFromIcon } from '@/ui/desktop-container/use
import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration' import type { SystemServiceIntegration } from '@/services/SystemServiceIntegration'
import type { SystemStatus } from '@/services/SystemServiceIntegration' import type { SystemStatus } from '@/services/SystemServiceIntegration'
import { appRegistry } from '@/apps' import { appRegistry } from '@/apps'
import { externalAppDiscovery } from '@/services/ExternalAppDiscovery' import { externalAppDiscovery } from '@/services/ExternalAppDiscovery.ts'
const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('.desktop-icons-container') const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('.desktop-icons-container')
const { getAppIconsWithPositions, saveIconPositions, refreshApps } = useDynamicAppIcons() const { getAppIconsWithPositions, saveIconPositions, refreshApps } = useDynamicAppIcons()
@@ -62,18 +50,11 @@ const systemStatus = ref<SystemStatus>({
initialized: false, initialized: false,
running: false, running: false,
servicesStatus: { servicesStatus: {
windowService: false, windowFormService: false,
resourceService: false, resourceService: false,
eventService: false,
sandboxEngine: false, sandboxEngine: false,
lifecycleManager: false lifecycleManager: false
}, },
performance: {
memoryUsage: 0,
cpuUsage: 0,
activeApps: 0,
activeWindows: 0
},
uptime: 0 uptime: 0
}) })
@@ -117,52 +98,7 @@ const startApp = async (appId: string) => {
try { try {
const lifecycleManager = systemService.getLifecycleManager() const lifecycleManager = systemService.getLifecycleManager()
// 检查是否为内置应用
if (appRegistry.hasApp(appId)) {
// 内置应用:使用主应用的窗口管理器
const appRegistration = appRegistry.getApp(appId)!
// 检查是否已在运行
if (lifecycleManager.isAppRunning(appId)) {
console.log(`应用 ${appRegistration.manifest.name} 已在运行`)
return
}
// 创建窗口
const windowService = systemService.getWindowService()
const windowConfig = {
title: appRegistration.manifest.name,
width: appRegistration.manifest.window.width,
height: appRegistration.manifest.window.height,
minWidth: appRegistration.manifest.window.minWidth,
minHeight: appRegistration.manifest.window.minHeight,
resizable: appRegistration.manifest.window.resizable !== false
}
const windowInstance = await windowService.createWindow(appId, windowConfig)
// 使用主应用的窗口管理器来渲染内置应用
if (windowManager?.value) {
await windowManager.value.addBuiltInWindow(windowInstance.id, appId)
console.log(`[主应用] 使用窗口管理器渲染内置应用: ${appId}`)
} else {
console.error('窗口管理器未初始化')
}
} else if (externalAppDiscovery.hasApp(appId)) {
// 外置应用直接使用ApplicationLifecycleManager
console.log(`启动外置应用: ${appId}`)
if (!lifecycleManager.isAppRunning(appId)) {
await lifecycleManager.startApp(appId) await lifecycleManager.startApp(appId)
console.log(`外置应用 ${appId} 启动成功`)
} else {
console.log(`外置应用 ${appId} 已在运行`)
}
} else {
console.error(`未知的应用: ${appId}`)
}
} catch (error) { } catch (error) {
console.error('启动应用失败:', error) console.error('启动应用失败:', error)
} }

View File

@@ -1,7 +1,6 @@
import { computed, watch } from 'vue' import { computed, watch } from 'vue'
import type { IDesktopAppIcon } from '@/ui/types/IDesktopAppIcon.ts' import type { IDesktopAppIcon } from '@/ui/types/IDesktopAppIcon.ts'
import { appRegistry } from '@/apps' import { appRegistry } from '@/apps'
import { externalAppDiscovery } from '@/services/ExternalAppDiscovery'
/** /**
* 动态应用图标管理 * 动态应用图标管理
@@ -31,20 +30,20 @@ export function useDynamicAppIcons() {
} }
// 添加外置应用 // 添加外置应用
const externalApps = externalAppDiscovery.getDiscoveredApps() // const externalApps = []
for (const app of externalApps) { // for (const app of externalApps) {
const x = ((position - 1) % 4) + 1 // const x = ((position - 1) % 4) + 1
const y = Math.floor((position - 1) / 4) + 1 // const y = Math.floor((position - 1) / 4) + 1
//
icons.push({ // icons.push({
name: app.manifest.name, // name: app.manifest.name,
icon: app.manifest.icon || '📱', // 默认图标 // icon: app.manifest.icon || '📱', // 默认图标
path: app.manifest.id, // path: app.manifest.id,
x, // x,
y // y
}) // })
position++ // position++
} // }
// 添加系统状态应用 // 添加系统状态应用
icons.push({ icons.push({
@@ -111,12 +110,6 @@ export function useDynamicAppIcons() {
const refreshApps = async () => { const refreshApps = async () => {
try { try {
// 只有在系统服务已启动的情况下才刷新 // 只有在系统服务已启动的情况下才刷新
if (externalAppDiscovery['hasStarted']) {
await externalAppDiscovery.refresh()
console.log('[DynamicAppIcons] 应用列表已刷新')
} else {
console.log('[DynamicAppIcons] 系统服务未启动,跳过刷新')
}
} catch (error) { } catch (error) {
console.error('[DynamicAppIcons] 刷新应用列表失败:', error) console.error('[DynamicAppIcons] 刷新应用列表失败:', error)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
*,
*::before,
*::after {
box-sizing: border-box; /* 使用更直观的盒模型 */
margin: 0;
padding: 0;
}
:host {
position: absolute;
top: 0;
left: 0;
display: block;
z-index: 10;
user-select: none;
--titlebar-height: 32px;
--shadow: 0 10px 30px rgba(0,0,0,0.25);
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
:host([focused]) {
z-index: 11;
.window {
border-color: #8338ec;
}
}
:host([windowFormState='maximized']) {
.window {
border-radius: 0;
box-shadow: none;
}
}
.window {
position: absolute;
box-shadow: var(--shadow, 0 10px 30px rgba(0,0,0,0.25));
background: linear-gradient(#ffffff, #f6f6f6);
border: 1px solid rgba(0,0,0,0.08);
border-radius: 6px;
overflow: hidden;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
&.focus {
border-color: #3a86ff;
}
}
.titlebar {
height: var(--titlebar-height);
display: flex;
align-items: center;
padding: 0 8px;
gap: 8px;
background: linear-gradient(#f2f2f2, #e9e9e9);
border-bottom: 1px solid rgba(0,0,0,0.06);
.title {
font-size: 13px;
font-weight: 600;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #111;
}
.controls {
display: flex; gap: 6px;
button.ctrl {
width: 34px;
height: 24px;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
&:hover {
background: rgba(0,0,0,0.06);
}
}
}
}
.content { flex: 1; overflow: auto; padding: 12px; background: transparent; }
.resizer { position: absolute; z-index: 20; }
.resizer.t { height: 6px; left: 0; right: 0; top: -3px; cursor: ns-resize; }
.resizer.b { height: 6px; left: 0; right: 0; bottom: -3px; cursor: ns-resize; }
.resizer.r { width: 6px; top: 0; bottom: 0; right: -3px; cursor: ew-resize; }
.resizer.l { width: 6px; top: 0; bottom: 0; left: -3px; cursor: ew-resize; }
.resizer.tr { width: 12px; height: 12px; right: -6px; top: -6px; cursor: nesw-resize; }
.resizer.tl { width: 12px; height: 12px; left: -6px; top: -6px; cursor: nwse-resize; }
.resizer.br { width: 12px; height: 12px; right: -6px; bottom: -6px; cursor: nwse-resize; }
.resizer.bl { width: 12px; height: 12px; left: -6px; bottom: -6px; cursor: nesw-resize; }

View File

@@ -13,7 +13,7 @@ export default defineConfig({
vue({ vue({
template: { template: {
compilerOptions: { compilerOptions: {
isCustomElement: tag => tag.endsWith('-element') // 忽略自定义元素 isCustomElement: (tag) => tag.endsWith('-element') // 忽略自定义元素
} }
} }
}), }),
@@ -24,7 +24,7 @@ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)),
'vue': 'vue/dist/vue.esm-bundler.js' vue: 'vue/dist/vue.esm-bundler.js'
} }
}, },
build: { build: {
@@ -33,7 +33,7 @@ export default defineConfig({
// 配置代码分割 // 配置代码分割
manualChunks: { manualChunks: {
// 将Vue相关库打包到单独的chunk中 // 将Vue相关库打包到单独的chunk中
vue: ['vue', 'vue-router', 'pinia'], vue: ['vue', 'pinia'],
// 将UI库打包到单独的chunk中 // 将UI库打包到单独的chunk中
ui: ['naive-ui'], ui: ['naive-ui'],
// 将工具库打包到单独的chunk中 // 将工具库打包到单独的chunk中