- 用户store新增fetchUserInfo action,调用/userInfo接口获取完整用户信息 - 登录页修复:LoginResponse只有token和userId,登录成功后调用fetchUserInfo获取资料 - App.vue启动时若已登录自动刷新用户信息 - 我的页面onShow时刷新用户信息,新增下拉刷新支持 - pages.json为我的页面启用enablePullDownRefresh
387 lines
8.4 KiB
Vue
387 lines
8.4 KiB
Vue
<template>
|
||
<view class="login-page">
|
||
<!-- 自定义导航栏 -->
|
||
<Navbar title="登录" :showBack="true" backgroundColor="#FFFFFF" />
|
||
|
||
<!-- 主内容区域 -->
|
||
<view class="login-content">
|
||
<!-- Logo区域 -->
|
||
<view class="logo-section">
|
||
<view class="logo-wrapper">
|
||
<image src="/static/logo.png" mode="aspectFit" class="logo-img" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部登录区域 -->
|
||
<view class="bottom-section">
|
||
<!-- 登录按钮 -->
|
||
<button
|
||
class="btn-login"
|
||
:class="{ 'btn-disabled': !isAgreed }"
|
||
:disabled="!isAgreed"
|
||
open-type="getPhoneNumber"
|
||
@getphonenumber="handleGetPhoneNumber"
|
||
>
|
||
一键注册/登录
|
||
</button>
|
||
|
||
<!-- 协议勾选 -->
|
||
<view class="agreement-section" @click="toggleAgreement">
|
||
<view class="checkbox" :class="{ 'checkbox-checked': isAgreed }">
|
||
<image v-if="isAgreed" src="/static/ic_check_s.png" class="checkbox-icon" mode="aspectFit" />
|
||
</view>
|
||
<view class="agreement-text">
|
||
<text>注册即同意我们的</text>
|
||
<text class="link" @click.stop="goUserAgreement">《用户协议》</text>
|
||
<text>与</text>
|
||
<text class="link" @click.stop="goPrivacyPolicy">《隐私政策》</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { useUserStore } from '@/store/user.js'
|
||
import { post } from '@/api/request.js'
|
||
import Navbar from '@/components/Navbar/index.vue'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
// 是否同意协议
|
||
const isAgreed = ref(false)
|
||
|
||
// 登录后重定向地址
|
||
const redirectUrl = ref('')
|
||
|
||
/**
|
||
* 切换协议勾选状态
|
||
*/
|
||
function toggleAgreement() {
|
||
isAgreed.value = !isAgreed.value
|
||
}
|
||
|
||
/**
|
||
* 跳转用户协议
|
||
*/
|
||
function goUserAgreement() {
|
||
uni.navigateTo({
|
||
url: '/pages/agreement/user/index'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 跳转隐私政策
|
||
*/
|
||
function goPrivacyPolicy() {
|
||
uni.navigateTo({
|
||
url: '/pages/agreement/privacy/index'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 处理获取手机号回调
|
||
* @param {Object} e - 回调事件对象
|
||
*/
|
||
async function handleGetPhoneNumber(e) {
|
||
if (!isAgreed.value) {
|
||
uni.showToast({
|
||
title: '请先同意用户协议和隐私政策',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 用户拒绝授权
|
||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||
uni.showToast({
|
||
title: '需要授权手机号才能登录',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 1. 获取微信登录code
|
||
const loginRes = await new Promise((resolve, reject) => {
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
|
||
if (!loginRes.code) {
|
||
uni.showToast({ title: '获取登录凭证失败', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '登录中...' })
|
||
|
||
// 2. 调用后端登录接口,传递 code 和手机号 code
|
||
const res = await post('/login', {
|
||
code: loginRes.code,
|
||
phoneCode: e.detail.code // 手机号授权 code
|
||
}, { needAuth: false })
|
||
|
||
uni.hideLoading()
|
||
|
||
if (res && res.code === 0 && res.data) {
|
||
// 保存登录信息(LoginResponse 只有 token 和 userId)
|
||
userStore.login({
|
||
token: res.data.token,
|
||
refreshToken: res.data.refreshToken,
|
||
userInfo: {
|
||
userId: res.data.userId
|
||
}
|
||
})
|
||
|
||
// 登录成功后获取完整用户信息
|
||
await userStore.fetchUserInfo()
|
||
|
||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||
|
||
// 延迟返回
|
||
setTimeout(() => {
|
||
if (redirectUrl.value) {
|
||
uni.redirectTo({
|
||
url: redirectUrl.value,
|
||
fail: () => {
|
||
uni.switchTab({
|
||
url: redirectUrl.value,
|
||
fail: () => {
|
||
uni.navigateBack()
|
||
}
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
uni.navigateBack()
|
||
}
|
||
}, 1000)
|
||
} else {
|
||
uni.showToast({
|
||
title: res?.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('登录失败:', error)
|
||
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理登录(备用方法,用于不需要手机号的场景)
|
||
*/
|
||
async function handleLogin() {
|
||
if (!isAgreed.value) {
|
||
uni.showToast({
|
||
title: '请先同意用户协议和隐私政策',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 1. 获取微信登录code
|
||
const loginRes = await new Promise((resolve, reject) => {
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
|
||
if (!loginRes.code) {
|
||
uni.showToast({ title: '获取登录凭证失败', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: '登录中...' })
|
||
|
||
// 2. 调用后端登录接口
|
||
const res = await post('/login', {
|
||
code: loginRes.code
|
||
}, { needAuth: false })
|
||
|
||
uni.hideLoading()
|
||
|
||
if (res && res.code === 0 && res.data) {
|
||
// 保存登录信息(LoginResponse 只有 token 和 userId)
|
||
userStore.login({
|
||
token: res.data.token,
|
||
refreshToken: res.data.refreshToken,
|
||
userInfo: {
|
||
userId: res.data.userId
|
||
}
|
||
})
|
||
|
||
// 登录成功后获取完整用户信息
|
||
await userStore.fetchUserInfo()
|
||
|
||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||
|
||
// 延迟返回
|
||
setTimeout(() => {
|
||
if (redirectUrl.value) {
|
||
uni.redirectTo({
|
||
url: redirectUrl.value,
|
||
fail: () => {
|
||
uni.switchTab({
|
||
url: redirectUrl.value,
|
||
fail: () => {
|
||
uni.navigateBack()
|
||
}
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
uni.navigateBack()
|
||
}
|
||
}, 1000)
|
||
} else {
|
||
uni.showToast({
|
||
title: res?.message || '登录失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('登录失败:', error)
|
||
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 页面加载
|
||
*/
|
||
onMounted(() => {
|
||
// 获取重定向参数
|
||
const pages = getCurrentPages()
|
||
const currentPage = pages[pages.length - 1]
|
||
const options = currentPage.options || {}
|
||
|
||
if (options.redirect) {
|
||
redirectUrl.value = decodeURIComponent(options.redirect)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/styles/variables.scss';
|
||
|
||
// 登录按钮橙色(设计图配色)
|
||
$login-btn-color: #F5A623;
|
||
$login-btn-active: #E09518;
|
||
|
||
.login-page {
|
||
min-height: 100vh;
|
||
background-color: $bg-white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.login-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0 60rpx;
|
||
}
|
||
|
||
.logo-section {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
|
||
.logo-wrapper {
|
||
width: 360rpx;
|
||
height: 360rpx;
|
||
border: 2rpx solid $border-color;
|
||
border-radius: $border-radius-md;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.logo-img {
|
||
width: 280rpx;
|
||
height: 280rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-section {
|
||
padding-bottom: 120rpx;
|
||
|
||
.btn-login {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
background-color: $login-btn-color;
|
||
border-radius: 44rpx;
|
||
font-size: $font-size-lg;
|
||
color: $text-white;
|
||
border: none;
|
||
font-weight: $font-weight-medium;
|
||
letter-spacing: 2rpx;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&:active {
|
||
background-color: $login-btn-active;
|
||
}
|
||
}
|
||
|
||
.btn-disabled {
|
||
background-color: #CCCCCC;
|
||
pointer-events: none;
|
||
}
|
||
}
|
||
|
||
.agreement-section {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 32rpx;
|
||
|
||
.checkbox {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
border: 2rpx solid $border-color;
|
||
border-radius: 50%;
|
||
margin-right: 10rpx;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
|
||
&-checked {
|
||
border-color: $success-color;
|
||
background-color: $success-color;
|
||
}
|
||
|
||
.checkbox-icon {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
}
|
||
}
|
||
|
||
.agreement-text {
|
||
font-size: $font-size-sm;
|
||
color: $text-placeholder;
|
||
line-height: 1.6;
|
||
|
||
.link {
|
||
color: $primary-color;
|
||
}
|
||
}
|
||
}
|
||
</style>
|