refactor: 完成多语言国际化全量替换与补充

1. 新增大量中英文语言包文件,覆盖自定义组件、页面、工具类的文案
2. 替换硬编码文本为多语言调用,统一全局文案管理
3. 优化语言包结构,拆分公共模块与业务模块
4. 修复部分语言加载与国际化适配问题
This commit is contained in:
2026-06-26 17:32:35 +08:00
parent f7fb9f34f9
commit 4ae21c5d7a
91 changed files with 2650 additions and 281 deletions

View File

@@ -1125,18 +1125,18 @@ _handleStreamError(msg) {
generateAIResponse(userMessage) {
const responses = {
'你好': '您好!我是AI智能客服,很高兴为您服务!有什么可以帮助您的吗?',
'价格': '我们的产品价格根据型号和配置有所不同,具体价格请查看商品详情页面。',
'发货': '一般情况下,订单会在24小时内发货,具体时效请查看物流信息。',
'退货': '支持7天无理由退货,请确保商品完好无损。',
'客服': '我们的客服工作时间是9:00-18:00,有问题可以随时联系我们。'
'你好': this.$lang('autoReplyGreeting'),
'价格': this.$lang('autoReplyPrice'),
'发货': this.$lang('autoReplyShipping'),
'退货': this.$lang('autoReplyReturn'),
'客服': this.$lang('autoReplyService')
};
for (let key in responses) {
if (userMessage.includes(key)) {
return responses[key];
}
}
return `感谢您的咨询!关于"${userMessage}"的问题,我们的专业客服会尽快为您解答。您也可以查看我们的帮助文档获取更多信息。`;
return this.$lang('autoReplyDefault', { question: userMessage });
},

View File

@@ -143,7 +143,7 @@ export default {
},
onShow() {
console.log('[AI Chat Page] onShow 触发');
console.log(this.$lang('logOnShowTriggered'));
// 监听键盘高度变化(H5 可能不支持,try-catch 保护)
try {
this._keyboardChangeHandler = (res) => {
@@ -154,7 +154,7 @@ export default {
};
uni.onKeyboardHeightChange(this._keyboardChangeHandler);
} catch (e) {
console.warn('onKeyboardHeightChange 不可用:', e.message);
console.warn(this.$lang('logKeyboardChangeUnavailable') + ':', e.message);
}
// 每次页面显示时尝试加载历史记录(H5可能组件尚未创建,用 nextTick 确保 ref 就绪)
this.$nextTick(() => {
@@ -177,7 +177,7 @@ export default {
},
onUnload() {
console.log('[AI Chat Page] onUnload 触发');
console.log(this.$lang('logOnUnloadTriggered'));
this.cleanup();
aiService.disconnectWebSocket();
},
@@ -233,7 +233,7 @@ export default {
},
handleEventError(error, event) {
console.error('事件处理错误:', {
console.error(this.$lang('logEventHandleError') + ':', {
error: error.message,
eventType: event?.type,
component: this. $options.name
@@ -263,7 +263,7 @@ export default {
this.calculateScrollViewHeight();
this.setupNavigationEvents();
} catch (error) {
console.error('初始化导航栏失败:', error);
console.error(this.$lang('logInitNavbarFailed') + ':', error);
this.setFallbackNavigationValues();
}
},
@@ -459,7 +459,7 @@ export default {
sendProductToAI(product) {
if (!this.$refs.chat) {
console.error('聊天组件未初始化')
console.error(this.$lang('logChatComponentNotInit'))
return
}
@@ -503,7 +503,7 @@ export default {
// 如果配置未加载,延迟重试(最多3次,防白屏)
if (!servicerConfig) {
console.warn('【AI客服】servicerConfig 未加载,200ms后重试...');
console.warn(this.$lang('logServicerConfigNotLoaded'));
setTimeout(() => {
this.checkAccessPermission();
}, 200);

View File

@@ -2,17 +2,17 @@
<view class="page" :style="themeColor">
<view class="help-title">{{ detail.article_title }}</view>
<view class="help-meta" v-if="detail.is_show_release_time == 1">
<text class="help-time">发表时间: {{ $util.timeStampTurnTime(detail.create_time) }}</text>
<text class="help-time">{{ $lang('publishTime') }} {{ $util.timeStampTurnTime(detail.create_time) }}</text>
</view>
<view class="help-content"><rich-text :nodes="content"></rich-text></view>
<view class="bottom-area">
<view v-if="detail.is_show_read_num == 1">
阅读:
{{ $lang('readCount') }}
<text class="price-font">{{ detail.read_num + detail.initial_read_num }}</text>
</view>
<view v-if="detail.is_show_dianzan_num == 1">
<text class="price-font">{{ detail.dianzan_num + detail.initial_dianzan_num }}</text>
人已赞
{{ $lang('likedSuffix') }}
</view>
</view>
<loading-cover ref="loadingCover"></loading-cover>

View File

@@ -21,7 +21,7 @@
</view>
</view>
</view>
<view v-else class="empty-wrap"><ns-empty text="暂无文章" :isIndex="false"></ns-empty></view>
<view v-else class="empty-wrap"><ns-empty :text="$lang('emptyText')" :isIndex="false"></ns-empty></view>
<loading-cover ref="loadingCover"></loading-cover>
</block>
</mescroll-uni>
@@ -88,7 +88,7 @@ export default {
setPublicShare() {
let shareUrl = this.$config.h5Domain + '/pages_tool/article/list';
this.$util.setPublicShare({
title: '文章列表',
title: this.$lang('title'),
desc: '',
link: shareUrl,
imgUrl: this.siteInfo ? this.$util.img(this.siteInfo.logo_square) : ''
@@ -96,7 +96,7 @@ export default {
}
},
onShareAppMessage(res) {
var title = '文章列表';
var title = this.$lang('title');
var path = '/pages_tool/article/list';
return {
title: title,
@@ -107,7 +107,7 @@ export default {
},
//分享到朋友圈
onShareTimeline() {
var title = '文章列表';
var title = this.$lang('title');
var query = '/pages_tool/article/list';
return {
title: title,

View File

@@ -2,7 +2,7 @@
<view>
<view class="search-wrap uni-flex uni-row">
<view class="flex-item input-wrap">
<input class="uni-input" maxlength="50" placeholder="请输入您要搜索的文件名称" v-model="keyword" @confirm="search"
<input class="uni-input" maxlength="50" :placeholder="$lang('searchPlaceholder')" v-model="keyword" @confirm="search"
@input="__set_model($event, 'keyword')" />
<text class="iconfont icon-sousuo3" @tap="search"></text>
</view>
@@ -20,7 +20,7 @@
<view class="info-wrap">
<view class="title">{{ item.$orig.files_title }}</view>
<view class="read-wrap">
<text>查看PDF文件</text>
<text>{{ $lang('viewPdf') }}</text>
</view>
</view>
</view>
@@ -29,7 +29,7 @@
</block>
<block v-else>
<view class="empty-wrap">
<ns-empty text="暂无文件" :isIndex="false"></ns-empty>
<ns-empty :text="$lang('emptyText')" :isIndex="false"></ns-empty>
</view>
</block>
<loading-cover ref="loadingCover"></loading-cover>
@@ -116,7 +116,7 @@ export default {
},
toDetail(item) {
uni.showLoading({
title: '正在加载'
title: this.$lang('loading')
})
try {
uni.downloadFile({
@@ -141,7 +141,7 @@ export default {
complete: res => {
if (res.statusCode === 404) {
uni.showToast({
title: '文件不存在',
title: this.$lang('fileNotExist'),
icon: 'none'
})
}

View File

@@ -5,12 +5,12 @@
<image :src="$util.img('public/uniapp/form/banner.png')" mode="widthFix"></image>
</view>
<view class="system-form-wrap">
<view class="form-title">请填写表单所需信息</view>
<view class="form-title">{{ $lang('form.form.title') }}</view>
<ns-form :data="detail.json_data" :formConfig="detail.form_config" ref="form"></ns-form>
<button type="primary" size="mini" class="button mini" @click="create()">提交</button>
<button type="primary" size="mini" class="button mini" @click="create()">{{ $lang('common.submit') }}</button>
</view>
</view>
<ns-empty :text="complete ? '提交成功' : '未获取到表单信息'" v-else></ns-empty>
<ns-empty :text="complete ? $lang('form.form.submitSuccess') : $lang('form.form.noFormInfo')" v-else></ns-empty>
<loading-cover ref="loadingCover"></loading-cover>
<ns-login ref="login"></ns-login>
@@ -88,7 +88,7 @@
},
success: res => {
if (res.code == 0) {
this.$util.showToast({ title: '提交成功' })
this.$util.showToast({ title: this.$lang('form.form.submitSuccess') })
setTimeout(() => {
this.complete = true;
this.detail = null;

View File

@@ -0,0 +1,380 @@
<template>
<view :style="themeColor">
<template v-if="memberInfo">
<!-- 一键授权绑定区域(小程序环境) -->
<!-- #ifdef MP -->
<view v-if="!showManualForm" class="bind-wrap">
<view class="bind-tip">
<view class="tip-title">{{ $lang('bindMobile.tipTitle') }}</view>
<view class="tip-desc">{{ $lang('bindMobile.bindBenefitTip') }}</view>
</view>
<view class="save-item bind-mobile">
<button type="primary" open-type="getPhoneNumber" @getphonenumber="mobileAuth">{{ $lang('bindMobile.oneClickAuthBind') }}</button>
</view>
<view class="save-item bind-mobile">
<button type="primary" @click="showManualForm = true">{{ $lang('bindMobile.manualBind') }}</button>
</view>
</view>
<!-- #endif -->
<!-- 手动绑定表单 -->
<view class="edit-info">
<!-- #ifdef MP -->
<view v-if="showManualForm" class="form-switch-back" @click="showManualForm = false">
<text>{{ $lang('bindMobile.oneClickAuthBind') }}</text>
</view>
<!-- #endif -->
<view class="edit-info-box">
<text class="info-name">{{ $lang('bindMobile.phoneNumber') }}</text>
<input class="uni-input info-content" type="number" maxlength="11" :placeholder="$lang('bindMobile.pleInputMobile')" v-model="formData.mobile" />
</view>
<view class="edit-info-box">
<text class="info-name">{{ $lang('bindMobile.confirmCode') }}</text>
<input class="uni-input info-content" type="number" maxlength="4" :placeholder="$lang('bindMobile.confirmCodeInput')" v-model="formData.mobileVercode" />
<image :src="captcha.img" class="captcha" @click="getCaptcha" show-menu-by-longpress></image>
</view>
<view class="edit-info-box">
<text class="info-name">{{ $lang('bindMobile.smsCode') }}</text>
<input class="uni-input info-content" type="number" maxlength="6" :placeholder="$lang('bindMobile.animateCodeInput')" v-model="formData.mobileDynacode" />
<button type="primary" class="dynacode" @click="bindMoblieCode()">{{ formData.mobileCodeText }}</button>
</view>
<view class="save-item" @click="bindMobile">
<button type="primary">{{ $lang('bindMobile.save') }}</button>
</view>
</view>
</template>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
import validate from '@/common/js/validate.js';
import auth from '@/common/js/auth.js';
export default {
data() {
return {
showManualForm: false,
formData: {
mobile: '',
mobileVercode: '',
mobileDynacode: '',
mobileCodeText: '',
},
seconds: 120,
timer: null,
isSend: false,
captcha: {
id: '',
img: ''
},
};
},
mixins: [auth],
onLoad() {
this.formData.mobileCodeText = this.$lang('bindMobile.findanimateCode');
this.getCaptcha();
// #ifdef MP-WEIXIN
this.getCode();
// #endif
},
onShow() {
if (!this.storeToken) {
this.$nextTick(() => {
this.$refs.login.open('/pages_tool/member/index');
});
}
},
onHide() {
this.seconds = 120;
this.formData.mobileCodeText = this.$lang('bindMobile.findanimateCode');
this.isSend = false;
clearInterval(this.timer);
},
watch: {
seconds(value) {
if (value == 0) {
this.seconds = 120;
this.formData.mobileCodeText = this.$lang('bindMobile.findanimateCode');
this.isSend = false;
clearInterval(this.timer);
}
}
},
computed: {
themeColor() {
return this.$store.state.themeStyle ? `--main_color:${this.$store.state.themeStyle.main_color}` : '';
},
themeStyle() {
return this.$store.state.themeStyle || {};
},
memberInfo() {
return this.$store.state.memberInfo || {};
},
storeToken() {
return this.$store.state.token;
},
},
methods: {
// 获取图形验证码
getCaptcha() {
this.$api.sendRequest({
url: '/api/captcha/captcha',
data: {
captcha_id: this.captcha.id
},
success: res => {
if (res.code >= 0) {
this.captcha = res.data;
this.captcha.img = this.captcha.img.replace(/\r\n/g, '');
}
}
});
},
// 验证手机号
vertifyMobile() {
var rule = [{
name: 'mobile',
checkType: 'required',
errorMsg: this.$lang('bindMobile.pleInputMobile')
}, {
name: 'mobile',
checkType: 'phoneno',
errorMsg: this.$lang('bindMobile.surePhoneNumber')
}];
var checkRes = validate.check(this.formData, rule);
if (!checkRes) {
this.$util.showToast({
title: validate.error
});
return false;
}
return true;
},
// 发送短信动态码
async bindMoblieCode() {
if (this.seconds != 120) return;
var rule = [{
name: 'mobile',
checkType: 'phoneno',
errorMsg: this.$lang('bindMobile.surePhoneNumber')
},
{
name: 'mobileVercode',
checkType: 'required',
errorMsg: this.$lang('bindMobile.confirmCodeInput')
},
];
var checkRes = validate.check(this.formData, rule);
if (checkRes && !this.isSend) {
this.isSend = true;
this.$api.sendRequest({
url: '/api/member/bindmobliecode',
data: {
mobile: this.formData.mobile,
captcha_id: this.captcha.id,
captcha_code: this.formData.mobileVercode
},
success: res => {
let data = res.data;
if (data.key) {
if (this.seconds == 120 && this.timer == null) {
this.timer = setInterval(() => {
this.seconds--;
this.formData.mobileCodeText = this.$lang('bindMobile.codeSent', { seconds: this.seconds });
}, 1000);
}
uni.setStorageSync('mobile_key', data.key);
} else {
this.$util.showToast({
title: res.message
});
this.isSend = false;
}
},
fail: res => {
this.isSend = false;
this.getCaptcha();
}
});
} else {
this.$util.showToast({
title: validate.error ? validate.error : this.$lang('bindMobile.doNotRepeatedlyClick')
});
}
},
// 绑定手机号
bindMobile() {
if (!this.vertifyMobile()) return;
var mobileRule = [{
name: 'mobileVercode',
checkType: 'required',
errorMsg: this.$lang('bindMobile.confirmCodeInput')
},
{
name: 'mobileDynacode',
checkType: 'required',
errorMsg: this.$lang('bindMobile.animateCodeInput')
},
];
var checkRes = validate.check(this.formData, mobileRule);
if (checkRes) {
if (this.memberInfo.mobile && this.formData.mobile == this.memberInfo.mobile) {
this.$util.showToast({
title: this.$lang('bindMobile.alikePhone')
});
return;
}
this.$api.sendRequest({
url: '/api/member/modifymobile',
data: {
mobile: this.formData.mobile,
captcha_id: this.captcha.id,
captcha_code: this.formData.mobileVercode,
code: this.formData.mobileDynacode,
key: uni.getStorageSync("mobile_key"),
},
success: res => {
if (res.code == 0) {
this.memberInfo.mobile = this.formData.mobile;
this.$store.commit('setMemberInfo', this.memberInfo);
this.$util.showToast({
title: this.$lang('bindMobile.bindSuccess')
});
uni.navigateBack({
delta: 1
});
} else {
this.$util.showToast({
title: res.message
});
this.getCaptcha();
}
},
fail: res => {
this.isSend = false;
this.getCaptcha();
}
});
} else {
this.$util.showToast({
title: validate.error
});
}
},
// 一键授权绑定(小程序)
mobileAuth(e) {
if (e.detail.errMsg == 'getPhoneNumber:ok') {
let data = e.detail
// #ifdef MP-WEIXIN
Object.assign(data, this.authInfo)
// #endif
this.$api.sendRequest({
url: '/api/member/mobileauth',
data,
success: res => {
if (res.code == 0) {
this.$util.showToast({
title: this.$lang('bindMobile.bindSuccess')
});
uni.navigateBack({
delta: 1
});
} else {
this.$util.showToast({
title: res.message
});
}
}
})
}
}
}
};
</script>
<style lang="scss">
.bind-wrap {
padding: 60rpx 40rpx;
}
.bind-tip {
text-align: center;
margin-bottom: 60rpx;
.tip-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.tip-desc {
font-size: 28rpx;
color: #999;
}
}
.edit-info-box {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 40rpx;
min-height: 50rpx;
background-color: #fff;
.info-name {
width: 150rpx;
font-size: 28rpx;
text-align: left;
}
.info-content {
flex: 1;
width: 0;
font-size: 28rpx;
padding: 0;
}
.dynacode {
margin: 0;
padding: 0 10rpx;
width: 250rpx;
height: 60rpx;
font-size: 24rpx;
line-height: 60rpx;
word-break: break-all;
}
}
.captcha {
width: 170rpx;
height: 50rpx;
}
.save-item {
margin-top: 50rpx;
button {
font-size: 30rpx;
}
}
.bind-mobile {
button {
border-radius: 60rpx;
}
}
.form-switch-back {
text-align: center;
padding: 20rpx;
font-size: 26rpx;
color: var(--main_color);
}
</style>

View File

@@ -10,9 +10,9 @@
<view class="align-center agreement-btn-select">
<text v-if="isSelect" class="iconfont icon-dui color-base-text" @click="changeSelect"></text>
<text v-else class="iconfont icon-yuan_checkbox" @click="changeSelect"></text>
<text class="agreement-text" @click="changeSelect">勾选即表示您已阅读并同意本协议</text>
<text class="agreement-text" @click="changeSelect">{{ $lang('agreeTermsText') }}</text>
</view>
<button class="btn color-base-bg" @click="next">下一步</button>
<button class="btn color-base-bg" @click="next">{{ $lang('nextStep') }}</button>
</view>
</view>
</view>
@@ -60,7 +60,7 @@ export default {
this.$util.redirectTo('/pages_tool/member/assets');
} else {
this.$util.showToast({
title: '请先勾选同意协议'
title: this.$lang('pleaseAgreeFirst')
});
}
}

View File

@@ -0,0 +1,314 @@
<template>
<view :style="themeColor">
<!-- 顶部统计 -->
<view class="stat-header">
<view class="stat-item">
<view class="stat-value">{{ cardStat.total_consume }}</view>
<view class="stat-label">{{ $lang('totalConsume') }}{{ $lang('statUnit', { unit: $lang('yuanSuffix') }) }}</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value">{{ cardStat.balance }}</view>
<view class="stat-label">{{ $lang('cardBalance') }}{{ $lang('statUnit', { unit: $lang('yuanSuffix') }) }}</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value">{{ cardStat.total_recharge }}</view>
<view class="stat-label">{{ $lang('totalRecharge') }}{{ $lang('statUnit', { unit: $lang('yuanSuffix') }) }}</view>
</view>
</view>
<!-- 筛选 tab -->
<scroll-view class="filter-tab" :scroll-x="true" :show-scrollbar="false">
<view v-for="(item, index) in filterList" :key="index"
class="tab-item" :class="{ active: currentFilter === item.value }"
@click="onFilterChange(item.value)">
<text>{{ item.label }}</text>
</view>
</scroll-view>
<!-- 列表 -->
<mescroll-uni @getData="getData" ref="mescroll">
<block slot="list">
<block v-if="logList.length > 0">
<view class="log-list">
<view class="log-item" v-for="(item, index) in logList" :key="index" @click="goDetail(item)">
<view class="log-icon" :class="item.type">
<text class="iconfont" :class="getIconClass(item.type)"></text>
</view>
<view class="log-info">
<view class="log-title">{{ item.title }}</view>
<view class="log-desc">{{ item.desc }}</view>
<view class="log-time">{{ formatTime(item.create_time) }}</view>
</view>
<view class="log-amount">
<text :class="item.amount > 0 ? 'income' : 'expense'">
{{ item.amount > 0 ? '+' : '' }}{{ $lang('amountWithUnit', { amount: item.amount }) }}
</text>
</view>
</view>
</view>
</block>
<block v-else>
<ns-empty :isIndex="false" :text="$lang('emptyTips')"></ns-empty>
</block>
</block>
</mescroll-uni>
<ns-login ref="login"></ns-login>
<loading-cover ref="loadingCover"></loading-cover>
</view>
</template>
<script>
export default {
data() {
return {
cardStat: {
total_consume: '0.00',
balance: '0.00',
total_recharge: '0.00'
},
logList: [],
filterList: [
{ label: this.$lang('filterAll'), value: 'all' },
{ label: this.$lang('filterIncome'), value: 'income' },
{ label: this.$lang('filterExpense'), value: 'expense' },
{ label: this.$lang('filterRecharge'), value: 'recharge' },
{ label: this.$lang('filterConsume'), value: 'consume' },
{ label: this.$lang('filterGift'), value: 'gift' }
],
currentFilter: 'all'
};
},
onLoad() {
if (!this.storeToken) {
this.$nextTick(() => {
this.$refs.login.open('/pages_tool/member/card_log');
});
return;
}
this.loadStat();
},
methods: {
// 加载统计数据
loadStat() {
this.$api.sendRequest({
url: '/api/membercard/stat',
success: res => {
if (res.code == 0 && res.data) {
this.cardStat = res.data;
}
}
});
},
// 筛选切换
onFilterChange(value) {
this.currentFilter = value;
this.$refs.mescroll.refresh();
},
// 获取图标 class
getIconClass(type) {
const iconMap = {
recharge: 'icon-chongzhi',
consume: 'icon-xiaofei',
gift: 'icon-liwu',
refund: 'icon-tuikuan'
};
return iconMap[type] || 'icon-jifen';
},
// 格式化时间
formatTime(timestamp) {
return this.$util.timeStampTurnTime(timestamp);
},
// 跳转详情
goDetail(item) {
if (item.related_type === 'order') {
this.$util.redirectTo('/pages_order/detail', { order_id: item.related_id });
}
},
// 获取列表数据
getData(mescroll) {
this.$api.sendRequest({
url: '/api/membercard/logPage',
data: {
page_size: mescroll.size,
page: mescroll.num,
type: this.currentFilter
},
success: res => {
let newArr = [];
if (res.code == 0 && res.data) {
newArr = res.data.list;
} else {
this.$util.showToast({
title: res.message
});
}
mescroll.endSuccess(newArr.length);
if (mescroll.num == 1) {
this.logList = [];
}
this.logList = this.logList.concat(newArr);
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
},
fail: res => {
mescroll.endErr();
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
}
});
}
}
};
</script>
<style lang="scss" scoped>
.stat-header {
display: flex;
align-items: center;
padding: 30rpx;
background: linear-gradient(135deg, #e3b66b 0%, #d4a04a 100%);
color: #fff;
.stat-item {
flex: 1;
text-align: center;
.stat-value {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
opacity: 0.85;
}
}
.stat-divider {
width: 1rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.3);
}
}
.filter-tab {
width: 100%;
white-space: nowrap;
background: #fff;
border-bottom: 1rpx solid $color-line;
.tab-item {
display: inline-block;
padding: 24rpx 32rpx;
font-size: $font-size-base;
color: $color-tip;
position: relative;
&.active {
color: $color-base-text;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: $color-base-text;
border-radius: 2rpx;
}
}
}
}
.log-list {
padding: 0 32rpx;
background: #fff;
.log-item {
display: flex;
align-items: center;
padding: 28rpx 0;
border-bottom: 1rpx solid $color-line;
&:last-child {
border-bottom: none;
}
.log-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
font-size: 32rpx;
&.recharge {
background: rgba(76, 175, 80, 0.1);
color: #4caf50;
}
&.consume {
background: rgba(244, 67, 54, 0.1);
color: #f44336;
}
&.gift {
background: rgba(255, 152, 0, 0.1);
color: #ff9800;
}
&.refund {
background: rgba(33, 150, 243, 0.1);
color: #2196f3;
}
}
.log-info {
flex: 1;
overflow: hidden;
.log-title {
font-size: $font-size-base;
color: $color-base-text;
font-weight: 500;
margin-bottom: 8rpx;
}
.log-desc {
font-size: $font-size-activity-tag;
color: $color-tip;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-time {
font-size: $font-size-activity-tag;
color: $color-tip;
}
}
.log-amount {
margin-left: 20rpx;
font-size: $font-size-toolbar;
font-weight: 500;
.income {
color: #4caf50;
}
.expense {
color: $color-base-text;
}
}
}
}
</style>

View File

@@ -13,7 +13,7 @@
<view class="goods_name font-size-base">{{ item.sku_name }}</view>
<view class="goods_opection">
<view class="left lineheight-clear ">
<text class="symbol price-style small">¥</text>
<text class="symbol price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{
parseFloat(item.discount_price).toFixed(2).split('.')[0] }}</text>
<text class="symbol price-style small">.{{
@@ -30,7 +30,7 @@
</block>
<!-- 第一个列表为空时 -->
<view class="collection-empty" v-else>
<ns-empty v-if="collectionList.length == 0 && isShowEmpty" text="暂无关注的商品" subText="赶紧去逛逛, 关注心仪的商品吧"
<ns-empty v-if="collectionList.length == 0 && isShowEmpty" :text="$lang('member.collection.emptyText')" :subText="$lang('member.collection.emptySubText')"
:isIndex="Boolean(storeToken)" :emptyBtn="{ text: $lang('toGoodsCategoryPage'), url: '/pages_goods/category' }"></ns-empty>
<button type="primary" size="mini" class="button mini" v-if="!storeToken"
@click="toLogin">{{ $lang('toLogin') }}</button>

View File

@@ -11,16 +11,16 @@
<view class="opection-box">
<block v-if="newImg == ''">
<!-- #ifdef MP-ALIPAY -->
<button type="primary" @click="uploadFace()">点击上传</button>
<button type="primary" @click="uploadFace()">{{$lang('member.modifyFace.clickToUpload')}}</button>
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
<button type="primary" @click="chooseImage()">点击上传</button>
<button type="primary" @click="chooseImage()">{{$lang('member.modifyFace.clickToUpload')}}</button>
<!-- #endif -->
</block>
<block v-else>
<view class="opec">
<button size="mini" class="mini" type="primary" @click="save()">确认保存</button>
<button size="mini" class="mini" type="primary" @click="chooseImage()">重新上传</button>
<button size="mini" class="mini" type="primary" @click="save()">{{$lang('member.modifyFace.confirmSave')}}</button>
<button size="mini" class="mini" type="primary" @click="chooseImage()">{{$lang('member.modifyFace.reupload')}}</button>
</view>
</block>
</view>
@@ -92,7 +92,7 @@ export default {
},
fail: () => {
this.$util.showToast({
title: '头像上传失败'
title: this.$lang('member.modifyFace.uploadFailed')
});
}
});
@@ -114,7 +114,7 @@ export default {
this.memberInfo.headimg = this.imgurl;
this.$store.commit('setMemberInfo', this.memberInfo);
this.$util.showToast({
title: '头像修改成功'
title: this.$lang('member.modifyFace.modifySuccess')
});
setTimeout(() => {
this.$util.redirectTo('/pages_tool/member/info', {}, 'redirectTo');

View File

@@ -53,7 +53,7 @@ export default {
success: res => {
if (res.code == 0) {
this.$util.showToast({
title: "删除成功"
title: this.$lang('member.collection.deleteSuccess')
})
let array = this.collectionList;
let newArray = array.filter((v) => {

View File

@@ -21,7 +21,7 @@
</view>
</block>
<block v-else>
<ns-empty :isIndex="false" text="暂无提现记录"></ns-empty>
<ns-empty :isIndex="false" :text="$lang('emptyTips')"></ns-empty>
</block>
</view>
</mescroll-uni>

View File

@@ -18,12 +18,12 @@
<view class="goods-num">
<view class="num-text color-base-text">{{ item.refund_status_name }}</view>
<view class="num-price">
<text>¥{{ item.price }}</text>
<text>{{ $lang('common.currencySymbol') }}{{ item.price }}</text>
<text class="num">×{{ item.num }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="goods-btn">
<view class="btn-text">
<text>{{ $lang('totalItems', { num: item.num }) }}</text>

View File

@@ -0,0 +1,518 @@
<template>
<view :style="themeColor" class="invoice-container">
<view class="invoice-header">
<view class="header-title">{{ $lang('header.title') }}</view>
<view class="header-desc">{{ $lang('header.desc') }}</view>
</view>
<view class="invoice-section">
<view class="section-title">{{ $lang('invoiceType.title') }}</view>
<view class="invoice-type-list">
<view
class="type-item"
:class="{ active: formData.invoice_type == 1 }"
@click="selectType(1)"
>
<view class="type-icon">
<text class="iconfont icon-zhifubao"></text>
</view>
<view class="type-name">{{ $lang('invoiceType.paper') }}</view>
<view class="type-desc">{{ $lang('invoiceType.paperDesc') }}</view>
</view>
<view
class="type-item"
:class="{ active: formData.invoice_type == 2 }"
@click="selectType(2)"
>
<view class="type-icon">
<text class="iconfont icon-weixinzhifu"></text>
</view>
<view class="type-name">{{ $lang('invoiceType.electronic') }}</view>
<view class="type-desc">{{ $lang('invoiceType.electronicDesc') }}</view>
</view>
</view>
</view>
<view class="invoice-section">
<view class="section-title">{{ $lang('invoiceTitle.title') }}</view>
<view class="title-type-switch">
<view
class="switch-item"
:class="{ active: formData.invoice_title_type == 1 }"
@click="selectTitleType(1)"
>
{{ $lang('invoiceTitle.personal') }}
</view>
<view
class="switch-item"
:class="{ active: formData.invoice_title_type == 2 }"
@click="selectTitleType(2)"
>
{{ $lang('invoiceTitle.company') }}
</view>
</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('invoiceTitle.label') }}
</view>
<input
class="form-input"
type="text"
v-model="formData.invoice_title"
:placeholder="formData.invoice_title_type == 1 ? $lang('invoiceTitle.personalPlaceholder') : $lang('invoiceTitle.companyPlaceholder')"
/>
</view>
<view class="form-item" v-if="formData.invoice_title_type == 2">
<view class="form-label">
<text class="required">*</text>
{{ $lang('invoiceTitle.taxpayerId') }}
</view>
<input
class="form-input"
type="text"
v-model="formData.taxpayer_id"
:placeholder="$lang('invoiceTitle.taxpayerIdPlaceholder')"
/>
</view>
</view>
<view class="invoice-section">
<view class="section-title">{{ $lang('invoiceContent.title') }}</view>
<view class="form-item">
<view class="form-label">{{ $lang('invoiceContent.label') }}</view>
<picker mode="selector" :range="invoiceContentList" range-key="name" @change="onContentChange">
<view class="picker-value">
<text v-if="formData.invoice_content">{{ formData.invoice_content }}</text>
<text class="placeholder" v-else>{{ $lang('invoiceContent.placeholder') }}</text>
<text class="iconfont icon-youjiantou"></text>
</view>
</picker>
</view>
</view>
<view class="invoice-section" v-if="formData.invoice_type == 1">
<view class="section-title">{{ $lang('mailInfo.title') }}</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('mailInfo.receiver') }}
</view>
<input
class="form-input"
type="text"
v-model="formData.receiver_name"
:placeholder="$lang('mailInfo.receiverPlaceholder')"
/>
</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('mailInfo.mobile') }}
</view>
<input
class="form-input"
type="number"
v-model="formData.receiver_mobile"
:placeholder="$lang('mailInfo.mobilePlaceholder')"
/>
</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('mailInfo.address') }}
</view>
<pick-regions :level="3" @finish="onRegionFinish">
<view class="picker-value">
<text v-if="formData.region">{{ formData.region }}</text>
<text class="placeholder" v-else>{{ $lang('mailInfo.regionPlaceholder') }}</text>
<text class="iconfont icon-youjiantou"></text>
</view>
</pick-regions>
</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('mailInfo.detailAddress') }}
</view>
<input
class="form-input"
type="text"
v-model="formData.address"
:placeholder="$lang('mailInfo.detailAddressPlaceholder')"
/>
</view>
</view>
<view class="invoice-section" v-if="formData.invoice_type == 2">
<view class="section-title">{{ $lang('emailInfo.title') }}</view>
<view class="form-item">
<view class="form-label">
<text class="required">*</text>
{{ $lang('emailInfo.receiverEmail') }}
</view>
<input
class="form-input"
type="text"
v-model="formData.invoice_email"
:placeholder="$lang('emailInfo.emailPlaceholder')"
/>
</view>
</view>
<view class="invoice-fee" v-if="invoiceData && invoiceData.invoice_money > 0">
<view class="fee-item">
<text class="fee-label">{{ $lang('fee.taxFee') }}</text>
<text class="fee-value">¥{{ invoiceData.invoice_money }}</text>
</view>
<view class="fee-item" v-if="formData.invoice_type == 1 && invoiceData.invoice_delivery_money > 0">
<text class="fee-label">{{ $lang('fee.deliveryFee') }}</text>
<text class="fee-value">¥{{ invoiceData.invoice_delivery_money }}</text>
</view>
</view>
<view class="invoice-footer">
<view class="footer-tip">{{ $lang('footer.tip', { days: 3 }) }}</view>
<view class="submit-btn" @click="submitInvoice">{{ $lang('footer.submit') }}</view>
</view>
<loading-cover ref="loadingCover"></loading-cover>
</view>
</template>
<script>
export default {
data() {
return {
orderId: 0,
formData: {
invoice_type: 2,
invoice_title_type: 1,
invoice_title: '',
taxpayer_id: '',
invoice_content: '',
receiver_name: '',
receiver_mobile: '',
region: '',
region_id: '',
address: '',
invoice_email: ''
},
invoiceContentList: [],
invoiceData: null
};
},
onLoad(options) {
if (options.order_id) {
this.orderId = options.order_id;
}
this.getInvoiceConfig();
},
methods: {
selectType(type) {
this.formData.invoice_type = type;
},
selectTitleType(type) {
this.formData.invoice_title_type = type;
this.formData.invoice_title = '';
this.formData.taxpayer_id = '';
},
onContentChange(e) {
const index = e.detail.value;
if (this.invoiceContentList[index]) {
this.formData.invoice_content = this.invoiceContentList[index].name;
}
},
onRegionFinish(region) {
this.formData.region = region.name;
this.formData.region_id = region.id;
},
getInvoiceConfig() {
this.$api.sendRequest({
url: '/api/orderinvoice/config',
data: {
order_id: this.orderId
},
success: res => {
if (res.code == 0 && res.data) {
this.invoiceData = res.data;
this.invoiceContentList = res.data.content_list || [];
if (res.data.invoice_info) {
Object.assign(this.formData, res.data.invoice_info);
}
} else {
this.$util.showToast({
title: res.message || this.$lang('api.getConfigFailed')
});
}
}
});
},
validateForm() {
if (!this.formData.invoice_title) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterTitle')
});
return false;
}
if (this.formData.invoice_title_type == 2 && !this.formData.taxpayer_id) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterTaxpayerId')
});
return false;
}
if (!this.formData.invoice_content) {
this.$util.showToast({
title: this.$lang('validation.pleaseSelectContent')
});
return false;
}
if (this.formData.invoice_type == 1) {
if (!this.formData.receiver_name) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterReceiver')
});
return false;
}
if (!this.formData.receiver_mobile) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterMobile')
});
return false;
}
if (!this.formData.region_id) {
this.$util.showToast({
title: this.$lang('validation.pleaseSelectRegion')
});
return false;
}
if (!this.formData.address) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterAddress')
});
return false;
}
}
if (this.formData.invoice_type == 2) {
if (!this.formData.invoice_email) {
this.$util.showToast({
title: this.$lang('validation.pleaseEnterEmail')
});
return false;
}
const emailReg = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
if (!emailReg.test(this.formData.invoice_email)) {
this.$util.showToast({
title: this.$lang('validation.invalidEmail')
});
return false;
}
}
return true;
},
submitInvoice() {
if (!this.validateForm()) return;
this.$refs.loadingCover.show();
this.$api.sendRequest({
url: '/api/orderinvoice/submit',
data: {
order_id: this.orderId,
...this.formData
},
success: res => {
this.$refs.loadingCover.hide();
if (res.code >= 0) {
this.$util.showToast({
title: this.$lang('api.submitSuccess')
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
this.$util.showToast({
title: res.message || this.$lang('api.submitFailed')
});
}
},
fail: () => {
this.$refs.loadingCover.hide();
this.$util.showToast({
title: this.$lang('api.networkError')
});
}
});
}
}
};
</script>
<style lang="scss" scoped>
.invoice-container {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 200rpx;
}
.invoice-header {
background: #fff;
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
.header-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.header-desc {
font-size: 26rpx;
color: #999;
}
}
.invoice-section {
background: #fff;
margin-bottom: 20rpx;
padding: 30rpx;
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
}
.invoice-type-list {
display: flex;
gap: 20rpx;
.type-item {
flex: 1;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 30rpx 20rpx;
text-align: center;
transition: all 0.3s;
&.active {
border-color: #ff6a3d;
background: #fff5f0;
}
.type-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
color: #ff6a3d;
}
.type-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 8rpx;
}
.type-desc {
font-size: 22rpx;
color: #999;
}
}
}
.title-type-switch {
display: flex;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
margin-bottom: 30rpx;
overflow: hidden;
.switch-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 28rpx;
color: #666;
&.active {
background: #ff6a3d;
color: #fff;
}
}
}
.form-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.form-label {
width: 200rpx;
font-size: 28rpx;
color: #333;
flex-shrink: 0;
.required {
color: #ff4d4f;
margin-right: 4rpx;
}
}
.form-input {
flex: 1;
font-size: 28rpx;
color: #333;
text-align: right;
}
.picker-value {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 28rpx;
color: #333;
.placeholder {
color: #ccc;
}
.icon-youjiantou {
font-size: 24rpx;
color: #ccc;
margin-left: 10rpx;
}
}
}
.invoice-fee {
background: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
.fee-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 0;
.fee-label {
font-size: 28rpx;
color: #666;
}
.fee-value {
font-size: 28rpx;
color: #ff4d4f;
font-weight: bold;
}
}
}
.invoice-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.footer-tip {
font-size: 22rpx;
color: #999;
text-align: center;
margin-bottom: 20rpx;
}
.submit-btn {
background: linear-gradient(90deg, #ff8a3d, #ff6a3d);
color: #fff;
font-size: 32rpx;
text-align: center;
padding: 24rpx 0;
border-radius: 40rpx;
font-weight: bold;
}
}
</style>

View File

@@ -34,11 +34,11 @@
<view class="company-logo"><image :src="$util.img(packageItem.express_company_image)" show-menu-by-longpress></image></view>
<view class="info">
<view class="company">
<text>承运公司: {{ packageItem.express_company_name }}</text>
<text>{{ $lang('order.logistics.expressCompany') }}{{ packageItem.express_company_name }}</text>
</view>
<view class="no">
<text>
运单号:
{{ $lang('order.logistics.trackingNo') }}
<text class="color-tip">{{ packageItem.delivery_no }}</text>
</text>
<text class="iconfont icon-fuzhi" @click="copyDeliveryNo(packageItem.delivery_no)"></text>
@@ -124,7 +124,7 @@ export default {
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
} else {
this.$util.showToast({
title: '未获取到订单信息!'
title: this.$lang('order.logistics.orderInfoNotFound')
});
setTimeout(() => {
this.$util.redirectTo('/pages_order/list');

View File

@@ -7,8 +7,8 @@ export default {
*/
cancleRefund(order_goods_id, callback) {
uni.showModal({
content: '撤销之后本次申请将会关闭,如后续仍有问题可再次发起申请。',
cancelText: '暂不撤销',
content: this.$lang('cancelRefund.content'),
cancelText: this.$lang('cancelRefund.cancelText'),
cancelColor: '#898989',
success: res => {
if (res.confirm) {

View File

@@ -14,8 +14,8 @@
</view>
<block v-if="g0>0">
<view class="foot">
<view @tap="generateImage" class="item" style="margin-right:30rpx;">{{ $lang('exportImage') }}</view>
<view @tap="generatePdf" class="item">{{ $lang('exportPdf') }}</view>
<view @tap="generateImage" class="item" style="margin-right:30rpx;">{{ $lang('seal.medium.detail.exportImage') }}</view>
<view @tap="generatePdf" class="item">{{ $lang('seal.medium.detail.exportPdf') }}</view>
</view>
</block>
</view>
@@ -39,7 +39,7 @@ export default {
}
},
onLoad(options) {
this.title = this.$lang('title');
this.title = this.$lang('seal.medium.detail.title');
if (options.keyword) {
this.keyword = options.keyword;
}
@@ -51,7 +51,7 @@ export default {
methods: {
generatePdf() {
const url = this.$util.img(this.info.files_url);
uni.showLoading({ title: this.$lang('loading') });
uni.showLoading({ title: this.$lang('seal.medium.detail.loading') });
try {
uni.downloadFile({
url: url,
@@ -71,7 +71,7 @@ export default {
},
complete: (res) => {
if (res.statusCode === 404) {
uni.showToast({ title: this.$lang('fileNotExist'), icon: 'none' });
uni.showToast({ title: this.$lang('seal.medium.detail.fileNotExist'), icon: 'none' });
}
}
});
@@ -81,7 +81,7 @@ export default {
},
generateImage() {
const url = this.$util.img(this.info.img_url);
uni.showLoading({ title: this.$lang('loading') });
uni.showLoading({ title: this.$lang('seal.medium.detail.loading') });
uni.downloadFile({
url: url,
success: (res) => {
@@ -90,21 +90,21 @@ export default {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
this.$util.showToast({ title: this.$lang('saveSuccess') });
this.$util.showToast({ title: this.$lang('seal.medium.detail.saveSuccess') });
},
fail: (err) => {
if (err.errMsg !== 'saveImageToPhotosAlbum:fail auth deny' && err.errMsg !== 'saveImageToPhotosAlbum:fail:auth denied') {
this.$util.showToast({ title: this.$lang('saveFailed') });
this.$util.showToast({ title: this.$lang('seal.medium.detail.saveFailed') });
}
}
});
} else {
this.$util.showToast({ title: this.$lang('downloadFailed') });
this.$util.showToast({ title: this.$lang('seal.medium.detail.downloadFailed') });
}
},
fail: (err) => {
uni.hideLoading();
this.$util.showToast({ title: this.$lang('downloadFailed') });
this.$util.showToast({ title: this.$lang('seal.medium.detail.downloadFailed') });
}
});
},