chore(build): 调整ai-chat-message组件的位置,减少对主包尺寸的影响

This commit is contained in:
2026-01-16 10:34:00 +08:00
parent dd4176998b
commit e40e6e73b2
4 changed files with 570 additions and 559 deletions

View File

@@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"ns-loading": "../ns-loading/ns-loading"
}
}

View File

@@ -109,6 +109,7 @@ export default {
}); });
}, },
methods: { methods: {
/** /**
* 初始化多语言配置 * 初始化多语言配置
*/ */
@@ -130,12 +131,14 @@ export default {
this.currentLangIndex = 0; this.currentLangIndex = 0;
} }
}, },
/** /**
* 电话联系客服 * 电话联系客服
*/ */
call() { call() {
this.customerService.makePhoneCall(this.tel); this.customerService.makePhoneCall(this.tel);
}, },
/** /**
* 切换中英文语言,并刷新当前页面(保留所有参数) * 切换中英文语言,并刷新当前页面(保留所有参数)
*/ */
@@ -153,12 +156,12 @@ export default {
openAIChat() { openAIChat() {
this.$util.redirectTo(this.$util.AI_CHAT_PAGE_URL); this.$util.redirectTo(this.$util.AI_CHAT_PAGE_URL);
}, },
/**
* 打开客服选择对话框
*/
openCustomerSelectPopup() { openCustomerSelectPopup() {
if (this.customerService) { this.customerService.openCustomerSelectPopupDialog();
this.customerService.openCustomerSelectPopupDialog();
} else {
uni.showToast({ title: '客服初始化中,请稍后重试', icon: 'none' });
}
} }
} }
} }

View File

@@ -1,11 +1,7 @@
<template> <template>
<view class="ai-chat-container"> <view class="ai-chat-container">
<!-- 聊天消息列表 --> <!-- 聊天消息列表 -->
<scroll-view <scroll-view class="chat-messages" scroll-y :scroll-top="scrollTop" @scroll="onScroll"
class="chat-messages"
scroll-y
:scroll-top="scrollTop"
@scroll="onScroll"
:scroll-with-animation="false"> :scroll-with-animation="false">
<!-- 加载更多历史消息 --> <!-- 加载更多历史消息 -->
@@ -14,11 +10,8 @@
</view> </view>
<!-- 消息列表 --> <!-- 消息列表 -->
<view <view v-for="(message, index) in messages" :key="`msg-${message.id || message.timestamp}-${index}`"
v-for="(message, index) in messages" class="message-item" :class="[message.role, { 'first-message': index === 0 }]">
:key="`msg-${message.id || message.timestamp}-${index}`"
class="message-item"
:class="[message.role, { 'first-message': index === 0 }]">
<!-- 用户消息 --> <!-- 用户消息 -->
<view v-if="message.role === 'user'" class="user-message"> <view v-if="message.role === 'user'" class="user-message">
@@ -32,7 +25,7 @@
<view class="message-bubble"> <view class="message-bubble">
<text class="message-text">{{ message.content }}</text> <text class="message-text">{{ message.content }}</text>
</view> </view>
</view> </view>
</view> </view>
<!-- AI消息 --> <!-- AI消息 -->
@@ -85,21 +78,12 @@
<text class="audio-duration">{{ formatDuration(message.duration) }}</text> <text class="audio-duration">{{ formatDuration(message.duration) }}</text>
</view> </view>
<view class="audio-controls"> <view class="audio-controls">
<button <button class="play-btn" :class="{ playing: message.playing }" @click="toggleAudio(message)">
class="play-btn"
:class="{ playing: message.playing }"
@click="toggleAudio(message)">
<text class="iconfont" :class="message.playing ? 'icon-pause' : 'icon-play'"></text> <text class="iconfont" :class="message.playing ? 'icon-pause' : 'icon-play'"></text>
</button> </button>
<slider <slider class="audio-slider" :value="message.currentTime || 0" :max="message.duration || 0"
class="audio-slider" @changing="onAudioSliderChange" @change="onAudioSliderChangeEnd" activeColor="#8a9fb8"
:value="message.currentTime || 0" backgroundColor="#e8edf3" block-size="12" />
:max="message.duration || 0"
@changing="onAudioSliderChange"
@change="onAudioSliderChangeEnd"
activeColor="#8a9fb8"
backgroundColor="#e8edf3"
block-size="12" />
</view> </view>
</view> </view>
</view> </view>
@@ -107,14 +91,8 @@
<!-- 视频消息 --> <!-- 视频消息 -->
<view v-else-if="message.type === 'video'" class="video-content"> <view v-else-if="message.type === 'video'" class="video-content">
<view class="video-player"> <view class="video-player">
<video <video :src="message.url" :poster="message.cover" :controls="true" :autoplay="false"
:src="message.url" class="video-element" @play="onVideoPlay(message)" @pause="onVideoPause(message)">
:poster="message.cover"
:controls="true"
:autoplay="false"
class="video-element"
@play="onVideoPlay(message)"
@pause="onVideoPause(message)">
</video> </video>
<view class="video-info"> <view class="video-info">
<text class="video-title">{{ message.title || '视频消息' }}</text> <text class="video-title">{{ message.title || '视频消息' }}</text>
@@ -163,12 +141,8 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="message-actions" v-if="message.actions && message.actions.length > 0"> <view class="message-actions" v-if="message.actions && message.actions.length > 0">
<button <button v-for="action in message.actions" :key="action.id" class="action-btn"
v-for="action in message.actions" :class="[`iconfont`, action.icon, action.type]" @click="handleAction(action, message)">
:key="action.id"
class="action-btn"
:class="[`iconfont`, action.icon, action.type]"
@click="handleAction(action, message)">
{{ action.text }} {{ action.text }}
</button> </button>
</view> </view>
@@ -199,21 +173,11 @@
</view> </view>
<view class="input-container"> <view class="input-container">
<textarea <textarea v-model="inputText" class="message-input" placeholder="请输入您的问题..." :maxlength="500"
v-model="inputText" :auto-height="true" :show-confirm-bar="false" @confirm="sendMessage" @input="onInput" />
class="message-input"
placeholder="请输入您的问题..."
:maxlength="500"
:auto-height="true"
:show-confirm-bar="false"
@confirm="sendMessage"
@input="onInput" />
<!-- 发送按钮 --> <!-- 发送按钮 -->
<button <button class="send-btn" :class="{ disabled: !inputText.trim() }" @click="sendMessage"
class="send-btn"
:class="{ disabled: !inputText.trim() }"
@click="sendMessage"
:disabled="!inputText.trim()"> :disabled="!inputText.trim()">
<text>发送</text> <text>发送</text>
</button> </button>
@@ -271,11 +235,7 @@
</view> </view>
<view class="voice-content"> <view class="voice-content">
<view class="voice-wave" :class="{ recording: voiceInputing }"> <view class="voice-wave" :class="{ recording: voiceInputing }">
<view <view v-for="i in 8" :key="i" class="wave-bar" :style="{ animationDelay: (i * 0.1) + 's' }"></view>
v-for="i in 8"
:key="i"
class="wave-bar"
:style="{ animationDelay: (i * 0.1) + 's' }"></view>
</view> </view>
<text class="voice-tip">{{ voiceInputing ? '正在录音,松开结束' : '点击开始录音' }}</text> <text class="voice-tip">{{ voiceInputing ? '正在录音,松开结束' : '点击开始录音' }}</text>
</view> </view>
@@ -331,23 +291,15 @@
</view> </view>
<view class="nickname-editor-content"> <view class="nickname-editor-content">
<view class="nickname-input-container"> <view class="nickname-input-container">
<input <input v-model="tempNickname" class="nickname-input" placeholder="请输入您的新昵称" :maxlength="12" type="text" />
v-model="tempNickname"
class="nickname-input"
placeholder="请输入您的新昵称"
:maxlength="12"
type="text" />
</view> </view>
<view class="nickname-tip">昵称长度限制1-12个字符仅自己可见</view> <view class="nickname-tip">昵称长度限制1-12个字符仅自己可见</view>
<view class="nickname-editor-actions"> <view class="nickname-editor-actions">
<button class="nickname-action-btn cancel" @click="closeNicknameEditor"> <button class="nickname-action-btn cancel" @click="closeNicknameEditor">
<text>取消</text> <text>取消</text>
</button> </button>
<button <button class="nickname-action-btn confirm" :class="{ disabled: !tempNickname.trim() }"
class="nickname-action-btn confirm" :disabled="!tempNickname.trim()" @click="saveNickname">
:class="{ disabled: !tempNickname.trim() }"
:disabled="!tempNickname.trim()"
@click="saveNickname">
<text>保存</text> <text>保存</text>
</button> </button>
</view> </view>
@@ -398,7 +350,7 @@ export default {
default: 20 default: 20
} }
}, },
data(){ data() {
return { return {
messages: [], messages: [],
inputText: '', inputText: '',
@@ -432,34 +384,34 @@ export default {
currentConversationId: null, currentConversationId: null,
isAIServiceAvailable: true, isAIServiceAvailable: true,
aiServiceError: null, aiServiceError: null,
isLoadingHistory: false, // isLoadingHistory: false, //
shouldScrollToBottom: false, shouldScrollToBottom: false,
scrollTimer: null, scrollTimer: null,
lastScrollTop: 0, lastScrollTop: 0,
historyOffset: 0, historyOffset: 0,
isFetchingHistory: false isFetchingHistory: false
} }
}, },
onShow() { onShow() {
// ID // ID
const localConvId = this.getConversationIdFromLocal(); const localConvId = this.getConversationIdFromLocal();
if (localConvId) { if (localConvId) {
this.currentConversationId = localConvId; this.currentConversationId = localConvId;
aiService.setConversationId(localConvId); aiService.setConversationId(localConvId);
} else { } else {
this.currentConversationId = aiService.getConversationId(); this.currentConversationId = aiService.getConversationId();
} }
// historyOffset // historyOffset
this.loadChatHistoryIfExist().then(() => { this.loadChatHistoryIfExist().then(() => {
// //
this.historyOffset = this.messages.length; this.historyOffset = this.messages.length;
}); });
// this.historyOffset = 0; // this.historyOffset = 0;
}, },
// //
beforeDestroy() { beforeDestroy() {
if (this.scrollTimer) clearTimeout(this.scrollTimer); if (this.scrollTimer) clearTimeout(this.scrollTimer);
}, },
created() { created() {
this.messages = [...this.initialMessages] this.messages = [...this.initialMessages]
this.messageId = this.messages.length this.messageId = this.messages.length
@@ -468,42 +420,42 @@ export default {
this.initUserAvatarData() this.initUserAvatarData()
// //
this.initUserNicknameData() this.initUserNicknameData()
this.currentConversationId = aiService.getConversationId(); this.currentConversationId = aiService.getConversationId();
},
mounted() {
//
this.$nextTick(() => {
this.scrollToBottom()
})
}, },
mounted() {
//
this.$nextTick(() => {
this.scrollToBottom()
})
},
watch: { watch: {
messages: { messages: {
handler() { handler() {
this.$nextTick(() => { this.$nextTick(() => {
// //
if (!this.isLoadingHistory && this.shouldScrollToBottom) { if (!this.isLoadingHistory && this.shouldScrollToBottom) {
this.scrollToBottom() this.scrollToBottom()
// //
this.shouldScrollToBottom = false this.shouldScrollToBottom = false
} }
}) })
}, },
deep: true deep: true
} }
}, },
methods: { methods: {
// ID // ID
saveConversationIdToLocal(convId) { saveConversationIdToLocal(convId) {
if (convId) { if (convId) {
uni.setStorageSync('dify_conversation_id', convId); uni.setStorageSync('dify_conversation_id', convId);
} }
}, },
getConversationIdFromLocal() { getConversationIdFromLocal() {
return uni.getStorageSync('dify_conversation_id') || null; return uni.getStorageSync('dify_conversation_id') || null;
}, },
clearConversationIdFromLocal() { clearConversationIdFromLocal() {
uni.removeStorageSync('dify_conversation_id'); uni.removeStorageSync('dify_conversation_id');
}, },
// //
initAudioContext() { initAudioContext() {
// #ifdef H5 // #ifdef H5
@@ -512,18 +464,18 @@ export default {
} }
// #endif // #endif
}, },
onScroll(e) { onScroll(e) {
const currentScrollTop = e.detail.scrollTop; const currentScrollTop = e.detail.scrollTop;
this.lastScrollTop = currentScrollTop; this.lastScrollTop = currentScrollTop;
// 50ms // 50ms
if (this.scrollTimer) clearTimeout(this.scrollTimer); if (this.scrollTimer) clearTimeout(this.scrollTimer);
this.scrollTimer = setTimeout(() => { this.scrollTimer = setTimeout(() => {
// <20px // <20px
if (currentScrollTop < 20 && !this.isLoadingHistory && this.showLoadMore) { if (currentScrollTop < 20 && !this.isLoadingHistory && this.showLoadMore) {
this.loadMoreHistory(); this.loadMoreHistory();
} }
}, 50); }, 50);
}, },
// //
initUserAvatarData() { initUserAvatarData() {
// 1. // 1.
@@ -544,42 +496,42 @@ export default {
} }
}, },
// 👇 // 👇
async loadChatHistoryIfExist() { async loadChatHistoryIfExist() {
const convId = aiService.getConversationId(); const convId = aiService.getConversationId();
if (convId) { if (convId) {
try { try {
const history = await aiService.getChatHistory(); const history = await aiService.getChatHistory();
if (!history || !Array.isArray(history.messages)) { if (!history || !Array.isArray(history.messages)) {
console.warn('历史记录为空或格式无效'); console.warn('历史记录为空或格式无效');
return; return;
} }
this.messages = history.messages.map(msg => { this.messages = history.messages.map(msg => {
let content = ''; let content = '';
if (msg.role === 'user') { if (msg.role === 'user') {
content = msg.query || (typeof msg.inputs === 'string' ? msg.inputs : JSON.stringify(msg.inputs || '')) || ''; content = msg.query || (typeof msg.inputs === 'string' ? msg.inputs : JSON.stringify(msg.inputs || '')) || '';
} else { } else {
content = msg.answer || ''; content = msg.answer || '';
} }
return { return {
id: msg.message_id || (Date.now() + Math.random() * 10000), id: msg.message_id || (Date.now() + Math.random() * 10000),
role: msg.role === 'user' ? 'user' : 'assistant', role: msg.role === 'user' ? 'user' : 'assistant',
content: content, content: content,
timestamp: msg.created_at, timestamp: msg.created_at,
actions: msg.role !== 'user' ? [ actions: msg.role !== 'user' ? [
{ id: 1, text: '有帮助', type: 'like' }, { id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' } { id: 2, text: '没帮助', type: 'dislike' }
] : [] ] : []
}; };
}); });
} catch (error) { } catch (error) {
console.error('加载历史记录失败:', error); console.error('加载历史记录失败:', error);
aiService.clearConversationId(); aiService.clearConversationId();
this.clearConversationIdFromLocal(); this.clearConversationIdFromLocal();
this.currentConversationId = null; this.currentConversationId = null;
} }
} }
}, },
// //
initUserNicknameData() { initUserNicknameData() {
// 1. // 1.
@@ -717,7 +669,7 @@ export default {
content: this.inputText.trim(), content: this.inputText.trim(),
timestamp: Date.now() timestamp: Date.now()
} }
this.shouldScrollToBottom = true this.shouldScrollToBottom = true
this.messages.push(userMessage) this.messages.push(userMessage)
this.inputText = '' this.inputText = ''
@@ -787,8 +739,8 @@ export default {
// ID // ID
if (response.conversationId) { if (response.conversationId) {
this.currentConversationId = response.conversationId; this.currentConversationId = response.conversationId;
aiService.setConversationId(response.conversationId); aiService.setConversationId(response.conversationId);
this.saveConversationIdToLocal(response.conversationId); this.saveConversationIdToLocal(response.conversationId);
} }
const aiMessage = { const aiMessage = {
@@ -823,52 +775,52 @@ export default {
// //
this.messages = this.messages.filter(msg => msg.type !== 'loading') this.messages = this.messages.filter(msg => msg.type !== 'loading')
this.shouldScrollToBottom = true this.shouldScrollToBottom = true
this.messages.push(streamMessage) this.messages.push(streamMessage)
// //
await aiService.sendStreamMessage( await aiService.sendStreamMessage(
userMessage, userMessage,
// //
(chunk) => { (chunk) => {
streamMessage.content += chunk streamMessage.content += chunk
this.$forceUpdate() // this.$nextTick() this.$forceUpdate() // this.$nextTick()
}, },
// //
(completeResult) => { (completeResult) => {
let finalContent = ''; let finalContent = '';
let convId = ''; let convId = '';
// //
if (typeof completeResult === 'string') { if (typeof completeResult === 'string') {
finalContent = completeResult; finalContent = completeResult;
} else { } else {
finalContent = completeResult.content || ''; finalContent = completeResult.content || '';
convId = completeResult.conversation_id || ''; // 👈 conversation_id convId = completeResult.conversation_id || ''; // 👈 conversation_id
} }
// //
streamMessage.isStreaming = false; streamMessage.isStreaming = false;
streamMessage.content = finalContent; streamMessage.content = finalContent;
// //
streamMessage.actions = [ streamMessage.actions = [
{ id: 1, text: '有帮助', type: 'like' }, { id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' } { id: 2, text: '没帮助', type: 'dislike' }
]; ];
// conversation_id // conversation_id
if (convId) { if (convId) {
aiService.setConversationId(convId); aiService.setConversationId(convId);
} }
// //
this.$emit('ai-response', streamMessage); this.$emit('ai-response', streamMessage);
this.saveConversationIdToLocal(convId); this.saveConversationIdToLocal(convId);
this.currentConversationId = convId; this.currentConversationId = convId;
} }
); );
}, },
generateAIResponse(userMessage) { generateAIResponse(userMessage) {
const responses = { const responses = {
'你好': '您好我是AI智能客服很高兴为您服务有什么可以帮助您的吗', '你好': '您好我是AI智能客服很高兴为您服务有什么可以帮助您的吗',
@@ -915,8 +867,8 @@ export default {
return Math.floor(diff / 3600000) + '小时前' return Math.floor(diff / 3600000) + '小时前'
} else { } else {
return date.getMonth() + 1 + '月' + date.getDate() + '日 ' + return date.getMonth() + 1 + '月' + date.getDate() + '日 ' +
date.getHours().toString().padStart(2, '0') + ':' + date.getHours().toString().padStart(2, '0') + ':' +
date.getMinutes().toString().padStart(2, '0') date.getMinutes().toString().padStart(2, '0')
} }
}, },
@@ -945,76 +897,76 @@ export default {
}, },
// //
async loadMoreHistory() { async loadMoreHistory() {
if (!this.currentConversationId) { if (!this.currentConversationId) {
this.currentConversationId = this.getConversationIdFromLocal(); this.currentConversationId = this.getConversationIdFromLocal();
aiService.setConversationId(this.currentConversationId); aiService.setConversationId(this.currentConversationId);
} }
// //
if (!this.showLoadMore || !this.currentConversationId || this.isLoadingHistory) { if (!this.showLoadMore || !this.currentConversationId || this.isLoadingHistory) {
return; return;
} }
this.isFetchingHistory = true; this.isFetchingHistory = true;
this.isLoadingHistory = true; this.isLoadingHistory = true;
this.loadingText = '加载历史消息中...'; this.loadingText = '加载历史消息中...';
this.hasMoreHistory = true; this.hasMoreHistory = true;
try { try {
// historyOffsetmessages.length // historyOffsetmessages.length
const response = await aiService.getConversationHistory({ const response = await aiService.getConversationHistory({
conversation_id: this.currentConversationId, conversation_id: this.currentConversationId,
limit: 20, limit: 20,
offset: this.historyOffset // offset: this.historyOffset //
}); });
if (response.success && Array.isArray(response.messages) && response.messages.length > 0) { if (response.success && Array.isArray(response.messages) && response.messages.length > 0) {
const historyMessages = response.messages.map(msg => ({ const historyMessages = response.messages.map(msg => ({
id: msg.id || `history-${Date.now()}-${Math.random().toString(36).slice(2)}`, id: msg.id || `history-${Date.now()}-${Math.random().toString(36).slice(2)}`,
role: msg.role === 'user' ? 'user' : 'ai', role: msg.role === 'user' ? 'user' : 'ai',
type: 'text', type: 'text',
content: msg.content, content: msg.content,
timestamp: msg.create_time * 1000 timestamp: msg.create_time * 1000
})); }));
// 1. // 1.
const newMsgCount = historyMessages.length; const newMsgCount = historyMessages.length;
// 2. // 2.
this.messages = [...historyMessages, ...this.messages]; this.messages = [...historyMessages, ...this.messages];
// 3. // 3.
this.historyOffset += newMsgCount; this.historyOffset += newMsgCount;
// 4. DOM // 4. DOM
await this.$nextTick(); await this.$nextTick();
// DOM // DOM
const query = uni.createSelectorQuery().in(this); const query = uni.createSelectorQuery().in(this);
const heightPromises = []; const heightPromises = [];
for (let i = 0; i < newMsgCount; i++) { for (let i = 0; i < newMsgCount; i++) {
heightPromises.push(new Promise(resolve => { heightPromises.push(new Promise(resolve => {
query.select(`.message-item:nth-child(${i + 1})`).boundingClientRect(rect => { query.select(`.message-item:nth-child(${i + 1})`).boundingClientRect(rect => {
resolve(rect ? rect.height + 32 : 0); // 32rpx resolve(rect ? rect.height + 32 : 0); // 32rpx
}).exec(); }).exec();
})); }));
} }
// //
const heights = await Promise.all(heightPromises); const heights = await Promise.all(heightPromises);
const totalNewHeight = heights.reduce((sum, h) => sum + h, 0); const totalNewHeight = heights.reduce((sum, h) => sum + h, 0);
// 5. scrollTop // 5. scrollTop
this.scrollTop = totalNewHeight; this.scrollTop = totalNewHeight;
} else { } else {
// //
this.showLoadMore = false; this.showLoadMore = false;
} }
} catch (error) { } catch (error) {
console.error('加载历史失败:', error); console.error('加载历史失败:', error);
uni.showToast({ title: '加载历史失败', icon: 'none' }); uni.showToast({ title: '加载历史失败', icon: 'none' });
this.showLoadMore = false; this.showLoadMore = false;
} finally { } finally {
this.isLoadingHistory = false; this.isLoadingHistory = false;
this.isFetchingHistory = false; this.isFetchingHistory = false;
this.hasMoreHistory = false; this.hasMoreHistory = false;
this.loadingText = '加载更多历史消息'; this.loadingText = '加载更多历史消息';
} }
}, },
// //
showMoreTools() { showMoreTools() {
this.showToolsPanel = true this.showToolsPanel = true
@@ -1402,7 +1354,8 @@ $radius-lg: 36rpx;
padding: 24rpx 32rpx; padding: 24rpx 32rpx;
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
min-height: 0; /* 重要防止flex元素溢出 */ min-height: 0;
/* 重要防止flex元素溢出 */
.load-more { .load-more {
text-align: center; text-align: center;
@@ -1417,7 +1370,8 @@ $radius-lg: 36rpx;
margin-top: 24rpx; margin-top: 24rpx;
} }
.user-message, .ai-message { .user-message,
.ai-message {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -1448,17 +1402,20 @@ $radius-lg: 36rpx;
margin-left: 24rpx; margin-left: 24rpx;
max-width: calc(100% - 112rpx); max-width: calc(100% - 112rpx);
min-width: 0; min-width: 0;
width: 0; /* 添加这个属性防止flex元素溢出 */ width: 0;
/* 添加这个属性防止flex元素溢出 */
.message-nickname { .message-nickname {
font-size: 24rpx; font-size: 24rpx;
color: $color-text-light; color: $color-text-light;
margin-bottom: 8rpx; margin-bottom: 8rpx;
letter-spacing: 0.5rpx; letter-spacing: 0.5rpx;
// AI // AI
&:not(.ai-message .message-nickname) { &:not(.ai-message .message-nickname) {
text-align: right; text-align: right;
} }
// AI - // AI -
&.ai-nickname { &.ai-nickname {
text-align: left; text-align: left;
@@ -1898,20 +1855,27 @@ $radius-lg: 36rpx;
text-align: right; text-align: right;
.message-bubble { .message-bubble {
background: linear-gradient(135deg, #ffadd2, #f783ac) !important; /* 粉色渐变 */ background: linear-gradient(135deg, #ffadd2, #f783ac) !important;
color: white !important; /* 粉色渐变 */
border-radius: 16rpx 16rpx 4rpx 16rpx; /* 右对齐气泡尖角适配 */ color: white !important;
box-shadow: 0 8rpx 20rpx rgba(247, 131, 172, 0.3) !important; border-radius: 16rpx 16rpx 4rpx 16rpx;
border: none !important; /* 右对齐气泡尖角适配 */
/* ✅ 关键:允许内容撑开高度 */ box-shadow: 0 8rpx 20rpx rgba(247, 131, 172, 0.3) !important;
min-height: auto; border: none !important;
height: auto; /* ✅ 关键:允许内容撑开高度 */
padding: 24rpx 32rpx; /* 保留内边距 */ min-height: auto;
display: inline-block; /* 让宽度也随内容收缩(可选) */ height: auto;
max-width: 80%; /* 防止过宽 */ padding: 24rpx 32rpx;
word-break: break-word; /* 保留内边距 */
white-space: pre-wrap; /* 保留用户输入的换行符 */ display: inline-block;
line-height: 1.6; /* 让宽度也随内容收缩(可选) */
max-width: 80%;
/* 防止过宽 */
word-break: break-word;
white-space: pre-wrap;
/* 保留用户输入的换行符 */
line-height: 1.6;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
@@ -1919,7 +1883,8 @@ $radius-lg: 36rpx;
right: -14rpx; right: -14rpx;
width: 24rpx; width: 24rpx;
height: 24rpx; height: 24rpx;
background: #ffffff !important; /* 菱形改为白色 */ background: #ffffff !important;
/* 菱形改为白色 */
border-radius: 6rpx; border-radius: 6rpx;
transform: rotate(45deg); transform: rotate(45deg);
box-shadow: 4rpx -4rpx 4rpx rgba(0, 0, 0, 0.05); box-shadow: 4rpx -4rpx 4rpx rgba(0, 0, 0, 0.05);
@@ -1932,11 +1897,9 @@ $radius-lg: 36rpx;
left: -100%; left: -100%;
width: 50%; width: 50%;
height: 100%; height: 100%;
background: linear-gradient( background: linear-gradient(to right,
to right, rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.15) 100%);
rgba(255, 255, 255, 0.15) 100%
);
animation: shine 4s infinite linear; animation: shine 4s infinite linear;
} }
} }
@@ -1952,19 +1915,22 @@ $radius-lg: 36rpx;
align-items: flex-start; align-items: flex-start;
.message-bubble { .message-bubble {
background: linear-gradient(135deg, #dbeafe, #bfdbfe) !important; /* 蓝色浅渐变 */ background: linear-gradient(135deg, #dbeafe, #bfdbfe) !important;
color: #2c3e50 !important; /* 蓝色浅渐变 */
border-radius: 16rpx 16rpx 16rpx 4rpx; /* 左对齐气泡尖角适配 */ color: #2c3e50 !important;
box-shadow: 0 8rpx 20rpx rgba(191, 219, 254, 0.4) !important; border-radius: 16rpx 16rpx 16rpx 4rpx;
border: 1rpx solid #dbeafe !important; /* 左对齐气泡尖角适配 */
box-shadow: 0 8rpx 20rpx rgba(191, 219, 254, 0.4) !important;
border: 1rpx solid #dbeafe !important;
min-height: auto; min-height: auto;
height: auto; height: auto;
padding: 24rpx 32rpx; padding: 24rpx 32rpx;
display: inline-block; display: inline-block;
max-width: 80%; max-width: 80%;
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
line-height: 1.6; line-height: 1.6;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
@@ -1972,7 +1938,8 @@ $radius-lg: 36rpx;
left: -14rpx; left: -14rpx;
width: 24rpx; width: 24rpx;
height: 24rpx; height: 24rpx;
background: #ffffff !important; /* 菱形改为白色 */ background: #ffffff !important;
/* 菱形改为白色 */
border-radius: 6rpx; border-radius: 6rpx;
transform: rotate(45deg); transform: rotate(45deg);
box-shadow: -4rpx 4rpx 4rpx rgba(0, 0, 0, 0.05); box-shadow: -4rpx 4rpx 4rpx rgba(0, 0, 0, 0.05);
@@ -2072,10 +2039,13 @@ $radius-lg: 36rpx;
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 500;
border: none; border: none;
text-align: center; /* 兼容多端文字居中 */ text-align: center;
white-space: nowrap; /* 强制文字单行横向显示 */ /* 兼容多端文字居中 */
line-height: 1; /* 重置行高,避免文字垂直偏移 */ white-space: nowrap;
top:-10px; /* 强制文字单行横向显示 */
line-height: 1;
/* 重置行高,避免文字垂直偏移 */
top: -10px;
&.disabled { &.disabled {
background-color: $color-primary-light; background-color: $color-primary-light;
@@ -2098,19 +2068,21 @@ $radius-lg: 36rpx;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
pointer-events: none; /* 让鼠标事件穿透容器,不影响主内容交互 */ pointer-events: none;
z-index: 999; /* 基础弹窗层级 */ /* 让鼠标事件穿透容器,不影响主内容交互 */
z-index: 999;
/* 基础弹窗层级 */
/* 子弹窗需要开启 pointer-events否则点击无效 */ /* 子弹窗需要开启 pointer-events否则点击无效 */
> .tools-popup, >.tools-popup,
> .voice-popup, >.voice-popup,
> .avatar-selector-popup, >.avatar-selector-popup,
> .nickname-editor-popup { >.nickname-editor-popup {
pointer-events: auto; pointer-events: auto;
} }
/* 昵称编辑弹窗层级最高,避免被其他弹窗遮挡 */ /* 昵称编辑弹窗层级最高,避免被其他弹窗遮挡 */
> .nickname-editor-popup { >.nickname-editor-popup {
z-index: 1001; z-index: 1001;
} }
} }
@@ -2470,14 +2442,45 @@ $radius-lg: 36rpx;
border-radius: 4rpx; border-radius: 4rpx;
animation: none; animation: none;
&:nth-child(1) { height: 60rpx; animation-delay: 0s; } &:nth-child(1) {
&:nth-child(2) { height: 90rpx; animation-delay: 0.1s; } height: 60rpx;
&:nth-child(3) { height: 50rpx; animation-delay: 0.2s; } animation-delay: 0s;
&:nth-child(4) { height: 120rpx; animation-delay: 0.3s; } }
&:nth-child(5) { height: 70rpx; animation-delay: 0.4s; }
&:nth-child(6) { height: 100rpx; animation-delay: 0.5s; } &:nth-child(2) {
&:nth-child(7) { height: 80rpx; animation-delay: 0.6s; } height: 90rpx;
&:nth-child(8) { height: 40rpx; animation-delay: 0.7s; } animation-delay: 0.1s;
}
&:nth-child(3) {
height: 50rpx;
animation-delay: 0.2s;
}
&:nth-child(4) {
height: 120rpx;
animation-delay: 0.3s;
}
&:nth-child(5) {
height: 70rpx;
animation-delay: 0.4s;
}
&:nth-child(6) {
height: 100rpx;
animation-delay: 0.5s;
}
&:nth-child(7) {
height: 80rpx;
animation-delay: 0.6s;
}
&:nth-child(8) {
height: 40rpx;
animation-delay: 0.7s;
}
} }
} }
@@ -2645,10 +2648,13 @@ $radius-lg: 36rpx;
/* 动画定义 */ /* 动画定义 */
@keyframes dotPulse { @keyframes dotPulse {
0%, 100% {
0%,
100% {
opacity: 0.4; opacity: 0.4;
transform: scale(0.8); transform: scale(0.8);
} }
50% { 50% {
opacity: 1; opacity: 1;
transform: scale(1.2); transform: scale(1.2);
@@ -2656,10 +2662,13 @@ $radius-lg: 36rpx;
} }
@keyframes wave { @keyframes wave {
0%, 100% {
0%,
100% {
transform: scaleY(0.5); transform: scaleY(0.5);
opacity: 0.7; opacity: 0.7;
} }
50% { 50% {
transform: scaleY(1); transform: scaleY(1);
opacity: 1; opacity: 1;
@@ -2670,6 +2679,7 @@ $radius-lg: 36rpx;
0% { 0% {
left: -100%; left: -100%;
} }
100% { 100% {
left: 200%; left: 200%;
} }
@@ -2679,9 +2689,11 @@ $radius-lg: 36rpx;
0% { 0% {
background-position: 0% 50%; background-position: 0% 50%;
} }
50% { 50% {
background-position: 100% 50%; background-position: 100% 50%;
} }
100% { 100% {
background-position: 0% 50%; background-position: 0% 50%;
} }
@@ -2718,7 +2730,8 @@ $radius-lg: 36rpx;
.message-item { .message-item {
margin-bottom: 24rpx; margin-bottom: 24rpx;
.user-message, .ai-message { .user-message,
.ai-message {
.avatar { .avatar {
width: 72rpx; width: 72rpx;
height: 72rpx; height: 72rpx;

View File

@@ -52,7 +52,12 @@ import { mapGetters, mapMutations } from 'vuex'
import navigationHelper from '@/common/js/navigation'; import navigationHelper from '@/common/js/navigation';
import { EventSafety } from '@/common/js/event-safety'; import { EventSafety } from '@/common/js/event-safety';
import aiChatMessage from './ai-chat-message.vue';
export default { export default {
components: {
aiChatMessage,
},
data() { data() {
return { return {
initialMessages: [ initialMessages: [
@@ -86,7 +91,6 @@ export default {
safeEventHandlers: new Map() safeEventHandlers: new Map()
} }
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
'globalAIKefuConfig' 'globalAIKefuConfig'
@@ -142,9 +146,6 @@ export default {
this.cleanup() this.cleanup()
}, },
methods: { methods: {
// ========== 安全事件处理 ========== // ========== 安全事件处理 ==========
setupSafeEventListeners() { setupSafeEventListeners() {