import './index.css'; import type { DialogOptions } from './index.type'; /** * 通用弹窗组件类 * 支持拖拽、缩放、自定义内容和位置。 */ export class BimDialog { private element: HTMLElement; private options: DialogOptions; private container: HTMLElement; private header: HTMLElement; private contentArea: HTMLElement; private _isDestroyed = false; /** * 构造函数 * @param options 弹窗配置选项 */ constructor(options: DialogOptions) { // 合并默认配置 this.options = { title: 'Dialog', width: 300, height: 'auto', position: 'center', draggable: true, resizable: false, minWidth: 200, minHeight: 100, ...options }; this.container = options.container; // 创建 DOM 结构 this.element = this.createDom(); this.header = this.element.querySelector('.bim-dialog-header') as HTMLElement; this.contentArea = this.element.querySelector('.bim-dialog-content') as HTMLElement; // 初始化 this.init(); } /** * 创建弹窗的 DOM 结构 */ private createDom(): HTMLElement { const el = document.createElement('div'); el.className = 'bim-dialog'; if (this.options.id) el.id = this.options.id; // 应用颜色配置到 CSS 变量 (局部作用域) const style = el.style; if (this.options.backgroundColor) style.setProperty('--bim-dialog-bg', this.options.backgroundColor); if (this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', this.options.headerBackgroundColor); if (this.options.titleColor) style.setProperty('--bim-dialog-title-color', this.options.titleColor); if (this.options.textColor) style.setProperty('--bim-dialog-text-color', this.options.textColor); if (this.options.borderColor) style.setProperty('--bim-dialog-border-color', this.options.borderColor); // 设置初始尺寸 this.setSize(el, this.options.width, this.options.height); // 创建标题栏 (Header) const header = document.createElement('div'); header.className = 'bim-dialog-header'; if (this.options.draggable) header.classList.add('draggable'); const title = document.createElement('span'); title.className = 'bim-dialog-title'; title.textContent = this.options.title || ''; const closeBtn = document.createElement('span'); closeBtn.className = 'bim-dialog-close'; closeBtn.innerHTML = '×'; closeBtn.onclick = () => this.close(); header.appendChild(title); header.appendChild(closeBtn); // 创建内容区域 (Content) const content = document.createElement('div'); content.className = 'bim-dialog-content'; if (typeof this.options.content === 'string') { content.innerHTML = this.options.content; } else if (this.options.content instanceof HTMLElement) { content.appendChild(this.options.content); } el.appendChild(header); el.appendChild(content); // 如果允许缩放,创建缩放手柄 if (this.options.resizable) { const resizeHandle = document.createElement('div'); resizeHandle.className = 'bim-dialog-resize-handle'; el.appendChild(resizeHandle); } return el; } /** * 设置元素尺寸 */ private setSize(el: HTMLElement, width?: number | string, height?: number | string) { if (width !== undefined) { el.style.width = typeof width === 'number' ? `${width}px` : width; } if (height !== undefined) { el.style.height = typeof height === 'number' ? `${height}px` : height; } } /** * 初始化组件功能 */ private init() { this.container.appendChild(this.element); // 必须先挂载才能计算尺寸进行定位 this.initPosition(); if (this.options.draggable) { this.initDrag(); } if (this.options.resizable) { this.initResize(); } } /** * 初始化弹窗位置 */ private initPosition() { const pos = this.options.position; const elRect = this.element.getBoundingClientRect(); // 计算相对父容器的定位 let left = 0; let top = 0; const pW = this.container.clientWidth; const pH = this.container.clientHeight; const elW = elRect.width; const elH = elRect.height; if (typeof pos === 'object' && 'x' in pos) { left = pos.x; top = pos.y; } else { switch (pos) { case 'center': left = (pW - elW) / 2; top = (pH - elH) / 2; break; case 'top-left': left = 0; top = 0; break; case 'top-center': left = (pW - elW) / 2; top = 0; break; case 'top-right': left = pW - elW; top = 0; break; case 'left-center': left = 0; top = (pH - elH) / 2; break; case 'right-center': left = pW - elW; top = (pH - elH) / 2; break; case 'bottom-left': left = 0; top = pH - elH; break; case 'bottom-center': left = (pW - elW) / 2; top = pH - elH; break; case 'bottom-right': left = pW - elW; top = pH - elH; break; default: left = (pW - elW) / 2; top = (pH - elH) / 2; } } // 简单的边界检查,防止初始位置溢出 left = Math.max(0, Math.min(left, pW - elW)); top = Math.max(0, Math.min(top, pH - elH)); this.element.style.left = `${left}px`; this.element.style.top = `${top}px`; } /** * 初始化拖拽功能 */ private initDrag() { let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; const onMouseDown = (e: MouseEvent) => { e.preventDefault(); startX = e.clientX; startY = e.clientY; startLeft = this.element.offsetLeft; startTop = this.element.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; const onMouseMove = (e: MouseEvent) => { const dx = e.clientX - startX; const dy = e.clientY - startY; let newLeft = startLeft + dx; let newTop = startTop + dy; // 边界限制,防止拖出容器 const maxLeft = this.container.clientWidth - this.element.offsetWidth; const maxTop = this.container.clientHeight - this.element.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxLeft)); newTop = Math.max(0, Math.min(newTop, maxTop)); this.element.style.left = `${newLeft}px`; this.element.style.top = `${newTop}px`; }; const onMouseUp = () => { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; this.header.addEventListener('mousedown', onMouseDown); } /** * 初始化缩放功能 */ private initResize() { const handle = this.element.querySelector('.bim-dialog-resize-handle') as HTMLElement; if (!handle) return; let startX = 0; let startY = 0; let startW = 0; let startH = 0; const onMouseDown = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); startX = e.clientX; startY = e.clientY; startW = this.element.offsetWidth; startH = this.element.offsetHeight; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; const onMouseMove = (e: MouseEvent) => { const dx = e.clientX - startX; const dy = e.clientY - startY; const newW = Math.max(this.options.minWidth || 100, startW + dx); const newH = Math.max(this.options.minHeight || 50, startH + dy); this.element.style.width = `${newW}px`; this.element.style.height = `${newH}px`; }; const onMouseUp = () => { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; handle.addEventListener('mousedown', onMouseDown); } /** * 动态设置内容 * @param content 内容元素或 HTML 字符串 */ public setContent(content: HTMLElement | string) { this.contentArea.innerHTML = ''; if (typeof content === 'string') { this.contentArea.innerHTML = content; } else { this.contentArea.appendChild(content); } } /** * 关闭弹窗并销毁 */ public close() { if (this._isDestroyed) return; this.element.remove(); this._isDestroyed = true; if (this.options.onClose) { this.options.onClose(); } } }