实现Area组件拖拽功能和边界限制,完善初始居中定位
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
:style="areaStyle"
|
:style="areaStyle"
|
||||||
>
|
>
|
||||||
<!-- 顶部标题栏 -->
|
<!-- 顶部标题栏 -->
|
||||||
<div v-if="showTitleBar" class="vs-title-bar">
|
<div v-if="showTitleBar" class="vs-title-bar" :class="{ 'cursor-move': !isMaximized }" @mousedown="onDragStart">
|
||||||
<div class="vs-title-left">
|
<div class="vs-title-left">
|
||||||
<div class="vs-app-icon" aria-label="AppIcon">
|
<div class="vs-app-icon" aria-label="AppIcon">
|
||||||
<svg class="vs-icon" viewBox="0 0 22.4 22.4" aria-hidden="true">
|
<svg class="vs-icon" viewBox="0 0 22.4 22.4" aria-hidden="true">
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, computed, defineEmits, ref } from 'vue'
|
import { defineProps, computed, defineEmits, ref, onMounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: { type: String, required: true },
|
id: { type: String, required: true },
|
||||||
@@ -85,6 +85,14 @@ const originalPosition = ref({
|
|||||||
top: props.top
|
top: props.top
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 拖拽相关状态
|
||||||
|
const isDragging = ref(false)
|
||||||
|
const dragStartPos = ref({ x: 0, y: 0 })
|
||||||
|
const areaStartPos = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
// 父容器引用
|
||||||
|
const parentContainer = ref(null)
|
||||||
|
|
||||||
// 根据本地状态计算是否最大化
|
// 根据本地状态计算是否最大化
|
||||||
const isMaximized = computed(() => localState.value === '最大化' || localState.value === 'maximized')
|
const isMaximized = computed(() => localState.value === '最大化' || localState.value === 'maximized')
|
||||||
|
|
||||||
@@ -98,7 +106,9 @@ const areaStyle = computed(() => {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
zIndex: 100
|
zIndex: 100,
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +129,69 @@ const areaStyle = computed(() => {
|
|||||||
return style
|
return style
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['close', 'update:WindowState'])
|
const emit = defineEmits(['close', 'update:WindowState', 'update:position'])
|
||||||
|
|
||||||
|
// 拖拽开始
|
||||||
|
const onDragStart = (e) => {
|
||||||
|
// 最大化状态下不允许拖拽
|
||||||
|
if (isMaximized.value) return
|
||||||
|
|
||||||
|
isDragging.value = true
|
||||||
|
dragStartPos.value = {
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY
|
||||||
|
}
|
||||||
|
areaStartPos.value = {
|
||||||
|
x: originalPosition.value.left || 0,
|
||||||
|
y: originalPosition.value.top || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加全局事件监听
|
||||||
|
document.addEventListener('mousemove', onDragMove)
|
||||||
|
document.addEventListener('mouseup', onDragEnd)
|
||||||
|
|
||||||
|
// 防止文本选择
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽移动
|
||||||
|
const onDragMove = (e) => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
|
||||||
|
// 计算移动距离
|
||||||
|
const deltaX = e.clientX - dragStartPos.value.x
|
||||||
|
const deltaY = e.clientY - dragStartPos.value.y
|
||||||
|
|
||||||
|
// 计算新位置
|
||||||
|
let newLeft = areaStartPos.value.x + deltaX
|
||||||
|
let newTop = areaStartPos.value.y + deltaY
|
||||||
|
|
||||||
|
// 如果有父容器引用,应用边界限制
|
||||||
|
if (parentContainer.value) {
|
||||||
|
const parentRect = parentContainer.value.getBoundingClientRect()
|
||||||
|
const areaWidth = originalPosition.value.width
|
||||||
|
const areaHeight = originalPosition.value.height
|
||||||
|
|
||||||
|
// 确保不超出父容器边界
|
||||||
|
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - areaWidth))
|
||||||
|
newTop = Math.max(0, Math.min(newTop, parentRect.height - areaHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新位置
|
||||||
|
originalPosition.value.left = newLeft
|
||||||
|
originalPosition.value.top = newTop
|
||||||
|
|
||||||
|
// 通知父组件位置变化
|
||||||
|
emit('update:position', { left: newLeft, top: newTop })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽结束
|
||||||
|
const onDragEnd = () => {
|
||||||
|
isDragging.value = false
|
||||||
|
// 移除全局事件监听
|
||||||
|
document.removeEventListener('mousemove', onDragMove)
|
||||||
|
document.removeEventListener('mouseup', onDragEnd)
|
||||||
|
}
|
||||||
|
|
||||||
const onToggleMaximize = () => {
|
const onToggleMaximize = () => {
|
||||||
const next = isMaximized.value ? '正常' : '最大化'
|
const next = isMaximized.value ? '正常' : '最大化'
|
||||||
@@ -128,6 +200,11 @@ const onToggleMaximize = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => emit('close')
|
const onClose = () => emit('close')
|
||||||
|
|
||||||
|
// 组件挂载后获取父容器引用
|
||||||
|
onMounted(() => {
|
||||||
|
parentContainer.value = document.querySelector('.dock-layout') || window
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dock-layout">
|
<div class="dock-layout" ref="dockLayoutRef">
|
||||||
<!-- 浮动区域列表 -->
|
<!-- 浮动区域列表 -->
|
||||||
<Area
|
<Area
|
||||||
v-for="area in floatingAreas"
|
v-for="area in floatingAreas"
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
zIndex: 100
|
zIndex: 100
|
||||||
}"
|
}"
|
||||||
@close="onCloseFloatingArea(area.id)"
|
@close="onCloseFloatingArea(area.id)"
|
||||||
|
@update:position="onUpdatePosition(area.id, $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 主区域 -->
|
<!-- 主区域 -->
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineExpose } from 'vue'
|
import { ref, defineExpose, nextTick } from 'vue'
|
||||||
import Area from './Area.vue';
|
import Area from './Area.vue';
|
||||||
import Panel from './Panel.vue';
|
import Panel from './Panel.vue';
|
||||||
|
|
||||||
@@ -42,25 +43,50 @@ const windowState = ref('最大化')
|
|||||||
// 浮动区域列表
|
// 浮动区域列表
|
||||||
const floatingAreas = ref([])
|
const floatingAreas = ref([])
|
||||||
|
|
||||||
|
// 容器引用
|
||||||
|
const dockLayoutRef = ref(null)
|
||||||
|
|
||||||
// 区域ID计数器
|
// 区域ID计数器
|
||||||
let areaIdCounter = 1
|
let areaIdCounter = 1
|
||||||
|
|
||||||
// 添加新的浮动区域
|
// 添加新的浮动区域
|
||||||
const addFloatingArea = () => {
|
const addFloatingArea = () => {
|
||||||
|
// 获取父容器尺寸以计算居中位置
|
||||||
|
let x = 50 + (areaIdCounter - 2) * 20
|
||||||
|
let y = 50 + (areaIdCounter - 2) * 20
|
||||||
|
|
||||||
|
// 如果容器已渲染,计算居中位置
|
||||||
|
if (dockLayoutRef.value) {
|
||||||
|
const containerRect = dockLayoutRef.value.getBoundingClientRect()
|
||||||
|
const width = 300
|
||||||
|
const height = 250
|
||||||
|
x = Math.floor((containerRect.width - width) / 2)
|
||||||
|
y = Math.floor((containerRect.height - height) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
// 创建新的Area组件配置
|
// 创建新的Area组件配置
|
||||||
const newArea = {
|
const newArea = {
|
||||||
id: `floating-area-${areaIdCounter++}`,
|
id: `floating-area-${areaIdCounter++}`,
|
||||||
title: `浮动区域 ${areaIdCounter - 1}`,
|
title: `浮动区域 ${areaIdCounter - 1}`,
|
||||||
x: 50 + (areaIdCounter - 2) * 20, // 错开位置
|
x: x,
|
||||||
y: 50 + (areaIdCounter - 2) * 20,
|
y: y,
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 200,
|
height: 250,
|
||||||
WindowState: '正常',
|
WindowState: '正常',
|
||||||
showTitleBar: true
|
showTitleBar: true
|
||||||
}
|
}
|
||||||
floatingAreas.value.push(newArea)
|
floatingAreas.value.push(newArea)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新区域位置
|
||||||
|
const onUpdatePosition = (id, position) => {
|
||||||
|
const area = floatingAreas.value.find(a => a.id === id)
|
||||||
|
if (area) {
|
||||||
|
area.x = position.left
|
||||||
|
area.y = position.top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 切换折叠状态
|
// 切换折叠状态
|
||||||
const onToggleCollapse = (id) => {
|
const onToggleCollapse = (id) => {
|
||||||
const area = floatingAreas.value.find(a => a.id === id)
|
const area = floatingAreas.value.find(a => a.id === id)
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
1. 初始添加时,默认宽300px,高250px。位置相对父容器水平居中,垂直居中。
|
1. 初始添加时,默认宽300px,高250px。位置相对父容器水平居中,垂直居中。
|
||||||
2. 最大化时,填充满父容器。
|
2. 最大化时,填充满父容器。
|
||||||
3. 还原时,恢复到最大化前的位置和大小。
|
3. 还原时,恢复到最大化前的位置和大小。
|
||||||
|
4. 关闭时,从父容器中移除。
|
||||||
|
5. 拖拽时,不允许超出父容器边界。
|
||||||
Reference in New Issue
Block a user