v3.9.0【优化】typescript版本;【优化】App端消息;【优化】弹出层z-index;

This commit is contained in:
zhuoda
2024-11-04 20:15:49 +08:00
parent 17a3e1fd86
commit 69fa9088f5
1376 changed files with 10373 additions and 9712 deletions

View File

@@ -0,0 +1,250 @@
/*
* ajax请求
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-06 20:46:03
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
import { message, Modal } from 'ant-design-vue';
import axios from 'axios';
import { localRead } from '/@/utils/local-util';
import { useUserStore } from '/@/store/modules/system/user';
import { decryptData, encryptData } from './encrypt';
import { DATA_TYPE_ENUM } from '../constants/common-const';
import _ from 'lodash';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
// token的消息头
const TOKEN_HEADER = 'x-access-token';
// 创建axios对象
const smartAxios = axios.create({
baseURL: import.meta.env.VITE_APP_API_URL,
});
// 退出系统
function logout() {
useUserStore().logout();
location.href = '/';
}
// ================================= 请求拦截器 =================================
smartAxios.interceptors.request.use(
(config) => {
// 在发送请求之前消息头加入token token
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (token) {
config.headers[TOKEN_HEADER] = token;
} else {
delete config.headers[TOKEN_HEADER];
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// ================================= 响应拦截器 =================================
// 添加响应拦截器
smartAxios.interceptors.response.use(
(response) => {
// 根据content-type ,判断是否为 json 数据
let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type'];
if (contentType.indexOf('application/json') === -1) {
return Promise.resolve(response);
}
// 如果是json数据
if (response.data && response.data instanceof Blob) {
return Promise.reject(response.data);
}
// 如果是加密数据
if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) {
response.data.encryptData = response.data.data;
let decryptStr = decryptData(response.data.data);
if (decryptStr) {
response.data.data = JSON.parse(decryptStr);
}
}
const res = response.data;
if (res.code && res.code !== 1) {
// `token` 过期或者账号已在别处登录
if (res.code === 30007 || res.code === 30008) {
message.destroy();
message.error('您没有登录,请重新登录');
setTimeout(logout, 300);
return Promise.reject(response);
}
// 等保安全的登录提醒
if (res.code === 30010 || res.code === 30011) {
Modal.error({
title: '重要提醒',
content: res.msg,
});
return Promise.reject(response);
}
// 长时间未操作系统,需要重新登录
if (res.code === 30012) {
Modal.error({
title: '重要提醒',
content: res.msg,
onOk: logout,
});
setTimeout(logout, 3000);
return Promise.reject(response);
}
message.destroy();
message.error(res.msg);
return Promise.reject(response);
} else {
return Promise.resolve(res);
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf('timeout') !== -1) {
message.destroy();
message.error('网络超时');
} else if (error.message === 'Network Error') {
message.destroy();
message.error('网络连接错误');
} else if (error.message.indexOf('Request') !== -1) {
message.destroy();
message.error('网络发生错误');
}
return Promise.reject(error);
}
);
// ================================= 对外提供请求方法通用请求get post, 下载download等 =================================
/**
* get请求
*/
export const getRequest = (url, params) => {
return request({ url, method: 'get', params });
};
/**
* 通用请求封装
* @param config
*/
export const request = (config) => {
return smartAxios.request(config);
};
/**
* post请求
*/
export const postRequest = (url, data) => {
return request({
data,
url,
method: 'post',
});
};
// ================================= 加密 =================================
/**
* 加密请求参数的post请求
*/
export const postEncryptRequest = (url, data) => {
return request({
data: { encryptData: encryptData(data) },
url,
method: 'post',
});
};
// ================================= 下载 =================================
export const postDownload = function (url, data) {
request({
method: 'post',
url,
data,
responseType: 'blob',
})
.then((data) => {
handleDownloadData(data);
})
.catch((error) => {
handleDownloadError(error);
});
};
/**
* 文件下载
*/
export const getDownload = function (url, params) {
request({
method: 'get',
url,
params,
responseType: 'blob',
})
.then((data) => {
handleDownloadData(data);
})
.catch((error) => {
handleDownloadError(error);
});
};
function handleDownloadError(error) {
if (error instanceof Blob) {
const fileReader = new FileReader();
fileReader.readAsText(error);
fileReader.onload = () => {
const msg = fileReader.result;
const jsonMsg = JSON.parse(msg);
message.destroy();
message.error(jsonMsg.msg);
};
} else {
message.destroy();
message.error('网络发生错误', error);
}
}
function handleDownloadData(response) {
if (!response) {
return;
}
// 获取返回类型
let contentType = _.isUndefined(response.headers['content-type']) ? response.headers['Content-Type'] : response.headers['content-type'];
// 构建下载数据
let url = window.URL.createObjectURL(new Blob([response.data], { type: contentType }));
let link = document.createElement('a');
link.style.display = 'none';
link.href = url;
// 从消息头获取文件名
let str = _.isUndefined(response.headers['content-disposition'])
? response.headers['Content-Disposition'].split(';')[1]
: response.headers['content-disposition'].split(';')[1];
let filename = _.isUndefined(str.split('fileName=')[1]) ? str.split('filename=')[1] : str.split('fileName=')[1];
link.setAttribute('download', decodeURIComponent(filename));
// 触发点击下载
document.body.appendChild(link);
link.click();
// 下载完释放
document.body.removeChild(link); // 下载完成移除元素
window.URL.revokeObjectURL(url); // 释放掉blob对象
}

View File

@@ -0,0 +1,29 @@
/*
* 时间选择框快捷选择
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-06 20:49:28
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
import dayjs from 'dayjs';
import {ref} from 'vue';
export const defaultTimeRanges = ref([{label: '今日', value: [dayjs(), dayjs()]}, {
label: '昨日',
value: [dayjs().subtract(1, 'days'), dayjs().subtract(1, 'days')]
}, {label: '本月', value: [dayjs().startOf('month'), dayjs().endOf('month')]}, {
label: '上个月',
value: [dayjs().subtract(1, 'months').startOf('month'), dayjs().subtract(1, 'months').endOf('month')]
}, {
label: '下个月',
value: [dayjs().subtract(-1, 'months').startOf('month'), dayjs().subtract(-1, 'months').endOf('month')]
},
{label: '本年度', value: [dayjs().startOf('year'), dayjs().endOf('year')]}, {
label: '上年度',
value: [dayjs().subtract(1, 'years').startOf('year'), dayjs().subtract(1, 'years').endOf('year')]
}]);

View File

@@ -0,0 +1,120 @@
import CryptoJS from 'crypto-js';
import CryptoSM from 'sm-crypto';
function object2string(data) {
if (typeof data === 'object') {
return JSON.stringify(data);
}
let str = JSON.stringify(data);
if (str.startsWith("'") || str.startsWith('"')) {
str = str.substring(1);
}
if (str.endsWith("'") || str.endsWith('"')) {
str = str.substring(0, str.length - 1);
}
return str;
}
/**
* 字符串转为数字
*/
function stringToHex(str) {
let hex = '';
for(let i = 0; i < str.length; i++) {
hex += str.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
}
/*
* -------------------- ※ AES 加密、解密 begin ※ --------------------
*
* 1、AES加密算法支持三种密钥长度128位、192位和256位这里选择128位
* 2、AES 要求秘钥为 128bit转化字节为 16个字节
* 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节
* 4、所以秘钥Key 组成为:字母、数字、特殊符号 一共16个即可
*
* -------------------- ※ AES 加密、解密 end ※ --------------------
*/
const AES_KEY = '1024lab__1024lab';
const AES = {
encryptData: function (data) {
// AES 加密 并转为 base64
let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data));
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
const encrypted = CryptoJS.AES.encrypt(utf8Data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
},
decryptData: function (data) {
// 第一步Base64 解码
let words = CryptoJS.enc.Base64.parse(data);
// 第二步AES 解密
const key = CryptoJS.enc.Utf8.parse(AES_KEY);
return CryptoJS.AES.decrypt({ ciphertext: words }, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString(CryptoJS.enc.Utf8);
},
};
/*
* -------------------- ※ 国密SM4算法 加密、解密 begin ※ --------------------
*
* 1、国密SM4 要求秘钥为 128bit转化字节为 16个字节
* 2、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节
* 3、java中 每个 字母数字 也是占用1个字节
* 4、所以前端和后端的 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可
*
* -------------------- ※ 国密SM4算法 加密、解密 end ※ --------------------
*/
// 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可
const SM4_KEY = '1024lab__1024lab';
const SM4 = {
encryptData: function (data) {
// 第一步SM4 加密
let encryptData = CryptoSM.sm4.encrypt(object2string(data), stringToHex(SM4_KEY));
// 第二步: Base64 编码
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData));
},
decryptData: function (data) {
// 第一步Base64 解码
let words = CryptoJS.enc.Base64.parse(data);
let decode64Str = CryptoJS.enc.Utf8.stringify(words);
// 第二步SM4 解密
return CryptoSM.sm4.decrypt(decode64Str, stringToHex(SM4_KEY));
},
};
// ----------------------- 对外暴露: 加密、解密 -----------------------
// 默认使用SM4算法
const EncryptObject = SM4;
// const EncryptObject = AES;
/**
* 加密
*/
export const encryptData = function (data) {
return !data ? null : EncryptObject.encryptData(data);
};
/**
* 解密
*/
export const decryptData = function (data) {
return !data ? null : EncryptObject.decryptData(data);
};

View File

@@ -0,0 +1,226 @@
// jshint multistr:true
let TABLE_NAME = 'hljs-ln',
LINE_NAME = 'hljs-ln-line',
CODE_BLOCK_NAME = 'hljs-ln-code',
NUMBERS_BLOCK_NAME = 'hljs-ln-numbers',
NUMBER_LINE_NAME = 'hljs-ln-n',
DATA_ATTR_NAME = 'data-line-number',
BREAK_LINE_REGEXP = /\r\n|\r|\n/g;
addStyles();
function addStyles() {
let css = document.createElement('style');
css.type = 'text/css';
css.innerHTML = format('.{0}{border-collapse:collapse}' + '.{0} td{padding:0}' + '.{1}:before{content:attr({2})}', [
TABLE_NAME,
NUMBER_LINE_NAME,
DATA_ATTR_NAME,
]);
document.getElementsByTagName('head')[0].appendChild(css);
}
function initLineNumbersOnLoad(options) {
if (document.readyState === 'interactive' || document.readyState === 'complete') {
documentReady(options);
} else {
window.addEventListener('DOMContentLoaded', function () {
documentReady(options);
});
}
}
function documentReady(options) {
try {
let blocks = document.querySelectorAll('code.hljs,code.nohighlight');
for (let i in blocks) {
// eslint-disable-next-line no-prototype-builtins
if (blocks.hasOwnProperty(i)) {
if (!isPluginDisabledForBlock(blocks[i])) {
lineNumbersBlock(blocks[i], options);
}
}
}
} catch (e) {
window.console.error('LineNumbers error: ', e);
}
}
function isPluginDisabledForBlock(element) {
return element.classList.contains('nohljsln');
}
function lineNumbersBlock(element, options) {
if (typeof element !== 'object') return;
element.innerHTML = lineNumbersInternal(element, options);
}
function lineNumbersInternal(element, options) {
let internalOptions = mapOptions(element, options);
duplicateMultilineNodes(element);
return addLineNumbersBlockFor(element.innerHTML, internalOptions);
}
function addLineNumbersBlockFor(inputHtml, options) {
let lines = getLines(inputHtml);
// if last line contains only carriage return remove it
if (lines[lines.length - 1].trim() === '') {
lines.pop();
}
if (lines.length > 1 || options.singleLine) {
let html = '';
for (let i = 0, l = lines.length; i < l; i++) {
html += format(
'<tr>' +
'<td class="{0} {1}" {3}="{5}">' +
'<div class="{2}" {3}="{5}"></div>' +
'</td>' +
'<td class="{0} {4}" {3}="{5}">' +
'{6}' +
'</td>' +
'</tr>',
[
LINE_NAME,
NUMBERS_BLOCK_NAME,
NUMBER_LINE_NAME,
DATA_ATTR_NAME,
CODE_BLOCK_NAME,
i + options.startFrom,
lines[i].length > 0 ? lines[i] : ' ',
]
);
}
return format('<table class="{0}">{1}</table>', [TABLE_NAME, html]);
}
return inputHtml;
}
/**
* @param {HTMLElement} element Code block.
* @param {Object} options External API options.
* @returns {Object} Internal API options.
*/
function mapOptions(element, options) {
options = options || {};
return {
singleLine: getSingleLineOption(options),
startFrom: getStartFromOption(element, options),
};
}
function getSingleLineOption(options) {
let defaultValue = false;
if (options.singleLine) {
return options.singleLine;
}
return defaultValue;
}
function getStartFromOption(element, options) {
let defaultValue = 1;
let startFrom = defaultValue;
if (isFinite(options.startFrom)) {
startFrom = options.startFrom;
}
// can be overridden because local option is priority
let value = getAttribute(element, 'data-ln-start-from');
if (value !== null) {
startFrom = toNumber(value, defaultValue);
}
return startFrom;
}
/**
* Recursive method for fix multi-line elements implementation in highlight.js
* Doing deep passage on child nodes.
* @param {HTMLElement} element
*/
function duplicateMultilineNodes(element) {
let nodes = element.childNodes;
for (let node in nodes) {
// eslint-disable-next-line no-prototype-builtins
if (nodes.hasOwnProperty(node)) {
let child = nodes[node];
if (getLinesCount(child.textContent) > 0) {
if (child.childNodes.length > 0) {
duplicateMultilineNodes(child);
} else {
duplicateMultilineNode(child.parentNode);
}
}
}
}
}
/**
* Method for fix multi-line elements implementation in highlight.js
* @param {HTMLElement} element
*/
function duplicateMultilineNode(element) {
let className = element.className;
if (!/hljs-/.test(className)) return;
let lines = getLines(element.innerHTML);
for (var i = 0, result = ''; i < lines.length; i++) {
let lineText = lines[i].length > 0 ? lines[i] : ' ';
result += format('<span class="{0}">{1}</span>\n', [className, lineText]);
}
element.innerHTML = result.trim();
}
function getLines(text) {
if (text.length === 0) return [];
return text.split(BREAK_LINE_REGEXP);
}
function getLinesCount(text) {
return (text.trim().match(BREAK_LINE_REGEXP) || []).length;
}
/**
* {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript}
* @param {string} format
* @param {array} args
*/
function format(format, args) {
return format.replace(/\{(\d+)\}/g, function (m, n) {
return args[n] !== undefined ? args[n] : m;
});
}
/**
* @param {HTMLElement} element Code block.
* @param {String} attrName Attribute name.
* @returns {String} Attribute value or empty.
*/
function getAttribute(element, attrName) {
return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null;
}
/**
* @param {String} str Source string.
* @param {Number} fallback Fallback value.
* @returns Parsed number or fallback value.
*/
function toNumber(str, fallback) {
if (!str) return fallback;
let number = Number(str);
return isFinite(number) ? number : fallback;
}
export { lineNumbersBlock, initLineNumbersOnLoad };

View File

@@ -0,0 +1,22 @@
/*
* 错误上报sentry
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-06 20:49:28
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
export const smartSentry = {
/**
* sentry 主动上报
*/
captureError: (error) => {
if (error.config && error.data && error && error.headers && error.request && error.status) {
return;
}
// Sentry.captureException(error);
console.error(error);
},
};

View File

@@ -0,0 +1,106 @@
/*
* 水印
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-06 20:50:10
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
import dayjs from 'dayjs';
/**
* 水印DOM id
*/
const WATER_MARK_DOM_ID = 'smart_admin_water_mark';
let smartAdminWaterMarkIntervalId = null;
/**
*
* 因为modal的z-index为1000所以为了modal的黑色背景隐藏掉z-index为 999
*
* @param id
* @param str
* @returns
*/
function setWatermark(id, str) {
//删掉之前的水印
if (document.getElementById(WATER_MARK_DOM_ID) !== null) {
document.getElementById(WATER_MARK_DOM_ID).remove();
}
str = str + ' ' + dayjs().format('YYYY-MM-DD HH:mm');
//创建一个画布
const can = document.createElement('canvas');
//设置画布的长宽
can.width = 400;
can.height = 200;
const cans = can.getContext('2d');
//旋转角度
cans.rotate((-15 * Math.PI) / 150);
cans.font = '16px Microsoft JhengHei';
//设置填充绘画的颜色、渐变或者模式
cans.fillStyle = 'rgba(190, 190, 190, 0.30)';
//设置文本内容的当前对齐方式
cans.textAlign = 'left';
//设置在绘制文本时使用的当前文本基线
cans.textBaseline = 'middle';
//在画布上绘制填色的文本输出的文本开始绘制文本的X坐标位置开始绘制文本的Y坐标位置
cans.fillText(str, can.width / 8, can.height / 2);
const div = document.createElement('div');
div.id = WATER_MARK_DOM_ID;
div.style.pointerEvents = 'none';
div.style.top = '0px';
div.style.left = '0px';
div.style.position = 'absolute';
div.style.zIndex = '99';
div.style.width = '100%';
div.style.height = '100%';
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
document.getElementById(id).appendChild(div);
}
const watermark = {
show: function () {
document.getElementById(WATER_MARK_DOM_ID).style.display = 'block';
},
hide: function () {
document.getElementById(WATER_MARK_DOM_ID).style.display = 'hide';
},
// 该方法只允许调用一次
set: function (id, str) {
// 如果存在水印,则不允许再调用了
if (document.getElementById(WATER_MARK_DOM_ID) !== null) {
return;
}
setWatermark(id, str);
//每隔1分钟检查一次水印
smartAdminWaterMarkIntervalId = setInterval(() => {
setWatermark(id, str);
}, 60000);
window.onresize = () => {
setWatermark(id, str);
};
},
// 清空水印
clear: function () {
let watermarkDom = document.getElementById(WATER_MARK_DOM_ID);
if (watermarkDom) {
watermarkDom.remove();
}
window.removeEventListener('resize', setWatermark);
if (smartAdminWaterMarkIntervalId) {
clearInterval(smartAdminWaterMarkIntervalId);
smartAdminWaterMarkIntervalId = null;
}
},
};
export default watermark;

View File

@@ -0,0 +1,30 @@
/**
* 计算表格自适应高度
*
* @param {*} heightRef
* @param {*} removeRefArray
* @param {*} extraRemoveHeight
* @returns
*/
import { useAppConfigStore } from '../store/modules/system/app-config';
export function calcTableHeight(heightRef, removeRefArray, extraRemoveHeight) {
let removeHeight = 0;
if (removeRefArray && removeRefArray.length > 0) {
for (const item of removeRefArray) {
removeHeight = removeHeight + item.value.$el.offsetHeight;
}
}
let due = 40;
if (useAppConfigStore().$state.pageTagFlag) {
due = due + 40;
}
if (useAppConfigStore().$state.footerFlag) {
due = due + 40;
}
removeHeight = removeHeight + extraRemoveHeight + due;
heightRef.value = document.querySelector('#smartAdminLayoutContent').offsetHeight - removeHeight;
}