760 lines
16 KiB
Vue
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> |