Files
sweekar-app/components/login.vue
T
2026-01-04 17:53:40 +08:00

856 lines
25 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.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { areaCodeOptions, checkTelPhone, checkEmail, createV, showLoadingToast, closeToast, showToast, isEmpty } from '@/common/common.js';
import { getLoginCode } from '@/common/login.js';
import { login, register, sendCode, policyServer, loginLRInit } from '@/api/user.js';
import { useUserStore } from '@/store/user';
import { useI18n } from 'vue-i18n';
import CommonPicker from './common-picker.vue';
const { t, locale } = useI18n();
const userStore = useUserStore();
const isLogin = ref(true); // 是否登录,默认登录,为false表示注册
import { uniLogin } from '@/common/login';
const isMobile = ref(true); //是否手机号登录
const isPassword = ref(false); //是否密码登录
const isForgetPassword = ref(false); //是否忘记密码
// const isDisable = ref(true); //是否登录注册按钮是否禁用,即是否通过校验
const mobile = ref(''); // 手机号
const areaCode = ref(''); // 地区码
const code = ref(''); // 验证码
const password = ref(''); // 密码
const passwordConfirm = ref(''); // 确认密码
const email = ref(''); // 邮箱
const emit = defineEmits(['close']);
const wxJsCode = ref({}); //wx授权码code
const codeType = ref(''); // 区分code信息类型:=mpLogin(小程序中手机号+验证码登录);=mpMobile(小程序中授权获取手机号登录)
//同意条款相关
const checked = ref(false);
const onChange = (e) => {
checked.value = !checked.value;
};
//地区码弹框相关
const columns = ref([]);
const showCommonPicker = ref(false);
const onConfirmCommonPicker = (data) => {
areaCode.value = data[0].value;
showCommonPicker.value = false;
};
const onCloseCommonPicker = () => {
showCommonPicker.value = false;
};
//导航手机号点击功能
const onMobile = () => {
isMobile.value = true;
password.value = '';
passwordConfirm.value = '';
// isDisable.value = true;
// console.log(isMobile.value);
};
//导航邮箱点击功能
const onEmail = () => {
isMobile.value = false;
isPassword.value = true;
password.value = '';
passwordConfirm.value = '';
// isDisable.value = true;
};
//去登录
const toLogin = () => {
isLogin.value = true;
isMobile.value = true;
isPassword.value = true;
mobile.value = '';
areaCode.value = '852';
code.value = '';
password.value = '';
passwordConfirm.value = '';
email.value = '';
clearInterval(timerId2.value);
};
//去注册
const toRegister = () => {
isLogin.value = false;
isMobile.value = true;
isPassword.value = true;
mobile.value = '';
areaCode.value = '852';
code.value = '';
password.value = '';
passwordConfirm.value = '';
email.value = '';
clearInterval(timerId1.value);
};
/**
* @desc 登录
*/
const onLogin = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
//登录参数
let params = {};
//小程序免验证登录,codeType.value = mpMobile时,表示本次登录为免验证登录,noVerify值为1
if (codeType.value == 'mpMobile') {
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
params.noVerify = 1; // 免验证登录必传, codeType.value = mpMobile 时,为1表示本次登录为免验证登录
} else {
//手机号+验证码登录
params.codeType = userStore.platform == 'h5' ? '' : codeType.value; // 小程序有效,仅在短信验证码登录时有值 mpLogin
params.loginType = userStore.platform == 'h5' ? 'mobile' : ''; // h5环境下短信验证码登录必须要
params.areaCode = isMobile.value ? areaCode.value : ''; // 区号
params.mobile = isMobile.value ? mobile.value : ''; // 手机号
params.verifyCode = code.value; // 验证码
// #ifdef MP-WEIXIN || MP-ALIPAY
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
// #endif
//登录前的验证
if (!LoginCheck()) {
return;
}
}
console.log(params);
try {
uni.showLoading({
mask: true,
});
login(params).then((res) => {
console.log(res);
uni.hideLoading();
if (res.success) {
userStore.userToken = res.data.token;
userStore.isLogin = res.data.isLogin;
userStore.setProfile({
...res.data.userData,
});
emit('close');
mobile.value = '';
password.value = '';
code.value = '';
email.value = '';
} else {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: error.message,
icon: 'none',
});
} finally {
}
};
//登录校验
const LoginCheck = (val) => {
if (isMobile.value) {
//手机号登录
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: t('personal.mobileTips'),
icon: 'none',
});
return false;
}
if (isPassword.value) {
//密码登录
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
}
} else {
//验证码登录
if (code.value == '' || code.value == null) {
uni.showToast({
title: t('personal.validatePlaceholder'),
icon: 'none',
});
return false;
}
}
} else {
//邮箱登录
if (!checkEmail(email.value)) {
uni.showToast({
title: '请输入正确的电子邮箱',
icon: 'none',
});
return false;
}
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
}
}
return true;
};
//注册
const onRegister = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
let params = {
regType: isMobile.value ? '1' : '2', //1手機號 2郵箱
areaCode: isMobile.value ? areaCode.value : '',
mobile: isMobile.value ? mobile.value : '',
password: isPassword.value ? password.value : '',
passwordConfirm: isPassword.value ? passwordConfirm.value : '',
code: isMobile.value ? code.value : '',
email: !isMobile.value ? email.value : '',
// #ifdef MP-WEIXIN || MP-ALIPAY
wxJsCode: wxJsCode.value, //微信端需要传递登录授权code
// #endif
};
console.log(params);
if (!RegisterCheck()) {
return;
}
try {
uni.showLoading({
mask: true,
});
//注册api
register(params).then((res) => {
uni.hideLoading();
if (res.success) {
userStore.userToken = res.data.token;
userStore.setProfile({
...res.data.user,
});
emit('close');
mobile.value = '';
password.value = '';
passwordConfirm.value = '';
code.value = '';
email.value = '';
} else {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
//重新获取code
// #ifdef MP-WEIXIN || MP-ALIPAY
wxGetLoginCode();
// #endif
uni.showToast({
title: error.message,
icon: 'none',
});
} finally {
}
};
//注册校验
const RegisterCheck = (val) => {
if (isMobile.value) {
//手机号注册
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: '请输入正确的地区号和手机号',
icon: 'none',
});
return false;
}
if (code.value == '' || code.value == null) {
uni.showToast({
title: '请输入验证码',
icon: 'none',
});
return false;
}
} else {
//邮箱注册
if (!checkEmail(email.value)) {
uni.showToast({
title: '请输入正确的电子邮箱',
icon: 'none',
});
return false;
}
}
if (password.value == '' || password.value == null) {
uni.showToast({
title: '请输入密码',
icon: 'none',
});
return false;
} else {
if (password.value?.length < 6 || password.value?.length > 20) {
uni.showToast({
title: '密碼長度應為6-20位',
icon: 'none',
});
return false;
}
}
if (passwordConfirm.value == '' || passwordConfirm.value == null) {
uni.showToast({
title: '請再次輸入密碼',
icon: 'none',
});
return false;
} else {
if (password.value != passwordConfirm.value) {
uni.showToast({
title: '兩次密碼不一樣',
icon: 'none',
});
return false;
}
}
return true;
};
const dataV = ref(''); //加密数据
const isCanWxLogin = ref(false); // 允许国内公司快速获取手机号登录
/**
* 初始化,获取data.v
*/
const initMobile = async () => {
try {
await loginLRInit().then((res) => {
if (res.success) {
areaCode.value = res.data.areaCodeList[0].value;
isCanWxLogin.value = res.data.isAllowMpMobile; // 允许国内公司快速获取手机号登录
res.data.areaCodeList.forEach((item, index) => {
columns.value[index] = {
value: item.value,
name: item.label,
};
});
dataV.value = res.data.v;
console.log(dataV.value);
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
const timerId1 = ref(null); // 定时器1(登录)
const timerId2 = ref(null); // 定时器2(注册)
const countDown1 = ref(60); // 倒计时1(登录)
const allowSendCode1 = ref(true); // 允许发送验证码1(登录)
const countDown2 = ref(60); // 倒计时2(注册)
const allowSendCode2 = ref(true); // 允许发送验证码2(注册)
//获取验证码
const getValifyCode = () => {
//手机号注册
if (!checkTelPhone(areaCode.value, mobile.value)) {
uni.showToast({
title: t('personal.mobileTips'),
icon: 'none',
});
return false;
}
let account = areaCode.value + ',' + mobile.value;
//加密data.v
let postV;
if (isLogin.value) {
postV = createV(dataV.value, account);
} else {
postV = createV(dataV.value, account);
}
let params = {
// account: isMobile.value ? account : '', //1手機號 2郵箱
// type: isMobile.value ? '1' : '2', //類型:1 手機號 2 郵箱
// purpose: !isLogin.value ? '1' : !isForgetPassword.value ? '2' : '3', //目的:1註冊 2登錄 3忘記密碼
areaCode: areaCode.value,
mobile: mobile.value,
v: postV, //加密数据
};
try {
// console.log(params);
uni.showLoading({
mask: true,
});
sendCode(params).then((res) => {
uni.hideLoading();
if (res.success) {
uni.showToast({
title: t('personal.resendSuccess'),
icon: 'none',
});
// #ifdef MP
codeType.value = 'mpLogin';
// #endif
if (isLogin.value) {
allowSendCode1.value = false;
timerId1.value = setInterval(() => {
countDown1.value--;
if (countDown1.value == 0) {
countDown1.value = 60;
allowSendCode1.value = true;
clearInterval(timerId1.value);
// console.log(timerId1.value);
}
}, 1000);
} else {
allowSendCode2.value = false;
timerId2.value = setInterval(() => {
countDown2.value--;
if (countDown2.value == 0) {
countDown2.value = 60;
allowSendCode2.value = true;
clearInterval(timerId2.value);
}
}, 1000);
}
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
//忘记密码
const toForgetPassword = () => {
uni.navigateTo({
url: '/pages/user/forgetPassword',
});
};
// 协议弹框相关
const noticeData = ref({}); //协议数据
const showAgreementPicker = ref(false);
const onShowAgreementPicker = () => {
showAgreementPicker.value = true;
};
const onCancelAgreementPicker = () => {
showAgreementPicker.value = false;
};
const getPolicyServer = async () => {
try {
await policyServer({
type: '',
}).then((res) => {
console.log(res);
if (res.success) {
noticeData.value = res.data?.content;
} else {
uni.showToast({
title: res.message,
icon: 'none',
});
}
});
} catch (error) {
uni.showToast({
title: error.message,
icon: 'none',
});
}
};
/**
* @desc 获取微信登录授权码
*/
const wxGetLoginCode = async () => {
await getLoginCode().then((res) => {
wxJsCode.value = res;
console.log(wxJsCode.value, '新获取的code');
});
};
/**
* @desc 免验证登录
*/
const quickLogin = () => {
getLoginCode().then((res) => {
// #ifdef MP
codeType.value = 'mpMobile';
// #endif
wxJsCode.value = res;
onLogin();
});
};
// 获取用户手机号码
const onGetphonenumber = (ev) => {
uni.$u.debounce(async () => {
if (ev.detail.errMsg.includes('fail')) return;
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
try {
uni.showLoading({
title: t('loading'),
mask: true,
});
const flag = await uniLogin(ev);
if (!flag) {
uni.showToast({
title: '登录失败',
icon: 'none',
});
} else {
emit('close');
console.log({
flag,
});
}
} catch (err) {
console.log({
err,
});
} finally {
uni.hideLoading();
}
}, 1000);
};
const handleChecked = () => {
if (!checked.value) {
uni.showToast({
title: t('policyTips'),
icon: 'none',
duration: 2000,
});
return;
}
};
onMounted(async () => {
await initMobile();
// await getPolicyServer();
// #ifdef MP-WEIXIN || MP-ALIPAY
await wxGetLoginCode();
// #endif
});
onUnmounted(() => {
clearInterval(timerId1.value);
clearInterval(timerId2.value);
});
</script>
<template>
<!-- <view class="login-type flex text-[34rpx] text-theme">
<view class="mobile-type flex-1 font-medium border-bottom" @tap="onMobile" :class="{ border-bottom-theme: isMobile }">{{ isLogin ? '手機號碼登入' : '手機號碼註冊' }}</view>
<view class="email-type flex-1 font-medium border-bottom" @tap="onEmail" :class="{ border-bottom-theme: !isMobile }">{{ isLogin ? '電郵登入' : '電郵註冊' }}</view>
</view> -->
<!-- 微信快捷登录 -->
<view v-if="isLogin && isCanWxLogin">
<view class="login-box">
<button
v-if="!checked"
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
@click="handleChecked"
>
{{ t('phoneQuickLogin') }}
</button>
<button
v-else
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
open-type="getPhoneNumber"
@getphonenumber="onGetphonenumber"
>
{{ t('phoneQuickLogin') }}
</button>
</view>
</view>
<!-- 登录 -->
<view v-else-if="isLogin">
<view class="mobile" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
<view class="mobile-text flex items-center border-round-theme">
<view class="areaCode flex items-center">
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
<view class="down-icon">
<up-icon name="arrow-down-fill" size="8" color></up-icon>
</view>
</view>
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
<!-- #endif -->
</view>
</view>
<view class="email" v-else>
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
<view class="email-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
<!-- #endif -->
</view>
</view>
<view class="password" v-if="isPassword">
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<view class="code" v-else>
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
<view class="code-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
<!-- #endif -->
<view v-if="allowSendCode1" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
t('personal.getValidateCode')
}}</view>
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]"> {{ countDown1 }}s{{ t('personal.resend') }} </view>
</view>
</view>
<!-- 免验证登录 -->
<!-- #ifdef MP -->
<view class="flex flex-row items-center justify-center flex-1" @click="quickLogin">
<text class="text-[15px]">{{ t('quickLogin') }}</text>
</view>
<!-- #endif -->
<!-- <view class="text-right text-[26rpx] mx-[30rpx] text-gray-600" @tap="toForgetPassword">忘記密碼?</view> -->
<view class="bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white" @tap="onLogin">{{
t('login')
}}</view>
<!-- <view class="flex mx-[30rpx] items-center justify-between text-[28rpx]">
<view>沒有賬號?點挈 <text class="text-theme" @tap="toRegister">註冊</text></view>
<view v-if="isMobile" class="text-theme" @tap="isPassword = !isPassword">{{ !isPassword ? '密碼登入' : '验证码登入' }}</view>
</view> -->
</view>
<!-- 注册 -->
<view v-else>
<view class="mobile" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
<view class="mobile-text flex items-center border-round-theme">
<view class="areaCode flex items-center">
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
<view class="down-icon">
<up-icon name="arrow-down-fill" size="8" color></up-icon>
</view>
</view>
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
<!-- #endif -->
</view>
</view>
<view class="email" v-else>
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
<view class="email-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
<!-- #endif -->
</view>
</view>
<view class="code" v-if="isMobile">
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
<view class="code-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
<!-- #endif -->
<view v-if="allowSendCode2" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
t('personal.getValidateCode')
}}</view>
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]">
{{ `${countDown2}s ${t('personal.resend')}` }}
</view>
</view>
</view>
<view class="password">
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<view class="password">
<view class="label text-[26rpx]">{{ t('personal.confirmPassword') }}</view>
<view class="password-text flex items-center border-round-theme">
<!-- #ifndef MP-ALIPAY -->
<up-input :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none"></up-input>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<input class="flex-1" :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none" />
<!-- #endif -->
</view>
</view>
<!-- :class="{ disable: isDisable }" -->
<view
class="bg-theme rounded-full mx-[30rpx] mt-[15rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
@tap="onRegister"
>{{ t('regLogin') }}</view
>
<view class="flex mx-[30rpx] mb-[40rpx] items-center justify-between text-[28rpx]">
<view
>{{ t('hasAccount') }} <text class="text-theme" @tap="toLogin">{{ t('login') }}</text></view
>
</view>
</view>
<view class="text-center my-[40rpx] text-[28rpx]">
<checkbox-group @change="onChange">
<checkbox class="checkbox" :checked="checked" color="#00A88E"> </checkbox>
{{ t('policyNote') }} <text class="text-theme font-medium" @tap="onShowAgreementPicker">{{ t('policy') }}</text>
</checkbox-group>
</view>
<!-- 地区号 -->
<CommonPicker
:title="t('personal.selectAreaCode')"
:show="showCommonPicker"
:columns="columns"
@confirm="onConfirmCommonPicker"
@close="onCloseCommonPicker"
></CommonPicker>
<!-- 用户协议弹框 -->
<up-popup :show="showAgreementPicker" :closeable="true" round="10" mode="bottom" @close="onCancelAgreementPicker">
<view class="h-[70vh] flex flex-col px-[30rpx] pb-[30rpx] text-[28rpx]">
<view class="py-[30rpx] text-center text-[36rpx]">
{{ t('policy') }}
</view>
<view class="flex-1 overflow-y-scroll">
<up-parse :content="noticeData"></up-parse>
</view>
</view>
</up-popup>
</template>
<style lang="scss" scoped>
.border-bottom-theme .login-type {
height: 88rpx;
margin: 30rpx;
.mobile-type,
.email-type {
text-align: center;
line-height: 88rpx;
}
}
.mobile,
.email,
.password,
.code {
margin: 30rpx;
.label {
font-size: 26rpx;
padding-left: 40rpx;
padding-bottom: 20rpx;
}
.mobile-text,
.email-text,
.password-text,
.code-text {
// margin: 0 15px;
height: 100rpx;
padding: 0 40rpx;
border-radius: 100rpx;
.van-field {
background-color: #ffffff !important;
padding: 0 10rpx !important;
}
.areaCode {
margin-right: 17rpx;
.down-icon {
width: 40rpx;
padding-left: 10rpx;
}
border-right: 2rpx solid #eee;
}
}
}
.disable {
opacity: 0.3;
}
</style>