refactor: 完成多语言国际化全量替换与补充
1. 新增大量中英文语言包文件,覆盖自定义组件、页面、工具类的文案 2. 替换硬编码文本为多语言调用,统一全局文案管理 3. 优化语言包结构,拆分公共模块与业务模块 4. 修复部分语言加载与国际化适配问题
This commit is contained in:
@@ -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 });
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
380
pages_tool/member/bind_mobile.vue
Normal file
380
pages_tool/member/bind_mobile.vue
Normal 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>
|
||||
@@ -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')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
314
pages_tool/member/card_log.vue
Normal file
314
pages_tool/member/card_log.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
518
pages_tool/order/invoice.vue
Normal file
518
pages_tool/order/invoice.vue
Normal 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>
|
||||
@@ -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');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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') });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user