优化拖动逻辑

This commit is contained in:
2025-11-11 13:26:05 +08:00
parent bbb9d42037
commit 38d8e24e90
+143 -89
View File
@@ -10,6 +10,15 @@ const draggingIndex = ref<number | null>(null);
const overIndex = ref<number | null>(null); const overIndex = ref<number | null>(null);
const dragPreview = ref<HTMLElement | null>(null); const dragPreview = ref<HTMLElement | null>(null);
const isDragging = ref(false); const isDragging = ref(false);
// 指针拖拽新增状态
const insertSide = ref<'before' | 'after' | null>(null);
const pointerId = ref<number | null>(null);
const startX = ref(0);
const startY = ref(0);
const lastX = ref(0);
const lastY = ref(0);
const dragStarted = ref(false);
const DRAG_THRESHOLD = 3; // 像素阈值,避免误触
const editingIndex = ref<number | null>(null); const editingIndex = ref<number | null>(null);
const editingValue = ref(''); const editingValue = ref('');
const addingMapIndex = ref<number | null>(null); const addingMapIndex = ref<number | null>(null);
@@ -138,20 +147,78 @@ function getTokenWrapperInfo(token: string) {
return store.getTokenWrapperInfo(token); return store.getTokenWrapperInfo(token);
} }
function onDragStart(index: number, e: DragEvent) { // 指针事件版拖拽:更高性能且可自定义插入指示
function onPointerDown(index: number, e: PointerEvent) {
if (editingIndex.value === index) return;
draggingIndex.value = index; draggingIndex.value = index;
pointerId.value = e.pointerId;
startX.value = e.clientX;
startY.value = e.clientY;
lastX.value = e.clientX;
lastY.value = e.clientY;
dragStarted.value = false;
isDragging.value = false;
insertSide.value = null;
// 监听全局移动与抬起
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp, { once: true });
}
function handlePointerMove(e: PointerEvent) {
lastX.value = e.clientX;
lastY.value = e.clientY;
const dx = e.clientX - startX.value;
const dy = e.clientY - startY.value;
if (!dragStarted.value && Math.hypot(dx, dy) > DRAG_THRESHOLD) {
dragStarted.value = true;
isDragging.value = true; isDragging.value = true;
if (draggingIndex.value != null) createPointerPreview(draggingIndex.value);
}
if (!isDragging.value) return;
positionPreview(e.clientX, e.clientY);
updateOverIndexAndSide(e.clientX, e.clientY);
}
if (e.dataTransfer) { function handlePointerUp(e: PointerEvent) {
e.dataTransfer.effectAllowed = 'move'; window.removeEventListener('pointermove', handlePointerMove);
e.dataTransfer.setData('text/plain', tokens.value[index] || ''); if (!dragStarted.value || draggingIndex.value == null) {
cleanupDrag();
return;
}
const from = draggingIndex.value!;
const j = overIndex.value;
const side = insertSide.value;
if (j != null && side) {
let to = j;
if (side === 'before') {
to = j - (from < j ? 1 : 0);
} else {
to = j + (from > j ? 1 : 0);
}
if (to < 0) to = 0;
if (to >= tokens.value.length) to = tokens.value.length - 1;
store.reorderTokens(from, to);
showNotification('已重新排序', 'success');
}
cleanupDrag();
}
// 创建自定义拖拽预览 function cleanupDrag() {
const dragElement = e.target as HTMLElement; draggingIndex.value = null;
overIndex.value = null;
isDragging.value = false;
insertSide.value = null;
pointerId.value = null;
if (dragPreview.value) {
document.body.removeChild(dragPreview.value);
dragPreview.value = null;
}
}
function createPointerPreview(index: number) {
const token = tokens.value[index] || ''; const token = tokens.value[index] || '';
const translation = displayTrans(token); const translation = displayTrans(token);
// 创建预览元素
const preview = document.createElement('div'); const preview = document.createElement('div');
preview.className = 'drag-preview'; preview.className = 'drag-preview';
preview.innerHTML = ` preview.innerHTML = `
@@ -161,83 +228,38 @@ function onDragStart(index: number, e: DragEvent) {
<span class="drag-preview-trans">${translation}</span> <span class="drag-preview-trans">${translation}</span>
</div> </div>
`; `;
// 设置预览样式(减少布局与重绘)
preview.style.position = 'fixed'; preview.style.position = 'fixed';
preview.style.top = '0'; preview.style.top = '0';
preview.style.left = '0'; preview.style.left = '0';
preview.style.zIndex = '1000'; preview.style.zIndex = '1000';
preview.style.pointerEvents = 'none'; preview.style.pointerEvents = 'none';
preview.style.visibility = 'hidden';
// 降低绘制成本
;(preview.style as any).contain = 'layout style paint'; ;(preview.style as any).contain = 'layout style paint';
preview.style.willChange = 'transform, opacity'; preview.style.willChange = 'transform, opacity';
document.body.appendChild(preview); document.body.appendChild(preview);
dragPreview.value = preview; dragPreview.value = preview;
// 设置拖拽图像
e.dataTransfer.setDragImage(preview, 0, 0);
// 预览节点在 dragend 中统一清理,避免频繁移除导致卡顿
}
} }
function onDragOver(index: number, e: DragEvent) { function positionPreview(x: number, y: number) {
e.preventDefault(); if (!dragPreview.value) return;
if (draggingIndex.value === null) return; dragPreview.value.style.transform = `translate(${x + 12}px, ${y + 12}px)`;
e.dataTransfer!.dropEffect = 'move';
// 只有当拖拽到不同位置时才更新
if (overIndex.value !== index) {
overIndex.value = index;
}
} }
function onDragEnter(index: number, e: DragEvent) { function updateOverIndexAndSide(x: number, y: number) {
e.preventDefault(); insertSide.value = null;
if (draggingIndex.value !== null && draggingIndex.value !== index) {
overIndex.value = index;
}
}
function onDragLeave(e: DragEvent) {
// 只有当离开整个拖拽区域时才清除
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
const x = e.clientX;
const y = e.clientY;
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
overIndex.value = null; overIndex.value = null;
} const el = document.elementFromPoint(x, y) as HTMLElement | null;
} if (!el) return;
const tokenEl = el.closest('.pe-token-compact, .pe-token-detail') as HTMLElement | null;
function onDrop(index: number, e: DragEvent) { if (!tokenEl) return;
e.preventDefault(); const idxAttr = tokenEl.getAttribute('data-index');
if (draggingIndex.value == null) return; if (idxAttr == null) return;
const idx = parseInt(idxAttr, 10);
// 执行重排序 if (Number.isNaN(idx)) return;
store.reorderTokens(draggingIndex.value, index); if (idx === draggingIndex.value) { overIndex.value = null; insertSide.value = null; return; }
const rect = tokenEl.getBoundingClientRect();
// 重置状态 const midX = rect.left + rect.width / 2;
draggingIndex.value = null; overIndex.value = idx;
overIndex.value = null; insertSide.value = x < midX ? 'before' : 'after';
isDragging.value = false;
showNotification('已重新排序', 'success');
}
function onDragEnd() {
// 清理拖拽状态
draggingIndex.value = null;
overIndex.value = null;
isDragging.value = false;
if (dragPreview.value) {
document.body.removeChild(dragPreview.value);
dragPreview.value = null;
}
} }
function beginEdit(i: number) { function beginEdit(i: number) {
@@ -456,20 +478,15 @@ function displayTrans(key: string): string {
<div <div
v-for="(k,i) in tokens" v-for="(k,i) in tokens"
:key="k + '_' + i" :key="k + '_' + i"
:draggable="editingIndex !== i" :data-index="i"
:class="{ :class="{
'dragging': draggingIndex === i, 'dragging': draggingIndex === i,
'drag-over': overIndex === i && draggingIndex !== i, 'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
'drag-placeholder': overIndex === i && draggingIndex !== null && draggingIndex !== i, 'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
'editing': editingIndex === i 'editing': editingIndex === i
}" }"
class="pe-token-compact" class="pe-token-compact"
@dragstart="onDragStart(i, $event)" @pointerdown="onPointerDown(i, $event)"
@dragover="onDragOver(i, $event)"
@dragenter="onDragEnter(i, $event)"
@dragleave="onDragLeave"
@drop="onDrop(i, $event)"
@dragend="onDragEnd"
@dblclick="beginEdit(i)" @dblclick="beginEdit(i)"
:title="`${k} → ${displayTrans(k)}`" :title="`${k} → ${displayTrans(k)}`"
> >
@@ -537,20 +554,15 @@ function displayTrans(key: string): string {
<div <div
v-for="(k,i) in tokens" v-for="(k,i) in tokens"
:key="k + '_' + i" :key="k + '_' + i"
:draggable="true" :data-index="i"
:class="{ :class="{
'dragging': draggingIndex === i, 'dragging': draggingIndex === i,
'drag-over': overIndex === i && draggingIndex !== i, 'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
'drag-placeholder': overIndex === i && draggingIndex !== null && draggingIndex !== i, 'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
'editing': editingIndex === i || addingMapIndex === i 'editing': editingIndex === i || addingMapIndex === i
}" }"
class="pe-token-detail" class="pe-token-detail"
@dragstart="onDragStart(i, $event)" @pointerdown="onPointerDown(i, $event)"
@dragover="onDragOver(i, $event)"
@dragenter="onDragEnter(i, $event)"
@dragleave="onDragLeave"
@drop="onDrop(i, $event)"
@dragend="onDragEnd"
> >
<div class="pe-token-header"> <div class="pe-token-header">
<span class="pe-handle-detail"></span> <span class="pe-handle-detail"></span>
@@ -1286,6 +1298,7 @@ function displayTrans(key: string): string {
} }
.pe-token-detail { .pe-token-detail {
position: relative;
background-color: var(--color-bg-secondary); background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
@@ -1718,6 +1731,47 @@ function displayTrans(key: string): string {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
/* 插入方向指示:目标项向前/后移动并显示清晰插入方向 */
.pe-token-compact.insert-before,
.pe-token-detail.insert-before {
transform: translateX(10px);
border-color: var(--color-accent);
}
.pe-token-compact.insert-after,
.pe-token-detail.insert-after {
transform: translateX(-10px);
border-color: var(--color-accent);
}
.pe-token-compact.insert-before::before,
.pe-token-detail.insert-before::before {
content: '';
position: absolute;
left: -6px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 60%;
background-color: var(--color-accent);
border-radius: 2px;
opacity: 0.6;
}
.pe-token-compact.insert-after::after,
.pe-token-detail.insert-after::after {
content: '';
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 60%;
background-color: var(--color-accent);
border-radius: 2px;
opacity: 0.6;
}
/* 拖拽容器样式 */ /* 拖拽容器样式 */
.pe-drag-container { .pe-drag-container {
position: relative; position: relative;