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,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;">&lt;template&gt;</div>
<div style="padding-left: 24px; color: #4f46e5;">&lt;div</div>
<div style="padding-left: 48px; color: #6b7280;">class=<span style="color: #10b981;">&quot;app&quot;</span></div>
<div style="padding-left: 24px; color: #4f46e5;">&gt;</div>
<div style="padding-left: 48px; color: #1e40af;">&lt;h1&gt;Hello, World!&lt;/h1&gt;</div>
<div style="padding-left: 48px; color: #1e40af;">&lt;p&gt;This is a demo of the Dock Panel Container.&lt;/p&gt;</div>
<div style="padding-left: 24px; color: #4f46e5;">&lt;/div&gt;</div>
<div style="color: #1e40af;">&lt;/template&gt;</div>
<br>
<div style="color: #1e40af;">&lt;script setup&gt;</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;">&lt;/script&gt;</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>

View 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>

View 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>

View 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>

View 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>