307 lines
7.4 KiB
Vue
307 lines
7.4 KiB
Vue
<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 } 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
|
||
}
|
||
|
||
// 未勾选,弹窗提示
|
||
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(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>
|
||
|