From eb9e9db8b1344c246502e78c8bc02d9270004373 Mon Sep 17 00:00:00 2001
From: jiang
Date: Sun, 8 Sep 2024 20:13:32 +0800
Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E9=97=AE?=
=?UTF-8?q?=E9=A2=98=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/login.js | 12 ++
src/api/system/user.js | 9 +
src/layout/index.vue | 159 ++++++++++++-----
src/store/modules/user.js | 188 +++++++++------------
src/utils/passwordConfig.js | 14 ++
src/utils/validate.js | 62 ++++++-
src/views/index.vue | 6 +-
src/views/index_v1.vue | 2 +-
src/views/login1.vue | 72 +++++++-
src/views/register1.vue | 7 +-
src/views/system/user/index.vue | 73 +++++++-
src/views/system/user/profile/resetPwd.vue | 4 +-
vue.config.js | 10 +-
13 files changed, 452 insertions(+), 166 deletions(-)
create mode 100644 src/utils/passwordConfig.js
diff --git a/src/api/login.js b/src/api/login.js
index 899b1311..bf8772db 100644
--- a/src/api/login.js
+++ b/src/api/login.js
@@ -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) {
return request({
url: '/auth/getPhoneCode',
diff --git a/src/api/system/user.js b/src/api/system/user.js
index 450f9dd1..cd5af3d9 100644
--- a/src/api/system/user.js
+++ b/src/api/system/user.js
@@ -145,3 +145,12 @@ export function approvalStatus(data) {
data: data
})
}
+
+//用户注册审批
+export function checkPasswordStatus(data) {
+ return request({
+ url: '/system/user/checkPasswordStatus',
+ method: 'get'
+ })
+}
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
index dba4393d..26f9ebf7 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -12,6 +12,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -21,9 +44,43 @@ import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
+import { validateNewPassword } from '@/utils/validate'
+import { updateUserPwd, checkPasswordStatus } from '@/api/system/user'
export default {
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: {
AppMain,
Navbar,
@@ -51,61 +108,87 @@ export default {
}
},
variables() {
- return variables;
+ return variables
}
},
+ created() {
+ this.checkPasswordStatus()
+ },
methods: {
+ checkPasswordStatus() {
+ checkPasswordStatus().then(response => {
+ if (response.code === 200) {
+ this.showChangePasswordDialog = response.data
+ this.title = response.msg
+ }
+ })
+ },
handleClickOutside() {
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';
+ })
}
}
}
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index fdede50d..6520776b 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -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'
+// 更严格的手机号和邮箱正则表达式
+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 = {
state: {
token: getToken(),
@@ -12,159 +41,110 @@ const user = {
},
mutations: {
- SET_TOKEN: (state, token) => {
+ SET_TOKEN(state, token) {
state.token = token
},
- SET_EXPIRES_IN: (state, time) => {
+ SET_EXPIRES_IN(state, time) {
state.expires_in = time
},
- SET_ID: (state, id) => {
+ SET_ID(state, id) {
state.id = id
},
- SET_NAME: (state, name) => {
+ SET_NAME(state, name) {
state.name = name
},
- SET_AVATAR: (state, avatar) => {
+ SET_AVATAR(state, avatar) {
state.avatar = avatar
},
- SET_ROLES: (state, roles) => {
+ SET_ROLES(state, roles) {
state.roles = roles
},
- SET_PERMISSIONS: (state, permissions) => {
+ SET_PERMISSIONS(state, permissions) {
state.permissions = permissions
}
},
actions: {
IsLogin({ commit }, userInfo) {
- let payload = {}
- const loginType = userInfo.loginType
- if (loginType === 'password') {
- payload = {
- 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)
- })
- })
+ const payload = buildPayload(userInfo)
+ return isLogin(payload)
+ .then(res => res)
+ .catch(error => Promise.reject(error))
},
+ IsAdmin({ commit }, userInfo) {
+ const payload = buildPayload(userInfo)
+ return isAdmin(payload)
+ .then(res => res)
+ .catch(error => Promise.reject(error))
+ },
+
// 登录
Login({ commit }, userInfo) {
- let payload = {}
- const loginType = userInfo.loginType
- if (loginType === 'password') {
- payload = {
- 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) => {
- 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)
+ const payload = buildPayload(userInfo)
+ return login(payload)
+ .then(res => {
+ const { access_token, expires_in } = res.data
+ setToken(access_token)
+ commit('SET_TOKEN', access_token)
+ setExpiresIn(expires_in)
+ commit('SET_EXPIRES_IN', expires_in)
})
- })
+ .catch(error => Promise.reject(error))
},
+
+ // 获取手机验证码
GetPhoneCode({ commit }, userInfo) {
- let payload = {}
- payload = {
+ const payload = {
mobile: userInfo.mobile.trim(),
uuid: userInfo.uuid,
code: userInfo.code,
mobileCodeType: userInfo.mobileCodeType
}
- return new Promise((resolve, reject) => {
- getPhoneCode(payload).then(res => {
- resolve()
- }).catch(error => {
- reject(error)
- })
- })
+ return getPhoneCode(payload)
+ .then(res => res)
+ .catch(error => Promise.reject(error))
},
+
// 获取用户信息
- GetInfo({ commit, state }) {
- return new Promise((resolve, reject) => {
- getInfo().then(res => {
+ GetInfo({ commit }) {
+ return getInfo()
+ .then(res => {
const user = res.user
- const avatar = (user.avatar == '' || user.avatar == null) ? require('@/assets/images/profile.jpg') : user.avatar
- if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
- commit('SET_ROLES', res.roles)
- commit('SET_PERMISSIONS', res.permissions)
- } else {
- commit('SET_ROLES', ['ROLE_DEFAULT'])
- }
+ const avatar = user.avatar ? user.avatar : require('@/assets/images/profile.jpg')
+ commit('SET_ROLES', res.roles && res.roles.length > 0 ? res.roles : ['ROLE_DEFAULT'])
+ commit('SET_PERMISSIONS', res.permissions)
commit('SET_ID', user.userId)
commit('SET_NAME', user.userName)
commit('SET_AVATAR', avatar)
- resolve(res)
- }).catch(error => {
- reject(error)
+ return res
})
- })
+ .catch(error => Promise.reject(error))
},
- // 刷新token
+ // 刷新 token
RefreshToken({ commit, state }) {
- return new Promise((resolve, reject) => {
- refreshToken(state.token).then(res => {
- setExpiresIn(res.data)
- commit('SET_EXPIRES_IN', res.data)
- resolve()
- }).catch(error => {
- reject(error)
+ return refreshToken(state.token)
+ .then(res => {
+ const expiresIn = res.data
+ setExpiresIn(expiresIn)
+ commit('SET_EXPIRES_IN', expiresIn)
})
- })
+ .catch(error => Promise.reject(error))
},
- // 退出系统
+ // 退出登录
LogOut({ commit, state }) {
- return new Promise((resolve, reject) => {
- logout(state.token).then(() => {
+ return logout(state.token)
+ .then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
- resolve()
- }).catch(error => {
- reject(error)
})
- })
+ .catch(error => Promise.reject(error))
},
- // 前端 登出
+ // 前端退出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
diff --git a/src/utils/passwordConfig.js b/src/utils/passwordConfig.js
new file mode 100644
index 00000000..f2a8f9be
--- /dev/null
+++ b/src/utils/passwordConfig.js
@@ -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 // 历史密码限制条数
+}
diff --git a/src/utils/validate.js b/src/utils/validate.js
index c83e72d0..e5a1abcf 100644
--- a/src/utils/validate.js
+++ b/src/utils/validate.js
@@ -1,4 +1,5 @@
-import {CONFIG} from '@/utils/configure'
+import { CONFIG } from '@/utils/configure'
+import passwordConfig from '@/utils/passwordConfig'
/**
* @param {string} path
@@ -149,3 +150,62 @@ export function validatePassword(rule, value, 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
+}
diff --git a/src/views/index.vue b/src/views/index.vue
index d5548d6f..da69c889 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -7,7 +7,6 @@
当前版本: v{{ version }}
-
@@ -57,9 +56,6 @@
>http://www.ahbonus.cn
-
-
-
@@ -91,6 +87,8 @@
+
+
diff --git a/src/views/index_v1.vue b/src/views/index_v1.vue
index d2d2ec63..405cb9e7 100644
--- a/src/views/index_v1.vue
+++ b/src/views/index_v1.vue
@@ -25,7 +25,7 @@
-
+
diff --git a/src/views/login1.vue b/src/views/login1.vue
index 6e372f53..2adf4dbe 100644
--- a/src/views/login1.vue
+++ b/src/views/login1.vue
@@ -40,6 +40,21 @@
+
+
+
+
+ {{ countdown === 0 ? '获取验证码' : `${countdown}s` }}
+
+
+
+
+
@@ -92,12 +107,22 @@
记住密码
-
+ 登 录
+ 登 录 中...
+
+
登 录
登 录 中...
@@ -140,6 +165,7 @@ export default {
wx: wx,
qq: qq,
codeUrl: '',
+ isAdmin: false,
loginForm: {
username: '',
password: '',
@@ -228,7 +254,7 @@ export default {
Cookies.remove('password')
Cookies.remove('rememberMe')
}
- this.loginForm.loginType = this.loginMethod
+ this.loginForm.loginMethod = this.loginMethod
this.$store.dispatch('Login', this.loginForm)
.then(() => this.$router.push({ path: this.redirect || '/' }))
.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() {
this.$refs.loginForm.validate(valid => {
if (valid) {
@@ -252,7 +289,7 @@ export default {
Cookies.remove('rememberMe')
}
let that = this
- this.loginForm.loginType = this.loginMethod
+ this.loginForm.loginMethod = this.loginMethod
this.$store.dispatch('IsLogin', this.loginForm)
.then(res => {
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() {
if (this.captchaEnabled ? !this.loginForm.code : false) {
this.$message.error('请先填写验证码')
return
}
-
if (!this.loginForm.mobile) {
this.$message.error('请先填写' + getCodePlaceholderText())
return
@@ -314,6 +375,7 @@ export default {
this.loginForm.code = ''
this.loginForm.verificationCode = ''
this.loginForm.loginMethod = ''
+ this.isAdmin=false
this.getCode()
}
}
diff --git a/src/views/register1.vue b/src/views/register1.vue
index 80584a87..6b7edb5c 100644
--- a/src/views/register1.vue
+++ b/src/views/register1.vue
@@ -97,8 +97,8 @@