保存一下
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,53 +597,131 @@ 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this.animateTo(targetX, targetY, 300, () => {
|
||||
this.target.style.width = '0px';
|
||||
this.target.style.height = '0px';
|
||||
});
|
||||
}
|
||||
|
||||
/** 最大化 */
|
||||
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;
|
||||
|
||||
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;
|
||||
@@ -657,7 +732,7 @@ export class DraggableResizableWindow {
|
||||
const step = (now: number) => {
|
||||
const elapsed = now - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const ease = 1 - Math.pow(1 - progress, 3); // 缓动函数
|
||||
const ease = 1 - Math.pow(1 - progress, 3);
|
||||
|
||||
const x = startLeft + deltaX * ease;
|
||||
const y = startTop + deltaY * ease;
|
||||
@@ -671,7 +746,6 @@ export class DraggableResizableWindow {
|
||||
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);
|
||||
@@ -679,14 +753,9 @@ export class DraggableResizableWindow {
|
||||
};
|
||||
|
||||
requestAnimationFrame(step);
|
||||
} else {
|
||||
// 不动画直接恢复
|
||||
this.target.style.width = `${b.width}px`;
|
||||
this.target.style.height = `${b.height}px`;
|
||||
this.applyPosition(b.left, b.top, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 更新默认 bounds */
|
||||
private updateDefaultBounds(x?: number, y?: number, width?: number, height?: number) {
|
||||
const rect = this.target.getBoundingClientRect();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user