Compare commits

...

34 Commits

Author SHA1 Message Date
5703405598 chore:添加了快应用打包步骤 2026-04-08 15:51:10 +08:00
e19fef785f chore:改了commom 2026-04-08 14:39:23 +08:00
92ae114c5c chore: 2026-04-08 11:47:07 +08:00
b25c366a5b chore:增加了弹窗用户隐私 2026-04-08 11:37:29 +08:00
4ad93ceace refactor: 将视频标题样式绑定从对象改为数组 2026-02-07 18:11:42 +08:00
294856d9ef Merge branch 'test/diy-tabs' into dev/1.0 2026-02-07 17:53:30 +08:00
ba2c7d6d64 Revert "chore:点击中英文按钮不跳到‘我的’了"
This reverts commit 19a53ef414.
2026-02-07 17:51:10 +08:00
ec9e76fda7 chore:改了中英文模式 2026-02-07 16:53:40 +08:00
5dba8071e4 refactor(diy-tab): 统一样式绑定语法为数组形式
将模板中的样式绑定从对象展开语法改为数组形式,保持代码风格一致
2026-02-07 16:16:43 +08:00
19a53ef414 chore:点击中英文按钮不跳到‘我的’了 2026-02-07 16:10:55 +08:00
e943b11c8f fix(组件): 修复tab组件中子组件moduleIndex冲突问题
为tab组件中的子组件添加tabIndex前缀,避免不同tab页中子组件的moduleIndex冲突
2026-02-07 15:57:49 +08:00
e9db54980e refactor(diy-tab): 使用模板字符串和可选链优化key生成逻辑 2026-02-07 14:59:26 +08:00
3ddcc665b8 refactor(diy-tab): 统一使用生成函数获取tab和panel的key
使用getTabKey和getPanelKey方法统一生成key,提高代码可维护性
2026-02-07 14:11:51 +08:00
a981c02baf chore:智能客服页面右上角改成齿轮图标,更多功能图标改为(...) 2026-02-07 13:59:43 +08:00
1c3f759b35 feat: 为DIY标签组件添加可配置的动画效果 2026-02-07 11:41:59 +08:00
bb58866192 feat: 为标签组件添加悬停动画和对齐选项 2026-02-07 11:34:45 +08:00
069b3c2c07 feat: 为DIY标签页组件添加滚动容器和样式 2026-02-06 17:55:44 +08:00
c6fd13956a chore:H5和微信小程序的第三方客服悬浮按钮应该会显示 👤 2026-02-06 17:49:48 +08:00
a489e24988 chore:改了企业微信客服,智能客服的悬浮按钮 2026-02-06 17:18:58 +08:00
6897fc631b feat: 重构DIY标签页组件,支持多种布局和样式配置 2026-02-06 16:47:32 +08:00
6d23a64aef chore:智能客服的页面全部显示出来 2026-02-06 16:30:33 +08:00
f34533b0c7 chore:进入智能客服的页面加了智能客服的头像 2026-02-06 16:08:50 +08:00
72c74f44f0 chore:删除弹出的框的一个叉 2026-02-06 16:02:11 +08:00
7f3d9c0f10 chore:将更改头像和昵称的按钮放在...的设置中 2026-02-06 15:56:00 +08:00
37f95e7267 chore:改了客服的图标 2026-02-05 18:30:16 +08:00
d3c630e860 chore:后台点击什么,页面显示什么 2026-02-05 16:58:54 +08:00
c5b57cedeb chore:后台点击不启用,H5和微信小程序不显示 2026-02-05 16:34:33 +08:00
bf24746a7c chore;更改了客服和中英文按钮的顺序 2026-02-05 10:14:27 +08:00
5ff2925ba1 chore: 2026-02-05 09:56:37 +08:00
3dc81e7f53 chore:优化了hover 2026-02-05 09:50:36 +08:00
0417c5c5ed chore:优化了hover 2026-02-05 09:27:43 +08:00
db26a85c8b chore:商品,名片,我的都能正常跳转了 2026-02-04 17:09:57 +08:00
54c39259a7 chore:代码合并,整理功能 2026-02-02 18:04:59 +08:00
15361211b1 Merge pull request 'tjh/merge' (#3) from tjh/merge into dev/1.0
Reviewed-on: #3
2026-02-02 09:52:19 +00:00
17 changed files with 2331 additions and 1251 deletions

View File

@@ -42,6 +42,14 @@ const localDevConfig = ({
uniacid: 2, uniacid: 2,
domain: 'http://localhost:8050/', domain: 'http://localhost:8050/',
}, },
})['1']; // 选择要使用的环境配置 '47': { // 修改弹窗
uniacid: 47,
domain: 'https://5g.aigc-quickapp.com/',
},
'2075': { //
uniacid: 2075,
domain: 'https://xcx.aigc-quickapp.com/',
},
})['47']; // 选择要使用的环境配置
export default localDevConfig; export default localDevConfig;

View File

@@ -76,6 +76,15 @@ export class CustomerService {
return config && typeof config === 'object' && config.type; return config && typeof config === 'object' && config.type;
} }
/**
* 检查是否有有效的客服配置(用于判断是否显示客服按钮)
* @returns {Boolean}
*/
hasValidKefuConfig() {
const config = this.getPlatformConfig();
return config && typeof config === 'object' && config.type && config.type !== 'none';
}
/** /**
* 验证客服配置完整性 * 验证客服配置完整性
* @returns {Object} 验证结果 * @returns {Object} 验证结果
@@ -453,6 +462,235 @@ export class CustomerService {
return { ...config, openType }; return { ...config, openType };
} }
/**
* 获取客服按钮图标
* @returns {String} 图标URL
*/
getKefuButtonIcon() {
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 '';
}
return shopInfo.kefuimg || this.vm.$util?.getDefaultImage()?.kefu || '';
}
/**
* 判断是否为微信官方客服
* @returns {Boolean}
*/
isWeappOfficialKefu() {
const config = this.getPlatformConfig();
return config?.type === 'weapp';
}
/**
* 判断是否为智能客服
* @returns {Boolean}
*/
isAIKefu() {
const config = this.getPlatformConfig();
return config?.type === 'aikefu';
}
/**
* 判断是否为企业微信客服
* @returns {Boolean}
*/
isWxworkKefu() {
const config = this.getPlatformConfig();
return config?.type === 'wxwork' || config?.type === 'qyweixin';
}
/**
* 判断是否为第三方客服
* @returns {Boolean}
*/
isThirdKefu() {
const config = this.getPlatformConfig();
return config?.type === 'third';
}
/**
* 判断是否需要同时显示小程序系统客服
* @returns {Boolean}
*/
shouldShowExtraWeappSystemKefu() {
const config = this.getPlatformConfig();
return (config?.show_system_service === true || config?.show_system_service === '1') && config?.type !== 'weapp';
}
/**
* 计算容器高度
* @param {Boolean} isLanguageSwitchEnabled 是否启用语言切换
* @param {Boolean} showWeappSystemKefu 是否显示小程序系统客服
* @param {Boolean} fixBtnShow 是否显示按钮
* @returns {String} 高度值(px)
*/
getContainerHeight(isLanguageSwitchEnabled, showWeappSystemKefu, fixBtnShow) {
if (!fixBtnShow) return '160px';
let buttonCount = 1;
if (isLanguageSwitchEnabled) buttonCount++;
if (showWeappSystemKefu) buttonCount++;
const totalRpx = 94 * buttonCount - 14;
const pxValue = Math.round(totalRpx * 0.5);
return `${pxValue}px`;
}
/**
* 获取未读消息数量
* @returns {Number}
*/
getUnreadCount() {
return this.vm.$store?.state?.aiUnreadCount || 0;
}
/**
* 拨打电话联系客服
* @param {String} phoneNumber 电话号码
*/
makePhoneCallByNumber(phoneNumber) {
if (phoneNumber) {
uni.makePhoneCall({ phoneNumber });
} else {
this.makePhoneCall();
}
}
/**
* 打开 AI 智能助手
*/
openAIChat() {
try {
if (typeof this.vm.setAiUnreadCount === 'function') {
this.vm.setAiUnreadCount(0);
}
const aiChatUrl = '/pages_tool/ai-chat/index';
uni.navigateTo({
url: aiChatUrl,
fail: (err) => {
console.error('跳转 AI 客服失败:', err);
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI 客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
/**
* 打开客服选择对话框(预留方法)
*/
openCustomerSelectPopupDialog() {
uni.showToast({ title: '客服选择功能开发中', icon: 'none' });
}
/**
* 打开客服选择弹出层(ActionSheet)
* @param {Array} kefuList 客服列表
*/
openKefuSelectPopup(kefuList) {
const kefuNames = kefuList.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
const selectedKefu = kefuList[res.tapIndex];
const cs = createCustomerService(this.vm, selectedKefu);
if (selectedKefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (selectedKefu.id === 'qyweixin-kefu') {
if (uni.getSystemInfoSync().platform === 'wechat') {
uni.navigateTo({
url: '/pages_tool/qyweixin-kefu/index'
});
} else {
const qyweixinUrl = this.vm.shopInfo?.qyweixinUrl;
if (qyweixinUrl) {
window.location.href = qyweixinUrl;
} else {
uni.showToast({ title: '企业微信客服未配置', icon: 'none' });
}
}
} else {
cs.handleCustomerClick();
}
}
});
}
/**
* 核心方法:统一客服入口(带验证和配置检查)
* @param {Object} options 选项参数
*/
handleUnifiedKefuClick(options = {}) {
const validation = this.validateConfig();
console.log('【客服配置验证】', validation);
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const platformConfig = this.getPlatformConfig();
console.log('【当前客服配置】', platformConfig);
// 检查企业微信配置
if (platformConfig.type === 'wxwork') {
const wxworkConfig = this.getWxworkConfig();
console.log('【企业微信配置】', wxworkConfig);
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
console.warn('企业微信配置不完整,使用原生客服');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
}
// #endif
// #ifdef H5
if (!wxworkConfig?.contact_url && !platformConfig.wxwork_url) {
console.error('企业微信链接未配置');
uni.showToast({ title: '企业微信链接未配置', icon: 'none' });
return;
}
// #endif
}
// 直接调用统一处理方法,由 CustomerService 内部根据配置路由
try {
this.handleCustomerClick({
sendMessageTitle: options.sendMessageTitle || '来自悬浮按钮的咨询',
sendMessagePath: options.sendMessagePath || '/pages/index/index'
});
} catch (error) {
console.error('客服处理失败:', error);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
} }
/** /**

View File

@@ -7,7 +7,7 @@ export default {
computed: { computed: {
// 是否是英文环境 // 是否是英文环境
isEnEnv() { isEnEnv() {
return uni.getStorageSync('lang') === 'en-us'; return this.$langConfig.getCurrentLocale() === 'en-us';
}, },
themeStyle() { themeStyle() {
return this.$store.state.themeStyle; return this.$store.state.themeStyle;

View File

@@ -1,5 +1,6 @@
import { langConfig } from './config-external.js'; import { langConfig } from './config-external.js';
// 缓存已加载的语言包 // 缓存已加载的语言包
const loadedLangPacks = {}; const loadedLangPacks = {};
@@ -8,15 +9,21 @@ function processRoutePath(route) {
let routeParts = route.split("/"); let routeParts = route.split("/");
// ---- 处理页面目录映射 <begin> 分包造成的,需要根据实际目录结构进行映射---- // ---- 处理页面目录映射 <begin> 分包造成的,需要根据实际目录结构进行映射----
// 先处理特殊的分包路径
if (routeParts[0] === 'pages_tool') { if (routeParts[0] === 'pages_tool') {
// pages_tool 分包下的页面,直接使用子目录作为语言包路径
routeParts = [routeParts[1], ...routeParts.slice(2)]; routeParts = [routeParts[1], ...routeParts.slice(2)];
} else if (routeParts[0] === 'pages_goods') { } else if (routeParts[0] === 'pages_goods') {
// pages_goods 分包映射到 goods 目录
routeParts[0] = 'goods'; routeParts[0] = 'goods';
} else if (routeParts[0] === 'pages_member') { } else if (routeParts[0] === 'pages_member') {
// pages_member 分包映射到 member 目录
routeParts[0] = 'member'; routeParts[0] = 'member';
} else if (routeParts[0] === 'pages_order') { } else if (routeParts[0] === 'pages_order') {
// pages_order 分包映射到 order 目录
routeParts[0] = 'order'; routeParts[0] = 'order';
} else if (routeParts[0] === 'pages_promotion') { } else if (routeParts[0] === 'pages_promotion') {
// pages_promotion 分包特殊处理
const promotionModules = ['point', 'fenxiao', 'merch']; const promotionModules = ['point', 'fenxiao', 'merch'];
if (routeParts[1] && promotionModules.includes(routeParts[1])) { if (routeParts[1] && promotionModules.includes(routeParts[1])) {
routeParts = [routeParts[1], ...routeParts.slice(2)]; routeParts = [routeParts[1], ...routeParts.slice(2)];
@@ -24,6 +31,7 @@ function processRoutePath(route) {
} }
// ---- 处理页面目录映射 <end>---- // ---- 处理页面目录映射 <end>----
// 去掉pages目录,只保留子目录
if (routeParts[0] === 'pages') { if (routeParts[0] === 'pages') {
routeParts = routeParts.slice(1); routeParts = routeParts.slice(1);
} }
@@ -46,32 +54,51 @@ function loadLangPackSync(lang, path) {
} }
} }
function getCurrentLocale() {
return uni.getStorageSync('lang') || "zh-cn";
}
export default { export default {
langList: langConfig.langList, langList: langConfig.langList,
/** /**
* 解析多语言 * 获得当前本地语言
* @returns
*/
getCurrentLocale,
/**
* * 解析多语言
* @param {Object} field
*/ */
lang(field) { lang(field) {
let _this = getCurrentPages()[getCurrentPages().length - 1]; let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return; if (!_page) return;
const locale = uni.getStorageSync('lang') || "zh-cn"; const locale = getCurrentLocale(); // 获得当前本地语言
let value = ''; let value = ''; // 存放解析后的语言值
let langPath = ''; let langPath = ''; // 存放当前页面语言包路径
try { try {
//公共语言包(同步加载)
var lang = loadLangPackSync(locale, 'common'); var lang = loadLangPackSync(locale, 'common');
let route = _this.route; //当前页面语言包(同步加载)
let route = _page.route;
langPath = processRoutePath(route); langPath = processRoutePath(route);
// 加载当前页面语言包
let currentPageLang = loadLangPackSync(locale, langPath); let currentPageLang = loadLangPackSync(locale, langPath);
// 合并语言包
let mergedLang = { ...lang, ...currentPageLang }; let mergedLang = { ...lang, ...currentPageLang };
// 解析字段
var arr = field.split("."); var arr = field.split(".");
if (arr.length > 1) { if (arr.length > 1) {
// 处理嵌套属性,如 common.currencySymbol
let temp = mergedLang; let temp = mergedLang;
let found = true; let found = true;
for (let key of arr) { for (let key of arr) {
@@ -93,12 +120,14 @@ export default {
} }
if (arguments.length > 1) { if (arguments.length > 1) {
//有参数,需要替换
for (var i = 1; i < arguments.length; i++) { for (var i = 1; i < arguments.length; i++) {
value = value.replace("{" + (i - 1) + "}", arguments[i]); value = value.replace("{" + (i - 1) + "}", arguments[i]);
} }
} }
if (value == undefined || (value == 'title' && field == 'title')) value = ''; if (value == undefined || (value == 'title' && field == 'title')) value = ''; // field
// 多语言调试,注释后可以关闭控制台输出
if (field == value) { if (field == value) {
console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`); console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`);
} }
@@ -106,19 +135,23 @@ export default {
return value; return value;
}, },
/** /**
* 切换语言 * * 切换语言
* @param {String} value 语言值
* @param {String} url 切换后跳转的页面url
*/ */
change(value, url = '/pages_tool/member/index') { change(value, url = '/pages_tool/member/index') {
let _this = getCurrentPages()[getCurrentPages().length - 1]; let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return; if (!_page) return;
uni.setStorageSync("lang", value); uni.setStorageSync("lang", value);
const locale = uni.getStorageSync('lang') || "zh-cn"; const locale = getCurrentLocale();
// ✅ 关键修复:清空所有语言包缓存(不再保留任何旧缓存) // 清空已加载的语言包缓存
for (let key in loadedLangPacks) { for (let key in loadedLangPacks) {
if (!key.startsWith(locale)) {
delete loadedLangPacks[key]; delete loadedLangPacks[key];
} }
}
this.refresh(); this.refresh();
@@ -128,16 +161,39 @@ export default {
}, },
//刷新标题、tabbar //刷新标题、tabbar
refresh() { refresh() {
let _this = getCurrentPages()[getCurrentPages().length - 1]; let _page = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return; if (!_page) return;
const locale = uni.getStorageSync('lang') || "zh-cn";
const locale = getCurrentLocale();
this.title(this.lang("title")); this.title(this.lang("title"));
//设置tabbar的文字语言
// uni.setTabBarItem({
// index: 0,
// text: this.lang("tabBar.home")
// });
// uni.setTabBarItem({
// index: 1,
// text: this.lang("tabBar.category")
// });
// uni.setTabBarItem({
// index: 2,
// text: this.lang("tabBar.cart")
// });
// uni.setTabBarItem({
// index: 3,
// text: this.lang("tabBar.member")
// });
}, },
title(str) { title(str) {
if (str) { if (str) {
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: str title: str,
success: function (res) {
},
fail: function (err) {
}
}); });
} }
}, },
@@ -145,6 +201,7 @@ export default {
list() { list() {
var list = []; var list = [];
try { try {
//公共语言包
for (var i = 0; i < langConfig.langList.length; i++) { for (var i = 0; i < langConfig.langList.length; i++) {
let langType = langConfig.langList[i]; let langType = langConfig.langList[i];
let item = loadLangPackSync(langType, 'common'); let item = loadLangPackSync(langType, 'common');

View File

@@ -14,7 +14,7 @@
</view> </view>
<view class="video-info"> <view class="video-info">
<!-- 视频标题,支持多行显示控制 --> <!-- 视频标题,支持多行显示控制 -->
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }} <view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
</view> </view>
<!-- 视频统计信息 --> <!-- 视频统计信息 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view> <view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view>
@@ -34,7 +34,7 @@
</view> </view>
<view class="video-info"> <view class="video-info">
<!-- 视频标题,支持多行显示控制 --> <!-- 视频标题,支持多行显示控制 -->
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }} <view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
</view> </view>
<!-- 视频统计信息,非列表模式下显示 --> <!-- 视频统计信息,非列表模式下显示 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ value.viewCount }}次观看</view> <view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ value.viewCount }}次观看</view>

View File

@@ -317,7 +317,12 @@ export default {
this.diyGlobalData.value.forEach((item, index) => { this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = ''; item.pageStyle = '';
// 给每个组件增加位置属性,用于定位,搜索、分类导航等定位 // 给每个组件增加位置属性,用于定位,搜索、分类导航等定位
// 如果是Tab组件内部的子组件,添加tabIndex前缀避免moduleIndex冲突
if (this.diyData.tabIndex !== undefined) {
item.moduleIndex = `${this.diyData.tabIndex}-${index + 1}`;
} else {
item.moduleIndex = index + 1; item.moduleIndex = index + 1;
}
// 特殊处理搜索框 当显示位置为滚动至顶部固定时,只设置背景颜色 // 特殊处理搜索框 当显示位置为滚动至顶部固定时,只设置背景颜色
if (item.componentName == 'Search' && item.positionWay == 'fixed') { if (item.componentName == 'Search' && item.positionWay == 'fixed') {

File diff suppressed because it is too large Load Diff

View File

@@ -156,11 +156,11 @@
</script> </script>
<style lang="scss"> <style lang="scss">
/deep/.uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { ::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
background-color: #000; background-color: #000;
} }
/deep/.uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box { ::v-deep .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
} }

View File

@@ -1,41 +1,10 @@
<template> <template>
<view v-if="pageCount == 1 || need" class="fixed-box" <view v-if="pageCount === 1 || need" class="fixed-box"
:style="[customContainerStyle, { :style="[customContainerStyle, {
height: containerHeight, height: containerHeight,
backgroundImage: bgUrl ? `url( $ {bgUrl})` : '', backgroundImage: bgUrl ? `url(${bgUrl})` : '',
backgroundSize: 'cover' backgroundSize: 'cover'
}]"> }]">
<!-- ✅ 统一客服入口(根据后台配置自动适配 AI / 企业微信 / 第三方等) -->
<!-- 微信官方客服需要使用 button open-type="contact" -->
<button
v-if="fixBtnShow && isWeappOfficialKefu"
class="btn-item common-bg"
open-type="contact"
:style="{ backgroundImage: currentKefuImg ? `url( $ {currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<text class="ai-icon" v-if="!currentKefuImg">🤖</text>
</button>
<!-- 其他类型客服使用普通 view -->
<view
v-else-if="fixBtnShow"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
:style="{ backgroundImage: currentKefuImg ? `url( $ {currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<text class="ai-icon" v-if="!currentKefuImg">🤖</text>
</view>
<!-- ✅ 新增:小程序系统客服按钮(当附加设置开启时显示) -->
<button
v-if="fixBtnShow && showWeappSystemKefu"
class="btn-item common-bg"
open-type="contact"
:style="{ backgroundImage: currentKefuImg ? `url( $ {currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<text class="ai-icon" v-if="!currentKefuImg">💬</text>
</button>
<!-- 中英文切换按钮 --> <!-- 中英文切换按钮 -->
<view <view
v-if="isLanguageSwitchEnabled && fixBtnShow" v-if="isLanguageSwitchEnabled && fixBtnShow"
@@ -44,13 +13,58 @@
> >
<text>{{ currentLangDisplayName }}</text> <text>{{ currentLangDisplayName }}</text>
</view> </view>
<!-- 微信官方客服按钮-->
<button
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>
<!-- 电话按钮(始终显示) --> <!-- 其他类型客服按钮(智能客服、企业微信等)-->
<view
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && !showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<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>
</view>
<!-- 智能客服按钮(当同时显示小程序系统客服时)-->
<view
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
:style="{ backgroundImage: currentKefuImg ? `url(${currentKefuImg})` : '', backgroundSize: '100% 100%' }"
>
<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>
</view>
<!-- 小程序系统客服按钮 -->
<button
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>
<!-- 电话按钮 -->
<view <view
v-if="fixBtnShow" v-if="fixBtnShow"
class="btn-item common-bg" class="btn-item common-bg"
@click="call()" @click="call"
:style="[{ backgroundImage: phoneimg ? `url( $ {phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]" :style="[{ backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
> >
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text> <text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
</view> </view>
@@ -73,12 +87,7 @@ export default {
shopInfo: null, shopInfo: null,
currentLangIndex: 0, currentLangIndex: 0,
langIndexMap: {}, langIndexMap: {},
kefuList: [ customerService: null,
{ id: 'weixin-official', name: '微信官方客服', isOfficial: true, type: 'weapp' },
{ id: 'custom-kefu', name: '自定义在线客服', isOfficial: false, type: 'custom' },
{ id: 'qyweixin-kefu', name: '企业微信客服', isOfficial: false, type: 'qyweixin' }
],
selectedKefu: null,
}; };
}, },
computed: { computed: {
@@ -89,10 +98,10 @@ export default {
return this.shopInfo?.aiAgentimg || ''; return this.shopInfo?.aiAgentimg || '';
}, },
kefuimg() { kefuimg() {
return this.shopInfo?.kefuimg || this. $util.getDefaultImage().kefu; return this.shopInfo?.kefuimg || this.$util.getDefaultImage().kefu;
}, },
phoneimg() { phoneimg() {
return this.shopInfo?.phoneimg || this. $util.getDefaultImage().phone; return this.shopInfo?.phoneimg || this.$util.getDefaultImage().phone;
}, },
tel() { tel() {
return this.shopInfo?.mobile || ''; return this.shopInfo?.mobile || '';
@@ -111,39 +120,36 @@ export default {
return this.shopInfo?.floatingButton?.button || {}; return this.shopInfo?.floatingButton?.button || {};
}, },
unreadCount() { unreadCount() {
return this. $store.state.aiUnreadCount || 0; const customerService = createCustomerService(this);
return customerService.getUnreadCount();
}, },
// ✅ 新增:根据当前客服类型动态返回图标
currentKefuImg() { currentKefuImg() {
if (!this.shopInfo) return '';
const customerService = createCustomerService(this); const customerService = createCustomerService(this);
const config = customerService.getPlatformConfig(); return customerService.getKefuButtonIcon();
if (config?.type === 'aikefu') {
return this.aiAgentimg;
} else if (config?.type === 'wxwork' || config?.type === 'qyweixin') {
// 企业微信客服专用图标
return this.aiAgentimg;
}
// 默认客服图标
return this.kefuimg;
}, },
// ✅ 新增:判断是否为微信官方客服
isWeappOfficialKefu() { isWeappOfficialKefu() {
if (!this.shopInfo) return false;
const customerService = createCustomerService(this); const customerService = createCustomerService(this);
const config = customerService.getPlatformConfig(); return customerService.isWeappOfficialKefu();
return config?.type === 'weapp';
}, },
// ✅ 新增:判断是否需要同时显示小程序系统客服 isAIKefu() {
showWeappSystemKefu() {
if (!this.shopInfo) return false;
const customerService = createCustomerService(this); const customerService = createCustomerService(this);
const config = customerService.getPlatformConfig(); return customerService.isAIKefu();
// 检查附加设置是否开启了同时显示小程序系统客服,且当前客服类型不是小程序系统客服 },
return (config?.show_system_service === true || config?.show_system_service === '1') && config?.type !== 'weapp'; isWxworkKefu() {
const customerService = createCustomerService(this);
return customerService.isWxworkKefu();
},
isThirdKefu() {
const customerService = createCustomerService(this);
return customerService.isThirdKefu();
},
showWeappSystemKefu() {
const customerService = createCustomerService(this);
return customerService.shouldShowExtraWeappSystemKefu();
},
hasValidKefuConfig() {
const customerService = createCustomerService(this);
return customerService.hasValidKefuConfig();
}, },
//根据显示的按钮数量动态计算容器高度 //根据显示的按钮数量动态计算容器高度
containerHeight() { containerHeight() {
@@ -162,7 +168,6 @@ export default {
watch: { watch: {
shopInfo: { shopInfo: {
handler(newVal) { handler(newVal) {
// 可在此添加额外逻辑(如埋点、通知等),当前无需操作
}, },
immediate: true immediate: true
} }
@@ -170,7 +175,6 @@ export default {
created() { created() {
this.initLanguage(); this.initLanguage();
this.pageCount = getCurrentPages().length; this.pageCount = getCurrentPages().length;
uni.getStorage({ uni.getStorage({
key: 'shopInfo', key: 'shopInfo',
success: (e) => { success: (e) => {
@@ -181,15 +185,17 @@ export default {
console.warn('未获取到 shopInfo,使用默认设置'); console.warn('未获取到 shopInfo,使用默认设置');
} }
}); });
// 初始化 customerService 实例
this.customerService = createCustomerService(this);
}, },
methods: { methods: {
initLanguage() { initLanguage() {
this.langList = this. $langConfig.list(); this.langList = this.$langConfig.list();
this.langIndexMap = {}; this.langIndexMap = {};
for (let i = 0; i < this.langList.length; i++) { for (let i = 0; i < this.langList.length; i++) {
this.langIndexMap[i] = this.langList[i].value; this.langIndexMap[i] = this.langList[i].value;
} }
const savedLang = uni.getStorageSync('lang'); const savedLang = this.$langConfig.getCurrentLocale();
if (savedLang) { if (savedLang) {
for (let i = 0; i < this.langList.length; i++) { for (let i = 0; i < this.langList.length; i++) {
if (this.langList[i].value === savedLang) { if (this.langList[i].value === savedLang) {
@@ -201,118 +207,37 @@ export default {
this.currentLangIndex = 0; this.currentLangIndex = 0;
} }
}, },
call() { call() {
if (this.tel) { this.customerService.makePhoneCallByNumber(this.tel);
uni.makePhoneCall({ phoneNumber: this.tel + '' });
} else {
uni.showToast({ title: '暂无联系电话', icon: 'none' });
}
}, },
toggleLanguage() { toggleLanguage() {
this.currentLangIndex = this.currentLangIndex === 0 ? 1 : 0; this.currentLangIndex = this.currentLangIndex === 0 ? 1 : 0;
const targetLang = this.langIndexMap[this.currentLangIndex]; const targetLang = this.langIndexMap[this.currentLangIndex];
this. $langConfig.change(targetLang);
if (uni.getSystemInfoSync().platform === 'browser') { uni.setStorageSync("lang", targetLang);
setTimeout(() => {
window.location.reload(); const pages = getCurrentPages();
}, 100); const currentPage = pages[pages.length - 1];
} const currentRoute = currentPage.route;
uni.reLaunch({
url: '/' + currentRoute
});
}, },
// ✅ 核心方法:统一客服入口
handleUnifiedKefuClick() { handleUnifiedKefuClick() {
const customerService = createCustomerService(this); this.customerService.handleUnifiedKefuClick({
const validation = customerService.validateConfig();
console.log('【客服配置验证】', validation);
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const platformConfig = customerService.getPlatformConfig();
console.log('【当前客服配置】', platformConfig);
// 检查企业微信配置
if (platformConfig.type === 'wxwork') {
const wxworkConfig = customerService.getWxworkConfig();
console.log('【企业微信配置】', wxworkConfig);
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
console.warn('企业微信配置不完整,使用原生客服');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
}
// #endif
// #ifdef H5
if (!wxworkConfig?.contact_url && !platformConfig.wxwork_url) {
console.error('企业微信链接未配置');
uni.showToast({ title: '企业微信链接未配置', icon: 'none' });
return;
}
// #endif
}
// 直接调用统一处理方法,由 CustomerService 内部根据配置路由
try {
customerService.handleCustomerClick({
sendMessageTitle: '来自悬浮按钮的咨询', sendMessageTitle: '来自悬浮按钮的咨询',
sendMessagePath: '/pages/index/index' sendMessagePath: '/pages/index/index'
}); });
} catch (error) {
console.error('客服处理失败:', error);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
},
// 以下方法保留用于 actionSheet(如仍需手动选择)
openKefuSelectPopup() {
const kefuNames = this.kefuList.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
this.selectedKefu = this.kefuList[res.tapIndex];
const cs = createCustomerService(this, this.selectedKefu);
if (this.selectedKefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (this.selectedKefu.id === 'qyweixin-kefu') {
// 处理企业微信客服
if (uni.getSystemInfoSync().platform === 'wechat') {
// 小程序端:跳转到企业微信客服
uni.navigateTo({
url: '/pages_tool/qyweixin-kefu/index'
});
} else {
// H5端:跳转到企业微信链接
const qyweixinUrl = this.shopInfo.qyweixinUrl; // 后端返回的企业微信链接
if (qyweixinUrl) {
window.location.href = qyweixinUrl;
} else {
uni.showToast({ title: '企业微信客服未配置', icon: 'none' });
}
}
} else {
cs.handleCustomerClick();
}
}
});
} }
} }
} }
</script> </script>
<style scoped> <style lang="scss" scoped>
.fixed-box { .fixed-box {
position: fixed; position: fixed;
right: 0rpx; right: 0rpx;
@@ -333,16 +258,25 @@ export default {
} }
.btn-item { .btn-item {
display: flex;
justify-content: center;
text-align: center;
flex-direction: column;
line-height: 1;
margin: 14rpx 0;
transition: 0.1s;
color: var(--hover-nav-text-color);
border-radius: 50%;
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
background: #fff; padding: 0;
border-radius: 50%; overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin: 14rpx 0;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
} }
.common-bg {
background-color: var(--hover-nav-bg-color);
}
.btn-item text { .btn-item text {
font-size: 28rpx; font-size: 28rpx;
} }
@@ -357,7 +291,6 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin: 14rpx 0; margin: 14rpx 0;
background: #fff;
border-radius: 50rpx; border-radius: 50rpx;
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
@@ -371,22 +304,6 @@ export default {
font-weight: bold; font-weight: bold;
} }
.unread-badge {
position: absolute;
top: -5rpx;
right: -5rpx;
background-color: #ff4544;
color: white;
border-radius: 20rpx;
min-width: 30rpx;
height: 30rpx;
font-size: 20rpx;
line-height: 30rpx;
text-align: center;
padding: 0 8rpx;
box-shadow: 0 2rpx 10rpx rgba(255, 69, 68, 0.3);
}
.ai-icon { .ai-icon {
font-size: 40rpx; font-size: 40rpx;
width: 100%; width: 100%;

View File

@@ -321,6 +321,12 @@
"navigationBarTitleText": "AI 客服" "navigationBarTitleText": "AI 客服"
} }
}, },
{
"path": "agreement/contenr",
"style": {
"navigationBarTitleText": ""
}
},
{ {
"path": "vr/index", "path": "vr/index",
"style": { "style": {
@@ -919,6 +925,12 @@
// #endif // #endif
"navigationBarTitleText": "查看文件" "navigationBarTitleText": "查看文件"
} }
},
{
"path": "file-preview/file-preview",
"style": {
"navigationBarTitleText": "文件预览"
}
} }
] ]
} }

View File

@@ -17,7 +17,7 @@
}}</view> }}</view>
</view> </view>
<diy-index-page v-if="topIndexValue" ref="indexPage" :value="topIndexValue" :bgUrl="safeBgUrl" <diy-index-page v-if="topIndexValue" ref="indexPage" :value="topIndexValue" :bgUrl="bgUrl"
:scrollTop="scrollTop" :diyGlobal="diyData.global" class="diy-index-page"> :scrollTop="scrollTop" :diyGlobal="diyData.global" class="diy-index-page">
<template v-slot:components> <template v-slot:components>
<diy-group ref="diyGroup" v-if="diyData.value" :diyData="diyData" :scrollTop="scrollTop" <diy-group ref="diyGroup" v-if="diyData.value" :diyData="diyData" :scrollTop="scrollTop"
@@ -29,7 +29,7 @@
</diy-index-page> </diy-index-page>
<view v-else class="bg-index" <view v-else class="bg-index"
:style="{ backgroundImage: backgroundUrlStyle, paddingTop: paddingTop, marginTop: marginTop }"> :style="{ backgroundImage: backgroundUrl, paddingTop: paddingTop, marginTop: marginTop }">
<diy-group ref="diyGroup" v-if="diyData.value" :diyData="diyData" :scrollTop="scrollTop" <diy-group ref="diyGroup" v-if="diyData.value" :diyData="diyData" :scrollTop="scrollTop"
:followOfficialAccount="followOfficialAccount" /> :followOfficialAccount="followOfficialAccount" />
<ns-copyright v-show="isShowCopyRight" /> <ns-copyright v-show="isShowCopyRight" />
@@ -46,7 +46,7 @@
:src="$util.img(item.imageUrl)" width="100%"></image> :src="$util.img(item.imageUrl)" width="100%"></image>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view class="small-bot-close" @click="closePopupWindow"> <view bindtap="adverclose" class="small-bot-close" @click="closePopupWindow">
<i class="iconfont icon-round-close" style="color:#fff"></i> <i class="iconfont icon-round-close" style="color:#fff"></i>
</view> </view>
</view> </view>
@@ -114,7 +114,27 @@
</view> </view>
</uni-popup> </uni-popup>
</view> </view>
<hover-nav :need="true"></hover-nav> <hover-nav :need="true"></hover-nav>
<!-- 隐私协议弹窗 UI (新增) -->
<view v-if="showPrivacyModal" class="privacy-modal-mask" @touchmove.prevent>
<view class="privacy-modal-container">
<view class="modal-header">用户隐私保护提示</view>
<view class="modal-body">
<text>感谢您使用本应用,在使用前您应当阅读并同意</text>
<text class="link" @click="viewPrivacy('privacy')">《隐私条款》</text>
<text class="link" @click="viewPrivacy('agreement')">《用户服务协议》</text>
<text>,当点击同意并继续时,即表示您已理解并同意该条款内容,该条款将对您产生法律约束力;如您不同意,将无法继续使用相关功能。</text>
</view>
<view class="modal-footer">
<view class="btn btn-disagree" @click="handleDisagree">不同意</view>
<view class="btn btn-agree" @click="handleAgree">同意并继续</view>
</view>
</view>
</view>
<!-- 隐私协议组件 (原有) -->
<privacy-popup ref="privacyPopup"></privacy-popup> <privacy-popup ref="privacyPopup"></privacy-popup>
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top> <to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
<ns-login ref="login"></ns-login> <ns-login ref="login"></ns-login>
@@ -129,37 +149,124 @@ import indexJs from './public/js/index.js';
export default { export default {
mixins: [diyJs, scroll, indexJs], mixins: [diyJs, scroll, indexJs],
data() { data() {
return { return {
diyData: { global: {}, value: null }, minScrollTop: 100,
followOfficialAccount: {},
wechatQrcode: '', wechatQrcode: '',
adv: { advshow: -1, list: [] }, diyRoute: '/pages/index/index',
// ❌ 不要定义 siteInfo、bgUrl,除非你明确知道它们不会冲突 showPrivacyModal: false // 新增:控制隐私弹窗显示
}; };
}, },
onLoad(option) {
computed: { // #ifdef H5
// ✅ 安全获取 bgUrl(用于 diy-index-page 的 prop) if (option.module && option.module == 'locationPicker') {
safeBgUrl() { option.name = '';
// 优先取全局配置中的 bgUrl,没有则用组件自己的 bgUrl(如有),否则为空 }
return this.diyData?.global?.bgUrl || this.bgUrl || ''; // #endif
this.name = option.name || 'DIY_VIEW_INDEX';
}, },
onShow() {
// ✅ 安全生成 background-image 样式字符串(用于 fallback 区域) this.getFollowQrcode();
backgroundUrlStyle() { // 页面显示时检查隐私设置
const url = this.diyData?.global?.bgUrl || this.bgUrl || ''; this.checkPrivacySetting();
return url ? `url(${url})` : 'none'; },
mounted() {
this.unsubscribe = this.$eventBus?.onMany([
'rubik-cube-tap',
'picture-tap',
'text-tap',
'goods-list-tap',
'presale-tap',
'notes-tap',
'merch-list-tap',
'map-tap',
'img-ads-tap',
'many-goods-list-tap',
'search-tap',
], this._handleDiyGroupInteractionEvent);
},
beforeDestroy() {
if (this.unsubscribe) this.unsubscribe();
},
methods: {
// 检查隐私弹窗逻辑
checkPrivacySetting() {
// 模拟检查本地存储,看用户是否已经同意过
// 实际项目中请替换为你真实的存储逻辑
const hasAgreed = uni.getStorageSync('HAS_AGREED_PRIVACY');
if (!hasAgreed) {
// 延迟显示,防止遮挡页面渲染
setTimeout(() => {
this.showPrivacyModal = true;
}, 500);
} }
}, },
// 如果你的 mixin 中已经处理了数据加载,这里可以留空 // 用户点击同意
// 否则建议在 created/mounted 中初始化默认值或请求数据 handleAgree() {
created() { // 保存同意状态
// 可选:如果 mixin 没有初始化 diyData,这里再兜底一次 uni.setStorageSync('HAS_AGREED_PRIVACY', true);
if (!this.diyData) { this.showPrivacyModal = false;
this.diyData = { global: {}, value: null }; },
// 用户点击不同意
handleDisagree() {
// 微信小程序环境下,不同意通常退出小程序
// #ifdef MP-WEIXIN
uni.exitMiniProgram();
// #endif
// H5 或 App 下可以提示或直接不显示内容
// #ifndef MP-WEIXIN
uni.showToast({
title: '您已拒绝协议,无法使用',
icon: 'none',
duration: 2000
});
// 这里可以选择关闭页面或保持弹窗
// this.showPrivacyModal = false;
// #endif
},
// 查看具体条款(可选功能)
viewPrivacy(type) {
// 这里可以跳转到具体的条款页面,或者使用你现有的 privacy-popup 组件
// 假设你有现成的组件逻辑:
if (this.$refs.privacyPopup) {
// 假设 privacy-popup 组件有一个 show 方法
// this.$refs.privacyPopup.show(type);
}
uni.showToast({
title: '请在此处实现查看条款逻辑',
icon: 'none'
});
},
getFollowQrcode() {
if (!this.$util.isWeiXin()) return;
this.$api.sendRequest({
url: '/wechat/api/wechat/followqrcode',
success: res => {
if (res.code >= 0 && res.data) {
this.wechatQrcode = res.data.qrcode;
}
}
});
},
officialAccountsOpen() {
this.$refs.officialAccountsPopup.open();
},
officialAccountsClose() {
this.$refs.officialAccountsPopup.close();
},
_handleDiyGroupInteractionEvent(payload) {
// #ifdef H5
return true;
// #endif
if (this.storeToken) {
return true;
}
this.$refs.login?.open();
return false;
} }
} }
}; };
@@ -207,10 +314,77 @@ export default {
.small-bot-close i { .small-bot-close i {
font-size: 60rpx; font-size: 60rpx;
} }
</style>
/* 新增:隐私弹窗样式 */
.privacy-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6); // 半透明背景
z-index: 9999; // 确保层级最高
display: flex;
justify-content: center;
align-items: center;
}
.privacy-modal-container {
width: 80%;
background-color: #ffffff;
border-radius: 12rpx;
padding: 40rpx 30rpx;
text-align: center;
}
.modal-header {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.modal-body {
font-size: 28rpx;
color: #666;
line-height: 1.6;
text-align: left;
margin-bottom: 30rpx;
}
.link {
color: #007aff; // 蓝色链接
margin: 0 4rpx;
}
.modal-footer {
display: flex;
justify-content: space-between;
padding: 0 20rpx;
}
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
text-align: center;
margin: 0 15rpx;
}
.btn-disagree {
background-color: #f5f5f5;
color: #333;
}
.btn-agree {
background-color: #07c160; // 微信绿
color: #fff;
}
</style>
<style scoped> <style scoped>
.swiper /deep/ .uni-swiper-dots-horizontal { .swiper ::v-deep .uni-swiper-dots-horizontal {
left: 40%; left: 40%;
bottom: 40px bottom: 40px
} }
@@ -223,26 +397,26 @@ export default {
width: 80%; width: 80%;
} }
/deep/.diy-index-page .uni-popup .uni-popup__wrapper-box { ::v-deep .diy-index-page .uni-popup .uni-popup__wrapper-box {
border-radius: 0; border-radius: 0;
} }
/deep/ .placeholder { ::v-deep .placeholder {
height: 0; height: 0;
} }
/deep/::-webkit-scrollbar { ::v-deep ::-webkit-scrollbar {
width: 0; width: 0;
height: 0; height: 0;
background-color: transparent; background-color: transparent;
display: none; display: none;
} }
/deep/ .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { ::v-deep .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
max-height: unset !important; max-height: unset !important;
} }
/deep/ .mescroll-totop { ::v-deep .mescroll-totop {
/* #ifdef H5 */ /* #ifdef H5 */
right: 28rpx !important; right: 28rpx !important;
bottom: 200rpx !important; bottom: 200rpx !important;

View File

@@ -3,7 +3,8 @@ export default {
return { return {
minScrollTop: 100, // 设置回到顶端按钮显示要求,最小页面滚动距离 minScrollTop: 100, // 设置回到顶端按钮显示要求,最小页面滚动距离
wechatQrcode: '', // 公众号二维码 wechatQrcode: '', // 公众号二维码
diyRoute: '/pages/index/index' diyRoute: '/pages/index/index',
showPrivacyModal: false // 新增:控制隐私弹窗显示的变量
}; };
}, },
onLoad(option) { onLoad(option) {
@@ -19,6 +20,9 @@ export default {
onShow() { onShow() {
// 获取关注公众号二维码 // 获取关注公众号二维码
this.getFollowQrcode(); this.getFollowQrcode();
// 打开隐私弹窗
this.openPrivacyModal();
}, },
mounted() { mounted() {
// 监听图片、文字、商品列表等组件的点击事件 // 监听图片、文字、商品列表等组件的点击事件
@@ -41,6 +45,39 @@ export default {
if (this.unsubscribe) this.unsubscribe(); if (this.unsubscribe) this.unsubscribe();
}, },
methods: { methods: {
// 检查隐私设置(保留原有逻辑,如果不需要可删除)
checkPrivacySetting() {
setTimeout(() => {
if (this.$refs.privacyPopup) {
this.$refs.privacyPopup.proactive();
}
}, 100);
},
// 新增:打开隐私弹窗的方法
openPrivacyModal() {
// 延迟一点执行,防止页面加载卡顿
setTimeout(() => {
this.showPrivacyModal = true;
}, 500);
},
// 新增:处理用户点击“同意并继续”
handlePrivacyAgree() {
this.showPrivacyModal = false;
// 这里可以添加保存到本地存储的逻辑,表示用户已同意
uni.setStorageSync('hasAgreedPrivacy', true);
},
// 新增:处理用户点击“不同意”
handlePrivacyDisagree() {
this.showPrivacyModal = false;
// 如果不同意,通常退出小程序或关闭页面
uni.navigateBack({
delta: 1
});
},
// 关注公众号 // 关注公众号
getFollowQrcode() { getFollowQrcode() {
if (!this.$util.isWeiXin()) return; if (!this.$util.isWeiXin()) return;
@@ -79,4 +116,4 @@ export default {
return false; return false;
} }
} }
} };

View File

@@ -38,8 +38,8 @@
<!-- AI消息 --> <!-- AI消息 -->
<view v-else class="ai-message"> <view v-else class="ai-message">
<view class="avatar"> <view class="avatar">
<!-- 直接使用内部固定的AI头像 --> <!-- AI头像使用🤖 -->
<image :src="aiAvatar" mode="aspectFill" /> <text>🤖</text>
</view> </view>
<view class="message-content"> <view class="message-content">
<!-- AI昵称固定,不可修改 - 左移到气泡上方 --> <!-- AI昵称固定,不可修改 - 左移到气泡上方 -->
@@ -183,19 +183,11 @@
<view class="input-area"> <view class="input-area">
<view class="input-tools"> <view class="input-tools">
<button class="tool-btn" @click="showMoreTools"> <button class="tool-btn" @click="showMoreTools">
<text class="iconfont icon-plus">📌</text> <text class="iconfont icon-plus">...</text>
</button> </button>
<button class="tool-btn" @click="toggleVoiceInput"> <button class="tool-btn" @click="toggleVoiceInput">
<text class="iconfont" :class="voiceInputing ? 'icon-stop' : 'icon-voice'">🎤</text> <text class="iconfont" :class="voiceInputing ? 'icon-stop' : 'icon-voice'">🎤</text>
</button> </button>
<!-- 更换用户头像按钮 -->
<button class="tool-btn" @click="openAvatarSelector">
<text class="iconfont icon-user">📷</text>
</button>
<!-- 新增:更换用户昵称按钮 -->
<button class="tool-btn" @click="openNicknameEditor">
<text class="iconfont icon-edit">✏️</text>
</button>
</view> </view>
<view class="input-container"> <view class="input-container">
@@ -1495,6 +1487,9 @@ $radius-lg: 36rpx;
background-color: $bg-card; background-color: $bg-card;
border: 2rpx solid #f0f4f8; border: 2rpx solid #f0f4f8;
transition: transform 0.2s ease; transition: transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
&:hover { &:hover {
transform: scale(1.03); transform: scale(1.03);
@@ -1505,6 +1500,11 @@ $radius-lg: 36rpx;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
text {
font-size: 56rpx;
line-height: 1;
}
} }
.message-content { .message-content {
@@ -2070,6 +2070,7 @@ $radius-lg: 36rpx;
.input-tools { .input-tools {
display: flex; display: flex;
margin-bottom: 16rpx; margin-bottom: 16rpx;
align-items: flex-start;
.tool-btn { .tool-btn {
width: 64rpx; width: 64rpx;
@@ -2077,6 +2078,7 @@ $radius-lg: 36rpx;
border-radius: 50%; border-radius: 50%;
background-color: $bg-main; background-color: $bg-main;
margin-right: 16rpx; margin-right: 16rpx;
margin-top: -8rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -14,7 +14,7 @@
</view> </view>
<view class="header-right"> <view class="header-right">
<button class="menu-btn" v-show="showMenuButton" @click="showMenu"> <button class="menu-btn" v-show="showMenuButton" @click="showMenu">
<text class="iconfont icon-ellipsis"></text> <text>⚙️</text>
</button> </button>
<!-- 占位元素,确保布局平衡 --> <!-- 占位元素,确保布局平衡 -->
<view v-show="!showMenuButton" class="placeholder"></view> <view v-show="!showMenuButton" class="placeholder"></view>
@@ -44,6 +44,41 @@
@product-view="onProductView" @product-view="onProductView"
@input-change="onInputChange" /> @input-change="onInputChange" />
</view> </view>
<!-- 设置面板 -->
<view v-if="showSettingsPanel" class="settings-popup">
<view class="settings-mask" @click="hideSettings"></view>
<view class="settings-panel">
<view class="settings-header">
<text class="settings-title">设置</text>
<view class="close-btn" @click="hideSettings">
<text>✕</text>
</view>
</view>
<view class="settings-content">
<view class="setting-item" @click="openAvatarSelector">
<view class="setting-icon">
<text class="iconfont icon-user">📷</text>
</view>
<view class="setting-info">
<text class="setting-label">更换头像</text>
<text class="setting-desc">自定义您的聊天头像</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
<view class="setting-item" @click="openNicknameEditor">
<view class="setting-icon">
<text class="iconfont icon-edit">✏️</text>
</view>
<view class="setting-info">
<text class="setting-label">更换昵称</text>
<text class="setting-desc">自定义您的昵称</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
</view>
</view>
</view>
</view> </view>
</template> </template>
@@ -81,6 +116,7 @@ export default {
showCustomNavbar: true, // 是否显示自定义导航栏 showCustomNavbar: true, // 是否显示自定义导航栏
showBackButton: false, // 是否显示返回按钮 showBackButton: false, // 是否显示返回按钮
showMenuButton: true, // 是否显示设置菜单按钮 showMenuButton: true, // 是否显示设置菜单按钮
showSettingsPanel: false, // 是否显示设置面板
// --- 聊天内容区域 --- // --- 聊天内容区域 ---
scrollViewHeight: '0px', scrollViewHeight: '0px',
@@ -330,9 +366,21 @@ export default {
}, },
showSettings() { showSettings() {
uni.navigateTo({ this.showSettingsPanel = true;
url: '/pages/settings/index' },
});
hideSettings() {
this.showSettingsPanel = false;
},
openAvatarSelector() {
this.hideSettings();
this.$refs.chat.openAvatarSelector();
},
openNicknameEditor() {
this.hideSettings();
this.$refs.chat.openNicknameEditor();
}, },
showHelp() { showHelp() {
@@ -511,7 +559,7 @@ export default {
background-color: white; /* 白色 */ background-color: white; /* 白色 */
overflow: hidden; overflow: hidden;
position: fixed; position: fixed;
top: 0; top: 48px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
@@ -611,4 +659,128 @@ export default {
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;
} }
/* 设置面板 */
.settings-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
.settings-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(6rpx);
}
.settings-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
border-radius: 24rpx;
padding: 32rpx;
width: calc(100% - 80rpx);
max-width: 520rpx;
box-shadow: 0 8rpx 36rpx rgba(0, 0, 0, 0.15);
box-sizing: border-box;
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
border-bottom: 2rpx solid #f0f4f8;
padding-bottom: 16rpx;
.settings-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.close-btn {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #999;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
}
}
}
.settings-content {
display: flex;
flex-direction: column;
gap: 16rpx;
.setting-item {
display: flex;
align-items: center;
padding: 24rpx;
border-radius: 16rpx;
background-color: #f8fafc;
border: 2rpx solid #f0f4f8;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
border-color: #d9e3ed;
}
.setting-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #c4e0ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
flex-shrink: 0;
}
.setting-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.setting-label {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.setting-desc {
font-size: 22rpx;
color: #999;
}
}
.iconfont.icon-right {
font-size: 36rpx;
color: #ccc;
flex-shrink: 0;
}
}
}
}
}
</style> </style>

View File

@@ -7,7 +7,7 @@
"setting": { "setting": {
"urlCheck": true, "urlCheck": true,
"es6": true, "es6": true,
"enhance": false, "enhance": true,
"postcss": true, "postcss": true,
"preloadBackgroundData": false, "preloadBackgroundData": false,
"minified": true, "minified": true,
@@ -39,19 +39,27 @@
"packNpmManually": false, "packNpmManually": false,
"packNpmRelationList": [], "packNpmRelationList": [],
"minifyWXSS": true, "minifyWXSS": true,
"compileWorklet": false, "condition": true,
"swc": false,
"disableSWC": true,
"minifyWXML": true, "minifyWXML": true,
"compileWorklet": true,
"localPlugins": false, "localPlugins": false,
"disableUseStrict": false, "disableUseStrict": false,
"useCompilerPlugins": false, "useCompilerPlugins": false
"condition": false,
"swc": false,
"disableSWC": true
}, },
"compileType": "miniprogram", "compileType": "miniprogram",
"libVersion": "3.12.0", "libVersion": "2.16.1",
"appid": "wx29215aa1bd97bbd6", "appid": "wx29215aa1bd97bbd6",
"projectname": "uniapp", "projectname": "uniapp",
"debugOptions": {
"hidedInDevtools": []
},
"scripts": {},
"staticServerOptions": {
"baseURL": "",
"servePath": ""
},
"isGameTourist": false, "isGameTourist": false,
"condition": { "condition": {
"search": { "search": {
@@ -72,7 +80,5 @@
"miniprogram": { "miniprogram": {
"list": [] "list": []
} }
}, }
"simulatorPluginLibVersion": {},
"editorSetting": {}
} }

View File

@@ -78,20 +78,23 @@
1. 使用 HBuilderX 打开项目 1. 使用 HBuilderX 打开项目
2. 打开项目根目录的 `manifest.json` 文件,切换至可视化配置界面: 2. 打开项目根目录的 `manifest.json` 文件,切换至可视化配置界面:
1. 依次点击「Web 配置」→「运行的基础配置」 1. 依次点击「Web 配置」→「运行的基础配置」
2. 修改路径中的客户编号 2. 根据情况查看是否修改路径中/hwappx/????/的客户编号
3. 📌 **通用版**保留原有 `/hwappx/common/` 📌 **通用版**保留原有 `/hwappx/common/`
📌 **测试版**保留原有 `/hwappx/test/`
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
💡 **示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号 3. 选择菜单栏「发行」 → 「自定义发行」 →(「H5-xcx.aigc-quickapp.com」查看.local.config.js中站点的网址前缀,站点的网址前缀是什么就选自定义发行中相同的格式)
例如:网址是5g.aigc-quickapp.com,自定义发行就选H5-5g.aigc-quickapp.com
3. 选择菜单栏「发行」 → 「自定义发行」 →「H5-xcx.aigc-quickapp.com」,修改以下配置: 网址是dev.aigc-quickapp.com,自定义发行就选H5-xcx.aigc-quickapp.com
4. 修改以下配置:
1. **网站标题**:快应用 1. **网站标题**:快应用
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com) 2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
3. 确认后点击「发行」等待构建完成 3. 确认后点击「发行」等待构建完成
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步 ⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web` 💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
4. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包 5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
*仅打包文件,不包含外层 web 目录* *仅打包文件,不包含外层 web 目录*
##### 4.2.1.2 【重命名】 ##### 4.2.1.2 【重命名】

View File

@@ -416,7 +416,7 @@ const store = new Vuex.Store({
}, },
// 生成主题颜色CSS变量 // 生成主题颜色CSS变量
themeColorSet() { themeColorSet() {
console.log('样式颜色设置...'); // console.log('样式颜色设置...');
let theme = this.state.themeStyle; let theme = this.state.themeStyle;
if (!theme?.main_color || !theme?.aux_color) return; if (!theme?.main_color || !theme?.aux_color) return;
try { try {
@@ -440,7 +440,7 @@ const store = new Vuex.Store({
} catch (e) { } catch (e) {
console.error('设置主题颜色失败', e); console.error('设置主题颜色失败', e);
} }
console.log('themeColor => ', this.state.themeColor); // console.log('themeColor => ', this.state.themeColor);
} }
} }
}) })