本次提交对全项目的国际化调用进行了规范化重构:
1. 为中文/英文语言包新增了avatar和gender的别名映射,兼容旧有headImg/sex写法
2. 修正了lang.js中的注释路径错误
3. 将所有页面和组件内的$lang调用从短路径改为带业务模块前缀的完整命名空间路径,例如将$lang('title')改为$lang('article.list.title')
4. 优化了i18n-lazy.js的注释说明,补充了正确的key命名格式示例
5. 统一了分销、订单、会员等模块的国际化key层级,确保所有语言调用路径一致
442 lines
9.8 KiB
Vue
442 lines
9.8 KiB
Vue
<template>
|
|
<view class="find" :style="themeColor">
|
|
<view class="iconfont icon-close" @click="navigateBack()"></view>
|
|
<view class="header-wrap">
|
|
<block v-if="stepShow == 0">
|
|
<view class="title">{{ $lang('login.find.step0Title') }}</view>
|
|
<view><text class="color-tip">{{ $lang('login.find.step0Tip') }}</text></view>
|
|
</block>
|
|
|
|
<block v-if="stepShow == 1">
|
|
<view class="title">{{ $lang('login.find.step1Title') }}</view>
|
|
<view>
|
|
<text class="color-tip">{{ $lang('login.find.step1Tip', { mobile: formData.mobile }) }}</text>
|
|
</view>
|
|
</block>
|
|
|
|
<block v-if="stepShow == 2">
|
|
<view class="title">{{ $lang('login.find.step2Title') }}</view>
|
|
<view><text class="color-tip">{{ $lang('login.find.step2Tip') }}</text></view>
|
|
</block>
|
|
</view>
|
|
<view class="find-form">
|
|
<!-- 输入手机号和验证码 -->
|
|
<block v-if="stepShow == 0">
|
|
<view class="form-input">
|
|
<input
|
|
class="uni-input"
|
|
placeholder-class="placeholder-class"
|
|
type="text"
|
|
maxlength="17"
|
|
v-model="formData.mobile"
|
|
:placeholder="$lang('login.find.accountPlaceholder')"
|
|
/>
|
|
</view>
|
|
|
|
<view class="form-input align-type">
|
|
<input
|
|
class="uni-input info-content"
|
|
placeholder-class="placeholder-class"
|
|
type="number"
|
|
maxlength="4"
|
|
:placeholder="$lang('login.find.captchaPlaceholder')"
|
|
v-model="formData.captcha"
|
|
/>
|
|
<image :src="captcha.img" class="captcha" @click="getCaptcha" show-menu-by-longpress></image>
|
|
</view>
|
|
|
|
<button type="primary" class="find-btn" @click="nextStep()">{{ $lang('login.find.next') }}</button>
|
|
</block>
|
|
|
|
<!-- 输入动态码 -->
|
|
<block v-if="stepShow == 1">
|
|
<myp-one :maxlength="4" @input="input" ref="input" :auto-focus="true"></myp-one>
|
|
<button type="primary" class="find-btn" :disabled="isSend" @click="sendDynaCode">{{ codeText }}</button>
|
|
</block>
|
|
<!-- 输入新密码 -->
|
|
<block v-if="stepShow == 2">
|
|
<view class="form-input">
|
|
<input
|
|
class="uni-input"
|
|
placeholder-class="placeholder-class"
|
|
type="text"
|
|
maxlength="30"
|
|
password="true"
|
|
:placeholder="$lang('login.login.passwordPlaceholder')"
|
|
v-model="formData.password"
|
|
/>
|
|
</view>
|
|
|
|
<view class="form-input">
|
|
<input
|
|
class="uni-input"
|
|
placeholder-class="placeholder-class"
|
|
type="text"
|
|
maxlength="30"
|
|
password="true"
|
|
:placeholder="$lang('login.login.rePasswordPlaceholder')"
|
|
v-model="formData.rePassword"
|
|
/>
|
|
</view>
|
|
|
|
<button type="primary" class="find-btn" @click="save">{{ $lang('components.ns-login.save') }}</button>
|
|
</block>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import validate from 'common/js/validate.js';
|
|
|
|
import mypOne from '../_components/myp-one/myp-one.vue';
|
|
|
|
export default {
|
|
components: {
|
|
mypOne
|
|
},
|
|
data() {
|
|
return {
|
|
findMode: 'mobile',
|
|
codeText: '',
|
|
seconds: 120,
|
|
timer: null,
|
|
formData: {
|
|
mobile: '',
|
|
password: '',
|
|
rePassword: '',
|
|
dynacode: '',
|
|
captcha: ''
|
|
},
|
|
stepShow: 0,
|
|
isSend: false,
|
|
captcha: {
|
|
id: '',
|
|
img: ''
|
|
},
|
|
registerConfig: {}
|
|
};
|
|
},
|
|
onLoad() {
|
|
this.codeText = this.$lang('login.find.resendCode');
|
|
this.getCaptcha();
|
|
},
|
|
onShow() {
|
|
this.getRegisterConfig();
|
|
},
|
|
methods: {
|
|
input(val) {
|
|
if (val.length == 4) {
|
|
this.formData.dynacode = val;
|
|
this.stepShow += 1;
|
|
}
|
|
},
|
|
// 导航跳转
|
|
navigateBack() {
|
|
if (this.stepShow > 0) {
|
|
this.stepShow -= 1;
|
|
} else {
|
|
this.$util.redirectTo(this.$util.LOGIN_PAGE_URL, '', 'redirectTo');
|
|
}
|
|
},
|
|
// 下一步
|
|
async nextStep() {
|
|
let step0Rule = [
|
|
{
|
|
name: 'mobile',
|
|
checkType: 'phoneno',
|
|
errorMsg: this.$lang('login.find.mobileInvalid')
|
|
},
|
|
{
|
|
name: 'captcha',
|
|
checkType: 'required',
|
|
errorMsg: this.$lang('login.find.captchaPlaceholder')
|
|
}
|
|
], //手机验证
|
|
step0CheckRes;
|
|
|
|
step0CheckRes = validate.check(this.formData, step0Rule);
|
|
|
|
if (step0CheckRes) {
|
|
this.findMode = 'mobile';
|
|
let res = await this.$api.sendRequest({
|
|
url: '/api/member/checkmobile',
|
|
data: {
|
|
mobile: this.formData.mobile
|
|
},
|
|
async: false
|
|
});
|
|
if (res.code == 0) {
|
|
this.$util.showToast({
|
|
title: this.$lang('login.find.mobileNotRegistered')
|
|
});
|
|
return false;
|
|
}
|
|
} else {
|
|
this.$util.showToast({
|
|
title: validate.error
|
|
});
|
|
return false;
|
|
}
|
|
this.sendDynaCode();
|
|
},
|
|
// 注册表单验证
|
|
vertify() {
|
|
let regConfig = this.registerConfig;
|
|
let rule = [
|
|
{
|
|
name: 'password',
|
|
checkType: 'required',
|
|
errorMsg: this.$lang('login.find.passwordRequired')
|
|
}
|
|
];
|
|
if (regConfig.pwd_len > 0) {
|
|
rule.push({
|
|
name: 'password',
|
|
checkType: 'lengthMin',
|
|
checkRule: regConfig.pwd_len,
|
|
errorMsg: this.$lang('login.find.passwordTooShort', { length: regConfig.pwd_len })
|
|
});
|
|
}
|
|
if (regConfig.pwd_complexity != '') {
|
|
let passwordErrorMsg = this.$lang('login.find.passwordComplexityPrefix'),
|
|
reg = '';
|
|
if (regConfig.pwd_complexity.indexOf('number') != -1) {
|
|
reg += '(?=.*?[0-9])';
|
|
passwordErrorMsg += this.$lang('login.find.complexityNumber');
|
|
}
|
|
if (regConfig.pwd_complexity.indexOf('letter') != -1) {
|
|
reg += '(?=.*?[a-z])';
|
|
passwordErrorMsg += this.$lang('login.find.complexityLowercase');
|
|
}
|
|
if (regConfig.pwd_complexity.indexOf('upper_case') != -1) {
|
|
reg += '(?=.*?[A-Z])';
|
|
passwordErrorMsg += this.$lang('login.find.complexityUppercase');
|
|
}
|
|
if (regConfig.pwd_complexity.indexOf('symbol') != -1) {
|
|
reg += '(?=.*?[#?!@$%^&*-])';
|
|
passwordErrorMsg += this.$lang('login.find.complexitySymbol');
|
|
}
|
|
rule.push({
|
|
name: 'password',
|
|
checkType: 'reg',
|
|
checkRule: reg,
|
|
errorMsg: passwordErrorMsg
|
|
});
|
|
}
|
|
|
|
var checkRes = validate.check(this.formData, rule);
|
|
if (checkRes) {
|
|
if (this.formData.password != this.formData.rePassword) {
|
|
this.$util.showToast({
|
|
title: this.$lang('login.find.passwordMismatch')
|
|
});
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
this.$util.showToast({
|
|
title: validate.error
|
|
});
|
|
return false;
|
|
}
|
|
},
|
|
// 获取验证码
|
|
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, '');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
// 发送动态验证码
|
|
async sendDynaCode() {
|
|
if (this.formData.captcha.length == 0) {
|
|
this.$util.showToast({
|
|
title: this.$lang('login.find.captchaPlaceholder')
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (this.isSend) return;
|
|
this.isSend = true;
|
|
|
|
var url,
|
|
data = {
|
|
captcha_id: this.captcha.id,
|
|
captcha_code: this.formData.captcha
|
|
};
|
|
data[this.findMode] = this.formData.mobile;
|
|
url = '/api/findpassword/mobilecode';
|
|
|
|
this.$api.sendRequest({
|
|
url: url,
|
|
data: data,
|
|
success: res => {
|
|
let data = res.data;
|
|
if (data.key) {
|
|
if (this.seconds == 120 && this.timer == null) {
|
|
this.timer = setInterval(() => {
|
|
this.seconds--;
|
|
this.codeText = this.$lang('login.find.resendCountdown', { seconds: this.seconds });
|
|
}, 1000);
|
|
}
|
|
uni.setStorageSync('forgot_password_token', data.key);
|
|
this.stepShow += 1;
|
|
} else {
|
|
this.$util.showToast({
|
|
title: res.message
|
|
});
|
|
this.isSend = false;
|
|
this.getCaptcha();
|
|
}
|
|
},
|
|
fail: res => {
|
|
this.isSend = false;
|
|
this.getCaptcha();
|
|
}
|
|
});
|
|
},
|
|
save() {
|
|
if (this.vertify()) {
|
|
var url,
|
|
data = {
|
|
code: this.formData.dynacode,
|
|
key: uni.getStorageSync('forgot_password_token'),
|
|
password: this.formData.password
|
|
};
|
|
data[this.findMode] = this.formData.mobile;
|
|
url = '/api/findpassword/mobile';
|
|
this.$api.sendRequest({
|
|
url: url,
|
|
data: data,
|
|
success: res => {
|
|
this.$util.showToast({
|
|
title: res.message
|
|
});
|
|
if (res.code == 0) {
|
|
setTimeout(() => {
|
|
uni.removeStorage({
|
|
key: 'forgot_password_token'
|
|
});
|
|
this.$util.redirectTo(this.$util.LOGIN_PAGE_URL, {}, 'redirectTo');
|
|
}, 1000);
|
|
} else {
|
|
this.stepShow -= 1;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* 获取注册配置
|
|
*/
|
|
getRegisterConfig() {
|
|
this.$api.sendRequest({
|
|
url: '/api/register/config',
|
|
success: res => {
|
|
if (res.code >= 0) {
|
|
this.registerConfig = res.data.value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
watch: {
|
|
seconds(value) {
|
|
if (value == 0) {
|
|
this.seconds = 120;
|
|
this.codeText = this.$lang('login.find.resendCode');
|
|
this.isSend = false;
|
|
clearInterval(this.timer);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
page {
|
|
background: #ffffff !important;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.captcha {
|
|
width: 170rpx;
|
|
height: 50rpx;
|
|
}
|
|
|
|
.find-form {
|
|
padding: 100rpx 80rpx 0;
|
|
|
|
.form-input {
|
|
margin-top: 60rpx;
|
|
height: 60rpx;
|
|
border-bottom: 2rpx solid $color-line;
|
|
|
|
input {
|
|
padding: 0;
|
|
font-size: $font-size-base;
|
|
}
|
|
}
|
|
|
|
.find-btn {
|
|
margin: 374rpx 0 0;
|
|
border-radius: $border-radius;
|
|
color: #fff;
|
|
|
|
&[disabled] {
|
|
background-color: #f7f7f7 !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
.forget-section {
|
|
display: flex;
|
|
flex-direction: row-reverse;
|
|
justify-content: space-between;
|
|
margin-top: 10rpx;
|
|
height: 70rpx;
|
|
line-height: 70rpx;
|
|
}
|
|
|
|
.align-type {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.header-wrap {
|
|
width: 80%;
|
|
height: 100%;
|
|
margin: calc(120rpx + 88rpx) auto 0;
|
|
background-repeat: no-repeat;
|
|
background-size: contain;
|
|
background-position: bottom;
|
|
position: relative;
|
|
|
|
.title {
|
|
font-size: 50rpx;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
|
|
.icon-close {
|
|
font-size: 52rpx;
|
|
position: fixed;
|
|
left: 24rpx;
|
|
top: 72rpx;
|
|
z-index: 9;
|
|
color: #000;
|
|
}
|
|
|
|
.placeholder-class {
|
|
color: #bfbfbf;
|
|
}
|
|
</style>
|