feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { ThemeConfig } from '@/types'
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
// 应用状态
|
||||
const loading = ref(false)
|
||||
const mobileMenuVisible = ref(false)
|
||||
const theme = ref<ThemeConfig>({
|
||||
primaryColor: '#4A90E2',
|
||||
secondaryColor: '#F5A623',
|
||||
backgroundColor: '#F7F8FA',
|
||||
textColor: '#333333',
|
||||
borderRadius: '8px'
|
||||
})
|
||||
|
||||
// 设备信息
|
||||
const isMobile = ref(false)
|
||||
const screenWidth = ref(window.innerWidth)
|
||||
|
||||
// 方法
|
||||
const setLoading = (value: boolean) => {
|
||||
loading.value = value
|
||||
}
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
mobileMenuVisible.value = !mobileMenuVisible.value
|
||||
}
|
||||
|
||||
const closeMobileMenu = () => {
|
||||
mobileMenuVisible.value = false
|
||||
}
|
||||
|
||||
const updateScreenWidth = () => {
|
||||
screenWidth.value = window.innerWidth
|
||||
isMobile.value = window.innerWidth < 768
|
||||
}
|
||||
|
||||
const setTheme = (newTheme: Partial<ThemeConfig>) => {
|
||||
theme.value = { ...theme.value, ...newTheme }
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
updateScreenWidth()
|
||||
window.addEventListener('resize', updateScreenWidth)
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
loading,
|
||||
mobileMenuVisible,
|
||||
theme,
|
||||
isMobile,
|
||||
screenWidth,
|
||||
|
||||
// 方法
|
||||
setLoading,
|
||||
toggleMobileMenu,
|
||||
closeMobileMenu,
|
||||
updateScreenWidth,
|
||||
setTheme,
|
||||
init
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* 认证状态管理
|
||||
*/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import AuthService from '@/services/auth'
|
||||
import { handleApiError } from '@/utils/errorHandler'
|
||||
import type {
|
||||
LoginRequest,
|
||||
RegisterRequest,
|
||||
AuthResponse,
|
||||
UserInfo
|
||||
} from '@/types/auth'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// 状态
|
||||
const accessToken = ref<string>('')
|
||||
const refreshToken = ref<string>('')
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
// 移除权限状态,该功能不存在
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 计算属性
|
||||
const isLoggedIn = computed(() => !!accessToken.value && !!userInfo.value)
|
||||
const userId = computed(() => userInfo.value?.id || '')
|
||||
const username = computed(() => userInfo.value?.username || '')
|
||||
const nickname = computed(() => userInfo.value?.nickname || '')
|
||||
const avatar = computed(() => userInfo.value?.avatar || '')
|
||||
const email = computed(() => userInfo.value?.email || '')
|
||||
const phone = computed(() => userInfo.value?.phone || '')
|
||||
|
||||
// 移除权限检查方法,该功能不存在
|
||||
|
||||
/**
|
||||
* 初始化认证状态
|
||||
*/
|
||||
const initAuth = async () => {
|
||||
try {
|
||||
console.log('🔄 初始化认证状态...')
|
||||
|
||||
// 从本地存储恢复token
|
||||
const storedAccessToken = localStorage.getItem('access_token')
|
||||
const storedRefreshToken = localStorage.getItem('refresh_token')
|
||||
const storedUserInfo = localStorage.getItem('user_info')
|
||||
|
||||
console.log('🔄 本地存储状态:', {
|
||||
hasToken: !!storedAccessToken,
|
||||
hasRefreshToken: !!storedRefreshToken,
|
||||
hasUserInfo: !!storedUserInfo
|
||||
})
|
||||
|
||||
if (storedAccessToken && storedUserInfo) {
|
||||
// 恢复认证状态
|
||||
accessToken.value = storedAccessToken
|
||||
refreshToken.value = storedRefreshToken || ''
|
||||
userInfo.value = JSON.parse(storedUserInfo)
|
||||
|
||||
console.log('🔄 认证状态已恢复')
|
||||
|
||||
// 简单验证:尝试获取用户信息来验证token是否有效
|
||||
try {
|
||||
await getCurrentUserInfo()
|
||||
console.log('🔄 Token验证成功')
|
||||
} catch (error) {
|
||||
console.warn('🔄 Token可能已过期,但不强制登出:', error)
|
||||
// 不强制登出,让用户在下次API调用时处理
|
||||
}
|
||||
} else {
|
||||
console.log('🔄 无有效的本地认证信息')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🔄 初始化认证状态失败:', error)
|
||||
// 不自动登出,避免清除用户刚登录的状态
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
const login = async (loginData: LoginRequest): Promise<boolean> => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
|
||||
console.log('🔐 开始登录流程...')
|
||||
const response = await AuthService.login(loginData)
|
||||
console.log('🔐 登录响应数据:', response)
|
||||
|
||||
// 保存认证信息
|
||||
setAuthData(response)
|
||||
console.log('🔐 认证信息已保存')
|
||||
|
||||
// 验证token是否正确保存
|
||||
const savedToken = localStorage.getItem('access_token')
|
||||
console.log('🔐 保存的token:', savedToken ? '已保存' : '未保存')
|
||||
|
||||
// 获取最新的用户信息
|
||||
await getCurrentUserInfo()
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
return true
|
||||
} catch (error: any) {
|
||||
console.error('🔐 登录失败:', error)
|
||||
handleApiError(error, '用户登录')
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
const register = async (registerData: RegisterRequest): Promise<boolean> => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
|
||||
const response = await AuthService.register(registerData)
|
||||
|
||||
// 保存认证信息
|
||||
setAuthData(response)
|
||||
|
||||
// 移除权限获取,该接口不存在
|
||||
|
||||
ElMessage.success('注册成功')
|
||||
return true
|
||||
} catch (error: any) {
|
||||
handleApiError(error, '用户注册')
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*/
|
||||
const logout = async (): Promise<void> => {
|
||||
try {
|
||||
// 调用登出接口
|
||||
if (accessToken.value) {
|
||||
await AuthService.logout()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登出接口调用失败:', error)
|
||||
} finally {
|
||||
// 清除本地状态
|
||||
clearAuthData()
|
||||
ElMessage.success('已退出登录')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
*/
|
||||
const refreshAccessToken = async (): Promise<boolean> => {
|
||||
try {
|
||||
if (!refreshToken.value) {
|
||||
throw new Error('没有刷新令牌')
|
||||
}
|
||||
|
||||
const response = await AuthService.refreshToken({
|
||||
refreshToken: refreshToken.value
|
||||
})
|
||||
|
||||
// 更新认证信息
|
||||
setAuthData(response)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('刷新Token失败:', error)
|
||||
// 刷新失败,清除认证状态
|
||||
await logout()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
const getCurrentUserInfo = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await AuthService.getCurrentUserInfo()
|
||||
// 后端直接返回用户信息,不是嵌套在userInfo字段中
|
||||
userInfo.value = response
|
||||
|
||||
// 更新本地存储
|
||||
localStorage.setItem('user_info', JSON.stringify(response))
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默恢复本地认证状态(不进行API调用)
|
||||
*/
|
||||
const restoreLocalAuth = () => {
|
||||
try {
|
||||
const storedAccessToken = localStorage.getItem('access_token')
|
||||
const storedRefreshToken = localStorage.getItem('refresh_token')
|
||||
const storedUserInfo = localStorage.getItem('user_info')
|
||||
|
||||
if (storedAccessToken && storedUserInfo) {
|
||||
accessToken.value = storedAccessToken
|
||||
refreshToken.value = storedRefreshToken || ''
|
||||
userInfo.value = JSON.parse(storedUserInfo)
|
||||
console.log('🔄 本地认证状态已恢复')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('🔄 恢复本地认证状态失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置认证数据
|
||||
*/
|
||||
const setAuthData = (authData: AuthResponse): void => {
|
||||
accessToken.value = authData.accessToken
|
||||
refreshToken.value = authData.refreshToken
|
||||
userInfo.value = authData.userInfo
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('access_token', authData.accessToken)
|
||||
localStorage.setItem('refresh_token', authData.refreshToken)
|
||||
localStorage.setItem('user_info', JSON.stringify(authData.userInfo))
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除认证数据
|
||||
*/
|
||||
const clearAuthData = (): void => {
|
||||
accessToken.value = ''
|
||||
refreshToken.value = ''
|
||||
userInfo.value = null
|
||||
|
||||
// 清除本地存储
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
localStorage.removeItem('user_info')
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
const updateUserInfo = (newUserInfo: Partial<UserInfo>): void => {
|
||||
if (userInfo.value) {
|
||||
userInfo.value = { ...userInfo.value, ...newUserInfo }
|
||||
localStorage.setItem('user_info', JSON.stringify(userInfo.value))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
accessToken,
|
||||
refreshToken,
|
||||
userInfo,
|
||||
isLoading,
|
||||
|
||||
// 计算属性
|
||||
isLoggedIn,
|
||||
userId,
|
||||
username,
|
||||
nickname,
|
||||
avatar,
|
||||
email,
|
||||
phone,
|
||||
|
||||
// 方法
|
||||
initAuth,
|
||||
restoreLocalAuth,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
refreshAccessToken,
|
||||
getCurrentUserInfo,
|
||||
setAuthData,
|
||||
clearAuthData,
|
||||
updateUserInfo
|
||||
}
|
||||
})
|
||||
+316
-37
@@ -1,12 +1,34 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { ChatMessage, ChatSession } from '@/types'
|
||||
import webSocketService, { type WebSocketMessage, type ConnectionStatus } from '@/services/websocket'
|
||||
import { useUserStore } from './user'
|
||||
import { stompWebSocketService, type WebSocketMessage, type ConnectionStatus } from '@/services/stomp-websocket'
|
||||
import { useAuthStore } from './auth'
|
||||
import { chatApi } from '@/services/chat'
|
||||
import MessageService, { messageApi } from '@/services/message'
|
||||
|
||||
// 聊天消息类型
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
content: string
|
||||
type: 'user' | 'ai'
|
||||
timestamp: string
|
||||
conversationId?: string
|
||||
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'failed'
|
||||
error?: string
|
||||
}
|
||||
|
||||
// 聊天会话类型
|
||||
export interface ChatSession {
|
||||
id: string
|
||||
title: string
|
||||
userId?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
messageCount: number
|
||||
}
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 聊天状态
|
||||
const currentSession = ref<ChatSession | null>(null)
|
||||
@@ -17,7 +39,16 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const connectionStatus = ref<ConnectionStatus>('DISCONNECTED')
|
||||
const wsConnected = ref(false)
|
||||
|
||||
// 方法
|
||||
// 计算属性
|
||||
const currentMessages = computed(() => {
|
||||
if (!currentSession.value) return []
|
||||
return messages.value.filter(msg =>
|
||||
msg.conversationId === currentSession.value?.id ||
|
||||
msg.sessionId === currentSession.value?.id
|
||||
)
|
||||
})
|
||||
|
||||
// 添加消息
|
||||
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'>) => {
|
||||
const newMessage: ChatMessage = {
|
||||
...message,
|
||||
@@ -60,8 +91,8 @@ export const useChatStore = defineStore('chat', () => {
|
||||
})
|
||||
|
||||
try {
|
||||
// 仅通过WebSocket推送,后端会统一处理消息保存
|
||||
webSocketService.sendChatMessage(content, currentSession.value?.id)
|
||||
// 仅通过STOMP WebSocket推送,后端会统一处理消息保存
|
||||
stompWebSocketService.sendChatMessage(content, currentSession.value?.id)
|
||||
|
||||
// 更新消息状态为已发送
|
||||
updateMessageStatus(userMessage.id, 'sent')
|
||||
@@ -87,8 +118,13 @@ export const useChatStore = defineStore('chat', () => {
|
||||
// 创建会话:同步后端
|
||||
const createSession = async (title?: string) => {
|
||||
let newSession: ChatSession
|
||||
if (userStore.user?.id) {
|
||||
newSession = await chatApi.createSession(userStore.user.id, title || `对话${sessions.value.length + 1}`)
|
||||
const currentUserId = authStore.userInfo?.id || authStore.userId
|
||||
|
||||
console.log('📝 创建会话,当前用户ID:', currentUserId)
|
||||
|
||||
if (currentUserId) {
|
||||
newSession = await chatApi.createSession(currentUserId, title || `对话${sessions.value.length + 1}`)
|
||||
console.log('✅ 已为登录用户创建会话:', newSession)
|
||||
} else {
|
||||
newSession = {
|
||||
id: Date.now().toString(),
|
||||
@@ -97,6 +133,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
updateTime: new Date().toISOString(),
|
||||
messageCount: 0
|
||||
}
|
||||
console.log('⚠️ 为访客创建本地会话:', newSession)
|
||||
}
|
||||
sessions.value.unshift(newSession)
|
||||
currentSession.value = newSession
|
||||
@@ -104,7 +141,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
// 如果WebSocket已连接,设置新的会话ID
|
||||
if (wsConnected.value) {
|
||||
webSocketService.setConversationId(newSession.id)
|
||||
stompWebSocketService.setConversationId(newSession.id)
|
||||
}
|
||||
|
||||
return newSession
|
||||
@@ -119,18 +156,38 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
// 如果WebSocket已连接,更新会话ID
|
||||
if (wsConnected.value) {
|
||||
webSocketService.setConversationId(sessionId)
|
||||
stompWebSocketService.setConversationId(sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载会话消息:从后端获取
|
||||
// 加载会话消息:使用现有的消息API
|
||||
const loadSessionMessages = async (sessionId: string) => {
|
||||
console.log('📨 开始加载会话消息:', sessionId)
|
||||
console.log('💡 注意:后端没有按会话ID获取消息的接口,使用最近消息代替')
|
||||
|
||||
try {
|
||||
const msgs = await chatApi.getAllSessionMessages(sessionId)
|
||||
messages.value = msgs
|
||||
// 由于后端没有按会话ID获取消息的接口,我们使用最近消息
|
||||
// 这是一个临时方案,理想情况下应该在后端添加相应接口
|
||||
console.log('📨 使用最近消息API代替会话消息...')
|
||||
const response = await messageApi.getRecentMessages(50)
|
||||
console.log('📨 最近消息API响应:', response)
|
||||
|
||||
// 处理API响应数据结构
|
||||
const messageList = response.data || response || []
|
||||
console.log('📨 提取的消息列表:', messageList)
|
||||
|
||||
const chatMessages = MessageService.convertToChatMessages(messageList)
|
||||
console.log('📨 转换后的聊天消息:', chatMessages)
|
||||
|
||||
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
|
||||
// const sessionMessages = chatMessages.filter(msg => msg.sessionId === sessionId)
|
||||
|
||||
messages.value = chatMessages
|
||||
console.log('📨 会话消息加载完成,消息数量:', messages.value.length)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load session messages:', error)
|
||||
console.error('❌ 加载会话消息失败:', error)
|
||||
messages.value = []
|
||||
}
|
||||
}
|
||||
@@ -156,14 +213,171 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空消息
|
||||
const clearMessages = () => {
|
||||
messages.value = []
|
||||
}
|
||||
|
||||
const searchMessages = (keyword: string) => {
|
||||
return messages.value.filter(message =>
|
||||
message.content.toLowerCase().includes(keyword.toLowerCase())
|
||||
)
|
||||
// 搜索消息:支持本地搜索和远程搜索
|
||||
const searchMessages = async (keyword: string) => {
|
||||
console.log('🔍 开始搜索消息:', { keyword })
|
||||
|
||||
if (!keyword.trim()) {
|
||||
console.log('🔍 搜索关键词为空,返回空结果')
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
// 先尝试远程搜索
|
||||
console.log('🔍 尝试远程搜索...')
|
||||
const response = await messageApi.searchUserMessages(keyword, 50)
|
||||
console.log('🔍 远程搜索API响应:', response)
|
||||
|
||||
// 处理API响应数据结构
|
||||
const searchResults = response.data || response || []
|
||||
console.log('🔍 提取的搜索结果:', searchResults)
|
||||
|
||||
const chatMessages = MessageService.convertToChatMessages(searchResults)
|
||||
console.log('🔍 转换后的搜索结果:', chatMessages)
|
||||
|
||||
return chatMessages
|
||||
} catch (error) {
|
||||
console.error('❌ 远程搜索失败,使用本地搜索:', error)
|
||||
// 如果远程搜索失败,使用本地搜索
|
||||
const localResults = messages.value.filter(message =>
|
||||
message.content.toLowerCase().includes(keyword.toLowerCase())
|
||||
)
|
||||
console.log('🔍 本地搜索结果:', localResults)
|
||||
return localResults
|
||||
}
|
||||
}
|
||||
|
||||
// 加载用户历史消息
|
||||
const loadUserMessages = async (page: number = 1, size: number = 20) => {
|
||||
console.log('📨 开始加载用户历史消息:', { page, size })
|
||||
try {
|
||||
const response = await messageApi.getUserMessages(page, size)
|
||||
console.log('📨 API响应原始数据:', response)
|
||||
|
||||
// 处理API响应数据结构
|
||||
const result = response.data || response
|
||||
const messageList = result.records || result.list || []
|
||||
|
||||
console.log('📨 提取的消息列表:', messageList)
|
||||
|
||||
const chatMessages = MessageService.convertToChatMessages(messageList)
|
||||
console.log('📨 转换后的聊天消息:', chatMessages)
|
||||
|
||||
if (page === 1) {
|
||||
// 第一页,替换现有消息
|
||||
messages.value = chatMessages
|
||||
console.log('📨 第一页数据已加载,消息总数:', messages.value.length)
|
||||
} else {
|
||||
// 后续页,追加到现有消息
|
||||
messages.value = [...messages.value, ...chatMessages]
|
||||
console.log('📨 追加数据已加载,消息总数:', messages.value.length)
|
||||
}
|
||||
|
||||
const returnData = {
|
||||
list: messageList,
|
||||
total: result.total || 0,
|
||||
page: result.current || page,
|
||||
size: result.size || size,
|
||||
pages: result.pages || 0
|
||||
}
|
||||
|
||||
console.log('📨 返回的分页数据:', returnData)
|
||||
return returnData
|
||||
} catch (error) {
|
||||
console.error('❌ 加载用户历史消息失败:', error)
|
||||
return { list: [], total: 0, page, size, pages: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// 加载最近消息
|
||||
const loadRecentMessages = async (limit: number = 10) => {
|
||||
console.log('📝 开始加载最近消息:', { limit })
|
||||
try {
|
||||
// 直接使用messageApi,避免多层封装
|
||||
const response = await messageApi.getRecentMessages(limit)
|
||||
console.log('📝 最近消息API响应:', response)
|
||||
|
||||
// 处理响应数据 - 根据您的修改,messageApi现在返回 response.data || response
|
||||
let messageList = []
|
||||
if (Array.isArray(response)) {
|
||||
messageList = response
|
||||
console.log('📝 直接使用响应数组,消息数量:', messageList.length)
|
||||
} else if (response && response.data && Array.isArray(response.data)) {
|
||||
messageList = response.data
|
||||
console.log('📝 使用response.data,消息数量:', messageList.length)
|
||||
} else {
|
||||
console.warn('📝 无法识别的响应格式:', response)
|
||||
messageList = []
|
||||
}
|
||||
|
||||
console.log('📝 提取的最近消息列表:', messageList)
|
||||
console.log('📝 第一条消息示例:', messageList[0])
|
||||
|
||||
if (messageList.length === 0) {
|
||||
console.log('📝 没有找到最近消息')
|
||||
messages.value = []
|
||||
return []
|
||||
}
|
||||
|
||||
const chatMessages = MessageService.convertToChatMessages(messageList)
|
||||
console.log('📝 转换后的最近消息:', chatMessages)
|
||||
|
||||
// 详细检查每条消息的转换结果
|
||||
chatMessages.forEach((msg, index) => {
|
||||
console.log(`📝 消息${index + 1}:`, {
|
||||
id: msg.id,
|
||||
content: msg.content.substring(0, 20) + '...',
|
||||
sender: msg.sender,
|
||||
type: msg.type,
|
||||
role: msg.role,
|
||||
timestamp: msg.timestamp
|
||||
})
|
||||
})
|
||||
|
||||
// 按时间排序(最新的在后面)
|
||||
chatMessages.sort((a, b) => {
|
||||
// 处理时间格式 "2025-07-26 22:09:10" -> ISO格式
|
||||
const parseTime = (timestamp: string | Date) => {
|
||||
if (timestamp instanceof Date) {
|
||||
return timestamp.getTime()
|
||||
}
|
||||
if (typeof timestamp === 'string') {
|
||||
// 如果是 "2025-07-26 22:09:10" 格式,转换为ISO格式
|
||||
if (timestamp.includes(' ') && !timestamp.includes('T')) {
|
||||
const isoString = timestamp.replace(' ', 'T')
|
||||
return new Date(isoString).getTime()
|
||||
}
|
||||
return new Date(timestamp).getTime()
|
||||
}
|
||||
return new Date().getTime()
|
||||
}
|
||||
|
||||
const timeA = parseTime(a.timestamp)
|
||||
const timeB = parseTime(b.timestamp)
|
||||
|
||||
console.log('📝 排序比较:', {
|
||||
a: { id: a.id.substring(0, 8), timestamp: a.timestamp, parsed: new Date(timeA).toLocaleString() },
|
||||
b: { id: b.id.substring(0, 8), timestamp: b.timestamp, parsed: new Date(timeB).toLocaleString() },
|
||||
result: timeA - timeB
|
||||
})
|
||||
|
||||
return timeA - timeB
|
||||
})
|
||||
|
||||
messages.value = chatMessages
|
||||
console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length)
|
||||
|
||||
return chatMessages
|
||||
} catch (error) {
|
||||
console.error('❌ 加载最近消息失败:', error)
|
||||
messages.value = []
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 添加AI回复消息(直接显示完整内容)
|
||||
@@ -184,7 +398,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
// WebSocket消息处理
|
||||
const handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
|
||||
let handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
|
||||
console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType)
|
||||
|
||||
switch (wsMessage.type) {
|
||||
@@ -232,10 +446,16 @@ export const useChatStore = defineStore('chat', () => {
|
||||
// WebSocket连接管理
|
||||
const connectWebSocket = async () => {
|
||||
try {
|
||||
// 优先使用userInfo中的用户ID,如果没有则使用user中的ID
|
||||
const userId = userStore.userInfo?.id || userStore.user?.id || undefined
|
||||
// 获取当前登录用户的ID
|
||||
const userId = authStore.userInfo?.id || authStore.userId || undefined
|
||||
console.log('🔌 准备连接WebSocket,当前用户:', {
|
||||
userId,
|
||||
userInfo: authStore.userInfo,
|
||||
isLoggedIn: authStore.isLoggedIn,
|
||||
accessToken: authStore.accessToken ? '已有token' : '无token'
|
||||
})
|
||||
|
||||
await webSocketService.connect(userId, {
|
||||
await stompWebSocketService.connect(userId, {
|
||||
onMessage: handleWebSocketMessage,
|
||||
onConnect: () => {
|
||||
console.log('WebSocket连接成功')
|
||||
@@ -244,7 +464,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
// 设置会话ID
|
||||
if (currentSession.value?.id) {
|
||||
webSocketService.setConversationId(currentSession.value.id)
|
||||
stompWebSocketService.setConversationId(currentSession.value.id)
|
||||
}
|
||||
},
|
||||
onDisconnect: () => {
|
||||
@@ -281,27 +501,69 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
const disconnectWebSocket = () => {
|
||||
webSocketService.disconnect()
|
||||
stompWebSocketService.disconnect()
|
||||
wsConnected.value = false
|
||||
isConnected.value = false
|
||||
isTyping.value = false
|
||||
}
|
||||
|
||||
// 初始化
|
||||
// 初始化聊天 - 参考web项目的实现
|
||||
const initChat = async () => {
|
||||
// 如果没有会话,创建一个默认会话
|
||||
if (sessions.value.length === 0) {
|
||||
await createSession('与开开的对话')
|
||||
}
|
||||
console.log('🚀 初始化聊天功能...')
|
||||
|
||||
// 连接WebSocket
|
||||
await connectWebSocket()
|
||||
try {
|
||||
// 1. 首先尝试加载最近消息(优先显示历史数据)
|
||||
console.log('📨 优先加载最近消息...')
|
||||
await loadRecentMessages(20)
|
||||
|
||||
// 2. 尝试加载用户的历史会话
|
||||
const currentUserId = authStore.userInfo?.id || authStore.userId
|
||||
if (currentUserId) {
|
||||
try {
|
||||
console.log('📂 尝试加载用户会话,用户ID:', currentUserId)
|
||||
const userSessions = await chatApi.getUserSessions(currentUserId)
|
||||
if (userSessions.length > 0) {
|
||||
sessions.value = userSessions
|
||||
currentSession.value = userSessions[0]
|
||||
console.log('✅ 加载到用户会话:', userSessions.length, '个')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 加载用户会话失败,继续使用已加载的消息:', error)
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ 未找到用户ID,无法加载用户会话')
|
||||
}
|
||||
|
||||
// 3. 如果没有会话,创建一个默认会话
|
||||
if (sessions.value.length === 0) {
|
||||
console.log('📝 创建默认会话...')
|
||||
await createSession('与开开的对话')
|
||||
}
|
||||
|
||||
// 4. 如果有特定会话但消息为空,尝试加载会话消息
|
||||
if (currentSession.value?.id && messages.value.length === 0) {
|
||||
console.log('📨 尝试加载特定会话消息...')
|
||||
try {
|
||||
await loadSessionMessages(currentSession.value.id)
|
||||
} catch (error) {
|
||||
console.warn('⚠️ 加载会话消息失败,保持当前消息:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 连接WebSocket
|
||||
console.log('🔌 连接WebSocket...')
|
||||
await connectWebSocket()
|
||||
|
||||
console.log('✅ 聊天功能初始化完成,当前消息数量:', messages.value.length)
|
||||
} catch (error) {
|
||||
console.error('❌ 聊天功能初始化失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听会话变化,更新WebSocket会话ID
|
||||
watch(currentSession, (newSession) => {
|
||||
if (newSession?.id && wsConnected.value) {
|
||||
webSocketService.setConversationId(newSession.id)
|
||||
stompWebSocketService.setConversationId(newSession.id)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -314,19 +576,36 @@ export const useChatStore = defineStore('chat', () => {
|
||||
isConnected,
|
||||
connectionStatus,
|
||||
wsConnected,
|
||||
currentMessages,
|
||||
|
||||
// 方法
|
||||
// 基础方法
|
||||
addMessage,
|
||||
sendMessage,
|
||||
createSession,
|
||||
switchSession,
|
||||
loadSessionMessages,
|
||||
deleteSession,
|
||||
clearMessages,
|
||||
searchMessages,
|
||||
initChat,
|
||||
|
||||
// 消息加载方法
|
||||
loadSessionMessages,
|
||||
loadUserMessages,
|
||||
loadRecentMessages,
|
||||
searchMessages,
|
||||
|
||||
// WebSocket方法
|
||||
connectWebSocket,
|
||||
disconnectWebSocket,
|
||||
handleWebSocketMessage
|
||||
handleWebSocketMessage,
|
||||
|
||||
// 消息监听方法
|
||||
onMessage: (callback: (message: any) => void) => {
|
||||
// 简单的消息监听实现
|
||||
const originalHandler = handleWebSocketMessage
|
||||
handleWebSocketMessage = (message: any) => {
|
||||
originalHandler(message)
|
||||
callback(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { DiaryEntry } from '@/types'
|
||||
|
||||
export const useDiaryStore = defineStore('diary', () => {
|
||||
// 日记状态
|
||||
const entries = ref<DiaryEntry[]>([])
|
||||
const currentEntry = ref<DiaryEntry | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 方法
|
||||
const addEntry = async (content: string, mood?: string, tags?: string[]) => {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const newEntry: DiaryEntry = {
|
||||
id: Date.now().toString(),
|
||||
content,
|
||||
mood,
|
||||
tags: tags || [],
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString()
|
||||
}
|
||||
|
||||
// TODO: 调用API保存日记
|
||||
// const response = await diaryApi.createEntry(newEntry)
|
||||
|
||||
// 模拟AI回复
|
||||
setTimeout(() => {
|
||||
newEntry.aiReply = generateAIReply(content, mood)
|
||||
entries.value.unshift(newEntry)
|
||||
isLoading.value = false
|
||||
}, 1000)
|
||||
|
||||
return newEntry
|
||||
} catch (error) {
|
||||
console.error('Failed to add diary entry:', error)
|
||||
isLoading.value = false
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const updateEntry = async (id: string, updates: Partial<DiaryEntry>) => {
|
||||
const index = entries.value.findIndex(entry => entry.id === id)
|
||||
if (index > -1) {
|
||||
entries.value[index] = {
|
||||
...entries.value[index],
|
||||
...updates,
|
||||
updateTime: new Date().toISOString()
|
||||
}
|
||||
|
||||
// TODO: 调用API更新日记
|
||||
// await diaryApi.updateEntry(id, updates)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteEntry = async (id: string) => {
|
||||
const index = entries.value.findIndex(entry => entry.id === id)
|
||||
if (index > -1) {
|
||||
entries.value.splice(index, 1)
|
||||
|
||||
// TODO: 调用API删除日记
|
||||
// await diaryApi.deleteEntry(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getEntry = (id: string) => {
|
||||
return entries.value.find(entry => entry.id === id)
|
||||
}
|
||||
|
||||
const searchEntries = (keyword: string) => {
|
||||
return entries.value.filter(entry =>
|
||||
entry.content.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
entry.tags?.some(tag => tag.toLowerCase().includes(keyword.toLowerCase()))
|
||||
)
|
||||
}
|
||||
|
||||
const getEntriesByMood = (mood: string) => {
|
||||
return entries.value.filter(entry => entry.mood === mood)
|
||||
}
|
||||
|
||||
const getEntriesByDateRange = (startDate: string, endDate: string) => {
|
||||
return entries.value.filter(entry => {
|
||||
const entryDate = new Date(entry.createTime).toISOString().split('T')[0]
|
||||
return entryDate >= startDate && entryDate <= endDate
|
||||
})
|
||||
}
|
||||
|
||||
const loadEntries = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
// TODO: 从API加载日记列表
|
||||
// const response = await diaryApi.getEntries()
|
||||
// entries.value = response.data
|
||||
|
||||
// 临时模拟数据
|
||||
entries.value = [
|
||||
{
|
||||
id: '1',
|
||||
content: '今天天气很好,心情也不错。和朋友一起去公园散步,看到了很多美丽的花朵。',
|
||||
mood: 'happy',
|
||||
tags: ['散步', '朋友', '公园'],
|
||||
createTime: new Date(Date.now() - 86400000).toISOString(),
|
||||
updateTime: new Date(Date.now() - 86400000).toISOString(),
|
||||
aiReply: '听起来你度过了美好的一天!和朋友一起在大自然中放松是很棒的体验。这样的时光能让我们感受到生活的美好。'
|
||||
}
|
||||
]
|
||||
|
||||
isLoading.value = false
|
||||
} catch (error) {
|
||||
console.error('Failed to load diary entries:', error)
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生成AI回复的辅助函数
|
||||
const generateAIReply = (_content: string, mood?: string) => {
|
||||
const replies = {
|
||||
happy: [
|
||||
'很高兴看到你心情愉快!继续保持这份美好的心情吧。',
|
||||
'你的快乐感染了我!希望这份喜悦能持续下去。',
|
||||
'看到你开心,我也很开心。愿你每天都有这样的好心情!'
|
||||
],
|
||||
sad: [
|
||||
'我能感受到你的难过。记住,这只是暂时的,一切都会好起来的。',
|
||||
'每个人都会有低落的时候,这很正常。我会陪伴你度过这段时光。',
|
||||
'虽然现在感到难过,但请相信明天会更好。我一直在这里支持你。'
|
||||
],
|
||||
neutral: [
|
||||
'感谢你分享今天的经历。每一天都是独特的,值得被记录。',
|
||||
'生活就是这样平凡而珍贵。感谢你让我了解你的日常。',
|
||||
'平静的日子也有它的美好。希望你能在平凡中发现小确幸。'
|
||||
]
|
||||
}
|
||||
|
||||
const moodReplies = replies[(mood as keyof typeof replies) || 'neutral'] || replies.neutral
|
||||
return moodReplies[Math.floor(Math.random() * moodReplies.length)]
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
entries,
|
||||
currentEntry,
|
||||
isLoading,
|
||||
|
||||
// 方法
|
||||
addEntry,
|
||||
updateEntry,
|
||||
deleteEntry,
|
||||
getEntry,
|
||||
searchEntries,
|
||||
getEntriesByMood,
|
||||
getEntriesByDateRange,
|
||||
loadEntries
|
||||
}
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
export default pinia
|
||||
|
||||
// 导出所有store
|
||||
export { useUserStore } from './user'
|
||||
export { useChatStore } from './chat'
|
||||
export { useDiaryStore } from './diary'
|
||||
export { useAppStore } from './app'
|
||||
@@ -1,157 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { authService, authUtils } from '@/services/auth'
|
||||
import type { User } from '@/types'
|
||||
import type { UserInfo, LoginRequest } from '@/types/auth'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 用户状态
|
||||
const user = ref<User | null>(null)
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
const token = ref<string>('')
|
||||
const isLoading = ref(false)
|
||||
const isLoggedIn = computed(() => !!token.value && (!!user.value || !!userInfo.value))
|
||||
|
||||
// 方法
|
||||
const setUser = (userData: User) => {
|
||||
user.value = userData
|
||||
}
|
||||
|
||||
const setToken = (tokenValue: string) => {
|
||||
token.value = tokenValue
|
||||
// 存储到localStorage
|
||||
if (tokenValue) {
|
||||
localStorage.setItem('token', tokenValue)
|
||||
} else {
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
}
|
||||
|
||||
const setUserInfo = (userInfoData: UserInfo | null) => {
|
||||
userInfo.value = userInfoData
|
||||
// 存储到localStorage
|
||||
if (userInfoData) {
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfoData))
|
||||
} else {
|
||||
localStorage.removeItem('userInfo')
|
||||
}
|
||||
}
|
||||
|
||||
// 新的登录方法,支持认证服务
|
||||
const loginWithAuth = async (loginData: LoginRequest) => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await authService.login(loginData)
|
||||
setToken(data.accessToken)
|
||||
setUserInfo(data.userInfo)
|
||||
return data
|
||||
} catch (error: any) {
|
||||
throw error
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const login = async (credentials: { username: string; password: string }) => {
|
||||
try {
|
||||
// TODO: 调用登录API
|
||||
// const response = await authApi.login(credentials)
|
||||
// setToken(response.data.token)
|
||||
// setUser(response.data.user)
|
||||
|
||||
// 临时模拟登录
|
||||
setToken('mock-token')
|
||||
setUser({
|
||||
id: '1',
|
||||
username: credentials.username,
|
||||
email: 'user@example.com',
|
||||
nickname: '用户',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString()
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await authService.logout()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
} finally {
|
||||
// 清除状态和本地存储
|
||||
user.value = null
|
||||
userInfo.value = null
|
||||
setToken('')
|
||||
authUtils.clearAuth()
|
||||
}
|
||||
}
|
||||
|
||||
const updateProfile = (profileData: Partial<User>) => {
|
||||
if (user.value) {
|
||||
user.value = { ...user.value, ...profileData }
|
||||
// TODO: 调用更新API
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化用户状态
|
||||
const initUser = () => {
|
||||
const savedToken = authUtils.getToken()
|
||||
const savedUserInfo = authUtils.getUserInfo()
|
||||
|
||||
console.log('初始化用户状态:', { savedToken: !!savedToken, savedUserInfo })
|
||||
|
||||
if (savedToken) {
|
||||
setToken(savedToken)
|
||||
}
|
||||
|
||||
if (savedUserInfo) {
|
||||
setUserInfo(savedUserInfo)
|
||||
}
|
||||
|
||||
console.log('用户状态初始化完成:', {
|
||||
token: !!token.value,
|
||||
userInfo: userInfo.value,
|
||||
isLoggedIn: isLoggedIn.value
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新用户信息
|
||||
const refreshUserInfo = async () => {
|
||||
if (!token.value) return
|
||||
|
||||
try {
|
||||
const response = await authService.getUserInfo()
|
||||
if (response.success) {
|
||||
userInfo.value = response.data
|
||||
authUtils.setUserInfo(response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Refresh user info error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
user,
|
||||
userInfo,
|
||||
token,
|
||||
isLoading,
|
||||
isLoggedIn,
|
||||
|
||||
// 方法
|
||||
setUser,
|
||||
setToken,
|
||||
setUserInfo,
|
||||
login,
|
||||
loginWithAuth,
|
||||
logout,
|
||||
updateProfile,
|
||||
initUser,
|
||||
refreshUserInfo
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user