YingXingAI/pages/test/websocket-test.vue

534 lines
12 KiB
Vue
Raw Normal View History

<template>
<view class="test-page">
<view class="test-header">
<text class="title">WebSocket 测试工具</text>
<text class="subtitle">实时测试 WebSocket 连接和消息</text>
</view>
<!-- 连接状态 -->
<view class="status-card">
<view class="status-item">
<text class="label">连接状态</text>
<text :class="['value', connectionStatus.class]">{{ connectionStatus.text }}</text>
</view>
<view class="status-item">
<text class="label">队列消息</text>
<text class="value">{{ queueLength }} </text>
</view>
<view class="status-item">
<text class="label">重连次数</text>
<text class="value">{{ reconnectAttempts }} </text>
</view>
</view>
<!-- 连接控制 -->
<view class="control-card">
<view class="control-title">连接控制</view>
<input
class="url-input"
v-model="wsUrl"
placeholder="输入 WebSocket 地址"
/>
<view class="button-group">
<button class="btn btn-primary" @click="handleConnect" :disabled="isConnected">
连接
</button>
<button class="btn btn-danger" @click="handleDisconnect" :disabled="!isConnected">
断开
</button>
</view>
</view>
<!-- 消息发送 -->
<view class="message-card">
<view class="control-title">发送消息</view>
<textarea
class="message-input"
v-model="messageToSend"
placeholder="输入消息内容JSON 格式)"
:rows="5"
></textarea>
<view class="button-group">
<button class="btn btn-success" @click="handleSendMessage" :disabled="!isConnected">
发送消息
</button>
<button class="btn btn-secondary" @click="handleSendPing" :disabled="!isConnected">
发送心跳
</button>
</view>
</view>
<!-- 快速测试 -->
<view class="quick-test-card">
<view class="control-title">快速测试</view>
<view class="button-group">
<button class="btn btn-info" @click="testEcho">
Echo 服务测试
</button>
<button class="btn btn-info" @click="testLocalServer">
本地服务器测试
</button>
</view>
</view>
<!-- 日志显示 -->
<view class="log-card">
<view class="log-header">
<text class="control-title">日志 ({{ logs.length }})</text>
<button class="btn-clear" @click="clearLogs">清空</button>
</view>
<scroll-view class="log-content" scroll-y>
<view
v-for="(log, index) in logs"
:key="index"
:class="['log-item', `log-${log.type}`]"
>
<text class="log-time">{{ log.time }}</text>
<text class="log-type">{{ log.typeText }}</text>
<text class="log-message">{{ log.message }}</text>
</view>
<view v-if="logs.length === 0" class="empty-log">
暂无日志
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import wsManager from '@/utils/websocket-manager.js'
export default {
name: 'WebSocketTest',
data() {
return {
// WebSocket URL
wsUrl: 'wss://echo.websocket.org',
// 连接状态
isConnected: false,
reconnectAttempts: 0,
queueLength: 0,
// 消息
messageToSend: JSON.stringify({
type: 'test',
content: 'Hello WebSocket!',
timestamp: Date.now()
}, null, 2),
// 日志
logs: [],
// 计时器
startTime: 0
}
},
computed: {
connectionStatus() {
if (this.isConnected) {
return { text: '已连接', class: 'status-connected' }
} else if (this.reconnectAttempts > 0) {
return { text: '重连中...', class: 'status-reconnecting' }
} else {
return { text: '未连接', class: 'status-disconnected' }
}
}
},
onLoad() {
this.setupEventListeners()
this.addLog('info', '测试工具已加载')
},
onUnload() {
// 清理事件监听
wsManager.off('open', this.handleOpen)
wsManager.off('close', this.handleClose)
wsManager.off('error', this.handleError)
wsManager.off('message', this.handleMessage)
wsManager.off('reconnect', this.handleReconnect)
},
methods: {
// 设置事件监听
setupEventListeners() {
this.handleOpen = () => {
this.isConnected = true
this.reconnectAttempts = 0
const duration = Date.now() - this.startTime
this.addLog('success', `连接成功 (耗时 ${duration}ms)`)
}
this.handleClose = (res) => {
this.isConnected = false
this.addLog('warning', `连接关闭 (code: ${res.code})`)
}
this.handleError = (err) => {
this.addLog('error', `连接错误: ${JSON.stringify(err)}`)
}
this.handleMessage = (data) => {
this.addLog('message', `收到消息: ${JSON.stringify(data, null, 2)}`)
}
this.handleReconnect = (attempts) => {
this.reconnectAttempts = attempts
this.addLog('info', `正在重连 (第 ${attempts} 次)`)
}
wsManager.on('open', this.handleOpen)
wsManager.on('close', this.handleClose)
wsManager.on('error', this.handleError)
wsManager.on('message', this.handleMessage)
wsManager.on('reconnect', this.handleReconnect)
},
// 连接
handleConnect() {
if (!this.wsUrl) {
uni.showToast({ title: '请输入 WebSocket 地址', icon: 'none' })
return
}
this.addLog('info', `开始连接: ${this.wsUrl}`)
this.startTime = Date.now()
wsManager.connect(this.wsUrl, {
reconnectInterval: 3000,
maxReconnectAttempts: 5,
heartbeatInterval: 30000
})
this.updateState()
},
// 断开连接
handleDisconnect() {
this.addLog('info', '手动断开连接')
wsManager.close(true)
this.isConnected = false
this.reconnectAttempts = 0
},
// 发送消息
handleSendMessage() {
try {
const message = JSON.parse(this.messageToSend)
this.addLog('send', `发送消息: ${JSON.stringify(message)}`)
wsManager.send(message)
} catch (e) {
this.addLog('error', `消息格式错误: ${e.message}`)
uni.showToast({ title: 'JSON 格式错误', icon: 'none' })
}
},
// 发送心跳
handleSendPing() {
const ping = {
type: 'ping',
timestamp: Date.now()
}
this.addLog('send', `发送心跳: ${JSON.stringify(ping)}`)
wsManager.send(ping)
},
// Echo 服务测试
testEcho() {
this.wsUrl = 'wss://echo.websocket.org'
this.messageToSend = JSON.stringify({
type: 'test',
content: 'Echo 测试消息',
timestamp: Date.now()
}, null, 2)
this.addLog('info', '开始 Echo 服务测试')
uni.showToast({ title: '点击"连接"开始测试', icon: 'none' })
},
// 本地服务器测试
testLocalServer() {
this.wsUrl = 'ws://localhost:8082/ws/chat?token=test-token'
this.messageToSend = JSON.stringify({
type: 'message',
fromUserId: 'test_user',
toUserId: 'admin',
content: '本地服务器测试',
timestamp: Date.now()
}, null, 2)
this.addLog('info', '开始本地服务器测试')
uni.showToast({ title: '点击"连接"开始测试', icon: 'none' })
},
// 更新状态
updateState() {
const state = wsManager.getState()
this.isConnected = state.isConnected
this.reconnectAttempts = state.reconnectAttempts
this.queueLength = state.queueLength
},
// 添加日志
addLog(type, message) {
const typeMap = {
info: ' 信息',
success: '✅ 成功',
warning: '⚠️ 警告',
error: '❌ 错误',
send: '📤 发送',
message: '📩 接收'
}
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
this.logs.unshift({
type,
typeText: typeMap[type] || type,
message,
time
})
// 最多保留 100 条日志
if (this.logs.length > 100) {
this.logs.pop()
}
},
// 清空日志
clearLogs() {
this.logs = []
this.addLog('info', '日志已清空')
}
}
}
</script>
<style scoped>
.test-page {
padding: 20rpx;
background-color: #f5f6fa;
min-height: 100vh;
}
.test-header {
text-align: center;
padding: 40rpx 0;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
display: block;
}
.subtitle {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
display: block;
}
.status-card,
.control-card,
.message-card,
.quick-test-card,
.log-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.status-item:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: #666;
}
.value {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.status-connected {
color: #52c41a;
}
.status-reconnecting {
color: #faad14;
}
.status-disconnected {
color: #999;
}
.control-title {
font-size: 30rpx;
font-weight: 500;
color: #333;
margin-bottom: 20rpx;
}
.url-input,
.message-input {
width: 100%;
padding: 20rpx;
border: 1rpx solid #d9d9d9;
border-radius: 8rpx;
font-size: 26rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
}
.message-input {
min-height: 200rpx;
font-family: 'Courier New', monospace;
}
.button-group {
display: flex;
gap: 20rpx;
}
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
}
.btn-primary {
background: #1890ff;
color: #fff;
}
.btn-danger {
background: #ff4d4f;
color: #fff;
}
.btn-success {
background: #52c41a;
color: #fff;
}
.btn-secondary {
background: #d9d9d9;
color: #666;
}
.btn-info {
background: #722ed1;
color: #fff;
}
.btn:disabled {
opacity: 0.5;
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.btn-clear {
padding: 10rpx 20rpx;
background: #f0f0f0;
border: none;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
}
.log-content {
height: 600rpx;
border: 1rpx solid #f0f0f0;
border-radius: 8rpx;
padding: 20rpx;
}
.log-item {
padding: 20rpx;
margin-bottom: 10rpx;
border-radius: 8rpx;
font-size: 24rpx;
line-height: 1.6;
word-break: break-all;
}
.log-info {
background: #e6f7ff;
border-left: 4rpx solid #1890ff;
}
.log-success {
background: #f6ffed;
border-left: 4rpx solid #52c41a;
}
.log-warning {
background: #fffbe6;
border-left: 4rpx solid #faad14;
}
.log-error {
background: #fff2f0;
border-left: 4rpx solid #ff4d4f;
}
.log-send {
background: #f9f0ff;
border-left: 4rpx solid #722ed1;
}
.log-message {
background: #fff0f6;
border-left: 4rpx solid #eb2f96;
}
.log-time {
color: #999;
margin-right: 20rpx;
}
.log-type {
font-weight: 500;
margin-right: 20rpx;
}
.log-message {
color: #333;
}
.empty-log {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
</style>