feat: 新增多语言文案、优化导航及工具函数,修复部分业务逻辑

1. 新增多语言资源文件,覆盖文章、预约日期、会员信息、优惠券、表单、购物车、积分商城、分销、AI聊天等多个模块
2. 优化底部导航组件,使用多语言配置替代硬编码文案
3. 重构工具函数,新增从后端数据提取多语言文案的工具方法
4. 修复邀请好友页面的保存提示文案,替换为多语言版本
5. 修复商品SKU组件的提示文案,使用多语言配置
6. 重构AI聊天页面的帮助文档和回复模板,使用多语言配置
7. 优化空状态组件,支持多语言默认文案
8. 修复跳转逻辑,补全参数传递
9. 新增本地开发环境配置
This commit is contained in:
2026-06-25 18:15:31 +08:00
parent ca4e7fd170
commit 2d8846c64c
82 changed files with 3497 additions and 1062 deletions

View File

@@ -138,12 +138,12 @@ export class CustomerService {
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.openFailed'), icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.openFailed'), icon: 'none' });
}
}
@@ -223,7 +223,7 @@ export class CustomerService {
// 检查是否有企业微信配置
if (!config.wxwork_url && !config.corpid) {
console.error('企业微信配置不完整,缺少 wxwork_url 或 corpid');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.wxworkConfigIncomplete'), icon: 'none' });
this.fallbackToPhoneCall();
return;
}
@@ -267,12 +267,12 @@ export class CustomerService {
},
fail: (err) => {
console.error('【跳转第三方小程序失败】', err);
uni.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.jumpFailed'), icon: 'none' });
}
});
// #endif
// #ifdef H5
uni.showToast({ title: '第三方小程序客服仅在微信小程序中可用', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.miniProgramOnly'), icon: 'none' });
// #endif
return;
}
@@ -286,7 +286,7 @@ export class CustomerService {
uni.setClipboardData({
data: config.third_url,
success: () => {
uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.linkCopied'), icon: 'none' });
}
});
// #endif
@@ -352,7 +352,7 @@ export class CustomerService {
// #endif
// #ifdef MP
uni.setClipboardData({ data: serviceUrl });
uni.showToast({ title: '客服链接已复制', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.customerLinkCopied'), icon: 'none' });
// #endif
return;
}
@@ -380,11 +380,11 @@ export class CustomerService {
if (res.code === 0 && res.data?.mobile) {
uni.makePhoneCall({ phoneNumber: res.data.mobile });
} else {
uni.showToast({ title: '暂无客服电话', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.noServicePhone'), icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '获取客服电话失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.getPhoneFailed'), icon: 'none' });
}
});
}
@@ -392,23 +392,23 @@ export class CustomerService {
showNoServicePopup() {
const siteInfo = this.vm.$store.state.siteInfo || {};
const message = siteInfo?.site_tel
? `请联系客服,客服电话是 ${siteInfo.site_tel}`
: '抱歉,商家暂无客服,请线下联系';
uni.showModal({ title: '联系客服', content: message, showCancel: false });
? this.vm.$lang('customerService.callServiceTip', { phone: siteInfo.site_tel })
: this.vm.$lang('customerService.noServiceOffline');
uni.showModal({ title: this.vm.$lang('customerService.contactCustomerService'), content: message, showCancel: false });
}
showConfigErrorPopup(errors) {
uni.showModal({
title: '客服配置错误',
content: `配置有误:\n${errors.join('\n')}`,
title: this.vm.$lang('customerService.configError'),
content: this.vm.$lang('customerService.configErrorDetail', { errors: errors.join('\n') }),
showCancel: false
});
}
fallbackToOriginalService() {
uni.showModal({
title: '提示',
content: '无法直接添加企业微信,是否使用其他方式联系客服?',
title: this.vm.$lang('customerService.tip'),
content: this.vm.$lang('customerService.cannotAddWxwork'),
success: (res) => {
if (res.confirm) {
this.openWxworkService(true);
@@ -419,8 +419,8 @@ export class CustomerService {
fallbackToPhoneCall() {
uni.showModal({
title: '提示',
content: '在线客服不可用,是否拨打电话联系客服?',
title: this.vm.$lang('customerService.tip'),
content: this.vm.$lang('customerService.onlineUnavailable'),
success: (res) => {
if (res.confirm) this.makePhoneCall();
}
@@ -567,12 +567,12 @@ export class CustomerService {
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.openFailed'), icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.openFailed'), icon: 'none' });
}
}
@@ -580,7 +580,7 @@ export class CustomerService {
* 打开客服选择对话框(预留方法)
*/
openCustomerSelectPopupDialog() {
uni.showToast({ title: '客服选择功能开发中', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.developing'), icon: 'none' });
}
/**
@@ -610,7 +610,7 @@ export class CustomerService {
if (qyweixinUrl) {
window.location.href = qyweixinUrl;
} else {
uni.showToast({ title: '企业微信客服未配置', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.wxworkNotConfigured'), icon: 'none' });
}
}
} else {
@@ -629,7 +629,7 @@ export class CustomerService {
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.serviceUnavailable'), icon: 'none' });
return;
}
@@ -646,14 +646,14 @@ export class CustomerService {
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
console.warn('企业微信配置不完整,使用原生客服');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.wxworkConfigIncomplete'), icon: 'none' });
}
// #endif
// #ifdef H5
if (!wxworkConfig?.contact_url && !platformConfig.wxwork_url) {
console.error('企业微信链接未配置');
uni.showToast({ title: '企业微信链接未配置', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.wxworkLinkNotConfigured'), icon: 'none' });
return;
}
// #endif
@@ -662,12 +662,12 @@ export class CustomerService {
// 直接调用统一处理方法,由 CustomerService 内部根据配置路由
try {
this.handleCustomerClick({
sendMessageTitle: options.sendMessageTitle || '来自悬浮按钮的咨询',
sendMessageTitle: options.sendMessageTitle || this.vm.$lang('customerService.fromFloatButton'),
sendMessagePath: options.sendMessagePath || '/pages/index/index'
});
} catch (error) {
console.error('客服处理失败:', error);
uni.showToast({ title: '打开客服失败', icon: 'none' });
uni.showToast({ title: this.vm.$lang('customerService.openFailed'), icon: 'none' });
}
}
}

View File

@@ -45,6 +45,12 @@ export default {
diyRoute: '', // 页面路由
openBottomNav: false,
isShowCopyRight: false,
// getDiyInfo 节流
diyInfoLoading: false, // 是否正在请求
lastDiyInfoTime: 0, // 上次成功加载的时间戳
lastDiyInfoLang: '', // 上次加载时的语言,语言切换时强制刷新
diyInfoTtl: 3 * 60 * 1000, // 3分钟内不重复请求
//启动广告
adv:{},
@@ -304,20 +310,45 @@ export default {
}
},
async getDiyInfo() {
let res = await this.$api.sendRequest({
url: '/api/diyview/info',
data: {
id: this.id,
name: this.name,
en_type:uni.getStorageSync("lang"),//获取语言底部
},
async: false
});
if (res.code != 0 || !res.data) {
async getDiyInfo(force = false) {
// 节流:正在请求 / N 秒内已加载过 / 语言未变 -> 跳过
const now = Date.now();
const currentLang = uni.getStorageSync("lang") || '';
if (!force) {
if (this.diyInfoLoading) return;
if (this.lastDiyInfoTime && (now - this.lastDiyInfoTime) < this.diyInfoTtl
&& this.lastDiyInfoLang === currentLang) {
return;
}
}
this.diyInfoLoading = true;
let res;
try {
res = await this.$api.sendRequest({
url: '/api/diyview/info',
data: {
id: this.id,
name: this.name,
en_type: currentLang,
},
async: false
});
} catch (e) {
console.error('getDiyInfo request failed', e);
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.$util.showToast({
title: '网络异常,请稍后重试'
});
this.diyInfoLoading = false;
return;
}
this.diyInfoLoading = false;
if (!res || res.code != 0 || !res.data) {
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
if (res.code == -3) {
if (res && res.code == -3) {
this.$util.showToast({
title: res.message
});
@@ -332,12 +363,16 @@ export default {
return;
}
this.lastDiyInfoTime = now;
this.lastDiyInfoLang = currentLang;
let diyDataValue = res.data;
if (diyDataValue.value) {
this.diyData = JSON.parse(diyDataValue.value);
// 导航栏标题要根据语言环境切换
const title = this.isEnEnv ? this.diyData?.global?.en_title : this.diyData?.global?.title;
// 优先使用 title_map 多语言 map,没有则回退到 title / en_title
const title = this.$langConfig.getField(this.diyData?.global, 'title');
if (title) this.$langConfig.title(title);
this.mpCollect = this.diyData.global.mpCollect;
@@ -582,7 +617,7 @@ export default {
if (this.id) shareUrl += '?id=' + this.id;
else if (this.name) shareUrl += '?name=' + this.name;
this.$util.setPublicShare({
title: this.diyData.global.title,
title: this.$langConfig.getField(this.diyData.global, 'title'),
desc: '',
link: shareUrl,
imgUrl: this.siteInfo ? this.$util.img(this.siteInfo.logo_square) : ''

View File

@@ -161,6 +161,52 @@ function applyLangParams(value, args) {
return result;
}
/**
* 从后端数据中提取当前语言的文案(向后兼容)
*
* 优先使用多语言 map(如 title_map / text_map),格式:
* { "zh-cn": "标题", "en-us": "Title" }
*
* 如果没有 map,回退到二元模式(兼容旧数据):
* field = 'title' → 取 title / en_title
* field = 'text' → 取 text / en_text
*
* @param {Object} data 后端数据对象
* @param {string} field 基础字段名(如 'title'、'text'、'goods_name')
* @param {string} [locale] 目标语言,默认取当前语言
* @returns {string} 对应语言的文案,找不到返回空串
*/
function getI18nField(data, field, locale) {
if (!data || !field) return '';
const lang = locale || getCurrentLocale();
// 1) 优先尝试多语言 map: title_map / text_map / goods_name_map
const mapKey = field + '_map';
const map = data[mapKey];
if (map && typeof map === 'object' && !Array.isArray(map)) {
// 精确匹配当前语言
if (map[lang] !== undefined && map[lang] !== null && map[lang] !== '') {
return String(map[lang]);
}
// 兜底:取第一个非空值
for (const k of Object.keys(map)) {
if (map[k] !== undefined && map[k] !== null && map[k] !== '') {
return String(map[k]);
}
}
}
// 2) 回退到二元模式: field / en_field
const enKey = 'en_' + field;
if (lang === 'en-us' && data[enKey] !== undefined && data[enKey] !== '') {
return String(data[enKey]);
}
if (data[field] !== undefined && data[field] !== '') {
return String(data[field]);
}
return '';
}
// 远端覆写:内存缓存
// shape: { [app_lang]: { version, map, ts, global_version } }
// key 格式 `${appType}_${lang}`,天然按平台 + 语种二维隔离,避免多端 cache 串味
@@ -487,6 +533,23 @@ export default {
*/
getCurrentLocale,
/**
* 从后端数据中提取当前语言的文案(向后兼容)
* 优先使用 xxx_map 多语言 map,没有则回退到 xxx / en_xxx 二元模式
*
* 用法:
* this.$langConfig.getField(item, 'title') // 取标题
* this.$langConfig.getField(item, 'text') // 取 tabBar 文案
* this.$langConfig.getField(item, 'goods_name') // 取商品名
*
* @param {Object} data 后端数据对象
* @param {string} field 基础字段名
* @returns {string}
*/
getField(data, field) {
return getI18nField(data, field);
},
/**
* 当前租户 id
*/

View File

@@ -958,6 +958,7 @@ export default {
// #ifdef MP
this.redirectTo('/pages_tool/webview/webview', {
...link,
src: encodeURIComponent(url)
});
// #endif
@@ -1024,11 +1025,11 @@ export default {
break;
case 'niushop':
// Niushop客服
this.redirectTo('/pages_tool/chat/room');
this.redirectTo('/pages_tool/chat/room', {...link});
break;
case 'weapp':
// 微信小程序,由于需要手动点击按钮触发,所以要跳转到中间页
this.redirectTo(link.wap_url);
this.redirectTo(link.wap_url, {...link});
break;
default:
// 拨打客服电话
@@ -1045,9 +1046,7 @@ export default {
}
} else if (link.wap_url) {
this.redirectTo(link.wap_url, {
...link
});
this.redirectTo(link.wap_url, {...link});
}
},
/**