Files
chat/frontend/src/components/MessageList.vue
T
2026-07-14 10:00:34 +08:00

121 lines
2.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="message-list" ref="listRef">
<div v-if="!visibleMessages.length && !loading && !sending && !streaming.trim()" class="welcome">
<div class="welcome-icon">💬</div>
<h2>有什么可以帮您的</h2>
<p>输入消息开始对话支持 Markdown图片文件等</p>
</div>
<div v-if="loading" class="loading-state">加载中...</div>
<MessageItem
v-for="msg in visibleMessages"
:key="msg.id"
:message="msg"
/>
<MessageItem
v-if="streaming.trim()"
:message="{ role: 'assistant', content: streaming, content_type: 'markdown' }"
:is-streaming="true"
/>
<div v-if="sending && !streaming" class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick, computed } from 'vue'
import MessageItem from './MessageItem.vue'
const props = defineProps({
messages: { type: Array, default: () => [] },
loading: Boolean,
streaming: { type: String, default: '' },
sending: Boolean
})
const listRef = ref(null)
const visibleMessages = computed(() => {
return props.messages.filter(msg => {
const hasContent = !!(msg.content && String(msg.content).trim())
const attachments = msg.attachments || []
const hasAttachments = Array.isArray(attachments) && attachments.length > 0
return hasContent || hasAttachments
})
})
watch(
() => [props.messages.length, props.streaming],
async () => {
await nextTick()
if (listRef.value) {
listRef.value.scrollTop = listRef.value.scrollHeight
}
}
)
</script>
<style scoped>
.message-list {
flex: 1;
overflow-y: auto;
padding: 24px 16px;
}
.welcome {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
color: var(--text-secondary);
}
.welcome-icon {
font-size: 64px;
margin-bottom: 16px;
}
.welcome h2 {
font-size: 24px;
color: var(--text-primary);
margin-bottom: 8px;
}
.welcome p {
font-size: 14px;
}
.loading-state {
text-align: center;
padding: 24px;
color: var(--text-muted);
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 16px 24px;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: var(--text-muted);
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
</style>