实现Area组件拖动边框调整大小功能,完成所有9点需求

This commit is contained in:
zqm
2025-11-04 17:00:26 +08:00
parent cadf6f3c89
commit 4ebaf069fd

View File

@@ -5,6 +5,47 @@
:class="{ 'is-maximized': isMaximized, 'is-normal': !isMaximized }" :class="{ 'is-maximized': isMaximized, 'is-normal': !isMaximized }"
:style="areaStyle" :style="areaStyle"
> >
<!-- 调整大小的边框 -->
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-nw"
@mousedown="onResizeStart('nw', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-ne"
@mousedown="onResizeStart('ne', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-sw"
@mousedown="onResizeStart('sw', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-se"
@mousedown="onResizeStart('se', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-n"
@mousedown="onResizeStart('n', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-e"
@mousedown="onResizeStart('e', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-s"
@mousedown="onResizeStart('s', $event)"
></div>
<div
v-if="resizable && !isMaximized"
class="resize-handle resize-handle-w"
@mousedown="onResizeStart('w', $event)"
></div>
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
<div v-if="showTitleBar" class="vs-title-bar" :class="{ 'cursor-move': !isMaximized }" @mousedown="onDragStart"> <div v-if="showTitleBar" class="vs-title-bar" :class="{ 'cursor-move': !isMaximized }" @mousedown="onDragStart">
<div class="vs-title-left"> <div class="vs-title-left">
@@ -93,6 +134,13 @@ const isDragging = ref(false)
const dragStartPos = ref({ x: 0, y: 0 }) const dragStartPos = ref({ x: 0, y: 0 })
const areaStartPos = ref({ x: 0, y: 0 }) const areaStartPos = ref({ x: 0, y: 0 })
// 调整大小相关状态
const isResizing = ref(false)
const resizeStartPos = ref({ x: 0, y: 0 })
const resizeDirection = ref(null)
const resizeStartSize = ref({ width: 0, height: 0 })
const resizeStartAreaPos = ref({ left: 0, top: 0 })
// 父容器引用 // 父容器引用
const parentContainer = ref(null) const parentContainer = ref(null)
@@ -229,6 +277,135 @@ const onDragEnd = () => {
document.removeEventListener('mouseup', onDragEnd) document.removeEventListener('mouseup', onDragEnd)
} }
// 调整大小开始
const onResizeStart = (direction, e) => {
if (isMaximized.value) return
isResizing.value = true
resizeDirection.value = direction
resizeStartPos.value = {
x: e.clientX,
y: e.clientY
}
resizeStartSize.value = {
width: originalPosition.value.width,
height: originalPosition.value.height
}
resizeStartAreaPos.value = {
left: originalPosition.value.left,
top: originalPosition.value.top
}
// 添加全局事件监听
document.addEventListener('mousemove', onResizeMove)
document.addEventListener('mouseup', onResizeEnd)
document.addEventListener('mouseleave', onResizeEnd)
// 防止文本选择
e.preventDefault()
e.stopPropagation()
}
// 调整大小移动
const onResizeMove = (e) => {
if (!isResizing.value) return
const deltaX = e.clientX - resizeStartPos.value.x
const deltaY = e.clientY - resizeStartPos.value.y
let newWidth = resizeStartSize.value.width
let newHeight = resizeStartSize.value.height
let newLeft = resizeStartAreaPos.value.left
let newTop = resizeStartAreaPos.value.top
// 根据方向调整大小
switch (resizeDirection.value) {
case 'nw':
newWidth = Math.max(200, resizeStartSize.value.width - deltaX)
newHeight = Math.max(150, resizeStartSize.value.height - deltaY)
newLeft = resizeStartPos.value.left + deltaX
newTop = resizeStartPos.value.top + deltaY
break
case 'ne':
newWidth = Math.max(200, resizeStartSize.value.width + deltaX)
newHeight = Math.max(150, resizeStartSize.value.height - deltaY)
newTop = resizeStartPos.value.top + deltaY
break
case 'sw':
newWidth = Math.max(200, resizeStartSize.value.width - deltaX)
newHeight = Math.max(150, resizeStartSize.value.height + deltaY)
newLeft = resizeStartPos.value.left + deltaX
break
case 'se':
newWidth = Math.max(200, resizeStartSize.value.width + deltaX)
newHeight = Math.max(150, resizeStartSize.value.height + deltaY)
break
case 'n':
newHeight = Math.max(150, resizeStartSize.value.height - deltaY)
newTop = resizeStartPos.value.top + deltaY
break
case 'e':
newWidth = Math.max(200, resizeStartSize.value.width + deltaX)
break
case 's':
newHeight = Math.max(150, resizeStartSize.value.height + deltaY)
break
case 'w':
newWidth = Math.max(200, resizeStartSize.value.width - deltaX)
newLeft = resizeStartPos.value.left + deltaX
break
}
// 确保不超出父容器边界
if (parentContainer.value) {
const parentRect = parentContainer.value.getBoundingClientRect()
// 右边界检查
if (newLeft + newWidth > parentRect.width) {
newWidth = parentRect.width - newLeft
}
// 下边界检查
if (newTop + newHeight > parentRect.height) {
newHeight = parentRect.height - newTop
}
// 左边界检查
if (newLeft < 0) {
newWidth += newLeft
newLeft = 0
}
// 上边界检查
if (newTop < 0) {
newHeight += newTop
newTop = 0
}
}
// 更新位置和大小
originalPosition.value.width = newWidth
originalPosition.value.height = newHeight
originalPosition.value.left = newLeft
originalPosition.value.top = newTop
// 通知父组件位置变化
emit('update:position', {
left: newLeft,
top: newTop
})
// 防止文本选择
e.preventDefault()
}
// 调整大小结束
const onResizeEnd = () => {
isResizing.value = false
resizeDirection.value = null
// 移除全局事件监听
document.removeEventListener('mousemove', onResizeMove)
document.removeEventListener('mouseup', onResizeEnd)
document.removeEventListener('mouseleave', onResizeEnd)
}
const onToggleMaximize = () => { const onToggleMaximize = () => {
const next = isMaximized.value ? '正常' : '最大化' const next = isMaximized.value ? '正常' : '最大化'
@@ -402,6 +579,89 @@ onMounted(() => {
min-height: 0; min-height: 0;
} }
/* 调整大小的手柄样式 */
.resize-handle {
position: absolute;
z-index: 20;
background: transparent;
pointer-events: auto;
}
/* 四个角 */
.resize-handle-nw {
width: 12px;
height: 12px;
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.resize-handle-ne {
width: 12px;
height: 12px;
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.resize-handle-sw {
width: 12px;
height: 12px;
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.resize-handle-se {
width: 12px;
height: 12px;
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
/* 四条边 */
.resize-handle-n {
height: 12px;
top: -6px;
left: 12px;
right: 12px;
cursor: ns-resize;
}
.resize-handle-e {
width: 12px;
right: -6px;
top: 12px;
bottom: 12px;
cursor: ew-resize;
}
.resize-handle-s {
height: 12px;
bottom: -6px;
left: 12px;
right: 12px;
cursor: ns-resize;
}
.resize-handle-w {
width: 12px;
left: -6px;
top: 12px;
bottom: 12px;
cursor: ew-resize;
}
/* 鼠标悬停在边框上时的样式提示 */
.vs-area.is-normal:not(:hover) .resize-handle {
opacity: 0;
}
.vs-area.is-normal:hover .resize-handle {
opacity: 0.5;
}
/* 左侧输出 */ /* 左侧输出 */
.vs-left { flex: 1; background: var(--vs-panel); display: flex; } .vs-left { flex: 1; background: var(--vs-panel); display: flex; }
.left-blank { flex: 1; background: #eef1f9; border-right: 1px solid var(--vs-border); } .left-blank { flex: 1; background: #eef1f9; border-right: 1px solid var(--vs-border); }