ruoyi-geek-App/pages/login.vue

612 lines
12 KiB
Vue
Raw Normal View History

2025-11-26 15:41:57 +00:00
<template>
<view class="login-container">
<view class="login-header">
<text class="app-name">{{ appName }}</text>
</view>
<view class="login-content">
<!-- 登录方式切换 -->
<view class="login-tabs">
<view class="tab-item" :class="{ active: activeTab === 'account' }" @click="switchTab('account')">
账号登录
</view>
<view class="tab-item" :class="{ active: activeTab === 'phone' }" @click="switchTab('phone')">
手机登录
</view>
</view>
<!-- 账号密码登录 -->
<view v-if="activeTab === 'account'" class="login-form">
<view class="form-item">
<text class="label">账号</text>
<input v-model="accountForm.username" class="input" placeholder="请输入用户名/手机号/邮箱"
placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">密码</text>
<input v-model="accountForm.password" class="input" password placeholder="请输入密码"
placeholder-class="placeholder" @confirm="handleAccountLogin" />
</view>
<view class="form-item">
<text class="label">验证码</text>
<view class="captcha-input">
<input v-model="accountForm.code" class="input" placeholder="请输入验证码"
placeholder-class="placeholder" />
<image :src="accountForm.captchaImg" class="captcha-img" @click="getCaptcha" mode="aspectFit">
</image>
</view>
</view>
<view class="form-options">
<text class="forget-pwd" @click="goForgetPwd">忘记密码?</text>
</view>
<button class="login-btn" @click="handleAccountLogin" :disabled="accountLogining">
{{ accountLogining ? '登录中...' : '登录' }}
</button>
</view>
<!-- 手机验证码登录 -->
<view v-if="activeTab === 'phone'" class="login-form">
<view class="form-item">
<text class="label">手机号</text>
<input v-model="phoneForm.phone" class="input" type="number" placeholder="请输入手机号"
placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">验证码</text>
<view class="code-input">
<input v-model="phoneForm.code" class="input" type="number" placeholder="请输入验证码"
placeholder-class="placeholder" />
<button class="send-code-btn" :disabled="phoneForm.countdown > 0" @click="handlesendPhoneCode">
{{ phoneForm.countdown > 0 ? `${phoneForm.countdown}s` : '获取验证码' }}
</button>
</view>
</view>
<button class="login-btn" @click="handlePhoneLogin" :disabled="phoneLogining">
{{ phoneLogining ? '登录中...' : '登录' }}
</button>
</view>
<!-- 快捷登录方式 -->
<view class="quick-login">
<view class="divider">
<text class="divider-text">快捷登录</text>
</view>
<view class="login-methods">
<view class="method-item" @click="handleOneClickLogin">
<view class="method-icon one-click">
<text class="iconfont"></text>
</view>
<text class="method-text">本机一键登录</text>
</view>
<view class="method-item" @click="handleWechatLogin">
<view class="method-icon wechat">
<text class="iconfont">💬</text>
</view>
<text class="method-text">微信登录</text>
</view>
</view>
</view>
<!-- 注册链接 -->
<view class="register-link">
<text>还没有账号? </text>
<text class="link" @click="goRegister">立即注册</text>
</view>
</view>
</view>
2025-11-24 14:57:53 +00:00
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue'
import {
onLoad,
onShow
} from '@dcloudio/uni-app'
import modal from '@/plugins/modal'
import {
getCodeImg,
login,
sendPhoneCode,
verifyPhoneCode
} from '@/api/login'
import {
getWxCode
} from '@/utils/geek'
import {
wxLogin
} from '@/api/oauth'
import useUserStore from '@/store/modules/user'
import {
setToken
} from '@/utils/auth'
import config from '@/config.js'
const userStore = useUserStore()
const appName = config.appInfo.name || '天然气工具箱'
// 响应式数据
const activeTab = ref('account')
const accountLogining = ref(false)
const phoneLogining = ref(false)
// 账号登录表单
const accountForm = reactive({
username: '',
password: '',
code: '',
captchaImg: '',
uuid: '',
showCaptcha: false
})
// 手机登录表单
const phoneForm = reactive({
phone: '',
code: '',
countdown: 0,
timer: null
})
// 切换登录方式
const switchTab = (tab) => {
activeTab.value = tab
if (tab === 'account' && !accountForm.captchaImg) {
getCaptcha()
}
}
// 获取验证码
const getCaptcha = async () => {
try {
const res = await getCodeImg()
accountForm.captchaImg = 'data:image/gif;base64,' + res.img
accountForm.uuid = res.uuid
accountForm.showCaptcha = true
} catch (error) {
modal.alert('验证码获取失败')
}
}
// 发送手机验证码
const handlesendPhoneCode = async () => {
if (!phoneForm.phone) {
modal.alert('请输入手机号')
return
}
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
modal.alert('请输入正确的手机号')
return
}
try {
await sendPhoneCode({
phone: phoneForm.phone
}, 'login')
modal.alert('验证码已发送')
// 开始倒计时
phoneForm.countdown = 60
phoneForm.timer = setInterval(() => {
phoneForm.countdown--
if (phoneForm.countdown <= 0) {
clearInterval(phoneForm.timer)
}
}, 1000)
} catch (error) {
modal.alert(error.message || '验证码发送失败')
}
}
// 账号密码登录
const handleAccountLogin = async () => {
if (!accountForm.username) {
modal.alert('请输入账号')
return
}
if (!accountForm.password) {
modal.alert('请输入密码')
return
}
if (accountForm.showCaptcha && !accountForm.code) {
modal.alert('请输入验证码')
return
}
accountLogining.value = true
try {
const params = {
username: accountForm.username,
password: accountForm.password
}
if (accountForm.showCaptcha) {
params.code = accountForm.code
params.uuid = accountForm.uuid
}
const res = await login(params.username, params.password, params.code, params.uuid)
if (res.token) {
setToken(res.token)
await userStore.getInfo()
uni.switchTab({
url: '/pages/index'
});
}
} catch (error) {
// 如果需要验证码,重新获取
if (error.code === 401 || error.message?.includes('验证码')) {
getCaptcha()
}
modal.alert(error.message || '登录失败')
} finally {
accountLogining.value = false
}
}
// 手机验证码登录
const handlePhoneLogin = async () => {
if (!phoneForm.phone) {
modal.alert('请输入手机号')
return
}
if (!phoneForm.code) {
modal.alert('请输入验证码')
return
}
phoneLogining.value = true
try {
// 验证验证码
await verifyPhoneCode({
phone: phoneForm.phone,
code: phoneForm.code
}, 'login')
// 验证成功后执行登录
const res = await login(phoneForm.phone, '', '', '')
if (res.token) {
setToken(res.token)
await userStore.getInfo()
uni.switchTab({
url: '/pages/index'
});
}
} catch (error) {
modal.alert(error.message || '登录失败')
} finally {
phoneLogining.value = false
}
}
// 本机一键登录
const handleOneClickLogin = async () => {
try {
// 这里需要调用uni的一键登录API
// 由于不同平台实现不同,这里使用模拟实现
modal.confirm('是否使用本机号码一键登录?', async () => {
try {
// 获取本机号码实际项目中需要调用运营商SDK
const phoneNumber = '' // 从SDK获取
if (!phoneNumber) {
modal.alert('无法获取本机号码')
return
}
// 执行一键登录
const res = await login(phoneNumber, '', '', '')
if (res.token) {
setToken(res.token)
await userStore.getInfo()
uni.switchTab({
url: '/pages/index'
});
}
} catch (error) {
modal.alert(error.message || '一键登录失败')
}
})
} catch (error) {
modal.alert('一键登录功能暂不可用')
}
}
// 微信登录
const handleWechatLogin = async () => {
try {
const code = await getWxCode()
const res = await wxLogin('miniapp', code)
if (res.token) {
setToken(res.token)
await userStore.getInfo()
uni.switchTab({
url: '/pages/index'
});
}
} catch (error) {
if (error.code === 400) {
// 需要绑定微信
modal.confirm('首次使用微信登录,需要绑定账号', () => {
uni.navigateTo({
url: '/pages_system/pages/login/bind-account'
})
})
} else {
modal.alert(error.message || '微信登录失败')
}
}
}
// 跳转注册页面
const goRegister = () => {
uni.navigateTo({
url: '/pages_system/pages/register/index'
})
}
// 跳转忘记密码页面
const goForgetPwd = () => {
uni.navigateTo({
url: '/pages_system/pages/forget/index'
})
}
// 生命周期
onMounted(() => {
getCaptcha()
})
onLoad(() => {
// 检查是否有token如果有则直接跳转
const token = uni.getStorageSync('token')
if (token) {
uni.reLaunch({
url: '/pages/index'
})
}
})
2025-11-24 14:57:53 +00:00
</script>
2025-11-26 15:41:57 +00:00
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60rpx 40rpx;
}
.login-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 80rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
}
.app-name {
font-size: 36rpx;
color: #fff;
font-weight: bold;
}
.login-content {
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
}
.login-tabs {
display: flex;
margin-bottom: 40rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx;
font-size: 32rpx;
color: #999;
position: relative;
}
.tab-item.active {
color: #007aff;
font-weight: bold;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: -2rpx;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #007aff;
border-radius: 2rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.input {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.placeholder {
color: #ccc;
}
.captcha-input,
.code-input {
display: flex;
align-items: center;
gap: 20rpx;
}
.captcha-img {
width: 200rpx;
height: 80rpx;
border-radius: 10rpx;
border: 2rpx solid #e0e0e0;
}
.send-code-btn {
width: 200rpx;
height: 80rpx;
background: #007aff;
color: #fff;
border: none;
border-radius: 10rpx;
font-size: 24rpx;
white-space: nowrap;
}
.send-code-btn[disabled] {
background: #ccc;
}
.form-options {
display: flex;
justify-content: flex-end;
margin-bottom: 40rpx;
}
.forget-pwd {
color: #007aff;
font-size: 26rpx;
}
.login-btn {
width: 100%;
height: 88rpx;
background: #007aff;
color: #fff;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 40rpx;
}
.login-btn[disabled] {
background: #ccc;
}
.quick-login {
margin-bottom: 40rpx;
}
.divider {
position: relative;
text-align: center;
margin: 40rpx 0;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1rpx;
background: #e0e0e0;
}
.divider-text {
background: #fff;
padding: 0 20rpx;
color: #999;
font-size: 24rpx;
}
.login-methods {
display: flex;
justify-content: center;
gap: 80rpx;
}
.method-item {
display: flex;
flex-direction: column;
align-items: center;
}
.method-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
font-size: 40rpx;
}
.method-icon.one-click {
background: #ff9500;
color: #fff;
}
.method-icon.wechat {
background: #07c160;
color: #fff;
}
.method-text {
font-size: 24rpx;
color: #666;
}
.register-link {
text-align: center;
font-size: 26rpx;
color: #666;
}
.link {
color: #007aff;
}
2025-11-26 15:41:57 +00:00
</style>