- 添加 MarkdownMessage.vue 组件 - 集成 markdown-it 库 - 集成 highlight.js 代码高亮 - 支持标题/列表/代码块/表格等 - ChatWindow 使用 Markdown 渲染 - 添加 .gitignore 忽略 node_modules
211 lines
4.4 KiB
Vue
211 lines
4.4 KiB
Vue
<script setup lang="ts">
|
||
import { ref, computed, watch, nextTick } from 'vue'
|
||
import { useChatStore } from '@/stores/chat'
|
||
import MarkdownMessage from '@/components/chat/MarkdownMessage.vue'
|
||
|
||
const chatStore = useChatStore()
|
||
const messagesContainer = ref<HTMLElement | null>(null)
|
||
|
||
const messages = computed(() => chatStore.messages)
|
||
|
||
watch(messages, () => {
|
||
nextTick(() => {
|
||
scrollToBottom()
|
||
})
|
||
}, { deep: true })
|
||
|
||
function scrollToBottom() {
|
||
if (messagesContainer.value) {
|
||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
||
}
|
||
}
|
||
|
||
function formatTime(dateStr: string): string {
|
||
return new Date(dateStr).toLocaleTimeString('zh-CN', {
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
})
|
||
}
|
||
|
||
function isOwnMessage(msg: any): boolean {
|
||
return msg.sender_type === 'user'
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="chat-window">
|
||
<div class="chat-header">
|
||
<div class="chat-info">
|
||
<div class="bot-avatar">
|
||
{{ chatStore.currentSession?.bot?.avatar || '🤖' }}
|
||
</div>
|
||
<div class="chat-title">
|
||
<h3>{{ chatStore.currentSession?.title }}</h3>
|
||
<span class="status" :class="chatStore.currentSession?.bot?.status">
|
||
{{ chatStore.currentSession?.bot?.status === 'online' ? '在线' : '离线' }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="messages-container" ref="messagesContainer">
|
||
<div class="messages-list">
|
||
<div
|
||
v-for="message in messages"
|
||
:key="message.id"
|
||
class="message"
|
||
:class="{ own: isOwnMessage(message) }"
|
||
>
|
||
<div class="message-avatar">
|
||
{{ message.sender_type === 'user' ? '👤' : (chatStore.currentSession?.bot?.avatar || '🤖') }}
|
||
</div>
|
||
<div class="message-content">
|
||
<div class="message-header">
|
||
<span class="sender-name">{{ message.sender_name }}</span>
|
||
<span class="message-time">{{ formatTime(message.created_at) }}</span>
|
||
</div>
|
||
<div class="message-bubble">
|
||
<MarkdownMessage :content="message.content" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="empty-messages" v-if="messages.length === 0">
|
||
<p>暂无消息,开始对话吧</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.chat-window {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.chat-header {
|
||
background: var(--bg-primary);
|
||
border-bottom: 1px solid var(--border-color);
|
||
padding: var(--spacing-md);
|
||
}
|
||
|
||
.chat-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.bot-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: var(--bg-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.chat-title h3 {
|
||
font-size: var(--font-size-md);
|
||
font-weight: 600;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.chat-title .status {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.chat-title .status.online {
|
||
color: var(--success-color);
|
||
}
|
||
|
||
.messages-container {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--spacing-md);
|
||
}
|
||
|
||
.messages-list {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.message {
|
||
display: flex;
|
||
gap: var(--spacing-sm);
|
||
max-width: 80%;
|
||
}
|
||
|
||
.message.own {
|
||
flex-direction: row-reverse;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.message-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background: var(--bg-tertiary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.message-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.message-header {
|
||
display: flex;
|
||
gap: var(--spacing-sm);
|
||
align-items: center;
|
||
font-size: var(--font-size-xs);
|
||
}
|
||
|
||
.message.own .message-header {
|
||
flex-direction: row-reverse;
|
||
}
|
||
|
||
.sender-name {
|
||
font-weight: 500;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.message-time {
|
||
color: var(--text-tertiary);
|
||
}
|
||
|
||
.message-bubble {
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
background: var(--bg-primary);
|
||
border-radius: var(--border-radius-lg);
|
||
color: var(--text-primary);
|
||
line-height: 1.5;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.message.own .message-bubble {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.empty-messages {
|
||
text-align: center;
|
||
color: var(--text-tertiary);
|
||
padding: var(--spacing-xl);
|
||
}
|
||
</style>
|