Add AutoRobot directory with Windows line endings
This commit is contained in:
615
AutoRobot/Windows/Robot/Web/src/views/DockPanelDemo.vue
Normal file
615
AutoRobot/Windows/Robot/Web/src/views/DockPanelDemo.vue
Normal file
@@ -0,0 +1,615 @@
|
||||
<template>
|
||||
<div class="dock-panel-demo w-full h-screen bg-gray-100">
|
||||
<!-- 顶部控制栏 -->
|
||||
<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="addPanel('left')"
|
||||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||||
>
|
||||
添加左侧面板
|
||||
</button>
|
||||
<button
|
||||
@click="addPanel('right')"
|
||||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||||
>
|
||||
添加右侧面板
|
||||
</button>
|
||||
<button
|
||||
@click="addPanel('top')"
|
||||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||||
>
|
||||
添加顶部面板
|
||||
</button>
|
||||
<button
|
||||
@click="addPanel('bottom')"
|
||||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||||
>
|
||||
添加底部面板
|
||||
</button>
|
||||
<button
|
||||
@click="addPanel('center')"
|
||||
class="px-3 py-1 bg-blue-50 text-blue-600 rounded hover:bg-blue-100 text-sm"
|
||||
>
|
||||
添加中心面板
|
||||
</button>
|
||||
<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 class="flex-1 w-full h-[calc(100%-4rem)]">
|
||||
<DockPanelContainer
|
||||
ref="dockPanelContainer"
|
||||
:panels="initialPanels"
|
||||
:layout="layoutConfig"
|
||||
:minSizes="minSizesConfig"
|
||||
@showContextMenu="handleShowContextMenu"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DockPanelContainer from '../components/DockPanelContainer.vue';
|
||||
|
||||
// 面板容器引用
|
||||
const dockPanelContainer = ref(null);
|
||||
const layoutFileInput = ref(null);
|
||||
|
||||
// 初始化面板配置
|
||||
const initialPanels = ref([
|
||||
{
|
||||
id: 'panel_solution_explorer',
|
||||
title: '解决方案资源管理器',
|
||||
icon: '📁',
|
||||
position: 'left',
|
||||
content: generateExplorerContent()
|
||||
},
|
||||
{
|
||||
id: 'panel_properties',
|
||||
title: '属性',
|
||||
icon: '⚙️',
|
||||
position: 'right',
|
||||
content: generatePropertiesContent()
|
||||
},
|
||||
{
|
||||
id: 'panel_terminal',
|
||||
title: '终端',
|
||||
icon: '💻',
|
||||
position: 'bottom',
|
||||
content: generateTerminalContent()
|
||||
},
|
||||
{
|
||||
id: 'panel_code_editor',
|
||||
title: '代码编辑器',
|
||||
icon: '📄',
|
||||
position: 'center',
|
||||
content: generateCodeEditorContent()
|
||||
}
|
||||
]);
|
||||
|
||||
// 布局配置
|
||||
const layoutConfig = ref({
|
||||
leftPanelWidth: 300,
|
||||
rightPanelWidth: 250,
|
||||
topPanelHeight: 150,
|
||||
bottomPanelHeight: 200
|
||||
});
|
||||
|
||||
// 最小尺寸配置
|
||||
const minSizesConfig = ref({
|
||||
panelWidth: 150,
|
||||
panelHeight: 100,
|
||||
floatingWindowWidth: 300,
|
||||
floatingWindowHeight: 200
|
||||
});
|
||||
|
||||
// 生成解决方案资源管理器内容
|
||||
function generateExplorerContent() {
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span style="font-weight: 500;">解决方案 'DemoApp'</span>
|
||||
</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 4px;">
|
||||
<div style="margin-bottom: 2px;">📁 src</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📁 components</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📁 views</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📁 assets</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📄 App.vue</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📄 main.js</div>
|
||||
</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 4px;">
|
||||
<div style="margin-bottom: 2px;">📁 public</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 2px;">📄 index.html</div>
|
||||
</div>
|
||||
<div style="padding-left: 16px; margin-bottom: 4px;">
|
||||
<div style="margin-bottom: 2px;">📄 package.json</div>
|
||||
<div style="margin-bottom: 2px;">📄 vite.config.js</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 生成属性面板内容
|
||||
function generatePropertiesContent() {
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<div style="font-weight: 500; margin-bottom: 4px;">属性</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 4px; font-size: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">名称:</span>
|
||||
<span>DemoPanel</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">ID:</span>
|
||||
<span>panel_123</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">位置:</span>
|
||||
<span>右侧</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">宽度:</span>
|
||||
<span>250px</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">高度:</span>
|
||||
<span>自动</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">可折叠:</span>
|
||||
<span>✓</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 2px 0;">
|
||||
<span style="color: #6b7280;">可浮动:</span>
|
||||
<span>✓</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 生成输出面板内容
|
||||
function generateOutputContent() {
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto; background-color: #f9fafb; font-family: monospace; font-size: 12px;">
|
||||
<div style="color: #10b981;">构建开始...</div>
|
||||
<div style="color: #6b7280;">vite v4.4.0 building for production...</div>
|
||||
<div style="color: #6b7280;">✓ 44 modules transformed.</div>
|
||||
<div style="color: #6b7280;">dist/index.html 0.50 kB │ gzip: 0.31 kB</div>
|
||||
<div style="color: #6b7280;">dist/assets/index-DJIXYkGf.css 2.34 kB │ gzip: 0.92 kB</div>
|
||||
<div style="color: #6b7280;">dist/assets/index-B5cY6mOY.js 42.75 kB │ gzip: 16.84 kB</div>
|
||||
<div style="color: #10b981;">✓ built in 2.45s</div>
|
||||
<div style="color: #10b981;">构建成功!</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 生成终端面板内容
|
||||
function generateTerminalContent() {
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto; background-color: #1f2937; color: #e5e7eb; font-family: monospace; font-size: 12px;">
|
||||
<div style="color: #9ca3af;">$ npm run dev</div>
|
||||
<div style="color: #6b7280;">> demo-app@1.0.0 dev</div>
|
||||
<div style="color: #6b7280;">> vite</div>
|
||||
<div style="color: #10b981;">VITE v4.4.0 ready in 536 ms</div>
|
||||
<div style="color: #60a5fa;">➜ Local: http://localhost:5173/</div>
|
||||
<div style="color: #60a5fa;">➜ Network: use --host to expose</div>
|
||||
<div style="color: #9ca3af;">$ </div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 生成代码编辑器内容
|
||||
function generateCodeEditorContent() {
|
||||
return `
|
||||
<div style="padding: 16px; height: 100%; overflow: auto; background-color: #ffffff; font-family: monospace; font-size: 13px;">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="color: #6b7280; margin-bottom: 8px;">// App.vue</div>
|
||||
<div style="color: #1e40af;"><template></div>
|
||||
<div style="padding-left: 24px; color: #4f46e5;"><div</div>
|
||||
<div style="padding-left: 48px; color: #6b7280;">class=<span style="color: #10b981;">"app"</span></div>
|
||||
<div style="padding-left: 24px; color: #4f46e5;">></div>
|
||||
<div style="padding-left: 48px; color: #1e40af;"><h1>Hello, World!</h1></div>
|
||||
<div style="padding-left: 48px; color: #1e40af;"><p>This is a demo of the Dock Panel Container.</p></div>
|
||||
<div style="padding-left: 24px; color: #4f46e5;"></div></div>
|
||||
<div style="color: #1e40af;"></template></div>
|
||||
<br>
|
||||
<div style="color: #1e40af;"><script setup></div>
|
||||
<div style="padding-left: 24px; color: #6b7280;">import { ref } from <span style="color: #10b981;">'vue'</span>;</div>
|
||||
<div style="padding-left: 24px; color: #6b7280;">import DockPanelContainer from <span style="color: #10b981;">'./components/DockPanelContainer.vue'</span>;</div>
|
||||
<br>
|
||||
<div style="padding-left: 24px; color: #65a30d;">const message = ref(<span style="color: #10b981;">'Hello from Vue!'</span>);</div>
|
||||
<div style="color: #1e40af;"></script></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 为新面板生成丰富内容
|
||||
function generateRichPanelContent(panelType) {
|
||||
switch (panelType) {
|
||||
case '数据视图':
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span style="font-weight: 500;">数据分析</span>
|
||||
</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 8px; margin-bottom: 8px;">
|
||||
<div style="height: 120px; background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); border-radius: 4px;"></div>
|
||||
<div style="margin-top: 8px; display: flex; justify-content: space-between;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-weight: 500;">56%</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">完成率</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-weight: 500;">128</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">项目数</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-weight: 500;">8.2</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">平均评分</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 8px;">
|
||||
<div style="font-size: 12px; margin-bottom: 4px;">最近活动</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">• 项目A更新于10分钟前</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">• 项目B完成于2小时前</div>
|
||||
<div style="font-size: 11px; color: #6b7280;">• 新项目C创建于昨天</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
case '调试器':
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto; font-family: monospace; font-size: 12px;">
|
||||
<div style="margin-bottom: 8px; font-weight: 500;">调试信息</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 4px; background-color: #f9fafb;">
|
||||
<div style="color: #6b7280; margin-bottom: 4px;">// 变量信息</div>
|
||||
<div style="color: #1e40af;">const <span style="color: #059669;">user</span> = <span style="color: #7c3aed;">{</span></div>
|
||||
<div style="padding-left: 16px; color: #7c3aed;">id: <span style="color: #d97706;">12345</span>,</div>
|
||||
<div style="padding-left: 16px; color: #7c3aed;">name: <span style="color: #10b981;">"John Doe"</span>,</div>
|
||||
<div style="padding-left: 16px; color: #7c3aed;">role: <span style="color: #10b981;">"admin"</span></div>
|
||||
<div style="color: #7c3aed;"><span style="color: #7c3aed;">}</span></div>
|
||||
<div style="color: #6b7280; margin: 4px 0;">// 调用栈</div>
|
||||
<div style="color: #ef4444;">1. processUserData (<span style="color: #10b981;">user.js:42</span>)</div>
|
||||
<div style="color: #6b7280;">2. handleLogin (<span style="color: #10b981;">auth.js:156</span>)</div>
|
||||
<div style="color: #6b7280;">3. submitForm (<span style="color: #10b981;">form.js:78</span>)</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
case '文档':
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="margin-bottom: 8px; font-weight: 500;">API文档</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 8px; margin-bottom: 8px;">
|
||||
<div style="font-weight: 500; margin-bottom: 4px;">processData(data, options)</div>
|
||||
<div style="font-size: 12px; color: #6b7280; margin-bottom: 4px;">处理数据并返回结果</div>
|
||||
<div style="font-size: 12px; margin-bottom: 2px;"><span style="color: #059669;">参数:</span></div>
|
||||
<div style="font-size: 11px; color: #6b7280; padding-left: 16px; margin-bottom: 2px;">- data: 要处理的数据对象</div>
|
||||
<div style="font-size: 11px; color: #6b7280; padding-left: 16px; margin-bottom: 2px;">- options: 处理选项,可选</div>
|
||||
<div style="font-size: 12px; margin-bottom: 2px;"><span style="color: #059669;">返回值:</span></div>
|
||||
<div style="font-size: 11px; color: #6b7280; padding-left: 16px;">处理后的结果对象</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 8px;">
|
||||
<div style="font-weight: 500; margin-bottom: 4px;">示例代码</div>
|
||||
<div style="font-family: monospace; font-size: 11px; color: #6b7280;">
|
||||
const result = processData(userData, {\n validate: true,\n format: 'json'\n});
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
case '搜索':
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 4px; margin-bottom: 8px; display: flex;">
|
||||
<input type="text" placeholder="搜索..." style="border: none; outline: none; flex: 1; padding: 4px; font-size: 12px;">
|
||||
<button style="background-color: #3b82f6; color: white; border: none; border-radius: 2px; padding: 4px 8px; font-size: 12px;">搜索</button>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px; font-weight: 500; font-size: 12px;">最近搜索</div>
|
||||
<div style="font-size: 11px; color: #6b7280; margin-bottom: 16px;">
|
||||
<div style="margin-bottom: 2px;">• 数据处理</div>
|
||||
<div style="margin-bottom: 2px;">• 组件设计</div>
|
||||
<div style="margin-bottom: 2px;">• API文档</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 8px;">
|
||||
<div style="font-size: 12px; font-weight: 500; margin-bottom: 4px;">搜索结果 (示例)</div>
|
||||
<div style="font-size: 11px; margin-bottom: 4px;">
|
||||
<div style="font-weight: 500;">DataProcessor.js</div>
|
||||
<div style="color: #6b7280; padding-left: 16px;">包含数据处理相关函数</div>
|
||||
</div>
|
||||
<div style="font-size: 11px; margin-bottom: 4px;">
|
||||
<div style="font-weight: 500;">api-docs.md</div>
|
||||
<div style="color: #6b7280; padding-left: 16px;">API文档说明</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
case '任务列表':
|
||||
return `
|
||||
<div style="padding: 8px; height: 100%; overflow: auto;">
|
||||
<div style="margin-bottom: 8px; font-weight: 500;">任务清单</div>
|
||||
<div style="border: 1px solid #e5e7eb; border-radius: 4px; padding: 4px;">
|
||||
<div style="display: flex; align-items: center; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
||||
<input type="checkbox" checked style="margin-right: 8px;">
|
||||
<div style="flex: 1; text-decoration: line-through; color: #6b7280; font-size: 12px;">完成文档编写</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
||||
<input type="checkbox" style="margin-right: 8px;">
|
||||
<div style="flex: 1; font-size: 12px;">修复数据处理bug</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
||||
<input type="checkbox" style="margin-right: 8px;">
|
||||
<div style="flex: 1; font-size: 12px;">更新组件样式</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; padding: 4px 0;">
|
||||
<input type="checkbox" style="margin-right: 8px;">
|
||||
<div style="flex: 1; font-size: 12px;">添加新功能模块</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px; text-align: center;">
|
||||
<button style="background-color: #10b981; color: white; border: none; border-radius: 4px; padding: 6px 12px; font-size: 12px;">添加新任务</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
default:
|
||||
return '<div style="padding: 16px; height: 100%; display: flex; align-items: center; justify-content: center; color: #6b7280;">面板内容</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新面板
|
||||
function addPanel(position) {
|
||||
const panelTypes = [
|
||||
{ title: '数据视图', icon: '📊' },
|
||||
{ title: '调试器', icon: '🐛' },
|
||||
{ title: '文档', icon: '📚' },
|
||||
{ title: '搜索', icon: '🔍' },
|
||||
{ title: '任务列表', icon: '📝' }
|
||||
];
|
||||
|
||||
// 随机选择一个面板类型
|
||||
const randomType = panelTypes[Math.floor(Math.random() * panelTypes.length)];
|
||||
|
||||
// 创建新面板,使用丰富的内容
|
||||
const newPanel = {
|
||||
id: 'panel_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||
title: randomType.title,
|
||||
icon: randomType.icon,
|
||||
position: position,
|
||||
content: generateRichPanelContent(randomType.title)
|
||||
};
|
||||
|
||||
// 添加到面板容器
|
||||
if (dockPanelContainer.value) {
|
||||
dockPanelContainer.value.addPanel(newPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加浮动面板
|
||||
function addFloatingPanel() {
|
||||
// 先添加到中心,然后立即浮动
|
||||
const panelTypes = [
|
||||
{
|
||||
title: '浮动工具',
|
||||
icon: '🧰',
|
||||
content: '<div style="padding: 16px; height: 100%; display: flex; align-items: center; justify-content: center; color: #6b7280;">这是一个浮动工具窗口</div>'
|
||||
},
|
||||
{
|
||||
title: '计算器',
|
||||
icon: '🧮',
|
||||
content: '<div style="padding: 16px; height: 100%; display: flex; align-items: center; justify-content: center; color: #6b7280;">计算器工具</div>'
|
||||
},
|
||||
{
|
||||
title: '笔记',
|
||||
icon: '📝',
|
||||
content: '<div style="padding: 16px; height: 100%; display: flex; align-items: center; justify-content: center; color: #6b7280;">快速笔记工具</div>'
|
||||
}
|
||||
];
|
||||
|
||||
// 随机选择一个面板类型
|
||||
const randomType = panelTypes[Math.floor(Math.random() * panelTypes.length)];
|
||||
|
||||
// 创建新面板
|
||||
const newPanel = {
|
||||
id: 'panel_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||
title: randomType.title,
|
||||
icon: randomType.icon,
|
||||
position: 'center',
|
||||
content: randomType.content
|
||||
};
|
||||
|
||||
// 添加到面板容器
|
||||
if (dockPanelContainer.value) {
|
||||
dockPanelContainer.value.addPanel(newPanel);
|
||||
// 延迟浮动,确保面板已添加
|
||||
setTimeout(() => {
|
||||
dockPanelContainer.value.floatPanel(newPanel.id);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出布局
|
||||
function exportLayout() {
|
||||
if (dockPanelContainer.value && dockPanelContainer.value.exportLayout) {
|
||||
dockPanelContainer.value.exportLayout();
|
||||
}
|
||||
}
|
||||
|
||||
// 导入布局
|
||||
function importLayout() {
|
||||
if (layoutFileInput.value) {
|
||||
layoutFileInput.value.click();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理布局文件选择
|
||||
function handleLayoutFileSelect(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target.result;
|
||||
if (dockPanelContainer.value && dockPanelContainer.value.importLayout) {
|
||||
dockPanelContainer.value.importLayout(content);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
// 重置文件输入,以便可以重复选择同一个文件
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
// 重置布局
|
||||
function resetLayout() {
|
||||
if (confirm('确定要重置布局吗?所有当前面板将丢失。')) {
|
||||
// 清除保存的布局数据 - 增加多种清除方式以确保彻底清除
|
||||
|
||||
// 方法1: 通过容器组件的clearSavedLayout方法清除
|
||||
if (dockPanelContainer.value && dockPanelContainer.value.clearSavedLayout) {
|
||||
try {
|
||||
dockPanelContainer.value.clearSavedLayout();
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 方法2: 直接从localStorage中清除所有可能的布局相关数据
|
||||
try {
|
||||
localStorage.removeItem('dockPanelLayout');
|
||||
localStorage.removeItem('dockLayout');
|
||||
localStorage.removeItem('panelLayout');
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
// 方法3: 添加URL参数标记为强制重置,确保组件重新初始化
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('resetLayout', 'true');
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理显示右键菜单事件
|
||||
function handleShowContextMenu(event) {
|
||||
// 这里可以实现自定义的右键菜单逻辑
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
// 添加键盘快捷键支持
|
||||
document.addEventListener('keydown', (event) => {
|
||||
// Ctrl+Alt+L 添加左侧面板
|
||||
if (event.ctrlKey && event.altKey && event.key === 'l') {
|
||||
event.preventDefault();
|
||||
addPanel('left');
|
||||
}
|
||||
// Ctrl+Alt+R 添加右侧面板
|
||||
else if (event.ctrlKey && event.altKey && event.key === 'r') {
|
||||
event.preventDefault();
|
||||
addPanel('right');
|
||||
}
|
||||
// Ctrl+Alt+T 添加顶部面板
|
||||
else if (event.ctrlKey && event.altKey && event.key === 't') {
|
||||
event.preventDefault();
|
||||
addPanel('top');
|
||||
}
|
||||
// Ctrl+Alt+B 添加底部面板
|
||||
else if (event.ctrlKey && event.altKey && event.key === 'b') {
|
||||
event.preventDefault();
|
||||
addPanel('bottom');
|
||||
}
|
||||
// Ctrl+Alt+C 添加中心面板
|
||||
else if (event.ctrlKey && event.altKey && event.key === 'c') {
|
||||
event.preventDefault();
|
||||
addPanel('center');
|
||||
}
|
||||
// Ctrl+Alt+F 添加浮动窗口
|
||||
else if (event.ctrlKey && event.altKey && event.key === 'f') {
|
||||
event.preventDefault();
|
||||
addFloatingPanel();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dock-panel-demo {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
}
|
||||
|
||||
.demo-control-bar {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
/* 按钮样式增强 */
|
||||
.demo-control-bar button {
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.demo-control-bar button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-control-bar button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.dock-panel-demo ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dock-panel-demo ::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.dock-panel-demo ::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dock-panel-demo ::-webkit-scrollbar-thumb:hover {
|
||||
background: #a1a1a1;
|
||||
}
|
||||
</style>
|
||||
523
AutoRobot/Windows/Robot/Web/src/views/FrameworkTest.vue
Normal file
523
AutoRobot/Windows/Robot/Web/src/views/FrameworkTest.vue
Normal file
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<div class="w-full h-screen flex items-center justify-center bg-gray-100">
|
||||
<!-- 停靠式面板容器 -->
|
||||
<div class="absolute inset-0 flex flex-col bg-white">
|
||||
<!-- 顶部标题栏 -->
|
||||
<div class="bg-gray-800 text-white px-4 py-2 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa-solid fa-layer-group"></i>
|
||||
<span class="font-medium">停靠式面板演示</span>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button class="p-1 hover:bg-gray-700 rounded">
|
||||
<i class="fa-solid fa-window-minimize text-sm"></i>
|
||||
</button>
|
||||
<button class="p-1 hover:bg-gray-700 rounded">
|
||||
<i class="fa-solid fa-window-maximize text-sm"></i>
|
||||
</button>
|
||||
<button class="p-1 hover:bg-gray-700 rounded">
|
||||
<i class="fa-solid fa-window-close text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 - 采用VS风格的布局 -->
|
||||
<div class="flex-1 flex overflow-hidden relative">
|
||||
<!-- 左侧面板组 -->
|
||||
<div v-if="leftPanelVisible" :style="{ width: leftPanelWidth + 'px' }" class="bg-white border-r border-gray-200 flex flex-col h-full transition-all duration-100 z-10">
|
||||
<!-- 面板选项卡 -->
|
||||
<div class="bg-gray-100 border-b border-gray-200 flex text-xs">
|
||||
<button class="flex-1 py-2 px-3 border-r border-gray-200 bg-white font-medium">解决方案</button>
|
||||
<button class="flex-1 py-2 px-3 border-r border-gray-200 text-gray-500 hover:bg-gray-200">类视图</button>
|
||||
<button class="flex-1 py-2 px-3 text-gray-500 hover:bg-gray-200">资源视图</button>
|
||||
<button @click="toggleLeftPanel" class="px-2 hover:bg-gray-200 text-gray-500">
|
||||
<i class="fa-solid fa-chevron-right text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 面板内容 -->
|
||||
<div class="flex-1 overflow-y-auto p-3">
|
||||
<div class="font-medium text-sm mb-2 text-gray-700">解决方案 'AutoRobot'</div>
|
||||
<div class="ml-4 mb-2">
|
||||
<div class="flex items-center cursor-pointer hover:text-blue-600">
|
||||
<i class="fa-solid fa-cube mr-1 text-xs text-yellow-600"></i>
|
||||
<span class="text-sm">Windows.Robot</span>
|
||||
</div>
|
||||
<div class="ml-4 mt-1">
|
||||
<div class="flex items-center cursor-pointer hover:text-blue-600">
|
||||
<i class="fa-solid fa-file-code mr-1 text-xs text-blue-600"></i>
|
||||
<span class="text-sm">main.go</span>
|
||||
</div>
|
||||
<div class="flex items-center cursor-pointer hover:text-blue-600">
|
||||
<i class="fa-solid fa-file-code mr-1 text-xs text-blue-600"></i>
|
||||
<span class="text-sm">go.mod</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="flex items-center cursor-pointer hover:text-blue-600">
|
||||
<i class="fa-solid fa-folder-open mr-1 text-xs text-yellow-500"></i>
|
||||
<span class="text-sm">Web</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 面板右侧垂直边框缩放手柄 -->
|
||||
<div class="absolute top-0 right-0 w-2 h-full cursor-col-resize resize-handle vertical-resize" @mousedown="startResize('left')"></div>
|
||||
</div>
|
||||
|
||||
<!-- 左侧面板折叠按钮 -->
|
||||
<div v-else class="bg-white border-r border-gray-200 w-8 h-full flex flex-col items-center justify-start py-4 z-20">
|
||||
<button @click="toggleLeftPanel" class="p-2 text-gray-500 hover:bg-gray-200 rounded">
|
||||
<i class="fa-solid fa-chevron-left text-xs"></i>
|
||||
</button>
|
||||
<div class="mt-4 text-center">
|
||||
<i class="fa-solid fa-cube text-xs text-yellow-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中央编辑区域 -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<!-- 标签页区域 -->
|
||||
<div class="bg-gray-100 border-b border-gray-200 flex text-xs overflow-x-auto">
|
||||
<button class="flex items-center py-2 px-3 border-b-2 border-blue-600 bg-white font-medium text-blue-600">
|
||||
<span>main.go</span>
|
||||
<button class="ml-2 p-0.5 hover:bg-gray-200 rounded-full">
|
||||
<i class="fa-solid fa-times text-xs"></i>
|
||||
</button>
|
||||
</button>
|
||||
<button class="flex items-center py-2 px-3 border-b-2 border-transparent hover:bg-gray-200 text-gray-600">
|
||||
<span>App.vue</span>
|
||||
<button class="ml-2 p-0.5 hover:bg-gray-200 rounded-full">
|
||||
<i class="fa-solid fa-times text-xs"></i>
|
||||
</button>
|
||||
</button>
|
||||
<button class="flex items-center py-2 px-3 border-b-2 border-transparent hover:bg-gray-200 text-gray-600">
|
||||
<span>index.js</span>
|
||||
<button class="ml-2 p-0.5 hover:bg-gray-200 rounded-full">
|
||||
<i class="fa-solid fa-times text-xs"></i>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 编辑区域 -->
|
||||
<div class="flex-1 bg-gray-50 p-6 overflow-y-auto">
|
||||
<pre class="font-mono text-sm text-gray-800 bg-gray-50 p-4 rounded border border-gray-200 whitespace-pre-wrap">
|
||||
{`package main
|
||||
|
||||
import main main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocket upgrader配置
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
// 允许所有源访问,生产环境中应限制具体域名
|
||||
return true
|
||||
},
|
||||
}`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板组 -->
|
||||
<div v-if="rightPanelVisible" :style="{ width: rightPanelWidth + 'px' }" class="bg-white border-l border-gray-200 flex flex-col h-full transition-all duration-100 z-10">
|
||||
<!-- 面板选项卡 -->
|
||||
<div class="bg-gray-100 border-b border-gray-200 flex text-xs">
|
||||
<button @click="switchTab('properties')" class="flex-1 py-2 px-3 border-r border-gray-200 bg-white font-medium">属性</button>
|
||||
<button @click="switchTab('resources')" class="flex-1 py-2 px-3 text-gray-500 hover:bg-gray-200">资源</button>
|
||||
<button @click="toggleRightPanel" class="px-2 hover:bg-gray-200 text-gray-500">
|
||||
<i class="fa-solid fa-chevron-left text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 面板内容 - 根据选中的标签显示不同内容 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div v-if="activeRightTab === 'properties'">
|
||||
<div class="p-2 border-b border-gray-200 bg-gray-50 font-medium text-xs">main.go</div>
|
||||
<div class="divide-y divide-gray-200">
|
||||
<div class="flex items-center justify-between p-2 text-sm">
|
||||
<div class="text-gray-600">文件名</div>
|
||||
<div class="font-medium">main.go</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 text-sm">
|
||||
<div class="text-gray-600">路径</div>
|
||||
<div class="font-medium text-blue-600">Windows\Robot</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 text-sm">
|
||||
<div class="text-gray-600">大小</div>
|
||||
<div class="font-medium">4 KB</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 text-sm">
|
||||
<div class="text-gray-600">修改日期</div>
|
||||
<div class="font-medium">2024-07-20</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 text-sm">
|
||||
<div class="text-gray-600">语言</div>
|
||||
<div class="font-medium">Go</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="activeRightTab === 'resources'">
|
||||
<div class="p-2 border-b border-gray-200 bg-gray-50 font-medium text-xs">资源管理器</div>
|
||||
<div class="p-3">
|
||||
<div class="mb-3">
|
||||
<div class="font-medium text-xs mb-2">图标</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="w-8 h-8 bg-blue-100 rounded flex items-center justify-center text-blue-600 cursor-pointer hover:bg-blue-200">
|
||||
<i class="fa-solid fa-file-code"></i>
|
||||
</div>
|
||||
<div class="w-8 h-8 bg-green-100 rounded flex items-center justify-center text-green-600 cursor-pointer hover:bg-green-200">
|
||||
<i class="fa-solid fa-file-alt"></i>
|
||||
</div>
|
||||
<div class="w-8 h-8 bg-yellow-100 rounded flex items-center justify-center text-yellow-600 cursor-pointer hover:bg-yellow-200">
|
||||
<i class="fa-solid fa-file-image"></i>
|
||||
</div>
|
||||
<div class="w-8 h-8 bg-purple-100 rounded flex items-center justify-center text-purple-600 cursor-pointer hover:bg-purple-200">
|
||||
<i class="fa-solid fa-file-audio"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-xs mb-2">颜色</div>
|
||||
<div class="grid grid-cols-6 gap-2">
|
||||
<div class="w-6 h-6 bg-red-500 rounded cursor-pointer"></div>
|
||||
<div class="w-6 h-6 bg-blue-500 rounded cursor-pointer"></div>
|
||||
<div class="w-6 h-6 bg-green-500 rounded cursor-pointer"></div>
|
||||
<div class="w-6 h-6 bg-yellow-500 rounded cursor-pointer"></div>
|
||||
<div class="w-6 h-6 bg-purple-500 rounded cursor-pointer"></div>
|
||||
<div class="w-6 h-6 bg-gray-500 rounded cursor-pointer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 面板左侧垂直边框缩放手柄 -->
|
||||
<div class="absolute top-0 left-0 w-2 h-full cursor-col-resize resize-handle vertical-resize" @mousedown="startResize('right')"></div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧面板折叠按钮 -->
|
||||
<div v-else class="bg-white border-l border-gray-200 w-8 h-full flex flex-col items-center justify-start py-4 z-20">
|
||||
<button @click="toggleRightPanel" class="p-2 text-gray-500 hover:bg-gray-200 rounded">
|
||||
<i class="fa-solid fa-chevron-right text-xs"></i>
|
||||
</button>
|
||||
<div class="mt-4 text-center">
|
||||
<i class="fa-solid fa-sliders text-xs text-blue-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部面板组 -->
|
||||
<div v-if="bottomPanelVisible" :style="{ height: bottomPanelHeight + 'px' }" class="bg-white border-t border-gray-200 flex flex-col transition-all duration-100">
|
||||
<!-- 面板选项卡 -->
|
||||
<div class="bg-gray-100 border-b border-gray-200 flex text-xs">
|
||||
<button @click="switchBottomTab('errors')" class="flex-1 py-2 px-3 border-r border-gray-200 text-gray-500 hover:bg-gray-200">错误列表</button>
|
||||
<button @click="switchBottomTab('output')" class="flex-1 py-2 px-3 border-r border-gray-200 text-gray-500 hover:bg-gray-200">输出</button>
|
||||
<button @click="switchBottomTab('debug')" class="flex-1 py-2 px-3 bg-white font-medium">调试</button>
|
||||
<button @click="toggleBottomPanel" class="px-2 hover:bg-gray-200 text-gray-500">
|
||||
<i class="fa-solid fa-chevron-down text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 面板内容 - 根据选中的标签显示不同内容 -->
|
||||
<div class="flex-1 overflow-y-auto bg-gray-50">
|
||||
<div v-if="activeBottomTab === 'debug'" class="p-2">
|
||||
<div class="text-xs font-mono text-gray-800">
|
||||
<div class="mb-1"><span class="text-green-600">[INFO]</span> 服务器已启动,监听端口 :8805...</div>
|
||||
<div class="mb-1"><span class="text-green-600">[INFO]</span> 前端页面: http://localhost:8805</div>
|
||||
<div class="mb-1"><span class="text-green-600">[INFO]</span> WebSocket连接: ws://localhost:8805</div>
|
||||
<div class="mb-1"><span class="text-blue-600">[DEBUG]</span> 收到HTTP请求: /</div>
|
||||
<div class="mb-1"><span class="text-blue-600">[DEBUG]</span> 返回静态文件: index.html</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="activeBottomTab === 'errors'" class="p-2">
|
||||
<div class="text-xs font-mono text-gray-800">
|
||||
<div class="mb-1"><span class="text-red-600">[ERROR]</span> main.go:2: import "main main" is a program, not an importable package</div>
|
||||
<div class="mb-1"><span class="text-yellow-600">[WARNING]</span> 未使用的导入: "net/http/httptest"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="activeBottomTab === 'output'" class="p-2">
|
||||
<div class="text-xs font-mono text-gray-800">
|
||||
<div class="mb-1">> go run main.go</div>
|
||||
<div class="mb-1">服务器已启动在端口 8805</div>
|
||||
<div class="mb-1">WebSocket服务已就绪</div>
|
||||
<div class="mb-1">静态文件服务已启动</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 面板顶部水平边框缩放手柄 -->
|
||||
<div class="absolute left-0 top-0 w-full h-2 cursor-row-resize resize-handle horizontal-resize" @mousedown="startResize('bottom')"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部面板折叠按钮 -->
|
||||
<div v-else class="bg-white border-t border-gray-200 h-8 w-full flex items-center justify-center z-20">
|
||||
<button @click="toggleBottomPanel" class="p-1 text-gray-500 hover:bg-gray-200 rounded">
|
||||
<i class="fa-solid fa-chevron-up text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 浮动窗口示例 -->
|
||||
<div v-if="floatingWindowVisible" class="absolute z-30" :style="{ top: floatingWindowY + 'px', left: floatingWindowX + 'px' }">
|
||||
<div class="bg-white border border-gray-300 shadow-lg rounded-sm w-80 h-60">
|
||||
<div class="bg-gray-100 border-b border-gray-300 flex items-center justify-between p-2 text-xs cursor-move" @mousedown="startDraggingFloatingWindow">
|
||||
<div class="font-medium">浮动属性面板</div>
|
||||
<div class="flex space-x-1">
|
||||
<button @click="dockFloatingWindow('right')" class="p-1 hover:bg-gray-300 rounded text-gray-600">
|
||||
<i class="fa-solid fa-window-restore text-xs"></i>
|
||||
</button>
|
||||
<button @click="dockFloatingWindow('bottom')" class="p-1 hover:bg-gray-300 rounded text-gray-600">
|
||||
<i class="fa-solid fa-window-maximize text-xs"></i>
|
||||
</button>
|
||||
<button @click="toggleFloatingWindow" class="p-1 hover:bg-gray-300 rounded text-gray-600">
|
||||
<i class="fa-solid fa-times text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 overflow-y-auto h-[calc(100%-32px)]">
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-medium mb-1">面板位置</div>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="dockPanel('left')" class="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded hover:bg-blue-200">左侧</button>
|
||||
<button @click="dockPanel('right')" class="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded hover:bg-blue-200">右侧</button>
|
||||
<button @click="dockPanel('bottom')" class="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded hover:bg-blue-200">底部</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-medium mb-1">颜色主题</div>
|
||||
<select class="text-xs p-1 border border-gray-300 rounded w-full">
|
||||
<option>亮色</option>
|
||||
<option>暗色</option>
|
||||
<option>高对比度</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-xs font-medium mb-1">字体大小</div>
|
||||
<input type="range" min="10" max="16" value="12" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// 面板大小状态
|
||||
const leftPanelWidth = ref(256);
|
||||
const rightPanelWidth = ref(288);
|
||||
const bottomPanelHeight = ref(192);
|
||||
|
||||
// 面板可见性状态
|
||||
const leftPanelVisible = ref(true);
|
||||
const rightPanelVisible = ref(true);
|
||||
const bottomPanelVisible = ref(true);
|
||||
|
||||
// 标签页状态
|
||||
const activeRightTab = ref('properties');
|
||||
const activeBottomTab = ref('debug');
|
||||
|
||||
// 拖拽状态
|
||||
const resizing = ref(false);
|
||||
const resizeSide = ref('');
|
||||
const startX = ref(0);
|
||||
const startY = ref(0);
|
||||
const startLeftWidth = ref(0);
|
||||
const startRightWidth = ref(0);
|
||||
const startBottomHeight = ref(0);
|
||||
|
||||
// 浮动窗口状态
|
||||
const floatingWindowVisible = ref(false);
|
||||
const floatingWindowX = ref(400);
|
||||
const floatingWindowY = ref(200);
|
||||
const draggingFloatingWindow = ref(false);
|
||||
const startFloatingWindowX = ref(0);
|
||||
const startFloatingWindowY = ref(0);
|
||||
|
||||
// 切换标签页
|
||||
function switchTab(tab) {
|
||||
activeRightTab.value = tab;
|
||||
}
|
||||
|
||||
function switchBottomTab(tab) {
|
||||
activeBottomTab.value = tab;
|
||||
}
|
||||
|
||||
// 折叠/展开面板
|
||||
function toggleLeftPanel() {
|
||||
leftPanelVisible.value = !leftPanelVisible.value;
|
||||
}
|
||||
|
||||
function toggleRightPanel() {
|
||||
rightPanelVisible.value = !rightPanelVisible.value;
|
||||
}
|
||||
|
||||
function toggleBottomPanel() {
|
||||
bottomPanelVisible.value = !bottomPanelVisible.value;
|
||||
}
|
||||
|
||||
// 切换浮动窗口
|
||||
function toggleFloatingWindow() {
|
||||
floatingWindowVisible.value = !floatingWindowVisible.value;
|
||||
}
|
||||
|
||||
// 开始调整大小
|
||||
function startResize(side) {
|
||||
resizing.value = true;
|
||||
resizeSide.value = side;
|
||||
startX.value = event.clientX;
|
||||
startY.value = event.clientY;
|
||||
startLeftWidth.value = leftPanelWidth.value;
|
||||
startRightWidth.value = rightPanelWidth.value;
|
||||
startBottomHeight.value = bottomPanelHeight.value;
|
||||
|
||||
// 防止文本选择
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = side === 'bottom' ? 'row-resize' : 'col-resize';
|
||||
}
|
||||
|
||||
// 开始拖拽浮动窗口
|
||||
function startDraggingFloatingWindow(event) {
|
||||
draggingFloatingWindow.value = true;
|
||||
startFloatingWindowX.value = event.clientX - floatingWindowX.value;
|
||||
startFloatingWindowY.value = event.clientY - floatingWindowY.value;
|
||||
|
||||
// 防止文本选择
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'move';
|
||||
}
|
||||
|
||||
// 停靠浮动窗口
|
||||
function dockFloatingWindow(location) {
|
||||
floatingWindowVisible.value = false;
|
||||
|
||||
// 根据位置显示对应面板
|
||||
if (location === 'right') {
|
||||
rightPanelVisible.value = true;
|
||||
activeRightTab.value = 'properties';
|
||||
} else if (location === 'bottom') {
|
||||
bottomPanelVisible.value = true;
|
||||
activeBottomTab.value = 'debug';
|
||||
}
|
||||
}
|
||||
|
||||
// 停靠面板到指定位置
|
||||
function dockPanel(location) {
|
||||
// 示例功能:根据位置显示对应面板
|
||||
if (location === 'left') {
|
||||
leftPanelVisible.value = true;
|
||||
} else if (location === 'right') {
|
||||
rightPanelVisible.value = true;
|
||||
} else if (location === 'bottom') {
|
||||
bottomPanelVisible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理鼠标移动
|
||||
function handleMouseMove(event) {
|
||||
if (resizing.value) {
|
||||
switch (resizeSide.value) {
|
||||
case 'left':
|
||||
const newLeftWidth = Math.max(150, Math.min(400, startLeftWidth.value + (event.clientX - startX.value)));
|
||||
leftPanelWidth.value = newLeftWidth;
|
||||
break;
|
||||
case 'right':
|
||||
const newRightWidth = Math.max(150, Math.min(400, startRightWidth.value - (event.clientX - startX.value)));
|
||||
rightPanelWidth.value = newRightWidth;
|
||||
break;
|
||||
case 'bottom':
|
||||
const newBottomHeight = Math.max(100, Math.min(400, startBottomHeight.value - (event.clientY - startY.value)));
|
||||
bottomPanelHeight.value = newBottomHeight;
|
||||
break;
|
||||
}
|
||||
} else if (draggingFloatingWindow.value) {
|
||||
// 拖拽浮动窗口
|
||||
floatingWindowX.value = event.clientX - startFloatingWindowX.value;
|
||||
floatingWindowY.value = event.clientY - startFloatingWindowY.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束调整大小或拖拽
|
||||
function handleMouseUp() {
|
||||
resizing.value = false;
|
||||
draggingFloatingWindow.value = false;
|
||||
resizeSide.value = '';
|
||||
document.body.style.userSelect = '';
|
||||
document.body.style.cursor = '';
|
||||
}
|
||||
|
||||
// 监听鼠标事件
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 停靠式面板样式 */
|
||||
.dock-panel {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dock-panel-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.dock-panel-header:hover {
|
||||
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
transition: background-color 0.2s ease;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 垂直边框缩放手柄样式 */
|
||||
.vertical-resize {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.vertical-resize:hover {
|
||||
background-color: rgba(107, 114, 128, 0.1);
|
||||
}
|
||||
|
||||
/* 水平边框缩放手柄样式 */
|
||||
.horizontal-resize {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.horizontal-resize:hover {
|
||||
background-color: rgba(107, 114, 128, 0.1);
|
||||
}
|
||||
|
||||
/* 调整光标样式以提供更好的视觉反馈 */
|
||||
.cursor-col-resize {
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.cursor-row-resize {
|
||||
cursor: row-resize;
|
||||
}
|
||||
</style>
|
||||
44
AutoRobot/Windows/Robot/Web/src/views/Home.vue
Normal file
44
AutoRobot/Windows/Robot/Web/src/views/Home.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-8">
|
||||
<div class="text-center py-16">
|
||||
<h2 class="text-4xl font-bold text-gray-800 mb-6">欢迎使用 AutoRobot</h2>
|
||||
<p class="text-xl text-gray-600 mb-10 max-w-2xl mx-auto">
|
||||
这是一个用于自动化控制和监控的应用程序,请从上方导航栏选择您需要的功能。
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-6">
|
||||
<router-link
|
||||
to="/ide"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-colors shadow-lg"
|
||||
>
|
||||
打开IDE界面
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/home"
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-colors shadow-lg"
|
||||
>
|
||||
进入控制面板
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/framework-test"
|
||||
class="bg-purple-600 hover:bg-purple-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-colors shadow-lg"
|
||||
>
|
||||
框架测试
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/dock-panel-demo"
|
||||
class="bg-orange-600 hover:bg-orange-700 text-white px-8 py-4 rounded-lg text-lg font-semibold transition-colors shadow-lg"
|
||||
>
|
||||
停靠面板演示
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 主页样式 */
|
||||
</style>
|
||||
398
AutoRobot/Windows/Robot/Web/src/views/HomePage.vue
Normal file
398
AutoRobot/Windows/Robot/Web/src/views/HomePage.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<div class="min-h-screen max-h-screen flex flex-col bg-gradient-to-br from-gray-50 via-gray-100 to-blue-50 overflow-hidden">
|
||||
<!-- 导航栏组件 -->
|
||||
<Header
|
||||
:is-connected="isConnected"
|
||||
:ws-url="wsUrl"
|
||||
@toggle-connection="toggleConnection"
|
||||
/>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="flex-grow h-full container mx-auto px-4 sm:px-6 py-6">
|
||||
<!-- 移动端连接状态条 -->
|
||||
<div class="sm:hidden mb-4 p-3 rounded-xl bg-white/80 backdrop-blur-sm shadow-md border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span :class="connectionIconClass" class="animate-pulse"></span>
|
||||
<span class="text-sm font-medium" :class="connectionTextClass">{{ connectionStatusText }}</span>
|
||||
</div>
|
||||
<div class="text-xs font-mono text-gray-500 truncate">
|
||||
{{ wsUrl }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区两列布局 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 h-full w-full">
|
||||
<!-- 标签页面板 - 占据2/3宽度 -->
|
||||
<div class="lg:col-span-2 h-full">
|
||||
<div class="h-full bg-white/90 backdrop-blur-sm rounded-2xl shadow-lg border border-gray-100">
|
||||
<TabPage :tabs="tabs">
|
||||
<template #tab-content="{ activeTab }">
|
||||
<div v-if="activeTab === 0" class="bg-transparent h-[calc(100vh-8rem-4rem-0.5rem)] flex flex-col">
|
||||
<!-- 消息显示区域 - 8rem=导航栏+页边距, 4rem=页脚, 3.5rem=标签栏 -->
|
||||
<MessageArea :messages="messages" class="flex-grow" />
|
||||
</div>
|
||||
<div v-else-if="activeTab === 1" class="p-4 bg-transparent h-full">
|
||||
<div v-if="screenshots.length > 0" class="h-full">
|
||||
<!-- 只显示最新的一张截图 -->
|
||||
<img
|
||||
:src="getImageSource(screenshots[0].data)"
|
||||
alt="手机截图"
|
||||
class="w-full h-auto max-h-[calc(100vh-16rem)] object-contain rounded-lg shadow-md"
|
||||
@error="handleImageError($event, screenshots[0].id)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="h-full flex items-center justify-center text-gray-400">
|
||||
<p>暂无截图数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</TabPage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制面板 - 占据1/3宽度 -->
|
||||
<div class="h-full">
|
||||
<div class="h-full bg-white/90 backdrop-blur-sm rounded-2xl shadow-lg border border-gray-100 overflow-y-auto">
|
||||
<div class="p-4 space-y-6">
|
||||
<!-- 发送消息卡片 -->
|
||||
<SendMessage
|
||||
:is-connected="isConnected"
|
||||
@send-message="sendMessage"
|
||||
/>
|
||||
|
||||
<!-- 手机端地址卡片 -->
|
||||
<ConnectionInfo
|
||||
:is-connected="isConnected"
|
||||
:ws-url="wsUrl"
|
||||
/>
|
||||
|
||||
<!-- 快捷操作卡片 -->
|
||||
<QuickActions
|
||||
:is-connected="isConnected"
|
||||
@clear-messages="clearMessages"
|
||||
@show-help="showConnectionHelp"
|
||||
@take-phone-screenshot="takePhoneScreenshot"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 页脚组件 -->
|
||||
<Footer class="mt-0" />
|
||||
|
||||
<!-- WebSocket客户端组件(无UI界面)- 使用私有库组件 -->
|
||||
<component
|
||||
:is="WebSocketClient"
|
||||
ref="wsClient"
|
||||
:url="wsUrl"
|
||||
:autoConnect="false"
|
||||
@connect="handleConnect"
|
||||
@disconnect="handleDisconnect"
|
||||
@message="handleMessage"
|
||||
@error="handleError"
|
||||
@status-change="handleStatusChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
|
||||
// 导入所有组件
|
||||
import Header from '../components/Header.vue';
|
||||
import MessageArea from '../components/MessageArea.vue';
|
||||
import SendMessage from '../components/SendMessage.vue';
|
||||
import ConnectionInfo from '../components/ConnectionInfo.vue';
|
||||
import QuickActions from '../components/QuickActions.vue';
|
||||
import Footer from '../components/Footer.vue';
|
||||
import TabPage from '../components/TabPage.vue';
|
||||
// 从私有库导入WebSocketClient组件
|
||||
import { WebSocketClient } from 'com.joyd.joydlib';
|
||||
|
||||
// 全局调试模式开关
|
||||
window.DEBUG = true;
|
||||
|
||||
// 状态管理
|
||||
const messages = ref([
|
||||
{
|
||||
id: 1,
|
||||
sender: '系统',
|
||||
content: '欢迎使用WebSocket通信中心,请连接服务器开始交互',
|
||||
time: new Date().toLocaleTimeString(),
|
||||
isSystem: true
|
||||
}
|
||||
]);
|
||||
const wsUrl = ref('ws://localhost:8805');
|
||||
const isConnected = ref(false);
|
||||
// 存储截图数据
|
||||
const screenshots = ref([]);
|
||||
|
||||
// 标签页配置
|
||||
const tabs = ref([
|
||||
{
|
||||
label: '消息面板',
|
||||
},
|
||||
{
|
||||
label: '手机截图',
|
||||
}
|
||||
]);
|
||||
|
||||
// 创建WebSocketClient组件的引用
|
||||
const wsClient = ref(null);
|
||||
|
||||
// 计算属性
|
||||
const connectionIconClass = computed(() => {
|
||||
return isConnected.value
|
||||
? 'rounded-full bg-green-500'
|
||||
: 'rounded-full bg-red-500';
|
||||
});
|
||||
|
||||
const connectionTextClass = computed(() => {
|
||||
return isConnected.value ? 'text-green-600' : 'text-red-600';
|
||||
});
|
||||
|
||||
const connectionStatusText = computed(() => {
|
||||
return isConnected.value ? '已连接' : '未连接';
|
||||
});
|
||||
|
||||
// 方法
|
||||
const addMessage = (sender, content, isSystem = false) => {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleTimeString();
|
||||
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
sender,
|
||||
content,
|
||||
time: timeString,
|
||||
isSystem
|
||||
});
|
||||
};
|
||||
|
||||
// WebSocket事件处理函数
|
||||
const handleConnect = (url) => {
|
||||
addMessage('系统', '已成功连接到WebSocket服务器', true);
|
||||
isConnected.value = true;
|
||||
};
|
||||
|
||||
const handleDisconnect = (code, reason) => {
|
||||
isConnected.value = false;
|
||||
let closeMessage = '连接已断开';
|
||||
if (code === 1006) {
|
||||
closeMessage = '连接异常断开';
|
||||
}
|
||||
addMessage('系统', closeMessage, true);
|
||||
};
|
||||
|
||||
const handleMessage = (data) => {
|
||||
// 检查是否为二进制数据(截图)
|
||||
if (data instanceof Blob) {
|
||||
|
||||
addMessage('系统', '收到手机截图', true);
|
||||
|
||||
// 将二进制数据添加到列表中
|
||||
screenshots.value.unshift({
|
||||
id: Date.now(),
|
||||
data: data,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
} else {
|
||||
// 处理文本消息
|
||||
addMessage('服务器', data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (errorMessage) => {
|
||||
addMessage('系统', errorMessage, true);
|
||||
};
|
||||
|
||||
const handleStatusChange = (connected) => {
|
||||
isConnected.value = connected;
|
||||
};
|
||||
|
||||
// 切换连接状态
|
||||
const toggleConnection = () => {
|
||||
if (wsClient.value) {
|
||||
wsClient.value.toggleConnection();
|
||||
}
|
||||
};
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = (message) => {
|
||||
if (!message || !wsClient.value || !isConnected.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 显示发送中状态
|
||||
const sendingMessage = {
|
||||
id: Date.now(),
|
||||
sender: 'me',
|
||||
content: message + ' <span class="text-xs text-gray-400">发送中...</span>',
|
||||
time: new Date().toLocaleTimeString(),
|
||||
isSystem: false,
|
||||
sending: true
|
||||
};
|
||||
messages.value.push(sendingMessage);
|
||||
|
||||
// 发送消息
|
||||
const success = wsClient.value.send(message);
|
||||
|
||||
// 更新消息状态
|
||||
setTimeout(() => {
|
||||
const index = messages.value.findIndex(m => m.id === sendingMessage.id);
|
||||
if (index !== -1) {
|
||||
messages.value.splice(index, 1);
|
||||
addMessage('me', message);
|
||||
}
|
||||
}, 300);
|
||||
} catch (error) {
|
||||
addMessage('系统', '消息发送失败', true);
|
||||
|
||||
// 移除发送中状态的消息
|
||||
const index = messages.value.findIndex(m => m.sending);
|
||||
if (index !== -1) {
|
||||
messages.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送手机截屏命令
|
||||
const takePhoneScreenshot = () => {
|
||||
if (!wsClient.value || !isConnected.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 显示命令发送状态
|
||||
addMessage('me', '发送手机截屏命令', true);
|
||||
|
||||
// 生成唯一的消息ID
|
||||
const messageId = Date.now().toString();
|
||||
// 发送符合 messageId:content 格式的消息
|
||||
const screenshotCommand = messageId + ':screenshot';
|
||||
const success = wsClient.value.send(screenshotCommand);
|
||||
|
||||
// 记录命令发送状态
|
||||
if (success) {
|
||||
addMessage('系统', '手机截屏命令已发送', true);
|
||||
} else {
|
||||
addMessage('系统', '手机截屏命令发送失败', true);
|
||||
}
|
||||
} catch (error) {
|
||||
addMessage('系统', '手机截屏命令发送失败', true);
|
||||
}
|
||||
};
|
||||
|
||||
// 事件处理方法
|
||||
const clearMessages = () => {
|
||||
if (confirm('确定要清空所有消息吗?')) {
|
||||
messages.value = [
|
||||
{
|
||||
id: Date.now(),
|
||||
sender: '系统',
|
||||
content: '消息已清空',
|
||||
time: new Date().toLocaleTimeString(),
|
||||
isSystem: true
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// 获取图片源,处理二进制Blob数据
|
||||
const getImageSource = (data) => {
|
||||
if (data instanceof Blob) {
|
||||
// 为二进制数据创建一个临时的URL
|
||||
return URL.createObjectURL(data);
|
||||
} else {
|
||||
// 向后兼容:如果数据是字符串,继续处理base64编码
|
||||
// 清理Base64数据,移除可能的无效字符和换行符
|
||||
let cleanedData = data.replace(/[\r\n\s]/g, '');
|
||||
|
||||
// 检查是否已经包含了data:image前缀
|
||||
if (cleanedData.startsWith('data:image')) {
|
||||
return cleanedData;
|
||||
}
|
||||
|
||||
// 尝试添加不同的MIME类型
|
||||
// 首先尝试标准的JPEG
|
||||
return 'data:image/jpeg;base64,' + cleanedData;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = (event, screenshotId) => {
|
||||
|
||||
// 显示错误提示
|
||||
event.target.alt = '图片加载失败';
|
||||
event.target.src = '';
|
||||
};
|
||||
|
||||
// 组件卸载时清理创建的object URLs
|
||||
onBeforeUnmount(() => {
|
||||
// 清理所有创建的object URLs
|
||||
screenshots.value.forEach(screenshot => {
|
||||
if (screenshot.data instanceof Blob) {
|
||||
// 注意:我们无法直接访问已创建的URL,所以这里不做清理
|
||||
// 在实际项目中,应该在创建URL时保存它的引用
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭WebSocket连接
|
||||
if (wsClient.value) {
|
||||
wsClient.value.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
// 保存截图到本地
|
||||
const saveScreenshot = (screenshotData) => {
|
||||
try {
|
||||
// 创建一个临时的下载链接
|
||||
const link = document.createElement('a');
|
||||
|
||||
if (screenshotData instanceof Blob) {
|
||||
// 对于二进制数据,使用URL.createObjectURL
|
||||
link.href = URL.createObjectURL(screenshotData);
|
||||
// 设置合适的文件名和扩展名
|
||||
link.download = `screenshot_${new Date().toISOString().replace(/:/g, '-')}.jpg`;
|
||||
} else {
|
||||
// 对于字符串数据(向后兼容),使用getImageSource
|
||||
link.href = getImageSource(screenshotData);
|
||||
link.download = `screenshot_${new Date().toISOString().replace(/:/g, '-')}.jpg`;
|
||||
}
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
// 保存完成后清理URL对象(仅针对Blob)
|
||||
if (screenshotData instanceof Blob) {
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
addMessage('系统', '保存截图失败', true);
|
||||
}
|
||||
};
|
||||
|
||||
const showConnectionHelp = () => {
|
||||
alert('WebSocket连接帮助:\n\n1. 点击"连接服务器"按钮建立连接\n2. 连接成功后可以发送和接收消息\n3. 确保服务器正在运行并且端口8805可访问\n\n如有问题,请检查网络连接和服务器状态。');
|
||||
};
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// 组件挂载时尝试自动连接服务器
|
||||
toggleConnection();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 组件卸载时关闭WebSocket连接
|
||||
if (wsClient.value) {
|
||||
wsClient.value.disconnect();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
237
AutoRobot/Windows/Robot/Web/src/views/ide.vue
Normal file
237
AutoRobot/Windows/Robot/Web/src/views/ide.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="vs-frame">
|
||||
<!-- 顶部菜单栏 -->
|
||||
<div class="vs-menu-bar">
|
||||
<div class="menu-item menu-dropdown">文件
|
||||
<div class="menu-dropdown-content">
|
||||
<div class="dropdown-item">新建</div>
|
||||
<div class="dropdown-item">退出</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item">编辑</div>
|
||||
<div class="menu-item">视图</div>
|
||||
<div class="menu-item">项目</div>
|
||||
<div class="menu-item">调试</div>
|
||||
<div class="menu-item">工具</div>
|
||||
<div class="menu-item">帮助</div>
|
||||
</div>
|
||||
|
||||
<!-- 主工作区 -->
|
||||
<div class="vs-workspace">
|
||||
<!-- 左侧解决方案资源管理器 -->
|
||||
<div class="vs-explorer">
|
||||
<div class="explorer-header">解决方案资源管理器</div>
|
||||
<div class="explorer-content">
|
||||
<div class="solution-item">
|
||||
<span class="expand-icon">▼</span>
|
||||
AutoRobot
|
||||
<div class="project-item">
|
||||
<span class="expand-icon">▼</span>
|
||||
Android
|
||||
</div>
|
||||
<div class="project-item">
|
||||
<span class="expand-icon">▼</span>
|
||||
Windows
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间主编辑区 -->
|
||||
<div class="vs-editor">
|
||||
<div class="editor-tabs">
|
||||
<div class="tab active">HomePage.vue</div>
|
||||
</div>
|
||||
<div class="editor-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<div class="vs-status-bar">
|
||||
<div class="status-item">就绪</div>
|
||||
<div class="status-item">行 1,列 1</div>
|
||||
<div class="status-item">UTF-8</div>
|
||||
<div class="status-item">Windows</div>
|
||||
<div class="status-item">解决方案配置: 调试</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Visual Studio风格框架样式 */
|
||||
.vs-frame {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部菜单栏 */
|
||||
.vs-menu-bar {
|
||||
height: 28px;
|
||||
background-color: #2d2d30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #484848;
|
||||
padding: 0 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 0 12px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
|
||||
/* 下拉菜单样式 */
|
||||
.menu-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: #2d2d30;
|
||||
border: 1px solid #484848;
|
||||
border-top: none;
|
||||
min-width: 120px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.menu-dropdown:hover .menu-dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #007acc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 主工作区 */
|
||||
.vs-workspace {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧解决方案资源管理器 */
|
||||
.vs-explorer {
|
||||
width: 250px;
|
||||
background-color: #252526;
|
||||
border-right: 1px solid #484848;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.explorer-header {
|
||||
height: 28px;
|
||||
background-color: #2d2d30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #484848;
|
||||
}
|
||||
|
||||
.explorer-content {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
overflow-y: auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.solution-item,
|
||||
.project-item {
|
||||
padding: 2px 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 10px;
|
||||
margin-right: 4px;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
/* 中间主编辑区 */
|
||||
.vs-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
.editor-tabs {
|
||||
height: 30px;
|
||||
background-color: #2d2d30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #484848;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0 16px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
background-color: #3c3c3c;
|
||||
border: 1px solid #484848;
|
||||
border-bottom: none;
|
||||
font-size: 13px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: #1e1e1e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 底部状态栏 */
|
||||
.vs-status-bar {
|
||||
height: 22px;
|
||||
background-color: #2d2d30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 1px solid #484848;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
padding: 0 12px;
|
||||
border-right: 1px solid #484848;
|
||||
}
|
||||
|
||||
.status-item:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user