Compare commits

...

77 Commits

Author SHA1 Message Date
cf411407dd chore:名片可以分享了 2026-04-28 17:35:40 +08:00
86e4be302b chore:商品分类可以分享了 2026-04-28 11:31:32 +08:00
2fd06796e1 chore:加了输入框扩大按钮 2026-04-28 09:59:52 +08:00
60c51e551a chore:修改表情位置 2026-04-28 09:12:15 +08:00
d0d3ac47c6 chore:修改表情位置 2026-04-28 08:59:34 +08:00
af677f6565 chore:资源详情改为商品详情 2026-04-25 16:53:40 +08:00
f889efeb16 chore: 2026-04-25 16:45:41 +08:00
d026bb6180 chore:增加一个后台控制首页弹出的隐私权限 2026-04-25 16:15:17 +08:00
82b856f5d9 chore:表情的布局改为横板 2026-04-25 14:36:31 +08:00
7f33b6948a chore:min-height: 300px; 替换成 min-height: 0px; 2026-04-25 14:26:48 +08:00
3489b89a0b chore:智能客服加了表情 2026-04-25 14:15:03 +08:00
dc4f2769f9 chore:资源详情里的立即咨询可以跳转到智能客服 2026-04-25 11:58:03 +08:00
5cf87e4d56 chore:资源详情的背景设改为绿色 2026-04-25 11:27:32 +08:00
c9ca63cff5 chore:联系我们改为立即质询 2026-04-25 11:09:47 +08:00
a6072f4895 chore: 2026-04-25 10:22:27 +08:00
38947ddc62 chore:立即咨询点击会弹出对话框 2026-04-25 10:09:38 +08:00
cfe83ada38 chore:点击商品分类中的立即咨询不要跳转到智能客服,跳转到资源详情页面 2026-04-25 09:59:45 +08:00
1e9e0ffea8 chore:资源详情智能客服按钮同步智能客服 2026-04-25 09:50:45 +08:00
28acdfb621 chore:红色背景收一些 2026-04-25 09:29:23 +08:00
ad9f1c495b chore:联系我们改为立即质询 2026-04-25 09:22:33 +08:00
7c2a7468e7 chore:调整红色背景 2026-04-25 09:03:13 +08:00
b2051dcf92 chore:立即质询背景改红色 2026-04-25 08:48:06 +08:00
e3c86de84a chore:删除红色的立即质询 2026-04-25 08:42:03 +08:00
6e0eb5d4f3 chore:询底价改为立即咨询 2026-04-25 08:38:15 +08:00
9d186ff094 chore:发送字体按钮调整变大 2026-04-24 16:49:01 +08:00
8611ec4dc3 chore:发送按钮改为微信的发送按钮 2026-04-24 16:38:29 +08:00
89bfd94a07 chore:上传image-config.js 2026-04-24 15:46:42 +08:00
5cf8b7b467 chore:发送按钮调整变大 2026-04-23 15:24:32 +08:00
2cc0162435 chore:输入框不被遮挡了 2026-04-22 11:59:58 +08:00
d8e75a0c9e chore: 2026-04-21 14:15:46 +08:00
8af3c0ccb9 chore:微信小程序真机版本可以运行,微信小程序客服图标显示正常 2026-04-20 17:50:37 +08:00
d8b78168a8 chore:改了头像的地址 2026-04-20 15:44:58 +08:00
7675e8e04d chore: 2026-04-20 09:52:27 +08:00
88d05048a6 chore:长按图片可以识别图中二维码 2026-04-20 09:36:08 +08:00
28a92c0635 chore:ai-service.js 的代码url: 'wss://dev.aigc-quickapp.com/ws/aikefu',被替换 2026-04-18 11:36:18 +08:00
b5a47b8b4e chore:聊天页面的我和时间换位置 2026-04-17 16:03:30 +08:00
4f99e8b5f2 chore:H5的发送按钮变成正圆形 2026-04-17 15:18:31 +08:00
87fc49146c chore:时间居中 2026-04-17 14:19:43 +08:00
826fcfb024 chore:时间和智能助手在同一水平线上 2026-04-17 11:49:33 +08:00
7174d87b79 chore:用户的气泡中商品名称,商品ID,价格都左对齐 2026-04-17 10:13:07 +08:00
4bc747cd9c chore:增加了用户默认头像,自定义头像 2026-04-17 09:48:46 +08:00
e046868665 chore:用户点击自己头像可以自定义头像 2026-04-17 09:21:10 +08:00
19c3ec4f9f chore:小程序输入框向下移动 2026-04-17 08:44:01 +08:00
ffbc15929d chore:智能客服的历史时间回复正常 2026-04-16 14:03:37 +08:00
524280d816 chore:智能助手加了时间 2026-04-16 11:00:50 +08:00
6e736e0733 chore:资源详情页面的智能客服头像变为圆形 2026-04-16 10:02:50 +08:00
4891566793 chore:智能客服改了头像 2026-04-16 09:34:56 +08:00
b02f5fbc21 chore:智能客服换了头像 2026-04-15 17:46:06 +08:00
4f5a300afa chore:H5的智能客服聊天页面上面导航栏自定义的删除 2026-04-15 17:18:22 +08:00
aae6e76797 chore:智能客服聊天不乱窜了 2026-04-15 10:23:43 +08:00
3162d6a9f2 chore:H5的智能客服聊天正常 2026-04-15 09:08:10 +08:00
8010ac45a6 chore:智能客服图标换了 2026-04-14 17:29:17 +08:00
baebba4cef chore:导航栏删除在线客服 2026-04-14 16:30:42 +08:00
1be7155fe8 chore:智能客服的头像换了 2026-04-14 09:42:09 +08:00
611c0b930b chore:删除喜欢不喜欢有帮助没帮助 2026-04-13 17:29:13 +08:00
ec1903519d chore:用户提问和客服回答可以看见了 2026-04-13 09:30:08 +08:00
b1953a020d chore:喜欢不喜欢和👍水平 2026-04-10 15:10:41 +08:00
3ce26322e0 chore: 2026-04-09 16:04:11 +08:00
588ec60948 chore:发送按钮向上调动 2026-04-08 10:10:47 +08:00
962ea124fa chore:退出聊天界面后,在进入聊天页面后没有多余的气泡 2026-04-08 09:14:57 +08:00
0ca0fe3b0d chore:修改了客服样式 2026-04-07 15:21:29 +08:00
cef2b6e02e chore:客服回答不重复 2026-04-07 15:10:26 +08:00
e018f9a8b9 chore:修改H5商品价格样式 2026-03-31 09:38:16 +08:00
def8a6cf6f chore:横竖屏 2026-03-30 14:49:24 +08:00
857f295923 chore:H5和小程序都可以自己分辨横竖屏 2026-03-28 17:38:26 +08:00
50ee885b74 chore:微信小程序实现竖屏是竖屏,横屏是横屏 2026-03-25 14:44:25 +08:00
410f248510 chore:H5视频改比例 2026-03-25 09:39:48 +08:00
ba5fcfaab1 chore:微信小程序的视频改为竖屏播放 2026-03-24 16:43:00 +08:00
5a46bb8d69 chore:智能客服回答不重复 2026-03-19 14:26:32 +08:00
e33ad16d10 chore:商品详情页面右上角有跳转到智能客服 2026-03-17 09:53:24 +08:00
d8ba7ace0f chore:跳转智能客服的更多详情按钮放到商品页面里面 2026-03-16 17:21:33 +08:00
875072bb68 chore:商品可以分享给智能客服进行解答 2026-03-14 10:53:55 +08:00
0912b9b031 chore:智能客服加了商品的图标 2026-03-13 16:41:00 +08:00
7ffe4acc0f chore:隐藏了更多功能,录音图标 2026-03-13 13:57:24 +08:00
4bcf0cbd2d chore:可以正常清空聊天记录 2026-03-12 18:29:11 +08:00
fdf85dfec7 chore:更换头像正常 2026-03-12 18:25:12 +08:00
1d62b37060 chore:头像功能从相册选择功能正常了 2026-03-12 15:09:27 +08:00
33 changed files with 2262 additions and 637 deletions

View File

@@ -41,7 +41,19 @@ const localDevConfig = ({
'local-2': { // 测试平台
uniacid: 2,
domain: 'http://localhost:8050/',
},
})['1']; // 选择要使用的环境配置
},
'100': {
uniacid: 100,
domain: 'https://5g.aigc-quickapp.com/',
},
'1': {
uniacid: 1,
domain: 'https://5g.aigc-quickapp.com/',
},
'115': {
uniacid: 115,
domain: 'https://5g.aigc-quickapp.com/',
},
})['115']; // 选择要使用的环境配置
export default localDevConfig;

View File

@@ -63,10 +63,14 @@ export default {
async sendStreamMessage(message, onChunk, onComplete) {
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
const socketTask = wx.connectSocket({
url: 'wss://dev.aigc-quickapp.com/ws/aikefu',
header: {}
});
const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/);
const domain = match ? match[1] : '';
const wssUrl = `wss://${domain}/ws/aikefu`;
console.log(`wssUrl = ${wssUrl}`);
const socketTask = wx.connectSocket({
url: wssUrl,
header: {}
});
let content = '';
let conversationId = '';
@@ -110,20 +114,20 @@ export default {
}
// 处理流式消息块
else if (data.type === 'message' || data.event === 'message') {
else if (data.event === 'message') {
const text = data.answer || data.content || data.text || '';
content += text;
if (onChunk) onChunk(text);
}
// 处理流结束
else if (data.event === 'message_end' || data.type === 'message_end') {
else if (data.event === 'message_end') {
conversationId = data.conversation_id || '';
if (conversationId) {
this.setConversationId(conversationId);
}
if (onComplete) {
onComplete({ content, conversation_id: conversationId });
onComplete({ content, conversation_id: conversationId });
}
resolve({ content, conversation_id: conversationId });
socketTask.close();

View File

@@ -2,6 +2,8 @@
* 客服统一处理服务
* 整合各种客服方式,提供统一的调用接口
*/
import IMAGE_CONFIG from './image-config.js';
export class CustomerService {
constructor(vueInstance, externalConfig = null) {
this.vm = vueInstance;
@@ -471,13 +473,11 @@ export class CustomerService {
const shopInfo = this.vm.shopInfo || {};
const config = this.getPlatformConfig();
if (config?.type === 'aikefu') {
return shopInfo.aiAgentimg || '';
} else if (config?.type === 'wxwork' || config?.type === 'qyweixin') {
return shopInfo.aiAgentimg || '';
} else if (config?.type === 'third') {
return '';
// 优先使用指定的智能客服头像
if (config?.type === 'aikefu' || config?.type === 'wxwork' || config?.type === 'qyweixin') {
return this.vm.$util.img(IMAGE_CONFIG.aiAvatar);
}
return shopInfo.kefuimg || this.vm.$util?.getDefaultImage()?.kefu || '';
}

15
common/js/image-config.js Normal file
View File

@@ -0,0 +1,15 @@
/**
* 图片配置文件
* 将大图片上传到服务器后,请在此处配置网络URL
*/
// 替换为你上传后的实际图片URL
const IMAGE_CONFIG = {
// AI客服头像
aiAvatar: 'public/static/img/s-agent.jpg',
// 用户默认头像
userAvatar: 'public/static/img/s-user.jpg'
};
export default IMAGE_CONFIG;

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)" :show-menu-by-longpress="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>
@@ -99,9 +99,6 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
<block v-else>
<text class="unit price-style small">{{ $lang('Make') ? $lang('Make') : '询底价' }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
@@ -120,6 +117,12 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @tap.stop="sendToAIChat(item)">
立即咨询
</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,28 +177,24 @@
: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" :show-menu-by-longpress="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>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">¥</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
<block v-else>
<text class="unit price-style small">{{ $lang('Make') ? $lang('Make') : '询底价' }}</text>
</block>
</view>
<block v-if="item.isinformation == 0">
<text class="unit price-style small">¥</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" />
@@ -212,6 +211,12 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @tap.stop="sendToAIChat(item)">
立即咨询
</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 +723,15 @@ export default {
}
}
},
/**
* 跳转到商品详情
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
diyRedirectTo(link) {
if (!link) return;
this.$util.diyRedirectTo(JSON.parse(link));
@@ -1384,4 +1398,25 @@ export default {
color: var(--main-color);
}
}
.ai-chat-btn-wrap {
display: flex;
justify-content: flex-end;
margin-top: auto;
padding-top: 10rpx;
}
.ai-chat-btn {
background-color: #ff0000;
color: #fff;
padding: 12rpx 30rpx;
border-radius: 40rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1.2;
text-align: center;
white-space: nowrap;
display: inline-block;
}
</style>

View File

@@ -8,7 +8,8 @@
<image class="goods-img"
:src="$util.img(item.goods_image, { size: goodsValue.template == 'large-mode' ? 'big' : 'mid' })"
mode="widthFix" @error="imgError(index)"
:style="{ borderRadius: goodsValue.imgAroundRadius * 2 + 'rpx' }" />
:style="{ borderRadius: goodsValue.imgAroundRadius * 2 + 'rpx' }"
:show-menu-by-longpress="true" />
<view class="sell-out" v-if="item.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
@@ -117,7 +118,7 @@
<view class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)"
:lazy-load="true" />
:lazy-load="true" :show-menu-by-longpress="true" />
<view class="sell-out" v-if="item.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
@@ -184,7 +185,7 @@
<view class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image, { size: 'mid' })" mode="widthFix"
@error="imgError(dataIndex)" :lazy-load="true" />
@error="imgError(dataIndex)" :lazy-load="true" :show-menu-by-longpress="true" />
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>

View File

@@ -17,7 +17,7 @@
<div class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image, { size: 'mid' })" mode="widthFix"
@error="imgError(index, dataIndex)" :lazy-load="true" />
@error="imgError(index, dataIndex)" :lazy-load="true" :show-menu-by-longpress="true" />
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>

View File

@@ -913,7 +913,7 @@ export default {
// ===== 主容器样式 =====
.tab-diy-component-preview-container {
width: 100%;
min-height: 300px;
min-height: 0px;
display: flex;
border: 1px solid #e0e0e0;
border-radius: 4px;

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

@@ -18,7 +18,6 @@
v-if="fixBtnShow && hasValidKefuConfig && isWeappOfficialKefu"
class="btn-item common-bg"
open-type="contact"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<text class="icox icox-kefu" v-if="!kefuimg"></text>
</button>
@@ -28,8 +27,13 @@
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && !showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<image
v-if="currentKefuImg"
:src="currentKefuImg"
mode="aspectFill"
class="kefu-img"
></image>
<text class="ai-icon" v-if="isAIKefu && !currentKefuImg">🤖</text>
<text class="ai-icon" v-if="isWxworkKefu && !currentKefuImg">💬</text>
<text class="ai-icon" v-if="!isAIKefu && !isWxworkKefu && !currentKefuImg">👤</text>
@@ -40,8 +44,13 @@
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<image
v-if="currentKefuImg"
:src="currentKefuImg"
mode="aspectFill"
class="kefu-img"
></image>
<text class="ai-icon" v-if="isAIKefu && !currentKefuImg">🤖</text>
<text class="ai-icon" v-if="isWxworkKefu && !currentKefuImg">💬</text>
<text class="ai-icon" v-if="!isAIKefu && !isWxworkKefu && !currentKefuImg">👤</text>
@@ -52,7 +61,6 @@
v-if="fixBtnShow && hasValidKefuConfig && showWeappSystemKefu"
class="btn-item common-bg"
open-type="contact"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<text class="icox icox-kefu" v-if="!kefuimg"></text>
</button>
@@ -304,6 +312,13 @@ export default {
font-weight: bold;
}
.kefu-img {
width: 100%;
height: 100%;
display: block;
border-radius: 50%;
}
.ai-icon {
font-size: 40rpx;
width: 100%;

View File

@@ -53,8 +53,15 @@ export default {
}
},
mounted() {
this.listenPrivacySettingChange()
this.curPageShow()
// 检查配置,决定是否显示隐私协议
const shopInfo = uni.getStorageSync('shopInfo');
if (shopInfo?.ischina == 1 && shopInfo?.index_login_auth == 1) {
this.listenPrivacySettingChange()
this.curPageShow()
} else {
// 如果不需要显示隐私协议,直接触发同意事件
this.$emit('agree');
}
},
created() {
try {
@@ -152,28 +159,62 @@ export default {
},
// 主动查询用户隐私政策授权状态
proactive() {
// 检查配置,决定是否显示隐私协议
const shopInfo = uni.getStorageSync('shopInfo');
if (!(shopInfo?.ischina == 1 && shopInfo?.index_login_auth == 1)) {
// 如果不需要显示隐私协议,直接触发同意事件
this.$emit('agree');
return;
}
// #ifdef MP-WEIXIN
if (wx.getPrivacySetting) {
wx.getPrivacySetting({
success: (res) => {
// console.log(res)
if (res.needAuthorization) {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (!privacyAgreed) {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
} else {
this.$emit('agree')
}
} else {
this.$emit('agree')
}
},
});
} else {
this.$emit('agree')
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
}
// #endif
// #ifdef QUICKAPP-WEBVIEW
this.getPrivacySettingByQuickApp();
// #endif
// #ifdef H5 || WEB
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
// #endif
},
//初始化监听程序
curPageShow() {
@@ -261,6 +302,8 @@ export default {
this.privacyResolves.clear()
//关闭弹窗
this.disPopUp()
// 保存用户授权状态到本地存储,无论同意还是不同意,都不再重复弹出
uni.setStorageSync('privacyAgreed', true);
//退出小程序
uni.showModal({
content: '未同意隐私协议,无法使用相关功能',
@@ -281,10 +324,8 @@ export default {
//关闭弹窗
this.disPopUp()
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
// 保存用户授权状态到本地存储,用于快应用Webview组件中的H5方式处理
// 保存用户授权状态到本地存储,用于所有环境
uni.setStorageSync('privacyAgreed', true);
// #endif
this.$emit('agree')
},
//打开弹窗

View File

@@ -3,5 +3,5 @@ export const lang = {
title: '商品分类',
search:'商品搜索',
seeMore : '查看更多',
Make: "询底价"
Make: "立即咨询"
}

View File

@@ -2,7 +2,7 @@ export const lang = {
//title为每个页面的标题
title: '',
select: '选择',
details:'资源详情',
details:'商品详情',
params: '参数',
service: '商品服务',
allGoods: '全部商品',
@@ -11,7 +11,7 @@ export const lang = {
home:'首页',
cart:'购物车',
leave:'立即留言',
make:'联系我们',
make:'立即咨询',
send:'配送',
express:'快递发货',
@@ -22,7 +22,7 @@ export const lang = {
status:'该商品已下架',
sellout:'库存不足',
max:'已达最大限购数量',
makebtn:'价格电议',
makebtn:'立即咨询',
sales:'销量',
stock:'库存',
kefu:'客服',

View File

@@ -6,6 +6,6 @@ export const lang = {
Price:'价格',
Filter:'筛选',
Search:'请输入您要搜索的商品',
Make:'立即询底价',
Make:'立即咨询',
}

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" ],
@@ -104,7 +107,7 @@
},
"router" : {
"mode" : "history",
"base" : "/hwappx/common/"
"base" : "/hwappx/test/"
},
"title" : "",
"devServer" : {

View File

@@ -43,7 +43,7 @@
{
"path": "detail",
"style": {
"navigationBarTitleText": "资源详情"
"navigationBarTitleText": "商品详情"
}
},
{

View File

@@ -3,7 +3,8 @@ export default {
return {
minScrollTop: 100, // 设置回到顶端按钮显示要求,最小页面滚动距离
wechatQrcode: '', // 公众号二维码
diyRoute: '/pages/index/index'
diyRoute: '/pages/index/index',
shopInfo: null // 店铺信息
};
},
onLoad(option) {
@@ -15,10 +16,15 @@ export default {
// #endif
this.name = option.name || 'DIY_VIEW_INDEX'; // 根据店铺运营模式,打开平台(shop)或者连锁门店(stroe)页面,接口会自行判断
// 获取店铺信息
this.shopInfo = uni.getStorageSync('shopInfo');
},
onShow() {
// 获取关注公众号二维码
this.getFollowQrcode();
// 重新获取店铺信息
this.shopInfo = uni.getStorageSync('shopInfo');
},
mounted() {
// 监听图片、文字、商品列表等组件的点击事件
@@ -59,6 +65,11 @@ export default {
officialAccountsClose() {
this.$refs.officialAccountsPopup.close();
},
// 判断是否需要登录授权
_needLoginAuth() {
// 检查 index_login_auth 都开启时才需要登录授权
return this.shopInfo?.index_login_auth == 1;
},
// 统一处理点击及触摸事件
_handleDiyGroupInteractionEvent(payload) {
// #ifdef H5
@@ -66,6 +77,11 @@ export default {
return true;
// #endif
// 如果不需要登录授权,直接返回 true 继续传播
if (!this._needLoginAuth()) {
return true;
}
// 如果已登录,直接返回 true 继续传播
if (this.storeToken) {
return true;

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="showAIChatBtn">
<image :src="aiAvatarUrl" mode="aspectFill" class="ai-avatar-icon" />
</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"
@@ -17,7 +22,7 @@
:item-id="'goods_id_' + index">
<view class="item" @click="previewMedia(index)">
<image :src="$util.img(item, { size: 'big' })" @error="swiperImageError(index)"
mode="aspectFit" />
mode="aspectFit" :show-menu-by-longpress="true" />
</view>
</swiper-item>
</swiper>
@@ -458,6 +463,8 @@
// 商品详情视图
import scroll from '@/common/js/scroll-view.js';
import detail from './detail.js';
import IMAGE_CONFIG from '@/common/js/image-config.js';
import { createCustomerService } from '@/common/js/customer-service.js';
import pengpaiFadeinOut from '../pengpai-fadein-out/pengpai-fadein-out.vue';
@@ -474,6 +481,15 @@ export default {
components: {
pengpaiFadeinOut
},
computed: {
aiAvatarUrl() {
return this.$util.img(IMAGE_CONFIG.aiAvatar);
},
showAIChatBtn() {
const customerService = createCustomerService(this);
return customerService.hasValidKefuConfig() && customerService.isAIKefu();
}
},
mixins: [scroll, detail]
};
</script>
@@ -481,4 +497,36 @@ 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;
overflow: hidden;
}
.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;
}
.ai-chat-float-btn .ai-avatar-icon {
width: 100%;
height: 100%;
border-radius: 50%;
}
</style>

View File

@@ -26,7 +26,8 @@
export default {
data() {
return {
diyData: null
diyData: null,
mpShareData: null //小程序分享数据
};
},
onLoad() {
@@ -38,6 +39,25 @@ export default {
onShow() {
this.$util.hideHomeButton();
if (this.$refs.category) this.$refs.category[0].pageShow();
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_goods/category').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
},
timeLine: {
title: '发现超多好物,快来看看',
query: '/pages_goods/category'
}
};
});
// #endif
},
onUnload() {
if (!this.storeToken && this.$refs.login) this.$refs.login.cancelCompleteInfo();
@@ -69,6 +89,24 @@ export default {
onPullDownRefresh() {
uni.hideTabBar();
this.getDiyInfo();
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '发现超多好物,快来看看',
query: '/pages_goods/category'
};
},
onAddToFavorites() {
return {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
};
}
};
</script>

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>
<!-- 限时折扣 -->
@@ -77,7 +77,6 @@
</block>
<block v-else>
<template>
<text class="price price-font" style="font-weight: 600;">{{$lang('makebtn')}}</text>
</template>
</block>
</view>
@@ -461,11 +460,12 @@
<ns-goods-action-icon :text="$lang('follow')" :icon="whetherCollection == 1 ? 'icon-likefill color-base-text' : 'icon-guanzhu'" @click="editCollection" />
<!-- #endif -->
<block v-if="goodsSkuDetail.isinformation >= 1">
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 1" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="phoneClick(goodsSkuDetail.goods_mobile)" :text="$lang('make')" :disabled="false" />
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="showinformation" :text="$lang('leave')" :disabled="false" />
<!-- <ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="buyNow" text="商品留言" :disabled="false" /> -->
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="showinformation" :text="$lang('leave')" :disabled="false" />
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 3" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="goToAIChat" text="智能客服" :disabled="false" />
<!-- <ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="buyNow" text="商品留言" :disabled="false" /> -->
</block>
<block v-else>
<block v-if="goodsSkuDetail.stock == 0 && !goodsSkuDetail.sku_spec_format">
@@ -515,6 +515,19 @@
<!-- 隐私弹窗 -->
<privacy-popup ref="privacyPopup"></privacy-popup>
<!-- 联系电话弹窗 -->
<uni-popup ref="contactPopup" type="center">
<view class="contact-popup">
<view class="contact-content">
<text class="contact-text">可在手机端拨打 021-51815181</text>
</view>
<view class="contact-buttons">
<button class="contact-btn cancel-btn" @click="closeContactPopup">取消</button>
<button class="contact-btn copy-btn" @click="copyPhoneNumber">复制到剪切板</button>
</view>
</view>
</uni-popup>
</view>
</template>
@@ -531,9 +544,61 @@ export default {
goodsDetailView,
nsGoodsPromotion
},
mixins: [base, detail, scroll]
mixins: [base, detail, scroll],
methods: {
/**
* 发送商品信息到智能客服
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
this.$refs.contactPopup.open();
},
/**
* 跳转到智能客服页面
*/
goToAIChat() {
const productInfo = {
goods_id: this.goodsSkuDetail.goods_id || '',
goods_name: this.goodsSkuDetail.goods_name || '',
price: this.goodsSkuDetail.discount_price || this.goodsSkuDetail.member_price || this.goodsSkuDetail.price || '',
image: this.goodsSkuDetail.sku_image || '',
category_id: this.goodsSkuDetail.category_id || ''
};
uni.setStorageSync('selectedProduct', productInfo);
uni.navigateTo({
url: '/pages_tool/ai-chat/index'
});
},
/**
* 关闭联系电话弹窗
*/
closeContactPopup() {
this.$refs.contactPopup.close();
},
/**
* 复制电话号码到剪切板
*/
copyPhoneNumber() {
uni.setClipboardData({
data: '021-51815181',
success: () => {
uni.showToast({
title: '复制成功',
icon: 'success'
});
this.closeContactPopup();
}
});
}
}
};
</script>
<style lang="scss">
@import '@/common/css/goods_detail.scss';
@import './public/css/detail.scss';
@@ -545,6 +610,57 @@ export default {
z-index: 999;
}
.contact-popup {
width: 600rpx;
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
}
.contact-content {
padding: 60rpx 40rpx;
text-align: center;
}
.contact-text {
font-size: 32rpx;
color: #333333;
line-height: 1.5;
}
.contact-buttons {
display: flex;
border-top: 1px solid #e5e5e5;
}
.contact-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
border: none;
background: transparent;
margin: 0;
padding: 0;
border-radius: 0;
}
.contact-btn::after {
border: none;
}
.cancel-btn {
color: #333333;
background: #ffffff;
border-right: 1px solid #e5e5e5;
}
.copy-btn {
color: #ffffff;
background: #54c952;
}
</style>
<style scoped>
::v-deep .action-icon-wrap .iconfont.icon-shouye1 {
@@ -640,4 +756,5 @@ export default {
height: 72rpx;
line-height: 72rpx;
}
</style>

View File

@@ -51,7 +51,7 @@
<view class="goods-item margin-bottom" v-for="(item, index) in goodsList" :key="index"
@click="toDetail(item)">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}
</view>
<view class="sell-out" v-if="item.goods_stock <= 0">
@@ -75,68 +75,23 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ langstatus ? $lang('Make') : '询底价' }}</text>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix"></image>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"></image>
</view>
</view>
<view class="pro-info" v-if="item.isinformation == 0">
<view class="delete-price color-tip price-font" v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
<view class="block-wrap">
<view class="sale color-tip" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
<view class="sale" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
? item.unit : '件' }}</view>
</view>
<view class="cart-action-wrap" v-if="config.control && item.is_virtual == 0">
<!-- 购物车图标 -->
<view v-if="config.style == 'icon-cart'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart shopping-cart-btn iconfont icon-gouwuche click-wrap" :id="'goods-' + item.id"
<view class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
购买
<view class="click-event"></view>
</view>
<!--加号图标 -->
<view v-else-if="config.style == 'icon-add'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart plus-sign-btn iconfont icon-add1 click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
</view>
<!-- 按钮 -->
<view v-else-if="config.style == 'button'" :style="{
backgroundColor: config.theme == 'diy' ? config.bgColor : '',
color: config.theme == 'diy' ? config.textColor : '',
fontWeight: config.theme == 'diy' ? (config.fontWeight ? 'bold' : 'normal') : '',
padding: config.theme == 'diy' ? '12rpx ' + config.padding * 2 + 'rpx' : ''
}" class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
{{ config.text }}
<view class="click-event"></view>
</view>
<!--自定义图标 -->
<view v-else-if="config.style == 'icon-diy'" :style="{
color: config.theme == 'diy' ? config.textColor : ''
}" class="icon-diy click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
<diy-icon :icon="config.iconDiy.icon"
:value="config.iconDiy.style ? config.iconDiy.style : null"></diy-icon>
</view>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @click.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
</view>
@@ -147,7 +102,7 @@
@click="toDetail(item)"
:style="{ left: listPosition[index] ? listPosition[index].left : '', top: listPosition[index] ? listPosition[index].top : '' }">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}
</view>
<view class="sell-out" v-if="item.stock <= 0">
@@ -168,66 +123,23 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ langstatus ? $lang('Make') : '询底价' }}</text>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix"></image>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"></image>
</view>
<view class="delete-price color-tip price-font" v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="pro-info" v-if="item.isinformation == 0">
<view class="block-wrap">
<view class="sale color-tip" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
<view class="sale" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
? item.unit : '件' }}</view>
</view>
<view class="cart-action-wrap" v-if="config.control && item.is_virtual == 0">
<!-- 购物车图标 -->
<view v-if="config.style == 'icon-cart'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart shopping-cart-btn iconfont icon-gouwuche click-wrap" :id="'goods-' + item.id"
<view class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
购买
<view class="click-event"></view>
</view>
<!--加号图标 -->
<view v-else-if="config.style == 'icon-add'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart plus-sign-btn iconfont icon-add1 click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
</view>
<!-- 按钮 -->
<view v-else-if="config.style == 'button'" :style="{
backgroundColor: config.theme == 'diy' ? config.bgColor : '',
color: config.theme == 'diy' ? config.textColor : '',
fontWeight: config.theme == 'diy' ? (config.fontWeight ? 'bold' : 'normal') : '',
padding: config.theme == 'diy' ? '12rpx ' + config.padding * 2 + 'rpx' : ''
}" class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
{{ config.text }}
<view class="click-event"></view>
</view>
<!--自定义图标 -->
<view v-else-if="config.style == 'icon-diy'" :style="{
color: config.theme == 'diy' ? config.textColor : ''
}" class="icon-diy click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
<diy-icon :icon="config.iconDiy.icon"
:value="config.iconDiy.style ? config.iconDiy.style : null"></diy-icon>
</view>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @click.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
</view>
@@ -311,7 +223,14 @@ export default {
data() {
return {};
},
mixins: [list]
mixins: [list],
methods: {
sendToAIChat(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
}
}
};
</script>
@@ -327,4 +246,24 @@ export default {
::v-deep .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
max-height: unset !important;
}
.ai-chat-btn-wrap {
display: flex;
justify-content: flex-end;
margin-top: auto;
padding-top: 10rpx;
}
.ai-chat-btn {
background-color: #ff0000;
color: #fff;
padding: 12rpx 30rpx;
border-radius: 40rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1.2;
text-align: center;
white-space: nowrap;
display: inline-block;
}
</style>

View File

@@ -295,7 +295,8 @@
.lineheight-clear {
display: flex;
justify-content: flex-end;
justify-content: space-between;
align-items: center;
}
// 商品列表单列样式
@@ -386,46 +387,30 @@
display: inline-block;
font-weight: bold;
line-height: 1;
margin-top: 16rpx;
background-color: var(--main-color);
border-radius: 35rpx;
padding: 12rpx 24rpx;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
margin-top: 0;
.unit {
margin-right: 6rpx;
color: var(--price-color);
color: #ff4444;
}
.price {
color: var(--price-color);
color: #fff !important;
font-size: 27rpx !important;
color: #ff4444;
font-size: 32rpx !important;
font-weight: bold !important;
}
}
.pro-info {
margin-top: 10rpx;
display: flex;
margin-top: auto;
justify-content: space-between;
align-items: center;
position: relative;
.delete-price {
text-decoration: line-through;
font-size: $font-size-tag !important;
flex: 1;
.unit {
margin-right: 0;
}
}
.block-wrap {
flex: 1;
line-height: 1;
margin-right: 20rpx;
.sale {
font-size: $font-size-tag !important;
color: #999;
}
}
}
@@ -543,6 +528,9 @@
.lineheight-clear {
margin-top: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.discount-price {
@@ -552,24 +540,26 @@
.unit {
margin-right: 6rpx;
color: var(--price-color);
color: #ff4444;
}
.price {
color: var(--price-color);
color: #ff4444;
font-size: 32rpx !important;
font-weight: bold !important;
}
}
.pro-info {
margin-top: 10rpx;
display: flex;
margin-top: auto;
justify-content: space-between;
align-items: center;
.block-wrap {
flex: 1;
line-height: 1;
margin-right: 20rpx;
.sale {
font-size: $font-size-tag !important;
color: #999;
}
}
}
@@ -644,9 +634,9 @@
}
.buy-btn {
background-color: $base-color;
color: var(--btn-text-color);
border-radius: 50rpx;
background-color: #ff4444;
color: #fff;
border-radius: 30rpx;
font-size: $font-size-tag;
padding: 12rpx 30rpx;
line-height: 1;

View File

@@ -88,7 +88,7 @@
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '询底价'
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '立即咨询'
}}</text>
</view>
@@ -182,7 +182,7 @@
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '询底价'
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '立即咨询'
}}</text>
</view>
<view class="member-price-tag"

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,17 @@
<template>
<view class="container" :style="wrapperPageStyle">
<!--自定义导航头部 -->
<!--自定义导航头部 - 只在微信小程序显示,H5端隐藏 -->
<!-- #ifdef MP-WEIXIN -->
<view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar">
<view class="header-left">
<button class="back-btn" v-show="showBackButton" @click="goBack">
<text class="iconfont icon-back"></text>
</button>
<!-- 占位元素,确保布局平衡 -->
<view v-show="!showBackButton" class="placeholder"></view>
</view>
<view class="header-center">
<text class="header-subtitle">在线为您服务</text>
</view>
<view class="header-right">
<button class="menu-btn" v-show="showMenuButton" @click="showMenu">
<text>⚙️</text>
</button>
<!-- 占位元素,确保布局平衡 -->
<view v-show="!showMenuButton" class="placeholder"></view>
</view>
</view>
<!-- #endif -->
<!-- 聊天内容区域 -->
<view class="chat-content"
@@ -87,6 +79,7 @@ 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';
import IMAGE_CONFIG from '@/common/js/image-config.js';
export default {
components: {
@@ -101,10 +94,6 @@ export default {
type: 'text',
content: '您好!我是AI智能客服,很高兴为您服务!\n\n我可以帮您:\n• 解答产品相关问题\n• 处理订单问题\n• 提供技术支持\n• 推荐相关商品\n\n请告诉我您需要什么帮助?',
timestamp: Date.now() - 60000,
actions: [
{ type: 'like', icon: 'icon-dianzan1', text: '喜欢', count: 0 },
{ type: 'dislike', icon: 'icon-dianzan1', text: '不喜欢', count: 0 }
]
}
],
@@ -121,6 +110,9 @@ export default {
// --- 聊天内容区域 ---
scrollViewHeight: '0px',
// --- 键盘高度 ---
keyboardHeight: 0,
// 事件处理器引用(用于清理)
safeEventHandlers: new Map()
}
@@ -133,10 +125,10 @@ export default {
/// ---- 关于AI客服的配置,支持远程配置 ----
userAvatar() {
return this.globalAIKefuConfig?.userAvatar || '/static/images/user-avatar.png'
return this.globalAIKefuConfig?.userAvatar || this.$util.img(IMAGE_CONFIG.userAvatar)
},
aiAvatar() {
return this.globalAIKefuConfig?.aiAvatar || '/static/images/ai-avatar.png'
return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar)
},
/// ---- others ----
containerHeight() {
@@ -154,10 +146,21 @@ export default {
return ``
},
wrapperChatContentStyle() {
// #ifdef H5
// H5端没有自定义导航栏,不需要paddingTop
return {
height: this.containerHeight,
paddingTop: '0'
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序端根据导航栏显示情况决定
return {
height: this.containerHeight,
paddingTop: this.showCustomNavbar ? '0' : (this.navBarHeight + 'px')
}
// #endif
}
},
@@ -178,11 +181,23 @@ export default {
},
onShow() {
// 可在此补充逻辑(如刷新未读数)
// 监听键盘高度变化
uni.onKeyboardHeightChange((res) => {
console.log('键盘高度变化:', res.height);
this.keyboardHeight = res.height;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(res.height);
}
});
},
onHide() {
// 页面隐藏时可暂停音频等
// 取消监听键盘高度变化
uni.offKeyboardHeightChange();
this.keyboardHeight = 0;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(0);
}
},
onUnload() {
@@ -257,6 +272,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 +511,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({
@@ -571,8 +614,8 @@ export default {
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background-color: #c4e0ff; /* 浅蓝色 */
border-bottom: 2rpx solid #eeeeee;
background-color: white; /* 白色 */
border-bottom: 1rpx solid #e5e5e5;
.header-left {
flex: 0 0 auto;

View File

@@ -248,7 +248,8 @@ export default {
latitude: 0,
longitude: 0,
},
personnel_bg: ''
personnel_bg: '',
mpShareData: null //小程序分享数据
};
},
onLoad(option) {
@@ -303,6 +304,24 @@ export default {
});
},
onShow() {
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_tool/contact/contact').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '联系我们',
path: '/pages_tool/contact/contact'
},
timeLine: {
title: '联系我们',
query: ''
}
};
});
// #endif
this.$util.hideHomeButton();
},
methods: {
@@ -449,9 +468,28 @@ export default {
longitude: parseFloat(this.shop.longitude),
name: this.$lang('oneClickNavigation'),
});
}
}
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '联系我们',
path: '/pages_tool/contact/contact'
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '联系我们',
query: ''
};
},
onAddToFavorites() {
return {
title: '联系我们',
path: '/pages_tool/contact/contact'
};
},
},
};
</script>
<style lang="scss">

View File

@@ -134,7 +134,8 @@ export default {
levelId: 0,
levelInfo: {
bg_color: '#333'
}
},
mpShareData: null //小程序分享数据
};
},
onLoad() {
@@ -161,7 +162,26 @@ export default {
});
this.levelInfo = levelInfo;
},
onShow() { },
onShow() {
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_tool/member/card').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
},
timeLine: {
title: '专属会员权益,快来体验',
query: '/pages_tool/member/card'
}
};
});
// #endif
},
methods: {
headimgError() {
this.memberInfo.headimg = this.$util.getDefaultImage().head;
@@ -185,6 +205,24 @@ export default {
}
this.$util.redirectTo(this.$util.MEMBER_PAGE_URL);
return true;
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '专属会员权益,快来体验',
query: '/pages_tool/member/card'
};
},
onAddToFavorites() {
return {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
};
}
};
</script>

View File

@@ -7,7 +7,7 @@
@click.stop="toDetail(item)">
<view class="pic">
<image :src="$util.img(item.goods_image.split(',')[0], { size: 'mid' })" mode="aspectFill"
@error="goodsImageError(index)"></image>
@error="goodsImageError(index)" :show-menu-by-longpress="true"></image>
</view>
<view class="goods_info">
<view class="goods_name font-size-base">{{ item.sku_name }}</view>

View File

@@ -13,7 +13,7 @@
<text class="iconfont" :class="$util.inArray(item.id, idArr) != -1 ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></text>
</view>
<view class="goods-img" @click="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}</view>
</view>
<view class="info-wrap" @click="toDetail(item)">

View File

@@ -37,7 +37,7 @@
*已有依赖可跳过此步骤*
##### 4.1.1.2 【发布构建】
1. 使用 HBuilderX 打开项目
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
3. 打开资源管理器→项目根目录,右键选择「在终端中打开」,执行命令:
@@ -67,7 +67,9 @@
- 确认 `site.js` 中的 `uniacid` 为客户编号
- 用微信开发者工具导入解压后的代码目录
- 编译验证无误后,上传发布
##### 4.1.1.5 【本地设置】
![](readme_files/1.jpg)
#####
---
### 4.2 快应用发布
@@ -78,20 +80,22 @@
1. 使用 HBuilderX 打开项目
2. 打开项目根目录的 `manifest.json` 文件,切换至可视化配置界面:
1. 依次点击「Web 配置」→「运行的基础配置」
2. 修改路径中的客户编号
3. 📌 **通用版**保留原有 `/hwappx/common/`
💡 **示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
2. 根据情况查看是否修改路径中/hwappx/????/的客户编号
📌 **通用版**保留原有 `/hwappx/common/`
📌 **测试版**保留原有 `/hwappx/test/`
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
3. 选择菜单栏「发行」 → 「自定义发行」 →「H5-xcx.aigc-quickapp.com」,修改以下配置:
3. 选择菜单栏「发行」 → 「自定义发行」 →(「H5-xcx.aigc-quickapp.com」查看.local.config.js中站点的网址前缀,站点的网址前缀是什么就选自定义发行中相同的格式)
例如:网址是5g.aigc-quickapp.com,自定义发行就选H5-5g.aigc-quickapp.com
网址是dev.aigc-quickapp.com,自定义发行就选H5-xcx.aigc-quickapp.com
4. 修改以下配置:
1. **网站标题**:快应用
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com)
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
3. 确认后点击「发行」等待构建完成
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
4. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
*仅打包文件,不包含外层 web 目录*
##### 4.2.1.2 【重命名】
@@ -105,12 +109,16 @@
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
##### 4.2.1.3 【交付与最终发布】🔍
1. 将重命名后的压缩包发送给运维人员
2. **运维人员操作流程**:
1. 解压压缩包
2. 打开快应用开发者工具,导入解压后的代码目录
3. 验证代码无报错后,执行上传发布操作
##### 4.2.1.3 【测试流程】
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
如果是test就把hwappx/common/改为hwappx/test/----------改站点
---
## 5. 📝 构建脚本说明

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}`);

View File

@@ -1,4 +1,4 @@
module.exports = {
baseUrl: "https://dev.aigc-quickapp.com/",//修改域名
uniacid: 1,//后台对应uniacid
baseUrl: "https://xcx6.aigc-quickapp.com/",//修改域名
uniacid: 2847,//后台对应uniacid
};