Files
lucky_shop/components/ns-login/ns-login.vue
ZF sun 2e535b9d93 refactor(member pages): unify onShow login redirect logic
1. extract common onShow login check to dedicated method redirectTologinOnShowLifecycle
2. add fallback callback for login failure to navigate back on wechat mini program
3. update all page login redirect calls to use the new unified method
4. add debug logs for redirect and auth flow tracing
2026-06-27 18:25:53 +08:00

892 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<!-- 完善会员资料 -->
<view @touchmove.prevent.stop class="complete-info-popup">
<uni-popup ref="completeInfoPopup" type="center" :maskClick="false">
<view class="complete-info-wrap">
<!-- #ifdef H5 -->
<template v-if="forceBindingMobileControl">
<view class="head">
<text class="title">{{ $lang('components.ns-login.bindMobileTitle') }}</text>
<text class="color-tip tips">{{ $lang('components.ns-login.bindMobileTip') }}</text>
<text class="iconfont icon-close color-tip" @tap="cancelCompleteInfo"></text>
</view>
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.mobileLabel') }}</text>
<input type="number" :placeholder="$lang('components.ns-login.mobilePlaceholder')" v-model="formData.mobile" maxlength="11" />
</view>
<view class="item-wrap" v-if="isOpenCaptcha">
<text class="label">{{ $lang('components.ns-login.captchaLabel') }}</text>
<input type="number" :placeholder="$lang('components.ns-login.captchaPlaceholder')" v-model="formData.vercode" maxlength="4" />
<image :src="captcha.img" class="captcha" @tap="getCaptcha" show-menu-by-longpress></image>
</view>
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.dynacodeLabel') }}</text>
<input type="number" :placeholder="$lang('components.ns-login.dynacodePlaceholder')" v-model="formData.dynacode" maxlength="4" />
<view class="send color-base-text" @tap="sendMobileCode">{{ dynacodeData.codeText }}
</view>
</view>
</template>
<button type="default" class="save-btn" @tap="saveH5" :disabled="isDisabled">{{ $lang('components.ns-login.save') }}</button>
<!-- #endif -->
<!-- #ifdef MP -->
<view class="head">
<text class="title">{{ $lang('components.ns-login.welcomeTitle', { siteName: site_name }) }}</text>
<text class="color-tip tips">
<template>{{ forceBindingMobileControl ? $lang('components.ns-login.loginWithMobile') : $lang('components.ns-login.registerLogin') }}</template>
{{ $lang('components.ns-login.getServicesTip') }}
</text>
<text class="iconfont icon-close color-tip" @tap="cancelCompleteInfo"></text>
</view>
<!-- 协议勾选 -->
<view style="display: flex;">
<view class="iconfont"
:class="isAgree ? 'icon-fuxuankuang1 color-base-text' : 'icon-fuxuankuang2'"
@tap="isAgree = !isAgree"></view>
<view style="text-align: left; margin-left: 10rpx; padding-top: 2rpx; font-size: 28rpx;">
{{ $lang('components.ns-login.agree') }}
<text style="color: #4395ff;"
@tap="tourl('/pages_tool/agreement/contenr?type=0')">{{ $lang('components.ns-login.privacyPolicy') }}</text>
<text style="color: #4395ff;"
@tap="tourl('/pages_tool/agreement/contenr?type=1')">{{ $lang('components.ns-login.userServiceAgreement') }}</text>
</view>
</view>
<!-- 手机号授权按钮 -->
<block v-if="isAgree">
<button class="auth-login save-btn border-0" style="border: none;" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber">
<text style="color: #fff; font-size: 30rpx;">{{ $lang('components.ns-login.agreeAndAuthorizeMobile') }}</text>
</button>
</block>
<block v-else>
<button class="auth-login save-btn border-0" style="border: none;" @tap="authno">
<text style="color: #fff; font-size: 30rpx;">{{ $lang('components.ns-login.authorizeMobile') }}</text>
</button>
</block>
<view @tap="closeLogin"
style="font-size: 30rpx; color: #888; text-align: center; padding-top: 10rpx;">
{{ $lang('components.ns-login.notNow') }}
</view>
<!-- 昵称和头像模式 -->
<template v-if="enableNickAndAvatar">
<!-- #ifdef MP-WEIXIN -->
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.avatarLabel') }}</text>
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
<text class="iconfont icon-right color-tip"></text>
</button>
</view>
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.nicknameLabel') }}</text>
<input type="nickname" :placeholder="$lang('components.ns-login.nicknamePlaceholder')" v-model="nickName" @blur="blurNickName"
maxlength="50" />
</view>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.avatarLabel') }}</text>
<button open-type="getAuthorize" scope="userInfo" @getAuthorize="aliappGetUserinfo"
:plain="true" class="border-0">
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
<text class="iconfont icon-right color-tip"></text>
</button>
</view>
<view class="item-wrap">
<text class="label">{{ $lang('components.ns-login.nicknameLabel') }}</text>
<input type="nickname" :placeholder="$lang('components.ns-login.nicknamePlaceholder')" v-model="nickName" @blur="blurNickName"
maxlength="50" />
</view>
<!-- #endif -->
<view class="item-wrap" v-if="forceBindingMobileControl">
<text class="label">{{ $lang('components.ns-login.mobileLabel') }}</text>
<button open-type="getPhoneNumber" :plain="true" class="auth-login border-0"
@getphonenumber="getPhoneNumber">
<text class="mobile" v-if="formData.mobile">{{ formData.mobile }}</text>
<text class="color-base-text" v-else>{{ $lang('components.ns-login.getMobile') }}</text>
</button>
</view>
<button type="default" class="save-btn" @tap="saveMp" :disabled="isDisabled">{{ $lang('components.ns-login.save') }}</button>
</template>
<!-- #endif -->
</view>
</uni-popup>
</view>
<!-- 隐私弹窗 -->
<privacy-popup ref="privacyPopup"></privacy-popup>
<register-reward ref="registerReward"></register-reward>
</view>
</template>
<script>
import auth from '@/common/js/auth.js';
import validate from '@/common/js/validate.js';
export default {
mixins: [auth],
name: 'ns-login',
data() {
return {
// 是否开启昵称和头像
enableNickAndAvatar: false,
// 协议勾选
isAgree: false,
url: '',
registerConfig: {},
avatarUrl: '', // 头像预览路径
headImg: '', // 头像上传路径
nickName: '', // 昵称
isSub: false,
// 绑定手机号
isOpenCaptcha: 0, // 前台登录验证码,0:关闭,1:开启
captcha: {
id: '',
img: ''
},
formData: {
key: '',
mobile: '',
vercode: '',
dynacode: ''
},
dynacodeData: {
seconds: 120,
timer: null,
codeText: '',
isSend: false
},
// 小程序获取手机号所需数据
authMobileData: {
iv: '',
encryptedData: ''
},
site_name: ''
};
},
options: {
styleIsolation: 'shared'
},
mounted() { },
watch: {
'dynacodeData.seconds': {
handler(newValue, oldValue) {
if (newValue == 0) {
this.refreshDynacodeData();
}
},
immediate: true,
deep: true
}
},
computed: {
// 控制按钮是否禁用
isDisabled() {
// #ifdef MP-WEIXIN
if (this.nickName.length == 0) return true;
// #endif
// 强制绑定手机号验证
if (this.forceBindingMobileControl) {
if (this.formData.mobile.length == 0) return true;
// #ifdef H5
// 验证码
if (this.isOpenCaptcha == 1 && this.formData.vercode.length == 0) return true;
// 动态码
if (this.formData.dynacode.length == 0) return true;
// #endif
}
return false;
},
forceBindingMobileControl() {
if (this.registerConfig && this.registerConfig.third_party == 1 && this.registerConfig.bind_mobile == 1)
return true;
else return false;
}
},
created() {
const siteInfo = uni.getStorageSync('siteInfo');
if (siteInfo) {
this.site_name = siteInfo.site_name;
}
// 初始化动态码按钮文字
this.dynacodeData.codeText = this.$lang('components.ns-login.getDynacode');
},
methods: {
// 同意协议
authno() {
if (!this.isAgree) {
this.$util.showToast({ title: this.$lang('components.ns-login.pleaseConfirmAgreement') });
}
},
// 关闭登录
closeLogin() {
this.$refs.completeInfoPopup.close();
},
// 获取注册配置
getRegisterConfig(callback = null) {
this.$api.sendRequest({
url: '/api/register/config',
success: res => {
if (res.code >= 0) {
this.registerConfig = res.data.value;
if (callback) callback();
}
}
});
},
// 打开弹窗
open(url = '', mode = 'navigateTo', callback = ()=>{}) {
console.log(`------> ns-login.open`, url);
if (url) this.url = url;
// #ifdef H5 || MP-HARMONY
if (this.$util.isWeChatMiniProgram()) {
// 微信小程序环境,使用微信授权登录
let authData = uni.getStorageSync('authInfo');
if (authData) {
this.authLogin(authData);
} else {
this.getCode(authData => {
this.authLogin(authData, 'authOnlyLogin');
});
}
} else if (this.$util.isWeiXin()) {
// 微信浏览器环境,显示登录页面
this.toLoginH5Page(mode);
} else {
// 其他浏览器环境,显示登录页面
this.toLoginH5Page(mode);
}
return;
// #endif
// #ifdef MP-WEIXIN
this.getCode(authData => {
this.authLogin(authData, 'authOnlyLogin');
}, (err) => {
callback && callback(err);
});
// #endif
},
// 跳转去登录页
toLoginH5Page(mode = 'navigateTo') {
if (this.url) this.$util.redirectTo(this.$util.LOGIN_PAGE_URL, {
back: encodeURIComponent(this.url)
}, mode);
else this.$util.redirectTo(this.$util.LOGIN_PAGE_URL, {}, mode);
},
// 取消完善信息
cancelCompleteInfo() {
if (this.$refs.completeInfoPopup) this.$refs.completeInfoPopup.close();
this.$store.commit('setBottomNavHidden', false); // 显示底部导航
},
// 昵称失焦
blurNickName(e) {
if (e.detail.value) this.nickName = e.detail.value;
},
// 选择头像
onChooseAvatar(e) {
this.avatarUrl = e.detail.avatarUrl;
uni.getFileSystemManager().readFile({
filePath: this.avatarUrl, //选择图片返回的相对路径
encoding: 'base64', //编码格式
success: res => {
let base64 = 'data:image/jpeg;base64,' + res.data; //不加上这串字符,在页面无法显示的哦
this.$api.uploadBase64({
base64,
success: res => {
if (res.code == 0) {
this.headImg = res.data.pic_path;
} else {
this.$util.showToast({
title: res.message
});
}
},
fail: () => {
this.$util.showToast({
title: this.$lang('components.ns-login.uploadFailed')
});
}
})
}
});
},
// 打开完善信息弹窗
openCompleteInfoPop() {
this.getRegisterConfig();
// #ifdef H5
if (!this.storeToken) this.getCaptchaConfig();
// #endif
this.$refs.completeInfoPopup.open(() => {
this.$store.commit('setBottomNavHidden', false); //显示底部导航
});
this.$store.commit('setBottomNavHidden', true); //隐藏底部导航
},
// 获取前台登录验证码开关配置
getCaptchaConfig() {
this.$api.sendRequest({
url: '/api/config/getCaptchaConfig',
success: res => {
if (res.code >= 0) {
this.isOpenCaptcha = res.data.shop_reception_login;
if (this.isOpenCaptcha) this.getCaptcha();
}
}
});
},
// 获取验证码
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, '');
}
}
});
},
// 发送手机动态码
sendMobileCode() {
if (this.dynacodeData.seconds != 120 || this.dynacodeData.isSend) return;
var data = {
mobile: this.formData.mobile,
captcha_id: this.captcha.id,
captcha_code: this.formData.vercode
};
var rule = [{
name: 'mobile',
checkType: 'required',
errorMsg: this.$lang('components.ns-login.pleaseEnterMobile')
}, {
name: 'mobile',
checkType: 'phoneno',
errorMsg: this.$lang('components.ns-login.pleaseEnterCorrectMobile')
}];
if (this.isOpenCaptcha == 1) {
rule.push({
name: 'captcha_code',
checkType: 'required',
errorMsg: this.$lang('components.ns-login.pleaseEnterCaptcha')
});
}
var checkRes = validate.check(data, rule);
if (!checkRes) {
this.$util.showToast({
title: validate.error
});
return;
}
this.dynacodeData.isSend = true;
if (this.dynacodeData.seconds == 120) {
this.dynacodeData.timer = setInterval(() => {
this.dynacodeData.seconds--;
this.dynacodeData.codeText = this.$lang('components.ns-login.resendAfter', { seconds: this.dynacodeData.seconds });
}, 1000);
}
this.$api.sendRequest({
url: '/api/tripartite/mobileCode',
data: data,
success: res => {
if (res.code >= 0) {
this.formData.key = res.data.key;
} else {
this.$util.showToast({
title: res.message
});
this.refreshDynacodeData();
}
},
fail: () => {
this.$util.showToast({
title: this.$lang('components.ns-login.requestFail')
});
this.refreshDynacodeData();
}
});
},
// 刷新动态码数据
refreshDynacodeData() {
this.getCaptcha();
clearInterval(this.dynacodeData.timer);
this.dynacodeData = {
seconds: 120,
timer: null,
codeText: this.$lang('components.ns-login.getDynacode'),
isSend: false
};
},
// 表单验证
verify(data) {
let rule = [{
name: 'mobile',
checkType: 'required',
errorMsg: this.$lang('components.ns-login.pleaseEnterMobile')
}, {
name: 'mobile',
checkType: 'phoneno',
errorMsg: this.$lang('components.ns-login.pleaseEnterCorrectMobile')
}];
if (this.isOpenCaptcha == 1) {
if (this.captcha.id != '') rule.push({
name: 'captcha_code',
checkType: 'required',
errorMsg: this.$lang('components.ns-login.pleaseEnterCaptcha')
});
}
rule.push({
name: 'code',
checkType: 'required',
errorMsg: this.$lang('components.ns-login.pleaseEnterDynacode')
});
var checkRes = validate.check(data, rule);
if (checkRes) {
return true;
} else {
this.$util.showToast({
title: validate.error
});
return false;
}
},
// 微信公众号强制绑定手机号
forceBindMobile() {
let authData = uni.getStorageSync('authInfo');
let data = {
mobile: this.formData.mobile,
key: this.formData.key,
code: this.formData.dynacode
};
if (this.captcha.id != '') {
data.captcha_id = this.captcha.id;
data.captcha_code = this.formData.vercode;
}
if (authData) Object.assign(data, authData);
if (authData.avatarUrl) data.headimg = authData.avatarUrl;
if (authData.nickName) data.nickname = authData.nickName;
if (uni.getStorageSync('source_member')) data.source_member = uni.getStorageSync('source_member');
if (this.isSub) return;
this.isSub = true;
this.$api.sendRequest({
url: '/api/tripartite/mobile',
data,
success: res => {
if (res.code >= 0) {
this.$store.commit('setToken', res.data.token);
this.getMemberInfo();
this.$store.dispatch('getCartNumber');
this.$refs.completeInfoPopup.close();
this.$store.commit('setBottomNavHidden', false); // 显示底部导航
if (res.data.is_register) this.$refs.registerReward.open(this.url);
} else {
this.isSub = false;
this.getCaptcha();
this.$util.showToast({
title: res.message
});
}
},
fail: res => {
this.isSub = false;
this.getCaptcha();
}
});
},
// 微信小程序获取手机号
getPhoneNumber(e) {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
const authInfo = uni.getStorageSync('authInfo');
if (authInfo) {
Object.assign(this.authMobileData, authInfo, e.detail);
}
if (uni.getStorageSync('source_member')) {
this.authMobileData.source_member = uni.getStorageSync('source_member');
}
this.$api.sendRequest({
url: '/api/tripartite/getPhoneNumber',
data: this.authMobileData,
success: res => {
if (res.code >= 0) {
this.formData.mobile = res.data.mobile;
if (!this.enableNickAndAvatar) { // 不开启昵称和头像绑定,直接保存手机号
this.saveMp();
}
} else {
this.formData.mobile = '';
this.$util.showToast({
title: res.message
});
}
}
});
} else {
this.$util.showToast({
title: e.detail.errMsg
})
}
},
// 微信小程序强制绑定手机号
bindMobile() {
let data = this.authMobileData;
let authData = uni.getStorageSync('authInfo');
if (authData) Object.assign(data, authData);
if (authData.avatarUrl) data.headimg = authData.avatarUrl;
if (authData.nickName) data.nickname = authData.nickName;
this.$api.sendRequest({
url: '/api/tripartite/mobileauth',
data,
success: res => {
if (res.code >= 0) {
this.$store.commit('setToken', res.data.token);
this.getMemberInfo();
this.$store.dispatch('getCartNumber');
this.cancelCompleteInfo();
if (res.data.is_register) this.$refs.registerReward.open(this.url);
} else {
this.$util.showToast({
title: res.message
});
}
},
fail: res => {
this.$util.showToast({
title: this.$lang('components.ns-login.requestFail')
});
}
});
},
// 授权登录
authLogin(data, type = 'authLogin') {
uni.showLoading({
title: this.$lang('components.ns-login.loggingIn')
});
uni.setStorageSync('authInfo', data);
if (uni.getStorageSync('source_member')) data.source_member = uni.getStorageSync('source_member');
this.$api.sendRequest({
url: type == 'authLogin' ? '/api/login/auth' : '/api/login/authonlylogin',
data,
success: res => {
if (res.code >= 0) {
this.$store.commit('setToken', res.data.token);
this.getMemberInfo();
this.$store.dispatch('getCartNumber');
if (res.data.is_register) this.$refs.registerReward.open(this
.url);
this.cancelCompleteInfo();
setTimeout(() => {
uni.hideLoading();
}, 1000);
} else if (res.data == 'MEMBER_NOT_EXIST') {
this.getRegisterConfig(() => {
uni.hideLoading();
if (this.registerConfig.third_party == 1 && this.registerConfig.bind_mobile == 1) {
this.openCompleteInfoPop();
} else if (this.registerConfig.third_party == 0) {
this.toLoginH5Page();
} else {
this.openCompleteInfoPop();
}
});
} else {
uni.hideLoading();
this.$util.showToast({
title: res.message
});
}
},
fail: () => {
uni.hideLoading();
this.$util.showToast({
title: this.$lang('components.ns-login.loginFailed')
});
}
});
},
// H5保存:微信公众号,强制绑定手机号,验证
saveH5() {
if (this.$util.isWeiXin() && this.forceBindingMobileControl) {
let data = {
mobile: this.formData.mobile,
key: this.formData.key,
code: this.formData.dynacode
};
if (this.captcha.id != '') {
data.captcha_id = this.captcha.id;
data.captcha_code = this.formData.vercode;
}
if (!this.verify(data)) return;
}
this.forceBindMobile();
},
// 微信小程序保存数据
saveMp() {
if (this.formData.mobile === '') {
this.$util.showToast({ title: this.$lang('components.ns-login.pleaseAuthorizeMobile') });
return;
}
if (this.nickName.length == 0 && this.enableNickAndAvatar) {
this.$util.showToast({ title: this.$lang('components.ns-login.pleaseEnterNickname') });
return;
}
let authInfo = uni.getStorageSync('authInfo');
if (authInfo) {
Object.assign(authInfo, {
nickName: this.nickName,
avatarUrl: this.headImg
});
uni.setStorageSync('authInfo', authInfo);
}
if (this.forceBindingMobileControl) {
this.bindMobile();
} else {
this.authLogin(authInfo);
}
},
// #ifdef MP-ALIPAY
aliappGetUserinfo() {
my.getOpenUserInfo({
success: (res) => {
let userInfo = JSON.parse(res.response).response
if (userInfo.code && userInfo.code == '10000') {
if (userInfo.avatar) {
this.avatarUrl = userInfo.avatar;
this.$api.pullImage({
path: this.avatarUrl,
success: res => {
if (res.code == 0) {
this.headImg = res.data.pic_path;
} else {
this.$util.showToast({
title: res.message
});
}
},
fail: () => {
this.$util.showToast({
title: this.$lang('components.ns-login.avatarPullFailed')
});
}
})
}
this.nickName = userInfo.nickName
} else {
this.$util.showToast({
title: userInfo.subMsg
})
}
},
fail: (err) => {
this.$util.showToast({
title: err.subMsg
})
}
});
},
// #endif
// 获取会员信息
getMemberInfo() {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 0) {
// 登录成功,存储会员信息
this.$store.commit('setMemberInfo', res.data);
}
}
});
},
// 跳转页面
tourl(url) {
this.$util.redirectTo(url);
}
}
};
</script>
<style lang="scss" scoped>
.complete-info-popup {
.complete-info-wrap {
background: #fff;
padding: 50rpx 40rpx 40rpx;
.head {
position: relative;
padding-bottom: 20rpx;
text-align: center;
.title {
font-size: 30rpx;
display: block;
}
.tips {
font-size: 28rpx;
display: block;
}
.iconfont {
position: absolute;
right: 0;
top: -30rpx;
display: inline-block;
width: 56rpx;
height: 56rpx;
line-height: 56rpx;
text-align: right;
font-size: 32rpx;
font-weight: 700;
}
}
.item-wrap {
border-bottom: 2rpx solid #eee;
display: flex;
align-items: center;
padding: 16rpx 0;
.label {
font-size: 32rpx;
margin-right: 40rpx;
width: 100rpx;
}
button {
background: transparent;
margin: 0;
padding: 0;
border-radius: 0;
flex: 1;
text-align: left;
display: flex;
align-items: center;
font-size: 32rpx;
border: none;
image {
width: 100rpx;
height: 100rpx;
border-radius: 10rpx;
overflow: hidden;
}
.iconfont {
flex: 1;
text-align: right;
font-size: 24rpx;
}
}
input {
flex: 1;
height: 80rpx;
box-sizing: border-box;
font-size: 32rpx;
}
.send {
border: 2rpx solid var(--base-color);
height: 60rpx;
line-height: 60rpx;
border-radius: 60rpx;
font-size: 24rpx;
text-align: center;
padding: 0 40rpx;
}
.captcha {
height: 80rpx;
width: 200rpx;
}
.auth-login {
width: calc(100% - 100rpx);
height: 80rpx;
line-height: 80rpx;
border-radius: 80rpx;
}
}
.save-btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background-color: #07c160;
color: #fff;
margin: 40rpx auto 20rpx;
border-radius: 50rpx;
}
}
&.reward-popup .uni-popup__wrapper-box {
background: none !important;
max-width: unset !important;
max-height: unset !important;
overflow: unset !important;
}
&.uni-popup__wrapper.bottom,
&.uni-popup__wrapper.bottom .uni-popup__wrapper-box {
border-top-left-radius: 30rpx !important;
border-top-right-radius: 30rpx !important;
}
}
</style>