Files
lucky_shop/common/js/customer-service.js

685 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 客服统一处理服务
* 整合各种客服方式,提供统一的调用接口
*/
import IMAGE_CONFIG from './image-config.js';
export class CustomerService {
constructor(vueInstance, externalConfig = null) {
this.vm = vueInstance;
this.externalConfig = externalConfig; // 外部传入的最新配置(优先级最高)
this.latestPlatformConfig = null;
}
/**
* 强制刷新配置(支持传入外部配置)
* @param {Object} externalConfig 外部最新配置
*/
refreshConfig(externalConfig = null) {
this.externalConfig = externalConfig || this.externalConfig;
this.latestPlatformConfig = null;
return this.getPlatformConfig();
}
/**
* 获取平台配置
* @returns {Object} 平台对应的客服配置
*/
getPlatformConfig() {
if (this.latestPlatformConfig) {
return this.latestPlatformConfig;
}
// 优先级:外部传入 > vuex store > 空对象
const servicerConfig = this.externalConfig || this.vm.$store.state.servicerConfig || {};
let platformConfig = null;
// #ifdef H5
platformConfig = servicerConfig.h5 && typeof servicerConfig.h5 === 'object' ? servicerConfig.h5 : null;
// #endif
// #ifdef MP-WEIXIN
platformConfig = servicerConfig.weapp && typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null;
// #endif
// #ifdef MP-ALIPAY
platformConfig = servicerConfig.aliapp && typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null;
// #endif
// #ifdef PC
platformConfig = servicerConfig.pc && typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null;
// #endif
// 防止空数组被当作有效配置
if (Array.isArray(platformConfig)) {
platformConfig = null;
}
this.latestPlatformConfig = platformConfig;
return platformConfig;
}
/**
* 获取企业微信配置
* @returns {Object} 企业微信配置
*/
getWxworkConfig() {
return this.vm.$store.state.wxworkConfig || {};
}
/**
* 检查客服配置是否可用
* @returns {boolean} 是否有可用配置
*/
isConfigAvailable() {
const config = this.getPlatformConfig();
return config && typeof config === 'object' && config.type;
}
/**
* 检查是否有有效的客服配置(用于判断是否显示客服按钮)
* @returns {Boolean}
*/
hasValidKefuConfig() {
const config = this.getPlatformConfig();
return config && typeof config === 'object' && config.type && config.type !== 'none';
}
/**
* 验证客服配置完整性
* @returns {Object} 验证结果
*/
validateConfig() {
const config = this.getPlatformConfig();
const wxworkConfig = this.getWxworkConfig();
const result = {
isValid: true,
errors: [],
warnings: []
};
if (!config || !config.type) {
result.isValid = false;
result.errors.push('客服类型未配置');
return result;
}
if (config.type === 'wxwork') {
if (!wxworkConfig || !wxworkConfig.enable) {
result.warnings.push('企业微信未启用');
}
if (!wxworkConfig.contact_url) {
result.warnings.push('企业微信活码链接未配置');
}
}
return result;
}
/**
* 跳转到 AI智能客服页面(Dify)
*/
openDifyService() {
try {
// 清除未读数(如果存在)
if (typeof this.vm.setAiUnreadCount === 'function') {
this.vm.setAiUnreadCount(0);
}
// ✅ 修正路径:必须与 pages.json 中注册的路径一致
const aiChatUrl = '/pages_tool/ai-chat/index';
// ✅ 使用 navigateTo 保留返回栈(体验更好)
uni.navigateTo({
url: aiChatUrl,
fail: (err) => {
console.error('跳转 AI智能客服失败:', err);
// H5 兜底
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
/**
* 处理客服点击事件(统一入口)
* @param {Object} options 选项参数(用于消息卡片等)
*/
handleCustomerClick(options = {}) {
const validation = this.validateConfig();
if (!validation.isValid) {
console.error('客服配置验证失败:', validation.errors);
this.showConfigErrorPopup(validation.errors);
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const config = this.getPlatformConfig();
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
if (config.type === 'none') {
this.showNoServicePopup();
return;
}
// 核心路由:根据 type 决定行为
switch (config.type) {
case 'aikefu':
this.openDifyService();
break;
case 'wxwork':
this.openWxworkService(false, config, options);
break;
case 'third':
this.openThirdService(config);
break;
case 'miniprogram':
this.openThirdService(config);
break;
case 'niushop':
this.openNiushopService(niushop);
break;
case 'weapp':
this.openWeappService(config, options);
break;
case 'aliapp':
this.openAliappService(config);
break;
default:
console.error('【未知客服类型】', config.type);
this.makePhoneCall();
}
}
// ================== 各类型客服实现 ==================
openWxworkService(useOriginalService = false, servicerConfig = null, options = {}) {
const config = servicerConfig || this.getPlatformConfig();
const wxworkConfig = this.getWxworkConfig();
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
// #ifdef MP-WEIXIN
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
wx.navigateToMiniProgram({
appId: 'wxeb490c6f9b154ef9',
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig.contact_url)}`,
success: () => {},
fail: (err) => {
console.error('跳转企业微信失败:', err);
this.fallbackToOriginalService();
}
});
} else {
// 检查是否有企业微信配置
if (!config.wxwork_url && !config.corpid) {
console.error('企业微信配置不完整,缺少 wxwork_url 或 corpid');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
this.fallbackToPhoneCall();
return;
}
wx.openCustomerServiceChat({
extInfo: { url: config.wxwork_url || '' },
corpId: config.corpid || '',
showMessageCard: true,
sendMessageTitle,
sendMessagePath,
sendMessageImg
});
}
// #endif
// #ifdef H5
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
window.location.href = wxworkConfig.contact_url;
} else if (config.wxwork_url) {
window.location.href = config.wxwork_url;
} else {
this.fallbackToPhoneCall();
}
// #endif
}
openThirdService(config) {
// 支持多种可能的字段名
const miniAppId = config.mini_app_id || config.miniAppId || config.appid || config.appId || config.app_id;
const miniAppPath = config.mini_app_path || config.miniAppPath || config.path || config.page_path || '';
// 优先处理第三方微信小程序客服
if (miniAppId) {
// #ifdef MP-WEIXIN
wx.navigateToMiniProgram({
appId: miniAppId,
path: miniAppPath,
success: () => {
},
fail: (err) => {
console.error('【跳转第三方小程序失败】', err);
uni.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
}
});
// #endif
// #ifdef H5
uni.showToast({ title: '第三方小程序客服仅在微信小程序中可用', icon: 'none' });
// #endif
return;
}
// 处理第三方链接客服
if (config.third_url) {
// #ifdef H5
window.location.href = config.third_url;
// #endif
// #ifdef MP-WEIXIN
uni.setClipboardData({
data: config.third_url,
success: () => {
uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' });
}
});
// #endif
} else {
console.error('【第三方客服配置不完整】缺少 mini_app_id 或 third_url');
this.fallbackToPhoneCall();
}
}
openNiushopService(niushop) {
if (Object.keys(niushop).length > 0 && this.vm.$util?.redirectTo) {
this.vm.$util.redirectTo('/pages_tool/chat/room', niushop);
} else {
this.makePhoneCall();
}
}
openWeappService(config, options = {}) {
// 如果 useOfficial 为 true 或 undefined,则使用原生系统客服(由 button open-type="contact" 触发)
// 此方法仅用于自定义跳转(如 useOfficial: false)
if (config.useOfficial !== false) {
// 不做任何事,应由 <button open-type="contact"> 触发
return;
}
this.handleCustomWeappService(config, options);
}
handleCustomWeappService(config, options = {}) {
if (config.customServiceUrl) {
let url = config.customServiceUrl;
const params = [];
const { sendMessageTitle, sendMessagePath, sendMessageImg } = options;
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 });
return;
}
this.tryThirdPartyService(config, options);
}
tryThirdPartyService(config, options = {}) {
// 支持第三方微信小程序客服
if (config.thirdPartyMiniAppId || config.mini_app_id) {
// #ifdef MP-WEIXIN
wx.navigateToMiniProgram({
appId: config.thirdPartyMiniAppId || config.mini_app_id,
path: config.thirdPartyMiniAppPath || config.mini_app_path || ''
});
// #endif
return;
}
// 支持第三方链接客服
if (config.thirdPartyServiceUrl || config.third_url) {
const serviceUrl = config.thirdPartyServiceUrl || config.third_url;
// #ifdef H5
window.open(serviceUrl, '_blank');
// #endif
// #ifdef MP-WEIXIN
uni.setClipboardData({ data: serviceUrl });
uni.showToast({ title: '客服链接已复制', icon: 'none' });
// #endif
return;
}
this.fallbackToPhoneCall();
}
openAliappService(config) {
if (config.type === 'aikefu') {
this.openDifyService();
} else if (config.type === 'third') {
this.openThirdService(config);
} else {
// 支付宝原生客服由 button open-type="contact" 触发,此处不处理
}
}
// ================== 辅助方法 ==================
makePhoneCall() {
this.vm.$api.sendRequest({
url: '/api/site/shopcontact',
success: res => {
if (res.code === 0 && res.data?.mobile) {
uni.makePhoneCall({ phoneNumber: res.data.mobile });
} else {
uni.showToast({ title: '暂无客服电话', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '获取客服电话失败', icon: 'none' });
}
});
}
showNoServicePopup() {
const siteInfo = this.vm.$store.state.siteInfo || {};
const message = siteInfo?.site_tel
? `请联系客服,客服电话是 ${siteInfo.site_tel}`
: '抱歉,商家暂无客服,请线下联系';
uni.showModal({ title: '联系客服', content: message, showCancel: false });
}
showConfigErrorPopup(errors) {
uni.showModal({
title: '客服配置错误',
content: `配置有误:\n${errors.join('\n')}`,
showCancel: false
});
}
fallbackToOriginalService() {
uni.showModal({
title: '提示',
content: '无法直接添加企业微信,是否使用其他方式联系客服?',
success: (res) => {
if (res.confirm) {
this.openWxworkService(true);
}
}
});
}
fallbackToPhoneCall() {
uni.showModal({
title: '提示',
content: '在线客服不可用,是否拨打电话联系客服?',
success: (res) => {
if (res.confirm) this.makePhoneCall();
}
});
}
/**
* 获取按钮配置(用于 template 中 v-if / open-type 判断)
* @returns {Object}
*/
getButtonConfig() {
const config = this.getPlatformConfig();
if (!config) return { openType: '' };
let openType = '';
// #ifdef MP-WEIXIN
if (config.type === 'weapp' && config.useOfficial !== false) {
openType = 'contact';
}
// #endif
// #ifdef MP-ALIPAY
if (config.type === 'aliapp') openType = 'contact';
// #endif
return { ...config, openType };
}
/**
* 获取客服按钮图标
* @returns {String} 图标URL
*/
getKefuButtonIcon() {
const shopInfo = this.vm.shopInfo || {};
const config = this.getPlatformConfig();
// 优先使用指定的智能客服头像
if (config?.type === 'aikefu' || config?.type === 'wxwork' || config?.type === 'qyweixin') {
return this.vm.$util.img(IMAGE_CONFIG.aiAvatar);
}
return shopInfo.kefuimg || this.vm.$util?.getDefaultImage()?.kefu || '';
}
/**
* 判断是否为微信官方客服
* @returns {Boolean}
*/
isWeappOfficialKefu() {
const config = this.getPlatformConfig();
return config?.type === 'weapp';
}
/**
* 判断是否为智能客服
* @returns {Boolean}
*/
isAIKefu() {
const config = this.getPlatformConfig();
return config?.type === 'aikefu';
}
/**
* 判断是否为企业微信客服
* @returns {Boolean}
*/
isWxworkKefu() {
const config = this.getPlatformConfig();
return config?.type === 'wxwork' || config?.type === 'qyweixin';
}
/**
* 判断是否为第三方客服
* @returns {Boolean}
*/
isThirdKefu() {
const config = this.getPlatformConfig();
return config?.type === 'third';
}
/**
* 判断是否需要同时显示小程序系统客服
* @returns {Boolean}
*/
shouldShowExtraWeappSystemKefu() {
const config = this.getPlatformConfig();
return (config?.show_system_service === true || config?.show_system_service === '1') && config?.type !== 'weapp';
}
/**
* 计算容器高度
* @param {Boolean} isLanguageSwitchEnabled 是否启用语言切换
* @param {Boolean} showWeappSystemKefu 是否显示小程序系统客服
* @param {Boolean} fixBtnShow 是否显示按钮
* @returns {String} 高度值(px)
*/
getContainerHeight(isLanguageSwitchEnabled, showWeappSystemKefu, fixBtnShow) {
if (!fixBtnShow) return '160px';
let buttonCount = 1;
if (isLanguageSwitchEnabled) buttonCount++;
if (showWeappSystemKefu) buttonCount++;
const totalRpx = 94 * buttonCount - 14;
const pxValue = Math.round(totalRpx * 0.5);
return `${pxValue}px`;
}
/**
* 获取未读消息数量
* @returns {Number}
*/
getUnreadCount() {
return this.vm.$store?.state?.aiUnreadCount || 0;
}
/**
* 拨打电话联系客服
* @param {String} phoneNumber 电话号码
*/
makePhoneCallByNumber(phoneNumber) {
if (phoneNumber) {
uni.makePhoneCall({ phoneNumber });
} else {
this.makePhoneCall();
}
}
/**
* 打开 AI 智能助手
*/
openAIChat() {
try {
if (typeof this.vm.setAiUnreadCount === 'function') {
this.vm.setAiUnreadCount(0);
}
const aiChatUrl = '/pages_tool/ai-chat/index';
uni.navigateTo({
url: aiChatUrl,
fail: (err) => {
console.error('跳转 AI智能客服失败:', err);
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
/**
* 打开客服选择对话框(预留方法)
*/
openCustomerSelectPopupDialog() {
uni.showToast({ title: '客服选择功能开发中', icon: 'none' });
}
/**
* 打开客服选择弹出层(ActionSheet)
* @param {Array} kefuList 客服列表
*/
openKefuSelectPopup(kefuList) {
const kefuNames = kefuList.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
const selectedKefu = kefuList[res.tapIndex];
const cs = createCustomerService(this.vm, selectedKefu);
if (selectedKefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (selectedKefu.id === 'qyweixin-kefu') {
if (uni.getSystemInfoSync().platform === 'wechat') {
uni.navigateTo({
url: '/pages_tool/qyweixin-kefu/index'
});
} else {
const qyweixinUrl = this.vm.shopInfo?.qyweixinUrl;
if (qyweixinUrl) {
window.location.href = qyweixinUrl;
} else {
uni.showToast({ title: '企业微信客服未配置', icon: 'none' });
}
}
} else {
cs.handleCustomerClick();
}
}
});
}
/**
* 核心方法:统一客服入口(带验证和配置检查)
* @param {Object} options 选项参数
*/
handleUnifiedKefuClick(options = {}) {
const validation = this.validateConfig();
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const platformConfig = this.getPlatformConfig();
// 检查企业微信配置
if (platformConfig.type === 'wxwork') {
const wxworkConfig = this.getWxworkConfig();
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
console.warn('企业微信配置不完整,使用原生客服');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
}
// #endif
// #ifdef H5
if (!wxworkConfig?.contact_url && !platformConfig.wxwork_url) {
console.error('企业微信链接未配置');
uni.showToast({ title: '企业微信链接未配置', icon: 'none' });
return;
}
// #endif
}
// 直接调用统一处理方法,由 CustomerService 内部根据配置路由
try {
this.handleCustomerClick({
sendMessageTitle: options.sendMessageTitle || '来自悬浮按钮的咨询',
sendMessagePath: options.sendMessagePath || '/pages/index/index'
});
} catch (error) {
console.error('客服处理失败:', error);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
}
/**
* 创建客服服务实例
* @param {Object} vueInstance Vue 实例(通常是 this)
* @param {Object} externalConfig 可选:外部传入的最新配置(如从 DIY 数据中提取)
* @returns {CustomerService}
*/
export function createCustomerService(vueInstance, externalConfig = null) {
return new CustomerService(vueInstance, externalConfig);
}