优化调整

This commit is contained in:
2025-07-26 00:37:18 +08:00
parent 08bbd4df0f
commit 0dfabc35d7
90 changed files with 3594 additions and 2294 deletions
-210
View File
@@ -1,210 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket调试页面</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.section {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.section h3 {
margin-top: 0;
color: #333;
}
.info {
background-color: #e7f3ff;
border-color: #b3d9ff;
}
.success {
background-color: #e7f5e7;
border-color: #b3d9b3;
}
.error {
background-color: #ffe7e7;
border-color: #ffb3b3;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #0056b3;
}
.log {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
padding: 10px;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>WebSocket调试页面</h1>
<div class="section info">
<h3>当前状态</h3>
<p><strong>Token:</strong> <span id="tokenStatus">检查中...</span></p>
<p><strong>用户信息:</strong> <span id="userInfo">检查中...</span></p>
<p><strong>WebSocket状态:</strong> <span id="wsStatus">未连接</span></p>
</div>
<div class="section">
<h3>操作</h3>
<button onclick="checkAuth()">检查认证状态</button>
<button onclick="connectWebSocket()">连接WebSocket</button>
<button onclick="sendTestMessage()">发送测试消息</button>
<button onclick="clearLog()">清空日志</button>
</div>
<div class="section">
<h3>日志</h3>
<div id="log" class="log"></div>
</div>
</div>
<script>
let ws = null;
function log(message) {
const logDiv = document.getElementById('log');
const timestamp = new Date().toLocaleTimeString();
logDiv.innerHTML += `[${timestamp}] ${message}\n`;
logDiv.scrollTop = logDiv.scrollHeight;
}
function checkAuth() {
log('检查认证状态...');
// 检查localStorage中的token
const token = localStorage.getItem('token');
const userInfo = localStorage.getItem('userInfo');
document.getElementById('tokenStatus').textContent = token ? `存在 (${token.substring(0, 20)}...)` : '不存在';
document.getElementById('userInfo').textContent = userInfo ? JSON.parse(userInfo).username || '未知用户' : '未登录';
log(`Token: ${token ? '存在' : '不存在'}`);
log(`用户信息: ${userInfo ? JSON.parse(userInfo).username || '未知' : '未登录'}`);
if (!token) {
log('警告: 没有找到token,需要先登录');
}
}
function connectWebSocket() {
if (ws && ws.readyState === WebSocket.OPEN) {
log('WebSocket已连接');
return;
}
log('开始连接WebSocket...');
const token = localStorage.getItem('token');
const userInfo = localStorage.getItem('userInfo');
const userId = userInfo ? JSON.parse(userInfo).id : `guest_${Date.now()}`;
log(`使用用户ID: ${userId}`);
log(`使用Token: ${token ? '是' : '否'}`);
// 使用SockJS和STOMP
const socket = new SockJS('http://localhost:19089/ws/chat');
const stompClient = Stomp.over(socket);
// 禁用调试日志
stompClient.debug = null;
const connectHeaders = {
'X-User-Id': userId
};
if (token) {
connectHeaders['Authorization'] = `Bearer ${token}`;
}
log(`连接头: ${JSON.stringify(connectHeaders)}`);
stompClient.connect(
connectHeaders,
function(frame) {
log('WebSocket连接成功!');
document.getElementById('wsStatus').textContent = '已连接';
ws = stompClient;
// 订阅消息
stompClient.subscribe('/user/queue/messages', function(message) {
const wsMessage = JSON.parse(message.body);
log(`收到消息: ${JSON.stringify(wsMessage)}`);
});
log('已订阅 /user/queue/messages');
},
function(error) {
log(`WebSocket连接失败: ${error}`);
document.getElementById('wsStatus').textContent = '连接失败';
}
);
}
function sendTestMessage() {
if (!ws || ws.readyState !== 1) {
log('WebSocket未连接,无法发送消息');
return;
}
const userInfo = localStorage.getItem('userInfo');
const userId = userInfo ? JSON.parse(userInfo).id : `guest_${Date.now()}`;
const chatRequest = {
content: '这是一条测试消息',
senderId: userId,
senderType: userId.startsWith('guest_') ? 'GUEST' : 'USER',
messageType: 'TEXT',
conversationId: 'test-conversation',
timestamp: Date.now()
};
log(`发送消息: ${JSON.stringify(chatRequest)}`);
ws.send('/app/chat.send', {}, JSON.stringify(chatRequest));
}
function clearLog() {
document.getElementById('log').innerHTML = '';
}
// 页面加载时检查状态
window.onload = function() {
checkAuth();
};
</script>
<!-- 引入SockJS和STOMP -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@6/bundles/stomp.umd.min.js"></script>
</body>
</html>
+3 -3
View File
@@ -26,15 +26,15 @@
"@types/stompjs": "^2.3.5",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"@vitejs/plugin-vue": "^4.3.0",
"@vitejs/plugin-vue": "^4.5.0",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"eslint": "^8.47.0",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^3.0.0",
"sass": "^1.66.0",
"sass": "^1.89.2",
"typescript": "^5.1.0",
"vite": "^4.4.0",
"vite": "^4.5.0",
"vue-tsc": "^3.0.4"
},
"engines": {
+4 -3
View File
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"build:silent": "vue-tsc && vite build 2>&1 | findstr /v \"Deprecation Warning\"",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
@@ -30,15 +31,15 @@
"@types/stompjs": "^2.3.5",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"@vitejs/plugin-vue": "^4.3.0",
"@vitejs/plugin-vue": "^4.5.0",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"eslint": "^8.47.0",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^3.0.0",
"sass": "^1.66.0",
"sass": "^1.89.2",
"typescript": "^5.1.0",
"vite": "^4.4.0",
"vite": "^4.5.0",
"vue-tsc": "^3.0.4"
},
"engines": {
+80 -80
View File
@@ -46,95 +46,95 @@
})
</script>
<style>
#app {
min-height: 100vh;
background-color: #f5f5f5;
<style lang="scss">
#app {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 自定义Ant Design样式 */
.ant-btn {
font-weight: 500;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
}
/* 自定义Ant Design样式 */
.ant-btn {
font-weight: 500;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
}
&.ant-btn-primary {
background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&:hover {
background: linear-gradient(135deg, #5ba0f2 0%, #6bb0ff 100%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
}
&.ant-btn-orange {
background: linear-gradient(135deg, #ff7849 0%, #ff8859 100%);
border: none;
color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&:hover {
background: linear-gradient(135deg, #ff8859 0%, #ff9869 100%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
color: white;
}
}
}
.ant-card {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
&.ant-btn-primary {
background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
border: none;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
}
.ant-input,
.ant-input-affix-wrapper {
border-radius: 12px;
border: 1px solid #e8e8e8;
transition: all 0.3s ease;
&:hover,
&:focus,
&.ant-input-affix-wrapper-focused {
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1);
}
}
.ant-message {
.ant-message-notice-content {
border-radius: 12px;
background: linear-gradient(135deg, #5ba0f2 0%, #6bb0ff 100%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
}
/* 滚动条美化 */
.ant-layout-content {
&::-webkit-scrollbar {
width: 6px;
}
&.ant-btn-orange {
background: linear-gradient(135deg, #ff7849 0%, #ff8859 100%);
border: none;
color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.2);
}
&:hover {
background: linear-gradient(135deg, #ff8859 0%, #ff9869 100%);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
color: white;
}
}
</style>
}
.ant-card {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
border: none;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
}
.ant-input,
.ant-input-affix-wrapper {
border-radius: 12px;
border: 1px solid #e8e8e8;
transition: all 0.3s ease;
&:hover,
&:focus,
&.ant-input-affix-wrapper-focused {
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1);
}
}
.ant-message {
.ant-message-notice-content {
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
}
/* 滚动条美化 */
.ant-layout-content {
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.2);
}
}
}
</style>
+1
View File
@@ -1,3 +1,4 @@
@use "@/assets/styles/variables.scss" as *;
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
/* 全局重置 */
+1 -1
View File
@@ -1 +1 @@
/* 空文件 - 解决构建问题 */
@use "@/assets/styles/variables.scss" as *;
+2 -1
View File
@@ -26,7 +26,8 @@
// 简化版Footer组件
</script>
<style scoped>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.app-footer {
margin-top: auto;
}
+1
View File
@@ -94,6 +94,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.app-header {
position: fixed;
top: 0;
+1 -9
View File
@@ -110,15 +110,7 @@ const routes: RouteRecordRaw[] = [
requiresAuth: false
}
},
{
path: '/token-test',
name: 'TokenTest',
component: () => import('@/views/TokenTest.vue'),
meta: {
title: 'Token测试',
requiresAuth: false
}
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
+1 -1
View File
@@ -50,7 +50,7 @@ api.interceptors.response.use(
window.location.href = '/login'
}
const error = new Error(data.message || '请求失败')
const error = new Error(data.message || '请求失败') as any
error.response = response
return Promise.reject(error)
}
-7
View File
@@ -166,13 +166,6 @@ export const useChatStore = defineStore('chat', () => {
)
}
// 分割AI回复为多条消息
const splitAiReply = (content: string): string[] => {
// 先按 \n\n 分割,再按 \n 分割
const segments = content.split(/\n\n|\n/).filter(segment => segment.trim().length > 0)
return segments
}
// 添加AI回复消息(直接显示完整内容)
const addAiReplyMessages = (content: string) => {
// 停止输入状态
+1
View File
@@ -19,6 +19,7 @@ export interface ChatMessage {
sessionId?: string
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'failed'
error?: string
sender?: string
}
// 聊天会话类型
+1
View File
@@ -373,6 +373,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.chat-history-page {
min-height: 100vh;
background: $light-gray;
+19 -49
View File
@@ -31,9 +31,6 @@
</div>
<div class="header-right">
<a-button type="text" @click="testAPI" class="action-btn" style="margin-right: 8px;">
测试API
</a-button>
<a-button type="text" @click="openHistoryDrawer" class="action-btn">
<HistoryOutlined />
</a-button>
@@ -444,7 +441,8 @@
showEmotionSummaryResult(result)
} catch (error) {
console.error('生成情绪记录时发生错误:', error)
const err = error as any;
console.error('生成情绪记录时发生错误:', err)
alert('生成情绪记录失败,请检查网络连接')
} finally {
emotionSummaryLoading.value = false
@@ -497,39 +495,7 @@
loadHistoryMessages(1)
}
// 测试API调用
const testAPI = async () => {
console.log('=== 开始测试API ===')
try {
// 测试原始API调用
console.log('1. 测试原始axios调用...')
const token = localStorage.getItem('token')
console.log('Token:', token ? `${token.substring(0, 20)}...` : 'null')
const response = await fetch('/api/message/user/page?current=1&size=5', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
console.log('原始响应状态:', response.status)
const rawData = await response.json()
console.log('原始响应数据:', rawData)
// 测试封装的API调用
console.log('2. 测试封装的API调用...')
const apiData = await messageApi.getUserMessages(1, 5)
console.log('封装API返回数据:', apiData)
} catch (error) {
console.error('API测试失败:', error)
}
console.log('=== API测试完成 ===')
}
// 加载历史记录
const loadHistoryMessages = async (page = 1) => {
@@ -577,22 +543,23 @@
})
} catch (error) {
console.error('加载历史记录时发生错误:', error)
const err = error as any;
console.error('加载历史记录时发生错误:', err)
console.error('错误详情:', {
message: error.message,
response: error.response,
status: error.response?.status,
data: error.response?.data
message: err.message,
response: err.response,
status: err.response?.status,
data: err.response?.data
})
// 显示用户友好的错误信息
if (error.response?.status === 401) {
if (err.response?.status === 401) {
console.log('认证失败,可能需要重新登录')
// 可以在这里添加跳转到登录页的逻辑
} else if (error.response?.status === 500) {
} else if (err.response?.status === 500) {
console.log('服务器错误,请稍后重试')
} else {
console.log('未知错误:', error.message)
console.log('未知错误:', err.message)
}
} finally {
console.log('加载历史记录完成,设置 loading 为 false')
@@ -625,10 +592,11 @@
})
} catch (error) {
console.error('搜索历史记录时发生错误:', error)
const err = error as any;
console.error('搜索历史记录时发生错误:', err)
// 显示用户友好的错误信息
if (error.response?.status === 401) {
if (err.response?.status === 401) {
console.log('认证失败,搜索功能需要登录')
}
} finally {
@@ -677,7 +645,7 @@
// 将最近的消息添加到聊天记录中
if (recentMessages && recentMessages.length > 0) {
// 转换为聊天消息格式
const chatMessages = recentMessages.map(msg => ({
const chatMessages = recentMessages.map((msg: any) => ({
id: msg.id,
content: msg.content,
sender: msg.sender === 'user' ? 'user' : 'ai',
@@ -686,7 +654,7 @@
}))
// 按时间顺序排列(最新的在最后)
chatMessages.sort((a, b) => a.timestamp - b.timestamp)
chatMessages.sort((a: any, b: any) => a.timestamp - b.timestamp)
// 添加到消息列表
chatStore.messages.push(...chatMessages)
@@ -694,7 +662,8 @@
console.log('加载最近聊天记录成功:', chatMessages.length, '条')
}
} catch (error) {
console.error('加载最近聊天记录失败:', error)
const err = error as any;
console.error('加载最近聊天记录失败:', err)
}
}
@@ -716,6 +685,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.chat-page {
display: flex;
flex-direction: column;
+1
View File
@@ -487,6 +487,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.dashboard-page {
min-height: 100vh;
background: $light-gray;
+1
View File
@@ -387,6 +387,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.diary-page {
min-height: 100vh;
background: $light-gray;
+2 -1
View File
@@ -67,7 +67,8 @@
import AppFooter from '@/components/layout/AppFooter.vue'
</script>
<style scoped>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.home-page {
min-height: 100vh;
background: #f5f5f5;
+1
View File
@@ -513,6 +513,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.life-trajectory-page {
min-height: 100vh;
background: $light-gray;
+1
View File
@@ -198,6 +198,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+1
View File
@@ -363,6 +363,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.messages-page {
min-height: 100vh;
background: $light-gray;
+1
View File
@@ -32,6 +32,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.not-found-page {
min-height: 100vh;
display: flex;
+1
View File
@@ -346,6 +346,7 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.profile-page {
min-height: 100vh;
background: #f5f5f5;
+1
View File
@@ -203,6 +203,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.register-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+1
View File
@@ -406,6 +406,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.settings-page {
min-height: 100vh;
background: $light-gray;
-185
View File
@@ -1,185 +0,0 @@
<template>
<div class="token-test">
<a-card title="Token和身份验证测试">
<div class="test-section">
<h3>当前状态</h3>
<a-descriptions :column="1" bordered>
<a-descriptions-item label="登录状态">
<a-tag :color="userStore.isLoggedIn ? 'green' : 'red'">
{{ userStore.isLoggedIn ? '已登录' : '未登录' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="Token">
<a-typography-text :code="true" :copyable="true">
{{ userStore.token || '无' }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item label="用户信息">
<pre>{{ JSON.stringify(userStore.userInfo || userStore.user, null, 2) }}</pre>
</a-descriptions-item>
<a-descriptions-item label="WebSocket状态">
<a-tag :color="chatStore.wsConnected ? 'green' : 'red'">
{{ chatStore.wsConnected ? '已连接' : '未连接' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</div>
<div class="test-section">
<h3>操作测试</h3>
<a-space direction="vertical" style="width: 100%">
<a-button type="primary" @click="testLogin" :loading="loginLoading">
测试登录
</a-button>
<a-button @click="testWebSocketConnect" :loading="wsLoading">
测试WebSocket连接
</a-button>
<a-button @click="testSendMessage" :disabled="!chatStore.wsConnected">
发送测试消息
</a-button>
<a-button @click="checkLocalStorage">
检查本地存储
</a-button>
<a-button @click="testApiCall" :loading="apiLoading">
测试API调用
</a-button>
</a-space>
</div>
<div class="test-section">
<h3>测试结果</h3>
<a-textarea
v-model:value="testResults"
:rows="10"
readonly
placeholder="测试结果将显示在这里..."
/>
<a-button @click="clearResults" style="margin-top: 8px">
清空结果
</a-button>
</div>
</a-card>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useUserStore, useChatStore } from '@/stores'
import { request } from '@/services/api'
const userStore = useUserStore()
const chatStore = useChatStore()
const loginLoading = ref(false)
const wsLoading = ref(false)
const apiLoading = ref(false)
const testResults = ref('')
const addResult = (message: string) => {
const timestamp = new Date().toLocaleTimeString()
testResults.value += `[${timestamp}] ${message}\n`
}
const testLogin = async () => {
loginLoading.value = true
try {
addResult('开始测试登录...')
const result = await userStore.loginWithAuth({
account: 'test@example.com',
password: '123456',
captcha: '1234'
})
addResult(`登录成功: ${JSON.stringify(result)}`)
addResult(`Token: ${userStore.token}`)
addResult(`用户信息: ${JSON.stringify(userStore.userInfo)}`)
} catch (error: any) {
addResult(`登录失败: ${error.message}`)
} finally {
loginLoading.value = false
}
}
const testWebSocketConnect = async () => {
wsLoading.value = true
try {
addResult('开始测试WebSocket连接...')
await chatStore.connectWebSocket()
addResult(`WebSocket连接状态: ${chatStore.wsConnected}`)
addResult(`连接状态: ${chatStore.connectionStatus}`)
} catch (error: any) {
addResult(`WebSocket连接失败: ${error.message}`)
} finally {
wsLoading.value = false
}
}
const testSendMessage = async () => {
try {
addResult('发送测试消息...')
await chatStore.sendMessage('这是一条测试消息,用于验证用户身份识别')
addResult('消息发送成功')
} catch (error: any) {
addResult(`消息发送失败: ${error.message}`)
}
}
const checkLocalStorage = () => {
addResult('检查本地存储...')
addResult(`localStorage.token: ${localStorage.getItem('token')}`)
addResult(`localStorage.userInfo: ${localStorage.getItem('userInfo')}`)
}
const testApiCall = async () => {
apiLoading.value = true
try {
addResult('测试API调用...')
const response = await request.get('/health')
addResult(`API调用成功: ${JSON.stringify(response)}`)
} catch (error: any) {
addResult(`API调用失败: ${error.message}`)
} finally {
apiLoading.value = false
}
}
const clearResults = () => {
testResults.value = ''
}
</script>
<style lang="scss" scoped>
.token-test {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.test-section {
margin-bottom: 24px;
h3 {
margin-bottom: 16px;
color: #1890ff;
}
}
pre {
background: #f5f5f5;
padding: 8px;
border-radius: 4px;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
}
</style>
+1
View File
@@ -468,6 +468,7 @@
</script>
<style lang="scss" scoped>
@use "@/assets/styles/variables.scss" as *;
.topic-tracker-page {
min-height: 100vh;
background: $light-gray;
-333
View File
@@ -1,333 +0,0 @@
<template>
<div class="websocket-test">
<div class="test-container">
<h1>WebSocket连接测试</h1>
<!-- 连接状态 -->
<div class="status-section">
<h3>连接状态</h3>
<div class="status-info">
<span class="status-label">状态:</span>
<span
class="status-value"
:class="{
'connected': chatStore.wsConnected,
'connecting': chatStore.connectionStatus === 'CONNECTING',
'disconnected': !chatStore.wsConnected
}"
>
{{ getConnectionStatusText() }}
</span>
</div>
<div class="status-actions">
<a-button
type="primary"
@click="chatStore.connectWebSocket()"
:loading="chatStore.connectionStatus === 'CONNECTING'"
:disabled="chatStore.wsConnected"
>
连接
</a-button>
<a-button
@click="chatStore.disconnectWebSocket()"
:disabled="!chatStore.wsConnected"
>
断开
</a-button>
</div>
</div>
<!-- 消息测试 -->
<div class="message-section">
<h3>消息测试</h3>
<div class="message-input">
<a-input
v-model:value="testMessage"
placeholder="输入测试消息..."
@press-enter="sendTestMessage"
:disabled="!chatStore.wsConnected"
/>
<a-button
type="primary"
@click="sendTestMessage"
:disabled="!chatStore.wsConnected || !testMessage.trim()"
>
发送
</a-button>
</div>
</div>
<!-- 消息历史 -->
<div class="messages-section">
<h3>消息历史</h3>
<div class="messages-list">
<div
v-for="message in messages"
:key="message.id"
class="message-item"
:class="{ 'user': message.type === 'user', 'ai': message.type === 'ai' }"
>
<div class="message-header">
<span class="message-sender">{{ message.type === 'user' ? '用户' : 'AI' }}</span>
<span class="message-time">{{ formatTime(message.timestamp) }}</span>
</div>
<div class="message-content">{{ message.content }}</div>
</div>
</div>
<div class="messages-actions">
<a-button @click="clearMessages">清空消息</a-button>
</div>
</div>
<!-- 配置信息 -->
<div class="config-section">
<h3>配置信息</h3>
<div class="config-info">
<div class="config-item">
<span class="config-label">WebSocket URL:</span>
<span class="config-value">{{ wsUrl }}</span>
</div>
<div class="config-item">
<span class="config-label">用户ID:</span>
<span class="config-value">{{ userId }}</span>
</div>
<div class="config-item">
<span class="config-label">会话ID:</span>
<span class="config-value">{{ chatStore.currentSession?.id || '未设置' }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useChatStore } from '@/stores/chat'
import { useUserStore } from '@/stores/user'
import dayjs from 'dayjs'
const chatStore = useChatStore()
const userStore = useUserStore()
const testMessage = ref('')
const messages = ref<Array<{
id: string
type: 'user' | 'ai'
content: string
timestamp: number
}>>([])
const wsUrl = computed(() => import.meta.env.VITE_WS_URL)
const userId = computed(() => userStore.user?.id || `guest_${Date.now()}`)
// 获取连接状态文本
const getConnectionStatusText = () => {
switch (chatStore.connectionStatus) {
case 'CONNECTED':
return '已连接'
case 'CONNECTING':
return '连接中...'
case 'DISCONNECTED':
return '已断开'
case 'ERROR':
return '连接错误'
default:
return '未知状态'
}
}
// 发送测试消息
const sendTestMessage = () => {
if (!testMessage.value.trim() || !chatStore.wsConnected) return
const message = {
id: Date.now().toString(),
type: 'user' as const,
content: testMessage.value.trim(),
timestamp: Date.now()
}
messages.value.push(message)
chatStore.sendMessage(testMessage.value.trim())
testMessage.value = ''
}
// 清空消息
const clearMessages = () => {
messages.value = []
chatStore.clearMessages()
}
// 格式化时间
const formatTime = (timestamp: number) => {
return dayjs(timestamp).format('HH:mm:ss')
}
// 监听AI回复
const handleAiMessage = (content: string) => {
const message = {
id: Date.now().toString(),
type: 'ai' as const,
content,
timestamp: Date.now()
}
messages.value.push(message)
}
onMounted(() => {
// 监听聊天store中的消息变化
chatStore.$subscribe((mutation, _state) => {
if (mutation.events && Array.isArray(mutation.events)) {
mutation.events.forEach((event: any) => {
if (event.key === 'messages' && event.type === 'add') {
const newMessage = event.newValue
if (newMessage && newMessage.type === 'ai') {
handleAiMessage(newMessage.content)
}
}
})
}
})
})
onUnmounted(() => {
chatStore.disconnectWebSocket()
})
</script>
<style lang="scss" scoped>
.websocket-test {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.test-container {
background: white;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.status-section,
.message-section,
.messages-section,
.config-section {
margin-bottom: 32px;
h3 {
margin-bottom: 16px;
color: #1890ff;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 8px;
}
}
.status-info {
margin-bottom: 16px;
.status-label {
font-weight: 500;
margin-right: 8px;
}
.status-value {
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
&.connected {
background: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
&.connecting {
background: #fffbe6;
color: #faad14;
border: 1px solid #ffe58f;
}
&.disconnected {
background: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
}
}
.status-actions {
display: flex;
gap: 12px;
}
.message-input {
display: flex;
gap: 12px;
.ant-input {
flex: 1;
}
}
.messages-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 12px;
margin-bottom: 16px;
.message-item {
margin-bottom: 12px;
padding: 8px;
border-radius: 4px;
&.user {
background: #e6f7ff;
border-left: 3px solid #1890ff;
}
&.ai {
background: #f6ffed;
border-left: 3px solid #52c41a;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
font-size: 12px;
color: #666;
.message-sender {
font-weight: 500;
}
}
.message-content {
color: #333;
line-height: 1.5;
}
}
}
.config-info {
.config-item {
display: flex;
margin-bottom: 8px;
.config-label {
font-weight: 500;
min-width: 120px;
color: #666;
}
.config-value {
color: #333;
word-break: break-all;
}
}
}
</style>
+6 -1
View File
@@ -18,10 +18,15 @@ export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/assets/styles/variables.scss";`
additionalData: `@use "@/assets/styles/variables.scss" as *;`,
sassOptions: {
quietDeps: true,
silenceDeprecations: ['legacy-js-api']
}
}
}
},
logLevel: 'error',
server: {
port: 3000,