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

186 lines
5.6 KiB
Vue
Raw Normal View History

2025-10-31 21:58:33 +08:00
<template>
<div class="panel absolute bg-white shadow rounded border overflow-hidden"
:style="{ top: y + 'px', left: x + 'px', width: width + 'px', height: height + 'px' }">
<div class="flex flex-col h-full">
<!-- 标题栏 -->
<div class="title-bar h-6 bg-[#435d9c] text-white px-2 flex items-center justify-between select-none cursor-move"
@mousedown="onDragStart">
2025-10-31 21:58:33 +08:00
<div class="flex items-center">
<span class="text-xs">{{ title }}</span>
</div>
<div class="title-bar-buttons flex items-center gap-0.5">
<button class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
@click="onToggleCollapse"
aria-label="折叠/展开">
<!-- 向下小三角使用内联SVG避免样式作用域问题 -->
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
<polygon points="5.5,8 2,3.5 9,3.5" fill="#cbd6ff" />
</svg>
</button>
<button class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
@click="onMaximize"
aria-label="最大化">
<!-- 最大化图标仅外框1px + 两行填充 -->
<svg class="icon-square-svg" width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
<rect x="0.5" y="0.5" width="10" height="10" fill="#cbd6ff" stroke="#8ea3d8" stroke-width="1" />
<rect x="3" y="3" width="5" height="1" fill="#b8c6ff" />
<!-- 下行采用更宽填充以贴近示例图 -->
<rect x="1" y="3" width="8.5" height="6.5" fill="#435d9c" />
</svg>
</button>
<button class="button-icon p-[2px] rounded hover:opacity-100 opacity-80"
@click="onClose"
aria-label="关闭">
<!-- 关闭图标X内联SVG确保1px线条 -->
<svg width="11" height="11" viewBox="0 0 11 11" aria-hidden="true">
<line x1="2" y1="2" x2="9" y2="9" stroke="#e6efff" stroke-width="1" />
<line x1="2" y1="9" x2="9" y2="2" stroke="#e6efff" stroke-width="1" />
</svg>
</button>
</div>
</div>
<!-- 工具栏位于标题栏下方右侧扩展钮 -->
<div class="toolbar h-6 bg-[#d5e2f6] text-[#2c3e7a] px-2 flex items-center justify-between border-b border-[#c7d2ea]">
<div class="toolbar-left flex items-center gap-2">
<span class="text-xs">工具栏</span>
<button v-if="toolbarExpanded" class="toolbar-button px-2 py-0.5 text-xs bg-white/60 rounded hover:bg-white">示例按钮</button>
</div>
<button class="toolbar-toggle px-2 py-0.5 text-xs rounded hover:bg-white/40"
@click="onToggleToolbar"
aria-label="展开工具栏">
<i class="fa-solid" :class="toolbarExpanded ? 'fa-angles-left' : 'fa-angles-right'"></i>
</button>
</div>
<!-- 内容区可折叠 -->
<div class="content-area bg-[#f5f7fb] flex-1" v-show="!collapsed"></div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
// 定义组件属性
const props = defineProps({
id: {
type: String,
required: true
},
title: {
type: String,
default: ''
},
x: {
type: Number,
default: 0
},
y: {
type: Number,
default: 0
},
width: {
type: Number,
default: 300
},
height: {
type: Number,
default: 180
},
collapsed: {
type: Boolean,
default: false
},
toolbarExpanded: {
type: Boolean,
default: false
}
});
// 定义事件
const emit = defineEmits(['toggleCollapse', 'maximize', 'close', 'toggleToolbar', 'dragStart', 'dragMove', 'dragEnd']);
2025-10-31 21:58:33 +08:00
// 事件处理函数
const onToggleCollapse = () => {
emit('toggleCollapse', props.id);
};
const onMaximize = () => {
emit('maximize', props.id);
};
const onClose = () => {
emit('close', props.id);
};
const onToggleToolbar = () => {
emit('toggleToolbar', props.id);
};
// 拖拽相关状态
let isDragging = false;
// 拖拽开始
const onDragStart = (e) => {
// 只有当点击的是标题栏区域(不是按钮)时才触发拖拽
if (!e.target.closest('.title-bar-buttons') && !e.target.closest('button')) {
isDragging = true;
emit('dragStart', {
clientX: e.clientX,
clientY: e.clientY
});
// 防止文本选择
e.preventDefault();
// 将鼠标移动和释放事件绑定到document确保拖拽的连续性
document.addEventListener('mousemove', onDragMove);
document.addEventListener('mouseup', onDragEnd);
document.addEventListener('mouseleave', onDragEnd);
}
};
// 拖拽移动
const onDragMove = (e) => {
if (isDragging) {
emit('dragMove', {
clientX: e.clientX,
clientY: e.clientY
});
}
};
// 拖拽结束
const onDragEnd = () => {
if (isDragging) {
isDragging = false;
emit('dragEnd');
// 拖拽结束后移除事件监听器
document.removeEventListener('mousemove', onDragMove);
document.removeEventListener('mouseup', onDragEnd);
document.removeEventListener('mouseleave', onDragEnd);
}
};
2025-10-31 21:58:33 +08:00
</script>
<style scoped>
/* 面板基础样式 */
.panel {
border: 1px solid #c7d2ea;
}
/* 图标样式优化 */
.icon-square-svg {
/* 优化SVG渲染避免1px边框显示过粗的问题 */
shape-rendering: crispEdges;
}
/* 禁用可能存在的旧伪元素样式 */
:deep(.icon-square::before),
:deep(.icon-square::after) {
content: none !important;
display: none !important;
border: 0 !important;
}
</style>