Files
JoyD/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue

269 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dock-layout" ref="dockLayoutRef">
<!-- 浮动区域列表 -->
<Area
v-for="area in floatingAreas"
:key="area.id"
:id="area.id"
:title="area.title"
v-model:WindowState="area.WindowState"
:showTitleBar="false"
:width="area.width"
:height="area.height"
:left="area.WindowState !== '最大化' ? area.x : undefined"
:top="area.WindowState !== '最大化' ? area.y : undefined"
:style="area.WindowState !== '最大化' ? {
position: 'absolute',
zIndex: 10
} : {
zIndex: 100
}"
@close="onCloseFloatingArea(area.id)"
@update:position="onUpdatePosition(area.id, $event)"
>
<!-- 每个Area内渲染其包含的Panels -->
<Panel
v-for="panel in area.panels"
:key="panel.id"
:id="panel.id"
:title="panel.title"
:collapsed="panel.collapsed"
:toolbarExpanded="panel.toolbarExpanded"
@toggleCollapse="onToggleCollapse"
@maximize="onMaximize"
@close="onClosePanel(area.id, panel.id)"
@toggleToolbar="onToggleToolbar"
@dragStart="onPanelDragStart(area.id, $event)"
@dragMove="onPanelDragMove(area.id, $event)"
@dragEnd="onPanelDragEnd"
/>
</Area>
<!-- 主区域 -->
<Area
:WindowState="windowState"
:showTitleBar="false"
title="主区域"
:style="{ position: 'relative', zIndex: 1 }"
/>
</div>
</template>
<script setup>
import { ref, defineExpose, nextTick } from 'vue'
import Area from './Area.vue';
import Panel from './Panel.vue';
// 主区域状态
const windowState = ref('最大化')
// 浮动区域列表 - 每个area包含panels数组
const floatingAreas = ref([])
// 容器引用
const dockLayoutRef = ref(null)
// 区域ID计数器
let areaIdCounter = 1
// Panel拖拽相关状态
const panelDragState = ref({
isDragging: false,
currentAreaId: null,
startClientPos: { x: 0, y: 0 },
startAreaPos: { x: 0, y: 0 }
})
// 添加新的浮动面板
const addFloatingPanel = () => {
// 获取父容器尺寸以计算居中位置
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组件配置
const newArea = {
id: `floating-area-${areaIdCounter++}`,
title: `浮动区域 ${areaIdCounter - 1}`,
x: x,
y: y,
width: 300,
height: 250,
WindowState: '正常',
showTitleBar: true,
// 添加一个Panel填充满父容器
panels: [
{
id: `panel-${areaIdCounter - 1}-1`,
title: `面板 1`,
x: 0,
y: 0,
width: 100,
height: 100,
collapsed: false,
toolbarExpanded: false
}
]
}
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 area = floatingAreas.value.find(a => a.id === id)
if (area) {
area.collapsed = !area.collapsed
}
}
// 最大化/还原
const onMaximize = (panelId) => {
// 查找包含该面板的区域
for (const area of floatingAreas.value) {
if (area.panels && area.panels.length === 1 && area.panels[0].id === panelId) {
// 当区域只包含一个Panel时切换Area的最大化状态并同时最大化Panel
if (area.WindowState === '最大化' || area.WindowState === 'maximized') {
area.WindowState = '正常'
} else {
area.WindowState = '最大化'
}
console.log('Panel最大化按钮触发切换Area状态:', area.WindowState)
break
}
}
}
// 关闭浮动区域
const onCloseFloatingArea = (id) => {
const index = floatingAreas.value.findIndex(a => a.id === id)
if (index !== -1) {
floatingAreas.value.splice(index, 1)
}
}
// 关闭面板
const onClosePanel = (areaId, panelId) => {
const area = floatingAreas.value.find(a => a.id === areaId)
if (area && area.panels) {
const panelIndex = area.panels.findIndex(p => p.id === panelId)
if (panelIndex !== -1) {
area.panels.splice(panelIndex, 1)
// 如果区域内没有面板了,可以考虑关闭整个区域
if (area.panels.length === 0) {
onCloseFloatingArea(areaId)
}
}
}
}
// 切换工具栏
const onToggleToolbar = (id) => {
const area = floatingAreas.value.find(a => a.id === id)
if (area) {
area.toolbarExpanded = !area.toolbarExpanded
}
}
// Panel拖拽开始
const onPanelDragStart = (areaId, event) => {
const area = floatingAreas.value.find(a => a.id === areaId)
// 只有当Area中只有一个Panel时才允许通过Panel标题栏移动Area
if (area && area.panels.length === 1) {
panelDragState.value.isDragging = true
panelDragState.value.currentAreaId = areaId
panelDragState.value.startClientPos = {
x: event.clientX,
y: event.clientY
}
panelDragState.value.startAreaPos = {
x: area.x,
y: area.y
}
console.log('Panel拖拽开始移动Area:', areaId)
}
}
// Panel拖拽移动
const onPanelDragMove = (areaId, event) => {
if (panelDragState.value.isDragging && panelDragState.value.currentAreaId === areaId) {
const area = floatingAreas.value.find(a => a.id === areaId)
if (area) {
// 计算移动距离
const deltaX = event.clientX - panelDragState.value.startClientPos.x
const deltaY = event.clientY - panelDragState.value.startClientPos.y
// 计算新位置
let newLeft = panelDragState.value.startAreaPos.x + deltaX
let newTop = panelDragState.value.startAreaPos.y + deltaY
// 确保不超出父容器边界
if (dockLayoutRef.value) {
const parentRect = dockLayoutRef.value.getBoundingClientRect()
// 严格边界检查
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - area.width))
newTop = Math.max(0, Math.min(newTop, parentRect.height - area.height))
}
// 更新位置
area.x = newLeft
area.y = newTop
// 调试信息
console.log('Panel拖拽移动Area新位置:', { x: newLeft, y: newTop })
}
}
}
// Panel拖拽结束
const onPanelDragEnd = () => {
console.log('Panel拖拽结束')
panelDragState.value.isDragging = false
panelDragState.value.currentAreaId = null
}
// 暴露方法给父组件
defineExpose({
addFloatingPanel
})
</script>
<style scoped>
.dock-layout {
position: relative;
width: 100%;
height: 100%;
overflow: visible;
}
/* 浮动区域样式已直接应用到Area组件 */
/* 添加浮动区域按钮样式 */
.add-floating-btn {
font-size: 14px;
cursor: pointer;
user-select: none;
}
.add-floating-btn:active {
transform: scale(0.98);
}
</style>