261 lines
8.4 KiB
Vue
261 lines
8.4 KiB
Vue
<template>
|
||
<div class="dock-layout-test w-full h-screen bg-gray-100">
|
||
<!-- 顶部控制栏(仿 DockPanelDemo.vue 样式) -->
|
||
<div class="demo-control-bar bg-white border-b border-gray-300 p-3 flex items-center justify-between">
|
||
<div class="flex items-center space-x-4">
|
||
<h1 class="text-lg font-semibold text-gray-800">停靠式面板容器演示</h1>
|
||
<div class="flex items-center space-x-2">
|
||
<button
|
||
@click="addFloatingPanel()"
|
||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||
>
|
||
添加浮动窗口
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center space-x-2">
|
||
<button
|
||
@click="exportLayout"
|
||
class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 text-sm"
|
||
>
|
||
导出布局
|
||
</button>
|
||
<input
|
||
ref="layoutFileInput"
|
||
type="file"
|
||
accept=".json"
|
||
style="display: none"
|
||
@change="handleLayoutFileSelect"
|
||
>
|
||
<button
|
||
@click="importLayout"
|
||
class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 text-sm"
|
||
>
|
||
导入布局
|
||
</button>
|
||
<button
|
||
@click="resetLayout"
|
||
class="px-3 py-1 bg-red-50 text-red-600 rounded hover:bg-red-100 text-sm"
|
||
>
|
||
重置布局
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 预留主区域(暂不放面板) -->
|
||
<div ref="panelHost" class="flex-1 w-full h-[calc(100%-4rem)] relative bg-gray-100">
|
||
<!-- 浮动面板渲染区 -->
|
||
<div
|
||
v-for="panel in floatingPanels"
|
||
:key="panel.id"
|
||
class="absolute bg-white shadow rounded border overflow-hidden"
|
||
:style="{ top: panel.y + 'px', left: panel.x + 'px', width: panel.width + 'px', height: panel.height + 'px' }"
|
||
>
|
||
<div class="flex flex-col h-full">
|
||
<!-- 标题栏:小三角移到右侧,压缩高度与间距 -->
|
||
<div class="h-6 bg-[#435d9c] text-white px-2 flex items-center justify-between select-none">
|
||
<div class="flex items-center">
|
||
<span class="text-xs">{{ panel.title }}</span>
|
||
</div>
|
||
<div class="flex items-center gap-0.5">
|
||
<button class="p-[2px] rounded hover:opacity-100 opacity-80" @click="toggleCollapse(panel.id)" aria-label="折叠/展开">
|
||
<span class="icon-triangle-down"></span>
|
||
</button>
|
||
<button class="p-[2px] rounded hover:opacity-100 opacity-80" @click="maximizePanel(panel.id)" aria-label="最大化">
|
||
<svg class="icon-square-svg" width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
|
||
<!-- 外框与底色(仅1px边框) -->
|
||
<rect x="0.5" y="0.5" width="10" height="10" fill="#cbd6ff" stroke="#8ea3d8" stroke-width="1" />
|
||
<!-- 两行填充:上行1px浅蓝,下行标题栏色 -->
|
||
<rect x="1" y="3" width="8.5" height="6.5" fill="#435d9c" />
|
||
</svg>
|
||
</button>
|
||
<button class="p-[2px] rounded hover:opacity-100 opacity-80" @click="closePanel(panel.id)" aria-label="关闭">
|
||
<span class="icon-x"></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- 工具栏:位于标题栏下方,右侧扩展钮 -->
|
||
<div class="h-6 bg-[#d5e2f6] text-[#2c3e7a] px-2 flex items-center justify-between border-b border-[#c7d2ea]">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-xs">工具栏</span>
|
||
<button v-if="panel.toolbarExpanded" class="px-2 py-0.5 text-xs bg-white/60 rounded hover:bg-white">示例按钮</button>
|
||
</div>
|
||
<button class="px-2 py-0.5 text-xs rounded hover:bg-white/40" @click="toggleToolbarExpand(panel.id)" aria-label="展开工具栏">
|
||
<i class="fa-solid" :class="panel.toolbarExpanded ? 'fa-angles-left' : 'fa-angles-right'"></i>
|
||
</button>
|
||
</div>
|
||
<!-- 内容区:可折叠 -->
|
||
<div class="bg-[#f5f7fb] flex-1" v-show="!panel.collapsed"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
|
||
// 顶部控制栏按钮的引用
|
||
const layoutFileInput = ref(null);
|
||
const panelHost = ref(null);
|
||
|
||
// 浮动面板列表
|
||
const floatingPanels = ref([]);
|
||
let nextPanelIdx = 1;
|
||
|
||
function addPanel(position) {
|
||
console.log('[DockLayoutTest] addPanel clicked:', position);
|
||
}
|
||
|
||
function addFloatingPanel() {
|
||
// 复原截图样式的默认面板参数
|
||
const defaultSize = { width: 300, height: 180 };
|
||
const offset = 16;
|
||
const idx = nextPanelIdx++;
|
||
const panel = {
|
||
id: 'float-' + idx,
|
||
title: '输出',
|
||
x: offset * idx,
|
||
y: offset * idx,
|
||
width: defaultSize.width,
|
||
height: defaultSize.height,
|
||
collapsed: false,
|
||
toolbarExpanded: false,
|
||
maximized: false,
|
||
};
|
||
floatingPanels.value.push(panel);
|
||
}
|
||
|
||
function closePanel(id) {
|
||
floatingPanels.value = floatingPanels.value.filter(p => p.id !== id);
|
||
}
|
||
|
||
function minimizePanel(id) {
|
||
const p = floatingPanels.value.find(p => p.id === id);
|
||
if (p) {
|
||
p.minimized = !p.minimized;
|
||
}
|
||
}
|
||
|
||
function maximizePanel(id) {
|
||
const p = floatingPanels.value.find(p => p.id === id);
|
||
if (!p) return;
|
||
const host = panelHost.value;
|
||
const rect = host && host.getBoundingClientRect ? host.getBoundingClientRect() : null;
|
||
|
||
if (!p.maximized) {
|
||
// 记录旧位置与尺寸
|
||
p.__prev = { x: p.x, y: p.y, width: p.width, height: p.height };
|
||
// 占满容器(留出1-2px避免外边框遮盖)
|
||
const w = rect ? Math.floor(rect.width) - 2 : 800;
|
||
const h = rect ? Math.floor(rect.height) - 2 : 600;
|
||
p.x = 0;
|
||
p.y = 0;
|
||
p.width = Math.max(300, w);
|
||
p.height = Math.max(180, h);
|
||
p.maximized = true;
|
||
} else {
|
||
// 还原旧位置与尺寸
|
||
if (p.__prev) {
|
||
p.x = p.__prev.x;
|
||
p.y = p.__prev.y;
|
||
p.width = p.__prev.width;
|
||
p.height = p.__prev.height;
|
||
}
|
||
p.maximized = false;
|
||
}
|
||
}
|
||
|
||
function exportLayout() {
|
||
console.log('[DockLayoutTest] exportLayout clicked');
|
||
}
|
||
|
||
function importLayout() {
|
||
if (layoutFileInput.value) {
|
||
layoutFileInput.value.click();
|
||
}
|
||
}
|
||
|
||
function handleLayoutFileSelect(event) {
|
||
const file = event?.target?.files?.[0];
|
||
console.log('[DockLayoutTest] handleLayoutFileSelect:', file?.name);
|
||
}
|
||
|
||
function resetLayout() {
|
||
console.log('[DockLayoutTest] resetLayout clicked');
|
||
}
|
||
|
||
function toggleCollapse(id) {
|
||
const p = floatingPanels.value.find(p => p.id === id);
|
||
if (p) {
|
||
p.collapsed = !p.collapsed;
|
||
}
|
||
}
|
||
|
||
function toggleToolbarExpand(id) {
|
||
const p = floatingPanels.value.find(p => p.id === id);
|
||
if (p) {
|
||
p.toolbarExpanded = !p.toolbarExpanded;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 可按需添加页面专属样式 */
|
||
.icon-triangle-down { /* 向下小三角(更小更细) */
|
||
width: 0; height: 0;
|
||
border-left: 5px solid transparent;
|
||
border-right: 5px solid transparent;
|
||
border-top: 6px solid #cbd6ff;
|
||
display: inline-block;
|
||
}
|
||
.icon-square { /* 最大化图标:外层浅蓝方块 + 内层细边框 */
|
||
position: relative;
|
||
width: 11px;
|
||
height: 11px;
|
||
background: linear-gradient(180deg, #cbd6ff 0%, #b9c8ff 100%);
|
||
border: 1px solid #b8c6ff;
|
||
box-sizing: border-box;
|
||
}
|
||
.icon-square::before { /* 内层小方框边线(回退为细线) */
|
||
content: '';
|
||
position: absolute;
|
||
left: 2px;
|
||
top: 2px;
|
||
width: 7px;
|
||
height: 7px;
|
||
border: 1px solid #b8c6ff;
|
||
box-sizing: border-box;
|
||
background: transparent;
|
||
}
|
||
.icon-square::after { /* 顶部工具栏小块 */
|
||
content: '';
|
||
position: absolute;
|
||
left: 3px; /* 内边距 1px */
|
||
top: 3px;
|
||
width: 5px; /* 与内框宽度对应 */
|
||
height: 2px; /* 小工具栏高度 */
|
||
background: #b8c6ff; /* 与边线同色,形成上沿条 */
|
||
border-radius: 0.5px;
|
||
}
|
||
.icon-x { /* X(略小) */
|
||
position: relative; width: 11px; height: 11px; display: inline-block;
|
||
}
|
||
.icon-x::before, .icon-x::after {
|
||
content: ''; position: absolute; left: 5px; top: 0; width: 1px; height: 11px; background: #e6efff;
|
||
}
|
||
.icon-x::before { transform: rotate(45deg); }
|
||
.icon-x::after { transform: rotate(-45deg); }
|
||
</style>
|
||
/* 禁用旧的最大化伪元素,避免影响SVG呈现 */
|
||
.icon-square::before,
|
||
.icon-square::after {
|
||
content: none !important;
|
||
display: none !important;
|
||
border: 0 !important;
|
||
}
|
||
.icon-square-svg {
|
||
width: 11px;
|
||
height: 11px;
|
||
display: inline-block;
|
||
} |