Compare commits

...

14 Commits

9 changed files with 685 additions and 119 deletions

View File

@@ -82,10 +82,10 @@
<block v-if="goodsList.length">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}
</view>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}
</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
@@ -120,6 +120,7 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<!-- <view class="right-wrap" v-if="value.template == 2 || value.template == 4">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
@@ -174,10 +175,10 @@
:data-template="value.template">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"
:lazy-load="true" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"
:lazy-load="true" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
@@ -212,6 +213,7 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="right-wrap" v-if="value.template == 2">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
@@ -718,6 +720,25 @@ export default {
}
}
},
/**
* 发送商品信息到智能客服
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
const productInfo = {
goods_id: item.goods_id,
goods_name: item.goods_name,
price: this.showPrice(item),
image: this.goodsImg(item.goods_image),
category_id: item.category_id
}
uni.setStorageSync('selectedProduct', productInfo)
uni.navigateTo({
url: '/pages_tool/ai-chat/index'
})
},
diyRedirectTo(link) {
if (!link) return;
this.$util.diyRedirectTo(JSON.parse(link));
@@ -1384,4 +1405,6 @@ export default {
color: var(--main-color);
}
}
</style>

View File

@@ -53,9 +53,19 @@
<!-- 视频播放弹窗 -->
<uni-popup ref="videoPopup" type="center" style="background:transparent;width:100%;height:100%;">
<view class="video-container" style="position:fixed;top:30%;width:100%;left:0;">
<video class="adaptive-video" :autoPauseIfNavigate="true" :autoPauseIfOpenNative="true"
:autoplay="false" :enableAutoRotation="true" id="myVideo" :src="video_url" :controls="true"></video>
<view class="video-container" :style="popupVideoStyle">
<video
ref="popupVideoRef"
class="adaptive-video"
:autoPauseIfNavigate="true"
:autoPauseIfOpenNative="true"
:autoplay="false"
:enableAutoRotation="true"
id="myVideo"
:src="video_url"
:controls="true"
@loadedmetadata="onPopupVideoLoaded"
></video>
</view>
</uni-popup>
</view>
@@ -126,12 +136,38 @@ export default {
isIndicatorDots() {
return this.value.carousel.type != 'hide' &&
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
},
// 弹窗视频容器样式
popupVideoStyle() {
let style = 'position:fixed;width:100%;left:0;';
if (this.popupVideoRatio) {
// 根据视频比例设置位置
if (this.popupVideoRatio === 'portrait') {
// 竖屏视频:居中显示,使用9/16比例
style += 'top:50%;transform:translateY(-50%);';
style += 'aspect-ratio: 9 / 16;';
} else {
// 横屏视频:靠近顶部显示,使用16/9比例
style += 'top:20%;';
style += 'aspect-ratio: 16 / 9;';
}
} else {
// 默认:居中显示,使用竖屏比例
style += 'top:50%;transform:translateY(-50%);';
style += 'aspect-ratio: 9 / 16;';
}
return style;
}
},
methods: {
// 显示视频播放弹窗
showVideo(item) {
this.video_url = item.videoUrl;
// 重置视频比例为null,等待新的检测结果
this.popupVideoRatio = null;
this.$refs.videoPopup.open();
},
@@ -139,6 +175,39 @@ export default {
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
},
// 弹窗视频加载完成事件
onPopupVideoLoaded(event) {
this.$nextTick(() => {
let width = 0;
let height = 0;
// H5 端:获取真实视频宽高
// #ifdef H5
const video = this.$refs.popupVideoRef;
if (video) {
width = video.videoWidth || 0;
height = video.videoHeight || 0;
}
// #endif
// 微信小程序端:通过事件对象获取视频宽高
// #ifdef MP-WEIXIN
if (event && event.detail) {
width = event.detail.width || 0;
height = event.detail.height || 0;
}
// #endif
if (width && height) {
this.popupVideoRatio = width >= height ? 'landscape' : 'portrait';
console.log(`弹窗视频宽高比检测: ${width}x${height}, 比例: ${this.popupVideoRatio}`);
} else {
// 获取失败,默认竖屏
this.popupVideoRatio = 'portrait';
}
});
}
}
}

View File

@@ -1,7 +1,36 @@
<template>
<video data-component-name="diy-video" class="diy-video" :src="$util.img(value.videoUrl)"
:poster="$util.img(value.imageUrl)" :style="videoWarpCss" objectFit="cover"
@tap="handlerClick(value.videoUrl)"></video>
<view class="video-container" :style="containerStyle">
<!-- H5端使用原生video标签 -->
<!-- #ifdef H5 -->
<video
ref="videoRef"
data-component-name="diy-video"
class="diy-video h5-video"
:src="videoUrl"
:poster="posterUrl"
:style="videoWarpCss"
controls
preload="metadata"
@loadedmetadata="onVideoLoaded"
@click="handlerClick(videoUrl)"
></video>
<!-- #endif -->
<!-- 微信小程序端使用uni-app的video组件 -->
<!-- #ifdef MP-WEIXIN -->
<video
ref="videoRef"
data-component-name="diy-video"
class="diy-video mp-video"
:src="videoUrl"
:poster="posterUrl"
:style="videoWarpCss"
controls
@loadedmetadata="onVideoLoaded"
@error="onVideoError"
></video>
<!-- #endif -->
</view>
</template>
<script>
@@ -15,27 +44,160 @@ export default {
}
},
data() {
return {};
return {
videoRatio: null, // 视频比例:'landscape'(横屏)或 'portrait'(竖屏)
videoUrl: '',
posterUrl: ''
};
},
created() {
this.initVideoData();
},
created() { },
mixins: [DiyMinx],
watch: {
// 组件刷新监听
componentRefresh: function (nval) { }
value: {
immediate: true,
handler(newVal) {
if (newVal) {
this.initVideoData();
}
}
}
},
computed: {
videoWarpCss: function () {
var obj = '';
if (this.value.componentAngle == 'round') {
if (this.value && this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 动态计算容器比例
containerStyle() {
let style = '';
if (this.videoRatio === 'landscape') {
// 横屏视频:16/9比例
style += 'aspect-ratio: 16 / 9;';
} else if (this.videoRatio === 'portrait') {
// 竖屏视频:9/16比例
style += 'aspect-ratio: 9 / 16;';
} else {
// 未加载完成 → 默认使用竖屏比例
style += 'aspect-ratio: 9 / 16;';
}
return style;
}
},
methods: {
// 初始化视频数据
initVideoData() {
this.videoUrl = this.getVideoUrl();
this.posterUrl = this.getPosterUrl();
// H5端:预加载视频获取宽高
// #ifdef H5
this.preloadVideoForRatio();
// #endif
},
// 获取视频URL
getVideoUrl() {
if (!this.value || !this.value.videoUrl) return '';
const videoUrl = this.value.videoUrl;
// 如果是完整的http/https链接,直接使用
if (videoUrl.indexOf('http://') === 0 || videoUrl.indexOf('https://') === 0) {
return videoUrl;
}
// 否则使用$util.img()处理
return this.$util.img(videoUrl);
},
// 获取封面图URL
getPosterUrl() {
if (!this.value || !this.value.imageUrl) return '';
return this.$util.img(this.value.imageUrl);
},
// H5端:预加载视频获取宽高比例
preloadVideoForRatio() {
const videoUrl = this.videoUrl;
if (!videoUrl) return;
console.log('H5端预加载视频获取比例:', videoUrl);
const video = document.createElement('video');
video.preload = 'metadata';
video.src = videoUrl;
video.muted = true;
video.playsInline = true;
video.addEventListener('loadedmetadata', () => {
const width = video.videoWidth;
const height = video.videoHeight;
console.log('H5端预加载获取视频宽高:', width, height);
if (width && height) {
this.videoRatio = width >= height ? 'landscape' : 'portrait';
console.log('H5端视频比例判定:', this.videoRatio, width >= height ? '横屏' : '竖屏');
}
});
video.addEventListener('error', (e) => {
console.error('H5端预加载视频失败:', e);
});
// 触发加载
video.load();
},
// 视频加载完成事件(组件内video标签)
onVideoLoaded(event) {
console.log('视频组件 loadedmetadata 事件触发');
let width = 0;
let height = 0;
// #ifdef H5
if (event && event.target) {
width = event.target.videoWidth || 0;
height = event.target.videoHeight || 0;
console.log('H5端组件视频宽高:', width, height);
}
// #endif
// #ifdef MP-WEIXIN
if (event && event.detail) {
width = event.detail.width || 0;
height = event.detail.height || 0;
console.log('微信小程序端视频宽高:', width, height);
}
// #endif
if (width && height) {
this.videoRatio = width >= height ? 'landscape' : 'portrait';
console.log('视频比例最终判定:', this.videoRatio);
}
},
// 视频播放事件
onVideoPlay() {
console.log('视频开始播放');
},
// 视频暂停事件
onVideoPause() {
console.log('视频暂停');
},
// 视频错误事件
onVideoError(event) {
console.error('视频加载失败:', event);
},
async handlerClick(videoUrl) {
await this.__$emitEvent({
eventName: 'video-tap', data: videoUrl, promiseCallback: (event, handler, awaitedResult) => {
@@ -48,11 +210,32 @@ export default {
</script>
<style scoped>
video {
.video-container {
width: 100%;
position: relative;
overflow: hidden;
background-color: #000;
}
.diy-video>>>.uni-video-container {
background-color: transparent;
/* 基础样式 */
.diy-video {
width: 100%;
height: 100%;
display: block;
}
</style>
/* H5端样式 */
/* #ifdef H5 */
.h5-video {
object-fit: cover;
}
/* #endif */
/* 微信小程序端样式 */
/* #ifdef MP-WEIXIN */
.mp-video {
width: 100%;
height: 100%;
}
/* #endif */
</style>

View File

@@ -71,11 +71,14 @@
"scope.userLocation" : {
"desc" : "为了更好地为您提供服务"
},
"scope.writePhotosAlbum" : {
"desc" : "为了更好地为您提供服务"
},
"scope.userClipboard" : {
"desc" : "为了方便您复制信息"
},
"scope.camera" : {
"desc" : "用于拍照上传头像"
},
"scope.writePhotosAlbum" : {
"desc" : "用于从相册选择和保存头像"
}
},
"requiredPrivateInfos" : [ "chooseLocation", "getLocation", "chooseAddress" ],

View File

@@ -2,6 +2,11 @@
<view>
<view scroll-y="true" class="goods-detail" :class="isIphoneX ? 'active' : ''">
<view class="goods-container">
<!-- AI客服悬浮按钮 -->
<view class="ai-chat-float-btn" @tap.stop="$emit('aiChatClick')" v-if="goodsSkuDetail">
<text class="iconfont">🤖</text>
</view>
<!-- 弹幕 -->
<pengpai-fadein-out v-if="goodsSkuDetail.barrage_show && goodsSkuDetail.barrageData" ref="pengpai"
:duration="1600" :wait="1900" :top="200" :left="0" :radius="60" :loop="true"
@@ -481,4 +486,29 @@ export default {
<style lang="scss">
@import '@/common/css/goods_detail.scss';
</style>
<style scoped></style>
<style scoped>
.ai-chat-float-btn {
position: fixed;
right: 20rpx;
top: 100rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
z-index: 9999;
transition: all 0.3s ease;
}
.ai-chat-float-btn:active {
transform: scale(0.9);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.ai-chat-float-btn .iconfont {
font-size: 40rpx;
}
</style>

View File

@@ -6,7 +6,7 @@
</view> -->
<!-- #endif -->
<goods-detail-view :goodsSkuDetail="goodsSkuDetail" ref="goodsDetailView">
<goods-detail-view :goodsSkuDetail="goodsSkuDetail" ref="goodsDetailView" @aiChatClick="sendToAIChat(goodsSkuDetail)">
<!-- 价格区域 -->
<template v-slot:price>
<!-- 限时折扣 -->
@@ -531,7 +531,28 @@ export default {
goodsDetailView,
nsGoodsPromotion
},
mixins: [base, detail, scroll]
mixins: [base, detail, scroll],
methods: {
/**
* 发送商品信息到智能客服
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
const productInfo = {
goods_id: item.goods_id || '',
goods_name: item.goods_name || '',
price: item.discount_price || item.member_price || item.price || '',
image: item.sku_image || '',
category_id: item.category_id || ''
}
uni.setStorageSync('selectedProduct', productInfo)
uni.navigateTo({
url: '/pages_tool/ai-chat/index'
})
}
}
};
</script>
<style lang="scss">
@@ -640,4 +661,5 @@ export default {
height: 72rpx;
line-height: 72rpx;
}
</style>

View File

@@ -108,11 +108,13 @@
<view v-else-if="message.type === 'video'" class="video-content">
<view class="video-player">
<video
ref="videoRef"
:src="message.url"
:poster="message.cover"
:controls="true"
:autoplay="false"
class="video-element"
@loadedmetadata="onVideoLoaded(message, $event)"
@play="onVideoPlay(message)"
@pause="onVideoPause(message)">
</video>
@@ -137,20 +139,7 @@
</view>
</view>
<!-- 商品卡片 -->
<view v-else-if="message.type === 'product'" class="product-content">
<view class="product-card" @click="viewProduct(message)">
<view class="product-image">
<image :src="message.image" mode="aspectFill" />
</view>
<view class="product-info">
<text class="product-title">{{ message.title }}</text>
<text class="product-price color-base-text">¥{{ message.price }}</text>
<text class="product-desc">{{ message.description }}</text>
</view>
</view>
</view>
<!-- 加载中状态 -->
<view v-else-if="message.type === 'loading'" class="loading-content">
<view class="loading-dots">
@@ -182,10 +171,10 @@
<!-- 输入区域 -->
<view class="input-area">
<view class="input-tools">
<button class="tool-btn" @click="showMoreTools">
<button class="tool-btn" @click="showMoreTools" style="display: none;">
<text class="iconfont icon-plus">...</text>
</button>
<button class="tool-btn" @click="toggleVoiceInput">
<button class="tool-btn" @click="toggleVoiceInput" style="display: none;">
<text class="iconfont" :class="voiceInputing ? 'icon-stop' : 'icon-voice'">🎤</text>
</button>
</view>
@@ -280,7 +269,7 @@
</view>
<!-- 头像选择弹窗 -->
<view v-if="showAvatarSelector" class="avatar-selector-popup">
<view v-if="showAvatarSelector" class="avatar-selector-popup" style="pointer-events: auto;">
<view class="avatar-selector-mask" @click="closeAvatarSelector"></view>
<view class="avatar-selector-panel">
<view class="avatar-selector-header">
@@ -299,10 +288,6 @@
<text class="iconfont icon-album"></text>
<text>从相册选择</text>
</button>
<button class="avatar-select-action" @click="takeAvatarWithCamera">
<text class="iconfont icon-camera"></text>
<text>拍照上传</text>
</button>
<button class="avatar-select-action cancel" @click="closeAvatarSelector">
<text>取消</text>
</button>
@@ -414,22 +399,23 @@ export default {
// 默认头像路径
defaultAvatar: '/static/images/default-avatar.png',
// 头像选择弹窗控制
showAvatarSelector: false,
// 新增:用户昵称相关
localUserNickname: '', // 当前用户昵称
defaultNickname: '我', // 默认昵称
showNicknameEditor: false, // 昵称编辑弹窗控制
tempNickname: '', // 昵称编辑临时值
// Dify API相关
currentConversationId: null,
isAIServiceAvailable: true,
aiServiceError: null,
isLoadingHistory: false, // 新增:标记历史加载状态
shouldScrollToBottom: false,
scrollTimer: null,
lastScrollTop: 0,
historyOffset: 0,
isFetchingHistory: false
showAvatarSelector: false, // 头像选择弹窗控制
// 新增:用户昵称相关
localUserNickname: '', // 当前用户昵称
defaultNickname: '我', // 默认昵称
showNicknameEditor: false, // 昵称编辑弹窗控制
tempNickname: '', // 昵称编辑临时值
// Dify API相关
currentConversationId: null,
isAIServiceAvailable: true,
aiServiceError: null,
isLoadingHistory: false, // 新增:标记历史加载状态
shouldScrollToBottom: false,
scrollTimer: null,
lastScrollTop: 0,
historyOffset: 0,
isFetchingHistory: false
}
},
onShow() {
@@ -612,20 +598,67 @@ export default {
// 从相册选择头像
chooseAvatarFromAlbum() {
this.selectAvatar('album')
console.log('选择从相册选择头像')
this.selectAvatar()
},
// 拍照上传头像
takeAvatarWithCamera() {
this.selectAvatar('camera')
},
// 统一头像选择方法
selectAvatar(sourceType) {
// 统一头像选择方法 - 仅从相册选择
selectAvatar() {
console.log('开始选择头像')
// #ifdef H5
// H5环境下的处理 - 仅从相册选择
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.style.display = 'none'
// 监听文件选择
input.onchange = (e) => {
const file = e.target.files[0]
if (file) {
// 创建临时URL
const tempFilePath = URL.createObjectURL(file)
// 1. 本地预览更新
this.localUserAvatar = tempFilePath
// 2. 持久化缓存(支持页面刷新后保留)
const reader = new FileReader()
reader.onload = (e) => {
const base64 = e.target.result
uni.setStorageSync('user_avatar', base64)
}
reader.readAsDataURL(file)
// 3. 通知父组件(可选,用于同步到服务器等场景)
this.$emit('avatar-changed', tempFilePath)
// 4. 关闭弹窗
this.closeAvatarSelector()
// 5. 提示成功
uni.showToast({
title: '头像更换成功',
icon: 'success',
duration: 1500
})
} else {
// 用户取消选择
this.closeAvatarSelector()
}
}
// 触发点击
input.click()
// #endif
// #ifdef MP-WEIXIN
// 微信小程序环境下,使用uni.chooseImage - 仅从相册选择
uni.chooseImage({
count: 1, // 仅选择1张
sizeType: ['compressed'], // 压缩图片
sourceType: [sourceType], // 来源:相册/相机
sourceType: ['album'], // 仅从相册选择
success: (res) => {
const tempFilePath = res.tempFilePaths[0]
@@ -651,12 +684,14 @@ export default {
fail: (error) => {
console.error('选择头像失败:', error)
// 权限拒绝处理
if (error.errMsg.includes('deny')) {
if (error.errMsg && error.errMsg.includes('deny')) {
uni.showToast({
title: '请开启相册/相机权限',
title: '请开启相册权限',
icon: 'none',
duration: 2000
})
} else if (error.errMsg && error.errMsg.includes('cancel')) {
// 用户取消选择,不显示错误提示
} else {
uni.showToast({
title: '头像选择失败,请重试',
@@ -664,8 +699,11 @@ export default {
duration: 2000
})
}
// 关闭弹窗
this.closeAvatarSelector()
}
})
// #endif
},
// 新增:打开昵称编辑弹窗
@@ -839,33 +877,28 @@ export default {
},
(completeResult) => {
// 流结束回调
let finalContent = '';
let convId = '';
if (typeof completeResult === 'string') {
finalContent = completeResult;
} else {
finalContent = completeResult.content || '';
if (typeof completeResult !== 'string') {
convId = completeResult.conversation_id || '';
}
// 更新最终内容
// 标记流式结束
streamMessage.isStreaming = false;
streamMessage.content = finalContent;
// 添加操作按钮
streamMessage.actions = [
{ id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' }
];
// 保存会话 ID
if (convId) {
this.currentConversationId = convId;
aiService.setConversationId(convId);
this.saveConversationIdToLocal(convId);
}
// 触发事件
this.$emit('ai-response', streamMessage);
}
@@ -882,30 +915,28 @@ export default {
this.$forceUpdate();
},
(completeResult) => {
// 流结束回调
let finalContent = completeResult?.content || '';
let convId = completeResult?.conversation_id || '';
// 更新最终内容
streamMessage.isStreaming = false;
streamMessage.content = finalContent;
// 添加操作按钮
streamMessage.actions = [
{ id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' }
];
// 保存会话 ID
if (convId) {
this.currentConversationId = convId;
aiService.setConversationId(convId);
this.saveConversationIdToLocal(convId);
}
// 触发事件
this.$emit('ai-response', streamMessage);
}
// 流结束回调
let convId = completeResult?.conversation_id || '';
// 标记流式结束
streamMessage.isStreaming = false;
// 添加操作按钮
streamMessage.actions = [
{ id: 1, text: '有帮助', type: 'like' },
{ id: 2, text: '没帮助', type: 'dislike' }
];
// 保存会话 ID
if (convId) {
this.currentConversationId = convId;
aiService.setConversationId(convId);
this.saveConversationIdToLocal(convId);
}
// 触发事件
this.$emit('ai-response', streamMessage);
}
);
// #endif
} catch (error) {
@@ -1196,6 +1227,42 @@ export default {
}
},
// 视频加载完成事件
onVideoLoaded(message, event) {
this.$nextTick(() => {
let width = 0;
let height = 0;
// H5端:通过video元素获取真实视频宽高
// #ifdef H5
const video = this.$refs.videoRef;
if (video) {
width = video.videoWidth || 0;
height = video.videoHeight || 0;
}
// #endif
// 微信小程序端:通过事件对象获取视频宽高
// #ifdef MP-WEIXIN
if (event && event.detail) {
width = event.detail.width || 0;
height = event.detail.height || 0;
}
// #endif
if (width && height) {
// 判断视频方向:宽度 >= 高度为横屏,否则为竖屏
const videoRatio = width >= height ? 'landscape' : 'portrait';
console.log(`AI聊天视频宽高比检测: ${width}x${height}, 比例: ${videoRatio}`);
// 动态设置视频样式
// 这里可以添加逻辑来动态调整视频播放器的样式
// 例如:message.videoRatio = videoRatio;
// 然后在模板中根据message.videoRatio调整样式
}
});
},
// 视频播放
onVideoPlay(message) {
this.$emit('video-play', message)
@@ -1219,11 +1286,7 @@ export default {
this.$emit('link-open', message)
},
// 查看商品
viewProduct(message) {
this.$emit('product-view', message)
},
// 处理操作
handleAction(action, message) {
// 处理重试操作
@@ -1416,6 +1479,24 @@ export default {
this.$emit('location-selected', locationMessage)
}
})
},
// 清空聊天记录
clearMessages() {
// 清空消息数组
this.messages = []
// 重置消息 ID
this.messageId = 0
// 清除会话 ID
this.currentConversationId = null
aiService.clearConversationId()
this.clearConversationIdFromLocal()
// 重置历史偏移量
this.historyOffset = 0
// 触发清空事件
this.$emit('messages-cleared')
}
}
}
@@ -2778,6 +2859,132 @@ $radius-lg: 36rpx;
}
}
/* 商品选择弹窗样式 */
.product-selector-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
.product-selector-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.product-selector-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 800rpx;
max-height: 80vh;
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
.product-selector-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #e8edf3;
text {
font-size: 32rpx;
font-weight: bold;
color: #4e5969;
}
.close-btn {
padding: 0;
background: none;
.iconfont {
font-size: 32rpx;
color: #8492a6;
}
}
}
.product-selector-content {
display: flex;
height: 60vh;
.category-list {
width: 200rpx;
border-right: 1rpx solid #e8edf3;
.category-item {
padding: 20rpx;
text-align: center;
border-bottom: 1rpx solid #e8edf3;
&.active {
background-color: #f5f7fa;
font-weight: bold;
color: #6c8ebf;
}
}
}
.product-list {
flex: 1;
padding: 20rpx;
.loading {
text-align: center;
padding: 40rpx;
color: #8492a6;
}
.empty {
text-align: center;
padding: 40rpx;
color: #8492a6;
}
.product-item {
display: flex;
padding: 15rpx;
border-bottom: 1rpx solid #e8edf3;
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
margin-right: 15rpx;
}
.product-info {
flex: 1;
.product-name {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 10rpx;
color: #4e5969;
}
.product-price {
color: #f56c6c;
font-weight: bold;
}
}
}
}
}
}
}
/* 响应式适配 */
@media (max-width: 750rpx) {
.ai-chat-container {

View File

@@ -257,6 +257,20 @@ export default {
// ========== 初始化页面 ==========
initChat() {
console.log('AI聊天页面初始化');
// 检查是否有从商品分类传递过来的商品信息
const selectedProduct = uni.getStorageSync('selectedProduct')
if (selectedProduct) {
console.log('检测到选择的商品:', selectedProduct)
// 清除存储的商品信息,避免重复发送
uni.removeStorageSync('selectedProduct')
// 延迟发送商品信息,确保聊天组件已经初始化
setTimeout(() => {
this.sendProductToAI(selectedProduct)
}, 500)
}
},
async initNavigation() {
@@ -482,6 +496,20 @@ export default {
onVideoPause(message) {
console.log('视频暂停:', message);
},
sendProductToAI(product) {
if (!this.$refs.chat) {
console.error('聊天组件未初始化')
return
}
const productContent = `我想了解以下商品的详细信息:\n商品名称:${product.goods_name}\n价格:¥${product.price}\n商品ID:${product.goods_id}`
console.log('商品信息已发送到AI客服:', productContent)
// 使用sendMessageWithContent方法发送商品信息
this.$refs.chat.sendMessageWithContent(productContent)
},
onLinkOpen(message) {
uni.showModal({

View File

@@ -134,8 +134,9 @@ async function main() {
// 2) 创建 ZIP 文件(如果未指定 --no-zip)
if (!options.noZip) {
const cwd = path.join(__dirname, '..');
const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
const buildDir = options.mode === 'production' ? 'build' : 'dev';
const sourceDir = path.join(cwd, 'unpackage', 'dist', buildDir, 'mp-weixin');
const destDir = path.join(cwd, 'unpackage', 'dist', buildDir);
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
console.log(`ZIP 文件路径: ${zipFilePath}`);