增加手机号登录
This commit is contained in:
parent
822b5fada3
commit
f9c1a3e82d
|
|
@ -19,6 +19,19 @@ export function login(username, password, code, uuid) {
|
|||
})
|
||||
}
|
||||
|
||||
// 手机号登录方法
|
||||
export function loginByPhoneApi(data) {
|
||||
return request({
|
||||
url: '/loginByPhone',
|
||||
headers: {
|
||||
isToken: false,
|
||||
repeatSubmit: false
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 注册方法
|
||||
export function register(data) {
|
||||
return request({
|
||||
|
|
@ -57,4 +70,4 @@ export function getCodeImg() {
|
|||
method: 'get',
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,67 @@
|
|||
import router from '@/router'
|
||||
import { ElMessageBox, } from 'element-plus'
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { login, logout, getInfo, loginByPhoneApi } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
import { isHttp, isEmpty } from '@/utils/validate'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
|
||||
const useUserStore = defineStore(
|
||||
'user',
|
||||
{
|
||||
state: () => ({
|
||||
token: getToken(),
|
||||
id: '',
|
||||
name: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
}),
|
||||
actions: {
|
||||
// 登录
|
||||
login(userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: getToken(),
|
||||
id: '',
|
||||
name: '',
|
||||
nickName: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
}),
|
||||
actions: {
|
||||
// 登录
|
||||
login(userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid)
|
||||
.then(res => {
|
||||
setToken(res.token)
|
||||
this.token = res.token
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取用户信息
|
||||
getInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
})
|
||||
},
|
||||
// 手机号登录
|
||||
loginByPhone(phoneInfo) {
|
||||
const phone = phoneInfo.phone.trim()
|
||||
const code = phoneInfo.code.trim()
|
||||
return new Promise((resolve, reject) => {
|
||||
loginByPhoneApi({ phone, code })
|
||||
.then(res => {
|
||||
setToken(res.token)
|
||||
this.token = res.token
|
||||
resolve()
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取用户信息
|
||||
getInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo()
|
||||
.then(res => {
|
||||
const user = res.user
|
||||
let avatar = user.avatar || ""
|
||||
let avatar = user.avatar || ''
|
||||
if (!isHttp(avatar)) {
|
||||
avatar = (isEmpty(avatar)) ? defAva : import.meta.env.VITE_APP_BASE_API + avatar
|
||||
avatar = isEmpty(avatar) ? defAva : import.meta.env.VITE_APP_BASE_API + avatar
|
||||
}
|
||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
this.roles = res.roles
|
||||
this.permissions = res.permissions
|
||||
} else {
|
||||
|
|
@ -54,38 +72,53 @@ const useUserStore = defineStore(
|
|||
this.nickName = user.nickName
|
||||
this.avatar = avatar
|
||||
/* 初始密码提示 */
|
||||
if(res.isDefaultModifyPwd) {
|
||||
ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
if (res.isDefaultModifyPwd) {
|
||||
ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
/* 过期密码提示 */
|
||||
if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
|
||||
ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
if (!res.isDefaultModifyPwd && res.isPasswordExpired) {
|
||||
ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 退出系统
|
||||
logOut() {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(this.token).then(() => {
|
||||
})
|
||||
},
|
||||
// 退出系统
|
||||
logOut() {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(this.token)
|
||||
.then(() => {
|
||||
this.token = ''
|
||||
this.roles = []
|
||||
this.permissions = []
|
||||
removeToken()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default useUserStore
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import cache from '@/plugins/cache'
|
|||
import { saveAs } from 'file-saver'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { decryptWithSM4, encryptWithSM4, hashWithSM3AndSalt } from '@/utils/sm'
|
||||
import router from '@/router'
|
||||
|
||||
let downloadLoadingInstance
|
||||
// 是否显示重新登录
|
||||
|
|
@ -106,7 +107,10 @@ service.interceptors.response.use(
|
|||
return res.data
|
||||
}
|
||||
if (code === 401) {
|
||||
if (!isRelogin.show) {
|
||||
// 判断当前是否在 login 页面
|
||||
const isLogin = router.currentRoute.value.path == '/login'
|
||||
console.log('🚀 ~ isLogin:', isLogin)
|
||||
if (!isRelogin.show && !isLogin) {
|
||||
isRelogin.show = true
|
||||
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
|
||||
confirmButtonText: '重新登录',
|
||||
|
|
@ -118,7 +122,7 @@ service.interceptors.response.use(
|
|||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
location.href = '/index'
|
||||
location.href = '/login'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
|||
|
|
@ -2,73 +2,106 @@
|
|||
<div class="login">
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="large"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.prevent="handleLogin"
|
||||
>
|
||||
|
||||
<!-- 账号密码登录 -->
|
||||
<template v-if="!isPhoneLogin">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
|
||||
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 手机号登录 -->
|
||||
<template v-else>
|
||||
<el-form-item prop="phone">
|
||||
<el-input v-model="loginForm.phone" type="text" size="large" placeholder="手机号" maxlength="11">
|
||||
<template #prefix><svg-icon icon-class="phone" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="smsCode">
|
||||
<el-input v-model="loginForm.smsCode" size="large" placeholder="短信验证码" style="width: 63%">
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<el-button
|
||||
:disabled="countdown > 0"
|
||||
type="primary"
|
||||
size="large"
|
||||
style="width: 33%; margin-left: 4%"
|
||||
@click="sendSmsCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-checkbox v-if="!isPhoneLogin" v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px">
|
||||
记住密码
|
||||
</el-checkbox>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right;" v-if="register">
|
||||
|
||||
<div style="float: right" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'">立即注册</router-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 切换登录方式 -->
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button size="large" type="" style="width: 100%" @click="toggleLoginType">
|
||||
<span>{{ isPhoneLogin ? '使用账号登录' : '使用手机号登录' }}</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
|
||||
<!-- <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCodeImg } from "@/api/login"
|
||||
import Cookies from "js-cookie"
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt"
|
||||
import { getCodeImg } from '@/api/login'
|
||||
import Cookies from 'js-cookie'
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Iphone } from '@element-plus/icons-vue'
|
||||
|
||||
const title = import.meta.env.VITE_APP_TITLE
|
||||
const userStore = useUserStore()
|
||||
|
|
@ -76,100 +109,159 @@ const route = useRoute()
|
|||
const router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
// 表单数据
|
||||
const loginForm = ref({
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
code: '',
|
||||
uuid: '',
|
||||
phone: '',
|
||||
smsCode: ''
|
||||
})
|
||||
|
||||
const loginRules = {
|
||||
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
|
||||
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
|
||||
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
|
||||
}
|
||||
// 校验规则
|
||||
const loginRules = reactive({
|
||||
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
|
||||
code: [{ required: true, trigger: 'change', message: '请输入验证码' }],
|
||||
phone: [{ required: true, trigger: 'blur', message: '请输入手机号' }],
|
||||
smsCode: [{ required: true, trigger: 'blur', message: '请输入短信验证码' }]
|
||||
})
|
||||
|
||||
const codeUrl = ref("")
|
||||
const codeUrl = ref('')
|
||||
const loading = ref(false)
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true)
|
||||
// 注册开关
|
||||
const register = ref(false)
|
||||
const redirect = ref(undefined)
|
||||
const isPhoneLogin = ref(false)
|
||||
const countdown = ref(0)
|
||||
let timer = null
|
||||
|
||||
watch(route, (newRoute) => {
|
||||
watch(
|
||||
route,
|
||||
newRoute => {
|
||||
redirect.value = newRoute.query && newRoute.query.redirect
|
||||
}, { immediate: true })
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 登录处理
|
||||
function handleLogin() {
|
||||
proxy.$refs.loginRef.validate(valid => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
Cookies.set("username", loginForm.value.username, { expires: 30 })
|
||||
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
|
||||
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
const formData = loginForm.value
|
||||
|
||||
// 账号密码登录
|
||||
if (!isPhoneLogin.value) {
|
||||
if (formData.rememberMe) {
|
||||
Cookies.set('username', formData.username, { expires: 30 })
|
||||
Cookies.set('password', encrypt(formData.password), { expires: 30 })
|
||||
Cookies.set('rememberMe', formData.rememberMe, { expires: 30 })
|
||||
} else {
|
||||
// 否则移除
|
||||
Cookies.remove("username")
|
||||
Cookies.remove("password")
|
||||
Cookies.remove("rememberMe")
|
||||
Cookies.remove('username')
|
||||
Cookies.remove('password')
|
||||
Cookies.remove('rememberMe')
|
||||
}
|
||||
// 调用action的登录方法
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
const query = route.query
|
||||
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== "redirect") {
|
||||
acc[cur] = query[cur]
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
router.push({ path: redirect.value || "/", query: otherQueryParams })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
getCode()
|
||||
}
|
||||
})
|
||||
delete formData.phone
|
||||
delete formData.smsCode
|
||||
userStore.login(formData).then(handleLoginSuccess).catch(handleLoginFail)
|
||||
}
|
||||
// 手机号登录
|
||||
else {
|
||||
userStore
|
||||
.loginByPhone({
|
||||
phone: formData.phone,
|
||||
code: formData.smsCode
|
||||
})
|
||||
.then(handleLoginSuccess)
|
||||
.catch()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleLoginSuccess() {
|
||||
const query = route.query
|
||||
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== 'redirect') acc[cur] = query[cur]
|
||||
return acc
|
||||
}, {})
|
||||
router.push({ path: redirect.value || '/', query: otherQueryParams })
|
||||
}
|
||||
|
||||
function handleLoginFail() {
|
||||
loading.value = false
|
||||
if (captchaEnabled.value && !isPhoneLogin.value) getCode()
|
||||
}
|
||||
|
||||
// 切换登录方式
|
||||
function toggleLoginType() {
|
||||
isPhoneLogin.value = !isPhoneLogin.value
|
||||
if (!isPhoneLogin.value && captchaEnabled.value) getCode()
|
||||
}
|
||||
|
||||
// 获取验证码图片
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = "data:image/gif;base64," + res.img
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
loginForm.value.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getCookie() {
|
||||
const username = Cookies.get("username")
|
||||
const password = Cookies.get("password")
|
||||
const rememberMe = Cookies.get("rememberMe")
|
||||
loginForm.value = {
|
||||
username: username === undefined ? loginForm.value.username : username,
|
||||
password: password === undefined ? loginForm.value.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
// 获取短信验证码
|
||||
const sendSmsCode = async () => {
|
||||
if (!loginForm.value.phone) {
|
||||
ElMessage.error('请输入手机号')
|
||||
return
|
||||
}
|
||||
// API发送短信
|
||||
try {
|
||||
ElMessage.success('验证码已发送')
|
||||
startCountdown()
|
||||
} catch (error) {
|
||||
console.log('🚀 ~ sendSmsCode ~ error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 倒计时
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60
|
||||
timer && clearInterval(timer)
|
||||
timer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// cookie 记住密码
|
||||
function getCookie() {
|
||||
const username = Cookies.get('username')
|
||||
const password = Cookies.get('password')
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
loginForm.value.username = username || loginForm.value.username
|
||||
loginForm.value.password = password ? decrypt(password) : loginForm.value.password
|
||||
loginForm.value.rememberMe = rememberMe ? Boolean(rememberMe) : false
|
||||
}
|
||||
|
||||
getCode()
|
||||
getCookie()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-image: url('../assets/images/login-background.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
|
|
|
|||
Loading…
Reference in New Issue