From 5e536afeae87d983e0b2a909cd809df6e9477dba Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 15 Dec 2025 14:26:01 +0800 Subject: [PATCH 001/100] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=89=93?= =?UTF-8?q?=E5=BC=80=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E5=AE=A2=E6=9C=8D?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/wxwork-jssdk.js | 187 +++ components/ns-contact/ns-contact.vue | 386 ++++--- components/wxwork-contact/wxwork-contact.vue | 285 +++++ pages/contact/contact.vue | 1080 +++++++++--------- 4 files changed, 1257 insertions(+), 681 deletions(-) create mode 100644 common/js/wxwork-jssdk.js create mode 100644 components/wxwork-contact/wxwork-contact.vue diff --git a/common/js/wxwork-jssdk.js b/common/js/wxwork-jssdk.js new file mode 100644 index 0000000..bd2932b --- /dev/null +++ b/common/js/wxwork-jssdk.js @@ -0,0 +1,187 @@ +/** + * 企业微信JS-SDK调用 + */ +let WxWork = function () { + // 企业微信JS-SDK + this.wxwork = null; + + /** + * 初始化企业微信JS-SDK + * @param {Object} params - 初始化参数 + * @param {string} params.corpId - 企业ID + * @param {string} params.agentId - 应用ID + * @param {string} params.timestamp - 时间戳 + * @param {string} params.nonceStr - 随机字符串 + * @param {string} params.signature - 签名 + * @param {Array} params.jsApiList - 需要使用的JS接口列表 + */ + this.init = function (params) { + if (typeof wx !== 'undefined' && wx.config) { + // 小程序环境下的企业微信 + this.wxwork = wx; + } else if (typeof WWOpenData !== 'undefined') { + // H5环境下的企业微信 + this.wxwork = WWOpenData; + } else { + console.error('企业微信JS-SDK未加载'); + return false; + } + + this.wxwork.config({ + beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题 + debug: false, // 开启调试模式 + corpId: params.corpId, // 必填,企业号的唯一标识 + agentId: params.agentId, // 必填,企业微信应用ID + timestamp: params.timestamp, // 必填,生成签名的时间戳 + nonceStr: params.nonceStr, // 必填,生成签名的随机串 + signature: params.signature, // 必填,签名 + jsApiList: params.jsApiList || [ + 'openUserProfile', + 'openEnterpriseChat', + 'getContext', + 'getCurExternalContact', + 'openExistedChatWithMsg' + ] // 必填,需要使用的JS接口列表 + }); + + return true; + }; + + /** + * 添加企业微信联系人 + * @param {Object} params - 参数 + * @param {string} params.userId - 用户ID + * @param {Function} success - 成功回调 + * @param {Function} fail - 失败回调 + */ + this.addContact = function (params, success, fail) { + if (!this.wxwork) { + console.error('企业微信JS-SDK未初始化'); + if (fail) fail('企业微信JS-SDK未初始化'); + return; + } + + this.wxwork.ready(() => { + this.wxwork.invoke('openUserProfile', { + type: 'external', // 外部联系人 + userId: params.userId // 用户ID + }, (res) => { + if (res.err_msg === 'openUserProfile:ok') { + if (success) success(res); + } else { + console.error('打开用户资料失败:', res); + if (fail) fail(res.err_msg); + } + }); + }); + }; + + /** + * 打开企业微信客服会话 + * @param {Object} params - 参数 + * @param {string} params.corpId - 企业ID + * @param {string} params.url - 客服URL + * @param {string} params.name - 会话名称 + * @param {Function} success - 成功回调 + * @param {Function} fail - 失败回调 + */ + this.openCustomerService = function (params, success, fail) { + if (!this.wxwork) { + console.error('企业微信JS-SDK未初始化'); + if (fail) fail('企业微信JS-SDK未初始化'); + return; + } + + this.wxwork.ready(() => { + // #ifdef MP-WEIXIN + if (typeof wx !== 'undefined' && wx.openCustomerServiceChat) { + // 微信小程序环境 + wx.openCustomerServiceChat({ + extInfo: { + url: params.url + }, + corpId: params.corpId, + showMessageCard: true, + sendMessageTitle: params.sendMessageTitle || '', + sendMessagePath: params.sendMessagePath || '', + sendMessageImg: params.sendMessageImg || '' + }); + if (success) success(); + } + // #endif + // #ifdef H5 + else if (typeof WWOpenData !== 'undefined') { + // H5环境 + window.location.href = params.url; + if (success) success(); + } + // #endif + else { + // 直接跳转链接 + window.location.href = params.url; + if (success) success(); + } + }); + }; + + /** + * 生成企业微信活码链接 + * @param {Object} params - 参数 + * @param {string} params.configId - 活码配置ID + * @param {string} params.userId - 用户ID + * @returns {string} 活码链接 + */ + this.generateContactUrl = function (params) { + // 企业微信活码链接格式 + const baseUrl = 'https://work.weixin.qq.com/kfid'; + if (params.configId) { + return `${baseUrl}/${params.configId}`; + } + return null; + }; + + /** + * 检查环境是否支持企业微信 + * @returns {boolean} 是否支持 + */ + this.isSupported = function () { + // #ifdef MP-WEIXIN + return typeof wx !== 'undefined' && wx.openCustomerServiceChat; + // #endif + // #ifdef H5 + return typeof WWOpenData !== 'undefined' || /wxwork/i.test(navigator.userAgent); + // #endif + return false; + }; + + /** + * 获取当前环境信息 + * @returns {Object} 环境信息 + */ + this.getEnvironment = function () { + // #ifdef MP-WEIXIN + return { + platform: 'miniprogram', + isWxWork: false, + supportContact: typeof wx !== 'undefined' && wx.openCustomerServiceChat + }; + // #endif + // #ifdef H5 + const isWxWork = /wxwork/i.test(navigator.userAgent); + return { + platform: 'h5', + isWxWork: isWxWork, + supportContact: isWxWork || typeof WWOpenData !== 'undefined' + }; + // #endif + return { + platform: 'unknown', + isWxWork: false, + supportContact: false + }; + }; +} + +export { + WxWork +} \ No newline at end of file diff --git a/components/ns-contact/ns-contact.vue b/components/ns-contact/ns-contact.vue index b69bca8..b3fca42 100644 --- a/components/ns-contact/ns-contact.vue +++ b/components/ns-contact/ns-contact.vue @@ -1,171 +1,217 @@ - - - - - - \ No newline at end of file diff --git a/components/wxwork-contact/wxwork-contact.vue b/components/wxwork-contact/wxwork-contact.vue new file mode 100644 index 0000000..76e7805 --- /dev/null +++ b/components/wxwork-contact/wxwork-contact.vue @@ -0,0 +1,285 @@ + + + + + \ No newline at end of file diff --git a/pages/contact/contact.vue b/pages/contact/contact.vue index 62984d6..0f652ca 100644 --- a/pages/contact/contact.vue +++ b/pages/contact/contact.vue @@ -1,511 +1,569 @@ - - - - - + + + + + From 08583aa8aa86be41ab04fbaad2a13291a104f8b0 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 15 Dec 2025 15:04:45 +0800 Subject: [PATCH 002/100] =?UTF-8?q?chore:=20=E4=BC=81=E4=B8=9A=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=AE=A2=E6=9C=8D=E7=BB=84=E4=BB=B6=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/wxwork-contact/wxwork-contact.vue | 70 ++++++++++++-------- pages/contact/contact.vue | 34 +++++----- store/index.js | 11 +++ 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/components/wxwork-contact/wxwork-contact.vue b/components/wxwork-contact/wxwork-contact.vue index 76e7805..65701bf 100644 --- a/components/wxwork-contact/wxwork-contact.vue +++ b/components/wxwork-contact/wxwork-contact.vue @@ -38,8 +38,28 @@ type: String, default: '添加企业微信客服' }, - // 企业微信配置 + // 企业ID(必需) corpId: { + type: String, + required: true + }, + // 应用ID + agentId: { + type: String, + default: '' + }, + // 时间戳 + timestamp: { + type: String, + default: '' + }, + // 随机字符串 + nonceStr: { + type: String, + default: '' + }, + // 签名 + signature: { type: String, default: '' }, @@ -71,24 +91,16 @@ /** * 初始化企业微信SDK */ - async initWxWork() { + initWxWork() { try { - // 获取企业微信配置 - const res = await this.$api.sendRequest({ - url: '/api/wxwork/config', - data: { - corp_id: this.corpId - } - }); - - if (res.code === 0 && res.data) { + if (this.corpId) { this.wxWorkSDK = new WxWork(); const initResult = this.wxWorkSDK.init({ - corpId: res.data.corp_id, - agentId: res.data.agent_id, - timestamp: res.data.timestamp, - nonceStr: res.data.nonceStr, - signature: res.data.signature, + corpId: this.corpId, + agentId: this.agentId, + timestamp: this.timestamp, + nonceStr: this.nonceStr, + signature: this.signature, jsApiList: ['openUserProfile', 'openEnterpriseChat'] }); @@ -97,7 +109,7 @@ } } } catch (error) { - console.error('获取企业微信配置失败:', error); + console.error('初始化企业微信SDK失败:', error); } }, @@ -118,22 +130,26 @@ confirmAdd() { this.closePopup(); + // 直接使用props传递的配置 + const contactUrl = this.contactUrl; + const contactId = this.contactId; + // #ifdef MP-WEIXIN - if (this.contactUrl) { + if (contactUrl) { // 方案1:直接跳转到企业微信活码 - this.jumpToWxWorkContact(); - } else if (this.contactId) { + this.jumpToWxWorkContact(contactUrl); + } else if (contactId) { // 方案2:使用SDK打开用户资料 - this.openUserProfile(); + this.openUserProfile(contactId); } else { this.showError('未配置企业微信客服信息'); } // #endif // #ifdef H5 - if (this.contactUrl) { + if (contactUrl) { // H5环境直接跳转 - window.location.href = this.contactUrl; + window.location.href = contactUrl; } else { this.showError('未配置企业微信客服信息'); } @@ -143,10 +159,10 @@ /** * 跳转到企业微信客服 */ - jumpToWxWorkContact() { + jumpToWxWorkContact(contactUrl) { uni.navigateToMiniProgram({ appId: 'wxeb490c6f9b154ef9', // 企业微信小程序AppID - path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(this.contactUrl)}`, + path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(contactUrl)}`, success: () => { console.log('跳转企业微信成功'); this.$util.showToast({ @@ -164,14 +180,14 @@ /** * 打开用户资料 */ - openUserProfile() { + openUserProfile(contactId) { if (!this.wxWorkSDK) { this.showError('企业微信SDK未初始化'); return; } this.wxWorkSDK.addContact({ - userId: this.contactId + userId: contactId }, (res) => { console.log('打开用户资料成功:', res); }, (err) => { diff --git a/pages/contact/contact.vue b/pages/contact/contact.vue index 0f652ca..8b48055 100644 --- a/pages/contact/contact.vue +++ b/pages/contact/contact.vue @@ -22,6 +22,10 @@ { - if (res.code === 0 && res.data) { - this.wxworkConfig = { - enabled: true, - corpId: res.data.corp_id, - contactId: res.data.contact_id, - contactUrl: res.data.contact_url - }; - } else { - this.wxworkConfig = { enabled: false }; - } - }, - fail: () => { - this.wxworkConfig = { enabled: false }; - } - }); + // 从全局store获取企业微信配置 + const wxworkConfig = this.$store.state?.wxworkConfig; + if (wxworkConfig) { + this.wxworkConfig = { + enabled: true, + corpId: wxworkConfig.corp_id, + contactId: wxworkConfig.contact_id, + contactUrl: wxworkConfig.contact_url + }; + } else { + this.wxworkConfig = { enabled: false }; + } }, /** diff --git a/store/index.js b/store/index.js index 34eb0f6..2d875ee 100644 --- a/store/index.js +++ b/store/index.js @@ -64,6 +64,7 @@ const store = new Vuex.Store({ cartPosition: null, // 购物车所在位置 componentRefresh: 0, // 组件刷新 servicerConfig: null, // 客服配置 + wxworkConfig: null, // 企业微信配置 diySeckillInterval: 0, diyGroupPositionObj: {}, diyGroupShowModule: '', @@ -160,6 +161,11 @@ const store = new Vuex.Store({ state.servicerConfig = value; uni.setStorageSync('servicerConfig', value); }, + // 企业微信配置 + setWxworkConfig(state, value) { + state.wxworkConfig = value; + uni.setStorageSync('wxworkConfig', value); + }, setDiySeckillInterval(state, value) { state.diySeckillInterval = value; }, @@ -215,6 +221,11 @@ const store = new Vuex.Store({ this.commit('setSiteInfo', data.site_info); this.commit('setServicerConfig', data.servicer); + + // 企业微信配置 + if (data.wxwork) { + this.commit('setWxworkConfig', data.wxwork); + } this.commit('setCopyright', data.copyright); From 23e81114e9fcb7f9ba8964f9b8a0c3cddf8cde9c Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 10:54:57 +0800 Subject: [PATCH 003/100] =?UTF-8?q?chore:=20=E5=90=8E=E7=AB=AFinit?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=95=B0=E6=8D=AE=E4=B8=BAwxwork=5Fconfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- store/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/index.js b/store/index.js index 2d875ee..8f295b5 100644 --- a/store/index.js +++ b/store/index.js @@ -223,8 +223,8 @@ const store = new Vuex.Store({ this.commit('setServicerConfig', data.servicer); // 企业微信配置 - if (data.wxwork) { - this.commit('setWxworkConfig', data.wxwork); + if (data?.wxwork_config) { + this.commit('setWxworkConfig', data.wxwork_config); } this.commit('setCopyright', data.copyright); From 9d12b0246371fb6ee4b0c120a86d3a5b82c5c428 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 14:41:15 +0800 Subject: [PATCH 004/100] =?UTF-8?q?chore:=20=E5=B0=86wxworkConfig=E6=94=BE?= =?UTF-8?q?=E5=88=B0=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/golbalConfig.js | 374 +++++++++++++++++++------------------- 1 file changed, 189 insertions(+), 185 deletions(-) diff --git a/common/js/golbalConfig.js b/common/js/golbalConfig.js index 73f39c6..d0ddab7 100644 --- a/common/js/golbalConfig.js +++ b/common/js/golbalConfig.js @@ -1,186 +1,190 @@ -export default { - data() { - return { - // 页面样式,动态设置主色调 - themeColor: '' //''--base-color:#fa5d14;--base-help-color:#ff7e00;' - } - }, - onLoad() {}, - onShow() { - // 刷新多语言 - this.$langConfig.refresh(); - let time = setInterval(() => { - let theme = this.themeStyle; - if (theme && theme.main_color) { - this.themeColorSet(); - clearInterval(time); - } - }, 50); - }, - computed: { - themeStyle() { - return this.$store.state.themeStyle; - }, - // 插件是否存在 - addonIsExist() { - return this.$store.state.addonIsExist; - }, - tabBarList() { - return this.$store.state.tabBarList; - }, - siteInfo() { - return this.$store.state.siteInfo; - }, - memberInfo() { - return this.$store.state.memberInfo; - }, - storeToken() { - return this.$store.state.token; - }, - bottomNavHidden() { - return this.$store.state.bottomNavHidden; - }, - globalStoreConfig() { - return this.$store.state.globalStoreConfig; - }, - globalStoreInfo() { - return this.$store.state.globalStoreInfo; - }, - // 定位信息 - location() { - return this.$store.state.location; - }, - // 定位信息(缓存) - locationStorage() { - let data = uni.getStorageSync('location'); - if (data) { - var date = new Date(); - if (this.mapConfig.wap_valid_time > 0) { - data.is_expired = (date.getTime() / 1000) > data.valid_time; // 是否过期 - } else { - data.is_expired = false; - } - } - return data; - }, - // 默认总店(定位失败后使用) - defaultStoreInfo() { - return this.$store.state.defaultStoreInfo; - }, - // 组件刷新计数 - componentRefresh() { - return this.$store.state.componentRefresh; - }, - // 客服配置 - servicerConfig() { - return this.$store.state.servicerConfig; - }, - diySeckillInterval() { - return this.$store.state.diySeckillInterval; - }, - tabBarHeight() { - return this.$store.state.tabBarHeight; - }, - mapConfig() { - return this.$store.state.mapConfig; - }, - copyright() { - let copyright = this.$store.state.copyright; - // 判断是否授权 - if (copyright && !copyright.auth) { - copyright.logo = ''; - copyright.copyright_link = ''; - } - return copyright; - }, - cartList() { - return this.$store.state.cartList; - }, - cartIds() { - return this.$store.state.cartIds; - }, - cartNumber() { - return this.$store.state.cartNumber; - }, - cartMoney() { - return this.$store.state.cartMoney; - } - }, - methods: { - themeColorSet() { - let theme = this.themeStyle; - this.themeColor = `--base-color:${theme.main_color};--base-help-color:${theme.aux_color};`; - if (this.tabBarHeight != '56px') this.themeColor += `--tab-bar-height:${this.tabBarHeight};` - Object.keys(theme).forEach(key => { - let data = theme[key]; - if (typeof(data) == "object") { - Object.keys(data).forEach(k => { - this.themeColor += '--' + k.replace(/_/g, "-") + ':' + data[k] + ';'; - }); - } else if (typeof(key) == "string" && key) { - this.themeColor += '--' + key.replace(/_/g, "-") + ':' + data + ';'; - } - }); - for (let i = 9; i >= 5; i--) { - let color = this.$util.colourBlend(theme.main_color, '#ffffff', (i / 10)); - this.themeColor += `--base-color-light-${i}:${color};`; - } - }, - // 颜色变浅(>0)、变深函数(<0) - lightenDarkenColor(color, amount) { - - var usePound = false; - - if (color[0] == "#") { - color = color.slice(1); - usePound = true; - } - - var num = parseInt(color, 16); - - var r = (num >> 16) + amount; - - if (r > 255) r = 255; - else if (r < 0) r = 0; - - var b = ((num >> 8) & 0x00FF) + amount; - - if (b > 255) b = 255; - else if (b < 0) b = 0; - - var g = (num & 0x0000FF) + amount; - - if (g > 255) g = 255; - else if (g < 0) g = 0; - - return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16); - - }, - /** - * 切换门店 - * @param {Object} info 门店信息 - * @param {Object} isJump 是否跳转到首页 - */ - changeStore(info, isJump) { - if (info) { - this.$store.commit('setGlobalStoreInfo', info); - } - let route = this.$util.getCurrRoute(); - if (isJump && route != 'pages/index/index') { - uni.setStorageSync('manual_change_store', true); // 手动切换门店 - this.$store.dispatch('getCartNumber'); //重新获取购物车数据 - this.$util.redirectTo('/pages/index/index'); - } - } - }, - filters: { - /** - * 金额格式化输出 - * @param {Object} money - */ - moneyFormat(money) { - if (isNaN(parseFloat(money))) return money; - return parseFloat(money).toFixed(2); - } - } +export default { + data() { + return { + // 页面样式,动态设置主色调 + themeColor: '' //''--base-color:#fa5d14;--base-help-color:#ff7e00;' + } + }, + onLoad() {}, + onShow() { + // 刷新多语言 + this.$langConfig.refresh(); + let time = setInterval(() => { + let theme = this.themeStyle; + if (theme && theme.main_color) { + this.themeColorSet(); + clearInterval(time); + } + }, 50); + }, + computed: { + themeStyle() { + return this.$store.state.themeStyle; + }, + // 插件是否存在 + addonIsExist() { + return this.$store.state.addonIsExist; + }, + tabBarList() { + return this.$store.state.tabBarList; + }, + siteInfo() { + return this.$store.state.siteInfo; + }, + memberInfo() { + return this.$store.state.memberInfo; + }, + storeToken() { + return this.$store.state.token; + }, + bottomNavHidden() { + return this.$store.state.bottomNavHidden; + }, + globalStoreConfig() { + return this.$store.state.globalStoreConfig; + }, + globalStoreInfo() { + return this.$store.state.globalStoreInfo; + }, + // 定位信息 + location() { + return this.$store.state.location; + }, + // 定位信息(缓存) + locationStorage() { + let data = uni.getStorageSync('location'); + if (data) { + var date = new Date(); + if (this.mapConfig.wap_valid_time > 0) { + data.is_expired = (date.getTime() / 1000) > data.valid_time; // 是否过期 + } else { + data.is_expired = false; + } + } + return data; + }, + // 默认总店(定位失败后使用) + defaultStoreInfo() { + return this.$store.state.defaultStoreInfo; + }, + // 组件刷新计数 + componentRefresh() { + return this.$store.state.componentRefresh; + }, + // 客服配置 + servicerConfig() { + return this.$store.state.servicerConfig; + }, + // 企业微信配置 + wxworkConfig() { + return this.$store.state.wxworkConfig; + }, + diySeckillInterval() { + return this.$store.state.diySeckillInterval; + }, + tabBarHeight() { + return this.$store.state.tabBarHeight; + }, + mapConfig() { + return this.$store.state.mapConfig; + }, + copyright() { + let copyright = this.$store.state.copyright; + // 判断是否授权 + if (copyright && !copyright.auth) { + copyright.logo = ''; + copyright.copyright_link = ''; + } + return copyright; + }, + cartList() { + return this.$store.state.cartList; + }, + cartIds() { + return this.$store.state.cartIds; + }, + cartNumber() { + return this.$store.state.cartNumber; + }, + cartMoney() { + return this.$store.state.cartMoney; + } + }, + methods: { + themeColorSet() { + let theme = this.themeStyle; + this.themeColor = `--base-color:${theme.main_color};--base-help-color:${theme.aux_color};`; + if (this.tabBarHeight != '56px') this.themeColor += `--tab-bar-height:${this.tabBarHeight};` + Object.keys(theme).forEach(key => { + let data = theme[key]; + if (typeof(data) == "object") { + Object.keys(data).forEach(k => { + this.themeColor += '--' + k.replace(/_/g, "-") + ':' + data[k] + ';'; + }); + } else if (typeof(key) == "string" && key) { + this.themeColor += '--' + key.replace(/_/g, "-") + ':' + data + ';'; + } + }); + for (let i = 9; i >= 5; i--) { + let color = this.$util.colourBlend(theme.main_color, '#ffffff', (i / 10)); + this.themeColor += `--base-color-light-${i}:${color};`; + } + }, + // 颜色变浅(>0)、变深函数(<0) + lightenDarkenColor(color, amount) { + + var usePound = false; + + if (color[0] == "#") { + color = color.slice(1); + usePound = true; + } + + var num = parseInt(color, 16); + + var r = (num >> 16) + amount; + + if (r > 255) r = 255; + else if (r < 0) r = 0; + + var b = ((num >> 8) & 0x00FF) + amount; + + if (b > 255) b = 255; + else if (b < 0) b = 0; + + var g = (num & 0x0000FF) + amount; + + if (g > 255) g = 255; + else if (g < 0) g = 0; + + return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16); + + }, + /** + * 切换门店 + * @param {Object} info 门店信息 + * @param {Object} isJump 是否跳转到首页 + */ + changeStore(info, isJump) { + if (info) { + this.$store.commit('setGlobalStoreInfo', info); + } + let route = this.$util.getCurrRoute(); + if (isJump && route != 'pages/index/index') { + uni.setStorageSync('manual_change_store', true); // 手动切换门店 + this.$store.dispatch('getCartNumber'); //重新获取购物车数据 + this.$util.redirectTo('/pages/index/index'); + } + } + }, + filters: { + /** + * 金额格式化输出 + * @param {Object} money + */ + moneyFormat(money) { + if (isNaN(parseFloat(money))) return money; + return parseFloat(money).toFixed(2); + } + } } \ No newline at end of file From 88debacf8c7425b5f41ac8b2705483cfc97aac29 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 14:41:45 +0800 Subject: [PATCH 005/100] =?UTF-8?q?chore:=20=E4=BD=BF=E7=94=A8=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E7=9A=84wxworkConfig=E9=85=8D=E7=BD=AE=E4=BD=9C?= =?UTF-8?q?=E4=B8=BA=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ns-contact/ns-contact.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/ns-contact/ns-contact.vue b/components/ns-contact/ns-contact.vue index b3fca42..f6e0d83 100644 --- a/components/ns-contact/ns-contact.vue +++ b/components/ns-contact/ns-contact.vue @@ -105,13 +105,13 @@ * 打开企业微信客服 * @param {boolean} useOriginalService 是否使用原有客服方式 */ - openWxWorkService(useOriginalService = false) { + openWxWorkService(useOriginalService = false) { // #ifdef MP-WEIXIN - if (this.config.wxwork_contact_url && !useOriginalService) { + if (this.wxworkConfig?.contact_url && !useOriginalService) { // 直接使用活码链接跳转 wx.navigateToMiniProgram({ - appId: 'wxeb490c6f9b154ef9', // 企业微信小程序AppID - path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(this.config.wxwork_contact_url)}`, + appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID(固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面) + path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(this.wxworkConfig.contact_url)}`, success: () => { console.log('跳转企业微信成功'); }, @@ -135,9 +135,9 @@ // #endif // #ifdef H5 - if (this.config.wxwork_contact_url) { + if (this.wxworkConfig?.contact_url) { // H5环境直接跳转活码链接 - window.location.href = this.config.wxwork_contact_url; + window.location.href = this.wxworkConfig.contact_url; } else { location.href = this.config.wxwork_url; } From b9455838577b46429167ae35773bfcb1942a90e1 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 15:41:15 +0800 Subject: [PATCH 006/100] =?UTF-8?q?chore:=20=E4=BC=98=E5=8C=96=E5=85=AC?= =?UTF-8?q?=E5=85=B1=E7=AB=AF=E9=83=BD=E6=94=AF=E6=8C=81=E6=89=93=E5=BC=80?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E5=AE=A2=E6=9C=8D=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/util.js | 2009 +++++++++++++------------- components/ns-contact/ns-contact.vue | 12 +- 2 files changed, 1026 insertions(+), 995 deletions(-) diff --git a/common/js/util.js b/common/js/util.js index cbe0f01..2d73a7a 100644 --- a/common/js/util.js +++ b/common/js/util.js @@ -1,990 +1,1021 @@ -import Config from './config.js' -import store from '@/store/index.js' -import Http from './http.js' -import { - Weixin -} from 'common/js/wx-jssdk.js'; - -export default { - /** - * 页面跳转 - * @param {string} to 跳转链接 /pages/idnex/index - * @param {Object} param 参数 {key : value, ...} - * @param {string} mode 模式 - */ - redirectTo(to, param, mode) { - let url = to; - let tabbarList = ['/pages/index/index', '/pages/goods/category', '/pages/vr/index', '/pages/contact/contact', '/pages/member/index']; - if (param != undefined) { - Object.keys(param).forEach(function (key) { - if (url.indexOf('?') != -1) { - url += "&" + key + "=" + param[key]; - } else { - url += "?" + key + "=" + param[key]; - } - }); - } - for (let i = 0; i < tabbarList.length; i++) { - if (url.indexOf(tabbarList[i]) == 0) { - uni.switchTab({ - url - }); - return; - } - } - switch (mode) { - case 'tabbar': - // 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。 - uni.switchTab({ - url - }); - break; - case 'redirectTo': - // 关闭当前页面,跳转到应用内的某个页面。 - uni.redirectTo({ - url - }); - break; - case 'reLaunch': - // 关闭所有页面,打开到应用内的某个页面。 - uni.reLaunch({ - url - }); - break; - default: - // 保留当前页面,跳转到应用内的某个页面 - uni.navigateTo({ - url - }); - } - }, - /** - * 图片路径转换 - * @param {String} img_path 图片地址 - * @param {Object} params 参数,针对商品、相册里面的图片区分大中小,size: big、mid、small - */ - img(img_path, params) { - - - var path = ""; - if (img_path != undefined && img_path != "") { - if (img_path.split(',').length > 1) { - img_path = img_path.split(',')[0]; - } - if (params && img_path != this.getDefaultImage().goods) { - // 过滤默认图 - let arr = img_path.split("."); - let suffix = arr[arr.length - 1]; - arr.pop(); - arr[arr.length - 1] = arr[arr.length - 1] + "_" + params.size.toUpperCase(); - arr.push(suffix); - // if(img_path.indexOf('attachment') == -1){ - // img_path = arr.join("."); - // } - - } - if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) { - path = Config.imgDomain + "/" + img_path; - } else { - // console.log(img_path) - path = img_path; - } - } - - // path += '?t=' + parseInt(new Date().getTime() / 1000); - return path; - }, - /** - * 时间戳转日期格式 - * @param {Object} timeStamp - */ - timeStampTurnTime(timeStamp, type = "") { - if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) { - var date = new Date(); - date.setTime(timeStamp * 1000); - var y = date.getFullYear(); - var m = date.getMonth() + 1; - m = m < 10 ? ('0' + m) : m; - var d = date.getDate(); - d = d < 10 ? ('0' + d) : d; - var h = date.getHours(); - h = h < 10 ? ('0' + h) : h; - var minute = date.getMinutes(); - var second = date.getSeconds(); - minute = minute < 10 ? ('0' + minute) : minute; - second = second < 10 ? ('0' + second) : second; - if (type) { - if (type == 'yearMonthDay') { - return y + '年' + m + '月' + d + '日'; - } - return y + '-' + m + '-' + d; - } else { - return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second; - } - } else { - return ""; - } - }, - /** - * 日期格式转时间戳 - * @param {Object} string - */ - timeTurnTimeStamp(string) { - var f = string.split(' ', 2); - var d = (f[0] ? f[0] : '').split('-', 3); - var t = (f[1] ? f[1] : '').split(':', 3); - return (new Date( - parseInt(d[0], 10) || null, - (parseInt(d[1], 10) || 1) - 1, - parseInt(d[2], 10) || null, - parseInt(t[0], 10) || null, - parseInt(t[1], 10) || null, - parseInt(t[2], 10) || null - )).getTime() / 1000; - }, - /** - * 倒计时 - * @param {Object} seconds 秒 - */ - countDown(seconds) { - let [day, hour, minute, second] = [0, 0, 0, 0] - if (seconds > 0) { - day = Math.floor(seconds / (60 * 60 * 24)) - hour = Math.floor(seconds / (60 * 60)) - (day * 24) - minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60) - second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60) - } - if (day < 10) { - day = '0' + day - } - if (hour < 10) { - hour = '0' + hour - } - if (minute < 10) { - minute = '0' + minute - } - if (second < 10) { - second = '0' + second - } - return { - d: day, - h: hour, - i: minute, - s: second - }; - }, - /** - * 数值去重 - * @param {Array} arr 数组 - * @param {string} field 字段 - */ - unique(arr, field) { - const res = new Map(); - return arr.filter((a) => !res.has(a[field]) && res.set(a[field], 1)); - }, - /** - * 判断值是否在数组中 - * @param {Object} elem - * @param {Object} arr - */ - inArray: function (elem, arr) { - return arr == null ? -1 : arr.indexOf(elem); - }, - /** - * 获取某天日期 - * @param {Object} day - */ - getDay: function (day) { - var today = new Date(); - var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day; - today.setTime(targetday_milliseconds); - - const doHandleMonth = function (month) { - var m = month; - if (month.toString().length == 1) { - m = "0" + month; - } - return m - } - - var tYear = today.getFullYear(); - var tMonth = today.getMonth(); - var tDate = today.getDate(); - var tWeek = today.getDay(); - var time = parseInt(today.getTime() / 1000); - tMonth = doHandleMonth(tMonth + 1); - tDate = doHandleMonth(tDate); - - const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] - return { - 't': time, - 'y': tYear, - 'm': tMonth, - 'd': tDate, - 'w': week[tWeek] - }; - }, - /** - * 图片选择加上传 - * @param num - * @param params - * @param callback - * @param url - */ - upload: function (num, params, callback, url) { - // #ifdef H5 - var app_type = this.isWeiXin() ? 'wechat' : 'h5'; - var app_type_name = this.isWeiXin() ? '微信公众号' : 'H5'; - // #endif - - // #ifdef MP-WEIXIN - var app_type = 'weapp'; - var app_type_name = '微信小程序'; - // #endif - - // #ifdef MP-ALIPAY - var app_type = 'aliapp'; - var app_type_name = '支付宝小程序'; - // #endif - - // #ifdef MP-BAIDU - var app_type = 'baiduapp'; - var app_type_name = '百度小程序'; - // #endif - - // #ifdef MP-TOUTIAO - var app_type = 'MP-TOUTIAO'; - var app_type_name = '头条小程序'; - // #endif - - // #ifdef MP-QQ - var app_type = 'MP-QQ'; - var app_type_name = 'QQ小程序'; - // #endif - var data = { - token: store.state.token, - app_type: app_type, - app_type_name: app_type_name - } - data = Object.assign(data, params); - - var imgs_num = num; - var _self = this; - - uni.chooseImage({ - count: imgs_num, - sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有 - sourceType: ['album', 'camera'], //从相册或者拍照 - success: async function (res) { - const tempFilePaths = res.tempFilePaths; - var _data = data; - var imgs = []; - uni.showLoading({ - title: '图片上传中' - }) - for (var i = 0; i < tempFilePaths.length; i++) { - var path = await _self.upload_file_server(tempFilePaths[i], _data, params.path, - url); - imgs.push(path); - if (imgs.length == tempFilePaths.length) { - uni.hideLoading() - uni.showToast({ - title: '上传成功', - icon: 'none' - }) - typeof callback == 'function' && callback(imgs); - } - } - }, - fail: err => { - uni.hideLoading() - uni.showToast({ - title: '上传失败', - icon: 'none' - }) - } - }); - }, - //上传 - upload_file_server(tempFilePath, data, path, url = "", callback) { - if (url) { - var uploadUrl = Config.baseUrl + url - } else { - var uploadUrl = Config.baseUrl + '/api/upload/' + path - } - return new Promise((resolve, reject) => { - uni.uploadFile({ - url: uploadUrl, - filePath: tempFilePath, - name: 'file', - fileType: data.fileType || 'image', - formData: data, - success: function (res) { - var path_str = JSON.parse(res.data); - if (path_str.code >= 0) { - resolve(path_str.data.pic_path); - typeof callback == 'function' && callback(path_str.data.pic_path); - } else { - reject("error"); - } - } - }); - - }); - - }, - /** - * 复制 - * @param {Object} value - * @param {Object} callback - */ - copy(value, callback) { - // #ifdef H5 - var oInput = document.createElement('input'); //创建一个隐藏input(重要!) - oInput.value = value; //赋值 - oInput.setAttribute("readonly", "readonly"); - document.body.appendChild(oInput); - oInput.select(); // 选择对象 - document.execCommand("Copy"); // 执行浏览器复制命令 - oInput.className = 'oInput'; - oInput.style.display = 'none'; - uni.hideKeyboard(); - this.showToast({ - title: '复制成功' - }); - - typeof callback == 'function' && callback(); - // #endif - - // #ifdef MP || APP-PLUS - uni.setClipboardData({ - data: value, - success: () => { - typeof callback == 'function' && callback(); - } - }); - // #endif - }, - /** - * 是否是微信浏览器 - */ - isWeiXin() { - // #ifndef H5 - return false; - // #endif - var ua = navigator.userAgent.toLowerCase(); - if (ua.match(/MicroMessenger/i) == "micromessenger") { - return true; - } else { - return false; - } - }, - /** - * 显示消息提示框 - * @param {Object} params 参数 - */ - showToast(params = {}) { - params.title = params.title || ""; - params.icon = params.icon || "none"; - // params.position = params.position || 'bottom'; - params.duration = params.duration || 1500; - uni.showToast(params); - if (params.success) params.success(); - }, - /** - * 检测苹果X以上的手机 - */ - isIPhoneX() { - let res = uni.getSystemInfoSync(); - if (res.model.search('iPhone X') != -1) { - return true; - } - return false; - }, - //判断安卓还是iOS - isAndroid() { - let platform = uni.getSystemInfoSync().platform - if (platform == 'ios') { - return false; - } else if (platform == 'android') { - return true; - } - }, - /** - * 深度拷贝对象 - * @param {Object} obj - */ - deepClone(obj) { - const isObject = function (obj) { - return typeof obj == 'object'; - } - - if (!isObject(obj)) { - throw new Error('obj 不是一个对象!') - } - //判断传进来的是对象还是数组 - let isArray = Array.isArray(obj) - let cloneObj = isArray ? [] : {} - //通过for...in来拷贝 - for (let key in obj) { - cloneObj[key] = isObject(obj[key]) ? this.deepClone(obj[key]) : obj[key] - } - return cloneObj - }, - /** - * 自定义模板的跳转链接 - * @param {Object} link - */ - diyRedirectTo(link) { - - //if (link == null || Object.keys(link).length == 1) return; - - // 外部链接 - if (link.wap_url && link.wap_url.indexOf('http') != -1 || link.wap_url && link.wap_url.indexOf('http') != -1) { - // #ifdef H5 - window.location.href = link.wap_url; - // #endif - - // #ifdef MP - this.redirectTo('/pages_tool/webview/webview', { - src: encodeURIComponent(link.wap_url) - }); - // #endif - - } else if (link.appid) { - // 跳转其他小程序 - - uni.navigateToMiniProgram({ - appId: link.appid, - path: link.page - }) - - } else if (link.name == 'MOBILE' && !link.wap_url) { - // 拨打电话 - - uni.makePhoneCall({ - phoneNumber: link.mobile, - success: (res) => { - }, - fail: (res) => { - } - }); - - } else if (link.name == 'MEMBER_CONTACT') { - // 客服 - let servicerConfig = store.state.servicerConfig - let config = { - type: 'none' - }; - - // #ifdef H5 - config = servicerConfig.h5; - // #endif - - // #ifdef MP-WEIXIN - config = servicerConfig.weapp; - // #endif - - switch (config.type) { - case 'wxwork': - // 企业微信客服 - - // #ifdef H5 - window.location.href = config.wxwork_url; - // #endif - - // #ifdef MP-WEIXIN - wx.openCustomerServiceChat({ - extInfo: { - url: config.wxwork_url - }, - corpId: config.corpid, - showMessageCard: true, - sendMessageTitle: 'this.sendMessageTitle', - sendMessagePath: 'this.sendMessagePath', - sendMessageImg: 'this.sendMessageImg' - }); - // #endif - break; - case 'third': - // 第三方客服 - window.location.href = config.third_url; - break; - case 'niushop': - // Niushop客服 - this.redirectTo('/pages_tool/chat/room'); - break; - case 'weapp': - // 微信小程序,由于需要手动点击按钮触发,所以要跳转到中间页 - this.redirectTo(link.wap_url); - break; - default: - // 拨打客服电话 - let siteInfo = store.state.siteInfo; - if (siteInfo && siteInfo.site_tel) { - uni.makePhoneCall({ - phoneNumber: siteInfo.site_tel - }); - } else { - this.showToast({ - title: '抱歉,商家暂无客服,请线下联系', - }) - } - } - - } else if (link.wap_url) { - this.redirectTo(link.wap_url); - } - }, - /** - * 获取默认图 - */ - getDefaultImage() { - let defaultImg = store.state.defaultImg; - defaultImg.goods = this.img(defaultImg.goods); - defaultImg.head = this.img(defaultImg.head); - defaultImg.store = this.img(defaultImg.store); - defaultImg.article = this.img(defaultImg.article); - defaultImg.kefu = this.img(defaultImg.kefu); - defaultImg.phone = this.img(defaultImg.phone); - return defaultImg; - }, - /** - * 判断手机是否为iphoneX系列 - */ - uniappIsIPhoneX() { - let isIphoneX = false; - let systemInfo = uni.getSystemInfoSync(); - // #ifdef MP - if (systemInfo.model.search('iPhone X') != -1 || systemInfo.model.search('iPhone 11') != -1 || systemInfo.model.search('iPhone 12') != -1 || systemInfo.model.search('iPhone 13') != -1) { - isIphoneX = true; - } - // #endif - - // #ifdef H5 - var u = navigator.userAgent; - var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端 - if (isIOS) { - if (systemInfo.screenWidth == 375 && systemInfo.screenHeight == 812 && systemInfo.pixelRatio == 3) { - isIphoneX = true; - } else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 3) { - isIphoneX = true; - } else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 2) { - isIphoneX = true; - } - } - // #endif - return isIphoneX; - }, - /** - * 判断手机是否为iphone11系列 - */ - uniappIsIPhone11() { - let isIphone11 = false; - let systemInfo = uni.getSystemInfoSync(); - // #ifdef MP - if (systemInfo.model.search('iPhone 11') != -1) { - isIphone11 = true; - } - // #endif - return isIphone11; - }, - // #ifdef H5 - //判断该浏览器是否为safaria浏览器 - isSafari() { - let res = uni.getSystemInfoSync(); - var ua = navigator.userAgent.toLowerCase(); - if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 && - ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 && - ua.indexOf('ios') === -1 && ua.indexOf('browser') === -1) { - return true; - } else { - return false; - } - }, - // #endif - numberFixed(e, f) { - if (!f) { - f = 0; - } - return Number(e).toFixed(f); - }, - /** - * 获取url参数 - */ - getUrlCode(callback) { - var url = location.search; - var theRequest = new Object(); - if (url.indexOf('?') != -1) { - var str = url.substr(1); - var strs = str.split('&'); - for (var i = 0; i < strs.length; i++) { - theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1]; - } - } - typeof callback == 'function' && callback(theRequest); - }, - /** - * 获取当前页面路由 - */ - getCurrRoute() { - let routes = getCurrentPages(); // 获取当前打开过的页面路由数组 - return routes.length ? routes[routes.length - 1].route : ''; - }, - goBack(backUrl = '/pages/index/index') { - if (getCurrentPages().length == 1) { - this.redirectTo(backUrl); - } else { - uni.navigateBack(); - } - }, - /** - * - * @param val 转化时间字符串 (转化时分秒) - * @returns {string} - */ - getTimeStr(val) { - var h = parseInt(val / 3600).toString(); - var m = parseInt((val % 3600) / 60).toString(); - if (m.length == 1) { - m = '0' + m; - } - if (h.length == 1) { - h = '0' + h; - } - return h + ':' + m; - }, - /** - * 获取定位信息 - */ - getLocation(param = {}) { - /*uni.getLocation({ - type: param.type ?? 'gcj02', - success: res => { - store.commit('setLocation', res); - typeof param.success == 'function' && param.success(res); - }, - fail: res => { - typeof param.fail == 'function' && param.fail(res); - }, - complete: res => { - typeof param.complete == 'function' && param.complete(res); - } - });*/ - }, - // 计算两个经纬度之间的距离 - getDistance(lat1, lng1, lat2, lng2) { - var radLat1 = lat1 * Math.PI / 180.0; - var radLat2 = lat2 * Math.PI / 180.0; - var a = radLat1 - radLat2; - var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; - var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + - Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); - s = s * 6378.137; // EARTH_RADIUS; - s = Math.round(s * 10000) / 10000; - return s; - }, - //记录分享人 - onSourceMember(source_member) { - Http.sendRequest({ - url: '/api/Member/alterShareRelation', - data: { - share_member: source_member, - }, - success: res => { - if (res.code >= 0) { - uni.removeStorage({ - key: 'source_member', - success: res => { - console.log('删除成功', res) - } - }) - } - } - }) - }, - /** - * 微信订阅消息 - */ - subscribeMessage(keywords) { - // #ifdef MP-WEIXIN - let url = '/weapp/api/weapp/messagetmplids'; - // #endif - // #ifdef MP-ALIPAY - let url = '/aliapp/api/aliapp/messagetmplids'; - // #endif - Http.sendRequest({ - url, - data: { - keywords: keywords - }, - success: res => { - if (res.data.length) { - // #ifdef MP-WEIXIN - uni.requestSubscribeMessage({ - tmplIds: res.data, - success: (res) => { - console.log("res", res) - }, - fail: (res) => { - console.log('fail', res) - } - }) - // #endif - - // #ifdef MP-ALIPAY - my.requestSubscribeMessage({ - entityIds: res.data, - success: res => { - console.log("res", res) - }, - fail: res => { - console.log('fail', res) - } - }); - // #endif - } - } - }) - }, - /** - * 获取小程序分享内容数据 - */ - getMpShare(path) { - //如果没有特别指定 则获取当前页面的路由 - if (!path) { - let route = this.getCurrentRoute(); - path = route.path; - if (path == '/pages/member/index') { - return new Promise((resolve, reject) => { - resolve({}) - }); - } - } - return new Promise((resolve, reject) => { - Http.sendRequest({ - url: "/weapp/api/weapp/share", - data: { - path: path - }, - success: res => { - if (res.code >= 0) { - let shareConfig = res.data.data; - if (shareConfig) { - //分享给好友 - let appMessageData = { - title: shareConfig.title, - path: shareConfig.path, - imageUrl: shareConfig.imageUrl, - success: res => { - }, - fail: res => { - } - } - //分享到朋友圈 - let query = ''; - if (shareConfig.path.indexOf('?') > 0) { - query = shareConfig.path.split('?')[1]; - } - let timeLineData = { - title: shareConfig.title, - query: shareConfig.path, - imageUrl: shareConfig.imageUrl, - } - resolve({ - appMessage: appMessageData, - timeLine: timeLineData, - }) - } else { - reject(res.data); - } - } else { - reject(res.data) - } - } - }); - }) - }, - /** - * 设置公众号分享 - * @param shareData - * @param callback - */ - setPublicShare(shareData, callback) { - if (!this.isWeiXin()) return; - Http.sendRequest({ - url: '/wechat/api/wechat/jssdkconfig', - data: { - url: uni.getSystemInfoSync().platform == 'ios' ? uni.getStorageSync('initUrl') : window.location - .href - }, - success: res => { - if (res.code == 0) { - var wxJS = new Weixin(); - wxJS.init(res.data); - wxJS.weixin.showOptionMenu(); - - wxJS.setShareData({ - title: shareData.title ?? '', - desc: shareData.desc ?? '', - link: shareData.link ?? location.href, - imgUrl: shareData.imgUrl ? this.img(shareData.imgUrl) : '' - }, (res) => { - typeof callback == 'function' && callback(res); - }) - } - } - }) - }, - //获取当前路由 - getCurrentRoute() { - let currentRoutes = getCurrentPages(); // 获取当前打开过的页面路由数组 - let currentRoute = currentRoutes[currentRoutes.length - 1].route //获取当前页面路由 - let currentParam = currentRoutes[currentRoutes.length - 1].options; //获取路由参数 - // 拼接参数 - let param = []; - for (let key in currentParam) { - param.push(key + '=' + currentParam[key]) - } - let currentPath = '/' + currentRoute; - let currentQuery = param.join('&'); - if (currentQuery) currentPath += '?' + currentQuery; - - return { - path: currentPath, - query: currentQuery, - } - }, - //获取分享路由 - getCurrentShareRoute(member_id) { - let route = this.getCurrentRoute(); - //去掉原来的分享人数据 - route.path = route.path.replace(/[?|&]source_member=\d+/, ''); - if (member_id) { - //路径的处理 - if (route.path.indexOf('?') > 0) { - route.path += '&'; - } else { - route.path += '?'; - } - route.path += 'source_member=' + member_id; - //参数的处理 - if (route.query) { - route.query += '&'; - } - route.query += 'source_member=' + member_id; - } - return route; - }, - /** - * 对象转style字符串 - * @param {Object} obj - */ - objToStyle(obj) { - let s = []; - for (let i in obj) { - s.push(i + ':' + obj[i]); - } - return s.join(';') - }, - /** - * 颜色减值 - * @param {Object} c1 - * @param {Object} c2 - * @param {Object} ratio - */ - colourBlend(c1, c2, ratio) { - ratio = Math.max(Math.min(Number(ratio), 1), 0) - let r1 = parseInt(c1.substring(1, 3), 16) - let g1 = parseInt(c1.substring(3, 5), 16) - let b1 = parseInt(c1.substring(5, 7), 16) - let r2 = parseInt(c2.substring(1, 3), 16) - let g2 = parseInt(c2.substring(3, 5), 16) - let b2 = parseInt(c2.substring(5, 7), 16) - let r = Math.round(r1 * (1 - ratio) + r2 * ratio) - let g = Math.round(g1 * (1 - ratio) + g2 * ratio) - let b = Math.round(b1 * (1 - ratio) + b2 * ratio) - r = ('0' + (r || 0).toString(16)).slice(-2) - g = ('0' + (g || 0).toString(16)).slice(-2) - b = ('0' + (b || 0).toString(16)).slice(-2) - return '#' + r + g + b - }, - /** - * 生成贝塞尔曲线轨迹 - * @param {Object} points - * @param {Object} times - */ - bezier(points, times) { - var bezier_points = []; - var points_D = []; - var points_E = []; - const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0][ - 'y' - ], 2)); - // 邻控制BC点间距 - const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1][ - 'y' - ], 2)); - // D每次在AB方向上移动的距离 - if (points[0]['x'] > points[2]['x']) { - var EACH_MOVE_AD = -(DIST_AB / times); - // E每次在BC方向上移动的距离 - var EACH_MOVE_BE = -(DIST_BC / times); - } else { - var EACH_MOVE_AD = +(DIST_AB / times); - // E每次在BC方向上移动的距离 - var EACH_MOVE_BE = +(DIST_BC / times); - } - // 点AB的正切 - const TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']); - // 点BC的正切 - const TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']); - // 点AB的弧度值 - const RADIUS_AB = Math.atan(TAN_AB); - // 点BC的弧度值 - const RADIUS_BC = Math.atan(TAN_BC); - // 每次执行 - for (var i = 1; i <= times; i++) { - // AD的距离 - var dist_AD = EACH_MOVE_AD * i; - // BE的距离 - var dist_BE = EACH_MOVE_BE * i; - // D点的坐标 - var point_D = {}; - point_D['x'] = dist_AD * Math.cos(RADIUS_AB) + points[0]['x']; - point_D['y'] = dist_AD * Math.sin(RADIUS_AB) + points[0]['y']; - points_D.push(point_D); - // E点的坐标 - var point_E = {}; - point_E['x'] = dist_BE * Math.cos(RADIUS_BC) + points[1]['x']; - point_E['y'] = dist_BE * Math.sin(RADIUS_BC) + points[1]['y']; - points_E.push(point_E); - // 此时线段DE的正切值 - var tan_DE = (point_E['y'] - point_D['y']) / (point_E['x'] - point_D['x']); - // tan_DE的弧度值 - var radius_DE = Math.atan(tan_DE); - // 地市DE的间距 - var dist_DE = Math.sqrt(Math.pow((point_E['x'] - point_D['x']), 2) + Math.pow((point_E['y'] - point_D['y']), - 2)); - // 此时DF的距离 - var dist_DF = (dist_AD / DIST_AB) * dist_DE; - // 此时DF点的坐标 - var point_F = {}; - point_F['x'] = dist_DF * Math.cos(radius_DE) + point_D['x']; - point_F['y'] = dist_DF * Math.sin(radius_DE) + point_D['y']; - bezier_points.push(point_F); - } - return { - 'bezier_points': bezier_points - }; - }, - /** - * 验证手机号 - * @param {string} mobile 被验证的mobile - * @return {object} 验证后的结果 - **/ - verifyMobile(mobile) { - var parse = /^\d{11}$/.test(mobile); - return parse; - } +import Config from './config.js' +import store from '@/store/index.js' +import Http from './http.js' +import { + Weixin +} from 'common/js/wx-jssdk.js'; + +export default { + /** + * 页面跳转 + * @param {string} to 跳转链接 /pages/idnex/index + * @param {Object} param 参数 {key : value, ...} + * @param {string} mode 模式 + */ + redirectTo(to, param, mode) { + let url = to; + let tabbarList = ['/pages/index/index', '/pages/goods/category', '/pages/vr/index', '/pages/contact/contact', '/pages/member/index']; + if (param != undefined) { + Object.keys(param).forEach(function (key) { + if (url.indexOf('?') != -1) { + url += "&" + key + "=" + param[key]; + } else { + url += "?" + key + "=" + param[key]; + } + }); + } + for (let i = 0; i < tabbarList.length; i++) { + if (url.indexOf(tabbarList[i]) == 0) { + uni.switchTab({ + url + }); + return; + } + } + switch (mode) { + case 'tabbar': + // 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。 + uni.switchTab({ + url + }); + break; + case 'redirectTo': + // 关闭当前页面,跳转到应用内的某个页面。 + uni.redirectTo({ + url + }); + break; + case 'reLaunch': + // 关闭所有页面,打开到应用内的某个页面。 + uni.reLaunch({ + url + }); + break; + default: + // 保留当前页面,跳转到应用内的某个页面 + uni.navigateTo({ + url + }); + } + }, + /** + * 图片路径转换 + * @param {String} img_path 图片地址 + * @param {Object} params 参数,针对商品、相册里面的图片区分大中小,size: big、mid、small + */ + img(img_path, params) { + + + var path = ""; + if (img_path != undefined && img_path != "") { + if (img_path.split(',').length > 1) { + img_path = img_path.split(',')[0]; + } + if (params && img_path != this.getDefaultImage().goods) { + // 过滤默认图 + let arr = img_path.split("."); + let suffix = arr[arr.length - 1]; + arr.pop(); + arr[arr.length - 1] = arr[arr.length - 1] + "_" + params.size.toUpperCase(); + arr.push(suffix); + // if(img_path.indexOf('attachment') == -1){ + // img_path = arr.join("."); + // } + + } + if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) { + path = Config.imgDomain + "/" + img_path; + } else { + // console.log(img_path) + path = img_path; + } + } + + // path += '?t=' + parseInt(new Date().getTime() / 1000); + return path; + }, + /** + * 时间戳转日期格式 + * @param {Object} timeStamp + */ + timeStampTurnTime(timeStamp, type = "") { + if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) { + var date = new Date(); + date.setTime(timeStamp * 1000); + var y = date.getFullYear(); + var m = date.getMonth() + 1; + m = m < 10 ? ('0' + m) : m; + var d = date.getDate(); + d = d < 10 ? ('0' + d) : d; + var h = date.getHours(); + h = h < 10 ? ('0' + h) : h; + var minute = date.getMinutes(); + var second = date.getSeconds(); + minute = minute < 10 ? ('0' + minute) : minute; + second = second < 10 ? ('0' + second) : second; + if (type) { + if (type == 'yearMonthDay') { + return y + '年' + m + '月' + d + '日'; + } + return y + '-' + m + '-' + d; + } else { + return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second; + } + } else { + return ""; + } + }, + /** + * 日期格式转时间戳 + * @param {Object} string + */ + timeTurnTimeStamp(string) { + var f = string.split(' ', 2); + var d = (f[0] ? f[0] : '').split('-', 3); + var t = (f[1] ? f[1] : '').split(':', 3); + return (new Date( + parseInt(d[0], 10) || null, + (parseInt(d[1], 10) || 1) - 1, + parseInt(d[2], 10) || null, + parseInt(t[0], 10) || null, + parseInt(t[1], 10) || null, + parseInt(t[2], 10) || null + )).getTime() / 1000; + }, + /** + * 倒计时 + * @param {Object} seconds 秒 + */ + countDown(seconds) { + let [day, hour, minute, second] = [0, 0, 0, 0] + if (seconds > 0) { + day = Math.floor(seconds / (60 * 60 * 24)) + hour = Math.floor(seconds / (60 * 60)) - (day * 24) + minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60) + second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60) + } + if (day < 10) { + day = '0' + day + } + if (hour < 10) { + hour = '0' + hour + } + if (minute < 10) { + minute = '0' + minute + } + if (second < 10) { + second = '0' + second + } + return { + d: day, + h: hour, + i: minute, + s: second + }; + }, + /** + * 数值去重 + * @param {Array} arr 数组 + * @param {string} field 字段 + */ + unique(arr, field) { + const res = new Map(); + return arr.filter((a) => !res.has(a[field]) && res.set(a[field], 1)); + }, + /** + * 判断值是否在数组中 + * @param {Object} elem + * @param {Object} arr + */ + inArray: function (elem, arr) { + return arr == null ? -1 : arr.indexOf(elem); + }, + /** + * 获取某天日期 + * @param {Object} day + */ + getDay: function (day) { + var today = new Date(); + var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day; + today.setTime(targetday_milliseconds); + + const doHandleMonth = function (month) { + var m = month; + if (month.toString().length == 1) { + m = "0" + month; + } + return m + } + + var tYear = today.getFullYear(); + var tMonth = today.getMonth(); + var tDate = today.getDate(); + var tWeek = today.getDay(); + var time = parseInt(today.getTime() / 1000); + tMonth = doHandleMonth(tMonth + 1); + tDate = doHandleMonth(tDate); + + const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] + return { + 't': time, + 'y': tYear, + 'm': tMonth, + 'd': tDate, + 'w': week[tWeek] + }; + }, + /** + * 图片选择加上传 + * @param num + * @param params + * @param callback + * @param url + */ + upload: function (num, params, callback, url) { + // #ifdef H5 + var app_type = this.isWeiXin() ? 'wechat' : 'h5'; + var app_type_name = this.isWeiXin() ? '微信公众号' : 'H5'; + // #endif + + // #ifdef MP-WEIXIN + var app_type = 'weapp'; + var app_type_name = '微信小程序'; + // #endif + + // #ifdef MP-ALIPAY + var app_type = 'aliapp'; + var app_type_name = '支付宝小程序'; + // #endif + + // #ifdef MP-BAIDU + var app_type = 'baiduapp'; + var app_type_name = '百度小程序'; + // #endif + + // #ifdef MP-TOUTIAO + var app_type = 'MP-TOUTIAO'; + var app_type_name = '头条小程序'; + // #endif + + // #ifdef MP-QQ + var app_type = 'MP-QQ'; + var app_type_name = 'QQ小程序'; + // #endif + var data = { + token: store.state.token, + app_type: app_type, + app_type_name: app_type_name + } + data = Object.assign(data, params); + + var imgs_num = num; + var _self = this; + + uni.chooseImage({ + count: imgs_num, + sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有 + sourceType: ['album', 'camera'], //从相册或者拍照 + success: async function (res) { + const tempFilePaths = res.tempFilePaths; + var _data = data; + var imgs = []; + uni.showLoading({ + title: '图片上传中' + }) + for (var i = 0; i < tempFilePaths.length; i++) { + var path = await _self.upload_file_server(tempFilePaths[i], _data, params.path, + url); + imgs.push(path); + if (imgs.length == tempFilePaths.length) { + uni.hideLoading() + uni.showToast({ + title: '上传成功', + icon: 'none' + }) + typeof callback == 'function' && callback(imgs); + } + } + }, + fail: err => { + uni.hideLoading() + uni.showToast({ + title: '上传失败', + icon: 'none' + }) + } + }); + }, + //上传 + upload_file_server(tempFilePath, data, path, url = "", callback) { + if (url) { + var uploadUrl = Config.baseUrl + url + } else { + var uploadUrl = Config.baseUrl + '/api/upload/' + path + } + return new Promise((resolve, reject) => { + uni.uploadFile({ + url: uploadUrl, + filePath: tempFilePath, + name: 'file', + fileType: data.fileType || 'image', + formData: data, + success: function (res) { + var path_str = JSON.parse(res.data); + if (path_str.code >= 0) { + resolve(path_str.data.pic_path); + typeof callback == 'function' && callback(path_str.data.pic_path); + } else { + reject("error"); + } + } + }); + + }); + + }, + /** + * 复制 + * @param {Object} value + * @param {Object} callback + */ + copy(value, callback) { + // #ifdef H5 + var oInput = document.createElement('input'); //创建一个隐藏input(重要!) + oInput.value = value; //赋值 + oInput.setAttribute("readonly", "readonly"); + document.body.appendChild(oInput); + oInput.select(); // 选择对象 + document.execCommand("Copy"); // 执行浏览器复制命令 + oInput.className = 'oInput'; + oInput.style.display = 'none'; + uni.hideKeyboard(); + this.showToast({ + title: '复制成功' + }); + + typeof callback == 'function' && callback(); + // #endif + + // #ifdef MP || APP-PLUS + uni.setClipboardData({ + data: value, + success: () => { + typeof callback == 'function' && callback(); + } + }); + // #endif + }, + /** + * 是否是微信浏览器 + */ + isWeiXin() { + // #ifndef H5 + return false; + // #endif + var ua = navigator.userAgent.toLowerCase(); + if (ua.match(/MicroMessenger/i) == "micromessenger") { + return true; + } else { + return false; + } + }, + /** + * 显示消息提示框 + * @param {Object} params 参数 + */ + showToast(params = {}) { + params.title = params.title || ""; + params.icon = params.icon || "none"; + // params.position = params.position || 'bottom'; + params.duration = params.duration || 1500; + uni.showToast(params); + if (params.success) params.success(); + }, + /** + * 检测苹果X以上的手机 + */ + isIPhoneX() { + let res = uni.getSystemInfoSync(); + if (res.model.search('iPhone X') != -1) { + return true; + } + return false; + }, + //判断安卓还是iOS + isAndroid() { + let platform = uni.getSystemInfoSync().platform + if (platform == 'ios') { + return false; + } else if (platform == 'android') { + return true; + } + }, + /** + * 深度拷贝对象 + * @param {Object} obj + */ + deepClone(obj) { + const isObject = function (obj) { + return typeof obj == 'object'; + } + + if (!isObject(obj)) { + throw new Error('obj 不是一个对象!') + } + //判断传进来的是对象还是数组 + let isArray = Array.isArray(obj) + let cloneObj = isArray ? [] : {} + //通过for...in来拷贝 + for (let key in obj) { + cloneObj[key] = isObject(obj[key]) ? this.deepClone(obj[key]) : obj[key] + } + return cloneObj + }, + + /** + * 打开微信企业客服 + * @param {Function} fallbackFunc 降级处理函数 + */ + openWxWorkServiceChat(fallbackFunc) { + // #ifdef MP-WEIXIN + let wxworkConfig = store.state?.wxworkConfig + if (wxworkConfig?.enable && wxworkConfig?.contact_url) { + // 直接使用活码链接跳转 + wx.navigateToMiniProgram({ + appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID(固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面) + path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig?.contact_url)}`, + success: () => { + console.log('跳转企业微信成功'); + }, + fail: (err) => { + console.error('跳转企业微信失败:', err); + // 降级处理:使用原有客服方式 + fallbackFunc && fallbackFunc(); + } + }); + } else { + fallbackFunc && fallbackFunc(); + } + // #endif + }, + + /** + * 自定义模板的跳转链接 + * @param {Object} link + */ + diyRedirectTo(link) { + + //if (link == null || Object.keys(link).length == 1) return; + + // 外部链接 + if (link.wap_url && link.wap_url.indexOf('http') != -1 || link.wap_url && link.wap_url.indexOf('http') != -1) { + // #ifdef H5 + window.location.href = link.wap_url; + // #endif + + // #ifdef MP + this.redirectTo('/pages_tool/webview/webview', { + src: encodeURIComponent(link.wap_url) + }); + // #endif + + } else if (link.appid) { + // 跳转其他小程序 + + uni.navigateToMiniProgram({ + appId: link.appid, + path: link.page + }) + + } else if (link.name == 'MOBILE' && !link.wap_url) { + // 拨打电话 + + uni.makePhoneCall({ + phoneNumber: link.mobile, + success: (res) => { + }, + fail: (res) => { + } + }); + + } else if (link.name == 'MEMBER_CONTACT') { + // 客服 + let servicerConfig = store.state.servicerConfig + let config = { + type: 'none' + }; + + // #ifdef H5 + config = servicerConfig.h5; + // #endif + + // #ifdef MP-WEIXIN + config = servicerConfig.weapp; + // #endif + + switch (config.type) { + case 'wxwork': + // 企业微信客服 + + // #ifdef H5 + window.location.href = config.wxwork_url; + // #endif + + // #ifdef MP-WEIXIN + this.openWxWorkServiceChat(() => { + wx.openCustomerServiceChat({ + extInfo: { + url: config.wxwork_url + }, + corpId: config.corpid, + showMessageCard: true, + sendMessageTitle: 'this.sendMessageTitle', + sendMessagePath: 'this.sendMessagePath', + sendMessageImg: 'this.sendMessageImg' + }); + }); + + // #endif + break; + case 'third': + // 第三方客服 + window.location.href = config.third_url; + break; + case 'niushop': + // Niushop客服 + this.redirectTo('/pages_tool/chat/room'); + break; + case 'weapp': + // 微信小程序,由于需要手动点击按钮触发,所以要跳转到中间页 + this.redirectTo(link.wap_url); + break; + default: + // 拨打客服电话 + let siteInfo = store.state.siteInfo; + if (siteInfo && siteInfo.site_tel) { + uni.makePhoneCall({ + phoneNumber: siteInfo.site_tel + }); + } else { + this.showToast({ + title: '抱歉,商家暂无客服,请线下联系', + }) + } + } + + } else if (link.wap_url) { + this.redirectTo(link.wap_url); + } + }, + /** + * 获取默认图 + */ + getDefaultImage() { + let defaultImg = store.state.defaultImg; + defaultImg.goods = this.img(defaultImg.goods); + defaultImg.head = this.img(defaultImg.head); + defaultImg.store = this.img(defaultImg.store); + defaultImg.article = this.img(defaultImg.article); + defaultImg.kefu = this.img(defaultImg.kefu); + defaultImg.phone = this.img(defaultImg.phone); + return defaultImg; + }, + /** + * 判断手机是否为iphoneX系列 + */ + uniappIsIPhoneX() { + let isIphoneX = false; + let systemInfo = uni.getSystemInfoSync(); + // #ifdef MP + if (systemInfo.model.search('iPhone X') != -1 || systemInfo.model.search('iPhone 11') != -1 || systemInfo.model.search('iPhone 12') != -1 || systemInfo.model.search('iPhone 13') != -1) { + isIphoneX = true; + } + // #endif + + // #ifdef H5 + var u = navigator.userAgent; + var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端 + if (isIOS) { + if (systemInfo.screenWidth == 375 && systemInfo.screenHeight == 812 && systemInfo.pixelRatio == 3) { + isIphoneX = true; + } else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 3) { + isIphoneX = true; + } else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 2) { + isIphoneX = true; + } + } + // #endif + return isIphoneX; + }, + /** + * 判断手机是否为iphone11系列 + */ + uniappIsIPhone11() { + let isIphone11 = false; + let systemInfo = uni.getSystemInfoSync(); + // #ifdef MP + if (systemInfo.model.search('iPhone 11') != -1) { + isIphone11 = true; + } + // #endif + return isIphone11; + }, + // #ifdef H5 + //判断该浏览器是否为safaria浏览器 + isSafari() { + let res = uni.getSystemInfoSync(); + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 && + ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 && + ua.indexOf('ios') === -1 && ua.indexOf('browser') === -1) { + return true; + } else { + return false; + } + }, + // #endif + numberFixed(e, f) { + if (!f) { + f = 0; + } + return Number(e).toFixed(f); + }, + /** + * 获取url参数 + */ + getUrlCode(callback) { + var url = location.search; + var theRequest = new Object(); + if (url.indexOf('?') != -1) { + var str = url.substr(1); + var strs = str.split('&'); + for (var i = 0; i < strs.length; i++) { + theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1]; + } + } + typeof callback == 'function' && callback(theRequest); + }, + /** + * 获取当前页面路由 + */ + getCurrRoute() { + let routes = getCurrentPages(); // 获取当前打开过的页面路由数组 + return routes.length ? routes[routes.length - 1].route : ''; + }, + goBack(backUrl = '/pages/index/index') { + if (getCurrentPages().length == 1) { + this.redirectTo(backUrl); + } else { + uni.navigateBack(); + } + }, + /** + * + * @param val 转化时间字符串 (转化时分秒) + * @returns {string} + */ + getTimeStr(val) { + var h = parseInt(val / 3600).toString(); + var m = parseInt((val % 3600) / 60).toString(); + if (m.length == 1) { + m = '0' + m; + } + if (h.length == 1) { + h = '0' + h; + } + return h + ':' + m; + }, + /** + * 获取定位信息 + */ + getLocation(param = {}) { + /*uni.getLocation({ + type: param.type ?? 'gcj02', + success: res => { + store.commit('setLocation', res); + typeof param.success == 'function' && param.success(res); + }, + fail: res => { + typeof param.fail == 'function' && param.fail(res); + }, + complete: res => { + typeof param.complete == 'function' && param.complete(res); + } + });*/ + }, + // 计算两个经纬度之间的距离 + getDistance(lat1, lng1, lat2, lng2) { + var radLat1 = lat1 * Math.PI / 180.0; + var radLat2 = lat2 * Math.PI / 180.0; + var a = radLat1 - radLat2; + var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; + var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); + s = s * 6378.137; // EARTH_RADIUS; + s = Math.round(s * 10000) / 10000; + return s; + }, + //记录分享人 + onSourceMember(source_member) { + Http.sendRequest({ + url: '/api/Member/alterShareRelation', + data: { + share_member: source_member, + }, + success: res => { + if (res.code >= 0) { + uni.removeStorage({ + key: 'source_member', + success: res => { + console.log('删除成功', res) + } + }) + } + } + }) + }, + /** + * 微信订阅消息 + */ + subscribeMessage(keywords) { + // #ifdef MP-WEIXIN + let url = '/weapp/api/weapp/messagetmplids'; + // #endif + // #ifdef MP-ALIPAY + let url = '/aliapp/api/aliapp/messagetmplids'; + // #endif + Http.sendRequest({ + url, + data: { + keywords: keywords + }, + success: res => { + if (res.data.length) { + // #ifdef MP-WEIXIN + uni.requestSubscribeMessage({ + tmplIds: res.data, + success: (res) => { + console.log("res", res) + }, + fail: (res) => { + console.log('fail', res) + } + }) + // #endif + + // #ifdef MP-ALIPAY + my.requestSubscribeMessage({ + entityIds: res.data, + success: res => { + console.log("res", res) + }, + fail: res => { + console.log('fail', res) + } + }); + // #endif + } + } + }) + }, + /** + * 获取小程序分享内容数据 + */ + getMpShare(path) { + //如果没有特别指定 则获取当前页面的路由 + if (!path) { + let route = this.getCurrentRoute(); + path = route.path; + if (path == '/pages/member/index') { + return new Promise((resolve, reject) => { + resolve({}) + }); + } + } + return new Promise((resolve, reject) => { + Http.sendRequest({ + url: "/weapp/api/weapp/share", + data: { + path: path + }, + success: res => { + if (res.code >= 0) { + let shareConfig = res.data.data; + if (shareConfig) { + //分享给好友 + let appMessageData = { + title: shareConfig.title, + path: shareConfig.path, + imageUrl: shareConfig.imageUrl, + success: res => { + }, + fail: res => { + } + } + //分享到朋友圈 + let query = ''; + if (shareConfig.path.indexOf('?') > 0) { + query = shareConfig.path.split('?')[1]; + } + let timeLineData = { + title: shareConfig.title, + query: shareConfig.path, + imageUrl: shareConfig.imageUrl, + } + resolve({ + appMessage: appMessageData, + timeLine: timeLineData, + }) + } else { + reject(res.data); + } + } else { + reject(res.data) + } + } + }); + }) + }, + /** + * 设置公众号分享 + * @param shareData + * @param callback + */ + setPublicShare(shareData, callback) { + if (!this.isWeiXin()) return; + Http.sendRequest({ + url: '/wechat/api/wechat/jssdkconfig', + data: { + url: uni.getSystemInfoSync().platform == 'ios' ? uni.getStorageSync('initUrl') : window.location + .href + }, + success: res => { + if (res.code == 0) { + var wxJS = new Weixin(); + wxJS.init(res.data); + wxJS.weixin.showOptionMenu(); + + wxJS.setShareData({ + title: shareData.title ?? '', + desc: shareData.desc ?? '', + link: shareData.link ?? location.href, + imgUrl: shareData.imgUrl ? this.img(shareData.imgUrl) : '' + }, (res) => { + typeof callback == 'function' && callback(res); + }) + } + } + }) + }, + //获取当前路由 + getCurrentRoute() { + let currentRoutes = getCurrentPages(); // 获取当前打开过的页面路由数组 + let currentRoute = currentRoutes[currentRoutes.length - 1].route //获取当前页面路由 + let currentParam = currentRoutes[currentRoutes.length - 1].options; //获取路由参数 + // 拼接参数 + let param = []; + for (let key in currentParam) { + param.push(key + '=' + currentParam[key]) + } + let currentPath = '/' + currentRoute; + let currentQuery = param.join('&'); + if (currentQuery) currentPath += '?' + currentQuery; + + return { + path: currentPath, + query: currentQuery, + } + }, + //获取分享路由 + getCurrentShareRoute(member_id) { + let route = this.getCurrentRoute(); + //去掉原来的分享人数据 + route.path = route.path.replace(/[?|&]source_member=\d+/, ''); + if (member_id) { + //路径的处理 + if (route.path.indexOf('?') > 0) { + route.path += '&'; + } else { + route.path += '?'; + } + route.path += 'source_member=' + member_id; + //参数的处理 + if (route.query) { + route.query += '&'; + } + route.query += 'source_member=' + member_id; + } + return route; + }, + /** + * 对象转style字符串 + * @param {Object} obj + */ + objToStyle(obj) { + let s = []; + for (let i in obj) { + s.push(i + ':' + obj[i]); + } + return s.join(';') + }, + /** + * 颜色减值 + * @param {Object} c1 + * @param {Object} c2 + * @param {Object} ratio + */ + colourBlend(c1, c2, ratio) { + ratio = Math.max(Math.min(Number(ratio), 1), 0) + let r1 = parseInt(c1.substring(1, 3), 16) + let g1 = parseInt(c1.substring(3, 5), 16) + let b1 = parseInt(c1.substring(5, 7), 16) + let r2 = parseInt(c2.substring(1, 3), 16) + let g2 = parseInt(c2.substring(3, 5), 16) + let b2 = parseInt(c2.substring(5, 7), 16) + let r = Math.round(r1 * (1 - ratio) + r2 * ratio) + let g = Math.round(g1 * (1 - ratio) + g2 * ratio) + let b = Math.round(b1 * (1 - ratio) + b2 * ratio) + r = ('0' + (r || 0).toString(16)).slice(-2) + g = ('0' + (g || 0).toString(16)).slice(-2) + b = ('0' + (b || 0).toString(16)).slice(-2) + return '#' + r + g + b + }, + /** + * 生成贝塞尔曲线轨迹 + * @param {Object} points + * @param {Object} times + */ + bezier(points, times) { + var bezier_points = []; + var points_D = []; + var points_E = []; + const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0][ + 'y' + ], 2)); + // 邻控制BC点间距 + const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1][ + 'y' + ], 2)); + // D每次在AB方向上移动的距离 + if (points[0]['x'] > points[2]['x']) { + var EACH_MOVE_AD = -(DIST_AB / times); + // E每次在BC方向上移动的距离 + var EACH_MOVE_BE = -(DIST_BC / times); + } else { + var EACH_MOVE_AD = +(DIST_AB / times); + // E每次在BC方向上移动的距离 + var EACH_MOVE_BE = +(DIST_BC / times); + } + // 点AB的正切 + const TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']); + // 点BC的正切 + const TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']); + // 点AB的弧度值 + const RADIUS_AB = Math.atan(TAN_AB); + // 点BC的弧度值 + const RADIUS_BC = Math.atan(TAN_BC); + // 每次执行 + for (var i = 1; i <= times; i++) { + // AD的距离 + var dist_AD = EACH_MOVE_AD * i; + // BE的距离 + var dist_BE = EACH_MOVE_BE * i; + // D点的坐标 + var point_D = {}; + point_D['x'] = dist_AD * Math.cos(RADIUS_AB) + points[0]['x']; + point_D['y'] = dist_AD * Math.sin(RADIUS_AB) + points[0]['y']; + points_D.push(point_D); + // E点的坐标 + var point_E = {}; + point_E['x'] = dist_BE * Math.cos(RADIUS_BC) + points[1]['x']; + point_E['y'] = dist_BE * Math.sin(RADIUS_BC) + points[1]['y']; + points_E.push(point_E); + // 此时线段DE的正切值 + var tan_DE = (point_E['y'] - point_D['y']) / (point_E['x'] - point_D['x']); + // tan_DE的弧度值 + var radius_DE = Math.atan(tan_DE); + // 地市DE的间距 + var dist_DE = Math.sqrt(Math.pow((point_E['x'] - point_D['x']), 2) + Math.pow((point_E['y'] - point_D['y']), + 2)); + // 此时DF的距离 + var dist_DF = (dist_AD / DIST_AB) * dist_DE; + // 此时DF点的坐标 + var point_F = {}; + point_F['x'] = dist_DF * Math.cos(radius_DE) + point_D['x']; + point_F['y'] = dist_DF * Math.sin(radius_DE) + point_D['y']; + bezier_points.push(point_F); + } + return { + 'bezier_points': bezier_points + }; + }, + /** + * 验证手机号 + * @param {string} mobile 被验证的mobile + * @return {object} 验证后的结果 + **/ + verifyMobile(mobile) { + var parse = /^\d{11}$/.test(mobile); + return parse; + } } \ No newline at end of file diff --git a/components/ns-contact/ns-contact.vue b/components/ns-contact/ns-contact.vue index f6e0d83..a8d8474 100644 --- a/components/ns-contact/ns-contact.vue +++ b/components/ns-contact/ns-contact.vue @@ -89,7 +89,7 @@ switch (this.config.type) { case 'wxwork': - this.openWxWorkService(); + this.openWxWorkServiceChat(); break; case 'third': location.href = this.config.third_url; @@ -103,11 +103,11 @@ }, /** * 打开企业微信客服 - * @param {boolean} useOriginalService 是否使用原有客服方式 + * @param {boolean} useOpenCustomerServiceChat 是否使用原有客服方式 */ - openWxWorkService(useOriginalService = false) { + openWxWorkServiceChat(useOpenCustomerServiceChat = false) { // #ifdef MP-WEIXIN - if (this.wxworkConfig?.contact_url && !useOriginalService) { + if (this.wxworkConfig?.enable && this.wxworkConfig?.contact_url && !useOpenCustomerServiceChat) { // 直接使用活码链接跳转 wx.navigateToMiniProgram({ appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID(固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面) @@ -135,7 +135,7 @@ // #endif // #ifdef H5 - if (this.wxworkConfig?.contact_url) { + if (this.wxworkConfig?.enable && this.wxworkConfig?.contact_url) { // H5环境直接跳转活码链接 window.location.href = this.wxworkConfig.contact_url; } else { @@ -154,7 +154,7 @@ success: (res) => { if (res.confirm) { console.log('降级处理:使用原有客服方式'); - this.openWxWorkService(true); + this.openWxWorkServiceChat(true); } } }); From 142a97d65c318d17b2ca9a962df09246df69950c Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 15:52:18 +0800 Subject: [PATCH 007/100] =?UTF-8?q?revert=EF=BC=9A=20pages/contact/contact?= =?UTF-8?q?.vue=20=E5=9B=9E=E9=80=80=E5=88=B0=E6=9C=80=E5=88=9D=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/contact/contact.vue | 94 ++++++++------------------------------- 1 file changed, 19 insertions(+), 75 deletions(-) diff --git a/pages/contact/contact.vue b/pages/contact/contact.vue index 8b48055..1eebc05 100644 --- a/pages/contact/contact.vue +++ b/pages/contact/contact.vue @@ -19,30 +19,7 @@ - - - - 企业微信 - - - @@ -160,12 +137,7 @@ \ No newline at end of file diff --git a/components/wxwork-contact/CHANGELOG.md b/components/wxwork-contact/CHANGELOG.md new file mode 100644 index 0000000..bbd49e1 --- /dev/null +++ b/components/wxwork-contact/CHANGELOG.md @@ -0,0 +1,66 @@ +# 企业微信联系客服组件更新日志 + +## v2.0.0 - 集成全局Store配置 + +### 新增功能 +- ✅ 企业微信配置集成到全局Store +- ✅ 从 `/api/config/init` 统一获取配置 +- ✅ 支持props覆盖全局配置 +- ✅ 优化配置获取逻辑 + +### 变更内容 +1. **Store集成**: + - 在 `store/index.js` 中添加 `wxworkConfig` 状态 + - 添加 `setWxworkConfig` mutation + - 在 `init` action 中从 `/api/config/init` 获取企业微信配置 + +2. **组件优化**: + - `wxwork-contact.vue` 组件现在优先从全局Store获取配置 + - 支持通过props覆盖全局配置 + - 移除单独的API调用,使用统一配置 + +3. **页面集成**: + - `pages/contact/contact.vue` 页面简化配置获取逻辑 + - 直接使用全局Store中的企业微信配置 + +### 配置格式 +后端 `/api/config/init` 需要返回以下格式的企业微信配置: + +```json +{ + "code": 0, + "data": { + // ... 其他配置 ... + "wxwork": { + "corp_id": "企业ID", + "agent_id": "应用ID", + "contact_id": "客服ID", + "contact_url": "活码链接", + "timestamp": "时间戳", + "nonceStr": "随机字符串", + "signature": "签名", + "enabled": true + } + } +} +``` + +### 使用方式 +```vue + + + + + +``` + +## v1.0.0 - 初始版本 + +### 功能 +- 企业微信JS-SDK封装 +- 基础联系客服组件 +- 支持小程序和H5环境 +- 活码跳转和SDK两种方式 \ No newline at end of file diff --git a/components/wxwork-contact/README.md b/components/wxwork-contact/README.md new file mode 100644 index 0000000..ce09d20 --- /dev/null +++ b/components/wxwork-contact/README.md @@ -0,0 +1,484 @@ +# 企业微信联系客服组件 + +## 功能说明 + +这个组件实现了在小程序中点击"联系客服"后,自动跳转到企业微信添加对应销售的功能。 + +## 关键前提条件 + +### ⚠️ 重要提醒 +微信小程序与企业微信互通有严格的平台限制和权限要求,使用前请确保满足以下所有条件: + +### 1. 微信小程序环境要求 +- **平台限制**:功能仅在微信小程序环境中可用 +- **基础库版本**:需要微信小程序基础库 2.3.0 及以上版本 +- **用户环境**:用户需要在微信中打开小程序 + +### 2. 企业微信配置要求 +- **企业认证**:企业微信账号必须完成企业认证 +- **客户联系功能**:需要开通企业微信"客户联系"功能 +- **应用权限**:企业微信应用需要有客户联系相关权限 + +### 3. 互通配置要求 +- **关联配置**:小程序必须与企业微信进行关联配置 +- **权限申请**:需要在微信开放平台和企业微信后台分别申请相应权限 +- **域名白名单**:相关域名需要在小程序和企业微信后台都配置白名单 + +### 4. 跳转权限要求 +- **小程序AppID**:需要在企业微信中配置允许跳转的小程序AppID +- **企业微信AppID**:需要在微信开放平台配置关联的企业微信AppID +- **业务域授权**:需要配置业务域授权,允许跨平台跳转 + +### 5. 开发调试要求 +- **测试环境**:需要在测试环境中验证跳转功能 +- **权限验证**:确保所有必要的API权限已申请并生效 +- **兼容性测试**:在不同微信版本中进行兼容性测试 + +### 6. 具体配置要求 + +| 条件 | 说明 | +|------|------| +| **小程序与企业微信绑定** | 在企业微信管理后台 → 「应用管理」→「小程序」中关联你的微信小程序AppID | +| **配置可信域名** | 如果涉及网页跳转或回调,需在企业微信后台配置业务域名 | +| **使用企业微信服务商 or 自建应用** | 若需高级功能(如获取客户详情),需有企业微信管理员权限或通过服务商代开发 | + +## 使用方法 + +### 1. 基础用法 + +```vue + + +``` + +### 组件架构 + +### 分层设计 +``` +调用方 (页面组件) + ↓ 传递配置参数 +wxwork-contact 组件 + ↓ 调用SDK +wxwork-jssdk.js + ↓ 调用企业微信API +企业微信服务 +``` + +### 组件设计原则 + +- **独立性**:组件不直接依赖全局Store,所有配置通过props传递 +- **职责分离**:组件只负责UI展示和企业微信SDK调用,配置管理由调用方负责 +- **灵活配置**:支持调用者覆盖任何配置参数 + +## 版本信息 + +### v3.0.0 - 统一客服服务重构版本 +- 创建 `CustomerService` 统一客服处理服务 +- 重构 `ns-contact.vue` 和 `hover-nav.vue` 消除重复代码 +- 提供统一的客服处理接口和平台适配 +- 完善错误处理和降级机制 +- 支持所有客服类型的统一调用 + +### v2.0.0 - Store集成版本 +- 企业微信配置集成到全局Store +- 从 `/api/config/init` 统一获取配置 +- 组件完全独立,通过props接收配置 +- 支持全局配置和局部覆盖 + +### v1.0.0 - 初始版本 +- 基础企业微信联系功能 +- 支持活码跳转和JS-SDK两种方式 + +### 2. 属性说明 + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| btnText | String | '添加企业微信客服' | 按钮文字 | +| corpId | String | - | 企业ID(必需) | +| agentId | String | '' | 应用ID | +| timestamp | String | '' | 时间戳 | +| nonceStr | String | '' | 随机字符串 | +| signature | String | '' | 签名 | +| contactId | String | '' | 客服ID或活码配置ID | +| contactUrl | String | '' | 活码链接 | +| showConfirm | Boolean | true | 是否显示确认弹窗 | + +### 3. 在联系页面中使用 + +在 `pages/contact/contact.vue` 中已集成使用示例: + +```vue + + +``` + +## 配置说明 + +### 后端接口需求 + +企业微信配置已集成到 `/api/config/init` 接口中,返回以下格式的数据: + +```json +{ + "code": 0, + "data": { + // ... 其他配置 ... + "wxwork": { + "corp_id": "企业ID", + "agent_id": "应用ID", + "contact_id": "客服ID", + "contact_url": "活码链接", + "timestamp": "时间戳", + "nonceStr": "随机字符串", + "signature": "签名", + "enabled": true + } + } +} +``` + +### 全局Store集成 + +企业微信配置通过以下方式集成到全局状态管理: + +1. **Store状态**:在 `store/index.js` 中添加 `wxworkConfig` 状态 +2. **配置获取**:在 `init` action 中从 `/api/config/init` 获取企业微信配置 +3. **状态更新**:使用 `setWxworkConfig` mutation 更新配置 +4. **持久化**:配置自动保存到本地存储 + +### 企业微信配置步骤 + +1. **获取企业微信活码**: + - 登录企业微信管理后台 + - 进入"客户联系" -> "配置" -> "联系我" + - 创建活码,获取配置ID或直接获取活码链接 + +2. **配置参数**: + - `corp_id`: 企业ID(在企业微信后台获取) + - `agent_id`: 企业微信应用ID + - `contact_id`: 客服的用户ID + - `contact_url`: 企业微信活码链接(推荐使用活码链接) + - `timestamp`: 生成签名的时间戳 + - `nonceStr`: 生成签名的随机字符串 + - `signature`: JS-SDK签名 + +## 典型业务流程示例 + +### 目标 +用户在小程序中点击"联系客服",自动添加对应的企业微信销售。 + +### 步骤 +1. **后端调用企业微信 API** 创建「联系我」二维码(可带场景值,如 user_id=123)。 +2. **前端在小程序中展示该二维码(或生成跳转链接)**。 +3. **用户长按识别 → 打开企业微信 → 添加客服**。 +4. **企业微信收到添加事件 → 通过 API 获取 external_userid → 关联到原小程序用户**。 + +## 实现原理 + +### 方案1:企业微信活码跳转(推荐) +- 使用企业微信活码链接 +- 通过 `uni.navigateToMiniProgram` 跳转到企业微信小程序 +- 直接添加对应的销售为联系人 + +### 方案2:JS-SDK方式 +- 使用企业微信JS-SDK +- 调用 `openUserProfile` 接口打开用户资料 +- 用户手动添加联系人 + +## 注意事项 + +### 功能限制 +1. **小程序环境**:需要在微信小程序环境中使用 +2. **权限配置**:确保小程序有跳转企业微信的权限 +3. **降级处理**:当企业微信不可用时,会降级到原有客服方式 +4. **用户体验**:建议添加确认弹窗,避免误操作 + +### 前提条件验证 +5. **权限检查**:使用前需要验证所有必需权限是否生效 +6. **配置完整性**:确保所有配置参数都已正确设置 +7. **网络环境**:确保用户网络环境允许访问企业微信服务 +8. **版本兼容**:检查微信版本和企业微信版本兼容性 + +### 调试建议 +9. **错误监控**:添加适当的错误日志和用户反馈机制 +10. **性能优化**:避免频繁的SDK初始化和配置获取 +11. **安全考虑**:敏感配置信息应在服务端处理,前端不暴露 + +## 兼容性 + +- 微信小程序:✅ 支持 +- H5环境:✅ 支持跳转活码链接 +- 其他平台:降级处理 + +## 文件结构 + +``` +components/wxwork-contact/ +├── wxwork-contact.vue # 主组件 +└── README.md # 说明文档 + +components/ns-contact/ +└── ns-contact.vue # 统一客服组件(重构后) + +components/hover-nav/ +└── hover-nav.vue # 悬浮导航组件(重构后) + +common/js/ +├── wxwork-jssdk.js # 企业微信JS-SDK封装 +└── customer-service.js # 客服统一处理服务(新增) + +store/ +└── index.js # 全局Store,包含wxworkConfig状态管理 + +pages/contact/ +└── contact.vue # 联系页面,集成企业微信功能 +``` + +## 系统梳理与优化 (v3.1.0) + +### 🔧 已修复的问题 + +#### 1. **App.vue 配置恢复** +- ✅ 修复了企业微信配置 (`wxworkConfig`) 在应用启动时的恢复 +- ✅ 确保所有配置都能正确从本地存储恢复到Store + +#### 2. **客服服务参数传递** +- ✅ 修复了 `openCustomerServiceChat` 参数传递错误 +- ✅ 正确传递 `sendMessageTitle`、`sendMessagePath`、`sendMessageImg` + +#### 3. **组件配置访问** +- ✅ 在 `ns-contact.vue` 和 `hover-nav.vue` 中添加 computed 属性 +- ✅ 确保能够正确访问 `servicerConfig` + +#### 4. **企业微信服务优化** +- ✅ 改进参数传递,支持自定义消息参数 +- ✅ 统一处理函数调用方式 + +### 🛡️ 新增功能 + +#### 1. **配置验证机制** +```javascript +const validation = this.customerService.validateConfig(); +if (!validation.isValid) { + // 处理配置错误 +} +``` + +#### 2. **错误处理增强** +- ✅ 添加配置错误弹窗 +- ✅ 改进错误日志记录 +- ✅ 警告信息提示 + +#### 3. **类型安全** +- ✅ 完善参数类型检查 +- ✅ 空值和异常情况处理 + +### 📋 当前系统状态 + +| 组件 | 状态 | 功能完整性 | +|------|------|-----------| +| **customer-service.js** | ✅ 完成 | 统一客服处理服务 | +| **ns-contact.vue** | ✅ 修复 | 支持所有客服类型 | +| **hover-nav.vue** | ✅ 修复 | 集成统一客服服务 | +| **App.vue** | ✅ 修复 | 配置完整恢复 | +| **contact.vue** | ⚠️ 待优化 | 仍使用原始方式 | + +### 🔄 待优化项 + +#### 1. **contact.vue 页面重构** +- 建议集成统一客服服务 +- 添加企业微信客服按钮 +- 统一UI交互体验 + +#### 2. **加载状态反馈** +- 客服功能调用时的loading状态 +- 网络请求的进度指示 + +#### 3. **用户体验优化** +- 客服按钮的点击反馈 +- 错误状态的友好提示 + +### 🧪 测试建议 + +1. **配置验证测试**: + - 测试各种配置缺失情况 + - 验证错误提示准确性 + +2. **平台兼容测试**: + - 微信小程序客服功能 + - H5环境跳转 + - 支付宝小程序客服 + +3. **企业微信功能测试**: + - 活码链接跳转 + - 降级处理机制 + - 参数传递正确性 + +### 📖 使用最佳实践 + +```javascript +// 推荐使用方式 +const customerService = createCustomerService(this); + +// 带配置验证的调用 +if (customerService.isConfigAvailable()) { + customerService.handleCustomerClick({ + sendMessageTitle: '商品咨询', + sendMessagePath: '/pages/goods/detail', + sendMessageImg: 'product-image.jpg' + }); +} +``` + +## 重构说明 (v3.0.0) + +### 统一客服处理服务 + +为了解决代码重复问题,我们创建了 `CustomerService` 类来统一处理所有客服逻辑: + +#### 主要改进 +1. **代码复用**:消除 `ns-contact.vue` 和 `hover-nav.vue` 中的重复代码 +2. **统一接口**:提供一致的客服处理API +3. **平台适配**:自动处理不同平台的客服配置 +4. **类型安全**:完善的错误处理和降级机制 + +#### 使用方式 + +**1. 在组件中导入** +```javascript +import { createCustomerService } from '@/common/js/customer-service.js'; +``` + +**2. 初始化服务** +```javascript +created() { + this.customerService = createCustomerService(this); + this.buttonConfig = this.customerService.getButtonConfig(); +} +``` + +**3. 处理客服点击** +```javascript +methods: { + contactServicer() { + this.customerService.handleCustomerClick({ + niushop: this.niushop, + sendMessageTitle: this.sendMessageTitle, + sendMessagePath: this.sendMessagePath, + sendMessageImg: this.sendMessageImg + }); + } +} +``` + +#### 核心方法 + +| 方法名 | 用途 | 参数 | +|--------|------|------| +| `handleCustomerClick(options)` | 统一处理客服点击 | 客服配置选项 | +| `getButtonConfig()` | 获取按钮配置 | - | +| `getServiceType()` | 获取客服类型 | - | +| `openWxworkService()` | 打开企业微信客服 | 是否使用原方式 | + +#### 支持的客服类型 + +- `wxwork` - 企业微信客服 +- `third` - 第三方客服 +- `niushop` - 牛商客服 +- `weapp` - 微信小程序客服 +- `aliapp` - 支付宝小程序客服 +- `none` - 无客服(显示提示) + +## 常见问题 (FAQ) + +### Q: contact_id 是小程序ID吗? +A: 不是的。`contact_id` 是**企业微信中客服人员的用户ID**,具体说明如下: + +- **定义**:企业微信系统内部分配给客服人员的唯一标识符 +- **获取方式**:通过企业微信管理后台的"客户联系" → "配置" → "联系我"功能创建活码时获得 +- **用途**:用于指定具体客服人员,当用户点击"联系客服"时,系统通过这个ID知道应该添加哪个企业微信客服人员 + +### Q: contact_id 和小程序ID的区别? +A: 两者的作用完全不同: + +| 字段 | 用途 | 获取方式 | +|------|------|----------| +| **contact_id** | 指定具体的企业微信客服人员 | 企业微信管理后台获取 | +| **小程序AppID** | 识别整个微信小程序应用 | 微信开放平台获取 | + +在业务流程中: +1. 用户在小程序中点击"联系客服" +2. 系统使用 `contact_id` 打开对应的企业微信客服人员资料 +3. 用户添加该企业微信客服为联系人 + +所以 `contact_id` 是企业微信客服的ID,不是小程序的ID。 + +### Q: wxwork_contact_url 从哪里来? +A: `wxwork_contact_url` 应该来自全局Store中的 `wxworkConfig`,而不是 `servicerConfig`: + +**正确的配置来源**: +- ✅ `this.$store.state.wxworkConfig.contact_url` - 企业微信配置 +- ❌ `this.config.wxwork_contact_url` - 错误的配置路径 + +**配置获取流程**: +1. **后端接口**:`/api/config/init` 返回 `wxwork_config` 数据 +2. **Store存储**:`setWxworkConfig` 将配置保存到 `wxworkConfig` 状态 +3. **组件使用**:通过 `this.$store.state.wxworkConfig.contact_url` 访问 + +**代码修正示例**: +```javascript +// 错误 ❌ +if (this.config.wxwork_contact_url) { ... } + +// 正确 ✅ +const wxworkConfig = this.$store.state?.wxworkConfig; +if (wxworkConfig?.contact_url) { ... } +``` + +**字段对应关系**: +| 后端字段 | Store字段 | 组件使用 | +|---------|----------|----------| +| `contact_url` | `contact_url` | `wxworkConfig.contact_url` | + +### Q: navigateToMiniProgram 中的企业微信小程序AppID 是指我的业务小程序ID吗? +A: 不是的!`appId: 'wxeb490c6f9b154ef9'` 是**企业微信官方小程序的AppID**,不是你的业务小程序ID。 + +**两者的区别**: + +| 小程序类型 | AppID示例 | 用途 | 所有者 | +|-----------|----------|------|--------| +| **企业微信官方小程序** | `wxeb490c6f9b154ef9` | 展示企业联系人详情页面 | 腾讯企业微信团队 | +| **你的业务小程序** | 你的AppID | 你的业务功能(电商、服务等) | 你的企业/组织 | + +**业务流程**: +``` +用户小程序 (你的业务) + ↓ 点击"联系客服" +navigateToMiniProgram 跳转到 +企业微信官方小程序 (wxeb490c6f9b154ef9) + ↓ 展示联系人详情 +用户添加客服人员到企业微信 +``` + +**关键点**: +- 这个AppID是企业微信官方小程序,通常是固定值 +- 用于跳转到企业微信环境展示联系人详情 +- 不需要替换成你自己的小程序AppID | \ No newline at end of file From ca74d4f8e58dde4364eec6e538b8e6c746e96e61 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Wed, 17 Dec 2025 09:19:19 +0800 Subject: [PATCH 009/100] =?UTF-8?q?chore:=20=E8=A6=81=E6=98=8E=E7=A1=AE?= =?UTF-8?q?=E9=9B=86=E6=88=90=E5=BE=AE=E4=BF=A1=E5=8F=8A=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=AE=9D=E5=8E=9F=E7=94=9F=E5=AE=A2=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/customer-service.js | 149 ++++++++++++++++++++++++++- components/hover-nav/hover-nav.vue | 31 +++++- components/ns-contact/ns-contact.vue | 48 ++++++++- 3 files changed, 218 insertions(+), 10 deletions(-) diff --git a/common/js/customer-service.js b/common/js/customer-service.js index a24af90..22a0857 100644 --- a/common/js/customer-service.js +++ b/common/js/customer-service.js @@ -148,7 +148,7 @@ export class CustomerService { this.openNiushopService(niushop); break; case 'weapp': - this.openWeappService(config); + this.openWeappService(config, options); break; case 'aliapp': this.openAliappService(config); @@ -234,10 +234,127 @@ export class CustomerService { /** * 打开微信小程序客服 * @param {Object} config 客服配置 + * @param {Object} options 选项参数 */ - openWeappService(config) { - // 微信小程序客服会由 open-type="contact" 自动处理 - console.log('微信小程序客服'); + openWeappService(config, options = {}) { + // 如果是官方客服,由 open-type="contact" 自动处理 + if (!this.shouldUseCustomService(config)) { + console.log('使用官方微信小程序客服'); + return; + } + + // 自定义客服处理 + console.log('使用自定义微信小程序客服'); + + // 这里可以实现自定义的客服逻辑 + // 例如:跳转到自定义客服页面、显示客服弹窗等 + const { + sendMessageTitle = '', + sendMessagePath = '', + sendMessageImg = '' + } = options; + + // 实现自定义客服逻辑 + this.handleCustomWeappService(config, options); + } + + /** + * 处理自定义微信小程序客服 + * @param {Object} config 客服配置 + * @param {Object} options 选项参数 + */ + handleCustomWeappService(config, options = {}) { + const { + sendMessageTitle = '', + sendMessagePath = '', + sendMessageImg = '' + } = options; + + // 优先级1: 如果有自定义客服页面URL,跳转到自定义页面 + if (config.customServiceUrl) { + // 构建带参数的URL + let url = config.customServiceUrl; + const params = []; + + 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) => { + console.error('跳转自定义客服页面失败:', err); + this.tryThirdPartyService(config, options); + } + }); + return; + } + + // 优先级2: 尝试第三方客服 + this.tryThirdPartyService(config, options); + } + + /** + * 尝试使用第三方客服 + * @param {Object} config 客服配置 + * @param {Object} options 选项参数 + */ + tryThirdPartyService(config, options = {}) { + // 如果配置了第三方客服URL + if (config.thirdPartyServiceUrl) { + // #ifdef H5 + window.open(config.thirdPartyServiceUrl, '_blank'); + // #endif + + // #ifdef MP-WEIXIN + // 微信小程序可以使用web-view或者跳转到第三方小程序 + if (config.thirdPartyMiniAppId) { + wx.navigateToMiniProgram({ + appId: config.thirdPartyMiniAppId, + path: config.thirdPartyMiniAppPath || '', + fail: (err) => { + console.error('跳转第三方小程序失败:', err); + this.fallbackToPhoneCall(); + } + }); + } else { + // 设置到剪贴板,让用户手动访问 + uni.setClipboardData({ + data: config.thirdPartyServiceUrl, + success: () => { + uni.showModal({ + title: '客服链接已复制', + content: '客服链接已复制到剪贴板,请在浏览器中粘贴访问', + showCancel: false + }); + } + }); + } + // #endif + return; + } + + // 降级到电话客服 + this.fallbackToPhoneCall(); + } + + /** + * 降级到电话客服 + */ + fallbackToPhoneCall() { + uni.showModal({ + title: '联系客服', + content: '在线客服暂时不可用,是否拨打电话联系客服?', + success: (res) => { + if (res.confirm) { + this.makePhoneCall(); + } + } + }); } /** @@ -321,7 +438,12 @@ export class CustomerService { let openType = ''; // #ifdef MP-WEIXIN - if (config.type === 'weapp') openType = 'contact'; + if (config.type === 'weapp') { + // 检查是否使用官方客服 + if (config.useOfficial !== false) { // 默认为true,使用官方客服 + openType = 'contact'; + } + } // #endif // #ifdef MP-ALIPAY @@ -333,6 +455,23 @@ export class CustomerService { openType }; } + + /** + * 判断是否应该使用自定义客服处理 + * @param {Object} config 客服配置 + * @returns {boolean} 是否使用自定义客服 + */ + shouldUseCustomService(config) { + // #ifdef MP-WEIXIN + // 如果是微信小程序且type为weapp,检查useOfficial配置 + if (config?.type === 'weapp') { + return config.useOfficial === false; // 明确设置为false才使用自定义 + } + // #endif + + // 其他平台或类型都使用自定义处理 + return true; + } } /** diff --git a/components/hover-nav/hover-nav.vue b/components/hover-nav/hover-nav.vue index d5a46bf..766cb2f 100644 --- a/components/hover-nav/hover-nav.vue +++ b/components/hover-nav/hover-nav.vue @@ -3,9 +3,15 @@ - - + + + @@ -66,7 +72,24 @@ export default { } }) }, - + computed: { + /** + * 是否使用官方客服 + */ + useOfficialService() { + if (!this.buttonConfig) return true; + + // #ifdef MP-WEIXIN + // 如果是微信小程序,检查配置 + if (this.buttonConfig.type === 'weapp') { + // 默认使用官方客服,除非明确设置为false + return this.buttonConfig.useOfficial !== false; + } + // #endif + + return false; + } + }, methods: { //拨打电话 call() { diff --git a/components/ns-contact/ns-contact.vue b/components/ns-contact/ns-contact.vue index 6c616f3..fc97a74 100644 --- a/components/ns-contact/ns-contact.vue +++ b/components/ns-contact/ns-contact.vue @@ -7,11 +7,25 @@ v-if="config.type == 'aliapp'" /> - + + + + + + + + + + + + @@ -57,6 +71,38 @@ export default { buttonConfig: null }; }, + computed: { + /** + * 是否使用官方客服 + */ + useOfficialService() { + if (!this.buttonConfig) return true; + + // #ifdef MP-WEIXIN + // 如果是微信小程序,检查配置 + if (this.buttonConfig.type === 'weapp') { + // 默认使用官方客服,除非明确设置为false + return this.buttonConfig.useOfficial !== false; + } + // #endif + + return false; + }, + + /** + * 客服配置 + */ + config() { + return this.customerService?.getPlatformConfig() || {}; + }, + + /** + * 打开类型 + */ + openType() { + return this.buttonConfig?.openType || ''; + } + }, created() { // 初始化客服服务 this.customerService = createCustomerService(this); From 2a0489d4b26b26c6c241344fb30ebd2c0d8821a2 Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Wed, 17 Dec 2025 11:17:50 +0800 Subject: [PATCH 010/100] =?UTF-8?q?chore=EF=BC=9A=E8=83=BD=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + common/js/ai-service.js | 489 +++ common/js/config.js | 10 +- common/js/event-safety.js | 112 + common/js/navigation.js | 288 ++ components/ai-chat-message/README.md | 271 ++ .../ai-chat-message/ai-chat-message.json | 6 + .../ai-chat-message/ai-chat-message.vue | 2766 +++++++++++++++++ components/ai-chat-message/demo.vue | 288 ++ components/hover-nav/hover-nav.vue | 320 +- lang/zh-cn/ai/ai-chat.js | 4 + manifest.json | 2 +- pages.json | 7 + pages/dify-chat/dify-chat.vue | 20 + pages_tool/ai-chat/index.json | 10 + pages_tool/ai-chat/index.vue | 650 ++++ pages_tool/kefu-demo/index.json | 6 + pages_tool/kefu-demo/index.vue | 704 +++++ project.private.config.json | 23 + scripts/iconfontcss-generate-preview.js | 57 + store/index.js | 21 +- 21 files changed, 5928 insertions(+), 127 deletions(-) create mode 100644 common/js/ai-service.js create mode 100644 common/js/event-safety.js create mode 100644 common/js/navigation.js create mode 100644 components/ai-chat-message/README.md create mode 100644 components/ai-chat-message/ai-chat-message.json create mode 100644 components/ai-chat-message/ai-chat-message.vue create mode 100644 components/ai-chat-message/demo.vue create mode 100644 lang/zh-cn/ai/ai-chat.js create mode 100644 pages/dify-chat/dify-chat.vue create mode 100644 pages_tool/ai-chat/index.json create mode 100644 pages_tool/ai-chat/index.vue create mode 100644 pages_tool/kefu-demo/index.json create mode 100644 pages_tool/kefu-demo/index.vue create mode 100644 project.private.config.json create mode 100644 scripts/iconfontcss-generate-preview.js diff --git a/.gitignore b/.gitignore index 14d7d69..db83a01 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.hbuilderx /.idea /node_modules +/iconfont-preview.html diff --git a/common/js/ai-service.js b/common/js/ai-service.js new file mode 100644 index 0000000..7b47481 --- /dev/null +++ b/common/js/ai-service.js @@ -0,0 +1,489 @@ +import Config from './config.js' +import http from './http.js' +import store from '@/store/index.js' +let currentConversationId = null; +const CONVERSATION_KEY = 'ai_conversation_id'; + +// 初始化时从本地读取 +try { + const saved = uni.getStorageSync(CONVERSATION_KEY); + if (saved) { + currentConversationId = saved; + } +} catch (e) { + console.warn('读取会话ID失败:', e); +} +export default { + /** + * 发送消息到Dify API + * @param {string} message 用户消息内容 + * @param {Object} options 配置选项 + * @returns {Promise} + */ + async sendMessage(message, options = {}) { + try { + const aiConfig = store.getters.globalAIKefuConfig + const params = { + url: '/api/kefu/chat', + data: { + user_id: store.state.memberInfo?.id || 'anonymous', + stream: false, + inputs: {}, + query: message, + response_mode: options.stream ? 'streaming' : 'blocking', + user: store.state.memberInfo?.id || 'anonymous', + conversation_id: this.getConversationId() || '' + }, + header: { + 'Content-Type': 'application/json' + } + } + console.log('Sending request to:', params.url); + console.log('Request data:', params.data); + + const response = await http.sendRequest({ + ...params, + async: false + }) + + const result = this.handleResponse(response, options); + if (result.conversationId) { + this.setConversationId(result.conversationId); + } + return result; + } catch (error) { + console.error('Dify API请求失败:', error) + throw new Error('AI服务暂时不可用,请稍后重试') + } + }, + + /** + * 流式消息处理 + */ + async sendStreamMessage(message, onChunk, onComplete) { + try { + return this.sendHttpStream(message, onChunk, onComplete) + } catch (error) { + console.error('流式消息发送失败:', error) + throw error + } + }, + + /** + * EventSource 流式聊天 + */ + chatWithEventSource(message, conversationId = '', onMessage, onComplete, onError) { + if (window.currentEventSource) { + window.currentEventSource.close(); + } + const params = new URLSearchParams({ + uniacid: store.state.uniacid || '1', + user_id: store.state.memberInfo?.id || '123456', + query: message, + conversation_id: conversationId || '', + stream: 'true' + }); + const url = `/api/kefu/chat?${params.toString()}`; + try { + const eventSource = new EventSource(url); + window.currentEventSource = eventSource; + let aiMessage = ''; + eventSource.addEventListener('message', (event) => { + try { + const data = JSON.parse(event.data); + if (data.event === 'message') { + aiMessage += data.answer || ''; + if (onMessage) onMessage(data.answer || ''); + } + if (data.event === 'message_end') { + if (onComplete) onComplete({ + conversation_id: data.conversation_id, + message: aiMessage + }); + } + if (data.conversation_id) { + conversationId = data.conversation_id; + } + } catch (error) { + console.error('解析消息失败:', error); + } + }); + eventSource.addEventListener('done', (event) => { + try { + const data = JSON.parse(event.data); + if (onComplete) onComplete(data); + } catch (error) { + console.error('解析完成事件失败:', error); + } + }); + eventSource.addEventListener('close', (event) => { + try { + const data = JSON.parse(event.data); + console.log('连接正常结束:', data); + } catch (error) { + console.error('解析关闭事件失败:', error); + } + window.currentEventSource = null; + }); + eventSource.addEventListener('error', (error) => { + console.error('EventSource错误:', error); + if (onError) onError({ error: 'EventSource连接错误' }); + window.currentEventSource = null; + }); + return eventSource; + } catch (error) { + console.error('创建EventSource失败:', error); + if (onError) onError({ error: error.message }); + return null; + } + }, + + /** + * Fetch API 流式聊天 + */ + async chatWithFetchStream(message, conversationId = '', onMessage, onComplete, onError) { + try { + let requestData; + let headers = { + 'Accept': 'text/event-stream' + }; + requestData = { + uniacid: store.state.uniacid || '1', + user_id: store.state.memberInfo?.id || '123456', + query: message, + conversation_id: conversationId || '', + stream: true, + inputs: {}, + response_mode: 'streaming', + user: store.state.memberInfo?.id || '123456' + }; + headers['Content-Type'] = 'application/json'; + const response = await fetch('/api/kefu/chat', { + method: 'POST', + headers: headers, + body: JSON.stringify(requestData) + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + if (!response.body) { + throw new Error('响应体不可用'); + } + const reader = response.body.getReader(); + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + let aiMessage = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + let lineEnd; + while ((lineEnd = buffer.indexOf('\n')) !== -1) { + const line = buffer.substring(0, lineEnd); + buffer = buffer.substring(lineEnd + 1); + if (line.startsWith('data: ')) { + try { + const data = JSON.parse(line.substring(6)); + if (data.event === 'message' || data.event === 'text_message') { + const textContent = data.answer || data.text || ''; + aiMessage += textContent; + if (onMessage) onMessage(textContent); + } else if (data.event === 'message_end' || data.event === 'done') { + if (onComplete) onComplete({ + conversation_id: data.conversation_id, + message: aiMessage, + ...data + }); + } else if (data.event === 'error' && onError) { + onError(data); + } + if (data.conversation_id) { + conversationId = data.conversation_id; + } + } catch (e) { + console.warn('解析流式数据失败:', e); + } + } + } + } + if (buffer.startsWith('data: ')) { + try { + const data = JSON.parse(buffer.substring(6)); + if ((data.event === 'message' || data.event === 'text_message') && onMessage) { + const textContent = data.answer || data.text || ''; + onMessage(textContent); + } else if ((data.event === 'message_end' || data.event === 'done') && onComplete) { + onComplete({ + conversation_id: data.conversation_id, + message: aiMessage, + ...data + }); + } + } catch (e) { + console.warn('解析剩余数据失败:', e); + } + } + } catch (error) { + console.error('Fetch流式聊天请求失败:', error); + if (onError) onError({ error: error.message }); + } + }, + + /** + * HTTP流式请求(修复conversationId未定义) + */ + async sendHttpStream(message, onChunk, onComplete) { + const params = { + url: '/api/kefu/chat', + data: { + query: message, + conversation_id: '', + user_id: store.state.memberInfo?.id || 'anonymous', + stream: true, + uniacid: store.state.uniacid, + inputs: {}, + response_mode: 'streaming', + user: store.state.memberInfo?.id || 'anonymous' + }, + header: { + 'Content-Type': 'application/json' + } + } + // #ifdef H5 + try { + const url = Config.baseUrl + `/api/kefu/chat`; + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream', + }, + body: JSON.stringify({ + uniacid: store.state.uniacid || '1', + inputs: {}, + query: message, + response_mode: 'streaming', + user_id: store.state.memberInfo?.id || 'anonymous' + }) + }) + const reader = response.body.getReader() + const decoder = new TextDecoder() + let content = '' + let buffer = '' + // 修复:声明conversationId变量 + let conversationId = ''; + + function processStreamData(buffer, callback) { + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + lines.forEach(line => { + line = line.trim(); + if (!line) return; + if (line.startsWith('data:')) { + const dataPart = line.slice(5).trim(); + if (dataPart) { + try { + const data = JSON.parse(dataPart); + if (data.event === 'message') { + callback(data.answer || ''); + } + if (data.conversation_id) { + conversationId = data.conversation_id; + } + if (data.event === 'message_end') { + console.log('对话完成'); + } + } catch (error) { + console.error('解析流式数据失败:', error); + } + } + } + }); + return buffer; + } + + while (true) { + const { done, value } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }); + buffer = processStreamData(buffer, (newData) => { + if (newData) { + content += newData; + if (onChunk) onChunk(newData); + } + }); + } + + if (onComplete) onComplete({ content, conversation_id: conversationId }); + return { content, conversation_id: conversationId }; + } catch (error) { + console.error('HTTP流式请求失败:', error) + throw error + } + // #endif + // #ifndef H5 + const response = await http.sendRequest({ + ...params, + async: false + }) + if (response.success && response.data) { + const content = response.data + const chunkSize = 3 + let index = 0 + const streamInterval = setInterval(() => { + if (index < content.length) { + const chunk = content.substring(index, index + chunkSize) + index += chunkSize + if (onChunk) onChunk(chunk) + } else { + clearInterval(streamInterval) + if (onComplete) onComplete(content) + } + }, 100) + } + // #endif + }, + + /** + * 处理API响应 + */ + handleResponse(response, options) { + if (response.code === 0 || response.success) { + return { + success: true, + content: response.data?.answer || response.data?.content || response.data?.reply|| response.data, + conversationId: response.data?.conversation_id, + messageId: response.data?.message_id + } + } else { + throw new Error(response.message || 'AI服务返回错误') + } + }, + + /** + * 生成会话ID + */ + async generateConversationId() { + const params = { + url: '/api/kefu/createConversation', + data: { + uniacid: store.state.uniacid, + user_id: store.state.memberInfo?.id || 'anonymous', + member_id: store.state.memberInfo?.id || '', + }, + header: { + 'Content-Type': 'application/json' + } + } + const response = await http.sendRequest({ + ...params, + async: false + }) + return this.handleResponse(response) + }, + + /** + * 获取AI服务状态 + */ + async getServiceStatus() { + try { + const response = await http.sendRequest({ + url: '/api/kefu/health', + async: false, + data: {} + }) + const available = [response?.data?.status, response?.data?.components?.ai_service_config?.status].filter(item => item === 'healthy').length > 0; + return { + available, + reason: available ? '服务正常' : '服务异常' + } + } catch (error) { + return { + available: false, + reason: '服务检查失败' + } + } + }, + + /** + * 清除会话历史 + */ + async clearConversation(conversationId) { + try { + const response = await http.sendRequest({ + url: '/api/kefu/clear-conversation', + data: { conversation_id: conversationId }, + async: false + }) + return response.success + } catch (error) { + console.error('清除会话失败:', error) + return false + } + }, + getConversationId() { + return currentConversationId; + }, + + setConversationId(id) { + if (id && id !== currentConversationId) { + currentConversationId = id; + try { + uni.setStorageSync(CONVERSATION_KEY, id); + } catch (e) { + console.error('保存会话ID失败:', e); + } + } + }, + + clearConversationId() { + currentConversationId = null; + try { + uni.removeStorageSync(CONVERSATION_KEY); + } catch (e) { + console.error('清除会话ID失败:', e); + } + }, + + /** + * 获取会话历史 + */ + async getConversationHistory(params = {}) { + try { + if (!params.conversation_id) { + throw new Error('会话ID(conversation_id)是必填参数'); + } + const requestData = { + uniacid: params.uniacid || store.state.uniacid || '1', + conversation_id: params.conversation_id, + user_id: params.user_id || store.state.memberInfo?.id || 'anonymous', + limit: params.limit || 20, + offset: params.offset || 0, + member_id: params.member_id || store.state.memberInfo?.id || '', + token: params.token || '' + }; + const response = await http.sendRequest({ + url: '/api/kefu/getHistory', + data: requestData, + header: { + 'Content-Type': 'application/json' + }, + async: false + }); + if (response.code === 0 || response.success) { + return { + success: true, + data: response.data, + messages: response.data?.messages || [], + total: response.data?.total || 0, + page_info: response.data?.page_info || { limit: 20, offset: 0 } + }; + } else { + throw new Error(response.message || '获取会话历史失败'); + } + } catch (error) { + console.error('获取会话历史失败:', error); + throw new Error(error.message || '获取会话历史时发生错误,请稍后重试'); + } + } +} \ No newline at end of file diff --git a/common/js/config.js b/common/js/config.js index e97d99d..a8b5e0c 100644 --- a/common/js/config.js +++ b/common/js/config.js @@ -14,16 +14,18 @@ try { // 调试版本,配置说明 const devCfg = { // 商户ID - uniacid: 460, //825 + uniacid: 1, //825 //api请求地址 - baseUrl: 'https://xcx30.5g-quickapp.com/', + baseUrl: 'https://dev.aigc-quickapp.com/', + // baseUrl: 'http://localhost:8010/', // 图片域名 - imgDomain: 'https://xcx30.5g-quickapp.com/', + imgDomain: 'https://dev.aigc-quickapp.com/', + //imgDomain: 'http://localhost:8010/', // H5端域名 - h5Domain: 'https://xcx30.5g-quickapp.com/', + h5Domain: 'https://dev.aigc-quickapp.com/', // // api请求地址 // baseUrl: 'https://tsaas.liveplatform.cn/', diff --git a/common/js/event-safety.js b/common/js/event-safety.js new file mode 100644 index 0000000..7798f8e --- /dev/null +++ b/common/js/event-safety.js @@ -0,0 +1,112 @@ +// 事件安全处理工具 +export class EventSafety { + // 创建安全的事件对象 + static createSafeEvent(originalEvent = {}) { + const safeEvent = { + type: originalEvent.type || 'unknown', + timeStamp: originalEvent.timeStamp || Date.now(), + detail: originalEvent.detail || {}, + // 安全的目标对象 + get target() { + return EventSafety.createSafeTarget(originalEvent.target) + }, + get currentTarget() { + return EventSafety.createSafeTarget(originalEvent.currentTarget) + }, + // 安全的 matches 方法 + matches(selector) { + return EventSafety.safeMatches(originalEvent.target, selector) + } + } + + return new Proxy(safeEvent, { + get(obj, prop) { + // 防止访问不存在的属性 + if (prop in obj) { + return obj[prop] + } + return undefined + } + }) + } + + // 创建安全的目标对象 + static createSafeTarget(target) { + if (!target || typeof target !== 'object') { + return EventSafety.getFallbackTarget() + } + + const safeTarget = { + // 基础属性 + tagName: target.tagName || '', + id: target.id || '', + className: target.className || '', + // 安全的方法 + matches: (selector) => EventSafety.safeMatches(target, selector), + // 数据集 + dataset: target.dataset || {} + } + + return safeTarget + } + + // 安全的 matches 检查 + static safeMatches(element, selector) { + if (!element || typeof element.matches !== 'function') { + return false + } + + try { + return element.matches(selector) + } catch (error) { + console.warn('matches 检查失败:', error) + return false + } + } + + // 回退目标对象 + static getFallbackTarget() { + return { + tagName: '', + id: '', + className: '', + matches: () => false, + dataset: {} + } + } + + // 包装事件处理器 + static wrapEventHandler(handler, options = {}) { + return function(event) { + try { + // 创建安全的事件对象 + const safeEvent = EventSafety.createSafeEvent(event) + return handler.call(this, safeEvent) + } catch (error) { + console.error('事件处理错误:', error) + // 可选的错误处理 + if (options.onError) { + options.onError(error, event, this) + } + } + } + } + + // 验证事件类型 + static isValidEventType(event, expectedType) { + return event && event.type === expectedType + } + + // 提取安全的事件数据 + static extractEventData(event, fields = ['type', 'timeStamp', 'detail']) { + const result = {} + + fields.forEach(field => { + if (event && field in event) { + result[field] = event[field] + } + }) + + return result + } +} \ No newline at end of file diff --git a/common/js/navigation.js b/common/js/navigation.js new file mode 100644 index 0000000..0550cd0 --- /dev/null +++ b/common/js/navigation.js @@ -0,0 +1,288 @@ +import { EventSafety } from './event-safety' + +export class NavigationHelper { + constructor() { + this.navigationCache = new Map() + } + + // 安全地获取导航栏高度 + async getNavigationHeight(component, options = {}) { + const cacheKey = `nav_height` + + // 检查缓存 + if (this.navigationCache.has(cacheKey) && !options.forceRefresh) { + return this.navigationCache.get(cacheKey) + } + + // 获取高度 + try { + // 尝试直接获取 uni-page-head + const height = await this.getDirectNavigationHeight(component) + if (height > 0) { + this.navigationCache.set(cacheKey, height) + return height + } + + // 备用方案:平台特定方法 + const platformHeight = await this.getPlatformNavigationHeight() + this.navigationCache.set(cacheKey, platformHeight) + return platformHeight + } catch (error) { + console.warn('获取导航栏高度失败,使用默认值:', error) + const defaultHeight = this.getDefaultNavHeight() + this.navigationCache.set(cacheKey, defaultHeight) + return defaultHeight + } + } + + // 直接查询导航栏高度 + getDirectNavigationHeight(component) { + return new Promise((resolve) => { + const query = uni.createSelectorQuery().in(component) + + query.select('.uni-page-head').boundingClientRect((rect) => { + if (rect && rect.height > 0) { + console.log('直接查询导航栏高度成功:', rect.height) + resolve(rect.height) + } else { + console.warn('未找到 uni-page-head 元素或高度为0') + resolve(0) + } + }).exec() + }) + } + + // 平台特定的高度获取 + getPlatformNavigationHeight() { + return new Promise((resolve) => { + // #ifdef MP-WEIXIN + // 微信小程序精确计算 + try { + const menuButtonInfo = wx.getMenuButtonBoundingClientRect() + const systemInfo = uni.getSystemInfoSync() + + const height = menuButtonInfo.bottom + + (menuButtonInfo.top - systemInfo.statusBarHeight) + console.log('微信小程序导航栏高度:', height) + resolve(height) + } catch (error) { + console.error('微信小程序高度计算失败:', error) + resolve(44) + } + + // #endif + + // #ifdef H5 + // H5环境:尝试获取自定义导航栏或使用默认值 + if (typeof document !== 'undefined') { + const customNav = document.querySelector('.uni-page-head') + if (customNav) { + resolve(customNav.offsetHeight) + } else { + resolve(44) // 默认导航栏高度 + } + } else { + resolve(44) + } + // #endif + + // #ifdef APP-PLUS + // App端:状态栏 + 导航栏 + try { + const statusBarHeight = plus.navigator.getStatusbarHeight() + resolve(statusBarHeight + 44) + } catch (error) { + console.error('App端高度获取失败:', error) + resolve(88) + } + // #endif + + // 默认值 + resolve(44) + }) + } + + // 获取默认高度 + getDefaultHeight() { + // #ifdef MP-WEIXIN + return 44 // 微信小程序默认 + // #endif + // #ifdef H5 + return 44 // H5默认 + // #endif + // #ifdef APP-PLUS + return 88 // App默认(状态栏44 + 导航栏44) + // #endif + return 44 + } + + // 获取状态栏高度 + getStatusBarHeight() { + // #ifdef MP-WEIXIN + const systemInfo = uni.getSystemInfoSync() + return systemInfo.statusBarHeight || 20 + // #endif + // #ifdef H5 + return 0 // H5通常没有状态栏 + // #endif + // #ifdef APP-PLUS + try { + return plus.navigator.getStatusbarHeight() + } catch (error) { + return 44 + } + // #endif + return 0 + } + + // 获取安全区域 + getSafeAreaInsets() { + try { + const systemInfo = uni.getSystemInfoSync() + return systemInfo.safeArea || { + top: 0, + bottom: 0, + left: 0, + right: 0 + } + } catch (error) { + return { + top: 0, + bottom: 0, + left: 0, + right: 0 + } + } + } + + // 创建安全的事件处理器 + createSafeEventHandler(handler, options = {}) { + return EventSafety.wrapEventHandler(handler, options) + } + + // 安全地处理服务请求事件 + createServiceRequestHandler(component) { + return this.createSafeEventHandler((event) => { + return this.handleServiceRequest(event, component) + }, { + onError: (error, event) => { + this.handleNavigationError(error, event, component) + } + }) + } + + // 处理服务请求 + async handleServiceRequest(event, component) { + console.log('处理导航相关服务请求:', event.type) + + // 安全检查事件目标 + if (this.shouldProcessNavigationRequest(event)) { + await this.processNavigationRequest(event, component) + } + } + + // 检查是否应该处理导航请求 + shouldProcessNavigationRequest(event) { + // 方法1:检查事件类型 + if (event.type === 'service.requestComponentInfo') { + return true + } + + // 方法2:检查目标元素 + if (event.matches('.navigation-component') || event.matches('.uni-page-head')) { + return true + } + + // 方法3:检查事件详情 + if (event.detail && event.detail.componentType === 'navigation') { + return true + } + + return false + } + + // 处理导航请求 + async processNavigationRequest(event, component) { + try { + // 获取导航栏信息 + const navInfo = await this.getNavigationInfo(component) + + // 发送响应 + this.emitNavigationResponse(navInfo, component) + + } catch (error) { + console.error('处理导航请求失败:', error) + throw error + } + } + + + // 获取完整的导航信息 + async getNavigationInfo(component) { + const [navHeight, statusBarHeight, safeArea] = await Promise.all([ + this.getNavigationHeight(component), + this.getStatusBarHeight(), + this.getSafeAreaInsets() + ]) + + return { + navHeight, + statusBarHeight, + safeArea, + timestamp: Date.now() + } + } + + // 发送导航响应 + emitNavigationResponse(navInfo, component) { + if (component && component.$emit) { + component.$emit('navigation.infoResponse', { + success: true, + data: navInfo, + timestamp: Date.now() + }) + } + } + + // 错误处理 + handleNavigationError(error, event, component) { + console.error('导航处理错误:', { + error: error.message, + eventType: event?.type, + component: component?.$options?.name + }) + + // 发送错误响应 + if (component && component.$emit) { + component.$emit('navigation.infoError', { + success: false, + error: error.message, + timestamp: Date.now() + }) + } + + // 显示用户友好的错误信息 + this.showError('导航服务暂时不可用') + } + + // 显示错误提示 + showError(message) { + uni.showToast({ + title: message, + icon: 'none', + duration: 2000 + }) + } + + // 清理缓存 + clearCache() { + this.navigationCache.clear() + console.log('导航缓存已清理') + } + +} + +// 创建全局实例 +const navigationHelper = new NavigationHelper() + +export default navigationHelper \ No newline at end of file diff --git a/components/ai-chat-message/README.md b/components/ai-chat-message/README.md new file mode 100644 index 0000000..0f999a8 --- /dev/null +++ b/components/ai-chat-message/README.md @@ -0,0 +1,271 @@ +# AI智能客服组件 + +一个功能完整的AI智能客服对话组件,支持多种消息类型和交互功能。 + +## 功能特性 + +- ✅ 支持对话上下文管理 +- ✅ 支持多种消息类型:文本、Markdown、文件、音频、视频、链接、商品卡片 +- ✅ 支持语音输入和录音 +- ✅ 支持图片、文件、位置等附件发送 +- ✅ 支持消息操作按钮(点赞、踩等) +- ✅ 支持历史消息加载 +- ✅ 响应式设计,适配多端 + +## 安装使用 + +### 1. 引入组件 + +在 `pages.json` 中注册组件: + +```json +{ + "usingComponents": { + "ai-chat-message": "/components/ai-chat-message/ai-chat-message" + } +} +``` + +### 2. 在页面中使用 + +```vue + + + +``` + +## Props 配置 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| initialMessages | Array | [] | 初始消息列表 | +| userAvatar | String | '/static/images/default-avatar.png' | 用户头像 | +| aiAvatar | String | '/static/images/ai-avatar.png' | AI头像 | +| showLoadMore | Boolean | true | 是否显示加载更多 | +| maxMessages | Number | 100 | 最大消息数量 | + +## Events 事件 + +| 事件名 | 参数 | 说明 | +|--------|------|------| +| message-sent | message | 用户发送消息 | +| ai-response | message | AI回复消息 | +| action-click | {action, message} | 操作按钮点击 | +| history-loaded | messages | 历史消息加载完成 | +| file-preview | message | 文件预览 | +| audio-play | message | 音频播放 | +| audio-pause | message | 音频暂停 | +| video-play | message | 视频播放 | +| video-pause | message | 视频暂停 | +| link-open | message | 链接打开 | +| product-view | message | 商品查看 | +| input-change | value | 输入内容变化 | + +## 消息类型格式 + +### 文本消息 +```javascript +{ + id: 1, + role: 'user', // 或 'ai' + type: 'text', + content: '消息内容', + timestamp: Date.now() +} +``` + +### Markdown消息 +```javascript +{ + id: 2, + role: 'ai', + type: 'markdown', + content: '# 标题\n**粗体** *斜体* `代码`', + timestamp: Date.now() +} +``` + +### 文件消息 +```javascript +{ + id: 3, + role: 'ai', + type: 'file', + fileName: '文档.pdf', + fileSize: 1024000, + url: '文件地址', + timestamp: Date.now() +} +``` + +### 音频消息 +```javascript +{ + id: 4, + role: 'ai', + type: 'audio', + title: '语音消息', + duration: 60, // 秒 + url: '音频地址', + timestamp: Date.now() +} +``` + +### 视频消息 +```javascript +{ + id: 5, + role: 'ai', + type: 'video', + title: '产品介绍', + duration: 120, // 秒 + url: '视频地址', + cover: '封面图', + timestamp: Date.now() +} +``` + +### 链接消息 +```javascript +{ + id: 6, + role: 'ai', + type: 'link', + title: '帮助文档', + description: '详细的使用说明', + url: 'https://example.com', + image: '缩略图', + timestamp: Date.now() +} +``` + +### 商品卡片 +```javascript +{ + id: 7, + role: 'ai', + type: 'product', + title: '商品名称', + price: 299, + description: '商品描述', + image: '商品图片', + timestamp: Date.now() +} +``` + +## 方法 + +通过 ref 调用组件方法: + +```javascript +// 添加消息 +this.$refs.chat.addMessage(message) + +// 清空消息 +this.$refs.chat.clearMessages() + +// 滚动到底部 +this.$refs.chat.scrollToBottom() +``` + +## 样式定制 + +组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制: + +```css +.ai-chat-container { + --primary-color: #ff4544; + --bg-color: #f8f8f8; + --text-color: #333; +} +``` + +## 方法 + +通过 ref 调用组件方法: + +```javascript +// 添加消息 +this.$refs.chat.addMessage(message) + +// 清空消息 +this.$refs.chat.clearMessages() + +// 滚动到底部 +this.$refs.chat.scrollToBottom() + +// 开始语音输入 +this.$refs.chat.startVoiceInput() + +// 停止语音输入 +this.$refs.chat.stopVoiceInput() +``` + +## 样式定制 + +组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制: + +```css +.ai-chat-container { + --primary-color: #ff4544; + --bg-color: #f8f8f8; + --text-color: #333; + --border-color: #eeeeee; + --user-bg: #e6f7ff; + --ai-bg: #f6f6f6; +} +``` + +## 图标字体 + +组件使用自定义图标字体,需要在页面中引入: + +```html + +``` + +## 注意事项 + +1. 组件已适配项目中已有的 `ns-loading` 组件 +2. 需要配置对应的图标字体文件 +3. 音频播放功能在 H5 和 APP 端支持较好 +4. 文件预览功能依赖平台能力 +5. 语音输入功能需要用户授权麦克风权限 \ No newline at end of file diff --git a/components/ai-chat-message/ai-chat-message.json b/components/ai-chat-message/ai-chat-message.json new file mode 100644 index 0000000..d806736 --- /dev/null +++ b/components/ai-chat-message/ai-chat-message.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "ns-loading": "../ns-loading/ns-loading" + } +} \ No newline at end of file diff --git a/components/ai-chat-message/ai-chat-message.vue b/components/ai-chat-message/ai-chat-message.vue new file mode 100644 index 0000000..528f729 --- /dev/null +++ b/components/ai-chat-message/ai-chat-message.vue @@ -0,0 +1,2766 @@ + \ No newline at end of file diff --git a/pages_tool/member/footprint.vue b/pages_tool/member/footprint.vue index 27e7361..5c0de12 100644 --- a/pages_tool/member/footprint.vue +++ b/pages_tool/member/footprint.vue @@ -23,9 +23,9 @@ - {{ $lang('common.currencySymbol') }} - {{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }} - .{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }} + + + @@ -39,7 +39,7 @@ {{ $lang('common.currencySymbol') }} {{ showMarketPrice(item) }} - 已售{{ item.sale_num }}{{ item.unit ? item.unit : '件' }} + From 81088022041778c98f0b4165678e55c0b027980b Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Thu, 8 Jan 2026 11:30:35 +0800 Subject: [PATCH 026/100] =?UTF-8?q?chore=EF=BC=9A=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E5=92=8Cdev=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/zh-cn/goods/category.js | 2 +- lang/zh-cn/goods/detail.js | 2 +- lang/zh-cn/goods/list.js | 2 +- pages_goods/detail.vue | 5 ++++- pages_goods/list.vue | 2 +- pages_tool/member/footprint.vue | 8 ++++---- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lang/zh-cn/goods/category.js b/lang/zh-cn/goods/category.js index 47c7eb3..87105e7 100644 --- a/lang/zh-cn/goods/category.js +++ b/lang/zh-cn/goods/category.js @@ -3,5 +3,5 @@ export const lang = { title: '商品分类', search:'商品搜索', seeMore : '查看更多', - Make: "咨询" + Make: "询底价" } diff --git a/lang/zh-cn/goods/detail.js b/lang/zh-cn/goods/detail.js index e1a0744..1faa509 100644 --- a/lang/zh-cn/goods/detail.js +++ b/lang/zh-cn/goods/detail.js @@ -22,7 +22,7 @@ export const lang = { status:'该商品已下架', sellout:'库存不足', max:'已达最大限购数量', - makebtn:' ', + makebtn:'价格电议', sales:'销量', stock:'库存', kefu:'客服', diff --git a/lang/zh-cn/goods/list.js b/lang/zh-cn/goods/list.js index 7806673..fc8bec1 100644 --- a/lang/zh-cn/goods/list.js +++ b/lang/zh-cn/goods/list.js @@ -6,6 +6,6 @@ export const lang = { Price:'价格', Filter:'筛选', Search:'请输入您要搜索的商品', - Make:'查看详情', + Make:'立即询底价', } diff --git a/pages_goods/detail.vue b/pages_goods/detail.vue index 5bdaefb..2328b31 100644 --- a/pages_goods/detail.vue +++ b/pages_goods/detail.vue @@ -90,7 +90,10 @@ {{ goodsSkuDetail.label_name }} - + + {{$lang('stock')}}: {{ goodsSkuDetail.stock +goodsSkuDetail.unit}} + {{$lang('sales')}}: {{ goodsSkuDetail.sale_num+goodsSkuDetail.unit }} + + + + + @@ -26,6 +31,10 @@ import has from '@system.has' // #endif +const closeOtherPagePopUpHooks = new Set(); +const privacyContractPage = '/pages_tool/agreement/contenr?type=0'; +const privacyServicePage = '/pages_tool/agreement/contenr?type=1'; + export default { name: 'PrivacyPopup', data() { @@ -34,7 +43,6 @@ export default { showPop: false, privacyAuthorization: null, privacyResolves: new Set(), - closeOtherPagePopUpHooks: new Set(), privacyContractName: '用户隐私保护指引', appName: '本小程序', // #ifdef WEB || H5 @@ -49,7 +57,6 @@ export default { this.curPageShow() }, created() { - console.log(`隐私组件初始化,隐私政策名称:${this.privacyContractName}`) try { // #ifdef MP-WEIXIN //查询微信侧记录的用户是否有待同意的隐私政策信息 @@ -64,7 +71,7 @@ export default { // #ifdef QUICKAPP-WEBVIEW || H5 if (this.$util.isQuickApp()) { - if (has?.getPrivacySetting) { + if (typeof has != 'undefined' && has?.getPrivacySetting) { has.getPrivacySetting({ success: (res) => { if (res.privacyContractName) { @@ -83,7 +90,7 @@ export default { getPrivacySettingByQuickApp() { // #ifdef QUICKAPP-WEBVIEW || H5 || WEB if (this.$util.isQuickApp()) { - if (has?.getPrivacySetting) { + if (typeof has != 'undefined' && has?.getPrivacySetting) { has.getPrivacySetting({ success: (res) => { // console.log(res) @@ -96,6 +103,15 @@ export default { } }, }) + } else { + // 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理,判断是否已经授权 + if (uni.getStorageSync('privacyAgreed')) { + this.$emit('agree') + } else { + this.popUp() + // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗 + this.closeOtherPagePopUp(this.disPopUp) + } } } // #endif @@ -116,7 +132,7 @@ export default { // #ifdef QUICKAPP-WEBVIEW || H5 || WEB if (this.$util.isQuickApp()) { // 监听快速应用侧隐私政策授权变化事件 - if (has?.onPrivacySettingChange) { + if (typeof has != 'undefined' && has?.onPrivacySettingChange) { has.onPrivacySettingChange((res) => { // console.log(res) if (res.needAuthorization) { @@ -132,7 +148,7 @@ export default { // #endif // 主动查询用户隐私政策授权状态,针对快速应用 - this.getPrivacySettingByQuickApp(); + this.getPrivacySettingByQuickApp(); }, // 主动查询用户隐私政策授权状态 proactive() { @@ -161,6 +177,7 @@ export default { }, //初始化监听程序 curPageShow() { + closeOtherPagePopUpHooks.add(this.disPopUp) this.privacyAuthorization = resolve => { this.privacyResolves.add(resolve) //打开弹窗 @@ -168,11 +185,10 @@ export default { // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗 this.closeOtherPagePopUp(this.disPopUp) } - this.closeOtherPagePopUpHooks.add(this.disPopUp) }, // 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗 closeOtherPagePopUp(closePopUp) { - this.closeOtherPagePopUpHooks.forEach(hook => { + closeOtherPagePopUpHooks.forEach(hook => { if (closePopUp !== hook) { hook() } @@ -193,7 +209,7 @@ export default { // #ifdef QUICKAPP-WEBVIEW || H5 || WEB if (this.$util.isQuickApp()) { - if (has?.openPrivacySetting) { + if (typeof has != 'undefined' && has?.openPrivacySetting) { has.openPrivacySetting({ success: (res) => { // console.log('打开隐私协议', res); @@ -202,10 +218,13 @@ export default { // console.error('打开隐私协议失败', err) } }); + } else { + // 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理 + this.$util.redirectTo(privacyContractPage); } } else { // H5 环境下的处理逻辑 - this.$util.redirectTo('/pages_tool/agreement/contenr?type=0'); + this.$util.redirectTo(privacyContractPage); } // #endif }, @@ -213,7 +232,7 @@ export default { openPrivacyService() { // #ifdef QUICKAPP-WEBVIEW || H5 || WEB if (this.$util.isQuickApp()) { - if (has?.openPrivacySetting) { + if (typeof has != 'undefined' && has?.openPrivacySetting) { has.openPrivacySetting({ success: (res) => { // console.log('打开用户服务协议', res); @@ -222,10 +241,13 @@ export default { // console.error('打开用户服务协议失败', err) } }); + } else { + // 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理 + this.$util.redirectTo(privacyServicePage); } } else { // H5 环境下的处理逻辑 - this.$util.redirectTo('/pages_tool/agreement/contenr?type=1'); + this.$util.redirectTo(privacyServicePage); } // #endif }, @@ -258,25 +280,26 @@ export default { this.privacyResolves.clear() //关闭弹窗 this.disPopUp() + + // #ifdef QUICKAPP-WEBVIEW || H5 || WEB + // 保存用户授权状态到本地存储,用于快应用Webview组件中的H5方式处理 + uni.setStorageSync('privacyAgreed', true); + // #endif this.$emit('agree') }, //打开弹窗 popUp() { - if (this.showPop === false) { - this.showPop = true - } + this.showPop = true; }, //关闭弹窗 disPopUp() { - if (this.showPop === true) { - this.showPop = false - } + this.showPop = false; }, }, beforeDestroy() { // 清理事件监听器和集合 this.privacyResolves.clear() - this.closeOtherPagePopUpHooks.clear() + closeOtherPagePopUpHooks.delete(this.disPopUp) // 注意:这里需要根据实际情况清理微信和快速应用的事件监听器 // 由于微信的 wx.onNeedPrivacyAuthorization 没有对应的 off 方法,这里可能需要其他方式处理 }, @@ -332,6 +355,9 @@ export default { .privacy-button-btn { color: #FFF; font-size: 30rpx; + // #ifdef QUICKAPP-WEBVIEW || H5 || WEB + font-size: 28rpx; + // #endif font-weight: 500; line-height: 100rpx; text-align: center; diff --git a/lang/zh-cn/order/activist.js b/lang/zh-cn/order/activist.js index 3113f05..280682c 100644 --- a/lang/zh-cn/order/activist.js +++ b/lang/zh-cn/order/activist.js @@ -1,6 +1,6 @@ -export const lang = { - //title为每个页面的标题 - title: '退款', - checkDetail: '查看详情', - emptyTips: '暂无退款记录' -} +export const lang = { + //title为每个页面的标题 + title: '售后', + checkDetail: '查看详情', + emptyTips: '暂无售后记录' +} diff --git a/pages_goods/public/css/list.scss b/pages_goods/public/css/list.scss index b7e0e4b..15aa9ca 100644 --- a/pages_goods/public/css/list.scss +++ b/pages_goods/public/css/list.scss @@ -400,7 +400,7 @@ .price { color: var(--price-color); color: #fff !important; - font-size: 15rpx !important; + font-size: 27rpx !important; font-weight: bold !important; } } From 52b5f5b00694a2c440fdc7d620f9ff9f2c2786ef Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Thu, 8 Jan 2026 14:58:10 +0800 Subject: [PATCH 028/100] =?UTF-8?q?chore=EF=BC=9A=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=BA=86dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages_tool/contact/contact.vue | 89 ++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/pages_tool/contact/contact.vue b/pages_tool/contact/contact.vue index 52dbbdb..70ae2b0 100644 --- a/pages_tool/contact/contact.vue +++ b/pages_tool/contact/contact.vue @@ -14,6 +14,7 @@ + @@ -32,7 +33,6 @@ - @@ -43,6 +43,7 @@ {{ item.realname }} {{ item.position }} + {{ item.address }} @@ -63,6 +64,20 @@ + @@ -73,8 +88,20 @@ + + + + + - + + @@ -117,6 +144,7 @@ {{ $lang('messageContent') }} + - - - - - - - - - - - - - - - - - diff --git a/components/goods-detail-view/detail.js b/pages_goods/_components/goods-detail-view/detail.js similarity index 100% rename from components/goods-detail-view/detail.js rename to pages_goods/_components/goods-detail-view/detail.js diff --git a/components/goods-detail-view/goods-detail-view.vue b/pages_goods/_components/goods-detail-view/goods-detail-view.vue similarity index 75% rename from components/goods-detail-view/goods-detail-view.vue rename to pages_goods/_components/goods-detail-view/goods-detail-view.vue index 3a6a6cb..0020ac9 100644 --- a/components/goods-detail-view/goods-detail-view.vue +++ b/pages_goods/_components/goods-detail-view/goods-detail-view.vue @@ -3,16 +3,21 @@ - + - + - - + + - + @@ -24,13 +29,16 @@ - + - {{ $lang('video') }} - {{ $lang('image') }} + {{ + $lang('video') }} + {{ $lang('image') }} @@ -40,19 +48,20 @@ - + - - - {{$lang('send')}} + + + {{ $lang('send') }} - {{$lang('express')}} - + {{ $lang('express') }} + @@ -63,7 +72,7 @@ - - + 服务 - + - - - + + + {{ item.service_name }} @@ -96,30 +111,32 @@ - + - - - - - - - - - + + + + + + + + + - + - + - - - {{goodsSkuDetail.merchinfo.merch_name}} - 官方认证商家,值得信赖! - + + + {{ goodsSkuDetail.merchinfo.merch_name }} + 官方认证商家,值得信赖! + @@ -136,7 +153,8 @@ - + {{ item.name }} @@ -158,11 +176,16 @@ - - - - - + + + + + {{ item.service_name }} @@ -187,17 +210,19 @@ - + - - + + {{ item.store_name }} - 距离{{ item.distance > 1 ? item.distance + 'km' : item.distance * 1000 + 'm' }} + 距离{{ item.distance > 1 ? item.distance + 'km' : item.distance * + 1000 + 'm' }} 营业时间:{{ item.open_date }} @@ -228,7 +253,7 @@ - - - + 展开 @@ -315,24 +350,25 @@ - + - {{$lang('details')}} + {{ $lang('details') }} - - - - + + + + - + @@ -419,25 +455,30 @@ \ No newline at end of file diff --git a/components/ns-goods-promotion/ns-goods-promotion.vue b/pages_goods/_components/ns-goods-promotion/ns-goods-promotion.vue similarity index 100% rename from components/ns-goods-promotion/ns-goods-promotion.vue rename to pages_goods/_components/ns-goods-promotion/ns-goods-promotion.vue diff --git a/components/pengpai-fadein-out/pengpai-fadein-out.vue b/pages_goods/_components/pengpai-fadein-out/pengpai-fadein-out.vue similarity index 100% rename from components/pengpai-fadein-out/pengpai-fadein-out.vue rename to pages_goods/_components/pengpai-fadein-out/pengpai-fadein-out.vue diff --git a/pages_goods/detail.vue b/pages_goods/detail.vue index 2328b31..5e7d1f1 100644 --- a/pages_goods/detail.vue +++ b/pages_goods/detail.vue @@ -519,12 +519,19 @@ - \ No newline at end of file diff --git a/pages_tool/form/formdata.vue b/pages_tool/form/formdata.vue index 7743cc8..db3deb7 100644 --- a/pages_tool/form/formdata.vue +++ b/pages_tool/form/formdata.vue @@ -20,7 +20,13 @@ \ No newline at end of file diff --git a/pages_tool/member/pay_password.vue b/pages_tool/member/pay_password.vue index 233fd4a..0511b4b 100644 --- a/pages_tool/member/pay_password.vue +++ b/pages_tool/member/pay_password.vue @@ -16,7 +16,11 @@ \ No newline at end of file From 47e1c2372df5aeb931146ee227f55d36cddc3400 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 16 Jan 2026 17:52:33 +0800 Subject: [PATCH 088/100] =?UTF-8?q?chore(sass):=20=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E5=99=A8=E4=BB=8Enode-sass=E8=BD=AC=E7=A7=BB=E6=88=90dart-sass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 2 +- common/css/diy.scss | 2 +- common/css/goods_detail.scss | 4 +- common/css/main.scss | 22 +- common/css/mp_html_patch.scss | 2 +- common/css/order_parment.scss | 4 +- common/js/config-external.js | 2 +- common/js/util.js | 2 +- components-diy/diy-bargain.vue | 4 +- components-diy/diy-category-item.vue | 6 +- components-diy/diy-category.vue | 4 +- components-diy/diy-channel-list.vue | 8 +- components-diy/diy-coupon.vue | 14 +- components-diy/diy-groupbuy.vue | 4 +- components-diy/diy-img-ads.vue | 14 +- components-diy/diy-index-page.vue | 2 +- components-diy/diy-member-info.vue | 8 +- components-diy/diy-pinfan.vue | 4 +- components-diy/diy-pintuan.vue | 4 +- components-diy/diy-presale.vue | 4 +- components-diy/diy-search.vue | 2 +- components-diy/diy-seckill.vue | 4 +- components-diy/diy-store-label.vue | 4 +- components/chat-message/chat-message.vue | 4 +- .../ns-goods-sku/ns-goods-sku-category.vue | 6 +- components/ns-mp-html/ns-mp-html.vue | 2 +- .../register-reward/register-reward.vue | 6 +- components/to-top/to-top.vue | 2 +- manifest.json | 2 +- node_modules/jweixin-module/README.md | 4 +- node_modules/jweixin-module/out/index.js | 1 - node_modules/jweixin-module/package.json | 80 ++-- package-lock.json | 377 ++++++++++++++++++ package.json | 3 +- pages/index/index.vue | 12 +- pages/index/public/css/index.scss | 2 +- pages_goods/cart.vue | 4 +- pages_goods/category.vue | 16 +- pages_goods/detail.vue | 30 +- pages_goods/list.vue | 2 +- pages_goods/public/css/cart.scss | 6 +- pages_order/detail.vue | 2 +- pages_order/detail_point.vue | 2 +- pages_order/list.vue | 4 +- pages_order/payment.vue | 6 +- pages_order/public/css/detail.scss | 2 +- pages_order/public/css/list.scss | 2 +- pages_promotion/fenxiao/apply.vue | 6 +- pages_promotion/fenxiao/bill.vue | 4 +- pages_promotion/fenxiao/goods_list.vue | 2 +- pages_promotion/fenxiao/index.vue | 6 +- pages_promotion/fenxiao/level.vue | 8 +- pages_promotion/fenxiao/public/css/order.scss | 4 +- pages_promotion/merch/detail.vue | 4 +- pages_promotion/merch/merchcategory.vue | 16 +- pages_promotion/merch/public/category.scss | 8 +- pages_promotion/point/detail.vue | 4 +- pages_promotion/point/order_list.vue | 4 +- pages_promotion/point/payment.vue | 6 +- .../ns-birthday-gift/ns-birthday-gift.vue | 6 +- .../_components/ns-new-gift/ns-new-gift.vue | 6 +- pages_tool/ai-chat/ai-chat-message.vue | 22 +- pages_tool/article/list.vue | 2 +- pages_tool/contact/contact.vue | 4 +- pages_tool/form/form.vue | 2 +- pages_tool/goods/brand.vue | 2 +- pages_tool/goods/search.vue | 4 +- pages_tool/index/diy.vue | 8 +- pages_tool/login/login.vue | 4 +- pages_tool/login/public/css/common.scss | 4 +- pages_tool/member/account.vue | 2 +- pages_tool/member/address.vue | 2 +- pages_tool/member/address_edit.vue | 2 +- pages_tool/member/card.vue | 2 +- pages_tool/member/collection.vue | 6 +- pages_tool/member/footprint.vue | 2 +- pages_tool/member/index.vue | 8 +- pages_tool/member/invite_friends.vue | 2 +- pages_tool/member/point_detail.vue | 2 +- pages_tool/notice/list.vue | 2 +- pages_tool/order/activist.vue | 2 +- pages_tool/order/logistics.vue | 4 +- pages_tool/order/refund.vue | 6 +- pages_tool/order/refund_batch.vue | 6 +- pages_tool/order/refund_type_select.vue | 6 +- pages_tool/pay/result.vue | 4 +- pages_tool/recharge/list.vue | 2 +- store/index.js | 4 +- uni.scss | 82 ++-- 89 files changed, 671 insertions(+), 314 deletions(-) delete mode 100644 node_modules/jweixin-module/out/index.js diff --git a/App.vue b/App.vue index 2625d3c..09f2e4e 100644 --- a/App.vue +++ b/App.vue @@ -314,7 +314,7 @@ @import './common/css/iconfont.css'; @import './common/css/icondiy.css'; // 自定义图标库 @import './common/css/icon/extend.css'; // 扩展图标库 - page{ + page { background: #f4f6fa; } \ No newline at end of file diff --git a/common/css/diy.scss b/common/css/diy.scss index e83aaee..943dbfd 100644 --- a/common/css/diy.scss +++ b/common/css/diy.scss @@ -123,7 +123,7 @@ image { } .choose-store { - /deep/ .uni-popup__wrapper{ + ::v-deep .uni-popup__wrapper{ background: none!important; } } diff --git a/common/css/goods_detail.scss b/common/css/goods_detail.scss index c1a25e1..f34c2bc 100644 --- a/common/css/goods_detail.scss +++ b/common/css/goods_detail.scss @@ -870,13 +870,13 @@ // 海报 // .uni-popup__wrapper-box .poster-layer { - /deep/ .uni-popup__wrapper.center { + ::v-deep .uni-popup__wrapper.center { width: 100vw!important; height: 100vh!important; background: none!important; } - /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: 100vw!important; max-height: 100vh!important; background: none!important; diff --git a/common/css/main.scss b/common/css/main.scss index a9cc722..b42a25b 100644 --- a/common/css/main.scss +++ b/common/css/main.scss @@ -559,7 +559,7 @@ scroll-view ::-webkit-scrollbar { background-color: transparent; } -/deep/::-webkit-scrollbar { +::v-deep ::-webkit-scrollbar { width: 0; height: 0; background-color: transparent; @@ -609,7 +609,7 @@ scroll-view ::-webkit-scrollbar { font-weight: 500!important; } -/deep/ .reward-popup .uni-popup__wrapper-box { +::v-deep .reward-popup .uni-popup__wrapper-box { background: none !important; max-width: unset !important; max-height: unset !important; @@ -618,7 +618,7 @@ scroll-view ::-webkit-scrollbar { // #ifdef H5 // 下拉加载动画【页面】 -/deep/ body uni-page-refresh div{ +::v-deep body uni-page-refresh div{ width: 14rpx !important; height: 14rpx !important; background-color: #ccc; @@ -626,7 +626,7 @@ scroll-view ::-webkit-scrollbar { clip: rect(-152rpx, 90rpx, 90rpx, -30rpx) !important; animation:.6s backgroundChange linear infinite; } -/deep/ body uni-page-refresh div::after{ +::v-deep body uni-page-refresh div::after{ content: ""; position: absolute; left: -22rpx; @@ -636,7 +636,7 @@ scroll-view ::-webkit-scrollbar { background-color: #ccc; animation:.5s backgroundChange linear infinite; } -/deep/ body uni-page-refresh div::before{ +::v-deep body uni-page-refresh div::before{ content: ""; position: absolute; right: -22rpx; @@ -646,15 +646,15 @@ scroll-view ::-webkit-scrollbar { background-color: #ccc; animation:.7s backgroundChange linear infinite; } -/deep/ body uni-page-refresh > div > div{ +::v-deep body uni-page-refresh > div > div{ display: none !important; } // 下拉加载动画【scroll-view】 -/deep/ body .uni-scroll-view-refresher{ +::v-deep body .uni-scroll-view-refresher{ background-color: transparent !important; } -/deep/ body .uni-scroll-view-refresher div{ +::v-deep body .uni-scroll-view-refresher div{ left: 50%; top: 50%; transform: translate(-50%, -50%); @@ -666,7 +666,7 @@ scroll-view ::-webkit-scrollbar { clip: rect(-152rpx, 90rpx, 90rpx, -30rpx) !important; animation:.6s backgroundChange linear infinite; } -/deep/ body .uni-scroll-view-refresher div::after{ +::v-deep body .uni-scroll-view-refresher div::after{ content: ""; position: absolute; left: -22rpx; @@ -676,7 +676,7 @@ scroll-view ::-webkit-scrollbar { background-color: #ccc; animation:.5s backgroundChange linear infinite; } -/deep/ body .uni-scroll-view-refresher div::before{ +::v-deep body .uni-scroll-view-refresher div::before{ content: ""; position: absolute; right: -22rpx; @@ -686,7 +686,7 @@ scroll-view ::-webkit-scrollbar { background-color: #ccc; animation:.7s backgroundChange linear infinite; } -/deep/ body .uni-scroll-view-refresher > div > div{ +::v-deep body .uni-scroll-view-refresher > div > div{ display: none !important; } @keyframes backgroundChange { diff --git a/common/css/mp_html_patch.scss b/common/css/mp_html_patch.scss index a3bbdbe..9679c0c 100644 --- a/common/css/mp_html_patch.scss +++ b/common/css/mp_html_patch.scss @@ -1,4 +1,4 @@ // 修复图片垂直对齐问题,解决两张图片上下有空白缝隙问题 -/deep/ ._img { +::v-deep ._img { vertical-align: top; } \ No newline at end of file diff --git a/common/css/order_parment.scss b/common/css/order_parment.scss index 0f1bc27..56d9b1b 100644 --- a/common/css/order_parment.scss +++ b/common/css/order_parment.scss @@ -377,7 +377,7 @@ view { } } -/deep/ .goods-form { +::v-deep .goods-form { display: flex; align-items: center; position: relative; @@ -1354,7 +1354,7 @@ view { border-bottom: 2rpx solid #F4F4F6; } - /deep/ .form-wrap { + ::v-deep .form-wrap { margin: 0 24rpx; .icon-right { diff --git a/common/js/config-external.js b/common/js/config-external.js index c1edd73..21735c7 100644 --- a/common/js/config-external.js +++ b/common/js/config-external.js @@ -116,7 +116,7 @@ class ConfigExternal { try { // 动态加载主题配置 const themeData = require(`@/common/js/style_color.js`)['default'][theme]; - console.log('async themeData => ', themeData); + // console.log('async themeData => ', themeData); this.loadedConfigs[`theme_${theme}`] = themeData; resolve(themeData); } catch (error) { diff --git a/common/js/util.js b/common/js/util.js index 8d9cb2f..21b3588 100644 --- a/common/js/util.js +++ b/common/js/util.js @@ -1202,7 +1202,7 @@ export default { let _isQuickApp = false; try { const ua = navigator?.userAgent?.toLowerCase(); - console.log('ua = ', ua); + // console.log('ua = ', ua); _isQuickApp = ua.indexOf('quickapp') !== -1; if (!_isQuickApp) { diff --git a/components-diy/diy-bargain.vue b/components-diy/diy-bargain.vue index 373dd8a..a264fee 100644 --- a/components-diy/diy-bargain.vue +++ b/components-diy/diy-bargain.vue @@ -365,7 +365,7 @@ export default { \ No newline at end of file diff --git a/components-diy/diy-index-page.vue b/components-diy/diy-index-page.vue index 5313aad..0f9ea3b 100644 --- a/components-diy/diy-index-page.vue +++ b/components-diy/diy-index-page.vue @@ -732,7 +732,7 @@ export default { margin-top: 100rpx; } -/deep/.uni-scroll-view::-webkit-scrollbar { +::v-deep .uni-scroll-view::-webkit-scrollbar { /* 隐藏滚动条,但依旧具备可以滚动的功能 */ display: none; } diff --git a/components-diy/diy-member-info.vue b/components-diy/diy-member-info.vue index fea7e80..c6b2e2e 100644 --- a/components-diy/diy-member-info.vue +++ b/components-diy/diy-member-info.vue @@ -1380,11 +1380,11 @@ export default { } } -/deep/ .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { +::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { background: none !important; } -/deep/ .member-info-style4 .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { +::v-deep .member-info-style4 .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box { background: #fff !important; } @@ -1484,8 +1484,8 @@ export default { } \ No newline at end of file diff --git a/components/ns-mp-html/ns-mp-html.vue b/components/ns-mp-html/ns-mp-html.vue index c60d07f..b6c1800 100644 --- a/components/ns-mp-html/ns-mp-html.vue +++ b/components/ns-mp-html/ns-mp-html.vue @@ -17,7 +17,7 @@ diff --git a/components/to-top/to-top.vue b/components/to-top/to-top.vue index 4d53471..3bc1c29 100644 --- a/components/to-top/to-top.vue +++ b/components/to-top/to-top.vue @@ -27,7 +27,7 @@ export default { \ No newline at end of file diff --git a/pages_goods/detail.vue b/pages_goods/detail.vue index 5e7d1f1..7db396b 100644 --- a/pages_goods/detail.vue +++ b/pages_goods/detail.vue @@ -547,18 +547,18 @@ export default { \ No newline at end of file diff --git a/pages_goods/public/css/cart.scss b/pages_goods/public/css/cart.scss index 514e550..ad662ed 100644 --- a/pages_goods/public/css/cart.scss +++ b/pages_goods/public/css/cart.scss @@ -177,18 +177,18 @@ } } - /deep/ .decrease { + ::v-deep .decrease { width: 52rpx; height: 52rpx; line-height: 48rpx; font-size: 40rpx; border-radius: 10rpx 0px 0px 10rpx; } - /deep/ input { + ::v-deep input { height: 52rpx; line-height: 52rpx; } - /deep/ .increase { + ::v-deep .increase { width: 52rpx; height: 52rpx; line-height: 48rpx; diff --git a/pages_order/detail.vue b/pages_order/detail.vue index 43043f3..cb41360 100644 --- a/pages_order/detail.vue +++ b/pages_order/detail.vue @@ -835,7 +835,7 @@ export default { margin-bottom: 30rpx; } -/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; } \ No newline at end of file diff --git a/pages_order/detail_point.vue b/pages_order/detail_point.vue index de67e82..38046e3 100644 --- a/pages_order/detail_point.vue +++ b/pages_order/detail_point.vue @@ -296,7 +296,7 @@ export default { @import './public/css/detail.scss'; \ No newline at end of file diff --git a/pages_order/list.vue b/pages_order/list.vue index 6587fe6..f7f7573 100644 --- a/pages_order/list.vue +++ b/pages_order/list.vue @@ -460,11 +460,11 @@ export default { \ No newline at end of file diff --git a/pages_order/payment.vue b/pages_order/payment.vue index 8e41120..d45e73e 100644 --- a/pages_order/payment.vue +++ b/pages_order/payment.vue @@ -95,15 +95,15 @@ export default { diff --git a/pages_order/public/css/detail.scss b/pages_order/public/css/detail.scss index a1b1c06..e16e277 100644 --- a/pages_order/public/css/detail.scss +++ b/pages_order/public/css/detail.scss @@ -104,7 +104,7 @@ view { } } -/deep/ #action-date .uni-countdown .uni-countdown__number { +::v-deep #action-date .uni-countdown .uni-countdown__number { border: none !important; padding: 0 !important; margin: 0 !important; diff --git a/pages_order/public/css/list.scss b/pages_order/public/css/list.scss index f90c89f..74b4d2f 100644 --- a/pages_order/public/css/list.scss +++ b/pages_order/public/css/list.scss @@ -353,7 +353,7 @@ $margin-both: 24rpx; } } -/deep/ #action-date .uni-countdown .uni-countdown__number { +::v-deep #action-date .uni-countdown .uni-countdown__number { border: none !important; padding: 0 !important; margin: 0 !important; diff --git a/pages_promotion/fenxiao/apply.vue b/pages_promotion/fenxiao/apply.vue index cf484cf..d9f1e03 100644 --- a/pages_promotion/fenxiao/apply.vue +++ b/pages_promotion/fenxiao/apply.vue @@ -421,11 +421,11 @@ width: 100%; } - /deep/.uni-scroll-view { + ::v-deep .uni-scroll-view { background-color: #fff; } - /deep/.uni-scroll-view::-webkit-scrollbar { + ::v-deep .uni-scroll-view::-webkit-scrollbar { /* 隐藏滚动条,但依旧具备可以滚动的功能 */ display: none; } @@ -802,7 +802,7 @@ \ No newline at end of file diff --git a/pages_promotion/fenxiao/bill.vue b/pages_promotion/fenxiao/bill.vue index 039f51c..f879171 100644 --- a/pages_promotion/fenxiao/bill.vue +++ b/pages_promotion/fenxiao/bill.vue @@ -84,11 +84,11 @@ export default { \ No newline at end of file diff --git a/pages_promotion/fenxiao/level.vue b/pages_promotion/fenxiao/level.vue index 1aeafd0..fdd8c21 100644 --- a/pages_promotion/fenxiao/level.vue +++ b/pages_promotion/fenxiao/level.vue @@ -299,21 +299,21 @@ @import './public/css/level.scss'; \ No newline at end of file diff --git a/pages_promotion/fenxiao/public/css/order.scss b/pages_promotion/fenxiao/public/css/order.scss index 6a786d9..da1eafd 100644 --- a/pages_promotion/fenxiao/public/css/order.scss +++ b/pages_promotion/fenxiao/public/css/order.scss @@ -1,8 +1,8 @@ -/deep/ .fixed { +::v-deep .fixed { position: relative; top: 0; } -/deep/ .empty { +::v-deep .empty { margin-top: 0 !important; } .cart-empty { diff --git a/pages_promotion/merch/detail.vue b/pages_promotion/merch/detail.vue index acbd9bb..9bd7c7b 100644 --- a/pages_promotion/merch/detail.vue +++ b/pages_promotion/merch/detail.vue @@ -308,7 +308,7 @@ - + @@ -333,7 +333,7 @@ export default { background-color: #f5f5f5 !important; } -/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; } \ No newline at end of file diff --git a/pages_promotion/merch/merchcategory.vue b/pages_promotion/merch/merchcategory.vue index 91180a8..4895914 100644 --- a/pages_promotion/merch/merchcategory.vue +++ b/pages_promotion/merch/merchcategory.vue @@ -228,38 +228,38 @@ \ No newline at end of file diff --git a/pages_promotion/merch/public/category.scss b/pages_promotion/merch/public/category.scss index c5168d0..e9d5897 100644 --- a/pages_promotion/merch/public/category.scss +++ b/pages_promotion/merch/public/category.scss @@ -261,7 +261,7 @@ z-index: 2; } - /deep/ .template-four { + ::v-deep .template-four { position: relative; z-index: 1; @@ -702,7 +702,7 @@ .right-wrap { display: flex; align-items: center; - justify-content: end; + justify-content: flex-end; .num { width: auto; @@ -856,7 +856,7 @@ } } - /deep/ .uni-popup__wrapper-box { + ::v-deep .uni-popup__wrapper-box { border-radius: 0; } @@ -890,7 +890,7 @@ justify-content: center; } - /deep/ .loading-layer { + ::v-deep .loading-layer { background: #fff !important; } diff --git a/pages_promotion/point/detail.vue b/pages_promotion/point/detail.vue index 592c3bb..e45b9ff 100644 --- a/pages_promotion/point/detail.vue +++ b/pages_promotion/point/detail.vue @@ -220,7 +220,7 @@ - + @@ -523,7 +523,7 @@ export default { } diff --git a/pages_promotion/point/order_list.vue b/pages_promotion/point/order_list.vue index 3260d45..1d6cf0c 100644 --- a/pages_promotion/point/order_list.vue +++ b/pages_promotion/point/order_list.vue @@ -252,12 +252,12 @@ export default { \ No newline at end of file diff --git a/pages_tool/_components/ns-birthday-gift/ns-birthday-gift.vue b/pages_tool/_components/ns-birthday-gift/ns-birthday-gift.vue index 254126c..34b10e4 100644 --- a/pages_tool/_components/ns-birthday-gift/ns-birthday-gift.vue +++ b/pages_tool/_components/ns-birthday-gift/ns-birthday-gift.vue @@ -161,7 +161,7 @@ }; \ No newline at end of file diff --git a/pages_tool/login/login.vue b/pages_tool/login/login.vue index a064dbe..49b63d1 100644 --- a/pages_tool/login/login.vue +++ b/pages_tool/login/login.vue @@ -481,14 +481,14 @@ export default { \ No newline at end of file diff --git a/pages_tool/login/public/css/common.scss b/pages_tool/login/public/css/common.scss index 3977688..d249c83 100644 --- a/pages_tool/login/public/css/common.scss +++ b/pages_tool/login/public/css/common.scss @@ -1,8 +1,8 @@ -/deep/.uni-scroll-view { +::v-deep .uni-scroll-view { background-color: #fff; } -/deep/.uni-scroll-view::-webkit-scrollbar { +::v-deep .uni-scroll-view::-webkit-scrollbar { /* 隐藏滚动条,但依旧具备可以滚动的功能 */ display: none; } diff --git a/pages_tool/member/account.vue b/pages_tool/member/account.vue index 6b7f43f..bbcb0f1 100644 --- a/pages_tool/member/account.vue +++ b/pages_tool/member/account.vue @@ -430,7 +430,7 @@ export default { } diff --git a/pages_tool/member/address.vue b/pages_tool/member/address.vue index aad8d7a..8003f5c 100644 --- a/pages_tool/member/address.vue +++ b/pages_tool/member/address.vue @@ -367,7 +367,7 @@ export default { \ No newline at end of file diff --git a/pages_tool/member/collection.vue b/pages_tool/member/collection.vue index a523c69..83e7718 100644 --- a/pages_tool/member/collection.vue +++ b/pages_tool/member/collection.vue @@ -73,12 +73,12 @@ export default { \ No newline at end of file diff --git a/pages_tool/member/footprint.vue b/pages_tool/member/footprint.vue index 27e7361..dc6f1c5 100644 --- a/pages_tool/member/footprint.vue +++ b/pages_tool/member/footprint.vue @@ -249,7 +249,7 @@ export default { diff --git a/pages_tool/member/point_detail.vue b/pages_tool/member/point_detail.vue index 4b1ca5c..7087667 100644 --- a/pages_tool/member/point_detail.vue +++ b/pages_tool/member/point_detail.vue @@ -232,7 +232,7 @@ export default { diff --git a/pages_tool/order/refund_batch.vue b/pages_tool/order/refund_batch.vue index bff69c4..5fe9db6 100644 --- a/pages_tool/order/refund_batch.vue +++ b/pages_tool/order/refund_batch.vue @@ -289,15 +289,15 @@ export default { @import './public/css/refund.scss'; \ No newline at end of file diff --git a/pages_tool/recharge/list.vue b/pages_tool/recharge/list.vue index 385c3db..1edd6c6 100644 --- a/pages_tool/recharge/list.vue +++ b/pages_tool/recharge/list.vue @@ -300,7 +300,7 @@ box-sizing: border-box; } - /deep/ .mescroll-uni-fixed { + ::v-deep .mescroll-uni-fixed { bottom: 280rpx !important; } .recharge-title{ diff --git a/store/index.js b/store/index.js index 4339ebd..f37eed9 100644 --- a/store/index.js +++ b/store/index.js @@ -412,7 +412,7 @@ const store = new Vuex.Store({ }, // 生成主题颜色CSS变量 themeColorSet() { - console.log('样式颜色设置...'); + // console.log('样式颜色设置...'); let theme = this.state.themeStyle; if (!theme?.main_color || !theme?.aux_color) return; try { @@ -436,7 +436,7 @@ const store = new Vuex.Store({ } catch (e) { console.error('设置主题颜色失败', e); } - console.log('themeColor => ', this.state.themeColor); + // console.log('themeColor => ', this.state.themeColor); } } }) diff --git a/uni.scss b/uni.scss index bd9cf10..d0bc610 100644 --- a/uni.scss +++ b/uni.scss @@ -1,32 +1,50 @@ -/** - * 你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 - * 建议使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App - */ - -//主色调,红色:#FF0036,绿色 #4CAF50,蓝色:#03A9F4,黄色:#FF9800,粉色:#FF547B,棕色:#C3A769,浅绿色:#65C4AA,黑色:#333333,紫色:#B323B4,淡粉色:#FF8B8B - -// 颜色 -$color-title: #303133; // 主标题 -$color-sub: #606266; // 副标题 -$color-tip: #909399; // 辅助提示色 -$color-bg: #f8f8f8; // 背景色 -$color-line: #eeeeee; //分割线 -$color-disabled: #cccccc; // 禁用色 - -// 文字 -$font-size-base: 28rpx; // 14px,正文文字 -$font-size-toolbar: 32rpx; // 16px,用于导航栏、标题 -$font-size-sub: 26rpx; // 13px,副标题 -$font-size-tag: 24rpx; // 12px,辅助性文字/大标签 -$font-size-goods-tag: 22rpx; // 11px,商品列表角标 -$font-size-activity-tag: 20rpx; // 10px,活动角标(拼团等角标)/小标签文字 - -$margin-both: 30rpx; //外边距 左右 -$margin-updown: 20rpx; // 外边距 上下 -$border-radius: 10rpx; //圆角 -$padding: 20rpx; //内边距 - -$base-color: var(--base-color); // 主色调 -$base-help-color: var(--base-help-color); //辅助颜色 - -$goods-price-color: var(--goods-price-color); +/** + * 你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * 建议使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + */ + +//主色调,红色:#FF0036,绿色 #4CAF50,蓝色:#03A9F4,黄色:#FF9800,粉色:#FF547B,棕色:#C3A769,浅绿色:#65C4AA,黑色:#333333,紫色:#B323B4,淡粉色:#FF8B8B + +// 颜色 +$color-title: #303133; // 主标题 +$color-sub: #606266; // 副标题 +$color-tip: #909399; // 辅助提示色 +$color-bg: #f8f8f8; // 背景色 +$color-line: #eeeeee; //分割线 +$color-disabled: #cccccc; // 禁用色 + +// 文字 +$font-size-base: 28rpx; // 14px,正文文字 +$font-size-toolbar: 32rpx; // 16px,用于导航栏、标题 +$font-size-sub: 26rpx; // 13px,副标题 +$font-size-tag: 24rpx; // 12px,辅助性文字/大标签 +$font-size-goods-tag: 22rpx; // 11px,商品列表角标 +$font-size-activity-tag: 20rpx; // 10px,活动角标(拼团等角标)/小标签文字 + +$margin-both: 30rpx; //外边距 左右 +$margin-updown: 20rpx; // 外边距 上下 +$border-radius: 10rpx; //圆角 +$padding: 20rpx; //内边距 + +$base-color: var(--base-color); // 主色调 +$base-help-color: var(--base-help-color); //辅助颜色 + +$goods-price-color: var(--goods-price-color); + +// uni 兼容变量 +$uni-font-size-sm: $font-size-tag; // 12px,小字体 +$uni-font-size-base: $font-size-base; // 14px,正文文字 +$uni-font-size-lg: $font-size-toolbar; // 16px,大字体 +$uni-bg-color: $color-bg; // 背景色 +$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩背景色 +$uni-border-color: $color-line; // 边框色 +$uni-text-color: $color-title; // 主文字色 +$uni-text-color-grey: $color-tip; // 灰色文字 +$uni-text-color-placeholder: $color-disabled; // 占位文字色 +$uni-bg-color-hover: $color-line; // hover 背景色 +$uni-color-primary: $base-color; // 主色调 +$uni-color-error: #ff4d4f; // 错误色 +$uni-color-success: #52c41a; // 成功色 +$uni-color-warning: #faad14; // 警告色 +$uni-spacing-row-base: $margin-updown; // 行间距 +$uni-opacity-disabled: 0.6; // 禁用透明度 \ No newline at end of file From 6144dc72b8f8a52cc33a716716b87e1e11533777 Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Fri, 16 Jan 2026 18:00:03 +0800 Subject: [PATCH 089/100] =?UTF-8?q?chore:=E9=9D=9E=20h5=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=20:key=20=E4=B8=8D=E6=94=AF=E6=8C=81=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages_tool/ai-chat/ai-chat-message.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index edcf3a8..4978f1b 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -10,8 +10,7 @@ - + @@ -392,6 +391,19 @@ export default { isFetchingHistory: false } }, + computed: { + // 为每条消息生成兼容小程序的唯一 key + messagesWithKey() { + return this.messages.map((msg, idx) => { + const uniqueId = msg.id || msg.timestamp || idx; + return { + ...msg, + __renderKey: `msg_${uniqueId}_${idx}`, + __index: idx // 保留原始索引,用于判断 first-message 等 + }; + }); + } + }, onShow() { // 优先读取本地缓存的会话 ID const localConvId = this.getConversationIdFromLocal(); From 2a36fa2b609d7c94bbee5f31807adecc51e3188c Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 16 Jan 2026 18:07:59 +0800 Subject: [PATCH 090/100] =?UTF-8?q?chore(build):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=B0=8F=E7=A8=8B=E5=BA=8FAppID=E5=8F=8Asite?= =?UTF-8?q?.js=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 4 ++-- site.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 565f7b6..6a72520 100644 --- a/manifest.json +++ b/manifest.json @@ -58,7 +58,7 @@ "quickapp" : {}, /* 小程序特有相关 */ "mp-weixin" : { - "appid" : "wxa8f94045d9c2fc10", + "appid" : "wx5e649e2286b29bdf", "setting" : { "urlCheck" : false, "postcss" : false, @@ -66,7 +66,7 @@ "minified" : true }, "usingComponents" : true, - "lazyCodeLoading": "requiredComponents", + "lazyCodeLoading" : "requiredComponents", "permission" : { "scope.userLocation" : { "desc" : "为了更好地为您提供服务" diff --git a/site.js b/site.js index dbbeb00..deb49f2 100644 --- a/site.js +++ b/site.js @@ -1,4 +1,4 @@ module.exports = { - baseUrl: "https://xcx30.5g-quickapp.com/",//修改域名 - uniacid: 460,//后台对应uniacid + baseUrl: "https://xcx6.aigc-quickapp.com/",//修改域名 + uniacid: 2811,//后台对应uniacid }; \ No newline at end of file From 1ebb94e9e2d9f80a05b64dc244e5ee506df72ee3 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 17 Jan 2026 13:52:19 +0800 Subject: [PATCH 091/100] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E9=9A=90?= =?UTF-8?q?=E7=A7=81=E5=8D=8F=E8=AE=AE=E5=8F=8A=E6=B3=A8=E5=86=8C=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=B1=95=E7=A4=BA=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages_tool/agreement/contenr.vue | 199 ++++++++++++++++++++++++++----- 1 file changed, 167 insertions(+), 32 deletions(-) diff --git a/pages_tool/agreement/contenr.vue b/pages_tool/agreement/contenr.vue index 0477a08..4d0f37f 100644 --- a/pages_tool/agreement/contenr.vue +++ b/pages_tool/agreement/contenr.vue @@ -1,52 +1,187 @@ - \ No newline at end of file From 7ae7a1d3bdf4cb9692b269dd15c5fc5ff392b763 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 17 Jan 2026 15:30:02 +0800 Subject: [PATCH 092/100] =?UTF-8?q?chore(uniapp):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8B=B7=E8=B4=9D=E6=96=87=E6=9C=AC=E8=B6=85=E6=97=B6=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E6=8F=90=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .local.config.js | 4 ++ common/js/uniapp.utils.js | 135 +++++++++++++++++++++++++++----------- 2 files changed, 101 insertions(+), 38 deletions(-) diff --git a/.local.config.js b/.local.config.js index 28cf028..6386cb0 100644 --- a/.local.config.js +++ b/.local.config.js @@ -38,6 +38,10 @@ const localDevConfig = ({ uniacid: 1, domain: 'https://test.aigc-quickapp.com', }, + 'local-2': { // 测试平台 + uniacid: 2, + domain: 'http://localhost:8050/', + }, })['2811']; // 选择要使用的环境配置 export default localDevConfig; \ No newline at end of file diff --git a/common/js/uniapp.utils.js b/common/js/uniapp.utils.js index e450d28..2034bd9 100644 --- a/common/js/uniapp.utils.js +++ b/common/js/uniapp.utils.js @@ -6,13 +6,23 @@ /** * 显示错误信息 * @param {Exception} err + * @param {Boolean} useModal */ -const showError = (err) => { - uni.showToast({ - title: err?.message || err?.errMsg || err?.toString(), - icon: 'none', - duration: 2000 - }); +const showError = (err, useModal = false) => { + const content = err?.message || err?.errMsg || err?.toString(); + if (!useModal) { + uni.showToast({ + title: content, + icon: 'none', + duration: 3000 + }); + } else { + uni.showModal({ + title: '错误提示', + content, + showCancel: false, + }) + } } /** @@ -33,43 +43,92 @@ export const makePhoneCall = (mobile) => { } /** - * 拷贝文本 + * 拷贝文本(返回 Promise) * @param {*} text * @param {*} options + * @returns {Promise} 返回 Promise,成功时 resolve,失败时 reject */ -export const copyText = (text, { copySuccess = '', copyFailed = '' } = {}) => { - try { - console.log('copyText'); - uni.setClipboardData({ - data: `${text}`, - success: () => { - console.error('复制成功'); - try { - uni.showToast({ - title: copySuccess, - icon: 'success', - duration: 2000 - }); - } catch (e) { - showError(e); +export const copyTextAsync = (text, { copySuccess = '', copyFailed = '' } = {}) => { + return new Promise((resolve, reject) => { + // 输入验证 + if (!text && text !== '') { + const error = new Error('复制文本不能为空'); + showError(error); + reject(error); + return; + } + + // 超时监测 + const timeoutId = setTimeout(() => { + let error = new Error('复制操作长时间无响应,请检查相关权限及配置是否正确'); + // #ifdef MP-WEIXIN + error = new Error([ + '复制操作长时间无响应!', + '原因:', + '1.微信平台->用户隐私保护指引->"剪贴板"功能未添加或审核未通过;', + '2.微信平台对剪贴板API调用频率有限制' + ].join('\r\n')); + // #endif + showError(error, true); + reject(error); + }, 5000); + + try { + uni.setClipboardData({ + data: `${text}`, + success: (res) => { + clearTimeout(timeoutId); + + try { + if (copySuccess) { + uni.showToast({ + title: copySuccess, + icon: 'success', + duration: 2000 + }); + } + } catch (e) { + showError(e); + } + + resolve(res); + }, + fail: (err) => { + clearTimeout(timeoutId); + try { + uni.showToast({ + title: err.message || err.errMsg || copyFailed || '复制失败', + icon: 'none', + duration: 2000 + }); + } catch (e) { + showError(e); + } + reject(err); } - }, - fail: (err) => { - console.error('复制失败:', err); - try { - uni.showToast({ - title: err.message || err.errMsg || copyFailed, - icon: 'none', - duration: 2000 - }); - } catch (e) { - showError(e); - } - } + }); + } catch (err) { + clearTimeout(timeoutId); + showError(err); + reject(err); + } + }); +} + +/** + * 拷贝文本(回调形式,兼容旧代码) + * @param {*} text + * @param {*} options + * @param {Function} callback 回调函数,接收 (success, error) 参数 + */ +export const copyText = (text, options = {}, callback) => { + copyTextAsync(text, options) + .then(res => { + if (callback) callback(true, null); + }) + .catch(err => { + if (callback) callback(false, err); }); - } catch (err) { - showError(err); - } } /** From 153c84266a3f27a7c72b5c4cb94aa0c7c17114a0 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 09:25:58 +0800 Subject: [PATCH 093/100] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=92=8C=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将项目名称从'lucky_shop'改为'mp-weixin' - 在project.config.json中添加include数组和多个新配置项 - 启用enhance功能并添加编译相关设置 --- project.config.json | 15 ++++++++++++--- project.private.config.json | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/project.config.json b/project.config.json index 7126295..776d6c4 100644 --- a/project.config.json +++ b/project.config.json @@ -1,12 +1,13 @@ { "description": "项目配置文件", "packOptions": { - "ignore": [] + "ignore": [], + "include": [] }, "setting": { "urlCheck": true, "es6": true, - "enhance": false, + "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": true, @@ -37,7 +38,15 @@ "userConfirmedBundleSwitch": false, "packNpmManually": false, "packNpmRelationList": [], - "minifyWXSS": true + "minifyWXSS": true, + "condition": true, + "swc": false, + "disableSWC": true, + "minifyWXML": true, + "compileWorklet": true, + "localPlugins": false, + "disableUseStrict": false, + "useCompilerPlugins": false }, "compileType": "miniprogram", "libVersion": "2.16.1", diff --git a/project.private.config.json b/project.private.config.json index cd9faf2..ba3edb6 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -1,6 +1,6 @@ { "libVersion": "3.12.0", - "projectname": "lucky_shop", + "projectname": "mp-weixin", "condition": {}, "setting": { "urlCheck": true, From e8ccb87266ff575c6baff59ceb6da2cf0bad948d Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 10:12:30 +0800 Subject: [PATCH 094/100] =?UTF-8?q?feat(=E8=84=9A=E6=9C=AC):=20=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=BE=AE=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E8=A1=A5?= =?UTF-8?q?=E4=B8=81=E8=84=9A=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加新的 npm scripts 支持不同模式运行补丁脚本 - 支持命令行参数 --no-zip 和 --mode 控制 ZIP 文件生成和运行模式 - 自动复制项目配置文件到目标目录 - 更新使用说明文档 --- package.json | 5 +++- scripts/mp-weixin.patch.js | 60 +++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 3c45a8f..c82e734 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { "scripts": { - "mp-weixin": "node scripts/mp-weixin.patch.js" + "mp-weixin": "node scripts/mp-weixin.patch.js", + "mp-weixin:patch": "node scripts/mp-weixin.patch.js --no-zip", + "mp-weixin:dev": "node scripts/mp-weixin.patch.js --mode development", + "mp-weixin:dev:patch": "node scripts/mp-weixin.patch.js --mode development --no-zip" }, "devDependencies": { "dart-sass": "^1.25.0", diff --git a/scripts/mp-weixin.patch.js b/scripts/mp-weixin.patch.js index c3c0de9..43f5032 100644 --- a/scripts/mp-weixin.patch.js +++ b/scripts/mp-weixin.patch.js @@ -9,7 +9,10 @@ * 如果这个文件开头已经有了这行代码,则不追加 * * 使用: - * node fix-wechat-miniapp.js + * node fix-wechat-miniapp.js # 打补丁并创建 ZIP 文件(默认 mode=production) + * node fix-wechat-miniapp.js --no-zip # 只打补丁,不创建 ZIP 文件 + * node fix-wechat-miniapp.js --mode development # 使用 development 模式打补丁 + * node fix-wechat-miniapp.js --mode production # 使用 production 模式打补丁(默认) * * 注意: * - 在 Windows 上路径使用反斜杠也是可以的;脚本使用 path.join 来兼容不同平台。 @@ -29,8 +32,6 @@ async function commonPatch(mode = 'production') { // 根据当前脚本所在目录(scripts),定位到项目根目录 const cwd = path.join(__dirname, '..'); - - const srcSitePath = path.join(cwd, 'site.js'); const destDir = path.join(cwd, 'unpackage', 'dist', mode === 'production' ? 'build' : 'dev', 'mp-weixin'); const destSitePath = path.join(destDir, 'site.js'); @@ -47,6 +48,22 @@ async function commonPatch(mode = 'production') { // 确保目标目录存在 await ensureDir(destDir); + // 复制 project.config.json 及 project.private.config.json 文件到 destDir 下面 + const configFiles = ['project.config.json', 'project.private.config.json']; + for (const fileName of configFiles) { + const srcPath = path.join(cwd, fileName); + const destPath = path.join(destDir, fileName); + + // 检查源文件是否存在 + const fileExists = await exists(srcPath); + if (fileExists) { + await fsp.copyFile(srcPath, destPath); + console.log(`已拷贝: ${srcPath} -> ${destPath}`); + } else { + console.warn(`源文件不存在,跳过复制: ${srcPath}`); + } + } + // 复制 site.js 到目标目录(覆盖) await fsp.copyFile(srcSitePath, destSitePath); console.log(`已拷贝: ${srcSitePath} -> ${destSitePath}`); @@ -96,22 +113,37 @@ async function commonPatch(mode = 'production') { } } - - async function main() { + // 解析命令行参数 + const argv = process.argv.slice(2); + const options = { + noZip: argv.includes('--no-zip'), + mode: 'production' // 默认值 + }; + + // 解析 --mode 参数 + const modeIndex = argv.indexOf('--mode'); + if (modeIndex !== -1 && modeIndex + 1 < argv.length) { + options.mode = argv[modeIndex + 1]; + } + // 1) 打补丁 - await commonPatch('production'); + await commonPatch(options.mode); // await commonPatch('development'); - // 2) 创建 ZIP 文件 - const cwd = path.join(__dirname, '..'); - const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin'); - const destDir = path.join(cwd, 'unpackage', 'dist', 'build'); - const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir); - console.log(`ZIP 文件路径: ${zipFilePath}`); + // 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 zipFilePath = await createZipWithSystemCommand(sourceDir, destDir); + console.log(`ZIP 文件路径: ${zipFilePath}`); - // 3) 自动打开zip所在的目录 - await openFileDirectory(zipFilePath); + // 3) 自动打开zip所在的目录 + await openFileDirectory(zipFilePath); + } else { + console.log('跳过创建 ZIP 文件和打开目录'); + } } async function exists(p) { From aa9d2e64d237c218539a24318bea8a49e689ccaa Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 11:18:55 +0800 Subject: [PATCH 095/100] =?UTF-8?q?feat(util):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E5=BE=AE=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 hideHomeButton 方法用于在微信小程序环境中隐藏首页按钮,提升用户体验 --- common/js/util.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/js/util.js b/common/js/util.js index 21b3588..59114f2 100644 --- a/common/js/util.js +++ b/common/js/util.js @@ -843,6 +843,15 @@ export default { uni.navigateBack(); } }, + /** + * 隐藏“返回首页/小房子”按钮 + * 这个函数,用到页面show, onshow 的生命周期时 + */ + hideHomeButton() { + // #ifdef MP-WEIXIN + wx.hideHomeButton(); + // #endif + }, /** * * @param val 转化时间字符串 (转化时分秒) From ceca4e5956b954ee5dd39eb90a58341b0e6138e1 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 11:36:15 +0800 Subject: [PATCH 096/100] =?UTF-8?q?fix:=20=E5=9C=A8=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=B7=BB=E5=8A=A0=E9=9A=90=E8=97=8F=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为统一处理首页按钮显示逻辑,在会员页、联系页和商品分类页的onShow生命周期中添加了hideHomeButton调用 --- pages_goods/category.vue | 1 + pages_tool/contact/contact.vue | 3 +++ pages_tool/member/index.vue | 3 +++ 3 files changed, 7 insertions(+) diff --git a/pages_goods/category.vue b/pages_goods/category.vue index 9edb1b9..a863f67 100644 --- a/pages_goods/category.vue +++ b/pages_goods/category.vue @@ -38,6 +38,7 @@ export default { }, onShow() { if (this.$refs.category) this.$refs.category[0].pageShow(); + this.$util.hideHomeButton(); }, onUnload() { if (!this.storeToken && this.$refs.login) this.$refs.login.cancelCompleteInfo(); diff --git a/pages_tool/contact/contact.vue b/pages_tool/contact/contact.vue index 08ece5a..bda2223 100644 --- a/pages_tool/contact/contact.vue +++ b/pages_tool/contact/contact.vue @@ -301,6 +301,9 @@ export default { fail: res => { } }); }, + onShow() { + this.$util.hideHomeButton(); + }, methods: { // 分享文件 shareFile(file) { diff --git a/pages_tool/member/index.vue b/pages_tool/member/index.vue index 499ee06..2259642 100644 --- a/pages_tool/member/index.vue +++ b/pages_tool/member/index.vue @@ -86,6 +86,9 @@ export default { nsNewGift }, mixins: [diyJs, indexJs], + onShow() { + this.$util.hideHomeButton(); + }, methods: { tourl(url) { From 29b5cfda6fe8c95c821215a85a2cca8e6ff3f4d9 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 11:54:31 +0800 Subject: [PATCH 097/100] =?UTF-8?q?fix:=20=E5=9C=A8=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=97=B6=E7=BB=9F=E4=B8=80=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在多个页面的onLoad生命周期中添加this.$util.hideHomeButton()调用,确保页面加载时即隐藏首页按钮,避免显示不一致问题。同时调整category.vue中onShow的逻辑顺序。 --- pages_goods/category.vue | 5 ++--- pages_tool/contact/contact.vue | 1 + pages_tool/member/index.vue | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pages_goods/category.vue b/pages_goods/category.vue index a863f67..be21c82 100644 --- a/pages_goods/category.vue +++ b/pages_goods/category.vue @@ -30,15 +30,14 @@ export default { }; }, onLoad() { + this.$util.hideHomeButton(); //刷新多语言 this.$langConfig.refresh(); - - uni.hideTabBar(); this.getDiyInfo(); }, onShow() { - if (this.$refs.category) this.$refs.category[0].pageShow(); this.$util.hideHomeButton(); + if (this.$refs.category) this.$refs.category[0].pageShow(); }, onUnload() { if (!this.storeToken && this.$refs.login) this.$refs.login.cancelCompleteInfo(); diff --git a/pages_tool/contact/contact.vue b/pages_tool/contact/contact.vue index bda2223..9b97a97 100644 --- a/pages_tool/contact/contact.vue +++ b/pages_tool/contact/contact.vue @@ -252,6 +252,7 @@ export default { }; }, onLoad(option) { + this.$util.hideHomeButton(); this.$langConfig.refresh(); this.$api.sendRequest({ url: '/api/member/personnel', diff --git a/pages_tool/member/index.vue b/pages_tool/member/index.vue index 2259642..27081c1 100644 --- a/pages_tool/member/index.vue +++ b/pages_tool/member/index.vue @@ -86,6 +86,9 @@ export default { nsNewGift }, mixins: [diyJs, indexJs], + onLoad() { + this.$util.hideHomeButton(); + }, onShow() { this.$util.hideHomeButton(); }, From 0939449aa79306695ecd7423a5192420297a0733 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 23 Jan 2026 17:07:38 +0800 Subject: [PATCH 098/100] =?UTF-8?q?fix(ns-login):=20=E4=BF=AE=E5=A4=8Dcomp?= =?UTF-8?q?lete-info-wrap=E5=BC=B9=E7=AA=97=E5=B1=82=E7=BA=A7=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ns-login/ns-login.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ns-login/ns-login.vue b/components/ns-login/ns-login.vue index f14421e..7b750d5 100644 --- a/components/ns-login/ns-login.vue +++ b/components/ns-login/ns-login.vue @@ -760,6 +760,7 @@ export default {