211 lines
4.1 KiB
Vue
211 lines
4.1 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
|
||
// --- 类型定义 ---
|
||
interface Message {
|
||
id: number
|
||
role: 'user' | 'ai'
|
||
content: string
|
||
timestamp: string
|
||
}
|
||
|
||
// --- 状态管理 ---
|
||
const messages = ref<Message[]>([
|
||
{
|
||
id: 1,
|
||
role: 'ai',
|
||
content: '你好!我是你的 AI 助手,有什么可以帮你的吗?',
|
||
timestamp: new Date().toLocaleTimeString()
|
||
}
|
||
])
|
||
const inputVal = ref('')
|
||
const chatContainerRef = ref<HTMLElement | null>(null)
|
||
|
||
|
||
|
||
// --- 模拟 API 调用 ---
|
||
const mockAiResponse = async () => {
|
||
// 模拟网络延迟
|
||
setTimeout(() => {
|
||
messages.value.push({
|
||
id: Date.now(),
|
||
role: 'ai',
|
||
content: '这是一个模拟的 AI 回复。它可以是很长很长很长很长很长很长很长很长很长很长很长很长的一段话,用来测试滚动条是否能正常工作。Vue 3 的性能非常棒!',
|
||
timestamp: new Date().toLocaleTimeString()
|
||
})
|
||
|
||
|
||
// 尝试滚动 (task2)
|
||
scrollToBottom()
|
||
}, 1500)
|
||
}
|
||
|
||
// --- 事件处理 ---
|
||
const sendMessage = () => {
|
||
if (!inputVal.value.trim()) return
|
||
|
||
|
||
|
||
// 添加用户消息
|
||
messages.value.push({
|
||
id: Date.now(),
|
||
role: 'user',
|
||
content: inputVal.value,
|
||
timestamp: new Date().toLocaleTimeString()
|
||
})
|
||
|
||
// 3. 清空输入
|
||
inputVal.value = ''
|
||
|
||
// task2
|
||
//
|
||
scrollToBottom()
|
||
|
||
// 4. 请求 AI
|
||
mockAiResponse()
|
||
}
|
||
|
||
// 滚动到底部
|
||
const scrollToBottom = () => {
|
||
if (chatContainerRef.value) {
|
||
// 滚动
|
||
chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="chat-wrapper">
|
||
<header class="header">
|
||
<h1>🤖 AI Chat Lite</h1>
|
||
</header>
|
||
|
||
<div class="chat-container" ref="chatContainerRef">
|
||
<div
|
||
v-for="msg in messages"
|
||
:key="msg.id"
|
||
class="message-row"
|
||
:class="msg.role === 'user' ? 'row-user' : 'row-ai'"
|
||
>
|
||
<div class="avatar">
|
||
{{ msg.role === 'ai' ? '🤖' : '👤' }}
|
||
</div>
|
||
<div class="bubble">
|
||
<div class="text">{{ msg.content }}</div>
|
||
<div class="time">{{ msg.timestamp }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="input-area">
|
||
<input
|
||
v-model="inputVal"
|
||
@keyup.enter="sendMessage"
|
||
type="text"
|
||
placeholder="输入你的问题..."
|
||
/>
|
||
<button @click="sendMessage" >
|
||
发送
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* 基础样式,无需修改 */
|
||
.chat-wrapper {
|
||
max-width: 600px;
|
||
margin: 40px auto;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
font-family: sans-serif;
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.header {
|
||
background: #f3f4f6;
|
||
padding: 16px;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
.header h1 { margin: 0; font-size: 1.25rem; color: #1f2937; }
|
||
|
||
.chat-container {
|
||
height: 400px;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
background: #fff;
|
||
/* scroll-behavior: smooth; */
|
||
/* 平滑滚动 */
|
||
}
|
||
|
||
/* 消息行样式 */
|
||
.message-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 16px;
|
||
gap: 10px;
|
||
}
|
||
.row-user { flex-direction: row-reverse; }
|
||
|
||
.avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: #eee;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.bubble {
|
||
max-width: 70%;
|
||
padding: 10px 14px;
|
||
border-radius: 10px;
|
||
position: relative;
|
||
}
|
||
.row-ai .bubble { background: #f3f4f6; color: #1f2937; border-top-left-radius: 2px; }
|
||
.row-user .bubble { background: #3b82f6; color: white; border-top-right-radius: 2px; }
|
||
|
||
.time {
|
||
font-size: 10px;
|
||
opacity: 0.7;
|
||
margin-top: 4px;
|
||
text-align: right;
|
||
}
|
||
|
||
.input-area {
|
||
padding: 16px;
|
||
background: #f9fafb;
|
||
border-top: 1px solid #e5e7eb;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
input {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
outline: none;
|
||
}
|
||
input:focus { border-color: #3b82f6; }
|
||
|
||
button {
|
||
padding: 0 20px;
|
||
background: #3b82f6;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: opacity 0.2s;
|
||
}
|
||
button:disabled {
|
||
background: #9ca3af;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|