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

View File

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

View File

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