534 lines
12 KiB
Vue
534 lines
12 KiB
Vue
<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>
|
||
|