pet/pages/pets/pet-chat-simple.vue

643 lines
15 KiB
Vue
Raw Permalink 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>
<view class="pet-chat-container page-container-with-bg">
<!-- 宠物信息卡片 -->
<view class="pet-info-card">
<view class="pet-avatar-wrapper">
<view class="pet-avatar">
<text class="avatar-emoji">🐱</text>
</view>
</view>
<view class="pet-info-text">
<text class="pet-name">{{ petInfo.name || '小橘' }}</text>
<text class="pet-status">{{ petInfo.personality || '活泼好动,喜欢撒娇' }}</text>
</view>
<view class="chat-status">
<view class="status-dot"></view>
<text class="status-text">在线</text>
</view>
</view>
<!-- 聊天消息列表 -->
<view class="chat-messages">
<scroll-view
class="chat-scroll"
scroll-y
:scroll-top="scrollTop"
:scroll-into-view="scrollIntoView"
scroll-with-animation
>
<view class="message-list">
<!-- 消息项 -->
<template v-for="message in messageList" :key="message.id">
<!-- AI消息 -->
<view class="message-item ai" v-if="message.type === 'ai'">
<view class="message-avatar">
<view class="pet-avatar">
<text class="avatar-emoji">🐱</text>
</view>
</view>
<view class="message-content ai">
<view class="message-bubble ai">
<text class="message-text">{{ message.content }}</text>
</view>
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
</view>
</view>
<!-- 用户消息 -->
<view class="message-item user" v-if="message.type === 'user'">
<view class="message-content user">
<view class="message-bubble user">
<text class="message-text">{{ message.content }}</text>
</view>
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
</view>
<view class="message-avatar">
<view class="user-avatar">
<text class="avatar-emoji">😊</text>
</view>
</view>
</view>
</template>
<!-- AI正在输入提示 -->
<view class="message-item ai" v-if="aiTyping">
<view class="message-avatar">
<view class="pet-avatar">
<text class="avatar-emoji">🐱</text>
</view>
</view>
<view class="message-content ai">
<view class="message-bubble ai typing-bubble">
<view class="typing-dots">
<view class="typing-dot"></view>
<view class="typing-dot"></view>
<view class="typing-dot"></view>
</view>
</view>
</view>
</view>
<!-- 滚动锚点 -->
<view id="scroll-bottom" style="height: 1rpx;"></view>
</view>
</scroll-view>
</view>
<!-- 输入区域 -->
<view class="chat-input-area">
<view class="input-container">
<view class="input-wrapper">
<input
v-model="inputMessage"
placeholder="和你的宠物说点什么..."
class="message-input"
@confirm="sendMessage"
confirm-type="send"
maxlength="500"
/>
</view>
<view class="send-button" @click="sendMessage" :class="{ active: inputMessage.trim() }">
<text class="send-text">发送</text>
</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,
scrollIntoView: '',
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(() => {
// 使用scroll-into-view滚动到底部锚点
this.scrollIntoView = 'scroll-bottom'
// 同时设置scrollTop作为备用方案
setTimeout(() => {
this.scrollTop = Math.random() * 1000 + 99999 // 每次不同的值确保触发
}, 100)
})
},
// 格式化时间
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 {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
.pet-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx 0 30rpx;
border-radius: 24rpx;
padding: 24rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
.pet-avatar-wrapper {
margin-right: 24rpx;
.pet-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(255, 138, 128, 0.3);
.avatar-emoji {
font-size: 40rpx;
color: #ffffff;
}
}
}
.pet-info-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
.pet-name {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.pet-status {
font-size: 24rpx;
color: #666666;
}
}
.chat-status {
display: flex;
align-items: center;
gap: 12rpx;
.status-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #4CAF50;
box-shadow: 0 0 0 4rpx rgba(76, 175, 80, 0.2);
}
.status-text {
font-size: 24rpx;
color: #4CAF50;
font-weight: 500;
}
}
}
.chat-messages {
position: absolute;
top: 140rpx; /* 宠物信息卡片的高度 */
left: 0;
right: 0;
bottom: 120rpx; /* 输入框的高度 */
width: 100%;
overflow: hidden;
}
.chat-scroll {
width: 100%;
height: 100%;
}
.message-list {
padding: 20rpx 0;
padding-bottom: 80rpx; /* 为输入框留出空间 */
min-height: 100%;
box-sizing: border-box;
}
.message-item {
display: flex;
margin-bottom: 24rpx;
align-items: flex-start;
}
.message-item.user {
justify-content: flex-end;
padding: 0 10rpx 0 80rpx; /* 右边距30rpx左边距80rpx给AI消息留空间 */
}
.message-item.ai {
justify-content: flex-start;
padding: 0 80rpx 0 10rpx; /* 左边距30rpx右边距80rpx给用户消息留空间 */
}
.message-avatar {
width: 64rpx;
height: 64rpx;
margin: 0 16rpx;
flex-shrink: 0;
}
.pet-avatar, .user-avatar {
width: 100%;
height: 100%;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.pet-avatar {
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
box-shadow: 0 8rpx 24rpx rgba(255, 138, 128, 0.3);
}
.user-avatar {
background: linear-gradient(135deg, #81C784, #A5D6A7);
box-shadow: 0 8rpx 24rpx rgba(129, 199, 132, 0.3);
}
.avatar-emoji {
color: #ffffff;
}
.message-content {
display: flex;
flex-direction: column;
max-width: 65%;
}
.message-content.user {
align-items: flex-end;
}
.message-content.ai {
align-items: flex-start;
}
.message-bubble {
padding: 20rpx 24rpx;
border-radius: 24rpx;
word-wrap: break-word;
position: relative;
}
.message-bubble.ai {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10rpx);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
border-bottom-left-radius: 8rpx;
}
.message-bubble.ai.typing-bubble {
padding: 24rpx;
}
.message-bubble.user {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 100%);
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.3);
border-bottom-right-radius: 8rpx;
}
.message-text {
font-size: 28rpx;
line-height: 1.4;
color: #333333;
}
.message-bubble.user .message-text {
color: #ffffff;
}
.message-time {
margin-top: 8rpx;
font-size: 20rpx;
color: rgba(255, 255, 255, 0.7);
}
.message-content.user .message-time {
text-align: right;
}
.message-content.ai .message-time {
text-align: left;
}
.typing-dots {
display: flex;
gap: 8rpx;
justify-content: center;
.typing-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #FF8A80;
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: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 16rpx 20rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
box-sizing: border-box;
}
.input-container {
display: flex;
align-items: center;
gap: 16rpx;
.input-wrapper {
flex: 1;
background: rgba(255, 255, 255, 0.9);
border-radius: 28rpx;
padding: 0 20rpx;
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 255, 255, 0.3);
height: 56rpx;
display: flex;
align-items: center;
.message-input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333333;
border: none;
outline: none;
background: transparent;
}
}
.send-button {
padding: 12rpx 20rpx;
border-radius: 24rpx;
background: rgba(200, 200, 200, 0.5);
transition: all 0.3s ease;
}
.send-button.active {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 100%);
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
}
.send-button:active {
transform: scale(0.95);
}
.send-text {
font-size: 26rpx;
color: #ffffff;
font-weight: 500;
}
.send-button:not(.active) .send-text {
color: #cccccc;
}
}
.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>