2023-11-14 17:21:03 +08:00
|
|
|
|
<!--
|
|
|
|
|
@name: 登录页
|
|
|
|
|
@author: kahu4
|
|
|
|
|
@date: 2023-11-08 16:52
|
|
|
|
|
@description:index
|
|
|
|
|
@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"
|
2023-11-17 20:55:32 +08:00
|
|
|
|
src="https://b2c-pro-static-dev.zkthink.com/static/icon/logo.png"
|
2023-11-14 17:21:03 +08:00
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="form-box">
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<view class="left">
|
|
|
|
|
<view class="icon">
|
|
|
|
|
<image
|
|
|
|
|
class="icon"
|
2023-11-17 20:55:32 +08:00
|
|
|
|
src="https://b2c-pro-static-dev.zkthink.com/static/icon/login/手机@2x.png"
|
2023-11-14 17:21:03 +08:00
|
|
|
|
/>
|
|
|
|
|
</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"
|
2023-11-17 20:55:32 +08:00
|
|
|
|
src="https://b2c-pro-static-dev.zkthink.com/static/icon/login/验证码@2x.png"
|
2023-11-14 17:21:03 +08:00
|
|
|
|
/>
|
|
|
|
|
</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>
|