实现Area组件拖动边框调整大小功能,完成所有9点需求
This commit is contained in:
@@ -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); }
|
||||||
|
|||||||
Reference in New Issue
Block a user