443 lines
10 KiB
Vue
443 lines
10 KiB
Vue
<template>
|
|
<view class="pet-chat-container">
|
|
<u-navbar :title="`与${petInfo.name}聊天`" left-icon="arrow-left" @left-click="goBack">
|
|
<template #right>
|
|
<u-icon name="more-circle" size="20" @click="showMenu = true"></u-icon>
|
|
</template>
|
|
</u-navbar>
|
|
|
|
<!-- 宠物信息卡片 -->
|
|
<view class="pet-info-card">
|
|
<u-avatar :src="petInfo.avatar || '/static/default-pet.png'" size="40" shape="circle"></u-avatar>
|
|
<view class="pet-info-text">
|
|
<u-text :text="petInfo.name" type="primary" size="14" bold></u-text>
|
|
<u-text :text="petInfo.personality" type="tips" size="12"></u-text>
|
|
</view>
|
|
<view class="chat-status">
|
|
<u-tag text="在线" type="success" size="mini"></u-tag>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 聊天消息列表 -->
|
|
<scroll-view
|
|
class="chat-messages"
|
|
scroll-y
|
|
:scroll-top="scrollTop"
|
|
scroll-with-animation
|
|
>
|
|
<view class="message-list">
|
|
<!-- 消息项 -->
|
|
<view
|
|
class="message-item"
|
|
v-for="message in messageList"
|
|
:key="message.id"
|
|
:class="message.type"
|
|
>
|
|
<view class="message-avatar" v-if="message.type === 'ai'">
|
|
<u-avatar :src="petInfo.avatar || '/static/default-pet.png'" size="32" shape="circle"></u-avatar>
|
|
</view>
|
|
|
|
<view class="message-content">
|
|
<view class="message-bubble" :class="message.type">
|
|
<u-text :text="message.content" size="14" :color="message.type === 'user' ? '#ffffff' : '#333333'"></u-text>
|
|
</view>
|
|
<view class="message-time">
|
|
<u-text :text="formatTime(message.timestamp)" type="tips" size="10"></u-text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="message-avatar" v-if="message.type === 'user'">
|
|
<u-avatar src="/static/user-avatar.png" size="32" shape="circle"></u-avatar>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- AI正在输入提示 -->
|
|
<view class="message-item ai" v-if="aiTyping">
|
|
<view class="message-avatar">
|
|
<u-avatar :src="petInfo.avatar || '/static/default-pet.png'" size="32" shape="circle"></u-avatar>
|
|
</view>
|
|
<view class="message-content">
|
|
<view class="message-bubble ai typing">
|
|
<view class="typing-dots">
|
|
<view class="dot"></view>
|
|
<view class="dot"></view>
|
|
<view class="dot"></view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 输入区域 -->
|
|
<view class="chat-input-area">
|
|
<view class="input-container">
|
|
<u-input
|
|
v-model="inputMessage"
|
|
placeholder="和你的宠物说点什么..."
|
|
:border="false"
|
|
:custom-style="inputStyle"
|
|
@confirm="sendMessage"
|
|
confirm-type="send"
|
|
></u-input>
|
|
<view class="input-actions">
|
|
<u-button
|
|
type="primary"
|
|
size="mini"
|
|
text="发送"
|
|
@click="sendMessage"
|
|
:disabled="!inputMessage.trim()"
|
|
></u-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 聊天菜单弹窗 -->
|
|
<u-popup v-model="showMenu" mode="bottom" border-radius="20">
|
|
<view class="chat-menu">
|
|
<view class="menu-title">
|
|
<u-text text="聊天设置" type="primary" size="16" bold></u-text>
|
|
</view>
|
|
<view class="menu-items">
|
|
<view class="menu-item" @click="clearHistory">
|
|
<u-icon name="delete" size="20" color="#ff6b6b"></u-icon>
|
|
<u-text text="清空聊天记录" size="14"></u-text>
|
|
</view>
|
|
</view>
|
|
<view class="menu-cancel" @click="showMenu = false">
|
|
<u-text text="取消" type="info" size="14"></u-text>
|
|
</view>
|
|
</view>
|
|
</u-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'PetChatSimple',
|
|
data() {
|
|
return {
|
|
petId: '',
|
|
petInfo: {},
|
|
messageList: [],
|
|
inputMessage: '',
|
|
scrollTop: 0,
|
|
aiTyping: false,
|
|
showMenu: false,
|
|
inputStyle: {
|
|
backgroundColor: '#f8f9fa',
|
|
borderRadius: '20px',
|
|
padding: '10px 15px'
|
|
}
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.petId = options.petId || '1'
|
|
this.loadPetInfo()
|
|
this.loadChatHistory()
|
|
},
|
|
methods: {
|
|
// 加载宠物信息
|
|
loadPetInfo() {
|
|
const mockPets = [
|
|
{
|
|
id: '1',
|
|
name: '小橘',
|
|
breed: '橘猫',
|
|
personality: '活泼好动,喜欢撒娇',
|
|
avatar: '/static/cat-avatar.jpg'
|
|
},
|
|
{
|
|
id: '2',
|
|
name: '小白',
|
|
breed: '金毛',
|
|
personality: '温顺友好,聪明伶俐',
|
|
avatar: '/static/dog-avatar.jpg'
|
|
}
|
|
]
|
|
|
|
this.petInfo = mockPets.find(pet => pet.id === this.petId) || mockPets[0]
|
|
},
|
|
|
|
// 加载聊天历史
|
|
loadChatHistory() {
|
|
const mockMessages = [
|
|
{
|
|
id: '1',
|
|
type: 'ai',
|
|
content: `你好主人!我是${this.petInfo.name},今天感觉特别开心呢!有什么想和我聊的吗?`,
|
|
timestamp: Date.now() - 3600000
|
|
},
|
|
{
|
|
id: '2',
|
|
type: 'user',
|
|
content: '小橘今天吃饭怎么样?',
|
|
timestamp: Date.now() - 3500000
|
|
},
|
|
{
|
|
id: '3',
|
|
type: 'ai',
|
|
content: '今天的小鱼干特别香!我吃得可开心了,还想要更多呢~主人你也要记得按时吃饭哦!',
|
|
timestamp: Date.now() - 3400000
|
|
}
|
|
]
|
|
|
|
this.messageList = mockMessages
|
|
this.scrollToBottom()
|
|
},
|
|
|
|
// 发送消息
|
|
sendMessage() {
|
|
if (!this.inputMessage.trim()) return
|
|
|
|
const userMessage = {
|
|
id: Date.now().toString(),
|
|
type: 'user',
|
|
content: this.inputMessage.trim(),
|
|
timestamp: Date.now()
|
|
}
|
|
|
|
this.messageList.push(userMessage)
|
|
const messageContent = this.inputMessage.trim()
|
|
this.inputMessage = ''
|
|
|
|
this.scrollToBottom()
|
|
this.simulateAIReply(messageContent)
|
|
},
|
|
|
|
// 模拟AI回复
|
|
simulateAIReply(userMessage) {
|
|
this.aiTyping = true
|
|
this.scrollToBottom()
|
|
|
|
setTimeout(() => {
|
|
const aiResponse = this.generateAIResponse(userMessage)
|
|
const aiMessage = {
|
|
id: (Date.now() + 1).toString(),
|
|
type: 'ai',
|
|
content: aiResponse,
|
|
timestamp: Date.now()
|
|
}
|
|
|
|
this.messageList.push(aiMessage)
|
|
this.aiTyping = false
|
|
this.scrollToBottom()
|
|
}, 1500)
|
|
},
|
|
|
|
// 生成AI回复内容
|
|
generateAIResponse(userMessage) {
|
|
const petName = this.petInfo.name
|
|
const responses = [
|
|
`${petName}最喜欢和主人聊天了!今天过得怎么样呀?`,
|
|
`主人好!${petName}今天心情特别好呢~`,
|
|
`哇!主人来找我聊天了,${petName}好开心!`,
|
|
`${petName}听不太懂,但是很开心能和主人聊天!`,
|
|
`主人说的话${petName}都会认真听的!`
|
|
]
|
|
|
|
return responses[Math.floor(Math.random() * responses.length)]
|
|
},
|
|
|
|
// 滚动到底部
|
|
scrollToBottom() {
|
|
this.$nextTick(() => {
|
|
this.scrollTop = 99999
|
|
})
|
|
},
|
|
|
|
// 格式化时间
|
|
formatTime(timestamp) {
|
|
const date = new Date(timestamp)
|
|
const now = new Date()
|
|
const diff = now - date
|
|
|
|
if (diff < 60000) return '刚刚'
|
|
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`
|
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`
|
|
|
|
return `${date.getMonth() + 1}月${date.getDate()}日`
|
|
},
|
|
|
|
// 清空聊天记录
|
|
clearHistory() {
|
|
uni.showModal({
|
|
title: '确认清空',
|
|
content: '确定要清空所有聊天记录吗?',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
this.messageList = []
|
|
this.showMenu = false
|
|
uni.showToast({
|
|
title: '已清空聊天记录',
|
|
icon: 'success'
|
|
})
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
// 返回上一页
|
|
goBack() {
|
|
uni.navigateBack()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.pet-chat-container {
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.pet-info-card {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 15px 20px;
|
|
background-color: #ffffff;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
.pet-info-text {
|
|
flex: 1;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.chat-status {
|
|
margin-left: 10px;
|
|
}
|
|
}
|
|
|
|
.chat-messages {
|
|
flex: 1;
|
|
padding: 0 15px;
|
|
}
|
|
|
|
.message-list {
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.message-item {
|
|
display: flex;
|
|
margin-bottom: 20px;
|
|
|
|
&.user {
|
|
flex-direction: row-reverse;
|
|
|
|
.message-content {
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.message-bubble {
|
|
background-color: #FF8A80;
|
|
color: #ffffff;
|
|
}
|
|
}
|
|
|
|
&.ai {
|
|
.message-bubble {
|
|
background-color: #ffffff;
|
|
border: 1px solid #f0f0f0;
|
|
}
|
|
}
|
|
}
|
|
|
|
.message-avatar {
|
|
margin: 0 10px;
|
|
}
|
|
|
|
.message-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
max-width: 70%;
|
|
}
|
|
|
|
.message-bubble {
|
|
padding: 12px 16px;
|
|
border-radius: 18px;
|
|
word-wrap: break-word;
|
|
|
|
&.typing {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
|
|
.message-time {
|
|
margin-top: 5px;
|
|
text-align: center;
|
|
}
|
|
|
|
.typing-dots {
|
|
display: flex;
|
|
gap: 4px;
|
|
|
|
.dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background-color: #ccc;
|
|
animation: typing 1.4s infinite ease-in-out;
|
|
|
|
&:nth-child(1) { animation-delay: -0.32s; }
|
|
&:nth-child(2) { animation-delay: -0.16s; }
|
|
}
|
|
}
|
|
|
|
@keyframes typing {
|
|
0%, 80%, 100% {
|
|
transform: scale(0.8);
|
|
opacity: 0.5;
|
|
}
|
|
40% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.chat-input-area {
|
|
background-color: #ffffff;
|
|
border-top: 1px solid #f0f0f0;
|
|
padding: 15px 20px;
|
|
padding-bottom: calc(15px + env(safe-area-inset-bottom));
|
|
}
|
|
|
|
.input-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.input-actions {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.chat-menu {
|
|
padding: 20px;
|
|
|
|
.menu-title {
|
|
text-align: center;
|
|
padding-bottom: 20px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.menu-items {
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
padding: 15px 0;
|
|
}
|
|
|
|
.menu-cancel {
|
|
text-align: center;
|
|
padding: 15px 0;
|
|
border-top: 1px solid #f0f0f0;
|
|
}
|
|
}
|
|
</style>
|