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">
@@ -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>
@@ -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; color: white !important;
border-radius: 16rpx 16rpx 4rpx 16rpx; /* 右对齐气泡尖角适配 */ border-radius: 16rpx 16rpx 4rpx 16rpx;
/* 右对齐气泡尖角适配 */
box-shadow: 0 8rpx 20rpx rgba(247, 131, 172, 0.3) !important; box-shadow: 0 8rpx 20rpx rgba(247, 131, 172, 0.3) !important;
border: none !important; border: none !important;
/* ✅ 关键:允许内容撑开高度 */ /* ✅ 关键:允许内容撑开高度 */
min-height: auto; min-height: auto;
height: auto; height: auto;
padding: 24rpx 32rpx; /* 保留内边距 */ padding: 24rpx 32rpx;
display: inline-block; /* 让宽度也随内容收缩(可选) */ /* 保留内边距 */
max-width: 80%; /* 防止过宽 */ display: inline-block;
/* 让宽度也随内容收缩(可选) */
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;
@@ -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,9 +1915,11 @@ $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; color: #2c3e50 !important;
border-radius: 16rpx 16rpx 16rpx 4rpx; /* 左对齐气泡尖角适配 */ border-radius: 16rpx 16rpx 16rpx 4rpx;
/* 左对齐气泡尖角适配 */
box-shadow: 0 8rpx 20rpx rgba(191, 219, 254, 0.4) !important; box-shadow: 0 8rpx 20rpx rgba(191, 219, 254, 0.4) !important;
border: 1rpx solid #dbeafe !important; border: 1rpx solid #dbeafe !important;
min-height: auto; min-height: auto;
@@ -1965,6 +1930,7 @@ $radius-lg: 36rpx;
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,9 +2039,12 @@ $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;
/* 强制文字单行横向显示 */
line-height: 1;
/* 重置行高,避免文字垂直偏移 */
top: -10px; top: -10px;
&.disabled { &.disabled {
@@ -2098,8 +2068,10 @@ $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,
@@ -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() {