This commit is contained in:
yvan 2025-08-13 15:16:05 +08:00
parent 7830a6ebbd
commit 479a246518
2 changed files with 424 additions and 137 deletions

View File

@ -11,8 +11,8 @@
{ {
"path": "pages/assistant/assistant", "path": "pages/assistant/assistant",
"style": { "style": {
"navigationBarTitleText": "AI助手", "navigationBarTitleText": "宠物助手",
"navigationBarBackgroundColor": "#64B5F6", "navigationBarBackgroundColor": "#FF8A80",
"navigationBarTextStyle": "white" "navigationBarTextStyle": "white"
} }
}, },

View File

@ -1,81 +1,109 @@
<template> <template>
<view class="assistant-container"> <view class="assistant-container">
<u-navbar title="AI助手" :border="false" bg-color="#4ecdc4"> <!-- 宠物助手信息卡片 -->
<template #right> <view class="assistant-info-card">
<u-icon name="book" color="white" size="20" @click="openKnowledge"></u-icon> <view class="assistant-avatar-wrapper">
</template> <view class="assistant-avatar">
</u-navbar> <text class="avatar-emoji">🤖</text>
</view>
<!-- 快捷问题 --> </view>
<view class="quick-questions" v-if="messageList.length <= 1"> <view class="assistant-info-text">
<u-text text="常见问题" type="primary" size="16" bold margin="20rpx 0"></u-text> <text class="assistant-name">宠物助手</text>
<view class="question-grid"> <text class="assistant-status">专业的宠物护理顾问</text>
<u-button </view>
v-for="question in quickQuestions" <view class="chat-status">
:key="question.id" <view class="status-dot"></view>
:text="question.text" <text class="status-text">在线</text>
type="info" </view>
size="mini" <view class="knowledge-icon" @click="openKnowledge">
plain <text class="icon-text">📚</text>
@click="askQuestion(question.text)"
></u-button>
</view> </view>
</view> </view>
<!-- 聊天区域 --> <!-- 聊天消息列表 -->
<scroll-view class="chat-area" scroll-y="true" :scroll-top="scrollTop" scroll-with-animation="true"> <view class="chat-messages">
<view class="message-list"> <scroll-view
<view class="message-item" v-for="(message, index) in messageList" :key="index" class="chat-scroll"
:class="message.type === 'user' ? 'user-message' : 'ai-message'"> scroll-y
<u-avatar :scroll-top="scrollTop"
v-if="message.type === 'user'" scroll-with-animation
:src="userAvatar" >
size="35" <view class="message-list">
shape="circle" <!-- 消息项 -->
></u-avatar> <template v-for="(message, index) in messageList" :key="index">
<u-avatar <!-- AI消息 -->
v-else <view class="message-item ai" v-if="message.type === 'ai'">
src="/static/ai-avatar.png" <view class="message-avatar">
size="35" <view class="assistant-avatar">
shape="circle" <text class="avatar-emoji">🤖</text>
></u-avatar> </view>
<view class="message-content"> </view>
<u-text :text="message.content" size="14" :type="message.type === 'user' ? 'primary' : 'info'"></u-text> <view class="message-content ai">
<u-text :text="message.time" type="tips" size="10" margin="8rpx 0 0 0"></u-text> <view class="message-bubble ai">
</view> <text class="message-text" v-if="!(isThinking && index === messageList.length - 1)">{{ message.content }}</text>
</view> <view class="typing-dots" v-else>
<view class="typing-dot"></view>
<view class="typing-dot"></view>
<view class="typing-dot"></view>
</view>
</view>
<text class="message-time" v-if="!(isThinking && index === messageList.length - 1)">{{ message.time }}</text>
</view>
</view>
<!-- AI思考中状态 --> <!-- 用户消息 -->
<view class="message-item ai-message" v-if="isThinking"> <view class="message-item user" v-if="message.type === 'user'">
<u-avatar src="/static/ai-avatar.png" size="35" shape="circle"></u-avatar> <view class="message-content user">
<view class="message-content thinking"> <view class="message-bubble user">
<u-loading-icon mode="flower" color="#4ecdc4"></u-loading-icon> <text class="message-text">{{ message.content }}</text>
<u-text text="AI正在思考中..." type="tips" size="12" margin="0 0 0 20rpx"></u-text> </view>
<text class="message-time">{{ message.time }}</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="isThinking">
<view class="message-avatar">
<view class="assistant-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>
</view> </view>
</view> </scroll-view>
</scroll-view> </view>
<!-- 输入区域 --> <!-- 输入区域 -->
<view class="input-area"> <view class="chat-input-area">
<view class="input-container"> <view class="input-container">
<u-input <view class="input-wrapper">
v-model="inputMessage" <input
placeholder="请输入您的问题..." v-model="inputMessage"
:border="false" placeholder="和宠物助手说点什么..."
bg-color="#f5f5f5" class="message-input"
shape="circle" @confirm="sendMessage"
@confirm="sendMessage" confirm-type="send"
confirm-type="send" maxlength="500"
></u-input> />
<u-button </view>
:text="inputMessage.trim() ? '发送' : '🎤'" <view class="send-button" @click="sendMessage" :class="{ active: inputMessage.trim() }">
type="primary" <text class="send-text">发送</text>
size="mini" </view>
shape="circle"
:disabled="isThinking"
@click="inputMessage.trim() ? sendMessage() : startVoiceInput()"
></u-button>
</view> </view>
</view> </view>
</view> </view>
@ -235,95 +263,354 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.assistant-container { .assistant-container {
display: flex; width: 100vw;
flex-direction: column; height: 100vh;
height: 100vh; display: flex;
background-color: #f8f9fa; flex-direction: column;
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
position: relative;
}
.assistant-info-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 20rpx 30rpx 0 30rpx;
margin-top: calc(20rpx + env(safe-area-inset-top));
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);
.assistant-avatar-wrapper {
margin-right: 24rpx;
.assistant-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;
}
}
}
.assistant-info-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
.assistant-name {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.assistant-status {
font-size: 24rpx;
color: #666666;
}
}
.chat-status {
display: flex;
align-items: center;
gap: 12rpx;
margin-right: 20rpx;
.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;
}
}
.knowledge-icon {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
.icon-text {
font-size: 28rpx;
}
}
} }
.quick-questions { .quick-questions {
background-color: white; background: rgba(255, 255, 255, 0.95);
padding: 20rpx; backdrop-filter: blur(20rpx);
margin: 20rpx; margin: 20rpx 30rpx;
border-radius: 20rpx; border-radius: 24rpx;
padding: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.question-grid { .section-title {
display: flex; font-size: 32rpx;
flex-wrap: wrap; font-weight: 600;
gap: 20rpx; color: #FF8A80;
margin-bottom: 20rpx;
display: block;
}
:deep(.u-button) { .question-grid {
margin: 0; display: flex;
} flex-wrap: wrap;
} gap: 20rpx;
.question-button {
background: rgba(255, 138, 128, 0.1);
border: 2rpx solid rgba(255, 138, 128, 0.3);
border-radius: 20rpx;
padding: 16rpx 24rpx;
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.2);
transform: scale(0.98);
}
.question-text {
font-size: 26rpx;
color: #FF8A80;
font-weight: 500;
}
}
}
} }
.chat-area { .chat-messages {
flex: 1; margin-top: 20rpx;
padding: 20rpx; flex: 1;
overflow: hidden;
}
.chat-scroll {
width: 100%;
height: 100%;
} }
.message-list { .message-list {
.message-item { padding: 20rpx 0;
display: flex; padding-bottom: 80rpx;
margin-bottom: 30rpx; min-height: 100%;
align-items: flex-start; box-sizing: border-box;
}
&.user-message { .message-item {
flex-direction: row-reverse; display: flex;
margin-bottom: 24rpx;
align-items: flex-start;
}
.message-content { .message-item.user {
background-color: #4ecdc4; justify-content: flex-end;
margin-right: 20rpx; padding: 0 10rpx 0 80rpx;
border-radius: 20rpx 20rpx 8rpx 20rpx; }
:deep(.u-text) { .message-item.ai {
color: white !important; justify-content: flex-start;
} padding: 0 80rpx 0 10rpx;
} }
}
&.ai-message { .message-avatar {
.message-content { width: 64rpx;
background-color: white; height: 64rpx;
margin-left: 20rpx; margin: 0 16rpx;
border-radius: 20rpx 20rpx 20rpx 8rpx; flex-shrink: 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); }
&.thinking { .assistant-avatar, .user-avatar {
display: flex; width: 100%;
align-items: center; height: 100%;
padding: 20rpx; border-radius: 32rpx;
} display: flex;
} align-items: center;
} justify-content: center;
} font-size: 32rpx;
}
.assistant-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 { .message-content {
max-width: 70%; display: flex;
padding: 20rpx; flex-direction: column;
max-width: 65%;
:deep(.u-text) {
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
}
} }
.input-area { .message-content.user {
background-color: white; align-items: flex-end;
padding: 20rpx 30rpx; }
border-top: 1rpx solid #eee;
.input-container { .message-content.ai {
display: flex; align-items: flex-start;
align-items: center; }
gap: 20rpx;
:deep(.u-input) { .message-bubble {
flex: 1; 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);
}
.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-text {
font-size: 28rpx;
color: #666666;
font-weight: 500;
}
}
.send-button.active {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 100%);
box-shadow: 0 4rpx 16rpx rgba(255, 138, 128, 0.4);
.send-text {
color: #ffffff;
}
}
} }
</style> </style>