保存一下

This commit is contained in:
2025-09-03 11:17:32 +08:00
parent 926ac698cc
commit 53fc32de24
3 changed files with 148 additions and 77 deletions

View File

@@ -13,7 +13,9 @@
/>
</div>
</div>
<div class="task-bar"></div>
<div class="task-bar">
<div id="taskbar" class="w-[80px] h-full flex justify-center items-center bg-blue">测试</div>
</div>
</div>
</n-config-provider>
</template>
@@ -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;
}

View File

@@ -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();

View File

@@ -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);