Files

446 lines
9.3 KiB
Vue
Raw Normal View History

2023-11-14 17:21:03 +08:00
<!--
@name: 登录页
@author: kahu4
@date: 2023-11-08 16:52
@descriptionindex
@update: 2023-11-08 16:52
-->
<script setup>
import { onLoad } from "@dcloudio/uni-app";
import { useRouter } from "@/hooks/useRouter";
import { nextTick, onBeforeUnmount, ref, unref } from "vue";
import { checkPhone } from "@/utils/utils";
import { useInterface } from "@/hooks/useInterface";
import { privacyAgreementUrl, sendSmsCode as sendSmsCodeRequest, smsLogin, userAgreementUrl } from '@/api/auth'
import { useMainStore } from "@/store/store";
import { afterLogin } from "@/utils";
const {getParams, goBack, push, pushToTab} = useRouter()
const {toast} = useInterface();
const mainStore = useMainStore()
const routeParams = ref({label: '账号登录'})
const mobileFocus = ref(false)
const codeFocus = ref(false)
const form = ref({
mobile: '',
code: ''
})
/**
* 清空手机号数据
*/
function clearPhone() {
unref(form).mobile = ''
}
const isSendSms = ref(false) // 是否已经发送短信
const countDown = ref(60) // 倒计时
let countDownObj
/**
* 发送验证码
* @returns {Promise<*>}
*/
async function sendSmsCode() {
if (!checkPhone(unref(form).mobile)) {
mobileFocus.value = true
codeFocus.value = false
return toast({title: '请输入正确的手机号'})
} else {
mobileFocus.value = false
codeFocus.value = true
}
if (isSendSms.value) return
isSendSms.value = true
await doSendSmsCode()
countDownObj = setInterval(() => {
countDown.value -= 1
if (countDown.value <= 0) {
clearInterval(countDownObj)
isSendSms.value = false
countDownObj = undefined
countDown.value = 60
}
}, 1000)
}
/**
* 验证码请求
* @returns {Promise<void>}
*/
async function doSendSmsCode() {
try {
await sendSmsCodeRequest({mobile: unref(form).mobile, scene: 1})
toast({
title: '发送成功',
icon: 'success'
})
} catch (e) {
toast({
title: '发送失败',
icon: 'error'
})
clearInterval(countDownObj)
isSendSms.value = false
countDownObj = undefined
countDown.value = 60
}
}
const showPrivacy = ref(true)
const privacyPolicy = ref([]) // 协议双向绑定
const isPrivacyError = ref(false) // 未勾选协议
/**
* 检查是否勾选协议
* @returns {boolean}
*/
function checkPrivacy() {
const flag = privacyPolicy.value.length > 0
if (!flag) {
isPrivacyError.value = true
setTimeout(() => {
isPrivacyError.value = false
}, 1000)
}
return flag
}
/**
* 跳转协议
* @param type
*/
function toAgreement(type) {
const urls = [userAgreementUrl, privacyAgreementUrl]
push({url: '/pages/webview/index'}, {data: {src: urls[type]}})
}
/**
* 设置隐私协议模块是否可见
* 解决键盘弹起fixed内容上顶问题
* @param flag
*/
function setShowPrivacy(flag) {
nextTick(() => {
showPrivacy.value = flag
})
}
const loginLoading = ref(false) // 正在登录
/**
* 登录
* @returns {Promise<*>}
*/
async function doLogin() {
if (!checkPrivacy()) return toast({title: '请先阅读并同意用户协议和隐私政策'})
if (!checkPhone(unref(form).mobile)) return toast({title: '请输入正确的手机号'})
if (!(unref(form).code)) return toast({title: '请输入正确的验证码'})
try {
loginLoading.value = true
const res = await smsLogin(form.value);
if (res) {
await mainStore.setAccessToken(res)
afterLogin()
}
} catch (e) {
console.error(e)
} finally {
loginLoading.value = false
}
}
onLoad((options) => {
routeParams.value = getParams(options);
})
onBeforeUnmount(() => {
countDownObj ? clearInterval(countDownObj) : void (0)
})
</script>
<template>
<view class="login-container">
<uv-navbar
:fixed="false"
:title="routeParams.label"
left-arrow
@leftClick="goBack"
/>
<view class="logo-box">
<image
class="logo"
src="@/static/icon/logo.png"
/>
</view>
<view class="form-box">
<view class="form-item">
<view class="left">
<view class="icon">
<image
class="icon"
src="@/static/icon/login/手机@2x.png"
/>
</view>
<view class="area-code">
+86
</view>
</view>
<view class="input">
<input
type="tel"
placeholder="请输入手机号"
v-model="form.mobile"
:focus="mobileFocus"
:adjust-position="false"
confirm-type="next"
confirm-hold
@focus="setShowPrivacy(false);codeFocus=false"
@blur="setShowPrivacy(true)"
@confirm="codeFocus=true"
/>
<view
v-if="form.mobile.length>0"
class="clear"
@click="clearPhone"
>
<uv-icon
name="close-circle-fill"
color="#7a7a7a"
size="24"
/>
</view>
</view>
</view>
<view class="form-item">
<view class="left">
<view class="icon">
<image
class="icon"
src="@/static/icon/login/验证码@2x.png"
/>
</view>
<view class="area-code">
</view>
</view>
<view class="input authCode flex flex-ai__center flex-jc__sb">
<input
type="number"
placeholder="请输入验证码"
:adjust-position="false"
v-model="form.code"
:focus="codeFocus"
confirm-type="done"
@focus="setShowPrivacy(false)"
@blur="setShowPrivacy(true)"
@confrim="doLogin"
/>
<view
class="animation-button send-button"
:class="{disabled:isSendSms}"
@click="sendSmsCode"
>
{{ isSendSms ? `${ countDown }S` : '发送' }}
</view>
</view>
</view>
<view class="button-group">
<view
class="animation-button button"
:class="{disabled:loginLoading}"
@click="doLogin"
>
确认
</view>
</view>
</view>
<view
class="agreement-box"
:class="{'error-animation':isPrivacyError,'hide-box':!showPrivacy}"
>
<uv-checkbox-group
v-model="privacyPolicy"
shape="circle"
activeColor="#ec6e47"
>
<uv-checkbox :name="1">
<view class="agreement-text">
阅读并同意
<span
class="color"
@click="toAgreement(0)"
>
YSHOP商城用户协议
</span>
<span
class="color"
@click="toAgreement(1)"
>
YSHOP商城隐私协议
</span>
</view>
</uv-checkbox>
</uv-checkbox-group>
</view>
</view>
</template>
<style
scoped
lang="scss"
>
.login-container {
width: 100%;
height: 100vh;
background: $white-color;
.logo-box {
@include useFlex(center, center);
width: 100%;
padding-top: 185rpx;
.logo {
width: 244rpx;
height: 96rpx;
}
}
.form-box {
@include usePadding(48, 0);
margin-top: 200rpx;
width: 100%;
.form-item {
@include usePadding(0, 30);
@include useFlex(space-between, center);
color: #000000;
font-size: 28rpx;
border-bottom: 1rpx solid #F5F5F5;
.left {
@include useFlex(space-between, center);
}
.icon {
width: 36rpx;
height: 36rpx;
}
.area-code {
@include usePadding(16, 0);
margin-right: 16rpx;
border-right: 1rpx solid #f5f5f5;
font-weight: bold;
}
.input {
position: relative;
flex-grow: 1;
.clear {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
transition: all .3s;
&:active {
scale: 1.1;
}
}
}
.authCode {
input {
width: 50%;
}
.send-button {
@include usePadding(50, 14)
}
}
}
}
.button-group {
margin-top: 64rpx;
.button {
height: 100rpx;
line-height: 100rpx;
text-align: center;
}
}
.agreement-box {
@include usePadding(30, 0);
position: fixed;
bottom: 5%;
width: 100%;
font-size: 24rpx;
color: $tips-color;
transition: all .3s;
.agreement-text {
text-align: center;
.color {
color: $primary-color;
}
}
:deep(.uv-checkbox ) {
width: 100%;
align-items: flex-start;
.uv-checkbox__icon-wrap {
}
}
}
.hide-box {
bottom: -100rpx;
}
.error-animation {
animation: error-text 0.8s 1;
}
}
@keyframes error-text {
0% {
transform: translateX(0);
}
5%, 25%, 45%, 65%, 85% {
transform: translateX(-10rpx);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(10rpx);
}
15%, 35%, 55%, 75%, 95% {
transform: translateX(20rpx);
}
20%, 40%, 60%, 80%, 100% {
transform: translateX(-20rpx);
}
}
</style>