保存一下

This commit is contained in:
2025-09-01 16:31:47 +08:00
parent 90d72e1649
commit d34e953440
3 changed files with 206 additions and 0 deletions

View File

@@ -84,6 +84,7 @@ export class DesktopProcess extends ProcessImpl {
dom.style.width = `${this._width}px`
dom.style.height = `${this._height}px`
dom.style.position = 'relative'
dom.style.overflow = 'hidden'
this._desktopRootDom = dom
const app = createApp(DesktopComponent, { process: this })

199
src/core/utils/Draggable.ts Normal file
View File

@@ -0,0 +1,199 @@
type TDragStartCallback = (x: number, y: number) => void;
type TDragMoveCallback = (x: number, y: number) => void;
type TDragEndCallback = (x: number, y: number) => void;
/**
* 拖拽参数
*/
interface IDraggableOptions {
/** 用来触发拖拽的元素、句柄 */
handle: HTMLElement;
/** 真正移动的元素 */
target: HTMLElement;
/** 拖拽模式 */
mode?: 'transform' | 'position';
/** 拖拽的边界或容器元素 */
boundary?: IBoundary | HTMLElement;
/** 拖拽开始回调 */
onStart?: TDragStartCallback;
/** 拖拽移动中回调 */
onMove?: TDragMoveCallback;
/** 拖拽结束回调 */
onEnd?: TDragEndCallback;
}
/** 拖拽的范围边界 */
interface IBoundary {
/** 最小 X 坐标 */
minX?: number;
/** 最大 X 坐标 */
maxX?: number;
/** 最小 Y 坐标 */
minY?: number;
/** 最大 Y 坐标 */
maxY?: number;
}
/**
* 拖拽功能通用类
*/
export class Draggable {
private handle: HTMLElement;
private target: HTMLElement;
private mode: 'transform' | 'position';
private boundary?: IBoundary;
private containerElement?: HTMLElement;
private containerBounds?: IBoundary;
private startX = 0;
private startY = 0;
private originX = 0;
private originY = 0;
private currentX = 0;
private currentY = 0;
private dragging = false;
private onStart?: TDragStartCallback;
private onMove?: TDragMoveCallback;
private onEnd?: TDragEndCallback;
private resizeObserver?: ResizeObserver;
constructor(options: IDraggableOptions) {
this.handle = options.handle;
this.target = options.target;
this.mode = options.mode ?? 'transform';
this.onStart = options.onStart;
this.onMove = options.onMove;
this.onEnd = options.onEnd;
// 判断 boundary 类型
if (options.boundary instanceof HTMLElement) {
this.containerElement = options.boundary;
this.observeResize(); // 监听容器和目标变化
} else {
this.boundary = options.boundary;
}
if (this.mode === 'position') {
const computed = window.getComputedStyle(this.target);
if (computed.position === 'static') {
this.target.style.position = 'absolute';
}
}
this.handle.addEventListener('mousedown', this.onMouseDown);
}
/** 监听容器和目标大小变化 */
private observeResize() {
this.resizeObserver = new ResizeObserver(() => {
this.updateContainerBounds();
});
if (this.containerElement) {
this.resizeObserver.observe(this.containerElement);
}
// 监听目标大小变化
this.resizeObserver.observe(this.target);
this.updateContainerBounds();
}
/** 更新边界 */
private updateContainerBounds() {
if (!this.containerElement) return;
const containerRect = this.containerElement.getBoundingClientRect();
const targetRect = this.target.getBoundingClientRect();
if (this.mode === 'transform') {
this.containerBounds = {
minX: containerRect.left + window.scrollX,
maxX: containerRect.right + window.scrollX - targetRect.width,
minY: containerRect.top + window.scrollY,
maxY: containerRect.bottom + window.scrollY - targetRect.height,
};
} else {
this.containerBounds = {
minX: 0,
minY: 0,
maxX: containerRect.width - targetRect.width,
maxY: containerRect.height - targetRect.height,
};
}
}
private onMouseDown = (e: MouseEvent) => {
e.preventDefault();
this.handle.style.cursor = 'move';
this.dragging = true;
const rect = this.target.getBoundingClientRect();
this.originX = rect.left + window.scrollX;
this.originY = rect.top + window.scrollY;
if (this.mode === 'position') {
const style = window.getComputedStyle(this.target);
this.originX = parseFloat(style.left) || 0;
this.originY = parseFloat(style.top) || 0;
}
this.startX = e.clientX;
this.startY = e.clientY;
this.currentX = this.originX;
this.currentY = this.originY;
this.onStart?.(this.currentX, this.currentY);
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
};
private onMouseMove = (e: MouseEvent) => {
if (!this.dragging) return;
let newX = this.originX + (e.clientX - this.startX);
let newY = this.originY + (e.clientY - this.startY);
const bounds = this.containerBounds || this.boundary;
if (bounds) {
if (bounds.minX !== undefined) newX = Math.max(newX, bounds.minX);
if (bounds.maxX !== undefined) newX = Math.min(newX, bounds.maxX);
if (bounds.minY !== undefined) newY = Math.max(newY, bounds.minY);
if (bounds.maxY !== undefined) newY = Math.min(newY, bounds.maxY);
}
if (this.mode === 'transform') {
this.target.style.transform = `translate(${newX}px, ${newY}px)`;
} else {
this.target.style.left = `${newX}px`;
this.target.style.top = `${newY}px`;
}
this.currentX = newX;
this.currentY = newY;
this.onMove?.(this.currentX, this.currentY);
};
private onMouseUp = () => {
this.dragging = false;
this.handle.style.cursor = 'default';
this.onEnd?.(this.currentX, this.currentY);
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
};
public destroy() {
this.handle.removeEventListener('mousedown', this.onMouseDown);
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
this.resizeObserver?.disconnect();
}
}

View File

@@ -5,6 +5,7 @@ import type { IWindowForm } from '@/core/window/IWindowForm.ts'
import type { IWindowFormConfig } from '@/core/window/types/IWindowFormConfig.ts'
import type { WindowFormPos } from '@/core/window/types/WindowFormTypes.ts'
import { processManager } from '@/core/process/ProcessManager.ts'
import { Draggable } from '@/core/utils/Draggable.ts'
export default class WindowFormImpl implements IWindowForm {
private readonly _id: string = uuidV4();
@@ -45,6 +46,11 @@ export default class WindowFormImpl implements IWindowForm {
dom.style.height = `${this.height}px`;
dom.style.zIndex = '100';
dom.style.backgroundColor = 'white';
new Draggable( {
handle: dom,
target: dom,
mode: 'position',
})
this.desktopRootDom.appendChild(dom);
}