Add AutoRobot directory with Windows line endings

This commit is contained in:
2025-10-20 09:04:09 +08:00
parent a7ade87dde
commit d663118a73
124 changed files with 22719 additions and 0 deletions

View File

@@ -0,0 +1,506 @@
<template>
<div
class="floating-window"
:style="windowStyle"
:data-window-id="window.id"
@click="handleWindowActivate"
>
<!-- 标题栏 -->
<div
class="floating-window-titlebar"
:style="titleBarStyle"
@mousedown="handleTitleBarMouseDown"
>
<span v-if="window.icon" class="window-icon">{{ window.icon }}</span>
<span class="window-title">{{ window.title }}</span>
<!-- 窗口操作按钮 -->
<div class="window-controls">
<button
class="window-btn window-btn-dock"
@click.stop="handleDock"
title="停靠窗口"
>
📌
</button>
<button
class="window-btn window-btn-minimize"
@click.stop="handleMinimize"
title="最小化窗口"
>
</button>
<button
class="window-btn window-btn-maximize"
@click.stop="handleMaximize"
:title="window.maximized ? '还原窗口' : '最大化窗口'"
>
{{ window.maximized ? '□' : '◻' }}
</button>
<button
class="window-btn window-btn-close"
@click.stop="handleClose"
title="关闭窗口"
>
×
</button>
</div>
</div>
<!-- 内容区 -->
<div
class="floating-window-content"
:style="contentStyle"
>
<!-- 渲染窗口内容 -->
<template v-if="window.component">
<component
:is="window.component"
v-bind="window.props || {}"
:style="{ height: '100%', width: '100%', overflow: 'auto' }"
/>
</template>
<template v-else-if="window.content">
<div class="window-default-content">
{{ window.content }}
</div>
</template>
<template v-else>
<div class="window-empty-content">
无内容
</div>
</template>
</div>
<!-- 调整大小手柄 -->
<template v-if="!window.maximized">
<!-- 左上 -->
<div
class="resize-handle resize-tl"
@mousedown="startResize({ top: true, left: true }, $event)"
/>
<!-- 右上 -->
<div
class="resize-handle resize-tr"
@mousedown="startResize({ top: true, right: true }, $event)"
/>
<!-- 左下 -->
<div
class="resize-handle resize-bl"
@mousedown="startResize({ bottom: true, left: true }, $event)"
/>
<!-- 右下 -->
<div
class="resize-handle resize-br"
@mousedown="startResize({ bottom: true, right: true }, $event)"
/>
<!-- -->
<div
class="resize-handle resize-t"
@mousedown="startResize({ top: true }, $event)"
/>
<!-- -->
<div
class="resize-handle resize-b"
@mousedown="startResize({ bottom: true }, $event)"
/>
<!-- -->
<div
class="resize-handle resize-l"
@mousedown="startResize({ left: true }, $event)"
/>
<!-- -->
<div
class="resize-handle resize-r"
@mousedown="startResize({ right: true }, $event)"
/>
</template>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
// 定义组件属性
const props = defineProps({
// 窗口数据
window: {
type: Object,
required: true
}
});
// 定义事件
const emit = defineEmits([
'close', // 关闭窗口
'dock', // 停靠窗口
'move', // 移动窗口
'minimize', // 最小化窗口
'maximize' // 最大化窗口
]);
// 窗口位置和尺寸状态
const position = ref({
x: props.window.x || 100,
y: props.window.y || 100
});
const size = ref({
width: props.window.width || 400,
height: props.window.height || 300
});
// 窗口状态
const isDragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const isResizing = ref(false);
const resizeStartPos = ref({ x: 0, y: 0 });
const resizeEdges = ref({});
// 计算窗口样式
const windowStyle = computed(() => {
const style = {
position: 'fixed',
top: `${position.value.y}px`,
left: `${position.value.x}px`,
width: `${size.value.width}px`,
height: `${size.value.height}px`,
backgroundColor: 'white',
border: '1px solid #e5e7eb',
borderRadius: '4px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
zIndex: props.window.zIndex || 50,
display: props.window.minimized ? 'none' : 'flex',
flexDirection: 'column',
overflow: 'hidden',
transition: 'box-shadow 0.2s ease'
};
// 最大化状态样式
if (props.window.maximized) {
style.top = '0px';
style.left = '0px';
style.width = '100vw';
style.height = '100vh';
style.borderRadius = '0px';
style.zIndex = 100;
}
return style;
});
// 计算标题栏样式
const titleBarStyle = computed(() => {
return {
display: 'flex',
alignItems: 'center',
padding: '0 12px',
height: '36px',
backgroundColor: '#f3f4f6',
borderBottom: '1px solid #e5e7eb',
cursor: 'move',
userSelect: 'none',
fontSize: '13px',
fontWeight: '500'
};
});
// 计算内容区样式
const contentStyle = computed(() => {
return {
flex: 1,
overflow: 'hidden',
position: 'relative'
};
});
// 处理标题栏鼠标按下事件
const handleTitleBarMouseDown = (event) => {
if (event.target.tagName.toLowerCase() === 'button') {
return; // 如果点击的是按钮,不触发拖拽
}
isDragging.value = true;
dragStartPos.value = {
x: event.clientX - position.value.x,
y: event.clientY - position.value.y
};
// 提升窗口层级
if (!props.window.maximized) {
props.window.zIndex = Math.max(...Array.from(document.querySelectorAll('.floating-window')).map(el => parseInt(el.style.zIndex) || 50)) + 1;
}
// 阻止默认行为
event.preventDefault();
};
// 处理窗口关闭
const handleClose = () => {
emit('close', props.window.id);
};
// 处理窗口最小化
const handleMinimize = () => {
emit('minimize', props.window.id);
};
// 处理窗口最大化/还原
const handleMaximize = () => {
emit('maximize', props.window.id);
};
// 处理窗口停靠
const handleDock = () => {
emit('dock', props.window.id);
};
// 开始调整大小
const startResize = (edges, event) => {
isResizing.value = true;
resizeStartPos.value = {
x: event.clientX,
y: event.clientY
};
resizeEdges.value = edges;
// 阻止默认行为
event.preventDefault();
};
// 处理鼠标移动
const handleMouseMove = (event) => {
if (isDragging.value) {
// 处理窗口移动
const newX = event.clientX - dragStartPos.value.x;
const newY = event.clientY - dragStartPos.value.y;
position.value = {
x: newX,
y: newY
};
emit('move', props.window.id, newX, newY);
} else if (isResizing.value) {
// 处理窗口调整大小
const deltaX = event.clientX - resizeStartPos.value.x;
const deltaY = event.clientY - resizeStartPos.value.y;
let newWidth = size.value.width;
let newHeight = size.value.height;
let newX = position.value.x;
let newY = position.value.y;
// 根据边缘调整尺寸和位置
if (resizeEdges.value.left) {
newWidth = Math.max(200, size.value.width - deltaX);
newX = position.value.x + deltaX;
}
if (resizeEdges.value.right) {
newWidth = Math.max(200, size.value.width + deltaX);
}
if (resizeEdges.value.top) {
newHeight = Math.max(150, size.value.height - deltaY);
newY = position.value.y + deltaY;
}
if (resizeEdges.value.bottom) {
newHeight = Math.max(150, size.value.height + deltaY);
}
// 更新窗口状态
size.value = {
width: newWidth,
height: newHeight
};
position.value = {
x: newX,
y: newY
};
// 通知父组件
emit('move', props.window.id, newX, newY);
// 更新拖拽起点
resizeStartPos.value = {
x: event.clientX,
y: event.clientY
};
}
};
// 处理鼠标松开
const handleMouseUp = () => {
isDragging.value = false;
isResizing.value = false;
};
// 处理窗口激活
const handleWindowActivate = () => {
// 提升窗口层级
if (!props.window.maximized) {
props.window.zIndex = Math.max(...Array.from(document.querySelectorAll('.floating-window')).map(el => parseInt(el.style.zIndex) || 50)) + 1;
}
};
// 生命周期钩子
onMounted(() => {
// 同步外部窗口状态
if (props.window.x !== undefined) position.value.x = props.window.x;
if (props.window.y !== undefined) position.value.y = props.window.y;
if (props.window.width !== undefined) size.value.width = props.window.width;
if (props.window.height !== undefined) size.value.height = props.window.height;
// 添加全局事件监听
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
});
onUnmounted(() => {
// 清理全局事件监听
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
});
</script>
<style scoped>
/* 窗口图标样式 */
.window-icon {
margin-right: 8px;
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* 窗口标题样式 */
.window-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 窗口控制按钮组样式 */
.window-controls {
display: flex;
align-items: center;
}
/* 窗口按钮通用样式 */
.window-btn {
background: none;
border: none;
cursor: pointer;
padding: 4px 6px;
border-radius: 2px;
color: #6b7280;
font-size: 12px;
margin-left: 4px;
transition: all 0.2s ease;
}
.window-btn:hover {
background-color: #e5e7eb;
color: #111827;
}
/* 关闭按钮特殊样式 */
.window-btn-close:hover {
background-color: #ef4444 !important;
color: white !important;
}
/* 默认内容样式 */
.window-default-content {
padding: 16px;
height: 100%;
width: 100%;
overflow: auto;
}
/* 空内容样式 */
.window-empty-content {
padding: 16px;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #9ca3af;
font-size: 14px;
}
/* 调整大小手柄样式 */
.resize-handle {
position: absolute;
background-color: transparent;
z-index: 10;
}
/* 四角手柄 */
.resize-tl {
top: -4px;
left: -4px;
width: 8px;
height: 8px;
cursor: nwse-resize;
}
.resize-tr {
top: -4px;
right: -4px;
width: 8px;
height: 8px;
cursor: nesw-resize;
}
.resize-bl {
bottom: -4px;
left: -4px;
width: 8px;
height: 8px;
cursor: nesw-resize;
}
.resize-br {
bottom: -4px;
right: -4px;
width: 8px;
height: 8px;
cursor: nwse-resize;
}
/* 四边手柄 */
.resize-t {
top: -4px;
left: 8px;
right: 8px;
height: 8px;
cursor: ns-resize;
}
.resize-b {
bottom: -4px;
left: 8px;
right: 8px;
height: 8px;
cursor: ns-resize;
}
.resize-l {
left: -4px;
top: 8px;
bottom: 8px;
width: 8px;
cursor: ew-resize;
}
.resize-r {
right: -4px;
top: 8px;
bottom: 8px;
width: 8px;
cursor: ew-resize;
}
</style>