|
@@ -60,8 +60,22 @@ const scrollToBottom = () => {
|
|
|
// 初始化页面后进行加载历史消息
|
|
// 初始化页面后进行加载历史消息
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
chatStore.loadChatHistory().then(() => scrollToBottom());
|
|
chatStore.loadChatHistory().then(() => scrollToBottom());
|
|
|
|
|
+ chatStore.loadSessions();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+// 会话日期格式化
|
|
|
|
|
+const formatSessionDate = (dateStr: string) => {
|
|
|
|
|
+ const date = new Date(dateStr);
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
|
+ const nowDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
|
|
|
+ const days = Math.round((nowDay.getTime() - dateDay.getTime()) / 86400000);
|
|
|
|
|
+ if (days === 0) return '今天';
|
|
|
|
|
+ if (days === 1) return '昨天';
|
|
|
|
|
+ if (days < 7) return `${days}天前`;
|
|
|
|
|
+ return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
// 使用响应式监听器 监听 messages 变化并滚动到底
|
|
// 使用响应式监听器 监听 messages 变化并滚动到底
|
|
|
watch(
|
|
watch(
|
|
|
() => chatStore.messages,
|
|
() => chatStore.messages,
|
|
@@ -73,12 +87,70 @@ watch(
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="min-h-screen bg-gray-900 text-white">
|
|
|
|
|
|
|
+ <div class="min-h-screen bg-gray-900 text-white flex flex-col">
|
|
|
<Header />
|
|
<Header />
|
|
|
|
|
|
|
|
- <!-- 具有居中聊天区域的主容器 -->
|
|
|
|
|
- <div class="flex justify-center px-4 py-6">
|
|
|
|
|
- <div class="w-full max-w-4xl flex flex-col h-[calc(100vh-120px)]">
|
|
|
|
|
|
|
+ <!-- 主内容区(相对定位,作为抽屉定位基准) -->
|
|
|
|
|
+ <div class="flex-1 relative overflow-hidden">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 遮罩层 -->
|
|
|
|
|
+ <transition name="fade">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="chatStore.sidebarOpen"
|
|
|
|
|
+ class="absolute inset-0 bg-black/50 z-10"
|
|
|
|
|
+ @click="chatStore.sidebarOpen = false"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ </transition>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 左侧抽屉 -->
|
|
|
|
|
+ <transition name="slide-left">
|
|
|
|
|
+ <aside
|
|
|
|
|
+ v-if="chatStore.sidebarOpen"
|
|
|
|
|
+ class="absolute left-0 top-0 h-full w-64 bg-gray-800 border-r border-gray-700 z-20 flex flex-col"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 抽屉顶部:新对话 + 关闭 -->
|
|
|
|
|
+ <div class="flex items-center justify-between px-4 py-3 border-b border-gray-700">
|
|
|
|
|
+ <button
|
|
|
|
|
+ @click="chatStore.newConversation()"
|
|
|
|
|
+ class="flex items-center gap-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 px-3 py-1.5 rounded-lg transition-colors"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ 新对话
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ @click="chatStore.sidebarOpen = false"
|
|
|
|
|
+ class="text-gray-400 hover:text-white p-1 rounded transition-colors"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 会话列表 -->
|
|
|
|
|
+ <div class="flex-1 overflow-y-auto py-2">
|
|
|
|
|
+ <p v-if="chatStore.sessions.length === 0" class="text-gray-500 text-sm text-center mt-8">暂无历史对话</p>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="session in chatStore.sessions"
|
|
|
|
|
+ :key="session.sessionId"
|
|
|
|
|
+ @click="chatStore.switchSession(session.sessionId)"
|
|
|
|
|
+ class="w-full text-left px-4 py-3 hover:bg-gray-700 transition-colors border-l-2"
|
|
|
|
|
+ :class="session.sessionId === chatStore.sessionId
|
|
|
|
|
+ ? 'border-blue-500 bg-gray-700/60 text-white'
|
|
|
|
|
+ : 'border-transparent text-gray-300'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <p class="text-sm truncate">{{ session.preview || '新对话' }}</p>
|
|
|
|
|
+ <p class="text-xs text-gray-500 mt-0.5">{{ formatSessionDate(session.createdAt) }}</p>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </aside>
|
|
|
|
|
+ </transition>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 聊天主区域 -->
|
|
|
|
|
+ <div class="flex justify-center px-4 py-6 h-full">
|
|
|
|
|
+ <div class="w-full max-w-4xl flex flex-col h-full">
|
|
|
|
|
|
|
|
<!-- 聊天消息容器 -->
|
|
<!-- 聊天消息容器 -->
|
|
|
<div id="chat-container" class="flex-1 overflow-y-auto space-y-4 mb-4 px-4 py-4 rounded-lg shadow-lg">
|
|
<div id="chat-container" class="flex-1 overflow-y-auto space-y-4 mb-4 px-4 py-4 rounded-lg shadow-lg">
|
|
@@ -210,6 +282,7 @@ watch(
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
@@ -390,6 +463,38 @@ watch(
|
|
|
color: #ef4444;
|
|
color: #ef4444;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* 抽屉滑入动画 */
|
|
|
|
|
+.slide-left-enter-active,
|
|
|
|
|
+.slide-left-leave-active {
|
|
|
|
|
+ transition: transform 0.25s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.slide-left-enter-from,
|
|
|
|
|
+.slide-left-leave-to {
|
|
|
|
|
+ transform: translateX(-100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 遮罩淡入动画 */
|
|
|
|
|
+.fade-enter-active,
|
|
|
|
|
+.fade-leave-active {
|
|
|
|
|
+ transition: opacity 0.25s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.fade-enter-from,
|
|
|
|
|
+.fade-leave-to {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 抽屉会话列表滚动条 */
|
|
|
|
|
+aside::-webkit-scrollbar {
|
|
|
|
|
+ width: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+aside::-webkit-scrollbar-track {
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+aside::-webkit-scrollbar-thumb {
|
|
|
|
|
+ background: #4b5563;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* 打字光标效果 */
|
|
/* 打字光标效果 */
|
|
|
.typing-cursor {
|
|
.typing-cursor {
|
|
|
display: inline-block;
|
|
display: inline-block;
|