用户登录问题修改
This commit is contained in:
parent
312c9ebaec
commit
eb9e9db8b1
|
|
@ -25,6 +25,18 @@ export function isLogin(data) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAdmin(data) {
|
||||||
|
return request({
|
||||||
|
url: '/auth/isAdmin',
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
repeatSubmit: false
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function getPhoneCode(payload) {
|
export function getPhoneCode(payload) {
|
||||||
return request({
|
return request({
|
||||||
url: '/auth/getPhoneCode',
|
url: '/auth/getPhoneCode',
|
||||||
|
|
|
||||||
|
|
@ -145,3 +145,12 @@ export function approvalStatus(data) {
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//用户注册审批
|
||||||
|
export function checkPasswordStatus(data) {
|
||||||
|
return request({
|
||||||
|
url: '/system/user/checkPasswordStatus',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,29 @@
|
||||||
<settings/>
|
<settings/>
|
||||||
</right-panel>
|
</right-panel>
|
||||||
</div>
|
</div>
|
||||||
|
<el-dialog :title="title" :visible.sync="showChangePasswordDialog" width="30%" :close-on-click-modal="false"
|
||||||
|
:show-close="false"
|
||||||
|
>
|
||||||
|
<el-form ref="form" :model="user" :rules="rules" label-width="80px">
|
||||||
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
|
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
|
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
|
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- <el-form-item>
|
||||||
|
<el-button type="primary" size="mini" @click="submit">保存</el-button>
|
||||||
|
<el-button type="danger" size="mini" @click="close">关闭</el-button>
|
||||||
|
</el-form-item>-->
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submit">确 定</el-button>
|
||||||
|
<el-button @click="close">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -21,9 +44,43 @@ import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
|
||||||
import ResizeMixin from './mixin/ResizeHandler'
|
import ResizeMixin from './mixin/ResizeHandler'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import variables from '@/assets/styles/variables.scss'
|
import variables from '@/assets/styles/variables.scss'
|
||||||
|
import { validateNewPassword } from '@/utils/validate'
|
||||||
|
import { updateUserPwd, checkPasswordStatus } from '@/api/system/user'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Layout',
|
name: 'Layout',
|
||||||
|
data() {
|
||||||
|
const equalToPassword = (rule, value, callback) => {
|
||||||
|
if (this.user.newPassword !== value) {
|
||||||
|
callback(new Error('两次输入的密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
showChangePasswordDialog: false, // 控制弹窗显示
|
||||||
|
title: '',
|
||||||
|
user: {
|
||||||
|
oldPassword: undefined,
|
||||||
|
newPassword: undefined,
|
||||||
|
confirmPassword: undefined
|
||||||
|
},
|
||||||
|
// 表单校验规则
|
||||||
|
rules: {
|
||||||
|
oldPassword: [
|
||||||
|
{ required: true, message: '旧密码不能为空', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '新密码不能为空', trigger: 'blur' },
|
||||||
|
{ validator: validateNewPassword, trigger: 'blur' }
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
|
||||||
|
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
AppMain,
|
AppMain,
|
||||||
Navbar,
|
Navbar,
|
||||||
|
|
@ -51,61 +108,87 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
return variables;
|
return variables
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.checkPasswordStatus()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
checkPasswordStatus() {
|
||||||
|
checkPasswordStatus().then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.showChangePasswordDialog = response.data
|
||||||
|
this.title = response.msg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
handleClickOutside() {
|
handleClickOutside() {
|
||||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$refs['form'].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {
|
||||||
|
this.showChangePasswordDialog = false
|
||||||
|
this.$modal.msgSuccess('修改成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
|
location.href = '/index';
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "~@/assets/styles/mixin.scss";
|
@import "~@/assets/styles/mixin.scss";
|
||||||
@import "~@/assets/styles/variables.scss";
|
@import "~@/assets/styles/variables.scss";
|
||||||
|
|
||||||
.app-wrapper {
|
.app-wrapper {
|
||||||
@include clearfix;
|
@include clearfix;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.mobile.openSidebar {
|
&.mobile.openSidebar {
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-bg {
|
|
||||||
background: #000;
|
|
||||||
opacity: 0.3;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-header {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
|
||||||
z-index: 9;
|
|
||||||
width: calc(100% - #{$base-sidebar-width});
|
|
||||||
transition: width 0.28s;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hideSidebar .fixed-header {
|
.drawer-bg {
|
||||||
width: calc(100% - 54px);
|
background: #000;
|
||||||
}
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebarHide .fixed-header {
|
.fixed-header {
|
||||||
width: 100%;
|
position: fixed;
|
||||||
}
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9;
|
||||||
|
width: calc(100% - #{$base-sidebar-width});
|
||||||
|
transition: width 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile .fixed-header {
|
.hideSidebar .fixed-header {
|
||||||
width: 100%;
|
width: calc(100% - 54px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarHide .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,35 @@
|
||||||
import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin } from '@/api/login'
|
import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin,isAdmin } from '@/api/login'
|
||||||
import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth'
|
import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth'
|
||||||
|
|
||||||
|
// 更严格的手机号和邮箱正则表达式
|
||||||
|
const phonePattern = /^(\+86)?1[3-9]\d{9}$/ // 支持前缀 +86
|
||||||
|
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||||
|
|
||||||
|
// 构建 payload 函数
|
||||||
|
const buildPayload = ({ loginMethod, username, password, uuid, code, mobile, verificationCode }) => {
|
||||||
|
let loginType = ''
|
||||||
|
if (loginMethod === 'mobile') {
|
||||||
|
loginType = phonePattern.test(mobile.trim()) ? 'PHONE_OTP' : emailPattern.test(mobile.trim()) ? 'EMAIL_OTP' : 'PHONE_OTP'
|
||||||
|
return {
|
||||||
|
username: mobile.trim(),
|
||||||
|
verificationCode,
|
||||||
|
uuid,
|
||||||
|
code,
|
||||||
|
loginType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loginType = phonePattern.test(username.trim()) ? 'PHONE_PASSWORD' : emailPattern.test(username.trim()) ? 'EMAIL_PASSWORD' : 'USERNAME_PASSWORD'
|
||||||
|
return {
|
||||||
|
username: username.trim(),
|
||||||
|
password,
|
||||||
|
verificationCode,
|
||||||
|
uuid,
|
||||||
|
code,
|
||||||
|
loginType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
state: {
|
state: {
|
||||||
token: getToken(),
|
token: getToken(),
|
||||||
|
|
@ -12,159 +41,110 @@ const user = {
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_TOKEN: (state, token) => {
|
SET_TOKEN(state, token) {
|
||||||
state.token = token
|
state.token = token
|
||||||
},
|
},
|
||||||
SET_EXPIRES_IN: (state, time) => {
|
SET_EXPIRES_IN(state, time) {
|
||||||
state.expires_in = time
|
state.expires_in = time
|
||||||
},
|
},
|
||||||
SET_ID: (state, id) => {
|
SET_ID(state, id) {
|
||||||
state.id = id
|
state.id = id
|
||||||
},
|
},
|
||||||
SET_NAME: (state, name) => {
|
SET_NAME(state, name) {
|
||||||
state.name = name
|
state.name = name
|
||||||
},
|
},
|
||||||
SET_AVATAR: (state, avatar) => {
|
SET_AVATAR(state, avatar) {
|
||||||
state.avatar = avatar
|
state.avatar = avatar
|
||||||
},
|
},
|
||||||
SET_ROLES: (state, roles) => {
|
SET_ROLES(state, roles) {
|
||||||
state.roles = roles
|
state.roles = roles
|
||||||
},
|
},
|
||||||
SET_PERMISSIONS: (state, permissions) => {
|
SET_PERMISSIONS(state, permissions) {
|
||||||
state.permissions = permissions
|
state.permissions = permissions
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
IsLogin({ commit }, userInfo) {
|
IsLogin({ commit }, userInfo) {
|
||||||
let payload = {}
|
const payload = buildPayload(userInfo)
|
||||||
const loginType = userInfo.loginType
|
return isLogin(payload)
|
||||||
if (loginType === 'password') {
|
.then(res => res)
|
||||||
payload = {
|
.catch(error => Promise.reject(error))
|
||||||
username: userInfo.username.trim(),
|
|
||||||
password: userInfo.password,
|
|
||||||
uuid: userInfo.uuid,
|
|
||||||
code: userInfo.code,
|
|
||||||
loginType: loginType
|
|
||||||
}
|
|
||||||
} else if (loginType === 'mobile') {
|
|
||||||
payload = {
|
|
||||||
mobile: userInfo.mobile.trim(),
|
|
||||||
verificationCode: userInfo.verificationCode,
|
|
||||||
uuid: userInfo.uuid,
|
|
||||||
code: userInfo.code,
|
|
||||||
loginType: loginType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
isLogin(payload).then(res => {
|
|
||||||
resolve(res);
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
IsAdmin({ commit }, userInfo) {
|
||||||
|
const payload = buildPayload(userInfo)
|
||||||
|
return isAdmin(payload)
|
||||||
|
.then(res => res)
|
||||||
|
.catch(error => Promise.reject(error))
|
||||||
|
},
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
Login({ commit }, userInfo) {
|
Login({ commit }, userInfo) {
|
||||||
let payload = {}
|
const payload = buildPayload(userInfo)
|
||||||
const loginType = userInfo.loginType
|
return login(payload)
|
||||||
if (loginType === 'password') {
|
.then(res => {
|
||||||
payload = {
|
const { access_token, expires_in } = res.data
|
||||||
username: userInfo.username.trim(),
|
setToken(access_token)
|
||||||
password: userInfo.password,
|
commit('SET_TOKEN', access_token)
|
||||||
uuid: userInfo.uuid,
|
setExpiresIn(expires_in)
|
||||||
code: userInfo.code,
|
commit('SET_EXPIRES_IN', expires_in)
|
||||||
loginType: loginType
|
|
||||||
}
|
|
||||||
} else if (loginType === 'mobile') {
|
|
||||||
payload = {
|
|
||||||
mobile: userInfo.mobile.trim(),
|
|
||||||
verificationCode: userInfo.verificationCode,
|
|
||||||
uuid: userInfo.uuid,
|
|
||||||
code: userInfo.code,
|
|
||||||
loginType: loginType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
login(payload).then(res => {
|
|
||||||
let data = res.data
|
|
||||||
setToken(data.access_token)
|
|
||||||
commit('SET_TOKEN', data.access_token)
|
|
||||||
setExpiresIn(data.expires_in)
|
|
||||||
commit('SET_EXPIRES_IN', data.expires_in)
|
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
})
|
||||||
})
|
.catch(error => Promise.reject(error))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取手机验证码
|
||||||
GetPhoneCode({ commit }, userInfo) {
|
GetPhoneCode({ commit }, userInfo) {
|
||||||
let payload = {}
|
const payload = {
|
||||||
payload = {
|
|
||||||
mobile: userInfo.mobile.trim(),
|
mobile: userInfo.mobile.trim(),
|
||||||
uuid: userInfo.uuid,
|
uuid: userInfo.uuid,
|
||||||
code: userInfo.code,
|
code: userInfo.code,
|
||||||
mobileCodeType: userInfo.mobileCodeType
|
mobileCodeType: userInfo.mobileCodeType
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return getPhoneCode(payload)
|
||||||
getPhoneCode(payload).then(res => {
|
.then(res => res)
|
||||||
resolve()
|
.catch(error => Promise.reject(error))
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
GetInfo({ commit, state }) {
|
GetInfo({ commit }) {
|
||||||
return new Promise((resolve, reject) => {
|
return getInfo()
|
||||||
getInfo().then(res => {
|
.then(res => {
|
||||||
const user = res.user
|
const user = res.user
|
||||||
const avatar = (user.avatar == '' || user.avatar == null) ? require('@/assets/images/profile.jpg') : user.avatar
|
const avatar = user.avatar ? user.avatar : require('@/assets/images/profile.jpg')
|
||||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
commit('SET_ROLES', res.roles && res.roles.length > 0 ? res.roles : ['ROLE_DEFAULT'])
|
||||||
commit('SET_ROLES', res.roles)
|
commit('SET_PERMISSIONS', res.permissions)
|
||||||
commit('SET_PERMISSIONS', res.permissions)
|
|
||||||
} else {
|
|
||||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
|
||||||
}
|
|
||||||
commit('SET_ID', user.userId)
|
commit('SET_ID', user.userId)
|
||||||
commit('SET_NAME', user.userName)
|
commit('SET_NAME', user.userName)
|
||||||
commit('SET_AVATAR', avatar)
|
commit('SET_AVATAR', avatar)
|
||||||
resolve(res)
|
return res
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
})
|
||||||
})
|
.catch(error => Promise.reject(error))
|
||||||
},
|
},
|
||||||
|
|
||||||
// 刷新token
|
// 刷新 token
|
||||||
RefreshToken({ commit, state }) {
|
RefreshToken({ commit, state }) {
|
||||||
return new Promise((resolve, reject) => {
|
return refreshToken(state.token)
|
||||||
refreshToken(state.token).then(res => {
|
.then(res => {
|
||||||
setExpiresIn(res.data)
|
const expiresIn = res.data
|
||||||
commit('SET_EXPIRES_IN', res.data)
|
setExpiresIn(expiresIn)
|
||||||
resolve()
|
commit('SET_EXPIRES_IN', expiresIn)
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
})
|
||||||
})
|
.catch(error => Promise.reject(error))
|
||||||
},
|
},
|
||||||
|
|
||||||
// 退出系统
|
// 退出登录
|
||||||
LogOut({ commit, state }) {
|
LogOut({ commit, state }) {
|
||||||
return new Promise((resolve, reject) => {
|
return logout(state.token)
|
||||||
logout(state.token).then(() => {
|
.then(() => {
|
||||||
commit('SET_TOKEN', '')
|
commit('SET_TOKEN', '')
|
||||||
commit('SET_ROLES', [])
|
commit('SET_ROLES', [])
|
||||||
commit('SET_PERMISSIONS', [])
|
commit('SET_PERMISSIONS', [])
|
||||||
removeToken()
|
removeToken()
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
})
|
||||||
})
|
.catch(error => Promise.reject(error))
|
||||||
},
|
},
|
||||||
|
|
||||||
// 前端 登出
|
// 前端退出
|
||||||
FedLogOut({ commit }) {
|
FedLogOut({ commit }) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
commit('SET_TOKEN', '')
|
commit('SET_TOKEN', '')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// src/config/passwordConfig.js
|
||||||
|
export default {
|
||||||
|
minLength: 8, // 密码最小长度
|
||||||
|
maxLength: 16, // 密码最大长度
|
||||||
|
requireUpperCase: true, // 是否需要大写字母
|
||||||
|
requireLowerCase: true, // 是否需要小写字母
|
||||||
|
requireDigit: true, // 是否需要数字
|
||||||
|
requireSpecialChar: true, // 是否需要特殊字符
|
||||||
|
weakPasswords: ['123456', 'password', 'qwerty'], // 弱密码列表
|
||||||
|
restrictConsecutiveChars: true, // 是否限制连续字符
|
||||||
|
maxConsecutiveChars: 3, // 最大连续字符数
|
||||||
|
excludeUsernameInPassword: true, // 是否不允许密码包含用户名
|
||||||
|
passwordHistoryLimit: 5 // 历史密码限制条数
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {CONFIG} from '@/utils/configure'
|
import { CONFIG } from '@/utils/configure'
|
||||||
|
import passwordConfig from '@/utils/passwordConfig'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
|
|
@ -149,3 +150,62 @@ export function validatePassword(rule, value, callback) {
|
||||||
}
|
}
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateNewPassword(rule, value, callback) {
|
||||||
|
// 使用配置文件中的策略进行验证
|
||||||
|
|
||||||
|
// 1. 检查密码长度
|
||||||
|
if (value.length < passwordConfig.minLength || value.length > passwordConfig.maxLength) {
|
||||||
|
callback(new Error('密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查密码复杂度
|
||||||
|
const hasUpperCase = /[A-Z]/.test(value)
|
||||||
|
const hasLowerCase = /[a-z]/.test(value)
|
||||||
|
const hasDigit = /\d/.test(value)
|
||||||
|
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value)
|
||||||
|
|
||||||
|
if (passwordConfig.requireUpperCase && !hasUpperCase) {
|
||||||
|
callback(new Error('密码必须包含大写字母!'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireLowerCase && !hasLowerCase) {
|
||||||
|
callback(new Error('密码必须包含小写字母!'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireDigit && !hasDigit) {
|
||||||
|
callback(new Error('密码必须包含数字!'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireSpecialChar && !hasSpecialChar) {
|
||||||
|
callback(new Error('密码必须包含特殊字符!'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 3. 检查是否包含弱密码
|
||||||
|
for (const weakPwd of passwordConfig.weakPasswords) {
|
||||||
|
if (value.includes(weakPwd)) {
|
||||||
|
callback(new Error(`密码包含常见的弱密码片段: ${weakPwd}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 4. 检查是否包含超过规定数量的连续字符
|
||||||
|
if (passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, passwordConfig.maxConsecutiveChars)) {
|
||||||
|
callback(new Error(`密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback() // 验证成功
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsConsecutiveCharacters(password, maxConsecutive) {
|
||||||
|
let count = 1
|
||||||
|
for (let i = 1; i < password.length; i++) {
|
||||||
|
if (password[i] === password[i - 1]) {
|
||||||
|
count++
|
||||||
|
if (count > maxConsecutive) return true
|
||||||
|
} else {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<b>当前版本:</b> <span>v{{ version }}</span>
|
<b>当前版本:</b> <span>v{{ version }}</span>
|
||||||
</p>
|
</p>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :sm="24" :lg="12" style="padding-left: 50px">
|
<el-col :sm="24" :lg="12" style="padding-left: 50px">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
|
|
@ -57,9 +56,6 @@
|
||||||
>http://www.ahbonus.cn</el-link
|
>http://www.ahbonus.cn</el-link
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
@ -91,6 +87,8 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,21 @@
|
||||||
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
|
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-show="isAdmin" prop="verificationCode">
|
||||||
|
<el-input v-model="loginForm.verificationCode" placeholder="请输入验证码">
|
||||||
|
<template slot="append">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="sendAdminCode"
|
||||||
|
:disabled="isSendingCode || captchaEnabled?!loginForm.code:false"
|
||||||
|
class="send-code-button"
|
||||||
|
>
|
||||||
|
{{ countdown === 0 ? '获取验证码' : `${countdown}s` }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<svg-icon slot="prefix" icon-class="message" class="el-input__icon input-icon"/>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-form-item prop="mobile">
|
<el-form-item prop="mobile">
|
||||||
|
|
@ -92,12 +107,22 @@
|
||||||
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<el-form-item style="width: 100%;">
|
<el-form-item style="width: 100%;">
|
||||||
<el-button
|
<el-button v-if="!isAdmin"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
size="medium"
|
size="medium"
|
||||||
type="primary"
|
type="primary"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
@click.native.prevent="isLogin"
|
@click="loginMethod === 'password' ?IsAdmin():isLogin()"
|
||||||
|
>
|
||||||
|
<span v-if="!loading">登 录</span>
|
||||||
|
<span v-else>登 录 中...</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="isAdmin"
|
||||||
|
:loading="loading"
|
||||||
|
size="medium"
|
||||||
|
type="primary"
|
||||||
|
style="width: 100%;"
|
||||||
|
@click="isLogin()"
|
||||||
>
|
>
|
||||||
<span v-if="!loading">登 录</span>
|
<span v-if="!loading">登 录</span>
|
||||||
<span v-else>登 录 中...</span>
|
<span v-else>登 录 中...</span>
|
||||||
|
|
@ -140,6 +165,7 @@ export default {
|
||||||
wx: wx,
|
wx: wx,
|
||||||
qq: qq,
|
qq: qq,
|
||||||
codeUrl: '',
|
codeUrl: '',
|
||||||
|
isAdmin: false,
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
|
@ -228,7 +254,7 @@ export default {
|
||||||
Cookies.remove('password')
|
Cookies.remove('password')
|
||||||
Cookies.remove('rememberMe')
|
Cookies.remove('rememberMe')
|
||||||
}
|
}
|
||||||
this.loginForm.loginType = this.loginMethod
|
this.loginForm.loginMethod = this.loginMethod
|
||||||
this.$store.dispatch('Login', this.loginForm)
|
this.$store.dispatch('Login', this.loginForm)
|
||||||
.then(() => this.$router.push({ path: this.redirect || '/' }))
|
.then(() => this.$router.push({ path: this.redirect || '/' }))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
@ -238,6 +264,17 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
IsAdmin() {
|
||||||
|
this.loginForm.loginMethod = this.loginMethod
|
||||||
|
this.$store.dispatch('IsAdmin', this.loginForm).then(res => {
|
||||||
|
if (res.data) {
|
||||||
|
this.isAdmin = res.data
|
||||||
|
//this.isLogin()
|
||||||
|
} else {
|
||||||
|
this.isLogin()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
isLogin() {
|
isLogin() {
|
||||||
this.$refs.loginForm.validate(valid => {
|
this.$refs.loginForm.validate(valid => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
|
@ -252,7 +289,7 @@ export default {
|
||||||
Cookies.remove('rememberMe')
|
Cookies.remove('rememberMe')
|
||||||
}
|
}
|
||||||
let that = this
|
let that = this
|
||||||
this.loginForm.loginType = this.loginMethod
|
this.loginForm.loginMethod = this.loginMethod
|
||||||
this.$store.dispatch('IsLogin', this.loginForm)
|
this.$store.dispatch('IsLogin', this.loginForm)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
|
|
@ -274,12 +311,36 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
sendAdminCode(){
|
||||||
|
this.loginForm.mobile = this.loginForm.username
|
||||||
|
this.$store.dispatch('GetPhoneCode', this.loginForm)
|
||||||
|
.then(() => {
|
||||||
|
this.isSendingCode = true
|
||||||
|
this.countdown = 60
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
this.countdown -= 1
|
||||||
|
if (this.countdown <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
this.isSendingCode = false
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
// 如果启用了验证码,则重新获取验证码
|
||||||
|
if (this.captchaEnabled) {
|
||||||
|
this.getCode()
|
||||||
|
}
|
||||||
|
// 其他失败处理逻辑
|
||||||
|
this.isSendingCode = false
|
||||||
|
this.countdown = 0
|
||||||
|
})
|
||||||
|
},
|
||||||
sendCode() {
|
sendCode() {
|
||||||
if (this.captchaEnabled ? !this.loginForm.code : false) {
|
if (this.captchaEnabled ? !this.loginForm.code : false) {
|
||||||
this.$message.error('请先填写验证码')
|
this.$message.error('请先填写验证码')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.loginForm.mobile) {
|
if (!this.loginForm.mobile) {
|
||||||
this.$message.error('请先填写' + getCodePlaceholderText())
|
this.$message.error('请先填写' + getCodePlaceholderText())
|
||||||
return
|
return
|
||||||
|
|
@ -314,6 +375,7 @@ export default {
|
||||||
this.loginForm.code = ''
|
this.loginForm.code = ''
|
||||||
this.loginForm.verificationCode = ''
|
this.loginForm.verificationCode = ''
|
||||||
this.loginForm.loginMethod = ''
|
this.loginForm.loginMethod = ''
|
||||||
|
this.isAdmin=false
|
||||||
this.getCode()
|
this.getCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getCodeImg, register } from '@/api/login'
|
import { getCodeImg, register } from '@/api/login'
|
||||||
import { validatePassword } from '@/utils/validate'
|
import { validateNewPassword, validatePassword } from '@/utils/validate'
|
||||||
import { CONFIG,REGISTER_CONFIG } from '@/utils/configure'
|
import { CONFIG, REGISTER_CONFIG } from '@/utils/configure'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Register',
|
name: 'Register',
|
||||||
|
|
@ -142,7 +142,6 @@ export default {
|
||||||
nickName: '',
|
nickName: '',
|
||||||
code: '',
|
code: '',
|
||||||
uuid: '',
|
uuid: '',
|
||||||
loginType: 'mobile',
|
|
||||||
mobileCodeType: 'register'
|
mobileCodeType: 'register'
|
||||||
},
|
},
|
||||||
registerRules: {
|
registerRules: {
|
||||||
|
|
@ -155,7 +154,7 @@ export default {
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, trigger: 'blur', message: '请输入您的密码' },
|
{ required: true, trigger: 'blur', message: '请输入您的密码' },
|
||||||
{ validator: validatePassword, trigger: 'blur' }
|
{ validator: validateNewPassword, trigger: 'blur' }
|
||||||
],
|
],
|
||||||
confirmPassword: [
|
confirmPassword: [
|
||||||
{ required: true, trigger: 'blur', message: '请再次输入您的密码' },
|
{ required: true, trigger: 'blur', message: '请再次输入您的密码' },
|
||||||
|
|
|
||||||
|
|
@ -305,8 +305,9 @@ import {
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import Treeselect from '@riophae/vue-treeselect'
|
import Treeselect from '@riophae/vue-treeselect'
|
||||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
||||||
import { validPwd, validatePassword } from '@/utils/validate'
|
import { validPwd, validatePassword, validateNewPassword } from '@/utils/validate'
|
||||||
import { hashWithSM3AndSalt } from '@/utils/sm'
|
import { hashWithSM3AndSalt } from '@/utils/sm'
|
||||||
|
import passwordConfig from '@/utils/passwordConfig'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'User',
|
name: 'User',
|
||||||
|
|
@ -416,7 +417,7 @@ export default {
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: '密码不能为空', trigger: 'blur' },
|
{ required: true, message: '密码不能为空', trigger: 'blur' },
|
||||||
{ validator: validatePassword, trigger: 'blur' }
|
{ validator: validateNewPassword, trigger: 'blur' }
|
||||||
],
|
],
|
||||||
roleIds: [
|
roleIds: [
|
||||||
{ required: true, message: '请选择角色', trigger: 'change' }
|
{ required: true, message: '请选择角色', trigger: 'change' }
|
||||||
|
|
@ -612,11 +613,13 @@ export default {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
closeOnClickModal: false,
|
closeOnClickModal: false,
|
||||||
inputPattern: /^.{8,20}$/,
|
inputPattern: /^.{8,16}$/,
|
||||||
inputErrorMessage: '用户密码长度必须介于 8 和 20 之间',
|
inputErrorMessage: '用户密码长度必须介于 8 和 16 之间',
|
||||||
inputValidator: (value) => {
|
inputValidator: (value) => {
|
||||||
if (!/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,20}$/.test(value)) {
|
// 调用 validateNewPassword 校验
|
||||||
return '密码规则为:至少一个字母,一个数字和一个特殊字符'
|
const errorMessage = this.validateNewPasswordForPrompt(row.userName, value)
|
||||||
|
if (errorMessage) {
|
||||||
|
return errorMessage // 返回错误信息进行提示
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(({ value }) => {
|
}).then(({ value }) => {
|
||||||
|
|
@ -694,6 +697,64 @@ export default {
|
||||||
// 提交上传文件
|
// 提交上传文件
|
||||||
submitFileForm() {
|
submitFileForm() {
|
||||||
this.$refs.upload.submit()
|
this.$refs.upload.submit()
|
||||||
|
},
|
||||||
|
validateNewPasswordForPrompt(username, newPassword) {
|
||||||
|
// 1. 检查密码长度
|
||||||
|
if (newPassword.length < passwordConfig.minLength || newPassword.length > passwordConfig.maxLength) {
|
||||||
|
return '密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查密码复杂度
|
||||||
|
const hasUpperCase = /[A-Z]/.test(newPassword)
|
||||||
|
const hasLowerCase = /[a-z]/.test(newPassword)
|
||||||
|
const hasDigit = /\d/.test(newPassword)
|
||||||
|
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(newPassword)
|
||||||
|
|
||||||
|
if (passwordConfig.requireUpperCase && !hasUpperCase) {
|
||||||
|
return '密码必须包含大写字母!'
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireLowerCase && !hasLowerCase) {
|
||||||
|
return '密码必须包含小写字母!'
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireDigit && !hasDigit) {
|
||||||
|
return '密码必须包含数字!'
|
||||||
|
}
|
||||||
|
if (passwordConfig.requireSpecialChar && !hasSpecialChar) {
|
||||||
|
return '密码必须包含特殊字符!'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否包含弱密码
|
||||||
|
for (const weakPwd of passwordConfig.weakPasswords) {
|
||||||
|
if (newPassword.includes(weakPwd)) {
|
||||||
|
return `密码包含常见的弱密码片段: ${weakPwd}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查是否包含超过规定数量的连续字符
|
||||||
|
if (passwordConfig.restrictConsecutiveChars && this.containsConsecutiveCharacters(newPassword, passwordConfig.maxConsecutiveChars)) {
|
||||||
|
return `密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 检查密码中是否包含用户名
|
||||||
|
if (passwordConfig.excludeUsernameInPassword && newPassword.includes(username)) {
|
||||||
|
return '密码不能包含账号!'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 新密码通过所有校验,返回空表示通过校验
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
containsConsecutiveCharacters(password, maxConsecutive) {
|
||||||
|
let count = 1
|
||||||
|
for (let i = 1; i < password.length; i++) {
|
||||||
|
if (password[i] === password[i - 1]) {
|
||||||
|
count++
|
||||||
|
if (count > maxConsecutive) return true
|
||||||
|
} else {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { updateUserPwd } from "@/api/system/user";
|
import { updateUserPwd } from "@/api/system/user";
|
||||||
import { validatePassword } from '@/utils/validate'
|
import { validateNewPassword, validatePassword } from '@/utils/validate'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -42,7 +42,7 @@ export default {
|
||||||
],
|
],
|
||||||
newPassword: [
|
newPassword: [
|
||||||
{ required: true, message: "新密码不能为空", trigger: "blur" },
|
{ required: true, message: "新密码不能为空", trigger: "blur" },
|
||||||
{ validator: validatePassword, trigger: 'blur' }
|
{ validator: validateNewPassword, trigger: 'blur' }
|
||||||
],
|
],
|
||||||
confirmPassword: [
|
confirmPassword: [
|
||||||
{ required: true, message: "确认密码不能为空", trigger: "blur" },
|
{ required: true, message: "确认密码不能为空", trigger: "blur" },
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,15 @@ module.exports = {
|
||||||
pathRewrite: {
|
pathRewrite: {
|
||||||
['^' + process.env.VUE_APP_BASE_API]: ''
|
['^' + process.env.VUE_APP_BASE_API]: ''
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"/api" : {
|
||||||
|
"target" : "http://192.168.0.21:17861",
|
||||||
|
//设置允许跨域——此处我经过测试发现可有可无
|
||||||
|
"changeOrigin" : true,
|
||||||
|
"pathRewrite" : {
|
||||||
|
"^/api" : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
disableHostCheck: true
|
disableHostCheck: true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue