398 lines
12 KiB
Vue
398 lines
12 KiB
Vue
<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 'joyd.web.vue.websocket-client';
|
||
|
||
// 全局调试模式开关
|
||
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> |