Files
uni-fans-score/subPackages/user/login/index.vue
T
2026-06-12 16:08:00 +08:00

305 lines
7.4 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>
<!-- 微信小程序一键手机号快捷登录 -->
<!-- #ifdef MP-WEIXIN -->
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
{{ $t('auth.getPhoneNumber') }}
</button>
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
{{ $t('auth.getPhoneNumber') }}
</button>
<!-- #endif -->
<!-- 支付宝小程序授权码快捷登录不支持 open-type=getPhoneNumber -->
<!-- #ifdef MP-ALIPAY -->
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
{{ $t('auth.loginBtn') }}
</button>
<button v-else class="btn primary" @click="onAlipayLogin">
{{ $t('auth.loginBtn') }}
</button>
<!-- #endif -->
<!-- H5不显示小程序快捷登录按钮 -->
<!-- 手机号验证码登录 -->
<button class="btn outline" @click="goToPhoneLogin" v-if="isHTML5">
{{ $t('auth.phoneLogin') }}
</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('/subPackages/other/legal/agreement')">{{ $t('user.userAgreement') }}</text>
{{ $t('common.and') }}
<text class="link" @tap.stop="go('/subPackages/other/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 { wxLogin, alipayLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
import { useI18n, showModalI18n } from '@/utils/i18n.js'
const { t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: t('auth.loginTitle')
})
})
const isHTML5 = ref(false) // 是否是HTML5模式
const redirect = ref('/pages/index/index')
const isAgreed = ref(false) // 是否同意协议
// 勾选协议变化
const onAgreementChange = (e) => {
isAgreed.value = e.detail.value.includes('agreed')
}
// 未勾选协议时点击登录按钮
const handleLoginClick = async () => {
try {
await checkAgreement()
// 协议已同意后,按钮会自动切换为带open-type的版本
} catch (error) {
// 用户取消了协议同意
}
}
// 检查是否同意协议
const checkAgreement = () => {
return new Promise((resolve, reject) => {
if (isAgreed.value) {
resolve()
return
}
// 未勾选,弹窗提示
showModalI18n({
title: t('common.tips'),
content: t('auth.pleaseAgreeToTerms'),
success: (res) => {
if (res.confirm) {
// 用户点击同意,自动勾选
isAgreed.value = true
resolve()
} else {
// 用户点击取消
reject(new Error(t('auth.pleaseAgreeToTerms')))
}
}
})
})
}
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 onWeChatLogin = async () => {
// try {
// await checkAgreement()
// await wxLogin()
// uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
// await navigateAfterLogin()
// } catch (error) {
// if (error.message !== t('auth.pleaseAgreeToTerms')) {
// uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
// }
// }
// }
// 支付宝快捷登录入口(支付宝小程序)
const onAlipayLogin = async () => {
try {
await checkAgreement()
await alipayLogin()
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
if (error.message !== t('auth.pleaseAgreeToTerms')) {
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
}
}
}
// 注意:手机号一致性校验不在登录页做;扫码/租借前校验(支付宝 my.getPhoneNumber
const onGetPhoneNumber = async (e) => {
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
return
}
console.log(e);
try {
// 先完成微信登录(/app/user/quickLogin,后端建立/查询 WECHAT_MINI 用户)
await wxLogin(e.detail.code)
// 再用微信返回的临时 code 换取手机号(后端按文档更新 phone 字段)
// await getUserPhoneNumber(e.detail.code)
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 (err) {
}
}
// #ifdef H5
isHTML5.value = true
// #endif
})
const go = (url) => {
uni.navigateTo({ url })
}
// 跳转到手机号登录页面
const goToPhoneLogin = () => {
uni.navigateTo({ url: '/subPackages/user/login/phone' })
}
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(180deg, #C8F4D9 0%, #FFFFFF 100%);
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>