|
|
@@ -1,5 +1,5 @@
|
|
|
<template>
|
|
|
- <div v-show="visible" class="smart-dialog" :style="dialogStyle" @mousedown="onRootMousedown" @dblclick="handleDoubleClick" :class="{ 'no-padding': noPadding }">
|
|
|
+ <div v-show="visible" class="smart-dialog" :style="dialogStyle" @mousedown="onRootMousedown" @dblclick="handleDoubleClick" :class="{ 'no-padding': noPadding, 'is-pending-drag': isPendingDrag, 'is-dragging': isDragging }">
|
|
|
<div class="dialog-header" :class="{ 'is-draggable': draggable }" @mousedown="startDrag" v-if="title">
|
|
|
<div class="title-content">
|
|
|
<slot name="header">
|
|
|
@@ -12,7 +12,7 @@
|
|
|
|
|
|
<div v-if="title" class="dialog-divider"></div>
|
|
|
|
|
|
- <div class="dialog-body" :class="{ 'no-padding': noPadding }">
|
|
|
+ <div class="dialog-body" :class="{ 'no-padding': noPadding }" @mousedown="onBodyMousedown">
|
|
|
<slot></slot>
|
|
|
</div>
|
|
|
|
|
|
@@ -57,9 +57,10 @@ export default {
|
|
|
y: 0,
|
|
|
w: 0,
|
|
|
h: 0,
|
|
|
- currentScale: 1,
|
|
|
+ currentScale: 1,
|
|
|
zIndex: globalZIndex,
|
|
|
isDragging: false,
|
|
|
+ isPendingDrag: false, // 用户按住 100ms 后, 视觉上提示"准备拖动"
|
|
|
isResizing: false,
|
|
|
dragOffset: { x: 0, y: 0 },
|
|
|
resizeStart: { x: 0, y: 0, w: 0, h: 0 }
|
|
|
@@ -67,13 +68,18 @@ export default {
|
|
|
},
|
|
|
computed: {
|
|
|
dialogStyle() {
|
|
|
+ // 拖动相关状态优先 → 显示 grabbing 表示"正在拖"
|
|
|
+ let cursor;
|
|
|
+ if (this.isDragging || this.isPendingDrag) cursor = 'grabbing';
|
|
|
+ else if (this.enableDblclickExpand) cursor = 'pointer';
|
|
|
+ else cursor = 'default';
|
|
|
return {
|
|
|
left: `${this.x}px`,
|
|
|
top: `${this.y}px`,
|
|
|
width: `${this.w}px`,
|
|
|
height: `${this.h}px`,
|
|
|
zIndex: this.zIndex,
|
|
|
- cursor: this.enableDblclickExpand ? 'pointer' : 'default'
|
|
|
+ cursor,
|
|
|
};
|
|
|
}
|
|
|
},
|
|
|
@@ -181,6 +187,52 @@ export default {
|
|
|
onRootMousedown() {
|
|
|
if (this.bringToFrontOnMousedown) this.bringToFront();
|
|
|
},
|
|
|
+
|
|
|
+ /** body 区域 mousedown 入口: "阈值 + 豁免"机制, 让用户在内容区任意位置按住拖动也能移动外层弹窗
|
|
|
+ * - 豁免清单内的元素 (button/input/拖动手柄等) → 直接放行, 不影响原交互
|
|
|
+ * - 其它位置 → 启动 5px 阈值监听; 100ms 后 cursor 变 grabbing 提示
|
|
|
+ * - 阈值内松开 → 原生 click 正常触发 (按钮 click 不丢)
|
|
|
+ * - 超阈值 → 调用 startDrag 移动外层弹窗 */
|
|
|
+ onBodyMousedown(e) {
|
|
|
+ if (!this.draggable) return;
|
|
|
+
|
|
|
+ // 豁免清单: 原生交互元素 / vuedraggable handle / SmartDialog 自身控件 / 业务声明
|
|
|
+ const EXEMPT = 'button, input, select, textarea, a, [contenteditable], [tabindex],'
|
|
|
+ + ' .drag-handle, .resize-handle, .close-btn, [data-no-drag]';
|
|
|
+ if (e.target.closest(EXEMPT)) return;
|
|
|
+
|
|
|
+ const startX = e.clientX, startY = e.clientY;
|
|
|
+ const THRESHOLD = 5;
|
|
|
+ const HINT_DELAY = 100; // 用户按住超过 100ms 给视觉提示
|
|
|
+ let dragStarted = false;
|
|
|
+
|
|
|
+ const hintTimer = setTimeout(() => {
|
|
|
+ if (!dragStarted) this.isPendingDrag = true;
|
|
|
+ }, HINT_DELAY);
|
|
|
+
|
|
|
+ const onMove = (ev) => {
|
|
|
+ if (dragStarted) return;
|
|
|
+ const dx = Math.abs(ev.clientX - startX);
|
|
|
+ const dy = Math.abs(ev.clientY - startY);
|
|
|
+ if (dx > THRESHOLD || dy > THRESHOLD) {
|
|
|
+ dragStarted = true;
|
|
|
+ clearTimeout(hintTimer);
|
|
|
+ this.isPendingDrag = false;
|
|
|
+ // 用初始 mousedown 位置触发拖动, 保证 dragOffset 准确
|
|
|
+ this.startDrag({ clientX: startX, clientY: startY, target: e.target });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const onUp = () => {
|
|
|
+ clearTimeout(hintTimer);
|
|
|
+ this.isPendingDrag = false;
|
|
|
+ document.removeEventListener('mousemove', onMove);
|
|
|
+ document.removeEventListener('mouseup', onUp);
|
|
|
+ };
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', onMove);
|
|
|
+ document.addEventListener('mouseup', onUp);
|
|
|
+ },
|
|
|
|
|
|
calculatePosition() {
|
|
|
this.$nextTick(() => {
|
|
|
@@ -294,6 +346,14 @@ export default {
|
|
|
|
|
|
<style scoped>
|
|
|
/* =========== CSS 保持不变 =========== */
|
|
|
+/* 拖动中/即将拖动时, 强制整个弹窗 (含子组件) 都显示 grabbing 光标作为视觉提示 */
|
|
|
+.smart-dialog.is-pending-drag,
|
|
|
+.smart-dialog.is-pending-drag *,
|
|
|
+.smart-dialog.is-dragging,
|
|
|
+.smart-dialog.is-dragging * {
|
|
|
+ cursor: grabbing !important;
|
|
|
+}
|
|
|
+
|
|
|
.smart-dialog {
|
|
|
position: fixed;
|
|
|
background: radial-gradient(circle at 20% 0%, rgba(40,120,200,0.2) 0%, rgba(20,60,130,0.4) 70%);
|