v3.6.0 三级等保重磅更新:1、【新增】双因子方式登录;2、【新增】定期修改密码;3、【新增】最大活跃时间;4、【新增】敏感数据脱敏;5、【新增】登录锁定配置;6、【新增】密码复杂度配置;7、【新增】三级等保可配置

This commit is contained in:
zhuoda
2024-09-02 22:20:29 +08:00
parent ac7c9940bf
commit 92dddd507b
80 changed files with 2770 additions and 501 deletions

View File

@@ -95,6 +95,8 @@
departmentId: undefined,
// 是否启用
disabledFlag: undefined,
// 邮箱
email: undefined,
// 备注
remark: '',
};
@@ -126,6 +128,7 @@
form.employeeId = data.employeeId;
form.loginName = data.loginName;
form.actualName = data.actualName;
form.email = data.email;
form.gender = data.gender;
form.phone = data.phone;
form.departmentId = data.departmentId;

View File

@@ -1,40 +1,75 @@
<template>
<div class="password-container">
<!-- 页面标题-->
<div class="header-title">修改密码</div>
<!-- 内容区域-->
<div class="password-form-area">
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="原密码" name="oldPassword">
<a-input class="form-item" v-model:value.trim="form.oldPassword" type="password" placeholder="请输入原密码" />
<a-input-password class="form-item" v-model:value.trim="form.oldPassword" type="password" placeholder="请输入原密码" />
</a-form-item>
<a-form-item label="新密码" name="newPassword" :help="tips">
<a-input class="form-item" v-model:value.trim="form.newPassword" type="password" placeholder="请输入新密码" />
<a-input-password class="form-item" v-model:value.trim="form.newPassword" type="password" placeholder="请输入新密码" />
</a-form-item>
<a-form-item label="确认密码" name="confirmPwd" :help="tips">
<a-input class="form-item" v-model:value.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" />
<a-input-password class="form-item" v-model:value.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" />
</a-form-item>
</a-form>
<a-button type="primary" @click="onSubmit">修改密码</a-button>
<a-button type="primary" style="margin: 20px 0 0 250px" @click="onSubmit">修改密码</a-button>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
import { employeeApi } from '/@/api/system/employee-api.js';
import { smartSentry } from '/@/lib/smart-sentry.js';
const formRef = ref();
const tips = '密码长度8-20位且包含大写字母、小写字母、数字三种'; //校验规则
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
const emits = defineEmits(['onSuccess']);
const rules = {
const formRef = ref();
const passwordComplexityEnabledTips = '密码长度8-20位必须包含字母、数字、特殊符号@#$%^&*()_+-=)等三种字符'; //校验规则
const passwordTips = '密码长度至少8位';
const tips = ref(passwordTips);
const reg =
/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]{8,20}$/;
// 获取系统的密码复杂度
const passwordComplexityEnabledFlag = ref(false);
async function getPasswordComplexityEnabled() {
try {
SmartLoading.show();
let res = await employeeApi.getPasswordComplexityEnabled();
passwordComplexityEnabledFlag.value = res.data;
tips.value = passwordComplexityEnabledFlag.value ? passwordComplexityEnabledTips : passwordTips;
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
onMounted(getPasswordComplexityEnabled);
const passwordComplexityEnabledRules = {
oldPassword: [{ required: true, message: '请输入原密码' }],
newPassword: [{ required: true, type: 'string', pattern: reg, message: '密码格式错误' }],
confirmPwd: [{ required: true, type: 'string', pattern: reg, message: '请输入确认密码' }],
};
const commonRules = {
oldPassword: [{ required: true, message: '请输入原密码' }],
newPassword: [
{ required: true, message: '密码格式错误' },
{ min: 8, message: '密码长度至少8位' },
],
confirmPwd: [
{ required: true, message: '密码格式错误' },
{ min: 8, message: '密码长度至少8位' },
],
};
const rules = computed(() => {
return passwordComplexityEnabledFlag.value ? passwordComplexityEnabledRules : commonRules;
});
const formDefault = {
oldPassword: '',
@@ -56,9 +91,12 @@
try {
await employeeApi.updateEmployeePassword(form);
message.success('修改成功');
form.oldPassword = '';
form.newPassword = '';
form.confirmPwd = '';
emits('onSuccess');
} catch (error) {
smartSentry.captureError(error);
} finally {
@@ -81,7 +119,7 @@
margin-top: 30px;
.form-item {
width: 500px !important;
width: 550px !important;
}
}
}

View File

@@ -32,6 +32,9 @@
<a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
<p class="hint">初始密码默认为随机</p>
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value.trim="form.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item label="性别" name="gender">
<smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
</a-form-item>
@@ -143,6 +146,7 @@
departmentId: [{ required: true, message: '部门不能为空' }],
disabledFlag: [{ required: true, message: '状态不能为空' }],
leaveFlag: [{ required: true, message: '在职状态不能为空' }],
email: [{ required: true, message: '请输入邮箱' }],
};
// 校验表单

View File

@@ -155,6 +155,11 @@
dataIndex: 'phone',
width: 85,
},
{
title: '邮箱',
dataIndex: 'email',
width: 100,
},
{
title: '超管',
dataIndex: 'administratorFlag',
@@ -237,6 +242,9 @@
params.pageNum = 1;
params.departmentId = allDepartment ? undefined : props.departmentId;
let res = await employeeApi.queryEmployee(params);
for (const item of res.data.list) {
item.roleNameList = _.join(item.roleNameList, ',');
}
tableData.value = res.data.list;
total.value = res.data.total;
// 清除选中

View File

@@ -0,0 +1,92 @@
<!--
* 客服人员弹窗
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-06 20:40:16
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
-->
<template>
<a-modal :open="visible" width="600px" :bodyStyle="{height:'480px'}" title="" :closable="false" :maskClosable="true">
<a-row><div style="font-weight:bolder;margin: 0 auto;font-size: 16px">助力卓大抖音1000个粉丝开播写代码🎉🎉</div> </a-row>
<a-row><div style="font-weight:bolder;margin: 20px auto;font-size: 15px">和1024创新实验室一起热爱代码热爱生活永远年轻永远前行🎉🎉</div> </a-row>
<br />
<div class="app-qr-box">
<div class="app-qr">
<a-image
:width="300"
style="border-radius: 15px;"
src="https://img.smartadmin.1024lab.net/wechat/douyin.png"
/>
<span class="qr-desc strong"> 打开抖音APP-点击左上角侧边栏-点击扫一扫-进行关注</span>
</div>
</div>
<template #footer>
<a-button type="primary" @click="hide">知道了</a-button>
</template>
</a-modal>
</template>
<script setup>
import {ref} from 'vue';
defineExpose({
show,
});
const visible = ref(false);
function show() {
visible.value = true;
}
function hide() {
visible.value = false;
}
</script>
<style lang="less" scoped>
.app-qr-box {
display: flex;
align-items: center;
justify-content: space-around;
.app-qr {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
> img {
width: 100%;
height: 100%;
}
.strong {
font-weight: 600;
}
.qr-desc {
display: flex;
margin-top: 20px;
align-items: center;
font-size: 13px;
color: red;
text-align: center;
overflow-x: hidden;
> img {
width: 15px;
height: 18px;
margin-right: 9px;
}
}
}
}
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 120px;
line-height: 120px;
width: 120px;
background: #364d79;
overflow: hidden;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>

View File

@@ -1,11 +1,11 @@
<!--
* 首页
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
-->
<template>
@@ -68,21 +68,17 @@
import { computed } from 'vue';
import HomeHeader from './home-header.vue';
import HomeNotice from './home-notice.vue';
import HomeQuickEntry from './components/quick-entry/home-quick-entry.vue';
import OfficialAccountCard from './components/official-account-card.vue';
import ToBeDoneCard from './components/to-be-done-card.vue';
import ChangelogCard from './components/changelog-card.vue';
import Gauge from './components/echarts/gauge.vue';
import Category from './components/echarts/category.vue';
import Pie from './components/echarts/pie.vue';
import Gradient from './components/echarts/gradient.vue';
import { Modal } from 'ant-design-vue';
// 业绩完成百分比
const saleTargetPercent = computed(() => {
return 75;
});
</script>
<style lang="less" scoped>
@import './index.less';

View File

@@ -8,7 +8,7 @@
justify-content: center;
.box-item {
width: 444px;
height: 570px;
height: 600px;
&.desc {
background: #003b94;
border-radius: 12px 0px 0px 12px;
@@ -148,6 +148,12 @@
border-radius: 4px;
}
.code-btn{
height: 44px;
padding: 4px 5px;
width: 108px;
}
.eye-box {
position: absolute;
right: 15px;

View File

@@ -59,12 +59,20 @@
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
<a-input-group compact>
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
{{ emailCodeTips }}
</a-button>
</a-input-group>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item name="captchaCode">
@@ -146,7 +154,7 @@
onMounted(() => {
document.onkeyup = (e) => {
if (e.keyCode == 13) {
if (e.keyCode === 13) {
onLogin();
}
};
@@ -185,7 +193,7 @@
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
stopRefreshCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
@@ -213,27 +221,78 @@
let captchaResult = await loginApi.getCaptcha();
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
loginForm.captchaUuid = captchaResult.data.captchaUuid;
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
} catch (e) {
console.log(e);
}
}
let refrestCaptchaInterval = null;
function beginRefrestCaptchaInterval(expireSeconds) {
if (refrestCaptchaInterval === null) {
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
let refreshCaptchaInterval = null;
function beginRefreshCaptchaInterval(expireSeconds) {
if (refreshCaptchaInterval === null) {
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefrestCaptchaInterval() {
if (refrestCaptchaInterval != null) {
clearInterval(refrestCaptchaInterval);
refrestCaptchaInterval = null;
function stopRefreshCaptchaInterval() {
if (refreshCaptchaInterval != null) {
clearInterval(refreshCaptchaInterval);
refreshCaptchaInterval = null;
}
}
onMounted(getCaptcha);
onMounted(() => {
getCaptcha();
getTwoFactorLoginFlag();
});
//--------------------- 邮箱验证码 ---------------------------------
const emailCodeShowFlag = ref(false);
let emailCodeTips = ref('获取邮箱验证码');
let emailCodeButtonDisabled = ref(false);
// 定时器
let countDownTimer = null;
// 开始倒计时
function runCountDown() {
emailCodeButtonDisabled.value = true;
let countDown = 60;
emailCodeTips.value = `${countDown}秒后重新获取`;
countDownTimer = setInterval(() => {
if (countDown > 1) {
countDown--;
emailCodeTips.value = `${countDown}秒后重新获取`;
} else {
clearInterval(countDownTimer);
emailCodeButtonDisabled.value = false;
emailCodeTips.value = '获取验证码';
}
}, 1000);
}
// 获取双因子登录标识
async function getTwoFactorLoginFlag() {
try {
let result = await loginApi.getTwoFactorLoginFlag();
emailCodeShowFlag.value = result.data;
} catch (e) {
smartSentry.captureError(e);
}
}
// 发送邮箱验证码
async function sendSmsCode() {
try {
SmartLoading.show();
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
message.success('验证码发送成功!请登录邮箱查看验证码~');
runCountDown();
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
</script>
<style lang="less" scoped>
@import './login.less';

View File

@@ -8,7 +8,7 @@
justify-content: center;
.box-item {
width: 444px;
height: 570px;
height: 600px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
@@ -135,6 +135,11 @@
border: 1px solid #ededed;
border-radius: 4px;
}
.code-btn{
height: 44px;
padding: 4px 5px;
width: 108px;
}
.eye-box {
position: absolute;

View File

@@ -23,12 +23,20 @@
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
<a-input-group compact>
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
{{ emailCodeTips }}
</a-button>
</a-input-group>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item name="captchaCode">
@@ -107,7 +115,7 @@
onMounted(() => {
document.onkeyup = (e) => {
if (e.keyCode == 13) {
if (e.keyCode === 13) {
onLogin();
}
};
@@ -127,7 +135,7 @@
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
stopRefreshCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
@@ -155,27 +163,78 @@
let captchaResult = await loginApi.getCaptcha();
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
loginForm.captchaUuid = captchaResult.data.captchaUuid;
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
} catch (e) {
console.log(e);
}
}
let refrestCaptchaInterval = null;
function beginRefrestCaptchaInterval(expireSeconds) {
if (refrestCaptchaInterval === null) {
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
let refreshCaptchaInterval = null;
function beginRefreshCaptchaInterval(expireSeconds) {
if (refreshCaptchaInterval === null) {
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefrestCaptchaInterval() {
if (refrestCaptchaInterval != null) {
clearInterval(refrestCaptchaInterval);
refrestCaptchaInterval = null;
function stopRefreshCaptchaInterval() {
if (refreshCaptchaInterval != null) {
clearInterval(refreshCaptchaInterval);
refreshCaptchaInterval = null;
}
}
onMounted(getCaptcha);
onMounted(() => {
getCaptcha();
getTwoFactorLoginFlag();
});
//--------------------- 邮箱验证码 ---------------------------------
const emailCodeShowFlag = ref(false);
let emailCodeTips = ref('获取邮箱验证码');
let emailCodeButtonDisabled = ref(false);
// 定时器
let countDownTimer = null;
// 开始倒计时
function runCountDown() {
emailCodeButtonDisabled.value = true;
let countDown = 60;
emailCodeTips.value = `${countDown}秒后重新获取`;
countDownTimer = setInterval(() => {
if (countDown > 1) {
countDown--;
emailCodeTips.value = `${countDown}秒后重新获取`;
} else {
clearInterval(countDownTimer);
emailCodeButtonDisabled.value = false;
emailCodeTips.value = '获取验证码';
}
}, 1000);
}
// 获取双因子登录标识
async function getTwoFactorLoginFlag() {
try {
let result = await loginApi.getTwoFactorLoginFlag();
emailCodeShowFlag.value = result.data;
} catch (e) {
smartSentry.captureError(e);
}
}
// 发送邮箱验证码
async function sendSmsCode() {
try {
SmartLoading.show();
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
message.success('验证码发送成功!请登录邮箱查看验证码~');
runCountDown();
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
</script>
<style lang="less" scoped>
@import './login.less';

View File

@@ -8,7 +8,7 @@
justify-content: center;
.box-item {
width: 444px;
height: 570px;
height: 600px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
@@ -143,7 +143,11 @@
border: 1px solid #ededed;
border-radius: 4px;
}
.code-btn{
height: 44px;
padding: 4px 5px;
width: 108px;
}
.eye-box {
position: absolute;
right: 15px;

View File

@@ -24,6 +24,14 @@
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="emailCode" v-if="emailCodeShowFlag">
<a-input-group compact>
<a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on" placeholder="请输入邮箱验证码" />
<a-button @click="sendSmsCode" class="code-btn" type="primary" :disabled="emailCodeButtonDisabled">
{{ emailCodeTips }}
</a-button>
</a-input-group>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
@@ -109,7 +117,7 @@
onMounted(() => {
document.onkeyup = (e) => {
if (e.keyCode == 13) {
if (e.keyCode === 13) {
onLogin();
}
};
@@ -129,7 +137,7 @@
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
stopRefreshCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
@@ -157,27 +165,78 @@
let captchaResult = await loginApi.getCaptcha();
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
loginForm.captchaUuid = captchaResult.data.captchaUuid;
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
} catch (e) {
console.log(e);
}
}
let refrestCaptchaInterval = null;
function beginRefrestCaptchaInterval(expireSeconds) {
if (refrestCaptchaInterval === null) {
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
let refreshCaptchaInterval = null;
function beginRefreshCaptchaInterval(expireSeconds) {
if (refreshCaptchaInterval === null) {
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefrestCaptchaInterval() {
if (refrestCaptchaInterval != null) {
clearInterval(refrestCaptchaInterval);
refrestCaptchaInterval = null;
function stopRefreshCaptchaInterval() {
if (refreshCaptchaInterval != null) {
clearInterval(refreshCaptchaInterval);
refreshCaptchaInterval = null;
}
}
onMounted(getCaptcha);
onMounted(() => {
getCaptcha();
getTwoFactorLoginFlag();
});
//--------------------- 邮箱验证码 ---------------------------------
const emailCodeShowFlag = ref(false);
let emailCodeTips = ref('获取邮箱验证码');
let emailCodeButtonDisabled = ref(false);
// 定时器
let countDownTimer = null;
// 开始倒计时
function runCountDown() {
emailCodeButtonDisabled.value = true;
let countDown = 60;
emailCodeTips.value = `${countDown}秒后重新获取`;
countDownTimer = setInterval(() => {
if (countDown > 1) {
countDown--;
emailCodeTips.value = `${countDown}秒后重新获取`;
} else {
clearInterval(countDownTimer);
emailCodeButtonDisabled.value = false;
emailCodeTips.value = '获取验证码';
}
}, 1000);
}
// 获取双因子登录标识
async function getTwoFactorLoginFlag() {
try {
let result = await loginApi.getTwoFactorLoginFlag();
emailCodeShowFlag.value = result.data;
} catch (e) {
smartSentry.captureError(e);
}
}
// 发送邮箱验证码
async function sendSmsCode() {
try {
SmartLoading.show();
let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
message.success('验证码发送成功!请登录邮箱查看验证码~');
runCountDown();
} catch (e) {
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
</script>
<style lang="less" scoped>
@import './login.less';