ruoyi-geek-App/pages_system/pages/login/login.vue

473 lines
11 KiB
Vue

<template>
<view class="login-container">
<!-- 顶部标题 -->
<view class="login-header">
<text class="login-title">欢迎登录</text>
</view>
<!-- 登录方式切换 -->
<view class="login-tabs">
<view
class="login-tab"
:class="{ 'active': loginType === 'account' }"
@click="loginType = 'account'"
>
账号密码
</view>
<view
class="login-tab"
:class="{ 'active': loginType === 'phone' }"
@click="loginType = 'phone'"
>
手机验证码
</view>
<view
class="login-tab"
:class="{ 'active': loginType === 'wx' }"
@click="loginType = 'wx'"
>
微信登录
</view>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 账号密码登录 -->
<view v-if="loginType === 'account'">
<view class="form-item">
<input
v-model="form.username"
type="text"
placeholder="请输入用户名/手机号"
class="input"
/>
</view>
<view class="form-item">
<input
v-model="form.password"
type="password"
placeholder="请输入密码"
class="input"
/>
</view>
<view class="form-item captcha">
<input
v-model="form.code"
type="text"
placeholder="验证码"
class="input"
/>
<image
:src="captchaUrl"
@click="refreshCaptcha"
class="captcha-img"
/>
</view>
<button
class="login-btn"
:disabled="isLogining"
@click="handleLogin"
>
{{ isLogining ? '登录中...' : '登录' }}
</button>
</view>
<!-- 手机验证码登录 -->
<view v-else-if="loginType === 'phone'">
<view class="form-item">
<input
v-model="form.phone"
type="tel"
placeholder="请输入手机号"
class="input"
/>
</view>
<view class="form-item">
<input
v-model="form.code"
type="number"
placeholder="请输入验证码"
class="input"
/>
<text
v-if="countdown > 0"
class="countdown"
>{{ countdown }}s</text>
<text
v-else
class="send-code"
@click="sendCode"
>发送验证码</text>
</view>
<button
class="login-btn"
:disabled="isLogining"
@click="handleLogin"
>
{{ isLogining ? '登录中...' : '登录' }}
</button>
</view>
<!-- 微信登录 -->
<view v-else-if="loginType === 'wx'">
<button
class="wx-login-btn"
@click="wxLogin"
:disabled="isLogining"
>
<image src="/static/icon-wechat.png" class="wx-icon" />
微信一键登录
</button>
<view class="one-click-login" @click="getPhone">
<image src="/static/icon-phone.png" class="phone-icon" />
本机一键登录
</view>
</view>
</view>
<!-- 底部链接 -->
<view class="login-footer">
<text class="link" @click="toRegister">注册新账号</text>
<text class="link" @click="toForget">忘记密码</text>
</view>
</view>
</template>
<script>
import { ref, onMounted } from 'vue';
import { login, getCodeImg, sendPhoneCode, verifyPhoneCode } from '@/api/login';
import { register } from '@/api/system/user';
import { getWxCode } from '@/utils/geek';
import { wxLogin, wxRegister } from '@/api/oauth';
import { setToken } from '@/utils/auth';
import { useUserStore } from '@/store/modules/user';
import modal from '@/plugins/modal';
export default {
setup() {
// 当前登录方式 (account/phone/wx)
const loginType = ref('account');
// 表单数据
const form = ref({
username: '',
password: '',
phone: '',
code: '',
captcha: ''
});
// 验证码图片
const captchaUrl = ref('');
// 倒计时(用于发送验证码)
const countdown = ref(0);
// 登录中状态
const isLogining = ref(false);
// 用户store
const userStore = useUserStore();
// 初始化验证码
const initCaptcha = () => {
getCodeImg().then(res => {
captchaUrl.value = res;
}).catch(() => {
modal.toast('验证码加载失败');
});
};
// 刷新验证码
const refreshCaptcha = () => {
initCaptcha();
};
// 发送手机验证码
const sendCode = () => {
if (!form.value.phone) {
modal.toast('请输入手机号');
return;
}
sendPhoneCode({ phone: form.value.phone }).then(() => {
modal.toast('验证码已发送');
countdown.value = 60;
const timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) clearInterval(timer);
}, 1000);
}).catch(err => {
modal.toast(err.message || '发送失败');
});
};
// 处理登录
const handleLogin = async () => {
isLogining.value = true;
try {
if (loginType.value === 'account') {
// 账号密码登录
if (!form.value.username || !form.value.password || !form.value.code) {
modal.toast('请填写完整信息');
return;
}
const res = await login(
form.value.username,
form.value.password,
form.value.code,
''
);
handleLoginSuccess(res);
} else if (loginType.value === 'phone') {
// 手机验证码登录
if (!form.value.phone || !form.value.code) {
modal.toast('请填写完整信息');
return;
}
// 1. 验证手机验证码
await verifyPhoneCode({ phone: form.value.phone, code: form.value.code });
// 2. 检查用户是否存在(自动注册逻辑)
try {
// 尝试用手机号登录(后端会自动注册新用户)
const res = await login(
form.value.phone,
'123456', // 默认密码(首次登录自动注册时使用)
'',
''
);
handleLoginSuccess(res);
} catch (err) {
// 用户不存在,自动注册
await register({
username: form.value.phone,
phonenumber: form.value.phone,
password: '123456'
});
// 注册后重新登录
const res = await login(
form.value.phone,
'123456',
'',
''
);
handleLoginSuccess(res);
}
}
} catch (err) {
modal.toast(err.message || '登录失败');
} finally {
isLogining.value = false;
}
};
// 处理登录成功
const handleLoginSuccess = (res) => {
setToken(res.token);
userStore.setUserInfo(res.user);
uni.switchTab({ url: '/pages/index' });
};
// 微信登录
const wxLogin = async () => {
isLogining.value = true;
try {
const code = await getWxCode();
const res = await wxLogin('pub', code); // pub: 公众号/小程序
if (res.token) {
handleLoginSuccess(res);
} else {
modal.toast('微信登录失败,请重试');
}
} catch (err) {
modal.toast('微信登录异常: ' + err.message);
} finally {
isLogining.value = false;
}
};
// 本机一键登录(获取手机号)
const getPhone = async () => {
try {
const res = await uni.getPhoneNumber();
if (res.code === 0) {
form.value.phone = res.code;
modal.toast('手机号获取成功');
} else {
modal.toast('获取手机号失败');
}
} catch (err) {
modal.toast('获取手机号异常: ' + err.message);
}
};
// 跳转到注册页面
const toRegister = () => {
uni.navigateTo({
url: '/pages_system/pages/login/register'
});
};
// 跳转到忘记密码页面
const toForget = () => {
uni.navigateTo({
url: '/pages_system/pages/login/forget'
});
};
onMounted(() => {
initCaptcha();
});
return {
loginType,
form,
captchaUrl,
countdown,
isLogining,
handleLogin,
sendCode,
refreshCaptcha,
wxLogin,
getPhone,
toRegister,
toForget
};
}
};
</script>
<style scoped>
.login-container {
padding: 40rpx;
background: #f5f5f5;
min-height: 100vh;
}
.login-header {
text-align: center;
margin-bottom: 80rpx;
}
.login-title {
font-size: 40rpx;
font-weight: bold;
color: #333;
}
.login-tabs {
display: flex;
justify-content: space-around;
margin-bottom: 60rpx;
}
.login-tab {
padding: 16rpx 32rpx;
border-radius: 40rpx;
border: 1px solid #ddd;
font-size: 28rpx;
color: #666;
}
.login-tab.active {
background: #007AFF;
color: white;
border-color: #007AFF;
}
.login-form {
background: white;
border-radius: 20rpx;
padding: 40rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.08);
}
.form-item {
margin-bottom: 30rpx;
position: relative;
}
.input {
width: 100%;
padding: 20rpx;
border: 1px solid #eee;
border-radius: 12rpx;
font-size: 28rpx;
}
.captcha {
display: flex;
align-items: center;
}
.captcha-img {
width: 200rpx;
height: 80rpx;
margin-left: 20rpx;
border-radius: 10rpx;
background: #f0f0f0;
}
.countdown {
color: #007AFF;
font-size: 26rpx;
margin-left: 10rpx;
}
.send-code {
color: #007AFF;
font-size: 26rpx;
margin-left: 10rpx;
}
.login-btn {
background: #007AFF;
color: white;
border-radius: 12rpx;
font-size: 32rpx;
height: 88rpx;
line-height: 88rpx;
}
.wx-login-btn {
background: #07C160;
color: white;
border-radius: 12rpx;
font-size: 32rpx;
height: 88rpx;
line-height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.wx-icon {
width: 40rpx;
height: 40rpx;
}
.one-click-login {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
color: #666;
font-size: 28rpx;
margin-top: 30rpx;
padding: 16rpx;
border-radius: 12rpx;
background: #f5f5f5;
}
.phone-icon {
width: 36rpx;
height: 36rpx;
}
.login-footer {
text-align: center;
margin-top: 60rpx;
color: #999;
font-size: 28rpx;
}
.link {
color: #007AFF;
margin: 0 20rpx;
}
</style>