mirror of
https://github.com/1024-lab/smart-admin.git
synced 2026-07-18 01:16:05 +00:00
v3.6.0 三级等保重磅更新:1、【新增】双因子方式登录;2、【新增】定期修改密码;3、【新增】最大活跃时间;4、【新增】敏感数据脱敏;5、【新增】登录锁定配置;6、【新增】密码复杂度配置;7、【新增】三级等保可配置
This commit is contained in:
+5
-10
@@ -20,7 +20,7 @@
|
||||
<SmartEnumSelect enum-name="GOODS_STATUS_ENUM" v-model:value="form.goodsStatus" />
|
||||
</a-form-item>
|
||||
<a-form-item label="产地" name="place">
|
||||
<DictSelect key-code="GODOS_PLACE" v-model:value="form.place" />
|
||||
<DictSelect width="100%" key-code="GODOS_PLACE" v-model:value="form.place" mode="tags" />
|
||||
</a-form-item>
|
||||
<a-form-item label="上架状态" name="shelvesFlag">
|
||||
<a-radio-group v-model:value="form.shelvesFlag">
|
||||
@@ -80,7 +80,7 @@
|
||||
//商品状态
|
||||
goodsStatus: GOODS_STATUS_ENUM.APPOINTMENT.value,
|
||||
//产地
|
||||
place: undefined,
|
||||
place: [],
|
||||
//商品价格
|
||||
price: undefined,
|
||||
//上架状态
|
||||
@@ -107,9 +107,8 @@
|
||||
Object.assign(form, rowData);
|
||||
}
|
||||
if (form.place && form.place.length > 0) {
|
||||
form.place = form.place[0].valueCode;
|
||||
form.place = form.place.map((e) => e.valueCode);
|
||||
}
|
||||
console.log(form);
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate();
|
||||
@@ -127,14 +126,10 @@
|
||||
.then(async () => {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let params = _.cloneDeep(form);
|
||||
if (params.place && Array.isArray(params.place) && params.place.length > 0) {
|
||||
params.place = params.place[0].valueCode;
|
||||
}
|
||||
if (form.goodsId) {
|
||||
await goodsApi.updateGoods(params);
|
||||
await goodsApi.updateGoods(form);
|
||||
} else {
|
||||
await goodsApi.addGoods(params);
|
||||
await goodsApi.addGoods(form);
|
||||
}
|
||||
message.success(`${form.goodsId ? '修改' : '添加'}成功`);
|
||||
onClose();
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'place'">
|
||||
<span>{{ text && text.length > 0 ? text[0].valueName : '' }}</span>
|
||||
<span>{{ text && text.length > 0 ? text.map((e) => e.valueName).join(',') : '' }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'goodsStatus'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('GOODS_STATUS_ENUM', text) }}</span>
|
||||
|
||||
+7
-4
@@ -2,6 +2,7 @@ import { convertUpperCamel } from '/@/utils/str-util';
|
||||
|
||||
// -------------------------------- java 类型 --------------------------------
|
||||
export const JavaTypeMap = new Map();
|
||||
JavaTypeMap.set('bit', 'Boolean');
|
||||
JavaTypeMap.set('int', 'Integer');
|
||||
JavaTypeMap.set('tinyint', 'Integer');
|
||||
JavaTypeMap.set('smallint', 'Integer');
|
||||
@@ -37,6 +38,7 @@ export function getJavaType(dataType) {
|
||||
|
||||
// -------------------------------- js 类型 --------------------------------
|
||||
export const JsTypeMap = new Map();
|
||||
JsTypeMap.set('bit', 'Boolean');
|
||||
JsTypeMap.set('int', 'Number');
|
||||
JsTypeMap.set('tinyint', 'Number');
|
||||
JsTypeMap.set('smallint', 'Number');
|
||||
@@ -56,11 +58,10 @@ JsTypeMap.set('date', 'Date');
|
||||
JsTypeMap.set('datetime', 'Date');
|
||||
|
||||
export const JsTypeList = [
|
||||
'Boolean', //
|
||||
'Number', //
|
||||
'String', //
|
||||
'Date', //
|
||||
'Boolean', //
|
||||
'String', //
|
||||
];
|
||||
|
||||
export function getJsType(dataType) {
|
||||
@@ -70,8 +71,9 @@ export function getJsType(dataType) {
|
||||
// -------------------------------- 前端组件 --------------------------------
|
||||
|
||||
export const FrontComponentMap = new Map();
|
||||
FrontComponentMap.set('bit', 'BooleanSelect');
|
||||
FrontComponentMap.set('int', 'InputNumber');
|
||||
FrontComponentMap.set('tinyint', 'BooleanSelect');
|
||||
FrontComponentMap.set('tinyint', 'InputNumber');
|
||||
FrontComponentMap.set('smallint', 'InputNumber');
|
||||
FrontComponentMap.set('integer', 'InputNumber');
|
||||
FrontComponentMap.set('year', 'Date');
|
||||
@@ -84,7 +86,7 @@ FrontComponentMap.set('varchar', 'Input');
|
||||
FrontComponentMap.set('tinytext', 'Input');
|
||||
FrontComponentMap.set('text', 'Textarea');
|
||||
FrontComponentMap.set('longtext', 'Textarea');
|
||||
FrontComponentMap.set('blob', 'Upload');
|
||||
FrontComponentMap.set('blob', 'FileUpload');
|
||||
FrontComponentMap.set('date', 'Date');
|
||||
FrontComponentMap.set('datetime', 'DateTime');
|
||||
|
||||
@@ -131,6 +133,7 @@ export const JAVA_FILE_LIST = [
|
||||
'Dao.java', //
|
||||
'Mapper.xml', //
|
||||
...JAVA_DOMAIN_FILE_LIST,
|
||||
'Menu.sql', //
|
||||
];
|
||||
|
||||
// -------------------------------- 枚举enum --------------------------------
|
||||
|
||||
+3
-2
@@ -68,11 +68,12 @@
|
||||
if (deletedFlagColumn) {
|
||||
deleteFlagColumnName.value = deletedFlagColumn.columnName;
|
||||
}
|
||||
console.log(deletedFlagColumn);
|
||||
|
||||
//表单
|
||||
let deleteInfo = config.delete;
|
||||
let deleteInfo = config.deleteInfo;
|
||||
|
||||
formData.isSupportDelete = deleteInfo && deleteInfo.isSupportDelete ? deleteInfo.isSupportDelete : true;
|
||||
formData.isSupportDelete = deleteInfo ? deleteInfo.isSupportDelete : true;
|
||||
formData.isPhysicallyDeleted = deleteInfo && deleteInfo.isPhysicallyDeleted ? deleteInfo.isPhysicallyDeleted : !deletedFlagColumn;
|
||||
formData.deleteEnum = deleteInfo && deleteInfo.deleteEnum ? deleteInfo.deleteEnum : CODE_DELETE_ENUM.SINGLE_AND_BATCH.value;
|
||||
}
|
||||
|
||||
+10
-1
@@ -11,6 +11,10 @@
|
||||
<a-alert :closable="true" message="请务必将每一个字段的 “ 字段名词 ” 填写完整!!!" type="success" show-icon>
|
||||
<template #icon><smile-outlined /></template>
|
||||
</a-alert>
|
||||
<!-- 为了方便再配置时中途新增字典后 可以重新刷新字典下拉 (需要先随便选择一个字典后才能看到最新的字典) -->
|
||||
<div style="float: right; padding: 10px 0px">
|
||||
<a-button type="primary" @click="refreshDict">刷新字典</a-button>
|
||||
</div>
|
||||
<a-table
|
||||
:scroll="{ x: 1300 }"
|
||||
size="small"
|
||||
@@ -64,7 +68,7 @@
|
||||
</template>
|
||||
|
||||
<template v-if="column.dataIndex === 'dict'">
|
||||
<DictKeySelect v-model:value="record.dict" />
|
||||
<DictKeySelect ref="dictRef" v-model:value="record.dict" />
|
||||
</template>
|
||||
|
||||
<template v-if="column.dataIndex === 'enumName'">
|
||||
@@ -81,6 +85,11 @@
|
||||
import { convertUpperCamel, convertLowerCamel } from '/@/utils/str-util';
|
||||
import _ from 'lodash';
|
||||
|
||||
const dictRef = ref();
|
||||
function refreshDict() {
|
||||
dictRef.value.queryDict();
|
||||
}
|
||||
|
||||
//------------------------ 全局数据 ---------------------
|
||||
const tableInfo = inject('tableInfo');
|
||||
|
||||
|
||||
+23
-35
@@ -75,11 +75,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import _ from 'lodash';
|
||||
import Sortable from 'sortablejs';
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { CODE_QUERY_FIELD_QUERY_TYPE_ENUM } from '/@/constants/support/code-generator-const';
|
||||
import { convertLowerCamel } from '/@/utils/str-util';
|
||||
|
||||
@@ -130,12 +128,13 @@
|
||||
|
||||
const tableColumns = ref([]);
|
||||
|
||||
let rowKeyCounter = 1;
|
||||
//初始化设置数据
|
||||
function setData(tableColumnInfos, config) {
|
||||
rowKeyCounter = 1;
|
||||
let data = config && config.queryFields ? config.queryFields : [];
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
data[index].rowKey = 'rowKey' + (index + 1);
|
||||
data[index].rowKey = 'rowKey' + rowKeyCounter;
|
||||
rowKeyCounter++;
|
||||
}
|
||||
tableData.value = data;
|
||||
@@ -147,7 +146,6 @@
|
||||
}
|
||||
|
||||
// ------------------- 增加、删除 -------------------
|
||||
let rowKeyCounter = 1;
|
||||
function addQuery() {
|
||||
tableData.value.push({
|
||||
rowKey: 'rowKey' + rowKeyCounter,
|
||||
@@ -155,13 +153,19 @@
|
||||
fieldName: '',
|
||||
queryTypeEnum: '',
|
||||
columnNameList: null,
|
||||
width: '',
|
||||
width: '200px',
|
||||
});
|
||||
rowKeyCounter++;
|
||||
}
|
||||
|
||||
function onDelete(index) {
|
||||
_.pullAt(tableData.value, index);
|
||||
// 以这种方式删除 列表才会重新渲染
|
||||
const tempList = [...tableData.value];
|
||||
tempList.splice(index, 1);
|
||||
tableData.value = [];
|
||||
nextTick(() => {
|
||||
tableData.value = tempList;
|
||||
});
|
||||
}
|
||||
|
||||
//初始化拖拽
|
||||
@@ -173,6 +177,10 @@
|
||||
ghostClass: 'smart-ghost-class', //设置拖拽停靠样式类名
|
||||
chosenClass: 'smart-ghost-class', //设置选中样式类名
|
||||
handle: '.handle',
|
||||
onEnd: ({ oldIndex, newIndex }) => {
|
||||
const oldRow = tableData.value.splice(oldIndex, 1)[0];
|
||||
tableData.value.splice(newIndex, 0, oldRow);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -211,36 +219,16 @@
|
||||
|
||||
// 获取表单数据
|
||||
function getFieldsForm() {
|
||||
let result = [];
|
||||
let trList = document.querySelectorAll('#smartCodeQueryFieldsTable tbody .column-row');
|
||||
if (trList && trList.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (let tr of trList) {
|
||||
let rowKey = tr.getAttribute('data-row-key');
|
||||
if (!rowKey) {
|
||||
continue;
|
||||
}
|
||||
if (rowKey && rowKey.indexOf('rowKey') === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = parseInt(rowKey.substring(6));
|
||||
let tableItem = tableData.value[index - 1];
|
||||
let obj = {
|
||||
queryTypeEnum: tableItem.queryTypeEnum,
|
||||
label: tableItem.label,
|
||||
fieldName: tableItem.fieldName,
|
||||
columnNameList: tableItem.columnNameList,
|
||||
width: tableItem.width,
|
||||
let result = tableData.value.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
width: item.width,
|
||||
fieldName: item.fieldName,
|
||||
queryTypeEnum: item.queryTypeEnum,
|
||||
// 字符串转为数组
|
||||
columnNameList: item.columnNameList && typeof item.columnNameList === 'string' ? [item.columnNameList] : item.columnNameList,
|
||||
};
|
||||
// 字符串转为数组
|
||||
if (obj.columnNameList && typeof obj.columnNameList === 'string') {
|
||||
obj.columnNameList = [obj.columnNameList];
|
||||
}
|
||||
result.push(obj);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -1,18 +1,18 @@
|
||||
<!--
|
||||
* 代码生成 配置信息
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-22 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-22 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-drawer
|
||||
title="代码配置"
|
||||
style=""
|
||||
:open="visibleFlag"
|
||||
:width="1000"
|
||||
:width="1200"
|
||||
:footerStyle="{ textAlign: 'right' }"
|
||||
@close="onClose"
|
||||
:maskClosable="false"
|
||||
@@ -175,7 +175,7 @@
|
||||
let insertAndUpdateValidated = await insertAndUpdateRef.value.validateForm();
|
||||
let deleteValidated = await deleteRef.value.validateForm();
|
||||
|
||||
if (!basicValidated || !insertAndUpdateValidated || !deleteValidated ) {
|
||||
if (!basicValidated || !insertAndUpdateValidated || !deleteValidated) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
onClose();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}finally{
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
+18
-18
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 代码生成 预览代码
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-22 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-22 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-drawer
|
||||
@@ -37,9 +37,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import { codeGeneratorApi } from '/@/api/support/code-generator-api';
|
||||
import { JAVA_FILE_LIST, LANGUAGE_LIST, JS_FILE_LIST,TS_FILE_LIST, JAVA_DOMAIN_FILE_LIST } from '../../code-generator-util';
|
||||
import { JAVA_FILE_LIST, LANGUAGE_LIST, JS_FILE_LIST, TS_FILE_LIST } from '../../code-generator-util';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { lineNumbersBlock } from '/@/lib/highlight-line-number';
|
||||
import hljs from 'highlight.js';
|
||||
@@ -74,11 +74,11 @@
|
||||
// ------------------ 标签页 ------------------
|
||||
const languageType = ref(LANGUAGE_LIST[0]);
|
||||
const tabList = computed(() => {
|
||||
if(languageType.value === LANGUAGE_LIST[0]){
|
||||
if (languageType.value === LANGUAGE_LIST[0]) {
|
||||
return JS_FILE_LIST;
|
||||
}else if(languageType.value === LANGUAGE_LIST[1]){
|
||||
} else if (languageType.value === LANGUAGE_LIST[1]) {
|
||||
return TS_FILE_LIST;
|
||||
}else{
|
||||
} else {
|
||||
return JAVA_FILE_LIST;
|
||||
}
|
||||
});
|
||||
@@ -86,21 +86,21 @@
|
||||
const fileKey = ref(tabList.value[0]);
|
||||
|
||||
function getLanguage() {
|
||||
if(languageType.value === LANGUAGE_LIST[0]){
|
||||
if (languageType.value === LANGUAGE_LIST[0]) {
|
||||
return 'javascript';
|
||||
}else if(languageType.value === LANGUAGE_LIST[1]){
|
||||
} else if (languageType.value === LANGUAGE_LIST[1]) {
|
||||
return 'typescript';
|
||||
}else{
|
||||
} else {
|
||||
return 'java';
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeLanguageType(e){
|
||||
if(e.target.value === LANGUAGE_LIST[0]){
|
||||
function onChangeLanguageType(e) {
|
||||
if (e.target.value === LANGUAGE_LIST[0]) {
|
||||
fileKey.value = JS_FILE_LIST[0];
|
||||
}else if(e.target.value === LANGUAGE_LIST[1]){
|
||||
} else if (e.target.value === LANGUAGE_LIST[1]) {
|
||||
fileKey.value = TS_FILE_LIST[0];
|
||||
}else{
|
||||
} else {
|
||||
fileKey.value = JAVA_FILE_LIST[0];
|
||||
}
|
||||
onChangeTab(fileKey.value);
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
// 下载文件
|
||||
async function download(file) {
|
||||
try {
|
||||
await fileApi.downLoadFile(file.fileName, file.fileKey);
|
||||
await fileApi.downLoadFile(file.fileKey);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<!--
|
||||
* 数据脱敏
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2024-08-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-alert>
|
||||
<template v-slot:message>
|
||||
<h4>数据脱敏 Data Masking介绍:</h4>
|
||||
</template>
|
||||
<template v-slot:description>
|
||||
<pre>
|
||||
简介:《信息安全技术 网络安全等级保护基本要求》明确规定,二级以上保护则需要对敏感数据进行脱敏处理。
|
||||
原理:数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。
|
||||
举例:在不违反系统规则条件下,身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。
|
||||
|
||||
使用方式:
|
||||
1)脱敏注解 @DataMasking ,支持数据类型如:用户ID、手机号、密码、地址、银行卡、车牌号等;
|
||||
2)脱敏工具类: SmartDataMaskingUtil ;
|
||||
</pre
|
||||
>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table
|
||||
size="small"
|
||||
bordered
|
||||
:scroll="{ x: 1100 }"
|
||||
:loading="tableLoading"
|
||||
class="smart-margin-top10"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { heartBeatApi } from '/@/api/support/heart-beat-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
import { dataMaskingApi } from '/@/api/support/data-masking-api.js';
|
||||
|
||||
//------------------------ 表格渲染 ---------------------
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: '默认',
|
||||
dataIndex: 'other',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '身份证',
|
||||
dataIndex: 'idCard',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '密码',
|
||||
dataIndex: 'password',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '车牌号',
|
||||
dataIndex: 'carLicense',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '银行卡',
|
||||
dataIndex: 'bankCard',
|
||||
width: 170,
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
dataIndex: 'address',
|
||||
width: 210,
|
||||
},
|
||||
]);
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
|
||||
function onSearch() {
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await dataMaskingApi.query();
|
||||
tableData.value = responseModel.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(ajaxQuery);
|
||||
</script>
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<!--
|
||||
* 三级等保配置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任-卓大
|
||||
* @Date: 2024-07-31 22:02:37
|
||||
* @Copyright 1024创新实验室
|
||||
-->
|
||||
<template>
|
||||
<a-alert closable>
|
||||
<template v-slot:message>
|
||||
<h4>三级等保:</h4>
|
||||
</template>
|
||||
<template v-slot:description>
|
||||
<pre>
|
||||
1.三级等保是中国国家等级保护认证中的最高级别认证,该认证包含了五个等级保护安全技术要求和五个安全管理要求,共涉及测评分类73类,要求非常严格。
|
||||
2.三级等保是地市级以上国家机关、重要企事业单位需要达成的认证,在金融行业中,可以看作是除了银行机构以外最高级别的信息安全等级保护。
|
||||
3.具体三级等保要求,请查看“1024创新实验室”写的相关文档 <a href="https://smartadmin.vip/asd">三级等保文档</a></pre>
|
||||
</template>
|
||||
</a-alert>
|
||||
<br />
|
||||
<!---------- 三级等保配置表单 begin ----------->
|
||||
<a-card title="三级等保配置">
|
||||
<a-form
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
style="width: 800px"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
autocomplete="off"
|
||||
class="smart-query-form"
|
||||
>
|
||||
<a-form-item
|
||||
label="配置双因子登录模式"
|
||||
class="smart-query-form-item"
|
||||
extra="在用户登录时,需要同时提供用户名和密码以及其他形式的身份验证信息,例如短信验证码等"
|
||||
>
|
||||
<a-switch v-model:checked="form.twoFactorLoginEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="最大连续登录失败次数"
|
||||
class="smart-query-form-item"
|
||||
extra="连续登录失败超过一定次数,则需要锁定;默认5次;0则不锁定;"
|
||||
name="loginFailMaxTimes"
|
||||
>
|
||||
<a-input-number :min="0" :max="10" v-model:value="form.loginFailMaxTimes" placeholder="最大连续登录失败次数" addon-after="次" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="loginFailLockMinutes"
|
||||
label="连续登录失败锁定分钟"
|
||||
class="smart-query-form-item"
|
||||
extra="连续登录失败锁定的时间;默认30分钟,0则不锁定"
|
||||
>
|
||||
<a-input-number :min="0" v-model:value="form.loginFailLockMinutes" placeholder="连续登录失败锁定时分钟" addon-after="分钟" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="loginActiveTimeoutMinutes"
|
||||
label="登录后无操作自动退出的分钟"
|
||||
class="smart-query-form-item"
|
||||
extra="如:登录1小时没操作自动退出当前登录状态;默认30分钟"
|
||||
>
|
||||
<a-input-number :min="-1" v-model:value="form.loginActiveTimeoutMinutes" placeholder="登录后无操作自动退出的分钟" addon-after="分钟" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="开启密码复杂度"
|
||||
class="smart-query-form-item"
|
||||
extra="密码长度为8-20位且必须包含字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"
|
||||
>
|
||||
<a-switch v-model:checked="form.passwordComplexityEnabled" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="regularChangePasswordMonths"
|
||||
label="定期修改密码时间间隔"
|
||||
class="smart-query-form-item"
|
||||
extra="定期修改密码时间间隔,默认3个月"
|
||||
>
|
||||
<a-input-number :min="-1" :max="6" v-model:value="form.regularChangePasswordMonths" placeholder="定期修改密码时间间隔" addon-after="月" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="regularChangePasswordNotAllowRepeatTimes"
|
||||
label="定期修改密码不允许重复次数"
|
||||
class="smart-query-form-item"
|
||||
extra="定期修改密码不允许重复次数,默认:3次以内密码不能相同"
|
||||
>
|
||||
<a-input-number
|
||||
:min="-1"
|
||||
:max="6"
|
||||
v-model:value="form.regularChangePasswordNotAllowRepeatTimes"
|
||||
placeholder="相同密码不允许重复次数"
|
||||
addon-after="次"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="文件安全检测"
|
||||
class="smart-query-form-item"
|
||||
extra="对文件类型、恶意文件进行检测;(具体请看后端: SecurityFileService 类 checkFile 方法 )"
|
||||
>
|
||||
<a-switch v-model:checked="form.fileDetectFlag" checked-children="开启 " un-checked-children="关闭 " />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="maxUploadFileSizeMb"
|
||||
label="上传文件大小限制"
|
||||
class="smart-query-form-item"
|
||||
extra="上传文件大小限制,默认 50 mb ( 0 表示不限制)"
|
||||
>
|
||||
<a-input-number :min="0" v-model:value="form.maxUploadFileSizeMb" placeholder="上传文件大小限制" addon-after="mb(兆)" />
|
||||
</a-form-item>
|
||||
<br />
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 6 }">
|
||||
<a-button type="primary" style="margin-right: 20px" @click.prevent="onSubmit">保存配置</a-button>
|
||||
<a-button style="margin-right: 20px" @click="reset">恢复三级等保默认配置</a-button>
|
||||
<a-button danger @click="clear">清除所有配置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<!---------- 请求参数加密 end ----------->
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { level3ProtectApi } from '/@/api/support/level3-protect-api.js';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
// 三级等保的默认值
|
||||
const protectDefaultValues = {
|
||||
// 连续登录失败次数则锁定
|
||||
loginFailMaxTimes: 5,
|
||||
// 连续登录失败锁定分钟
|
||||
loginFailLockMinutes: 30,
|
||||
// 最低活跃时间分钟
|
||||
loginActiveTimeoutMinutes: 30,
|
||||
// 密码复杂度
|
||||
passwordComplexityEnabled: true,
|
||||
// 定期修改密码时间间隔 月份
|
||||
regularChangePasswordMonths: 3,
|
||||
// 定期修改密码不允许重复次数,默认:3次以内密码不能相同
|
||||
regularChangePasswordNotAllowRepeatTimes: 3,
|
||||
// 开启双因子登录
|
||||
twoFactorLoginEnabled: true,
|
||||
// 文件检测,默认:不开启
|
||||
fileDetectFlag: true,
|
||||
// 文件大小限制,单位 mb ,(默认:50 mb)
|
||||
maxUploadFileSizeMb: 50,
|
||||
};
|
||||
|
||||
// 三级等保的不保护的默认值
|
||||
const noProtectDefaultValues = {
|
||||
// 连续登录失败次数则锁定
|
||||
loginFailMaxTimes: 0,
|
||||
// 连续登录失败锁定分钟
|
||||
loginFailLockMinutes: 0,
|
||||
// 最低活跃时间分钟
|
||||
loginActiveTimeoutMinutes: 0,
|
||||
// 密码复杂度
|
||||
passwordComplexityEnabled: false,
|
||||
// 定期修改密码时间间隔 月份
|
||||
regularChangePasswordMonths: 0,
|
||||
// 定期修改密码不允许重复次数,
|
||||
regularChangePasswordNotAllowRepeatTimes: 0,
|
||||
// 开启双因子登录
|
||||
twoFactorLoginEnabled: false,
|
||||
// 文件大小限制,单位 mb ,
|
||||
maxUploadFileSizeMb: 0,
|
||||
};
|
||||
|
||||
// 三级等保配置表单
|
||||
const form = reactive({
|
||||
...protectDefaultValues,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
loginFailMaxTimes: [{ required: true, message: '请输入 最大连续登录失败次数' }],
|
||||
loginFailLockMinutes: [{ required: true, message: '请输入 连续登录失败锁定分钟' }],
|
||||
loginActiveTimeoutMinutes: [{ required: true, message: '请输入 最低活跃时间分钟' }],
|
||||
regularChangePasswordMonths: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||
regularChangePasswordNotAllowRepeatTimes: [{ required: true, message: '请输入 定期修改密码时间间隔' }],
|
||||
maxUploadFileSizeMb: [{ required: true, message: '请输入 上传文件大小限制' }],
|
||||
};
|
||||
|
||||
//获取配置
|
||||
async function getConfig() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let res = await level3ProtectApi.getConfig();
|
||||
if (!res.data) {
|
||||
message.warn('当前未配置三级等保');
|
||||
return;
|
||||
}
|
||||
let json = JSON.parse(res.data);
|
||||
form.loginFailMaxTimes = json.loginFailMaxTimes;
|
||||
form.loginFailLockMinutes = json.loginFailLockMinutes;
|
||||
form.loginActiveTimeoutMinutes = json.loginActiveTimeoutMinutes;
|
||||
form.passwordComplexityEnabled = json.passwordComplexityEnabled;
|
||||
form.regularChangePasswordMonths = json.regularChangePasswordMonths;
|
||||
form.regularChangePasswordNotAllowRepeatTimes = json.regularChangePasswordNotAllowRepeatTimes;
|
||||
form.twoFactorLoginEnabled = json.twoFactorLoginEnabled;
|
||||
form.maxUploadFileSizeMb = json.maxUploadFileSizeMb;
|
||||
form.fileDetectFlag = json.fileDetectFlag;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(getConfig);
|
||||
|
||||
const formRef = ref();
|
||||
// 提交修改
|
||||
function onSubmit() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(save)
|
||||
.catch((error) => {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
});
|
||||
}
|
||||
|
||||
// 提交修改配置
|
||||
async function save() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let res = await level3ProtectApi.updateConfig(form);
|
||||
message.success(res.msg);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
function reset() {
|
||||
Object.assign(form, protectDefaultValues);
|
||||
save();
|
||||
}
|
||||
|
||||
// 清除所有配置
|
||||
function clear() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要清除三级等保配置吗?这样系统不安全哦',
|
||||
okText: '清除三级等保配置',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
Object.assign(form, noProtectDefaultValues);
|
||||
save();
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
+9
-1
@@ -10,8 +10,14 @@
|
||||
<template>
|
||||
<a-form class="smart-query-form" v-privilege="'support:operateLog:query'">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="操作关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 150px" v-model:value="queryForm.keywords" placeholder="模块/操作内容" />
|
||||
</a-form-item>
|
||||
<a-form-item label="请求关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 220px" v-model:value="queryForm.requestKeywords" placeholder="请求地址/请求方法/请求参数" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名称" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
||||
<a-input style="width: 100px" v-model:value="queryForm.userName" placeholder="用户名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="请求时间" class="smart-query-form-item">
|
||||
@@ -168,6 +174,8 @@
|
||||
|
||||
const queryFormState = {
|
||||
userName: '',
|
||||
requestKeywords: '',
|
||||
keywords: '',
|
||||
successFlag: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined,
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="类型" class="smart-query-form-item">
|
||||
<smart-enum-select v-model:value="queryForm.messageType" placeholder="消息类型" enum-name="MESSAGE_TYPE_ENUM" />
|
||||
<smart-enum-select style="width: 150px" v-model:value="queryForm.messageType" placeholder="消息类型" enum-name="MESSAGE_TYPE_ENUM" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="消息时间" class="smart-query-form-item">
|
||||
@@ -43,7 +43,7 @@
|
||||
</a-form>
|
||||
|
||||
<a-table size="small" :dataSource="tableData" :columns="columns" rowKey="messageId" :pagination="false" bordered>
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'messageType'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('MESSAGE_TYPE_ENUM', text) }}</span>
|
||||
</template>
|
||||
|
||||
+50
-12
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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: '请输入邮箱' }],
|
||||
};
|
||||
|
||||
// 校验表单
|
||||
|
||||
+10
-6
@@ -155,6 +155,12 @@
|
||||
dataIndex: 'phone',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '超管',
|
||||
dataIndex: 'administratorFlag',
|
||||
@@ -169,6 +175,7 @@
|
||||
title: '职务',
|
||||
dataIndex: 'positionName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
@@ -181,12 +188,6 @@
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'employeeName',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operate',
|
||||
@@ -242,6 +243,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;
|
||||
// 清除选中
|
||||
|
||||
@@ -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>
|
||||
-1
@@ -49,7 +49,6 @@
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.card-container {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
<!--
|
||||
* 已办/代办
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<default-home-card icon="Star" title="已办待办">
|
||||
<div style="height: 280px">
|
||||
<div class="center column">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<div v-for="(item, index) in toDoList" :key="index" :class="['to-do', { done: item.doneFlag }]">
|
||||
<a-checkbox v-model:checked="item.doneFlag">
|
||||
<span class="task">{{ item.title }}</span>
|
||||
</a-checkbox>
|
||||
<div class="star-icon" @click="itemStar(item)">
|
||||
<StarFilled v-if="item.starFlag" style="color: #ff8c00" />
|
||||
<StarOutlined v-else style="color: #c0c0c0" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, index) in doneList" :key="index" :class="['to-do', { done: item.doneFlag }]">
|
||||
<a-checkbox v-model:checked="item.doneFlag">
|
||||
<span class="task">{{ item.title }}</span>
|
||||
</a-checkbox>
|
||||
<div class="star-icon" @click="itemStar(item)">
|
||||
<StarFilled v-if="item.starFlag" style="color: #ff8c00" />
|
||||
<StarOutlined v-else style="color: #c0c0c0" />
|
||||
</div>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</default-home-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
let taskList = ref([
|
||||
{
|
||||
title: '周五下班前需要提交周报',
|
||||
doneFlag: true,
|
||||
starFlag: true,
|
||||
starTime: 0,
|
||||
},
|
||||
{
|
||||
title: '为SmartAdmin前端小组分配任务',
|
||||
doneFlag: false,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
},
|
||||
{
|
||||
title: '跟进团建内容事宜',
|
||||
doneFlag: false,
|
||||
starFlag: true,
|
||||
starTime: 0,
|
||||
},
|
||||
{
|
||||
title: '跟进客户定制一个软件平台',
|
||||
doneFlag: false,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
},
|
||||
{
|
||||
title: '下个版本的需求确认',
|
||||
doneFlag: false,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
},
|
||||
{
|
||||
title: '线上版本发布',
|
||||
doneFlag: true,
|
||||
starFlag: true,
|
||||
starTime: dayjs().unix(),
|
||||
},
|
||||
{
|
||||
title: '周一财务报销',
|
||||
doneFlag: true,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
let toDoList = computed(() => {
|
||||
return taskList.value.filter((e) => !e.doneFlag).sort((a, b) => b.starTime - a.starTime);
|
||||
});
|
||||
|
||||
let doneList = computed(() => {
|
||||
return taskList.value.filter((e) => e.doneFlag);
|
||||
});
|
||||
|
||||
function itemStar(item) {
|
||||
item.starFlag = !item.starFlag;
|
||||
if (item.starFlag) {
|
||||
item.starTime = dayjs().unix();
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------任务新建-----------------------
|
||||
let taskTitle = ref('');
|
||||
function addTask() {
|
||||
if (!taskTitle.value) {
|
||||
message.warn('请输入任务标题');
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
title: taskTitle.value,
|
||||
doneFlag: false,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
};
|
||||
taskList.value.unshift(data);
|
||||
taskTitle.value = '';
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.to-do {
|
||||
width: 100%;
|
||||
border: 1px solid #d3d3d3;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.star-icon {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.done {
|
||||
text-decoration: line-through;
|
||||
color: #8c8c8c;
|
||||
|
||||
.task {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
<!--
|
||||
* 待办工作
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<default-home-card extra="添加" icon="StarTwoTone" title="待办工作" @extraClick="showAddToBeDone">
|
||||
<div style="height: 280px">
|
||||
<div class="center column">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-empty v-if="$lodash.isEmpty(toBeDoneList)" description="暂无待办工作" />
|
||||
<div v-for="(item, index) in toDoList" :key="index" :class="['to-do', { done: item.doneFlag }]">
|
||||
<a-checkbox v-model:checked="item.doneFlag" @change="handleCheckbox">
|
||||
<span class="task">{{ item.title }}</span>
|
||||
</a-checkbox>
|
||||
<div v-if="!item.doneFlag" class="star-icon" @click="itemStar(item)">
|
||||
<StarFilled v-if="item.starFlag" style="color: #ff8c00" />
|
||||
<StarOutlined v-else style="color: #c0c0c0" />
|
||||
</div>
|
||||
<close-circle-outlined class="delete-icon" @click="toDelete(item)" />
|
||||
</div>
|
||||
<div v-for="(item, index) in doneList" :key="index" :class="['to-do', { done: item.doneFlag }]">
|
||||
<a-checkbox v-model:checked="item.doneFlag" @change="handleCheckbox">
|
||||
<span class="task">{{ item.title }}</span>
|
||||
</a-checkbox>
|
||||
<div v-if="!item.doneFlag" class="star-icon" @click="itemStar(item)">
|
||||
<StarFilled v-if="item.starFlag" style="color: #ff8c00" />
|
||||
<StarOutlined v-else style="color: #c0c0c0" />
|
||||
</div>
|
||||
<close-circle-outlined class="delete-icon" @click="toDelete(item)" />
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</default-home-card>
|
||||
<ToBeDoneModal ref="toBeDoneModalRef" @addToBeDone="addToBeDone" />
|
||||
</template>
|
||||
<script setup>
|
||||
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
|
||||
import ToBeDoneModal from './to-be-done-modal.vue';
|
||||
import localKey from '/@/constants/local-storage-key-const';
|
||||
import { localRead, localSave } from '/@/utils/local-util';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
let toBeDoneList = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
initTaskList();
|
||||
});
|
||||
|
||||
function initTaskList() {
|
||||
let localTaskList = localRead(localKey.TO_BE_DONE);
|
||||
if (localTaskList) {
|
||||
toBeDoneList.value = JSON.parse(localTaskList);
|
||||
}
|
||||
}
|
||||
|
||||
let toDoList = computed(() => {
|
||||
return toBeDoneList.value.filter((e) => !e.doneFlag);
|
||||
});
|
||||
|
||||
let doneList = computed(() => {
|
||||
return toBeDoneList.value.filter((e) => e.doneFlag);
|
||||
});
|
||||
|
||||
function handleCheckbox(e) {
|
||||
localSave(localKey.TO_BE_DONE, JSON.stringify(toBeDoneList.value));
|
||||
useUserStore().toBeDoneCount = toDoList.value.length;
|
||||
}
|
||||
|
||||
function itemStar(data) {
|
||||
data.starFlag = !data.starFlag;
|
||||
// 将取消 star 的删除掉
|
||||
const index = toBeDoneList.value.findIndex((item) => item.title === data.title);
|
||||
toBeDoneList.value.splice(index, 1);
|
||||
if (data.starFlag) {
|
||||
// 最新添加标记star的移动到第一位
|
||||
toBeDoneList.value.unshift(data);
|
||||
} else {
|
||||
// 取消标记star的移动到最后一个标记 star 的后面添加
|
||||
const lastStarIndex = toBeDoneList.value.findLastIndex((item) => item.starFlag);
|
||||
toBeDoneList.value.splice(lastStarIndex + 1, 0, data);
|
||||
}
|
||||
localSave(localKey.TO_BE_DONE, JSON.stringify(toBeDoneList.value));
|
||||
}
|
||||
|
||||
//-------------------------任务新建-----------------------
|
||||
|
||||
let toBeDoneModalRef = ref();
|
||||
|
||||
function showAddToBeDone() {
|
||||
toBeDoneModalRef.value.showModal();
|
||||
}
|
||||
|
||||
// 添加待办工作
|
||||
function addToBeDone(data) {
|
||||
toBeDoneList.value.push(data);
|
||||
localSave(localKey.TO_BE_DONE, JSON.stringify(toBeDoneList.value));
|
||||
useUserStore().toBeDoneCount = toDoList.value.length;
|
||||
}
|
||||
|
||||
function toDelete(data) {
|
||||
if (!data.doneFlag) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
deleteToBeDone(data);
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
} else {
|
||||
deleteToBeDone(data);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除待办工作
|
||||
function deleteToBeDone(data) {
|
||||
const index = toBeDoneList.value.findIndex((item) => item.title === data.title);
|
||||
toBeDoneList.value.splice(index, 1);
|
||||
localSave(localKey.TO_BE_DONE, JSON.stringify(toBeDoneList.value));
|
||||
useUserStore().toBeDoneCount = toDoList.value.length;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.to-do {
|
||||
width: 100%;
|
||||
border: 1px solid #d3d3d3;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.star-icon {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.done {
|
||||
text-decoration: line-through;
|
||||
color: #8c8c8c;
|
||||
|
||||
.task {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: #f08080;
|
||||
padding-left: 10px;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-modal v-model:open="visible" title="新建待办" @close="onClose">
|
||||
<a-form ref="formRef" :model="form" :rules="rules">
|
||||
<a-form-item label="标题" name="title">
|
||||
<a-input v-model:value="form.title" placeholder="请输入标题" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button type="primary" @click="onSubmit">确定</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['addToBeDone']);
|
||||
|
||||
// 组件ref
|
||||
const formRef = ref();
|
||||
|
||||
const formDefault = {
|
||||
title: undefined,
|
||||
doneFlag: false,
|
||||
starFlag: false,
|
||||
starTime: 0,
|
||||
};
|
||||
let form = reactive({ ...formDefault });
|
||||
const rules = {
|
||||
title: [{ required: true, message: '标题不能为空' }],
|
||||
};
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
function showModal() {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
Object.assign(form, formDefault);
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
emit('addToBeDone', _.cloneDeep(form));
|
||||
onClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
@@ -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 ToBeDoneCard from './components/to-be-done-card/home-to-be-done.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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@
|
||||
<a-switch v-model:checked="form.visibleFlag" checked-children="显示" un-checked-children="不显示" />
|
||||
</a-form-item>
|
||||
<a-form-item label="禁用状态" name="frameFlag">
|
||||
<a-switch v-model:checked="form.disabledFlag" checked-children="启用" un-checked-children="禁用" />
|
||||
<a-switch v-model:checked="form.disabledFlag" :checkedValue="false" :unCheckedValue="true" checked-children="启用" un-checked-children="禁用" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 目录 菜单 end -->
|
||||
@@ -74,7 +74,7 @@
|
||||
<MenuTreeSelect ref="contextMenuTreeSelect" v-model:value="form.contextMenuId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="功能点状态" name="frameFlag">
|
||||
<a-switch v-model:checked="form.disabledFlag" checked-children="启用" un-checked-children="禁用" />
|
||||
<a-switch v-model:checked="form.disabledFlag" :checkedValue="false" :unCheckedValue="true" checked-children="启用" un-checked-children="禁用" />
|
||||
</a-form-item>
|
||||
<a-form-item label="权限类型" name="permsType">
|
||||
<a-radio-group v-model:value="form.permsType">
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@
|
||||
|
||||
async function addRoleEmployee() {
|
||||
let res = await roleApi.getRoleAllEmployee(selectRoleId.value);
|
||||
let selectedIdList = res.data.map((e) => e.roleId) || [];
|
||||
let selectedIdList = res.data.map((e) => e.employeeId) || [];
|
||||
selectEmployeeModal.value.showModal(selectedIdList);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user