import { reactive, ref, type Ref } from 'vue'; const STORAGE_KEY = 'mes_ding_tpl_launch_pos'; export interface FloatPosition { left: number; top: number; } function readAllPositions(): Record { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') || {}; } catch { return {}; } } /** 查询条件区域(BasicTable 搜索表单)右侧的默认坐标 */ export function calcDefaultPosition(btnWidth: number, btnHeight: number): FloatPosition { const formEl = document.querySelector('.jeecg-basic-table-form-container .ant-form') || document.querySelector('.jeecg-basic-table-form-container'); if (!formEl) { return { left: Math.max(8, window.innerWidth - btnWidth - 24), top: 100, }; } const rect = formEl.getBoundingClientRect(); return { left: Math.max(8, rect.right - btnWidth - 12), top: Math.max(8, rect.top + (rect.height - btnHeight) / 2), }; } /** * 可拖拽悬浮按钮位置:默认对齐查询区右侧,拖拽后按路由持久化。 */ export function useDraggablePosition(routePath: Ref) { const pos = reactive({ left: 0, top: 0 }); const isDragging = ref(false); let moved = false; let pointerId: number | null = null; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; let btnWidth = 120; let btnHeight = 36; function setButtonSize(width: number, height: number) { btnWidth = width; btnHeight = height; } function loadPosition(path: string): boolean { const saved = readAllPositions()[path]; if (saved && typeof saved.left === 'number' && typeof saved.top === 'number') { pos.left = saved.left; pos.top = saved.top; return true; } return false; } function savePosition(path: string) { const all = readAllPositions(); all[path] = { left: pos.left, top: pos.top }; localStorage.setItem(STORAGE_KEY, JSON.stringify(all)); } function applyDefaultPosition() { const p = calcDefaultPosition(btnWidth, btnHeight); pos.left = p.left; pos.top = p.top; } function initPosition(path: string, preferDefault = false) { if (!path) return; if (!preferDefault && loadPosition(path)) return; applyDefaultPosition(); } function clampPosition() { const maxLeft = window.innerWidth - btnWidth - 8; const maxTop = window.innerHeight - btnHeight - 8; pos.left = Math.min(Math.max(8, pos.left), maxLeft); pos.top = Math.min(Math.max(8, pos.top), maxTop); } function onPointerMove(e: PointerEvent) { if (pointerId !== e.pointerId) return; const dx = e.clientX - startX; const dy = e.clientY - startY; if (!moved && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) { moved = true; isDragging.value = true; } if (!moved) return; pos.left = startLeft + dx; pos.top = startTop + dy; clampPosition(); } function endDrag(path: string) { document.removeEventListener('pointermove', onPointerMove); document.removeEventListener('pointerup', onPointerUp); document.removeEventListener('pointercancel', onPointerUp); if (moved && path) { savePosition(path); } pointerId = null; setTimeout(() => { isDragging.value = false; moved = false; }, 0); } function onPointerUp(e: PointerEvent) { if (pointerId !== e.pointerId) return; endDrag(routePath.value); } function onPointerDown(e: PointerEvent, el: HTMLElement | null) { if (e.button !== 0) return; if (el) { const rect = el.getBoundingClientRect(); setButtonSize(rect.width, rect.height); } moved = false; isDragging.value = false; pointerId = e.pointerId; startX = e.clientX; startY = e.clientY; startLeft = pos.left; startTop = pos.top; (e.currentTarget as HTMLElement)?.setPointerCapture?.(e.pointerId); document.addEventListener('pointermove', onPointerMove); document.addEventListener('pointerup', onPointerUp); document.addEventListener('pointercancel', onPointerUp); } function wasDragged() { return moved; } return { pos, isDragging, setButtonSize, initPosition, applyDefaultPosition, onPointerDown, wasDragged, clampPosition, }; }