用户登录问题修改

This commit is contained in:
jiang 2024-09-08 20:13:32 +08:00
parent 312c9ebaec
commit eb9e9db8b1
13 changed files with 452 additions and 166 deletions

View File

@ -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',

View File

@ -145,3 +145,12 @@ export function approvalStatus(data) {
data: data data: data
}) })
} }
//用户注册审批
export function checkPasswordStatus(data) {
return request({
url: '/system/user/checkPasswordStatus',
method: 'get'
})
}

View File

@ -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>

View File

@ -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', '')

View File

@ -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 // 历史密码限制条数
}

View File

@ -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
}

View File

@ -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>

View File

@ -25,7 +25,7 @@
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>

View File

@ -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()
} }
} }

View File

@ -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: '请再次输入您的密码' },

View File

@ -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
} }
} }
} }

View File

@ -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" },

View File

@ -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
}, },