修复Area组件最大化功能,确保填充满父容器

This commit is contained in:
zqm
2025-11-04 09:10:15 +08:00
parent 7df715f612
commit 8c8419d3e3
3 changed files with 109 additions and 27 deletions

View File

@@ -1,6 +1,10 @@
<template>
<div class="vs-area-wrapper">
<div class="vs-area select-none" :class="{ 'is-maximized': isMaximized }" :style="areaStyle">
<div
class="vs-area select-none"
:class="{ 'is-maximized': isMaximized, 'is-normal': !isMaximized }"
:style="areaStyle"
>
<!-- 顶部标题栏 -->
<div v-if="showTitleBar" class="vs-title-bar">
<div class="vs-title-left">
@@ -32,7 +36,7 @@
clip-rule="evenodd"
d="M1 4 L4 4 L4 1 L11 1 L11 8 L8 8 L8 11 L1 11 Z
M5 4 L5 3 L10 3 L10 7 L8 7 L8 4 Z
M2 6 L12.6 5 L7 6 L7 10 L2 10 Z" />
M2 6 L12.6 5 L7 6 L7 10 L2 10 Z" />
</svg>
</button>
<button class="button-icon p-[2px] rounded hover:opacity-100 opacity-80" aria-label="关闭" @click="onClose">
@@ -44,9 +48,9 @@
</div>
</div>
<!-- 内容区域 -->
<!-- 内容区域 -->
<div class="vs-content">
<!-- 内容区域已清空 -->
<!-- 内容区域 -->
</div>
</div>
</div>
@@ -59,35 +63,68 @@ const props = defineProps({
id: { type: String, required: true },
title: { type: String, default: '面板区' },
resizable: { type: Boolean, default: true },
// 新增:初始状态(支持中文值)
// 初始状态(支持中文值)
WindowState: { type: String, default: '正常' },
// 新增:默认尺寸
// 默认尺寸
width: { type: Number, default: 300 },
height: { type: Number, default: 250 },
// 新增:控制标题栏显示
showTitleBar: { type: Boolean, default: true }
// 控制标题栏显示
showTitleBar: { type: Boolean, default: true },
// 位置属性,可选
left: { type: Number, default: undefined },
top: { type: Number, default: undefined }
})
// 本地状态:不再向父组件发事件,内部维护最大化/还原
// 本地状态
const localState = ref(props.WindowState)
// 保存原始位置和大小信息
const originalPosition = ref({
width: props.width,
height: props.height,
left: props.left,
top: props.top
})
// 根据本地状态计算是否最大化
const isMaximized = computed(() => localState.value === '最大化' || localState.value === 'maximized')
// 新增:根据状态计算尺寸样式
// 根据状态计算尺寸和位置样式
const areaStyle = computed(() => {
if (isMaximized.value) {
return { width: '100%', height: '100%' }
// 最大化时填充满父容器
return {
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: 0,
zIndex: 100
}
}
return { width: `${props.width}px`, height: `${props.height}px` }
// 非最大化状态:使用原始位置或默认居中
const style = {
width: `${originalPosition.value.width}px`,
height: `${originalPosition.value.height}px`
}
// 如果有明确的位置,则使用指定位置
if (originalPosition.value.left !== undefined) {
style.left = `${originalPosition.value.left}px`
}
if (originalPosition.value.top !== undefined) {
style.top = `${originalPosition.value.top}px`
}
return style
})
const emit = defineEmits(['close'])
const emit = defineEmits(['close', 'update:WindowState'])
const onToggleMaximize = () => {
const next = isMaximized.value ? '正常' : '最大化'
// 直接更新本地状态,不再 emit 到父组件
localState.value = next
emit('update:WindowState', next)
}
const onClose = () => emit('close')
@@ -110,6 +147,15 @@ const onClose = () => emit('close')
}
/* 容器 */
.vs-area-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
position: relative;
}
.vs-area {
display: flex;
flex-direction: column;
@@ -119,6 +165,23 @@ const onClose = () => emit('close')
min-height: 250px;
}
/* 正常状态样式 */
.vs-area.is-normal {
position: static;
}
/* 最大化状态样式 */
.vs-area.is-maximized {
width: 100% !important;
height: 100% !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
z-index: 100 !important;
margin: 0;
padding: 0;
}
/* 标题栏 */
.vs-title-bar {
height: 28px;
@@ -195,19 +258,27 @@ const onClose = () => emit('close')
:deep(::-webkit-scrollbar-thumb:hover) { background: linear-gradient(to bottom, #c1c7e2, #b2b8d9); }
:deep(*) { box-sizing: border-box; }
.vs-area.is-maximized { width: 100%; height: 100%; }
.vs-area.is-maximized {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 100;
}
.vs-icon-stage { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: transparent; overflow: auto; }
.vs-app-icon--x200 { width: 2800px; height: 2800px; }
.vs-app-icon { width: 14px; height: 14px; display: inline-block; background: transparent; opacity: 0.95; }
.vs-icon { width: 100%; height: 100%; shape-rendering: crispEdges; }
.vs-app-icon svg { display: block; }
/* 外层包裹,居中内容 */
/* 外层包裹,确保最大化时填充父容器,非最大化时居中 */
.vs-area-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -1,26 +1,33 @@
<template>
<div class="dock-layout">
<!-- 主区域 -->
<Area :WindowState="windowState" :showTitleBar="false" title="主区域" />
<!-- 浮动区域列表 -->
<Area
v-for="area in floatingAreas"
:key="area.id"
:id="area.id"
:title="area.title"
:WindowState="area.WindowState"
v-model:WindowState="area.WindowState"
:showTitleBar="area.showTitleBar"
:width="area.width"
:height="area.height"
:style="{
:left="area.WindowState !== '最大化' ? area.x : undefined"
:top="area.WindowState !== '最大化' ? area.y : undefined"
:style="area.WindowState !== '最大化' ? {
position: 'absolute',
left: area.x + 'px',
top: area.y + 'px',
zIndex: 10
} : {
zIndex: 100
}"
@close="onCloseFloatingArea(area.id)"
/>
<!-- 主区域 -->
<Area
:WindowState="windowState"
:showTitleBar="false"
title="主区域"
:style="{ position: 'relative', zIndex: 1 }"
/>
</div>
</template>
@@ -100,7 +107,7 @@ defineExpose({
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
overflow: visible;
}
/* 浮动区域样式已直接应用到Area组件 */

View File

@@ -0,0 +1,4 @@
### Area
1. 初始添加时默认宽300px高250px。位置相对父容器水平居中垂直居中。
2. 最大化时,填充满父容器。
3. 还原时,恢复到最大化前的位置和大小。