Files
2026-06-02 10:42:33 +08:00

538 lines
10 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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="container">
<!-- 连接状态卡片 -->
<view class="status-card">
<text class="status-label">连接状态</text>
<view class="status-value" :class="connectionStatusClass">
<text>{{ socketStore.getStatusText() }}</text>
</view>
<view class="status-info" v-if="socketStore.reconnectAttempts > 0">
<text class="reconnect-info">重连次数: {{ socketStore.reconnectAttempts }}</text>
</view>
</view>
<!-- 配置区域 -->
<view class="config-card">
<text class="section-title">连接配置</text>
<view class="input-group">
<text class="input-label">Token</text>
<input class="input-field" v-model="config.token" placeholder="请输入Token" />
</view>
<view class="input-group">
<text class="input-label">Conversation ID</text>
<input class="input-field" v-model="config.conversationId" placeholder="请输入会话ID(可选)" />
</view>
</view>
<!-- 连接控制 -->
<view class="control-card">
<button class="btn btn-primary" :disabled="socketStore.isConnected || socketStore.isConnecting"
@click="handleConnect">
{{ socketStore.isConnecting ? '连接中...' : '连接' }}
</button>
<button class="btn btn-danger" :disabled="!socketStore.isConnected" @click="handleDisconnect">
断开
</button>
<button class="btn btn-secondary" @click="handleSendPing" :disabled="!socketStore.isConnected">
Ping
</button>
</view>
<!-- 发送消息区域 -->
<view class="send-card">
<text class="section-title">发送消息</text>
<view class="send-input-wrapper">
<textarea class="send-input" v-model="sendMessage" placeholder="输入要发送的消息(JSON格式)"
:disabled="!socketStore.isConnected"></textarea>
<view class="send-buttons">
<button class="btn btn-primary btn-small"
:disabled="!socketStore.isConnected || !sendMessage.trim()" @click="handleSend">
发送
</button>
<button class="btn btn-outline btn-small" :disabled="!sendMessage.trim()" @click="formatJson">
格式化
</button>
</view>
</view>
<!-- 快捷消息 -->
<view class="quick-messages">
<text class="quick-label">快捷消息:</text>
<view class="quick-btns">
<button v-for="item in quickMessages" :key="item.label" class="quick-btn"
:disabled="!socketStore.isConnected" @click="sendQuickMessage(item.data)">
{{ item.label }}
</button>
</view>
</view>
</view>
<!-- 日志区域 -->
<view class="log-card">
<view class="log-header">
<view class="log-title-wrapper">
<text class="section-title">日志</text>
<text class="log-count">({{ socketStore.logs.length }})</text>
</view>
<view class="log-actions">
<button class="btn btn-small btn-secondary" @click="socketStore.clearLogs">清空</button>
</view>
</view>
<scroll-view class="log-list" scroll-y @scrolltoupper="loadMoreLogs">
<view v-for="(log, index) in socketStore.logs" :key="index" class="log-item" :class="'log-' + log.type">
<text class="log-time">{{ log.time }}</text>
<text class="log-content">{{ log.content }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import {
ref,
computed,
onMounted,
onUnmounted,
watch
} from 'vue'
import {
useSocketStore
} from '@/stores/socket.js'
import {
getToken,
getTaskCallId,
getCurrentSessionId
} from '@/utils/user-info.js'
// Store
const socketStore = useSocketStore()
// 响应式数据
const config = ref({
token: getToken(),
conversationId: getCurrentSessionId()
})
const sendMessage = ref('')
const quickMessages = ref([{
label: 'Ping',
data: {
ws_event: 'ping'
}
},
{
label: '认证',
data: {
ws_event: 'auth',
data: {
token: '',
conversation_id: ''
}
}
},
{
label: '测试消息',
data: {
ws_event: 'message',
data: {
task_call_id: getTaskCallId(),
token: getToken(),
conversation_id: getCurrentSessionId()
}
}
}
])
// 计算属性
const connectionStatusClass = computed(() => {
const status = socketStore.connectionStatus
return {
'status-connected': status === 'connected',
'status-connecting': status === 'connecting',
'status-error': status === 'error',
'status-disconnected': status === 'disconnected'
}
})
// 方法
const handleConnect = () => {
if (!config.value.token) {
uni.showToast({
title: '请输入Token',
icon: 'none'
})
return
}
socketStore.connect({
token: config.value.token,
conversationId: getCurrentSessionId()
})
}
const handleDisconnect = () => {
socketStore.disconnect()
}
const handleSendPing = async () => {
try {
await socketStore.sendPing()
} catch (e) {
uni.showToast({
title: '发送失败',
icon: 'none'
})
}
}
const handleSend = async () => {
if (!sendMessage.value.trim()) return
try {
const messageData = {
ws_event: 'message',
data: {
task_call_id: getTaskCallId(),
result: {
tools: []
}
}
}
await socketStore.send(messageData)
sendMessage.value = ''
} catch (e) {
uni.showToast({
title: '发送失败',
icon: 'none'
})
}
}
const sendQuickMessage = async (template) => {
// console.log('getCurrentSessionId:', getCurrentSessionId())
const messageData = {
ws_event: 'message',
data: {
task_call_id: getTaskCallId(),
token: getToken(),
conversation_id: getCurrentSessionId()
}
}
try {
await socketStore.send(messageData)
} catch (e) {
uni.showToast({
title: '发送失败',
icon: 'none'
})
}
}
const formatJson = () => {
if (!sendMessage.value.trim()) return
try {
const obj = JSON.parse(sendMessage.value)
sendMessage.value = JSON.stringify(obj, null, 2)
} catch {
uni.showToast({
title: '不是有效的JSON',
icon: 'none'
})
}
}
const loadMoreLogs = () => {
// 预留扩展
}
watch(() => socketStore.isDisconnected, (newVal) => {
if (newVal && socketStore.messageString) {
console.log(socketStore.messageString);
}
})
// 生命周期
onMounted(() => {
socketStore.addLog('info', '=== Socket测试页面 ===')
socketStore.addLog('info', '页面已加载')
if (socketStore.isConnected) {
socketStore.addLog('info', '当前已处于连接状态')
}
})
onUnmounted(() => {
socketStore.addLog('info', '页面卸载')
// 注意这里不主动断开连接由App级别管理
})
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.status-card,
.config-card,
.control-card,
.send-card,
.log-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.status-label {
font-size: 28rpx;
color: #666;
margin-bottom: 16rpx;
}
.status-value {
display: inline-block;
padding: 12rpx 32rpx;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
}
.status-connected {
background-color: #e6f7e6;
color: #52c41a;
}
.status-connecting {
background-color: #fff7e6;
color: #faad14;
}
.status-disconnected {
background-color: #f5f5f5;
color: #999;
}
.status-error {
background-color: #fff1f0;
color: #ff4d4f;
}
.status-info {
margin-top: 16rpx;
}
.reconnect-info {
font-size: 24rpx;
color: #faad14;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
display: block;
}
.input-group {
margin-bottom: 24rpx;
}
.input-label {
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
display: block;
}
.input-field {
border: 2rpx solid #e8e8e8;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
background-color: #fafafa;
}
.control-card {
display: flex;
gap: 20rpx;
}
.btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 12rpx;
font-size: 32rpx;
text-align: center;
border: none;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
}
.btn-danger {
background-color: #ff4d4f;
color: #fff;
}
.btn-secondary {
background-color: #f0f0f0;
color: #666;
}
.btn-outline {
background-color: transparent;
color: #1890ff;
border: 2rpx solid #1890ff;
}
.btn-small {
height: 64rpx;
line-height: 64rpx;
font-size: 26rpx;
padding: 0 24rpx;
}
.btn[disabled] {
opacity: 0.5;
}
.send-input-wrapper {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.send-input {
border: 2rpx solid #e8e8e8;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
min-height: 160rpx;
background-color: #fafafa;
box-sizing: border-box;
}
.send-buttons {
display: flex;
gap: 20rpx;
}
.quick-messages {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 2rpx solid #f0f0f0;
}
.quick-label {
font-size: 26rpx;
color: #999;
margin-bottom: 16rpx;
display: block;
}
.quick-btns {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.quick-btn {
padding: 12rpx 24rpx;
background-color: #f0f5ff;
color: #1890ff;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
}
.quick-btn[disabled] {
opacity: 0.5;
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.log-title-wrapper {
display: flex;
align-items: center;
gap: 12rpx;
}
.log-title-wrapper .section-title {
margin-bottom: 0;
}
.log-count {
font-size: 24rpx;
color: #999;
}
.log-actions {
display: flex;
gap: 12rpx;
}
.log-list {
max-height: 500rpx;
background-color: #1e1e1e;
border-radius: 8rpx;
padding: 20rpx;
}
.log-item {
display: flex;
margin-bottom: 12rpx;
font-family: 'Courier New', monospace;
font-size: 24rpx;
line-height: 1.6;
}
.log-time {
color: #888;
margin-right: 16rpx;
flex-shrink: 0;
}
.log-content {
word-break: break-all;
flex: 1;
color: #fff;
}
.log-info .log-content {
color: #fff;
}
.log-success .log-content {
color: #52c41a;
}
.log-error .log-content {
color: #ff4d4f;
}
.log-warn .log-content {
color: #faad14;
}
.log-send .log-content {
color: #1890ff;
}
.log-receive .log-content {
color: #52c41a;
}
</style>