10 changed files with 401 additions and 337 deletions
@ -1,266 +1,282 @@ |
|||
<template> |
|||
<view class="login-bg"> |
|||
<view class="login-welcome"> |
|||
<image |
|||
class="login-bg-img" |
|||
src="../../static/img/login-top.png" |
|||
mode="aspectFill" |
|||
></image> |
|||
<view class="login-welcome-content"> |
|||
<text class="login-title">您好!</text> |
|||
<text class="login-title">欢迎使用</text> |
|||
</view> |
|||
</view> |
|||
<view class="login-form-box"> |
|||
<view class="login-form-item"> |
|||
<text class="login-label">用户名</text> |
|||
<u-input |
|||
v-model="form.username" |
|||
placeholder="请输入用户名" |
|||
type="text" |
|||
border="none" |
|||
shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" |
|||
/> |
|||
</view> |
|||
<view class="login-form-item"> |
|||
<text class="login-label">密码</text> |
|||
<u-input |
|||
v-model="form.password" |
|||
placeholder="请输入密码" |
|||
type="password" |
|||
border="none" |
|||
shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" |
|||
/> |
|||
</view> |
|||
<view class="login-form-item captcha-row"> |
|||
<text class="login-label">验证码</text> |
|||
<view class="captcha-flex"> |
|||
<u-input |
|||
v-model="form.code" |
|||
placeholder="验证码" |
|||
border="none" |
|||
shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" |
|||
/> |
|||
<image |
|||
:src="codeUrl" |
|||
class="captcha-img" |
|||
mode="aspectFit" |
|||
@click="getCaptcha" |
|||
/> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="login-btn-box"> |
|||
<u-button |
|||
:hairline="false" |
|||
shape="circle" |
|||
:custom-style="btnStyle" |
|||
@click="handleLogin" |
|||
color="#fff" |
|||
>登录</u-button |
|||
> |
|||
</view> |
|||
</view> |
|||
<view class="login-bg"> |
|||
<view class="login-welcome"> |
|||
<image class="login-bg-img" src="../../static/img/login-top.png" mode="aspectFill"></image> |
|||
<view class="login-welcome-content"> |
|||
<text class="login-title">您好!</text> |
|||
<text class="login-title">欢迎使用</text> |
|||
</view> |
|||
</view> |
|||
<view class="login-form-box"> |
|||
<view class="login-form-item"> |
|||
<text class="login-label">用户名</text> |
|||
<u-input v-model="form.username" placeholder="请输入用户名" type="text" border="none" shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" /> |
|||
</view> |
|||
<view class="login-form-item"> |
|||
<text class="login-label">密码</text> |
|||
<u-input v-model="form.password" placeholder="请输入密码" type="password" border="none" shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" /> |
|||
</view> |
|||
<view class="login-form-item captcha-row"> |
|||
<text class="login-label">验证码</text> |
|||
<view class="captcha-flex"> |
|||
<u-input v-model="form.code" placeholder="验证码" border="none" shape="circle" |
|||
custom-style="background:#f6f6f6;padding:16rpx 0;padding-left:16px;" /> |
|||
<image :src="codeUrl" class="captcha-img" mode="aspectFit" @click="getCaptcha" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="login-btn-box"> |
|||
<u-button :hairline="false" shape="circle" :custom-style="btnStyle" @click="handleLogin" |
|||
color="#fff">登录</u-button> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { login, getCodeImg } from "../api"; |
|||
import { encrypt, decrypt } from "../../utils/jsencrypt"; |
|||
export default { |
|||
data() { |
|||
return { |
|||
form: { |
|||
username: "",//admin |
|||
password: "",//!Aa12345 |
|||
code: "", |
|||
uuid: "", |
|||
rememberMe: false, |
|||
}, |
|||
loading: false, |
|||
loginFailCounts: {}, // 记录失败次数 |
|||
accountLockTimes: {}, // 记录锁定时间 |
|||
codeUrl: "", // 验证码图片base64或url |
|||
btnStyle: { |
|||
width: "60%", |
|||
height: "44px", |
|||
fontSize: "18px", |
|||
margin: "50px", |
|||
marginTop: "200rpx", |
|||
background: "linear-gradient(90deg, #0DC6C6 0%, #13C2C2 100%)", |
|||
}, |
|||
}; |
|||
}, |
|||
methods: { |
|||
async getCaptcha() { |
|||
const res = await getCodeImg(); |
|||
this.captchaEnabled = |
|||
res.captchaEnabled === undefined ? true : res.captchaEnabled; |
|||
if (this.captchaEnabled) { |
|||
this.codeUrl = "data:image/gif;base64," + res.img; |
|||
this.form.uuid = res.uuid; |
|||
} |
|||
}, |
|||
async handleLogin() { |
|||
const username = String(this.form.username); // 强制转为字符串 |
|||
// 1. 检查锁定 |
|||
const lockInfo = JSON.parse( |
|||
uni.getStorageSync(`lock_${username}`) || "{}" |
|||
); |
|||
if (lockInfo.lockEndTime) { |
|||
const currentTime = new Date().getTime(); |
|||
if (currentTime < lockInfo.lockEndTime) { |
|||
const remainingMinutes = Math.ceil( |
|||
(lockInfo.lockEndTime - currentTime) / (1000 * 60) |
|||
); |
|||
uni.showToast({ |
|||
title: `账号被锁定,请${remainingMinutes}分钟后再试`, |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} else { |
|||
uni.removeStorageSync(`lock_${username}`); |
|||
} |
|||
} |
|||
// 2. 校验表单 |
|||
if (!this.form.username || !this.form.password || !this.form.code) { |
|||
uni.showToast({ title: "请填写完整信息", icon: "none" }); |
|||
return; |
|||
} |
|||
this.loading = true; |
|||
// 3. 记住我 |
|||
if (this.form.rememberMe) { |
|||
uni.setStorageSync("username", this.form.username); |
|||
uni.setStorageSync("password", encrypt(this.form.password)); // 如需加密可加密 |
|||
uni.setStorageSync("rememberMe", true); |
|||
} else { |
|||
uni.removeStorageSync("username"); |
|||
uni.removeStorageSync("password"); |
|||
uni.removeStorageSync("rememberMe"); |
|||
} |
|||
const res = await login({ |
|||
username: String(this.form.username), |
|||
password: encrypt(this.form.password), // 如需加密可加密 |
|||
code: this.form.code, |
|||
uuid: this.form.uuid, |
|||
client: "gypc", |
|||
}); |
|||
this.loading = false; |
|||
console.log(res); |
|||
|
|||
if (res.code === 200) { |
|||
this.loginFailCounts[username] = 0; |
|||
uni.setStorageSync("token", res.token); |
|||
uni.switchTab({ url: "/pages/tabBar/work/index" }); |
|||
} else { |
|||
uni.showToast({ title: res.msg, icon: "none" }); |
|||
// 失败计数 |
|||
this.loginFailCounts[username] = |
|||
(this.loginFailCounts[username] || 0) + 1; |
|||
if (this.loginFailCounts[username] >= 5) { |
|||
const lockEndTime = new Date().getTime() + 10 * 60 * 1000; |
|||
this.accountLockTimes[username] = lockEndTime; |
|||
uni.setStorageSync( |
|||
`lock_${username}`, |
|||
JSON.stringify({ lockEndTime, username }) |
|||
); |
|||
uni.showToast({ |
|||
title: "账号登录失败次数过多,已锁定10分钟", |
|||
icon: "none", |
|||
}); |
|||
} else { |
|||
uni.showToast({ title: res[1].data.msg || "登录失败", icon: "none" }); |
|||
} |
|||
this.getCaptcha(); |
|||
} |
|||
}, |
|||
}, |
|||
mounted() { |
|||
this.getCaptcha(); |
|||
}, |
|||
}; |
|||
import { |
|||
login, |
|||
getCodeImg |
|||
} from "../api"; |
|||
import { |
|||
encrypt, |
|||
decrypt |
|||
} from "../../utils/jsencrypt"; |
|||
export default { |
|||
data() { |
|||
return { |
|||
form: { |
|||
username: "", //admin |
|||
password: "", //!Aa12345 |
|||
code: "", |
|||
uuid: "", |
|||
rememberMe: false, |
|||
}, |
|||
loading: false, |
|||
loginFailCounts: {}, // 记录失败次数 |
|||
accountLockTimes: {}, // 记录锁定时间 |
|||
codeUrl: "", // 验证码图片base64或url |
|||
btnStyle: { |
|||
width: "60%", |
|||
height: "44px", |
|||
fontSize: "18px", |
|||
margin: "50px", |
|||
marginTop: "200rpx", |
|||
background: "linear-gradient(90deg, #0DC6C6 0%, #13C2C2 100%)", |
|||
}, |
|||
}; |
|||
}, |
|||
methods: { |
|||
async getCaptcha() { |
|||
const res = await getCodeImg(); |
|||
this.captchaEnabled = |
|||
res.captchaEnabled === undefined ? true : res.captchaEnabled; |
|||
if (this.captchaEnabled) { |
|||
this.codeUrl = "data:image/gif;base64," + res.img; |
|||
this.form.uuid = res.uuid; |
|||
} |
|||
}, |
|||
async handleLogin() { |
|||
// 调用uni.login方法获取code |
|||
uni.login({ |
|||
provider: 'weixin', |
|||
success: function(loginRes) { |
|||
// 打印获取到的code |
|||
console.log('获取的用户code是:' + loginRes.code); |
|||
// uni.request({ |
|||
// url: '', |
|||
// method: 'POST' |
|||
// }).then(res => { |
|||
// console.log(res, 'res') |
|||
// }) |
|||
}, |
|||
fail: function(error) { |
|||
// 登录失败的回调 |
|||
console.error('登录失败:', error); |
|||
} |
|||
}); |
|||
const username = String(this.form.username); // 强制转为字符串 |
|||
// 1. 检查锁定 |
|||
const lockInfo = JSON.parse( |
|||
uni.getStorageSync(`lock_${username}`) || "{}" |
|||
); |
|||
if (lockInfo.lockEndTime) { |
|||
const currentTime = new Date().getTime(); |
|||
if (currentTime < lockInfo.lockEndTime) { |
|||
const remainingMinutes = Math.ceil( |
|||
(lockInfo.lockEndTime - currentTime) / (1000 * 60) |
|||
); |
|||
uni.showToast({ |
|||
title: `账号被锁定,请${remainingMinutes}分钟后再试`, |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} else { |
|||
uni.removeStorageSync(`lock_${username}`); |
|||
} |
|||
} |
|||
// 2. 校验表单 |
|||
if (!this.form.username || !this.form.password || !this.form.code) { |
|||
uni.showToast({ |
|||
title: "请填写完整信息", |
|||
icon: "none" |
|||
}); |
|||
return; |
|||
} |
|||
this.loading = true; |
|||
// 3. 记住我 |
|||
if (this.form.rememberMe) { |
|||
uni.setStorageSync("username", this.form.username); |
|||
uni.setStorageSync("password", encrypt(this.form.password)); // 如需加密可加密 |
|||
uni.setStorageSync("rememberMe", true); |
|||
} else { |
|||
uni.removeStorageSync("username"); |
|||
uni.removeStorageSync("password"); |
|||
uni.removeStorageSync("rememberMe"); |
|||
} |
|||
const res = await login({ |
|||
username: String(this.form.username), |
|||
password: encrypt(this.form.password), // 如需加密可加密 |
|||
code: this.form.code, |
|||
uuid: this.form.uuid, |
|||
client: "gypc", |
|||
}); |
|||
this.loading = false; |
|||
console.log(res); |
|||
|
|||
if (res.code === 200) { |
|||
this.loginFailCounts[username] = 0; |
|||
uni.setStorageSync("token", res.token); |
|||
uni.switchTab({ |
|||
url: "/pages/tabBar/work/index" |
|||
}); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg, |
|||
icon: "none" |
|||
}); |
|||
// 失败计数 |
|||
this.loginFailCounts[username] = |
|||
(this.loginFailCounts[username] || 0) + 1; |
|||
if (this.loginFailCounts[username] >= 5) { |
|||
const lockEndTime = new Date().getTime() + 10 * 60 * 1000; |
|||
this.accountLockTimes[username] = lockEndTime; |
|||
uni.setStorageSync( |
|||
`lock_${username}`, |
|||
JSON.stringify({ |
|||
lockEndTime, |
|||
username |
|||
}) |
|||
); |
|||
uni.showToast({ |
|||
title: "账号登录失败次数过多,已锁定10分钟", |
|||
icon: "none", |
|||
}); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res[1].data.msg || "登录失败", |
|||
icon: "none" |
|||
}); |
|||
} |
|||
this.getCaptcha(); |
|||
} |
|||
}, |
|||
}, |
|||
mounted() { |
|||
this.getCaptcha(); |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.login-bg { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
background: #f7fafd; |
|||
height: 100vh; |
|||
} |
|||
.login-welcome { |
|||
position: relative; |
|||
width: 100vw; |
|||
padding-top: 244rpx; |
|||
overflow: hidden; |
|||
} |
|||
.login-bg-img { |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
width: 100vw; |
|||
height: 526px; |
|||
z-index: 1; |
|||
} |
|||
.login-welcome-content { |
|||
position: relative; |
|||
z-index: 2; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
padding-left: 60rpx; |
|||
margin-bottom: 70rpx; |
|||
} |
|||
.login-title { |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #222; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
.login-bg { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
background: #f7fafd; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.login-welcome { |
|||
position: relative; |
|||
width: 100vw; |
|||
padding-top: 244rpx; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.login-bg-img { |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
width: 100vw; |
|||
height: 526px; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.login-welcome-content { |
|||
position: relative; |
|||
z-index: 2; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
padding-left: 60rpx; |
|||
margin-bottom: 70rpx; |
|||
} |
|||
|
|||
.login-title { |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #222; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.login-form-box { |
|||
width: 85%; |
|||
background: #fff; |
|||
border-radius: 24rpx; |
|||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06); |
|||
padding: 40rpx 30rpx 30rpx 30rpx; |
|||
margin-bottom: -46rpx; |
|||
} |
|||
|
|||
.login-form-item { |
|||
margin-bottom: 36rpx; |
|||
} |
|||
|
|||
.login-label { |
|||
font-size: 28rpx; |
|||
font-weight: bold; |
|||
color: #222; |
|||
margin-bottom: 12rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.login-btn-box { |
|||
width: 100%; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.captcha-row { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.captcha-flex { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.login-form-box { |
|||
width: 85%; |
|||
background: #fff; |
|||
border-radius: 24rpx; |
|||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06); |
|||
padding: 40rpx 30rpx 30rpx 30rpx; |
|||
margin-bottom: -46rpx; |
|||
} |
|||
.login-form-item { |
|||
margin-bottom: 36rpx; |
|||
} |
|||
.login-label { |
|||
font-size: 28rpx; |
|||
font-weight: bold; |
|||
color: #222; |
|||
margin-bottom: 12rpx; |
|||
display: block; |
|||
} |
|||
.login-btn-box { |
|||
width: 100%; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
.captcha-row { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.captcha-flex { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.captcha-img { |
|||
width: 160rpx; |
|||
height: 60rpx; |
|||
margin-left: 16rpx; |
|||
border: 1rpx solid #e0e0e0; |
|||
border-radius: 8rpx; |
|||
background: #fff; |
|||
box-sizing: border-box; |
|||
} |
|||
</style> |
|||
.captcha-img { |
|||
width: 160rpx; |
|||
height: 60rpx; |
|||
margin-left: 16rpx; |
|||
border: 1rpx solid #e0e0e0; |
|||
border-radius: 8rpx; |
|||
background: #fff; |
|||
box-sizing: border-box; |
|||
} |
|||
</style> |
Loading…
Reference in new issue