From 53fc32de248ac3ccd281c7ecb22cd3876d1ed328 Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Wed, 3 Sep 2025 11:17:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/desktop/ui/DesktopComponent.vue | 6 +- src/core/utils/DraggableResizableWindow.ts | 217 ++++++++++++++------- src/core/window/impl/WindowFormImpl.ts | 2 +- 3 files changed, 148 insertions(+), 77 deletions(-) diff --git a/src/core/desktop/ui/DesktopComponent.vue b/src/core/desktop/ui/DesktopComponent.vue index 62f2eb2..88e091c 100644 --- a/src/core/desktop/ui/DesktopComponent.vue +++ b/src/core/desktop/ui/DesktopComponent.vue @@ -13,7 +13,9 @@ /> -
+ @@ -70,7 +72,7 @@ $taskBarHeight: 40px; } .task-bar { - @apply w-full bg-gray-200; + @apply w-full bg-gray-200 flex justify-center items-center; height: $taskBarHeight; flex-shrink: 0; } diff --git a/src/core/utils/DraggableResizableWindow.ts b/src/core/utils/DraggableResizableWindow.ts index 905b65e..7b98fdf 100644 --- a/src/core/utils/DraggableResizableWindow.ts +++ b/src/core/utils/DraggableResizableWindow.ts @@ -19,12 +19,7 @@ type TResizeDirection = /** 窗口状态 */ type WindowState = 'default' | 'minimized' | 'maximized'; -interface TaskbarOptions { - /** 任务栏图标 DOM 元素或目标位置 {x, y} */ - element?: HTMLElement; - position?: { x: number; y: number }; -} - +/** 元素边界 */ interface IElementRect { /** 宽度 */ width: number; @@ -70,6 +65,8 @@ interface IDraggableResizableOptions { snapAnimationDuration?: number; /** 是否允许超出边界 */ allowOverflow?: boolean; + /** 最小化任务栏位置的元素ID */ + taskbarElementId: string; /** 拖拽开始回调 */ onDragStart?: TDragStartCallback; @@ -106,7 +103,7 @@ interface IBoundaryRect { } /** - * 拖拽 + 调整尺寸通用类 + * 拖拽 + 调整尺寸 + 最大最小化 通用类 */ export class DraggableResizableWindow { private handle?: HTMLElement; @@ -153,10 +150,10 @@ export class DraggableResizableWindow { private targetDefaultBounds: IElementRect; /** 最大化前保存 bounds */ private maximizedBounds?: IElementRect; - /** 任务栏相关配置 */ - private taskbar?: TaskbarOptions; + /** 最小化任务栏位置的元素ID */ + private taskbarElementId: string; - constructor(options: IDraggableResizableOptions & { taskbar?: TaskbarOptions }) { + constructor(options: IDraggableResizableOptions) { // Drag this.handle = options.handle; this.target = options.target; @@ -181,7 +178,7 @@ export class DraggableResizableWindow { this.onResizeEnd = options.onResizeEnd; this.targetDefaultBounds = { width: this.target.offsetWidth, height: this.target.offsetHeight, top: this.target.offsetTop, left: this.target.offsetLeft }; - this.taskbar = options.taskbar; + this.taskbarElementId = options.taskbarElementId; this.init(); } @@ -600,93 +597,165 @@ export class DraggableResizableWindow { if (this.state === 'minimized') return; this.state = 'minimized'; - // 获取目标任务栏位置 - let targetX = 50, targetY = window.innerHeight - 40; - if (this.taskbar?.element) { - const rect = this.taskbar.element.getBoundingClientRect(); - targetX = rect.left; - targetY = rect.top; - } else if (this.taskbar?.position) { - targetX = this.taskbar.position.x; - targetY = this.taskbar.position.y; - } + // 获取任务栏位置 + const taskbarElement = document.querySelector(this.taskbarElementId) + if (!taskbarElement) throw new Error('任务栏元素未找到'); + const rect = taskbarElement.getBoundingClientRect() + const targetX = rect.left; + const targetY = rect.top; + const targetWidth = rect.width; + const targetHeight = rect.height; - this.animateTo(targetX, targetY, 300, () => { - this.target.style.width = '0px'; - this.target.style.height = '0px'; - }); + const startX = this.currentX; + const startY = this.currentY; + const startWidth = this.target.offsetWidth; + const startHeight = this.target.offsetHeight; + + const deltaX = targetX - startX; + const deltaY = targetY - startY; + const deltaW = targetWidth - startWidth; + const deltaH = targetHeight - startHeight; + const duration = 400; // Windows 风格稍慢 + const startTime = performance.now(); + + const step = (now: number) => { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const ease = 1 - Math.pow(1 - progress, 3); // 缓动 + + const x = startX + deltaX * ease; + const y = startY + deltaY * ease; + const w = startWidth + deltaW * ease; + const h = startHeight + deltaH * ease; + + this.target.style.width = `${w}px`; + this.target.style.height = `${h}px`; + this.applyPosition(x, y, false); + + if (progress < 1) { + requestAnimationFrame(step); + } else { + // 最终值 + this.target.style.width = `${targetWidth}px`; + this.target.style.height = `${targetHeight}px`; + this.applyPosition(targetX, targetY, true); + } + }; + + requestAnimationFrame(step); } - /** 最大化 */ - public maximize() { + /** 最大化窗口(带动画) */ + public maximize(withAnimation = true) { if (this.state === 'maximized') return; this.state = 'maximized'; + // 保存原始 bounds const rect = this.target.getBoundingClientRect(); this.targetDefaultBounds = { width: rect.width, height: rect.height, top: rect.top, left: rect.left }; - this.maximizedBounds = { ...this.targetDefaultBounds }; + const startWidth = rect.width; + const startHeight = rect.height; + const startX = this.currentX; + const startY = this.currentY; - const width = this.containerRect?.width ?? window.innerWidth; - const height = this.containerRect?.height ?? window.innerHeight; + const targetWidth = this.containerRect?.width ?? window.innerWidth; + const targetHeight = this.containerRect?.height ?? window.innerHeight; + const targetX = 0; + const targetY = 0; - this.target.style.width = `${width}px`; - this.target.style.height = `${height}px`; - this.applyPosition(0, 0, true); + if (!withAnimation) { + this.target.style.width = `${targetWidth}px`; + this.target.style.height = `${targetHeight}px`; + this.applyPosition(targetX, targetY, true); + return; + } + + // 动画过渡 + const deltaX = targetX - startX; + const deltaY = targetY - startY; + const deltaW = targetWidth - startWidth; + const deltaH = targetHeight - startHeight; + const duration = 300; + const startTime = performance.now(); + + const step = (now: number) => { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const ease = 1 - Math.pow(1 - progress, 3); + + const x = startX + deltaX * ease; + const y = startY + deltaY * ease; + const w = startWidth + deltaW * ease; + const h = startHeight + deltaH * ease; + + this.target.style.width = `${w}px`; + this.target.style.height = `${h}px`; + this.applyPosition(x, y, false); + + if (progress < 1) { + requestAnimationFrame(step); + } else { + this.target.style.width = `${targetWidth}px`; + this.target.style.height = `${targetHeight}px`; + this.applyPosition(targetX, targetY, true); + } + }; + + requestAnimationFrame(step); } - /** 恢复到默认状态 */ + /** 恢复窗口(带动画,从最大化或最小化) */ public restore(withAnimation = true) { if (this.state === 'default') return; this.state = 'default'; const b = this.targetDefaultBounds; - if (withAnimation) { - // 从当前位置(可能是任务栏位置或 0 尺寸)动画过渡到 targetDefaultBounds - const startWidth = this.target.offsetWidth || 0; - const startHeight = this.target.offsetHeight || 0; - const startLeft = this.currentX; - const startTop = this.currentY; + const startWidth = this.target.offsetWidth || 0; + const startHeight = this.target.offsetHeight || 0; + const startLeft = this.currentX; + const startTop = this.currentY; - const deltaX = b.left - startLeft; - const deltaY = b.top - startTop; - const deltaW = b.width - startWidth; - const deltaH = b.height - startHeight; - const duration = 300; - const startTime = performance.now(); - - const step = (now: number) => { - const elapsed = now - startTime; - const progress = Math.min(elapsed / duration, 1); - const ease = 1 - Math.pow(1 - progress, 3); // 缓动函数 - - const x = startLeft + deltaX * ease; - const y = startTop + deltaY * ease; - const w = startWidth + deltaW * ease; - const h = startHeight + deltaH * ease; - - this.target.style.width = `${w}px`; - this.target.style.height = `${h}px`; - this.applyPosition(x, y, false); - - if (progress < 1) { - requestAnimationFrame(step); - } else { - // 最终值 - this.target.style.width = `${b.width}px`; - this.target.style.height = `${b.height}px`; - this.applyPosition(b.left, b.top, true); - } - }; - - requestAnimationFrame(step); - } else { - // 不动画直接恢复 + if (!withAnimation) { this.target.style.width = `${b.width}px`; this.target.style.height = `${b.height}px`; this.applyPosition(b.left, b.top, true); + return; } + + const deltaX = b.left - startLeft; + const deltaY = b.top - startTop; + const deltaW = b.width - startWidth; + const deltaH = b.height - startHeight; + const duration = 300; + const startTime = performance.now(); + + const step = (now: number) => { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const ease = 1 - Math.pow(1 - progress, 3); + + const x = startLeft + deltaX * ease; + const y = startTop + deltaY * ease; + const w = startWidth + deltaW * ease; + const h = startHeight + deltaH * ease; + + this.target.style.width = `${w}px`; + this.target.style.height = `${h}px`; + this.applyPosition(x, y, false); + + if (progress < 1) { + requestAnimationFrame(step); + } else { + this.target.style.width = `${b.width}px`; + this.target.style.height = `${b.height}px`; + this.applyPosition(b.left, b.top, true); + } + }; + + requestAnimationFrame(step); } + /** 更新默认 bounds */ private updateDefaultBounds(x?: number, y?: number, width?: number, height?: number) { const rect = this.target.getBoundingClientRect(); diff --git a/src/core/window/impl/WindowFormImpl.ts b/src/core/window/impl/WindowFormImpl.ts index 5e40f28..2f71e78 100644 --- a/src/core/window/impl/WindowFormImpl.ts +++ b/src/core/window/impl/WindowFormImpl.ts @@ -80,7 +80,7 @@ export default class WindowFormImpl implements IWindowForm { mode: 'position', snapThreshold: 20, boundary: document.body, - taskbar: { position: { x: 50, y: window.innerHeight - 40 } }, + taskbarElementId: '#taskbar', }) this.desktopRootDom.appendChild(dom);