1
This commit is contained in:
314
.qoder/quests/window-drag-resize-control.md
Normal file
314
.qoder/quests/window-drag-resize-control.md
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
# 窗体功能增强设计文档
|
||||||
|
|
||||||
|
## 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 用户体验测试
|
||||||
|
|
||||||
|
- 拖拽流畅性测试
|
||||||
|
- 视觉反馈效果测试
|
||||||
|
- 不同设备兼容性测试
|
||||||
@@ -10,49 +10,64 @@ export interface IWindowFormEvent extends IEventMap {
|
|||||||
* 窗口最小化
|
* 窗口最小化
|
||||||
* @param id 窗口id
|
* @param id 窗口id
|
||||||
*/
|
*/
|
||||||
windowFormMinimize: (id: string) => void;
|
windowFormMinimize: (id: string) => void
|
||||||
/**
|
/**
|
||||||
* 窗口最大化
|
* 窗口最大化
|
||||||
* @param id 窗口id
|
* @param id 窗口id
|
||||||
*/
|
*/
|
||||||
windowFormMaximize: (id: string) => void;
|
windowFormMaximize: (id: string) => void
|
||||||
/**
|
/**
|
||||||
* 窗口还原
|
* 窗口还原
|
||||||
* @param id 窗口id
|
* @param id 窗口id
|
||||||
*/
|
*/
|
||||||
windowFormRestore: (id: string) => void;
|
windowFormRestore: (id: string) => void
|
||||||
/**
|
/**
|
||||||
* 窗口关闭
|
* 窗口关闭
|
||||||
* @param id 窗口id
|
* @param id 窗口id
|
||||||
*/
|
*/
|
||||||
windowFormClose: (id: string) => void;
|
windowFormClose: (id: string) => void
|
||||||
/**
|
/**
|
||||||
* 窗口聚焦
|
* 窗口聚焦
|
||||||
* @param id 窗口id
|
* @param id 窗口id
|
||||||
*/
|
*/
|
||||||
windowFormFocus: (id: string) => void;
|
windowFormFocus: (id: string) => void
|
||||||
/**
|
/**
|
||||||
* 窗口数据更新
|
* 窗口数据更新
|
||||||
* @param data 窗口数据
|
* @param data 窗口数据
|
||||||
*/
|
*/
|
||||||
windowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void;
|
windowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void
|
||||||
/**
|
/**
|
||||||
* 窗口创建完成
|
* 窗口创建完成
|
||||||
*/
|
*/
|
||||||
windowFormCreated: () => void;
|
windowFormCreated: () => void
|
||||||
|
/**
|
||||||
|
* 开始调整窗体尺寸
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormResizeStart: (id: string) => void
|
||||||
|
/**
|
||||||
|
* 调整尺寸过程中
|
||||||
|
* @param data 窗口数据
|
||||||
|
*/
|
||||||
|
windowFormResizing: (data: IWindowFormDataUpdateParams) => void
|
||||||
|
/**
|
||||||
|
* 完成窗体尺寸调整
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormResizeEnd: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWindowFormDataUpdateParams {
|
export interface IWindowFormDataUpdateParams {
|
||||||
/** 窗口id */
|
/** 窗口id */
|
||||||
id: string;
|
id: string
|
||||||
/** 窗口状态 */
|
/** 窗口状态 */
|
||||||
state: TWindowFormState,
|
state: TWindowFormState
|
||||||
/** 窗口宽度 */
|
/** 窗口宽度 */
|
||||||
width: number,
|
width: number
|
||||||
/** 窗口高度 */
|
/** 窗口高度 */
|
||||||
height: number,
|
height: number
|
||||||
/** 窗口x坐标(左上角) */
|
/** 窗口x坐标(左上角) */
|
||||||
x: number,
|
x: number
|
||||||
/** 窗口y坐标(左上角) */
|
/** 窗口y坐标(左上角) */
|
||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -508,7 +508,10 @@ export class EventCommunicationService {
|
|||||||
|
|
||||||
if (subscribers.length === 0) {
|
if (subscribers.length === 0) {
|
||||||
message.status = MessageStatus.FAILED
|
message.status = MessageStatus.FAILED
|
||||||
console.warn(`[EventCommunication] 没有找到频道 ${message.channel} 的订阅者[消息 ID: ${message.id}]`)
|
// 只对非系统频道显示警告信息
|
||||||
|
if (message.channel !== 'system') {
|
||||||
|
console.warn(`[EventCommunication] 没有找到频道 ${message.channel} 的订阅者[消息 ID: ${message.id}]`)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统服务配置接口
|
* 系统服务配置接口
|
||||||
@@ -404,6 +405,64 @@ export class SystemServiceIntegration {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听窗体数据更新事件
|
||||||
|
this.eventBus.addEventListener(
|
||||||
|
'onWindowFormDataUpdate',
|
||||||
|
(data: IWindowFormDataUpdateParams) => {
|
||||||
|
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) => {
|
||||||
|
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(
|
||||||
|
'onResizing',
|
||||||
|
(windowId: string, width: number, height: number) => {
|
||||||
|
console.log(`[SystemIntegration] 接收到窗体调整尺寸过程中事件: ${windowId}`, {
|
||||||
|
width,
|
||||||
|
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) => {
|
||||||
|
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(
|
this.eventBus.addEventListener(
|
||||||
'onResourceQuotaExceeded',
|
'onResourceQuotaExceeded',
|
||||||
@@ -567,8 +626,18 @@ export class SystemServiceIntegration {
|
|||||||
return this.windowService.setWindowSize(windowId, data.width, data.height)
|
return this.windowService.setWindowSize(windowId, data.width, data.height)
|
||||||
|
|
||||||
case 'move':
|
case 'move':
|
||||||
// 需要实现窗体移动功能
|
// 实现窗体移动功能
|
||||||
return true
|
const window = this.windowService.getWindow(windowId);
|
||||||
|
if (window && window.element) {
|
||||||
|
// 更新窗体位置
|
||||||
|
window.config.x = data.x;
|
||||||
|
window.config.y = data.y;
|
||||||
|
window.element.style.left = `${data.x}px`;
|
||||||
|
window.element.style.top = `${data.y}px`;
|
||||||
|
window.element.style.transform = 'none'; // 确保移除transform
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
case 'minimize':
|
case 'minimize':
|
||||||
return this.windowService.minimizeWindow(windowId)
|
return this.windowService.minimizeWindow(windowId)
|
||||||
@@ -583,14 +652,14 @@ export class SystemServiceIntegration {
|
|||||||
return this.lifecycleManager.stopApp(appId)
|
return this.lifecycleManager.stopApp(appId)
|
||||||
|
|
||||||
case 'getState':
|
case 'getState':
|
||||||
const window = this.windowService.getWindow(windowId)
|
const windowInfo = this.windowService.getWindow(windowId)
|
||||||
return window?.state
|
return windowInfo?.state
|
||||||
|
|
||||||
case 'getSize':
|
case 'getSize':
|
||||||
const windowInfo = this.windowService.getWindow(windowId)
|
const windowData = this.windowService.getWindow(windowId)
|
||||||
return {
|
return {
|
||||||
width: windowInfo?.config.width,
|
width: windowData?.config.width,
|
||||||
height: windowInfo?.config.height,
|
height: windowData?.config.height,
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -870,4 +939,4 @@ export class SystemServiceIntegration {
|
|||||||
console.log(`[SystemService] ${message}`, data)
|
console.log(`[SystemService] ${message}`, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder'
|
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import type { ResizeDirection, ResizeState } from '@/ui/types/WindowFormTypes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 窗体状态枚举
|
* 窗体状态枚举
|
||||||
@@ -132,6 +133,10 @@ export interface WindowInstance {
|
|||||||
* 窗体更新时间
|
* 窗体更新时间
|
||||||
*/
|
*/
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
|
/**
|
||||||
|
* 拖拽调整尺寸状态
|
||||||
|
*/
|
||||||
|
resizeState?: ResizeState
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,6 +149,9 @@ export interface WindowEvents extends IEventMap {
|
|||||||
onFocus: (windowId: string) => void
|
onFocus: (windowId: string) => void
|
||||||
onBlur: (windowId: string) => void
|
onBlur: (windowId: string) => void
|
||||||
onClose: (windowId: string) => void
|
onClose: (windowId: string) => void
|
||||||
|
onResizeStart: (windowId: string) => void
|
||||||
|
onResizing: (windowId: string, width: number, height: number) => void
|
||||||
|
onResizeEnd: (windowId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,6 +165,7 @@ export class WindowService {
|
|||||||
|
|
||||||
constructor(eventBus: IEventBuilder<WindowEvents>) {
|
constructor(eventBus: IEventBuilder<WindowEvents>) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
|
this.setupGlobalResizeEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,6 +257,9 @@ export class WindowService {
|
|||||||
window.element.style.display = 'none'
|
window.element.style.display = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,10 +288,15 @@ export class WindowService {
|
|||||||
width: '100vw',
|
width: '100vw',
|
||||||
height: 'calc(100vh - 40px)', // 减去任务栏高度
|
height: 'calc(100vh - 40px)', // 减去任务栏高度
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
transform: 'none' // 确保移除transform
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setActiveWindow(windowId)
|
this.setActiveWindow(windowId)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,14 +329,22 @@ export class WindowService {
|
|||||||
Object.assign(window.element.style, {
|
Object.assign(window.element.style, {
|
||||||
width: originalWidth ? `${originalWidth}px` : `${window.config.width}px`,
|
width: originalWidth ? `${originalWidth}px` : `${window.config.width}px`,
|
||||||
height: originalHeight ? `${originalHeight}px` : `${window.config.height}px`,
|
height: originalHeight ? `${originalHeight}px` : `${window.config.height}px`,
|
||||||
left: originalX ? `${originalX}px` : '50%',
|
left: originalX ? `${originalX}px` : '0px',
|
||||||
top: originalY ? `${originalY}px` : '50%',
|
top: originalY ? `${originalY}px` : '0px',
|
||||||
transform: originalX && originalY ? 'none' : 'translate(-50%, -50%)',
|
transform: 'none' // 确保移除transform
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 更新配置中的位置
|
||||||
|
if (originalX) window.config.x = parseFloat(originalX);
|
||||||
|
if (originalY) window.config.y = parseFloat(originalY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setActiveWindow(windowId)
|
this.setActiveWindow(windowId)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +366,9 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,14 +380,8 @@ export class WindowService {
|
|||||||
if (!window) return false
|
if (!window) return false
|
||||||
|
|
||||||
// 检查尺寸限制
|
// 检查尺寸限制
|
||||||
const finalWidth = Math.max(
|
const finalWidth = this.clampDimension(width, window.config.minWidth, window.config.maxWidth)
|
||||||
window.config.minWidth || 200,
|
const finalHeight = this.clampDimension(height, window.config.minHeight, window.config.maxHeight)
|
||||||
Math.min(window.config.maxWidth || Infinity, width),
|
|
||||||
)
|
|
||||||
const finalHeight = Math.max(
|
|
||||||
window.config.minHeight || 150,
|
|
||||||
Math.min(window.config.maxHeight || Infinity, height),
|
|
||||||
)
|
|
||||||
|
|
||||||
window.config.width = finalWidth
|
window.config.width = finalWidth
|
||||||
window.config.height = finalHeight
|
window.config.height = finalHeight
|
||||||
@@ -371,6 +393,10 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.eventBus.notifyEvent('onResize', windowId, finalWidth, finalHeight)
|
this.eventBus.notifyEvent('onResize', windowId, finalWidth, finalHeight)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,6 +436,10 @@ export class WindowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.eventBus.notifyEvent('onFocus', windowId)
|
this.eventBus.notifyEvent('onFocus', windowId)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,21 +464,36 @@ export class WindowService {
|
|||||||
windowElement.className = 'system-window'
|
windowElement.className = 'system-window'
|
||||||
windowElement.id = `window-${id}`
|
windowElement.id = `window-${id}`
|
||||||
|
|
||||||
|
// 计算初始位置
|
||||||
|
let left = config.x;
|
||||||
|
let top = config.y;
|
||||||
|
|
||||||
|
// 如果没有指定位置,则居中显示
|
||||||
|
if (left === undefined || top === undefined) {
|
||||||
|
const centerX = Math.max(0, (window.innerWidth - config.width) / 2);
|
||||||
|
const centerY = Math.max(0, (window.innerHeight - config.height) / 2);
|
||||||
|
left = left !== undefined ? left : centerX;
|
||||||
|
top = top !== undefined ? top : centerY;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置基本样式
|
// 设置基本样式
|
||||||
Object.assign(windowElement.style, {
|
Object.assign(windowElement.style, {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
width: `${config.width}px`,
|
width: `${config.width}px`,
|
||||||
height: `${config.height}px`,
|
height: `${config.height}px`,
|
||||||
left: config.x ? `${config.x}px` : '50%',
|
left: `${left}px`,
|
||||||
top: config.y ? `${config.y}px` : '50%',
|
top: `${top}px`,
|
||||||
transform: config.x && config.y ? 'none' : 'translate(-50%, -50%)',
|
|
||||||
zIndex: windowInstance.zIndex.toString(),
|
zIndex: windowInstance.zIndex.toString(),
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
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.y = top;
|
||||||
|
|
||||||
// 创建窗体标题栏
|
// 创建窗体标题栏
|
||||||
const titleBar = this.createTitleBar(windowInstance)
|
const titleBar = this.createTitleBar(windowInstance)
|
||||||
@@ -496,6 +541,11 @@ export class WindowService {
|
|||||||
|
|
||||||
windowElement.appendChild(contentArea)
|
windowElement.appendChild(contentArea)
|
||||||
|
|
||||||
|
// 添加拖拽调整尺寸功能
|
||||||
|
if (config.resizable !== false) {
|
||||||
|
this.addResizeFunctionality(windowElement, windowInstance)
|
||||||
|
}
|
||||||
|
|
||||||
// 添加到页面
|
// 添加到页面
|
||||||
document.body.appendChild(windowElement)
|
document.body.appendChild(windowElement)
|
||||||
|
|
||||||
@@ -624,6 +674,17 @@ export class WindowService {
|
|||||||
let startTop = 0
|
let startTop = 0
|
||||||
|
|
||||||
titleBar.addEventListener('mousedown', (e) => {
|
titleBar.addEventListener('mousedown', (e) => {
|
||||||
|
// 检查是否正在调整尺寸,如果是则不处理拖拽
|
||||||
|
if (windowInstance.resizeState?.isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否点击在调整尺寸手柄上,如果是则不处理拖拽
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (target.classList.contains('resize-handle')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!windowInstance.element) return
|
if (!windowInstance.element) return
|
||||||
|
|
||||||
isDragging = true
|
isDragging = true
|
||||||
@@ -631,6 +692,17 @@ export class WindowService {
|
|||||||
startY = e.clientY
|
startY = e.clientY
|
||||||
|
|
||||||
const rect = windowInstance.element.getBoundingClientRect()
|
const rect = windowInstance.element.getBoundingClientRect()
|
||||||
|
|
||||||
|
// 如果使用了transform,需要转换为实际坐标
|
||||||
|
if (windowInstance.element.style.transform && windowInstance.element.style.transform.includes('translate')) {
|
||||||
|
// 移除transform并设置实际的left/top值
|
||||||
|
windowInstance.element.style.transform = 'none';
|
||||||
|
windowInstance.config.x = rect.left;
|
||||||
|
windowInstance.config.y = rect.top;
|
||||||
|
windowInstance.element.style.left = `${rect.left}px`;
|
||||||
|
windowInstance.element.style.top = `${rect.top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
startLeft = rect.left
|
startLeft = rect.left
|
||||||
startTop = rect.top
|
startTop = rect.top
|
||||||
|
|
||||||
@@ -638,9 +710,15 @@ export class WindowService {
|
|||||||
this.setActiveWindow(windowInstance.id)
|
this.setActiveWindow(windowInstance.id)
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('mousemove', (e) => {
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
// 检查是否正在调整尺寸,如果是则不处理拖拽
|
||||||
|
if (windowInstance.resizeState?.isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDragging || !windowInstance.element) return
|
if (!isDragging || !windowInstance.element) return
|
||||||
|
|
||||||
const deltaX = e.clientX - startX
|
const deltaX = e.clientX - startX
|
||||||
@@ -651,13 +729,15 @@ export class WindowService {
|
|||||||
|
|
||||||
windowInstance.element.style.left = `${newLeft}px`
|
windowInstance.element.style.left = `${newLeft}px`
|
||||||
windowInstance.element.style.top = `${newTop}px`
|
windowInstance.element.style.top = `${newTop}px`
|
||||||
windowInstance.element.style.transform = 'none'
|
|
||||||
|
|
||||||
// 更新配置
|
// 更新配置
|
||||||
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.notifyEvent('onMove', windowInstance.id, newLeft, newTop)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowInstance.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('mouseup', () => {
|
document.addEventListener('mouseup', () => {
|
||||||
@@ -665,6 +745,382 @@ export class WindowService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加窗体调整尺寸功能
|
||||||
|
*/
|
||||||
|
private addResizeFunctionality(windowElement: HTMLElement, windowInstance: WindowInstance): void {
|
||||||
|
// 初始化调整尺寸状态
|
||||||
|
windowInstance.resizeState = {
|
||||||
|
isResizing: false,
|
||||||
|
direction: 'none',
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
startWidth: 0,
|
||||||
|
startHeight: 0,
|
||||||
|
startXPosition: 0,
|
||||||
|
startYPosition: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建8个调整尺寸的手柄
|
||||||
|
const resizeHandles = this.createResizeHandles(windowElement)
|
||||||
|
|
||||||
|
// 添加鼠标事件监听器
|
||||||
|
resizeHandles.forEach(handle => {
|
||||||
|
this.addResizeHandleEvents(handle, windowElement, windowInstance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加窗口边缘检测
|
||||||
|
windowElement.addEventListener('mousemove', (e) => {
|
||||||
|
if (!windowInstance.resizeState || windowInstance.resizeState.isResizing) return
|
||||||
|
this.updateCursorForResize(e, windowElement, windowInstance)
|
||||||
|
})
|
||||||
|
|
||||||
|
windowElement.addEventListener('mouseleave', () => {
|
||||||
|
if (!windowInstance.resizeState || windowInstance.resizeState.isResizing) return
|
||||||
|
windowElement.style.cursor = 'default'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建调整尺寸的手柄
|
||||||
|
*/
|
||||||
|
private createResizeHandles(windowElement: HTMLElement): HTMLElement[] {
|
||||||
|
const handles: HTMLElement[] = []
|
||||||
|
const directions: ResizeDirection[] = [
|
||||||
|
'topLeft', 'top', 'topRight',
|
||||||
|
'right', 'bottomRight', 'bottom',
|
||||||
|
'bottomLeft', 'left'
|
||||||
|
]
|
||||||
|
|
||||||
|
directions.forEach(direction => {
|
||||||
|
const handle = document.createElement('div')
|
||||||
|
handle.className = `resize-handle resize-handle-${direction}`
|
||||||
|
|
||||||
|
// 设置手柄样式
|
||||||
|
handle.style.position = 'absolute'
|
||||||
|
handle.style.zIndex = '1001'
|
||||||
|
|
||||||
|
// 根据方向设置位置和光标
|
||||||
|
switch (direction) {
|
||||||
|
case 'topLeft':
|
||||||
|
handle.style.top = '-6px'
|
||||||
|
handle.style.left = '-6px'
|
||||||
|
handle.style.cursor = 'nw-resize'
|
||||||
|
break
|
||||||
|
case 'top':
|
||||||
|
handle.style.top = '-4px'
|
||||||
|
handle.style.left = '6px'
|
||||||
|
handle.style.right = '6px'
|
||||||
|
handle.style.cursor = 'n-resize'
|
||||||
|
break
|
||||||
|
case 'topRight':
|
||||||
|
handle.style.top = '-6px'
|
||||||
|
handle.style.right = '-6px'
|
||||||
|
handle.style.cursor = 'ne-resize'
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
handle.style.top = '6px'
|
||||||
|
handle.style.bottom = '6px'
|
||||||
|
handle.style.right = '-4px'
|
||||||
|
handle.style.cursor = 'e-resize'
|
||||||
|
break
|
||||||
|
case 'bottomRight':
|
||||||
|
handle.style.bottom = '-6px'
|
||||||
|
handle.style.right = '-6px'
|
||||||
|
handle.style.cursor = 'se-resize'
|
||||||
|
break
|
||||||
|
case 'bottom':
|
||||||
|
handle.style.bottom = '-4px'
|
||||||
|
handle.style.left = '6px'
|
||||||
|
handle.style.right = '6px'
|
||||||
|
handle.style.cursor = 's-resize'
|
||||||
|
break
|
||||||
|
case 'bottomLeft':
|
||||||
|
handle.style.bottom = '-6px'
|
||||||
|
handle.style.left = '-6px'
|
||||||
|
handle.style.cursor = 'sw-resize'
|
||||||
|
break
|
||||||
|
case 'left':
|
||||||
|
handle.style.top = '6px'
|
||||||
|
handle.style.bottom = '6px'
|
||||||
|
handle.style.left = '-4px'
|
||||||
|
handle.style.cursor = 'w-resize'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置手柄尺寸
|
||||||
|
if (direction === 'top' || direction === 'bottom') {
|
||||||
|
handle.style.height = '8px'
|
||||||
|
} else if (direction === 'left' || direction === 'right') {
|
||||||
|
handle.style.width = '8px'
|
||||||
|
} else {
|
||||||
|
// 对角方向的手柄需要更大的点击区域
|
||||||
|
handle.style.width = '12px'
|
||||||
|
handle.style.height = '12px'
|
||||||
|
}
|
||||||
|
|
||||||
|
windowElement.appendChild(handle)
|
||||||
|
handles.push(handle)
|
||||||
|
})
|
||||||
|
|
||||||
|
return handles
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加调整尺寸手柄的事件监听器
|
||||||
|
*/
|
||||||
|
private addResizeHandleEvents(
|
||||||
|
handle: HTMLElement,
|
||||||
|
windowElement: HTMLElement,
|
||||||
|
windowInstance: WindowInstance
|
||||||
|
): void {
|
||||||
|
const direction = handle.className.split(' ').find(cls => cls.startsWith('resize-handle-'))?.split('-')[2] as ResizeDirection
|
||||||
|
|
||||||
|
handle.addEventListener('mousedown', (e) => {
|
||||||
|
if (!windowInstance.resizeState) return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// 确保窗体位置是最新的
|
||||||
|
if (windowInstance.element) {
|
||||||
|
const rect = windowInstance.element.getBoundingClientRect();
|
||||||
|
// 如果使用了transform,需要转换为实际坐标
|
||||||
|
if (windowInstance.element.style.transform && windowInstance.element.style.transform.includes('translate')) {
|
||||||
|
// 移除transform并设置实际的left/top值
|
||||||
|
windowInstance.element.style.transform = 'none';
|
||||||
|
windowInstance.config.x = rect.left;
|
||||||
|
windowInstance.config.y = rect.top;
|
||||||
|
windowInstance.element.style.left = `${rect.left}px`;
|
||||||
|
windowInstance.element.style.top = `${rect.top}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始调整尺寸
|
||||||
|
windowInstance.resizeState.isResizing = true
|
||||||
|
windowInstance.resizeState.direction = direction
|
||||||
|
windowInstance.resizeState.startX = e.clientX
|
||||||
|
windowInstance.resizeState.startY = e.clientY
|
||||||
|
windowInstance.resizeState.startWidth = windowInstance.config.width
|
||||||
|
windowInstance.resizeState.startHeight = windowInstance.config.height
|
||||||
|
windowInstance.resizeState.startXPosition = windowInstance.config.x || 0
|
||||||
|
windowInstance.resizeState.startYPosition = windowInstance.config.y || 0
|
||||||
|
|
||||||
|
// 添加半透明遮罩效果
|
||||||
|
windowElement.style.opacity = '0.8'
|
||||||
|
|
||||||
|
// 触发开始调整尺寸事件
|
||||||
|
this.eventBus.notifyEvent('onResizeStart', windowInstance.id)
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据鼠标位置更新光标样式
|
||||||
|
*/
|
||||||
|
private updateCursorForResize(
|
||||||
|
e: MouseEvent,
|
||||||
|
windowElement: HTMLElement,
|
||||||
|
windowInstance: WindowInstance
|
||||||
|
): void {
|
||||||
|
if (!windowInstance.resizeState) return
|
||||||
|
|
||||||
|
const rect = windowElement.getBoundingClientRect()
|
||||||
|
const x = e.clientX - rect.left
|
||||||
|
const y = e.clientY - rect.top
|
||||||
|
const edgeSize = 8
|
||||||
|
|
||||||
|
// 检查鼠标位置确定调整方向
|
||||||
|
let direction: ResizeDirection = 'none'
|
||||||
|
|
||||||
|
// 优先检查角落区域,使用更精确的检测
|
||||||
|
if (x >= 0 && x < edgeSize && y >= 0 && y < edgeSize) {
|
||||||
|
direction = 'topLeft'
|
||||||
|
} else if (x > rect.width - edgeSize && x <= rect.width && y >= 0 && y < edgeSize) {
|
||||||
|
direction = 'topRight'
|
||||||
|
} else if (x >= 0 && x < edgeSize && y > rect.height - edgeSize && y <= rect.height) {
|
||||||
|
direction = 'bottomLeft'
|
||||||
|
} else if (x > rect.width - edgeSize && x <= rect.width && y > rect.height - edgeSize && y <= rect.height) {
|
||||||
|
direction = 'bottomRight'
|
||||||
|
}
|
||||||
|
// 然后检查边缘区域
|
||||||
|
else if (x >= 0 && x < edgeSize && y >= edgeSize && y <= rect.height - edgeSize) {
|
||||||
|
direction = 'left'
|
||||||
|
} else if (x > rect.width - edgeSize && x <= rect.width && y >= edgeSize && y <= rect.height - edgeSize) {
|
||||||
|
direction = 'right'
|
||||||
|
} else if (y >= 0 && y < edgeSize && x >= edgeSize && x <= rect.width - edgeSize) {
|
||||||
|
direction = 'top'
|
||||||
|
} else if (y > rect.height - edgeSize && y <= rect.height && x >= edgeSize && x <= rect.width - edgeSize) {
|
||||||
|
direction = 'bottom'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新光标样式
|
||||||
|
windowElement.style.cursor = direction === 'none' ? 'default' : `${direction.replace(/([A-Z])/g, '-$1').toLowerCase()}-resize`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置全局调整尺寸事件监听器
|
||||||
|
*/
|
||||||
|
private setupGlobalResizeEvents(): void {
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
// 处理调整尺寸过程中的鼠标移动
|
||||||
|
this.handleResizeMouseMove(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
// 处理调整尺寸结束
|
||||||
|
this.handleResizeMouseUp()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理调整尺寸过程中的鼠标移动
|
||||||
|
*/
|
||||||
|
private handleResizeMouseMove(e: MouseEvent): void {
|
||||||
|
// 找到正在调整尺寸的窗体
|
||||||
|
const resizingWindow = Array.from(this.windows.values()).find(
|
||||||
|
window => window.resizeState?.isResizing
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果没有正在调整尺寸的窗体,直接返回
|
||||||
|
if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return
|
||||||
|
|
||||||
|
const {
|
||||||
|
direction,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
startWidth,
|
||||||
|
startHeight,
|
||||||
|
startXPosition,
|
||||||
|
startYPosition
|
||||||
|
} = resizingWindow.resizeState
|
||||||
|
|
||||||
|
const deltaX = e.clientX - startX
|
||||||
|
const deltaY = e.clientY - startY
|
||||||
|
|
||||||
|
let newWidth = startWidth
|
||||||
|
let newHeight = startHeight
|
||||||
|
let newX = startXPosition
|
||||||
|
let newY = startYPosition
|
||||||
|
|
||||||
|
// 根据调整方向计算新尺寸和位置
|
||||||
|
switch (direction) {
|
||||||
|
case 'topLeft':
|
||||||
|
newWidth = Math.max(200, startWidth - deltaX)
|
||||||
|
newHeight = Math.max(150, startHeight - deltaY)
|
||||||
|
newX = startXPosition + (startWidth - newWidth)
|
||||||
|
newY = startYPosition + (startHeight - newHeight)
|
||||||
|
break
|
||||||
|
case 'top':
|
||||||
|
newHeight = Math.max(150, startHeight - deltaY)
|
||||||
|
newY = startYPosition + (startHeight - newHeight)
|
||||||
|
break
|
||||||
|
case 'topRight':
|
||||||
|
newWidth = Math.max(200, startWidth + deltaX)
|
||||||
|
newHeight = Math.max(150, startHeight - deltaY)
|
||||||
|
newY = startYPosition + (startHeight - newHeight)
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
newWidth = Math.max(200, startWidth + deltaX)
|
||||||
|
break
|
||||||
|
case 'bottomRight':
|
||||||
|
newWidth = Math.max(200, startWidth + deltaX)
|
||||||
|
newHeight = Math.max(150, startHeight + deltaY)
|
||||||
|
break
|
||||||
|
case 'bottom':
|
||||||
|
newHeight = Math.max(150, startHeight + deltaY)
|
||||||
|
break
|
||||||
|
case 'bottomLeft':
|
||||||
|
newWidth = Math.max(200, startWidth - deltaX)
|
||||||
|
newHeight = Math.max(150, startHeight + deltaY)
|
||||||
|
newX = startXPosition + (startWidth - newWidth)
|
||||||
|
break
|
||||||
|
case 'left':
|
||||||
|
newWidth = Math.max(200, startWidth - deltaX)
|
||||||
|
newX = startXPosition + (startWidth - newWidth)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用尺寸限制
|
||||||
|
newWidth = this.clampDimension(newWidth, resizingWindow.config.minWidth, resizingWindow.config.maxWidth)
|
||||||
|
newHeight = this.clampDimension(newHeight, resizingWindow.config.minHeight, resizingWindow.config.maxHeight)
|
||||||
|
|
||||||
|
// 应用新尺寸和位置
|
||||||
|
resizingWindow.config.width = newWidth
|
||||||
|
resizingWindow.config.height = newHeight
|
||||||
|
resizingWindow.config.x = newX
|
||||||
|
resizingWindow.config.y = newY
|
||||||
|
|
||||||
|
if (resizingWindow.element) {
|
||||||
|
resizingWindow.element.style.width = `${newWidth}px`
|
||||||
|
resizingWindow.element.style.height = `${newHeight}px`
|
||||||
|
resizingWindow.element.style.left = `${newX}px`
|
||||||
|
resizingWindow.element.style.top = `${newY}px`
|
||||||
|
resizingWindow.element.style.transform = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发调整尺寸事件
|
||||||
|
this.eventBus.notifyEvent('onResizing', resizingWindow.id, newWidth, newHeight)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(resizingWindow.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理调整尺寸结束
|
||||||
|
*/
|
||||||
|
private handleResizeMouseUp(): void {
|
||||||
|
// 找到正在调整尺寸的窗体
|
||||||
|
const resizingWindow = Array.from(this.windows.values()).find(
|
||||||
|
window => window.resizeState?.isResizing
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!resizingWindow || !resizingWindow.resizeState || !resizingWindow.element) return
|
||||||
|
|
||||||
|
// 结束调整尺寸
|
||||||
|
resizingWindow.resizeState.isResizing = false
|
||||||
|
|
||||||
|
// 移除半透明遮罩效果
|
||||||
|
resizingWindow.element.style.opacity = '1'
|
||||||
|
|
||||||
|
// 触发调整尺寸结束事件
|
||||||
|
this.eventBus.notifyEvent('onResizeEnd', resizingWindow.id)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(resizingWindow.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限制尺寸在最小值和最大值之间
|
||||||
|
*/
|
||||||
|
private clampDimension(value: number, min: number = 0, max: number = Infinity): number {
|
||||||
|
return Math.max(min, Math.min(max, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送窗体数据更新事件
|
||||||
|
*/
|
||||||
|
private notifyWindowFormDataUpdate(windowId: string): void {
|
||||||
|
const window = this.windows.get(windowId)
|
||||||
|
if (!window || !window.element) return
|
||||||
|
|
||||||
|
// 获取窗体数据
|
||||||
|
const rect = window.element.getBoundingClientRect()
|
||||||
|
const data = {
|
||||||
|
id: windowId,
|
||||||
|
state: window.state,
|
||||||
|
width: window.config.width,
|
||||||
|
height: window.config.height,
|
||||||
|
x: window.config.x !== undefined ? window.config.x : rect.left,
|
||||||
|
y: window.config.y !== undefined ? window.config.y : rect.top
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送事件到事件总线
|
||||||
|
this.eventBus.notifyEvent('onWindowFormDataUpdate', data)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载应用
|
* 加载应用
|
||||||
*/
|
*/
|
||||||
@@ -728,5 +1184,8 @@ export class WindowService {
|
|||||||
// 所有状态变化都应该触发事件,这是正常的系统行为
|
// 所有状态变化都应该触发事件,这是正常的系统行为
|
||||||
console.log(`[WindowService] 窗体状态变化: ${windowId} ${oldState} -> ${newState}`)
|
console.log(`[WindowService] 窗体状态变化: ${windowId} ${oldState} -> ${newState}`)
|
||||||
this.eventBus.notifyEvent('onStateChange', windowId, newState, oldState)
|
this.eventBus.notifyEvent('onStateChange', windowId, newState, oldState)
|
||||||
|
|
||||||
|
// 发送窗体数据更新事件
|
||||||
|
this.notifyWindowFormDataUpdate(windowId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,3 +8,35 @@ export interface WindowFormPos {
|
|||||||
|
|
||||||
/** 窗口状态 */
|
/** 窗口状态 */
|
||||||
export type TWindowFormState = 'default' | 'minimized' | 'maximized';
|
export type TWindowFormState = 'default' | 'minimized' | 'maximized';
|
||||||
|
|
||||||
|
/** 拖拽方向 */
|
||||||
|
export type ResizeDirection =
|
||||||
|
| 'topLeft'
|
||||||
|
| 'top'
|
||||||
|
| 'topRight'
|
||||||
|
| 'right'
|
||||||
|
| 'bottomRight'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottomLeft'
|
||||||
|
| 'left'
|
||||||
|
| 'none'
|
||||||
|
|
||||||
|
/** 拖拽状态 */
|
||||||
|
export interface ResizeState {
|
||||||
|
/** 是否正在调整尺寸 */
|
||||||
|
isResizing: boolean
|
||||||
|
/** 调整方向 */
|
||||||
|
direction: ResizeDirection
|
||||||
|
/** 起始鼠标位置 */
|
||||||
|
startX: number
|
||||||
|
/** 起始鼠标位置 */
|
||||||
|
startY: number
|
||||||
|
/** 起始窗体宽度 */
|
||||||
|
startWidth: number
|
||||||
|
/** 起始窗体高度 */
|
||||||
|
startHeight: number
|
||||||
|
/** 起始窗体X坐标 */
|
||||||
|
startXPosition: number
|
||||||
|
/** 起始窗体Y坐标 */
|
||||||
|
startYPosition: number
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user