feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="bg-light-gray font-sans text-text-dark flex flex-col h-screen antialiased">
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-md z-10 flex-shrink-0">
|
||||
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<router-link to="/chat" class="text-text-medium hover:text-tech-blue transition-colors">
|
||||
<i data-lucide="chevron-left" class="w-6 h-6"></i>
|
||||
</router-link>
|
||||
<h1 class="text-lg font-bold text-text-dark">聊天记录</h1>
|
||||
</div>
|
||||
<button
|
||||
@click="toggleSearch"
|
||||
class="text-text-medium hover:text-tech-blue transition-colors"
|
||||
>
|
||||
<i data-lucide="search" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div v-if="searchOpen" class="bg-white border-b border-gray-200 px-4 py-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
@input="handleSearch"
|
||||
type="text"
|
||||
placeholder="搜索聊天记录..."
|
||||
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-tech-blue focus:border-transparent"
|
||||
>
|
||||
<i data-lucide="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-tech-blue mx-auto mb-2"></div>
|
||||
<p class="text-text-medium">加载聊天记录中...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat History List -->
|
||||
<main v-else id="history-list" class="flex-1 overflow-y-auto p-4 lg:p-6 space-y-3">
|
||||
<!-- 无数据状态 -->
|
||||
<div v-if="chatHistoryData.length === 0" class="text-center py-12">
|
||||
<i data-lucide="message-circle" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i>
|
||||
<p class="text-text-medium">暂无聊天记录</p>
|
||||
<router-link to="/chat" class="inline-block mt-4 px-6 py-2 bg-tech-blue text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
开始聊天
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- 聊天记录列表 -->
|
||||
<div
|
||||
v-for="item in chatHistoryData"
|
||||
:key="item.id"
|
||||
@click="goToChat(item.id)"
|
||||
class="block bg-white p-4 rounded-xl shadow-sm hover:shadow-md hover:border-tech-blue/50 border border-transparent transition-all duration-300 group cursor-pointer"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm text-text-medium mb-1 group-hover:text-tech-blue transition-colors">{{ formatDate(item.createTime) }}</p>
|
||||
<p class="font-medium text-text-dark truncate">{{ item.title }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ item.messageCount }} 条消息</p>
|
||||
</div>
|
||||
<i data-lucide="chevron-right" class="w-5 h-5 text-gray-300 group-hover:text-tech-blue transition-colors flex-shrink-0 ml-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="hasMore && !loading" class="text-center py-4">
|
||||
<button
|
||||
@click="loadMore"
|
||||
class="px-6 py-2 text-tech-blue border border-tech-blue rounded-lg hover:bg-tech-blue hover:text-white transition-colors"
|
||||
>
|
||||
加载更多
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { chatApi } from '@/services/chat'
|
||||
import { messageApi } from '@/services/message'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import type { ChatSession } from '@/types'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 响应式数据
|
||||
const searchOpen = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const loading = ref(false)
|
||||
const chatHistoryData = ref<ChatSession[]>([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const hasMore = ref(true)
|
||||
|
||||
// 方法
|
||||
const toggleSearch = () => {
|
||||
searchOpen.value = !searchOpen.value
|
||||
if (!searchOpen.value) {
|
||||
searchKeyword.value = ''
|
||||
loadChatHistory() // 重新加载完整列表
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateInput: string | Date) => {
|
||||
try {
|
||||
let date: Date
|
||||
|
||||
if (dateInput instanceof Date) {
|
||||
if (isNaN(dateInput.getTime())) {
|
||||
return '日期无效'
|
||||
}
|
||||
date = dateInput
|
||||
} else if (typeof dateInput === 'string') {
|
||||
// 精确匹配后端格式 "2025-07-26 22:09:10"
|
||||
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateInput)) {
|
||||
const dateStr = dateInput.replace(' ', 'T')
|
||||
date = new Date(dateStr)
|
||||
} else {
|
||||
date = new Date(dateInput)
|
||||
}
|
||||
} else {
|
||||
return '未知日期'
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return '日期无效'
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const diffTime = Math.abs(now.getTime() - date.getTime())
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (diffDays === 1) {
|
||||
return '今天'
|
||||
} else if (diffDays === 2) {
|
||||
return '昨天'
|
||||
} else if (diffDays <= 7) {
|
||||
return `${diffDays}天前`
|
||||
} else {
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
return '日期错误'
|
||||
}
|
||||
}
|
||||
|
||||
const goToChat = (sessionId: string) => {
|
||||
router.push(`/chat?session=${sessionId}`)
|
||||
}
|
||||
|
||||
const loadChatHistory = async (page: number = 1) => {
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('📂 加载聊天历史:', { page, pageSize: pageSize.value })
|
||||
|
||||
// 获取当前用户ID
|
||||
const currentUserId = authStore.userInfo?.id || authStore.userId
|
||||
if (!currentUserId) {
|
||||
console.warn('⚠️ 未找到用户ID,无法加载聊天历史')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户的所有会话
|
||||
const userSessions = await chatApi.getUserSessions(currentUserId)
|
||||
|
||||
if (page === 1) {
|
||||
chatHistoryData.value = userSessions
|
||||
} else {
|
||||
chatHistoryData.value.push(...userSessions)
|
||||
}
|
||||
|
||||
hasMore.value = userSessions.length === pageSize.value
|
||||
console.log('✅ 聊天历史加载完成:', chatHistoryData.value.length)
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 加载聊天历史失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
if (!loading.value && hasMore.value) {
|
||||
currentPage.value++
|
||||
loadChatHistory(currentPage.value)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
loadChatHistory()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
console.log('🔍 搜索聊天记录:', searchKeyword.value)
|
||||
|
||||
// 使用消息搜索API来查找相关会话
|
||||
const searchResults = await messageApi.searchUserMessages(searchKeyword.value, 50)
|
||||
|
||||
// 从搜索结果中提取唯一的会话ID
|
||||
const conversationIds = [...new Set(searchResults.data?.map((msg: any) => msg.conversationId) || [])]
|
||||
|
||||
// 过滤出匹配的会话
|
||||
const filteredSessions = chatHistoryData.value.filter(session =>
|
||||
conversationIds.includes(session.id) ||
|
||||
session.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
|
||||
chatHistoryData.value = filteredSessions
|
||||
console.log('✅ 搜索完成,找到', filteredSessions.length, '个会话')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 搜索失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(async () => {
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
|
||||
// 延迟初始化图标
|
||||
setTimeout(() => {
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
}, 100)
|
||||
|
||||
// 加载聊天历史
|
||||
await loadChatHistory()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 导入原始样式变量 */
|
||||
:root {
|
||||
--tech-blue: #4A90E2;
|
||||
--warm-orange: #F5A623;
|
||||
--white: #FFFFFF;
|
||||
--light-gray: #F7F8FA;
|
||||
--text-dark: #333333;
|
||||
--text-medium: #888888;
|
||||
}
|
||||
|
||||
/* 应用原始样式类 */
|
||||
.bg-tech-blue { background-color: var(--tech-blue); }
|
||||
.bg-warm-orange { background-color: var(--warm-orange); }
|
||||
.bg-light-gray { background-color: var(--light-gray); }
|
||||
.text-tech-blue { color: var(--tech-blue); }
|
||||
.text-text-dark { color: var(--text-dark); }
|
||||
.text-text-medium { color: var(--text-medium); }
|
||||
.border-tech-blue { border-color: var(--tech-blue); }
|
||||
|
||||
.chat-history-page {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
background-color: var(--light-gray);
|
||||
color: var(--text-dark);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 全局样式 */
|
||||
body {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user