Files
JoyD/AutoRobot/Windows/Robot/Web/src/views/DockLayoutTest.vue
2025-10-29 22:35:18 +08:00

261 lines
8.4 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-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;
}