CarRental/pages/login/login.vue
2025-01-10 10:05:41 +08:00

760 lines
16 KiB
Vue

<template>
<view class="login-page">
<u-navbar title="登录" :border-bottom="false" :background="bgc" title-color='#262B37' title-size='38'
height='50' :custom-back='backPage'></u-navbar>
<view v-if="pageIndex == 1">
<image class="background-image" src="https://lxnapi.ccttiot.com/bike/img/static/uWOnjtkaVDDLhY4PtMd8"
mode="aspectFill">
</image>
<view class="overlay">
<view class="container">
<!-- 登录/注册切换标签 -->
<view class="tab-bar-box" v-if="!isRegister">
<view class="tab-item" @tap="setIsCodeLogin(true)">
<image :src="isCodeLogin ?
'https://lxnapi.ccttiot.com/bike/img/static/uLTEz0tXCElq04X9ruhR' :
'https://lxnapi.ccttiot.com/bike/img/static/uVTppaVeTsblOqJsLqEk'" class="tab-bar">
</image>
</view>
<view class="tab-item" @tap="setIsCodeLogin(false)">
<image :src="!isCodeLogin ?
'https://lxnapi.ccttiot.com/bike/img/static/unCWmblh1uzRcYxBzI1K' :
'https://lxnapi.ccttiot.com/bike/img/static/uqPMG4DDTpTyVg7fFV50'" class="tab-bar-1">
</image>
</view>
</view>
<!-- 表单区域 -->
<view class="form-container">
<!-- 手机号输入框 -->
<view class="input-box">
<image src="https://lxnapi.ccttiot.com/bike/img/static/uAjqjitdgGf19n0G3uuI"
class="input-icon"></image>
<input type="number" v-model="phone" placeholder="请输入手机号" maxlength="11" class="input"
@input="validatePhone" />
</view>
<text class="error-tip" v-if="errors.phone">{{ errors.phone }}</text>
<!-- 密码输入框 -->
<view class="input-box" v-if="!isCodeLogin || isRegister">
<image src="https://lxnapi.ccttiot.com/bike/img/static/ud4QtAXZp6fOitzpapuw"
class="input-icon"></image>
<input type="password" v-model="password" placeholder="请输入密码" class="input-2"
@input="validatePassword" />
<text class="get-code" v-if="!isRegister && !isCodeLogin" @tap="forgetPassword">忘记密码?</text>
</view>
<text class="error-tip" v-if="errors.password">{{ errors.password }}</text>
<!-- 验证码输入框 -->
<view class="input-box" v-if="isCodeLogin || isRegister">
<image src="https://lxnapi.ccttiot.com/bike/img/static/u7H7WpZhmsvtgZawoxW6"
class="input-icon"></image>
<input type="number" v-model="code" placeholder="请输入验证码" maxlength="4" class="input-2"
@input="validateCode" />
<text class="get-code" :class="{ 'disabled': countdown > 0 }" @tap="handleGetCode">
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
</text>
</view>
<text class="error-tip" v-if="errors.code">{{ errors.code }}</text>
<!-- 登录按钮 -->
<view class="login-btn" @tap="handleLogin" :class="{ 'disabled': loading }">
<text class="login-txt">{{ loading ? '处理中...' : (isRegister ? '立即注册' : '立即登录') }}</text>
</view>
<!-- 切换登录/注册 -->
<view class="switch-mode" @tap="switchMode">
<text class="tip-txt">{{ isRegister ? '已有账号?' : '没有账号?' }}</text>
<text class="tip-txt highlight">{{ isRegister ? '去登录' : '去注册' }}</text>
</view>
<view class="choose_login" @click="pageIndex=0">
<image src="https://lxnapi.ccttiot.com/bike/img/static/u3giTY4VkWYpnGWRuFHF" mode=""></image>
选择登录方式
</view>
</view>
</view>
</view>
</view>
<view v-else class="page1">
<view class="button-container">
<view class="btn1" @tap="handlePhoneLogin">
手机登录
</view>
<button class="btn2" open-type="getPhoneNumber" @getphonenumber="handleQuickLogin" v-if="isAgree">
快捷登录
</button>
<view class="btn2" @tap="weChatLogin" v-if="!isAgree">
快捷登录
</view>
<view class="agreement-container">
<view class="checkbox" @tap="toggleAgreement">
<image
:src="isAgree ? 'https://lxnapi.ccttiot.com/bike/img/static/u0vgvsizwNYtdwgmhdLR' : 'https://lxnapi.ccttiot.com/bike/img/static/u3AnE2Nd41NwRjs8DUWz'"
class="checkbox-icon"></image>
</view>
<view class="agreement-text">
我已阅读并同意
<text class="link" @tap="openUserAgreement">《用户服务协议》</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
isCodeLogin: true,
isRegister: false,
phone: '',
code: '',
password: '',
uuid: '',
loading: false,
countdown: 0,
errors: {
phone: '',
code: '',
password: ''
},
modalConfig: {
title: '',
message: ''
},
bgc: {
backgroundColor: " ",
},
showPhoneLogin: true,
pageIndex: 0,
isAgree: false, // 添加用户协议勾选状态
}
},
methods: {
backPage(){
uni.reLaunch({
url:'/pages/index/index'
})
},
// 获取微信登录code
getWxLoginCode() {
return new Promise((resolve, reject) => {
wx.login({
success(res) {
if (res.code) {
resolve(res.code);
} else {
reject('获取code失败');
}
},
fail(err) {
reject(err);
}
});
});
},
weChatLogin() {
if (!this.checkAgreement()) return;
},
toggleAgreement() {
this.isAgree = !this.isAgree;
},
// 检查协议是否已勾选
checkAgreement() {
if (!this.isAgree) {
uni.showToast({
title: '请先阅读并同意用户协议和隐私政策',
icon: 'none',
duration: 2000
});
return false;
}
return true;
},
// 打开用户协议
openUserAgreement() {
uni.navigateTo({
url: '/page_user/word?id=25'
});
},
// 打开隐私政策
openPrivacyPolicy() {
uni.navigateTo({
url: '/pages/agreement/privacy-policy'
});
},
// 手机登录按钮处理
handlePhoneLogin() {
if (!this.checkAgreement()) return;
this.pageIndex = 1;
},
// 快捷登录按钮处理
handleQuickLogin(e) {
console.log("handleQuickLogin", e);
if (!this.isAgree) {
uni.showToast({
title: '请先阅读并同意用户协议和隐私政策',
icon: 'none',
duration: 2000
});
return;
}
this.getPhoneNumber(e);
},
getPhoneNumber(e) {
let that = this;
console.log("eeeeeeee", e);
const wxLoginAsync = () => {
return new Promise((resolve, reject) => {
wx.login({
success(res) {
if (res.code) {
console.log('登录!', res);
let data = {
jsCode: res.code,
mobileCode: e.detail.code,
};
resolve(data);
} else {
reject(res.errMsg);
}
},
fail(err) {
reject(err);
}
});
});
};
wxLoginAsync()
.then(async (data) => {
this.$u.post("/wxlogin?mobileCode=" + data.mobileCode + '&jsCode=' + data.jsCode
).then((res) => {
if (res.code == 200) {
console.log(res, 'resres');
wx.setStorageSync('token', res.token);
that.ceshi()
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
}
});
})
.catch((err) => {
console.error(err);
});
},
ceshi() {
this.$u.get('/getInfo').then((res) => {
if (res.code == 200) {
wx.setStorageSync('userInfo', res.user)
if (res.user.isAuthentication) {
uni.redirectTo({
url: '/pages/index/index'
})
} else {
uni.redirectTo({
url: '/page_user/idTest'
})
}
}
})
},
// 验证手机号
validatePhone() {
const phoneRegex = /^1[3-9]\d{9}$/;
if (!this.phone.trim()) {
this.errors.phone = '请输入手机号';
return false;
} else if (!phoneRegex.test(this.phone)) {
this.errors.phone = '请输入正确的手机号格式';
return false;
}
this.errors.phone = '';
return true;
},
// 验证验证码
validateCode() {
const codeRegex = /^\d{4}$/;
if (!this.code.trim()) {
this.errors.code = '请输入验证码';
return false;
} else if (!codeRegex.test(this.code)) {
this.errors.code = '请输入4位数字验证码';
return false;
}
this.errors.code = '';
return true;
},
// 验证密码
validatePassword() {
if (!this.password.trim()) {
this.errors.password = '请输入密码';
return false;
} else if (this.password.length < 6) {
this.errors.password = '密码长度不能少于6位';
return false;
}
this.errors.password = '';
return true;
},
validateForm() {
let isValid = true;
const newErrors = {
phone: '',
code: '',
password: ''
};
// 手机号验证
const phoneRegex = /^1[3-9]\d{9}$/;
if (!this.phone.trim()) {
newErrors.phone = '请输入手机号';
isValid = false;
} else if (!phoneRegex.test(this.phone)) {
newErrors.phone = '请输入正确的手机号格式';
isValid = false;
}
// 验证码验证
if (this.isCodeLogin || this.isRegister) {
const codeRegex = /^\d{4}$/;
if (!this.code.trim()) {
newErrors.code = '请输入验证码';
isValid = false;
} else if (!codeRegex.test(this.code)) {
newErrors.code = '请输入4位数字验证码';
isValid = false;
}
}
// 密码验证
if (!this.isCodeLogin || this.isRegister) {
if (!this.password.trim()) {
newErrors.password = '请输入密码';
isValid = false;
} else if (this.password.length < 6) {
newErrors.password = '密码长度不能少于6位';
isValid = false;
}
}
this.errors = newErrors;
return isValid;
},
async handleLogin() {
if (!this.validateForm()) return;
if ((this.isCodeLogin || this.isRegister) && !this.uuid) {
uni.showToast({
title: '请先获取验证码',
icon: 'none',
duration: 2000
});
return;
}
this.loading = true;
let url, data;
try {
// 获取微信登录code
const jsCode = await this.getWxLoginCode();
if (this.isRegister) {
url = '/register';
data = {
username: this.phone,
password: this.password,
code: this.code,
uuid: this.uuid,
jsCode: jsCode
};
} else if (this.isCodeLogin) {
url = '/appCodeLogin';
data = {
phone: this.phone,
phoneCode: this.code,
uuid: this.uuid,
jsCode: jsCode
};
} else {
url = '/appLogin';
data = {
username: this.phone,
password: this.password,
jsCode: jsCode
};
}
const res = await this.$u.post(url, data);
if (res.code == 200) {
wx.setStorageSync('token', res.token);
this.ceshi();
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
}
} catch (error) {
console.error('登录失败:', error);
uni.showToast({
title: '登录失败,请重试',
icon: 'none',
duration: 2000
});
} finally {
this.loading = false;
}
},
handleGetCode() {
if (this.countdown > 0) return;
const phoneRegex = /^1[3-9]\d{9}$/;
if (!this.phone.trim() || !phoneRegex.test(this.phone)) {
this.errors.phone = '请输入正确的手机号格式';
return;
}
const type = this.isRegister ? '2' : '1'; // 1:登录 2:注册
this.$u.get("/appCaptcha?type=" + type + "&phone=" + this.phone).then((res) => {
if (res.code == 200) {
this.uuid = res.uuid;
this.countdown = 60;
this.startCountdown();
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
}
});
},
startCountdown() {
if (this.countdown <= 0) return;
setTimeout(() => {
this.countdown--;
if (this.countdown > 0) {
this.startCountdown();
}
}, 1000);
},
switchMode() {
this.isRegister = !this.isRegister;
this.isCodeLogin = true;
this.clearForm();
},
setIsCodeLogin(value) {
this.isCodeLogin = value;
this.clearForm();
},
clearForm() {
this.phone = '';
this.code = '';
this.password = '';
this.errors = {
phone: '',
code: '',
password: ''
};
},
showModal(title, message) {
this.modalConfig.title = title;
this.modalConfig.message = message;
this.$refs.messageBox.open();
},
closeModal() {
this.$refs.messageBox.close();
},
forgetPassword() {
uni.navigateTo({
url: '/pages/login/ResetPassword'
});
},
async getUserInfo() {
try {
const res = await this.$u.get('/getInfo');
if (res.code === 200) {
uni.setStorageSync('userInfo', res.user);
if (res.user.isAuthentication) {
uni.redirectTo({
url: '/pages/index/index'
});
} else {
uni.redirectTo({
url: '/pages/id-test/id-test'
});
}
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
}
}
}
</script>
<style lang="scss">
.login-page {
position: relative;
width: 100%;
height: 100vh;
.page1 {
position: relative;
width: 100%;
height: 100vh;
background-image: url('https://lxnapi.ccttiot.com/bike/img/static/uWIMugXcD13DAIBbsXhI');
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
.button-container {
margin-top: 55vh;
// margin-top: calc(100vh - 750rpx);
padding: 0 68rpx;
display: flex;
flex-direction: column;
gap: 32rpx;
}
.agreement-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
padding: 0 20rpx;
.checkbox {
padding: 10rpx;
.checkbox-icon {
margin-top: 6rpx;
width: 32rpx;
height: 32rpx;
}
}
.agreement-text {
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
.link {
color: #4297F3;
}
}
}
.btn1 {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 108rpx;
background: linear-gradient(90deg, #4297F3 0%, rgba(66, 151, 243, 0.66) 100%);
border-radius: 54rpx;
font-weight: 500;
font-size: 36rpx;
color: #FFFFFF;
}
.btn2 {
width: 100%;
height: 108rpx;
border-radius: 54rpx;
border: 2rpx solid #4297F3;
font-weight: 500;
font-size: 36rpx;
color: #4297F3;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
&:disabled {
opacity: 0.5;
color: #999;
border-color: #999;
}
}
}
.background-image {
width: 750rpx;
height: 472rpx;
}
.overlay {
position: absolute;
top: 400rpx;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
padding: 68rpx;
}
.container {
border-radius: 16rpx;
background-color: rgba(255, 255, 255, 0.9);
}
.tab-bar-box {
margin-top: 42rpx;
display: flex;
flex-direction: row;
}
.tab-bar {
width: 319rpx;
height: 108rpx;
}
.tab-bar-1 {
margin-left: -30rpx;
width: 319rpx;
height: 108rpx;
}
.input-box {
padding-left: 36rpx;
display: flex;
flex-direction: row;
align-items: center;
margin-top: 68rpx;
width: 614rpx;
height: 108rpx;
background-color: #EFEFEF;
border-radius: 54rpx;
}
.input-icon {
width: 38rpx;
height: 38rpx;
}
.input {
width: 500rpx;
margin-left: 20rpx;
background-color: transparent;
}
.input-2 {
width: 350rpx;
margin-left: 20rpx;
background-color: transparent;
}
.get-code {
width: 40%;
font-weight: 400;
font-size: 28rpx;
color: #4297F3;
padding: 0 30rpx;
&.disabled {
color: #999;
}
}
.error-tip {
margin-top: 10rpx;
padding-left: 36rpx;
font-size: 24rpx;
color: #ff4d4f;
}
.login-btn {
display: flex;
align-items: center;
justify-content: center;
margin-top: 68rpx;
width: 614rpx;
height: 108rpx;
background-color: #4297F3;
border-radius: 54rpx;
&.disabled {
opacity: 0.7;
}
}
.login-txt {
font-weight: 700;
font-size: 36rpx;
color: #FFFFFF;
}
.choose_login{
margin: 0 auto;
margin-top: 100rpx;
padding-left: 12rpx;
display: flex;
align-items: center;
width: 384rpx;
height: 108rpx;
border-radius: 54rpx 54rpx 54rpx 54rpx;
border: 2rpx solid #4297F3;
font-weight: 500;
font-size: 36rpx;
color: #4297F3;
image{
margin-right: 20rpx;
width: 84rpx;
height: 84rpx;
}
}
.switch-mode {
margin-top: 68rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.tip-txt {
font-weight: 400;
font-size: 32rpx;
color: #3D3D3D;
&.highlight {
color: #4297F3;
}
}
}
</style>