Files
uni-fans-score/pages/login/index.vue
T

270 lines
6.3 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 class="login-container">
<view class="logo">
<image src="/static/logo.png" mode="aspectFit" />
<text class="app-name">{{ $t('app.slogan') }}</text>
</view>
<view class="title">{{ $t('auth.loginTitle') }}</view>
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
<!-- 支付宝一键手机号快捷登录推荐 -->
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
{{ $t('auth.getPhoneNumber') }}
</button>
<button v-else class="btn primary" open-type="getAuthorize" scope="phoneNumber" @getAuthorize="onGetPhoneNumber">
{{ $t('auth.getPhoneNumber') }}
</button>
<!-- 仅支付宝登录不授权手机号时使用 -->
<!-- <button class="btn outline" @click="onAlipayLogin">仅支付宝登录</button> -->
<view class="agreement-box">
<checkbox-group @change="onAgreementChange">
<label class="agreement-label">
<checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" />
<text class="agreement-text">
{{ $t('auth.agreeToTerms') }}
<text class="link" @tap.stop="go('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
{{ $t('common.and') }}
<text class="link" @tap.stop="go('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
</text>
</label>
</checkbox-group>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { alipayLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('auth.loginTitle')
})
})
const redirect = ref('/pages/index/index')
const isAgreed = ref(false) // 是否同意协议
// 勾选协议变化
const onAgreementChange = (e) => {
isAgreed.value = e.detail.value.includes('agreed')
console.log('协议勾选状态:', isAgreed.value, e.detail.value)
}
// 未勾选协议时点击登录按钮
const handleLoginClick = async () => {
try {
await checkAgreement()
// 协议已同意后,按钮会自动切换为带open-type的版本
} catch (error) {
// 用户取消了协议同意
}
}
// 检查是否同意协议
const checkAgreement = () => {
return new Promise((resolve, reject) => {
if (isAgreed.value) {
resolve()
return
}
// 未勾选,弹窗提示
uni.showModal({
title: $t('common.tips'),
content: $t('auth.pleaseAgreeToTerms'),
confirmText: $t('common.confirm'),
cancelText: $t('common.cancel'),
success: (res) => {
if (res.confirm) {
// 用户点击同意,自动勾选
isAgreed.value = true
resolve()
} else {
// 用户点击取消
reject(new Error('需要同意协议才能登录'))
}
}
})
})
}
const navigateAfterLogin = async () => {
try {
// 可选:刷新一次用户信息
await getUserInfo().catch(() => {})
} catch (e) {}
// 读取跳转路径(支持 tabBar 页面)
const target = '/pages/index/index'
const tabPages = ['/pages/index/index', '/pages/my/index']
if (tabPages.includes(target)) {
uni.reLaunch({ url: target })
return
}
uni.reLaunch({ url: target })
}
const onAlipayLogin = async () => {
try {
// 先检查是否同意协议
await checkAgreement()
await alipayLogin()
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
if (error.message !== '需要同意协议才能登录') {
uni.showToast({ title: error.message || '登录失败', icon: 'none' })
}
}
}
const onGetPhoneNumber = async (e) => {
// 支付宝获取手机号的回调处理
if (!e || !e.detail || !e.detail.response) {
uni.showToast({ title: $t('auth.phoneCancelled'), icon: 'none' })
return
}
try {
// 先支付宝登录,获取 token
await alipayLogin()
// 再用支付宝返回的授权码换取手机号
// 支付宝的授权码在 e.detail.response 中
const authCode = e.detail.response?.response?.code || e.detail.response?.code
if (authCode) {
await getUserPhoneNumber(authCode)
}
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
uni.showToast({ title: error.message || $t('auth.loginFailed'), icon: 'none' })
}
}
onLoad((opts) => {
if (opts && opts.redirect) {
try {
redirect.value = decodeURIComponent(opts.redirect)
} catch (_) {}
}
})
const go = (url) => {
uni.navigateTo({ url })
}
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: #f8f8f8;
padding: 80rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
.logo {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 60rpx;
image {
width: 160rpx;
height: 160rpx;
margin-bottom: 16rpx;
}
.app-name {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
.title {
font-size: 40rpx;
font-weight: 600;
color: #222;
margin-bottom: 12rpx;
}
.subtitle {
font-size: 26rpx;
color: #888;
margin-bottom: 60rpx;
}
.btn {
width: 100%;
height: 96rpx;
border-radius: 48rpx;
font-size: 32rpx;
margin-bottom: 24rpx;
}
.primary {
background: #07c160;
color: #fff;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
}
.outline {
background: #fff;
color: #07c160;
border: 2rpx solid #07c160;
}
.agreement-box {
margin-top: 32rpx;
display: flex;
justify-content: center;
align-items: center;
left: 50%;
transform: translateX(-50%);
width: 100%;
bottom: 40rpx;
position: absolute;
.agreement-label {
display: flex;
align-items: center;
width: 100%;
.agreement-checkbox {
flex-shrink: 0;
// margin-right: 12rpx;
// margin-top: 2rpx;
transform:scale(0.7);
}
.agreement-text {
flex: 1;
font-size: 24rpx;
color: #666;
line-height: 1.8;
word-break: break-all;
.link {
color: #07c160;
font-weight: 500;
text-decoration: none;
}
}
}
}
}
</style>