后台登录已经成功

This commit is contained in:
2025-08-31 21:09:33 +08:00
parent c658033023
commit 5b5d65e072
10 changed files with 3984 additions and 6 deletions

View File

@@ -0,0 +1,712 @@
<template>
<view class="login-page">
<!-- 顶部背景 -->
<view class="login-header">
<image src="/static/auth/login-bg.png" class="header-bg" mode="aspectFill"></image>
<view class="header-content">
<text class="welcome-text">欢迎来到结伴客</text>
<text class="subtitle">开启您的结伴之旅</text>
</view>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 手机号登录 -->
<view class="form-section" v-if="loginType === 'phone'">
<view class="input-group">
<view class="input-item">
<uni-icons type="phone" size="20" color="#999"></uni-icons>
<input
type="number"
v-model="phoneForm.phone"
placeholder="请输入手机号码"
placeholder-class="placeholder"
maxlength="11"
/>
</view>
<view class="input-item">
<uni-icons type="locked" size="20" color="#999"></uni-icons>
<input
type="number"
v-model="phoneForm.code"
placeholder="请输入验证码"
placeholder-class="placeholder"
maxlength="6"
/>
<view
class="send-code-btn"
:class="{ disabled: !canSendCode }"
@click="sendCode"
>
{{ codeCountdown > 0 ? `${codeCountdown}s` : '获取验证码' }}
</view>
</view>
</view>
<button class="login-btn" :class="{ loading: loading }" @click="handlePhoneLogin">
<text v-if="!loading">登录</text>
<uni-icons v-else type="spinner-cycle" size="20" color="#fff"></uni-icons>
</button>
</view>
<!-- 微信一键登录 -->
<view class="form-section" v-else-if="loginType === 'wechat'">
<view class="wechat-login-tip">
<text>一键授权快速登录</text>
</view>
<button class="wechat-login-btn" open-type="getPhoneNumber" @getphonenumber="handleWechatLogin">
<uni-icons type="weixin" size="24" color="#fff"></uni-icons>
<text>微信一键登录</text>
</button>
</view>
<!-- 其他登录方式 -->
<view class="other-login-methods">
<text class="divider">其他登录方式</text>
<view class="method-list">
<view class="method-item" @click="switchLoginType('phone')" v-if="loginType !== 'phone'">
<uni-icons type="phone" size="24" color="#007aff"></uni-icons>
<text>手机验证码登录</text>
</view>
<view class="method-item" @click="switchLoginType('wechat')" v-if="loginType !== 'wechat'">
<uni-icons type="weixin" size="24" color="#07c160"></uni-icons>
<text>微信一键登录</text>
</view>
<view class="method-item" @click="navigateToPasswordLogin">
<uni-icons type="locked" size="24" color="#ff9500"></uni-icons>
<text>密码登录</text>
</view>
</view>
</view>
<!-- 服务协议 -->
<view class="agreement">
<view class="agreement-checkbox" @click="toggleAgreement">
<uni-icons
:type="agreed ? 'checkbox-filled' : 'circle'"
size="16"
:color="agreed ? '#007aff' : '#999'"
></uni-icons>
<text>我已阅读并同意</text>
</view>
<text class="agreement-link" @click="viewAgreement">用户服务协议</text>
<text></text>
<text class="agreement-link" @click="viewPrivacy">隐私政策</text>
</view>
</view>
<!-- 游客模式 -->
<view class="guest-mode">
<text class="guest-text" @click="enterAsGuest">先逛逛稍后登录</text>
</view>
<!-- 协议弹窗 -->
<uni-popup ref="agreementPopup" type="bottom">
<view class="agreement-popup">
<view class="popup-header">
<text class="popup-title">用户服务协议</text>
<uni-icons type="close" size="20" color="#999" @click="closeAgreementPopup"></uni-icons>
</view>
<scroll-view class="popup-content" scroll-y="true">
<text class="agreement-content">
用户服务协议内容...
请您务必审慎阅读充分理解各条款内容特别是免除或者限制责任的条款以及开通或使用某项服务的单独协议并选择接受或不接受
</text>
</scroll-view>
<view class="popup-actions">
<button class="agree-btn" @click="agreeAndClose">同意并关闭</button>
</view>
</view>
</uni-popup>
<!-- 隐私政策弹窗 -->
<uni-popup ref="privacyPopup" type="bottom">
<view class="privacy-popup">
<view class="popup-header">
<text class="popup-title">隐私政策</text>
<uni-icons type="close" size="20" color="#999" @click="closePrivacyPopup"></uni-icons>
</view>
<scroll-view class="popup-content" scroll-y="true">
<text class="privacy-content">
隐私政策内容...
我们深知个人信息对您的重要性并会尽全力保护您的个人信息安全可靠我们致力于维持您对我们的信任恪守以下原则保护您的个人信息权责一致原则目的明确原则选择同意原则最少够用原则确保安全原则主体参与原则公开透明原则等
</text>
</scroll-view>
<view class="popup-actions">
<button class="agree-btn" @click="agreeAndClose">同意并关闭</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { authService } from '../../api/services.js'
export default {
data() {
return {
loginType: 'phone', // phone, wechat
agreed: false,
loading: false,
// 手机登录表单
phoneForm: {
phone: '',
code: ''
},
// 验证码倒计时
codeCountdown: 0,
canSendCode: true,
// 微信登录数据
wechatForm: {
code: '',
encryptedData: '',
iv: ''
}
}
},
onLoad(options) {
// 检查是否有重定向参数
if (options.redirect) {
this.redirectUrl = decodeURIComponent(options.redirect)
}
// 检查是否已登录
this.checkLoginStatus()
},
onShow() {
// 页面显示时检查登录状态
this.checkLoginStatus()
},
methods: {
async checkLoginStatus() {
try {
const token = uni.getStorageSync('token')
if (token) {
// 验证token是否有效
const isValid = await authService.checkToken(token)
if (isValid) {
this.navigateAfterLogin()
return
}
}
} catch (error) {
console.error('检查登录状态失败:', error)
}
},
async sendCode() {
if (!this.phoneForm.phone) {
uni.showToast({
title: '请输入手机号码',
icon: 'none'
})
return
}
if (!/^1[3-9]\d{9}$/.test(this.phoneForm.phone)) {
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
})
return
}
if (this.codeCountdown > 0) {
return
}
this.loading = true
try {
await authService.sendSmsCode(this.phoneForm.phone)
uni.showToast({
title: '验证码已发送',
icon: 'success'
})
// 开始倒计时
this.startCountdown()
} catch (error) {
console.error('发送验证码失败:', error)
uni.showToast({
title: '发送失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
startCountdown() {
this.codeCountdown = 60
this.canSendCode = false
const timer = setInterval(() => {
this.codeCountdown--
if (this.codeCountdown <= 0) {
clearInterval(timer)
this.canSendCode = true
}
}, 1000)
},
async handlePhoneLogin() {
if (!this.agreed) {
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
})
return
}
if (!this.phoneForm.phone) {
uni.showToast({
title: '请输入手机号码',
icon: 'none'
})
return
}
if (!this.phoneForm.code) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
})
return
}
this.loading = true
try {
const result = await authService.phoneLogin({
phone: this.phoneForm.phone,
code: this.phoneForm.code
})
await this.handleLoginSuccess(result)
} catch (error) {
console.error('手机登录失败:', error)
uni.showToast({
title: error.message || '登录失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
async handleWechatLogin(e) {
if (!this.agreed) {
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
})
return
}
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
console.error('微信登录失败:', e.detail)
return
}
this.loading = true
try {
// 获取微信code
const loginRes = await uni.login({
provider: 'weixin'
})
const result = await authService.wechatLogin({
code: loginRes.code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv
})
await this.handleLoginSuccess(result)
} catch (error) {
console.error('微信登录失败:', error)
uni.showToast({
title: error.message || '登录失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
async handleLoginSuccess(result) {
// 保存token和用户信息
uni.setStorageSync('token', result.token)
uni.setStorageSync('userInfo', result.userInfo)
// 显示登录成功提示
uni.showToast({
title: '登录成功',
icon: 'success'
})
// 导航到目标页面
await this.navigateAfterLogin()
// 触发全局登录事件
uni.$emit('loginSuccess', result.userInfo)
},
async navigateAfterLogin() {
// 延迟一下让Toast显示完整
await new Promise(resolve => setTimeout(resolve, 1500))
if (this.redirectUrl) {
// 跳转到重定向页面
uni.redirectTo({
url: this.redirectUrl
})
} else {
// 获取页面栈,决定返回还是跳转到首页
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({
url: '/pages/index/index'
})
}
}
},
switchLoginType(type) {
this.loginType = type
},
navigateToPasswordLogin() {
uni.navigateTo({
url: '/pages/auth/password-login'
})
},
toggleAgreement() {
this.agreed = !this.agreed
},
viewAgreement() {
this.$refs.agreementPopup.open()
},
viewPrivacy() {
this.$refs.privacyPopup.open()
},
closeAgreementPopup() {
this.$refs.agreementPopup.close()
},
closePrivacyPopup() {
this.$refs.privacyPopup.close()
},
agreeAndClose() {
this.agreed = true
this.closeAgreementPopup()
this.closePrivacyPopup()
},
enterAsGuest() {
// 设置游客标识
uni.setStorageSync('isGuest', true)
// 跳转到首页
uni.switchTab({
url: '/pages/index/index'
})
}
}
}
</script>
<style scoped>
.login-page {
background-color: #f8f9fa;
min-height: 100vh;
}
.login-header {
height: 300rpx;
position: relative;
}
.header-bg {
width: 100%;
height: 100%;
}
.header-content {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
text-align: center;
color: #fff;
}
.welcome-text {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.subtitle {
font-size: 28rpx;
opacity: 0.9;
}
.login-form {
padding: 40rpx 30rpx;
}
.form-section {
background-color: #fff;
border-radius: 20rpx;
padding: 40rpx 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.input-group {
margin-bottom: 40rpx;
}
.input-item {
display: flex;
align-items: center;
height: 80rpx;
border-bottom: 1rpx solid #eee;
margin-bottom: 30rpx;
}
.input-item:last-child {
margin-bottom: 0;
}
.input-item uni-icons {
margin-right: 15rpx;
}
.input-item input {
flex: 1;
font-size: 28rpx;
height: 100%;
}
.placeholder {
font-size: 28rpx;
color: #999;
}
.send-code-btn {
padding: 8rpx 20rpx;
background-color: #007aff;
color: #fff;
border-radius: 20rpx;
font-size: 24rpx;
}
.send-code-btn.disabled {
background-color: #ccc;
}
.login-btn {
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
color: #fff;
border: none;
border-radius: 40rpx;
height: 80rpx;
font-size: 28rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.login-btn.loading {
opacity: 0.7;
}
.wechat-login-tip {
text-align: center;
margin-bottom: 40rpx;
font-size: 26rpx;
color: #666;
}
.wechat-login-btn {
background: linear-gradient(135deg, #07c160 0%, #059c4d 100%);
color: #fff;
border: none;
border-radius: 40rpx;
height: 80rpx;
font-size: 28rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.wechat-login-btn uni-icons {
margin-right: 10rpx;
}
.other-login-methods {
background-color: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.divider {
display: block;
text-align: center;
font-size: 26rpx;
color: #999;
margin-bottom: 30rpx;
position: relative;
}
.divider::before,
.divider::after {
content: '';
position: absolute;
top: 50%;
width: 100rpx;
height: 1rpx;
background-color: #eee;
}
.divider::before {
left: 50rpx;
}
.divider::after {
right: 50rpx;
}
.method-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
.method-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
border: 1rpx solid #eee;
border-radius: 12rpx;
background-color: #fafafa;
}
.method-item uni-icons {
margin-bottom: 15rpx;
}
.method-item text {
font-size: 24rpx;
color: #666;
}
.agreement {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
font-size: 24rpx;
color: #999;
margin-bottom: 40rpx;
}
.agreement-checkbox {
display: flex;
align-items: center;
margin-right: 5rpx;
}
.agreement-checkbox uni-icons {
margin-right: 5rpx;
}
.agreement-link {
color: #007aff;
margin: 0 5rpx;
}
.guest-mode {
text-align: center;
padding: 20rpx;
}
.guest-text {
font-size: 26rpx;
color: #007aff;
}
/* 协议弹窗样式 */
.agreement-popup,
.privacy-popup {
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
max-height: 80vh;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-content {
padding: 30rpx;
max-height: 50vh;
}
.agreement-content,
.privacy-content {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.popup-actions {
padding: 20rpx 30rpx 40rpx;
}
.agree-btn {
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
color: #fff;
border: none;
border-radius: 40rpx;
height: 80rpx;
font-size: 28rpx;
font-weight: bold;
}
</style>