2025-04-11 02:31:26 +00:00
|
|
|
<template>
|
2025-04-22 06:26:41 +00:00
|
|
|
<div>
|
|
|
|
<h1>{{ msg }}</h1>
|
|
|
|
|
|
|
|
<!-- 用户ID输入部分 -->
|
|
|
|
<div class="user-input-container">
|
|
|
|
<div class="input-group" :class="{ 'disabled': isConnected }">
|
|
|
|
<input
|
|
|
|
v-model="email"
|
|
|
|
placeholder="请输入您的用户邮箱"
|
|
|
|
:disabled="isConnected"
|
|
|
|
/>
|
|
|
|
<input
|
|
|
|
v-model="targetEmail"
|
|
|
|
placeholder="请输入目标用户邮箱"
|
|
|
|
:disabled="isConnected"
|
|
|
|
@keyup.enter="!isConnected && !isConnecting && connectWebSocket()"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
|
|
<button
|
|
|
|
@click="connectWebSocket"
|
|
|
|
:disabled="isConnected || isConnecting || !email || !targetEmail"
|
|
|
|
:class="{ 'disabled': isConnected || isConnecting || !email || !targetEmail }"
|
|
|
|
>
|
|
|
|
{{ isConnecting ? '连接中...' : '连接' }}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
v-if="isConnected"
|
|
|
|
@click="disconnectWebSocket"
|
|
|
|
class="disconnect-btn"
|
|
|
|
>
|
|
|
|
断开连接
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="connectionError" class="error-message">
|
|
|
|
{{ connectionError }}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="isConnected">
|
|
|
|
<!-- WebSocket 聊天部分 -->
|
|
|
|
<div class="chat-container">
|
|
|
|
<div class="message-list" ref="messageList">
|
|
|
|
<div v-for="(msg, index) in receivedMessages" :key="index" class="message"
|
|
|
|
:class="{ 'error-message': msg.error }">
|
|
|
|
<div v-if="typeof msg === 'string'">{{ msg }}</div>
|
|
|
|
<div v-else>
|
|
|
|
<div class="message-header">
|
|
|
|
<span class="message-sender">{{ msg.sender || 'Unknown' }}</span>
|
|
|
|
<span class="message-time">{{ formatTime(msg.timestamp) }}</span>
|
|
|
|
</div>
|
|
|
|
<div class="message-content">{{ msg.content }}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="input-container">
|
|
|
|
<textarea
|
|
|
|
v-model="message"
|
|
|
|
@keyup.enter="sendMessage"
|
|
|
|
placeholder="输入消息..."
|
|
|
|
rows="3"
|
|
|
|
></textarea>
|
|
|
|
<button @click="sendMessage" :disabled="!message.trim()">
|
|
|
|
发送
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2025-04-11 02:31:26 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2025-04-22 06:26:41 +00:00
|
|
|
<script setup>
|
|
|
|
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
|
|
|
import { Stomp } from '@stomp/stompjs'
|
2025-04-11 02:31:26 +00:00
|
|
|
|
2025-04-22 06:26:41 +00:00
|
|
|
const props = defineProps({
|
|
|
|
msg: {
|
|
|
|
type: String,
|
|
|
|
required: true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const message = ref('')
|
|
|
|
const receivedMessages = ref([])
|
|
|
|
const email = ref('')
|
|
|
|
const targetEmail = ref('')
|
|
|
|
const isConnected = ref(false)
|
|
|
|
const isConnecting = ref(false)
|
|
|
|
const connectionError = ref('')
|
|
|
|
const messageList = ref(null)
|
|
|
|
|
|
|
|
// 创建一个响应式的 stompClient
|
|
|
|
const stompClient = ref(null)
|
|
|
|
|
|
|
|
// 添加连接时间变量
|
|
|
|
const connectTime = ref(null)
|
|
|
|
|
|
|
|
// 自动滚动到底部
|
|
|
|
const scrollToBottom = async () => {
|
|
|
|
await nextTick()
|
|
|
|
if (messageList.value) {
|
|
|
|
messageList.value.scrollTop = messageList.value.scrollHeight
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 监听消息列表变化,自动滚动
|
|
|
|
watch(receivedMessages, () => {
|
|
|
|
scrollToBottom()
|
|
|
|
})
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
const formatTime = (timestamp) => {
|
|
|
|
if (!timestamp) return ''
|
|
|
|
try {
|
|
|
|
const date = new Date(timestamp)
|
|
|
|
return date.toLocaleTimeString()
|
|
|
|
} catch (e) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 连接WebSocket
|
|
|
|
const connectWebSocket = () => {
|
|
|
|
if (!email.value || !targetEmail.value) {
|
|
|
|
connectionError.value = '请输入用户邮箱和目标用户邮箱'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
connectionError.value = ''
|
|
|
|
isConnecting.value = true
|
|
|
|
connectTime.value = new Date() // 记录连接时间
|
|
|
|
|
|
|
|
try {
|
|
|
|
stompClient.value = Stomp.client('ws://localhost:8101/chat/ws')
|
|
|
|
stompClient.value.heartbeat.outgoing = 20000
|
|
|
|
stompClient.value.heartbeat.incoming = 0
|
|
|
|
|
|
|
|
const connectHeaders = {
|
|
|
|
'email': email.value,
|
|
|
|
'type': 2 //0 游客 1 登录用户 2 客服
|
2025-04-11 02:31:26 +00:00
|
|
|
}
|
|
|
|
|
2025-04-22 06:26:41 +00:00
|
|
|
stompClient.value.connect(
|
|
|
|
connectHeaders,
|
|
|
|
function(frame) {
|
|
|
|
console.log('连接成功: ' + frame)
|
|
|
|
console.log('连接时间:', connectTime.value?.toLocaleString())
|
|
|
|
isConnected.value = true
|
|
|
|
isConnecting.value = false
|
|
|
|
|
|
|
|
// 添加系统消息
|
|
|
|
receivedMessages.value.push({
|
|
|
|
sender: 'System',
|
|
|
|
content: '已连接到聊天服务器',
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
system: true
|
|
|
|
})
|
|
|
|
|
|
|
|
// 订阅自己管道的消息
|
|
|
|
stompClient.value.subscribe(`/user/queue/${email.value}`, function(message) {
|
|
|
|
console.log('收到消息:', message.body)
|
|
|
|
try {
|
|
|
|
const parsedMessage = JSON.parse(message.body)
|
|
|
|
receivedMessages.value.push(parsedMessage)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('消息解析失败:', error)
|
|
|
|
receivedMessages.value.push({
|
|
|
|
sender: 'System',
|
|
|
|
content: `消息格式错误: ${message.body}`,
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
error: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2025-04-11 02:31:26 +00:00
|
|
|
},
|
2025-04-22 06:26:41 +00:00
|
|
|
function(error) {
|
|
|
|
console.error('连接失败:', error)
|
|
|
|
isConnected.value = false
|
|
|
|
isConnecting.value = false
|
|
|
|
connectionError.value = `连接失败: ${error.headers?.message || error.message || '未知错误'}`
|
2025-04-11 02:31:26 +00:00
|
|
|
}
|
2025-04-22 06:26:41 +00:00
|
|
|
)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('初始化WebSocket客户端失败:', error)
|
|
|
|
isConnected.value = false
|
|
|
|
isConnecting.value = false
|
|
|
|
connectionError.value = `初始化失败: ${error.message || '未知错误'}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 添加断开连接方法
|
|
|
|
const disconnectWebSocket = () => {
|
|
|
|
if (stompClient.value?.connected) {
|
|
|
|
stompClient.value.disconnect(() => {
|
|
|
|
const disconnectTime = new Date()
|
|
|
|
const duration = connectTime.value ?
|
|
|
|
Math.floor((disconnectTime.getTime() - connectTime.value.getTime()) / 1000) : 0
|
|
|
|
|
|
|
|
console.log('断开连接时间:', disconnectTime.toLocaleString())
|
|
|
|
console.log(`连接持续时间: ${Math.floor(duration / 60)}分${duration % 60}秒`)
|
|
|
|
|
|
|
|
isConnected.value = false
|
|
|
|
receivedMessages.value = []
|
|
|
|
connectTime.value = null
|
|
|
|
|
|
|
|
// 添加提示
|
|
|
|
connectionError.value = '已断开连接'
|
|
|
|
setTimeout(() => {
|
|
|
|
connectionError.value = ''
|
|
|
|
}, 3000)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 发送消息方法
|
|
|
|
const sendMessage = () => {
|
|
|
|
if (!message.value.trim()) return
|
|
|
|
|
|
|
|
if (stompClient.value?.connected) {
|
|
|
|
try {
|
|
|
|
const messageObj = {
|
|
|
|
email: targetEmail.value,
|
|
|
|
content: message.value.trim()
|
2025-04-11 02:31:26 +00:00
|
|
|
}
|
2025-04-22 06:26:41 +00:00
|
|
|
|
|
|
|
stompClient.value.send(
|
|
|
|
`/point/send/message`,
|
|
|
|
{},
|
|
|
|
JSON.stringify(messageObj)
|
|
|
|
)
|
|
|
|
|
|
|
|
// 添加自己发送的消息到显示列表
|
|
|
|
receivedMessages.value.push({
|
|
|
|
sender: email.value,
|
|
|
|
content: message.value.trim(),
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
isSelf: true
|
|
|
|
})
|
|
|
|
|
|
|
|
message.value = ''
|
|
|
|
} catch (error) {
|
|
|
|
console.error('发送消息失败:', error)
|
|
|
|
|
|
|
|
receivedMessages.value.push({
|
|
|
|
sender: 'System',
|
|
|
|
content: `发送失败: ${error.message || '未知错误'}`,
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
error: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
connectionError.value = '连接已断开,请重新连接'
|
|
|
|
isConnected.value = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 组件卸载时断开连接
|
|
|
|
onUnmounted(() => {
|
|
|
|
if (stompClient.value?.connected) {
|
|
|
|
stompClient.value.disconnect()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
.read-the-docs {
|
|
|
|
color: #888;
|
|
|
|
}
|
|
|
|
|
|
|
|
.user-input-container {
|
|
|
|
max-width: 400px;
|
|
|
|
margin: 20px auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.input-group {
|
|
|
|
display: flex;
|
|
|
|
gap: 10px;
|
|
|
|
margin-bottom: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.chat-container {
|
|
|
|
max-width: 600px;
|
|
|
|
margin: 20px auto;
|
|
|
|
padding: 20px;
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
border-radius: 8px;
|
|
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-list {
|
|
|
|
height: 300px;
|
|
|
|
overflow-y: auto;
|
|
|
|
border: 1px solid #eee;
|
|
|
|
padding: 10px;
|
|
|
|
margin-bottom: 10px;
|
|
|
|
border-radius: 4px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message {
|
|
|
|
margin: 8px 0;
|
|
|
|
padding: 10px;
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
border-radius: 8px;
|
|
|
|
max-width: 80%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
font-size: 0.8em;
|
|
|
|
margin-bottom: 4px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-sender {
|
|
|
|
font-weight: bold;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-time {
|
|
|
|
color: #999;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message-content {
|
|
|
|
word-break: break-word;
|
|
|
|
}
|
|
|
|
|
|
|
|
.message[class*="isSelf"] {
|
|
|
|
background-color: #dcf8c6;
|
|
|
|
margin-left: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error-message {
|
|
|
|
background-color: #ffebee;
|
|
|
|
color: #d32f2f;
|
|
|
|
padding: 8px;
|
|
|
|
border-radius: 4px;
|
|
|
|
margin: 10px 0;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.input-container {
|
|
|
|
display: flex;
|
|
|
|
gap: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
input, textarea {
|
|
|
|
flex: 1;
|
|
|
|
padding: 10px;
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
border-radius: 4px;
|
|
|
|
font-family: inherit;
|
|
|
|
resize: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
|
|
|
padding: 8px 16px;
|
|
|
|
background-color: #4CAF50;
|
|
|
|
color: white;
|
|
|
|
border: none;
|
|
|
|
border-radius: 4px;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
button:hover:not(:disabled) {
|
|
|
|
background-color: #45a049;
|
|
|
|
}
|
|
|
|
|
|
|
|
button:disabled {
|
|
|
|
opacity: 0.6;
|
|
|
|
cursor: not-allowed;
|
|
|
|
}
|
|
|
|
|
|
|
|
.button-group {
|
|
|
|
display: flex;
|
|
|
|
gap: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.disabled {
|
|
|
|
opacity: 0.6;
|
|
|
|
cursor: not-allowed;
|
|
|
|
}
|
|
|
|
|
|
|
|
.disconnect-btn {
|
|
|
|
background-color: #dc3545;
|
|
|
|
}
|
|
|
|
|
|
|
|
.disconnect-btn:hover {
|
|
|
|
background-color: #c82333;
|
|
|
|
}
|
2025-04-11 02:31:26 +00:00
|
|
|
|
2025-04-22 06:26:41 +00:00
|
|
|
input:disabled, textarea:disabled {
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
cursor: not-allowed;
|
|
|
|
}
|
|
|
|
</style>
|