<template>
|
<view class="login-page">
|
<view class="nav" :style="navStyle">
|
<view class="nav__bar" :style="navBarStyle">
|
<view class="nav__back" @tap="handleBack">
|
<image class="nav__back-icon" src="/static/ui-icons/back-black.svg" mode="aspectFit" />
|
</view>
|
<text class="nav__title">登录</text>
|
</view>
|
</view>
|
|
<view class="login-content">
|
<view class="hero-card">
|
<view class="hero-card__logo-wrap">
|
<image class="hero-card__logo" src="/static/ui-icons/mingdao-logo-tight.png" mode="aspectFit" />
|
</view>
|
</view>
|
|
<button
|
class="login-button"
|
:class="{
|
'login-button--disabled': !agreementChecked && !loggingIn && !loginSuccess,
|
'login-button--loading': loggingIn,
|
'login-button--success': loginSuccess
|
}"
|
:open-type="agreementChecked && !loggingIn && !loginSuccess ? 'getPhoneNumber' : ''"
|
@tap="handleLoginTap"
|
@getphonenumber="handleGetPhoneNumber"
|
>
|
<view v-if="loggingIn" class="login-button__spinner" />
|
<text v-else-if="loginSuccess" class="login-button__success-mark">✓</text>
|
<image v-else class="login-button__icon" src="/static/ui-icons/phone-black.svg" mode="aspectFit" />
|
<text>{{ loginButtonText }}</text>
|
</button>
|
|
<view class="agreement-row" @tap="toggleAgreement">
|
<view class="agreement-checkbox" :class="{ 'agreement-checkbox--checked': agreementChecked }">
|
<text v-if="agreementChecked" class="agreement-checkbox__mark">✓</text>
|
</view>
|
<view class="agreement-copy">
|
<text class="agreement-text">我已阅读并同意</text>
|
<text class="agreement-link" @tap.stop="openAgreement('VISITOR_USER_AGREEMENT')">《服务条款》</text>
|
<text class="agreement-text">、</text>
|
<text class="agreement-link" @tap.stop="openAgreement('VISITOR_PRIVACY_PERMISSION')">《隐私与权限》</text>
|
</view>
|
</view>
|
|
<view class="skip-login" @tap="handleSkip">
|
<text>暂不登录</text>
|
</view>
|
</view>
|
|
<view v-if="showPrompt" class="prompt-mask" @tap="closePrompt">
|
<view class="prompt-card" @tap.stop>
|
<text class="prompt-title">提示</text>
|
<text class="prompt-desc">请确认已同意《服务条款》《隐私与权限》</text>
|
<view class="prompt-actions">
|
<view class="prompt-button prompt-button--ghost" @tap="closePrompt">取消</view>
|
<view class="prompt-button prompt-button--primary" @tap="confirmPrompt">确认</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, ref } from 'vue'
|
import { getNavMetrics } from '@/utils/system-info'
|
import { onLoad } from '@dcloudio/uni-app'
|
import { LOGIN_STORAGE_KEY } from '@/constants/auth'
|
import { loginWithWechatPhone } from '@/services/auth'
|
|
const agreementChecked = ref(false)
|
const showPrompt = ref(false)
|
const redirectUrl = ref('/pages/home/index')
|
const navMetrics = ref(getNavMetrics())
|
const loggingIn = ref(false)
|
const loginSuccess = ref(false)
|
|
const navStyle = computed(() => ({
|
height: `${navMetrics.value.navHeight}px`,
|
paddingTop: `${navMetrics.value.statusBarHeight}px`
|
}))
|
|
const navBarStyle = computed(() => ({
|
height: `${navMetrics.value.navBarHeight}px`
|
}))
|
|
const loginButtonText = computed(() => {
|
if (loginSuccess.value) return '登录成功'
|
if (loggingIn.value) return '登录中...'
|
return '手机号一键登录'
|
})
|
|
function handleBack() {
|
const pages = getCurrentPages()
|
if (pages.length > 1) {
|
uni.navigateBack()
|
return
|
}
|
|
uni.reLaunch({
|
url: '/pages/home/index'
|
})
|
}
|
|
function handleLoginTap() {
|
if (loggingIn.value || loginSuccess.value) return
|
|
if (!agreementChecked.value) {
|
showPrompt.value = true
|
return
|
}
|
}
|
|
async function handleGetPhoneNumber(event) {
|
if (loggingIn.value || loginSuccess.value) return
|
|
const detail = event && event.detail ? event.detail : {}
|
if (!detail.code) {
|
if (detail.errMsg && detail.errMsg.includes('deny')) {
|
uni.showToast({
|
title: '需要手机号授权才能登录',
|
icon: 'none'
|
})
|
return
|
}
|
|
uni.showToast({
|
title: '未获取到手机号授权码,请重试',
|
icon: 'none'
|
})
|
return
|
}
|
|
loggingIn.value = true
|
|
try {
|
const loginState = await loginWithWechatPhone(detail.code)
|
uni.setStorageSync(LOGIN_STORAGE_KEY, {
|
...loginState,
|
expiresAt: Date.now() + Number(loginState.expiresIn || 0) * 1000
|
})
|
loginSuccess.value = true
|
|
setTimeout(() => {
|
uni.reLaunch({
|
url: redirectUrl.value
|
})
|
}, 180)
|
} catch (error) {
|
loggingIn.value = false
|
}
|
}
|
|
function toggleAgreement() {
|
agreementChecked.value = !agreementChecked.value
|
}
|
|
function closePrompt() {
|
showPrompt.value = false
|
}
|
|
function confirmPrompt() {
|
agreementChecked.value = true
|
showPrompt.value = false
|
}
|
|
function openAgreement(code) {
|
uni.navigateTo({
|
url: `/pages/agreement-detail/index?code=${code}`
|
})
|
}
|
|
function handleSkip() {
|
const pages = getCurrentPages()
|
if (pages.length > 1) {
|
uni.navigateBack()
|
return
|
}
|
|
uni.reLaunch({
|
url: '/pages/home/index'
|
})
|
}
|
|
onLoad((options = {}) => {
|
if (options.redirect) {
|
redirectUrl.value = decodeURIComponent(options.redirect)
|
}
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
.login-page {
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
background: linear-gradient(180deg, #f6f9ff 0%, #ffffff 38%);
|
}
|
|
.nav {
|
position: relative;
|
z-index: 10;
|
flex: 0 0 auto;
|
width: 100%;
|
background: transparent;
|
}
|
|
.nav__bar {
|
position: relative;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 0 28rpx;
|
}
|
|
.nav__back {
|
position: absolute;
|
left: 20rpx;
|
top: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 64rpx;
|
height: 64rpx;
|
transform: translateY(-50%);
|
}
|
|
.nav__back-icon {
|
width: 34rpx;
|
height: 34rpx;
|
}
|
|
.nav__title {
|
color: #111827;
|
font-size: 32rpx;
|
font-weight: 700;
|
line-height: 1;
|
}
|
|
.login-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 42rpx 40rpx 48rpx;
|
box-sizing: border-box;
|
}
|
|
.hero-card {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 96rpx 0 72rpx;
|
box-sizing: border-box;
|
}
|
|
.hero-card__logo-wrap {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
width: 100%;
|
padding: 0;
|
}
|
|
.hero-card__logo {
|
display: block;
|
width: 100%;
|
max-width: 470rpx;
|
height: 330rpx;
|
}
|
|
.login-button {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 14rpx;
|
width: 100%;
|
height: 88rpx;
|
margin-top: 12rpx;
|
border-radius: 22rpx;
|
background: #278bff;
|
color: #ffffff;
|
font-size: 28rpx;
|
font-weight: 900;
|
line-height: 1;
|
box-shadow: 0 14rpx 30rpx rgba(39, 139, 255, 0.18);
|
}
|
|
.login-button--disabled {
|
background: #c8d0db;
|
box-shadow: none;
|
color: rgba(255, 255, 255, 0.92);
|
}
|
|
.login-button--loading {
|
background: #278bff;
|
box-shadow: 0 14rpx 30rpx rgba(39, 139, 255, 0.18);
|
}
|
|
.login-button--success {
|
background: #278bff;
|
box-shadow: 0 10rpx 24rpx rgba(39, 139, 255, 0.2);
|
}
|
|
.login-button::after {
|
border: 0;
|
}
|
|
.login-button__icon {
|
width: 32rpx;
|
height: 32rpx;
|
filter: brightness(0) invert(1);
|
}
|
|
.login-button__spinner {
|
width: 24rpx;
|
height: 24rpx;
|
border: 3rpx solid rgba(255, 255, 255, 0.42);
|
border-top-color: #ffffff;
|
border-radius: 50%;
|
box-sizing: border-box;
|
animation: login-button-spin 680ms linear infinite;
|
}
|
|
.login-button__success-mark {
|
color: #ffffff;
|
font-size: 24rpx;
|
font-weight: 900;
|
line-height: 1;
|
}
|
|
.agreement-row {
|
display: flex;
|
align-items: center;
|
width: 100%;
|
gap: 14rpx;
|
margin-top: 28rpx;
|
}
|
|
.agreement-checkbox {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex: 0 0 auto;
|
width: 26rpx;
|
height: 26rpx;
|
border: 2rpx solid #c7cdd8;
|
border-radius: 6rpx;
|
box-sizing: border-box;
|
}
|
|
.agreement-checkbox--checked {
|
border-color: #278bff;
|
background: #278bff;
|
}
|
|
.agreement-checkbox__mark {
|
color: #ffffff;
|
font-size: 18rpx;
|
font-weight: 900;
|
line-height: 1;
|
}
|
|
.agreement-copy {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
flex: 1;
|
min-width: 0;
|
}
|
|
.agreement-text {
|
color: #111827;
|
font-size: 24rpx;
|
font-weight: 500;
|
line-height: 32rpx;
|
}
|
|
.agreement-link {
|
color: #278bff;
|
font-size: 24rpx;
|
font-weight: 700;
|
line-height: 32rpx;
|
}
|
|
.skip-login {
|
margin-top: auto;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
min-width: 180rpx;
|
height: 72rpx;
|
padding: 0 24rpx;
|
border-radius: 36rpx;
|
background: #f4f6fa;
|
color: #6b7280;
|
font-size: 24rpx;
|
font-weight: 700;
|
line-height: 1;
|
}
|
|
.prompt-mask {
|
position: fixed;
|
inset: 0;
|
z-index: 100;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: rgba(17, 24, 39, 0.28);
|
}
|
|
.prompt-card {
|
width: 600rpx;
|
min-height: 356rpx;
|
padding: 56rpx 34rpx 52rpx;
|
border-radius: 20rpx;
|
background: #ffffff;
|
box-sizing: border-box;
|
}
|
|
.prompt-title {
|
display: block;
|
color: #278bff;
|
font-size: 30rpx;
|
font-weight: 900;
|
line-height: 1;
|
text-align: center;
|
}
|
|
.prompt-desc {
|
display: block;
|
margin-top: 48rpx;
|
color: #9aa3b2;
|
font-size: 24rpx;
|
font-weight: 700;
|
line-height: 1.9;
|
text-align: center;
|
}
|
|
.prompt-actions {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 22rpx;
|
margin-top: 70rpx;
|
}
|
|
.prompt-button {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex: 1;
|
height: 72rpx;
|
border-radius: 10rpx;
|
font-size: 24rpx;
|
font-weight: 800;
|
line-height: 1;
|
}
|
|
.prompt-button--ghost {
|
border: 2rpx solid #78a6ff;
|
color: #278bff;
|
}
|
|
.prompt-button--primary {
|
background: #278bff;
|
color: #ffffff;
|
}
|
|
@keyframes login-button-spin {
|
from {
|
transform: rotate(0deg);
|
}
|
|
to {
|
transform: rotate(360deg);
|
}
|
}
|
</style>
|