diff --git a/common/js/config.js b/common/js/config.js index 4b3fbbc..ec8d65b 100644 --- a/common/js/config.js +++ b/common/js/config.js @@ -77,18 +77,16 @@ export default { * 然后将 site.js 文件放到 `unpackage\dist\build\mp-weixin\` 目录下面 */ // 商户ID - uniacid: 1, //825 + uniacid: uniacid, //825 //api请求地址 - baseUrl: 'https://dev.aigc-quickapp.com/', - // baseUrl: 'http://localhost:8010/', + baseUrl: domain, // 图片域名 - imgDomain: 'https://dev.aigc-quickapp.com/', - //imgDomain: 'http://localhost:8010/', + imgDomain: domain, // H5端域名 - h5Domain: 'https://dev.aigc-quickapp.com/', + h5Domain: domain, // // api请求地址 // baseUrl: 'https://tsaas.liveplatform.cn/', diff --git a/common/js/customer-service.js b/common/js/customer-service.js index 4960919..bd1b508 100644 --- a/common/js/customer-service.js +++ b/common/js/customer-service.js @@ -2,13 +2,68 @@ * 客服统一处理服务 * 整合各种客服方式,提供统一的调用接口 */ -export class CustomerService { - constructor(vueInstance, externalConfig = null) { +class CustomerService { + constructor(vueInstance, externalConfig = {}) { + if (!vueInstance.$lang) { + throw new Error('CustomerService 必须在 Vue 实例中初始化'); + } + this.vm = vueInstance; this.externalConfig = externalConfig; // 外部传入的最新配置(优先级最高) this.latestPlatformConfig = null; } + getSupoortKeFuList() { + if (!this.vm) return []; + + const vm = this.vm; + + return [ + { + id: 'weixin-official', + name: vm.$lang('customer.weChatKefu'), + isOfficial: true, + type: 'weapp' + }, + { + id: 'custom-kefu', + name: vm.$lang('customer.systemKefu'), + isOfficial: false + }, + { + id: 'qyweixin-kefu', + name: vm.$lang('customer.weChatWorkKefu'), + isOfficial: false + }, + ] + } + + /** + * 打开客服选择弹窗 + */ + openCustomerSelectPopupDialog() { + const kefu_list = this.getSupoortKeFuList(); + const kefuNames = kefu_list.map(item => item.name); + + uni.showActionSheet({ + itemList: kefuNames, + success: (res) => { + const kefu = kefu_list[res.tapIndex]; + this.externalConfig = kefu ?? this.externalConfig ?? {}; + if (kefu.isOfficial) { + uni.openCustomerServiceConversation({ + sessionFrom: 'weapp', + showMessageCard: true + }); + } else if (kefu.id === 'custom-kefu') { + this.handleCustomerClick(); + } else if (kefu.id === 'qyweixin-kefu') { + this.handleQyWeixinKefuClick(); + } + } + }); + } + /** * 强制刷新配置(支持传入外部配置) * @param {Object} externalConfig 外部最新配置 @@ -82,7 +137,7 @@ export class CustomerService { validateConfig() { const config = this.getPlatformConfig(); const wxworkConfig = this.getWxworkConfig(); - + const result = { isValid: true, errors: [], @@ -122,35 +177,11 @@ export class CustomerService { } /** - * 跳转到Dify客服页面 + * 跳转到AI客服页面 */ - openDifyService() { - try { - if (this.vm.setAiUnreadCount) { - this.vm.setAiUnreadCount(0); - } - // 强制跳转,忽略框架层的封装 - uni.redirectTo({ - url: '/pages_tool/ai-chat/index', - fail: (err) => { - // 兜底:使用window.location跳转(H5) - // #ifdef H5 - window.location.href = '/pages_tool/ai-chat/index'; - // #endif - console.error('跳转Dify客服失败:', err); - uni.showToast({ - title: '跳转客服失败', - icon: 'none' - }); - } - }); - } catch (e) { - console.error('跳转Dify客服异常:', e); - uni.showToast({ - title: '跳转客服失败', - icon: 'none' - }); - } + openAIKeFuService() { + const vm = this.vm; + vm.$util.redirectTo(vm.$util.AI_CHAT_PAGE_URL); } /** @@ -170,7 +201,7 @@ export class CustomerService { } const config = this.getPlatformConfig(); - const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options; + const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {}; if (config.type === 'none') { this.showNoServicePopup(); @@ -180,7 +211,7 @@ export class CustomerService { // 核心分支:根据最新的type处理 switch (config.type) { case 'aikefu': - this.openDifyService(); + this.openAIKeFuService(); break; case 'wxwork': this.openWxworkService(false, config, options); @@ -211,7 +242,7 @@ export class CustomerService { openWxworkService(useOriginalService = false, servicerConfig = null, options = {}) { const config = servicerConfig || this.getPlatformConfig(); const wxworkConfig = this.getWxworkConfig(); - const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options; + const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {}; // #ifdef MP-WEIXIN if (wxworkConfig?.enable && wxworkConfig?.contact_url && !useOriginalService) { @@ -292,7 +323,7 @@ export class CustomerService { * @param {Object} options 选项参数 */ handleCustomWeappService(config, options = {}) { - const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options; + const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {}; if (config.customServiceUrl) { let url = config.customServiceUrl; @@ -300,11 +331,11 @@ export class CustomerService { if (sendMessageTitle) params.push(`title=${encodeURIComponent(sendMessageTitle)}`); if (sendMessagePath) params.push(`path=${encodeURIComponent(sendMessagePath)}`); if (sendMessageImg) params.push(`img=${encodeURIComponent(sendMessageImg)}`); - + if (params.length > 0) { url += (url.includes('?') ? '&' : '?') + params.join('&'); } - + uni.navigateTo({ url: url, fail: (err) => { @@ -328,7 +359,7 @@ export class CustomerService { // #ifdef H5 window.open(config.thirdPartyServiceUrl, '_blank'); // #endif - + // #ifdef MP-WEIXIN if (config.thirdPartyMiniAppId) { wx.navigateToMiniProgram({ @@ -381,7 +412,7 @@ export class CustomerService { console.log('支付宝小程序客服', config); switch (config.type) { case 'aikefu': - this.openDifyService(); + this.openAIKeFuService(); break; case 'third': this.openThirdService(config); @@ -395,26 +426,29 @@ export class CustomerService { /** * 拨打电话 */ - makePhoneCall() { - this.vm.$api.sendRequest({ - url: '/api/site/shopcontact', - success: res => { - if (res.code === 0 && res.data?.mobile) { + makePhoneCall(mobileNumber) { + if (mobileNumber) { + return uni.makePhoneCall({ + phoneNumber: mobileNumber + }); + } + + // 从缓存中获取电话信息 + uni.getStorage({ + key: 'shopInfo', + success: (res) => { + const shopInfo = res.data; + const mobile = shopInfo?.mobile ?? ''; + if (mobile) { uni.makePhoneCall({ - phoneNumber: res.data.mobile - }); + phoneNumber: mobile + }); } else { uni.showToast({ title: '暂无客服电话', icon: 'none' - }); + }); } - }, - fail: () => { - uni.showToast({ - title: '获取客服电话失败', - icon: 'none' - }); } }); } @@ -424,8 +458,8 @@ export class CustomerService { */ showNoServicePopup() { const siteInfo = this.vm.$store.state.siteInfo || {}; - const message = siteInfo?.site_tel - ? `请联系客服,客服电话是${siteInfo.site_tel}` + const message = siteInfo?.site_tel + ? `请联系客服,客服电话是${siteInfo.site_tel}` : '抱歉,商家暂无客服,请线下联系'; uni.showModal({ @@ -506,6 +540,6 @@ export class CustomerService { * @param {Object} externalConfig 外部最新配置 * @returns {CustomerService} 客服服务实例 */ -export function createCustomerService(vueInstance, externalConfig = null) { +export function createCustomerService(vueInstance, externalConfig = {}) { return new CustomerService(vueInstance, externalConfig); } \ No newline at end of file diff --git a/common/js/lang.js b/common/js/lang.js index 613012d..c8ca741 100644 --- a/common/js/lang.js +++ b/common/js/lang.js @@ -1,161 +1,205 @@ import { langConfig } from './config-external.js'; + // 缓存已加载的语言包 const loadedLangPacks = {}; // 处理页面目录映射 function processRoutePath(route) { - let routeParts = route.split("/"); + let routeParts = route.split("/"); - // ---- 处理页面目录映射 分包造成的,需要根据实际目录结构进行映射---- - if (routeParts[0] === 'pages_tool') { - routeParts = [routeParts[1], ...routeParts.slice(2)]; - } else if (routeParts[0] === 'pages_goods') { - routeParts[0] = 'goods'; - } else if (routeParts[0] === 'pages_member') { - routeParts[0] = 'member'; - } else if (routeParts[0] === 'pages_order') { - routeParts[0] = 'order'; - } else if (routeParts[0] === 'pages_promotion') { - const promotionModules = ['point', 'fenxiao', 'merch']; - if (routeParts[1] && promotionModules.includes(routeParts[1])) { - routeParts = [routeParts[1], ...routeParts.slice(2)]; - } - } - // ---- 处理页面目录映射 ---- - - if (routeParts[0] === 'pages') { - routeParts = routeParts.slice(1); - } - - return routeParts.join("/"); + // ---- 处理页面目录映射 分包造成的,需要根据实际目录结构进行映射---- + // 先处理特殊的分包路径 + if (routeParts[0] === 'pages_tool') { + // pages_tool 分包下的页面,直接使用子目录作为语言包路径 + routeParts = [routeParts[1], ...routeParts.slice(2)]; + } else if (routeParts[0] === 'pages_goods') { + // pages_goods 分包映射到 goods 目录 + routeParts[0] = 'goods'; + } else if (routeParts[0] === 'pages_member') { + // pages_member 分包映射到 member 目录 + routeParts[0] = 'member'; + } else if (routeParts[0] === 'pages_order') { + // pages_order 分包映射到 order 目录 + routeParts[0] = 'order'; + } else if (routeParts[0] === 'pages_promotion') { + // pages_promotion 分包特殊处理 + const promotionModules = ['point', 'fenxiao', 'merch']; + if (routeParts[1] && promotionModules.includes(routeParts[1])) { + routeParts = [routeParts[1], ...routeParts.slice(2)]; + } + } + // ---- 处理页面目录映射 ---- + + // 去掉pages目录,只保留子目录 + if (routeParts[0] === 'pages') { + routeParts = routeParts.slice(1); + } + + return routeParts.join("/"); } // 加载语言包(同步方式) function loadLangPackSync(lang, path) { - try { - if (loadedLangPacks[`${lang}_${path}`]) { - return loadedLangPacks[`${lang}_${path}`]; - } - const langData = require(`@/lang/${lang}/${path}.js`).lang; - loadedLangPacks[`${lang}_${path}`] = langData; - return langData; - } catch (error) { - console.error(`加载语言包 ${lang}/${path} 失败:`, error); - return {}; - } + try { + if (loadedLangPacks[`${lang}_${path}`]) { + return loadedLangPacks[`${lang}_${path}`]; + } + const langData = require(`@/lang/${lang}/${path}.js`).lang; + loadedLangPacks[`${lang}_${path}`] = langData; + return langData; + } catch (error) { + console.error(`加载语言包 ${lang}/${path} 失败:`, error); + return {}; + } } export default { - langList: langConfig.langList, - /** - * 解析多语言 - */ - lang(field) { - let _this = getCurrentPages()[getCurrentPages().length - 1]; - if (!_this) return; + langList: langConfig.langList, + /** + * * 解析多语言 + * @param {Object} field + */ + lang(field) { + let _this = getCurrentPages()[getCurrentPages().length - 1]; + if (!_this) return; - const locale = uni.getStorageSync('lang') || "zh-cn"; + const locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言 - let value = ''; - let langPath = ''; + let value = ''; // 存放解析后的语言值 + let langPath = ''; // 存放当前页面语言包路径 - try { - var lang = loadLangPackSync(locale, 'common'); - - let route = _this.route; - langPath = processRoutePath(route); - - let currentPageLang = loadLangPackSync(locale, langPath); + try { + //公共语言包(同步加载) + var lang = loadLangPackSync(locale, 'common'); + + //当前页面语言包(同步加载) + let route = _this.route; + langPath = processRoutePath(route); + + // 加载当前页面语言包 + let currentPageLang = loadLangPackSync(locale, langPath); - let mergedLang = { ...lang, ...currentPageLang }; + // 合并语言包 + let mergedLang = { ...lang, ...currentPageLang }; - var arr = field.split("."); - if (arr.length > 1) { - let temp = mergedLang; - let found = true; - for (let key of arr) { - if (temp[key] !== undefined) { - temp = temp[key]; - } else { - found = false; - break; - } - } - value = found ? temp : field; - } else { - value = mergedLang[field] !== undefined ? mergedLang[field] : field; - } - - } catch (e) { - console.error('解析语言包失败:', e, { langPath, field, locale }); - value = field; - } + // 解析字段 + var arr = field.split("."); + if (arr.length > 1) { + // 处理嵌套属性,如 common.currencySymbol + let temp = mergedLang; + let found = true; + for (let key of arr) { + if (temp[key] !== undefined) { + temp = temp[key]; + } else { + found = false; + break; + } + } + value = found ? temp : field; + } else { + value = mergedLang[field] !== undefined ? mergedLang[field] : field; + } + + } catch (e) { + console.error('解析语言包失败:', e, { langPath, field, locale }); + value = field; + } - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - value = value.replace("{" + (i - 1) + "}", arguments[i]); - } - } - if (value == undefined || (value == 'title' && field == 'title')) value = ''; + if (arguments.length > 1) { + //有参数,需要替换 + for (var i = 1; i < arguments.length; i++) { + value = value.replace("{" + (i - 1) + "}", arguments[i]); + } + } + if (value == undefined || (value == 'title' && field == 'title')) value = ''; // field - if (field == value) { - console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`); - } + // 多语言调试,注释后可以关闭控制台输出 + if (field == value) { + console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`); + } - return value; - }, - /** - * 切换语言 - */ - change(value, url = '/pages_tool/member/index') { - let _this = getCurrentPages()[getCurrentPages().length - 1]; - if (!_this) return; + return value; + }, + /** + * * 切换语言 + * @param {String} value 语言值 + * @param {String} url 切换后跳转的页面url + */ + change(value, url = '/pages_tool/member/index') { + let _this = getCurrentPages()[getCurrentPages().length - 1]; + if (!_this) return; - uni.setStorageSync("lang", value); - const locale = uni.getStorageSync('lang') || "zh-cn"; + uni.setStorageSync("lang", value); + const locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言 - // ✅ 关键修复:清空所有语言包缓存(不再保留任何旧缓存) - for (let key in loadedLangPacks) { - delete loadedLangPacks[key]; - } + // 清空已加载的语言包缓存 + for (let key in loadedLangPacks) { + if (!key.startsWith(locale)) { + delete loadedLangPacks[key]; + } + } - this.refresh(); + this.refresh(); - if (url) { - uni.reLaunch({ url: url }); - } - }, - //刷新标题、tabbar - refresh() { - let _this = getCurrentPages()[getCurrentPages().length - 1]; - if (!_this) return; - const locale = uni.getStorageSync('lang') || "zh-cn"; - - this.title(this.lang("title")); - }, - title(str) { - if (str) { - uni.setNavigationBarTitle({ - title: str - }); - } - }, - // 获取语言包列表 - list() { - var list = []; - try { - for (var i = 0; i < langConfig.langList.length; i++) { - let langType = langConfig.langList[i]; - let item = loadLangPackSync(langType, 'common'); - list.push({ - name: item.common ? item.common.name : langType, - value: langType - }); - } - } catch (e) { - console.error('获取语言包列表失败:', e); - } - return list; - } -} \ No newline at end of file + if (url) { + uni.reLaunch({ url: url }); + } + }, + //刷新标题、tabbar + refresh() { + let _this = getCurrentPages()[getCurrentPages().length - 1]; + if (!_this) return; + const locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言 + + 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) { + if (str) { + uni.setNavigationBarTitle({ + title: str, + success: function (res) { + }, + fail: function (err) { + } + }); + } + }, + // 获取语言包列表 + list() { + var list = []; + try { + //公共语言包 + for (var i = 0; i < langConfig.langList.length; i++) { + let langType = langConfig.langList[i]; + let item = loadLangPackSync(langType, 'common'); + list.push({ + name: item.common ? item.common.name : langType, + value: langType + }); + } + } catch (e) { + console.error('获取语言包列表失败:', e); + } + return list; + } +} diff --git a/common/js/util.js b/common/js/util.js index 24036f4..8d9cb2f 100644 --- a/common/js/util.js +++ b/common/js/util.js @@ -13,6 +13,7 @@ export const CATEGORY_PAGE_URL = '/pages_goods/category'; export const CONTACT_PAGE_URL = '/pages_tool/contact/contact'; export const MEMBER_PAGE_URL = '/pages_tool/member/index'; export const LOGIN_PAGE_URL = '/pages_tool/login/login'; +export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index'; // 当前最新的tabBar.list (参见pages.json 中的tabBar.list 配置) export const systemTabBarList = [ @@ -119,6 +120,7 @@ export default { CONTACT_PAGE_URL, INDEX_PAGE_URL, LOGIN_PAGE_URL, + AI_CHAT_PAGE_URL, /** * 页面跳转 diff --git a/components/hover-nav/hover-nav.vue b/components/hover-nav/hover-nav.vue index f2b1d7a..3ef65a7 100644 --- a/components/hover-nav/hover-nav.vue +++ b/components/hover-nav/hover-nav.vue @@ -1,64 +1,43 @@