YingXingAI/pages/test/websocket-test.vue

534 lines
12 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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