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",
"style": {
"navigationBarTitleText": "AI助手",
"navigationBarBackgroundColor": "#64B5F6",
"navigationBarTitleText": "宠物助手",
"navigationBarBackgroundColor": "#FF8A80",
"navigationBarTextStyle": "white"
}
},

View File

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