import { ElMessage, ElMessageBox } from 'element-plus';
|
import JSONbig from 'json-bigint';
|
import { storeToRefs } from 'pinia';
|
import { unref, type Ref } from 'vue';
|
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
import { useThemeConfig } from '/@/stores/themeConfig';
|
import request from '/@/utils/request';
|
|
/**
|
* @description 当碰到 JSON 中存在过长的数字时,使用 JSONbigString 解析,数字会转为字符串处理
|
* 用法:JSONbigString.parse(jsonStr))
|
*
|
*/
|
export const JSONbigString = JSONbig({ storeAsString: true });
|
export const maxInArray = (arr) => {
|
return Math.max.apply(null, arr);
|
};
|
export function dateFormat(dateString, fmt) {
|
const date = new Date(dateString);
|
let ret;
|
const opt = {
|
'Y+': date.getFullYear().toString(), // 年
|
'm+': (date.getMonth() + 1).toString(), // 月
|
'd+': date.getDate().toString(), // 日
|
'H+': date.getHours().toString(), // 时
|
'M+': date.getMinutes().toString(), // 分
|
'S+': date.getSeconds().toString(), // 秒
|
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
};
|
for (const k in opt) {
|
ret = new RegExp('(' + k + ')').exec(fmt);
|
if (ret) {
|
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
}
|
}
|
return fmt;
|
}
|
|
export function timeFix() {
|
const time = new Date();
|
const hour = time.getHours();
|
return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好';
|
}
|
|
export function welcome() {
|
const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 LOL', '我猜你可能累了'];
|
const index = Math.floor(Math.random() * arr.length);
|
return arr[index];
|
}
|
|
/**
|
* 触发 window.resize
|
*/
|
export function triggerWindowResizeEvent() {
|
const event = document.createEvent('HTMLEvents');
|
event.initEvent('resize', true, true);
|
(event as any).eventType = 'message';
|
window.dispatchEvent(event);
|
}
|
|
export function handleScrollHeader(callback) {
|
let timer = 0;
|
|
let beforeScrollTop = window.pageYOffset;
|
callback = callback || function () {};
|
window.addEventListener(
|
'scroll',
|
(event) => {
|
clearTimeout(timer);
|
timer = setTimeout(() => {
|
let direction = 'up';
|
const afterScrollTop = window.pageYOffset;
|
const delta = afterScrollTop - beforeScrollTop;
|
if (delta === 0) {
|
return false;
|
}
|
direction = delta > 0 ? 'down' : 'up';
|
callback(direction);
|
beforeScrollTop = afterScrollTop;
|
}, 50);
|
},
|
false
|
);
|
}
|
|
export function isIE() {
|
const bw = window.navigator.userAgent;
|
const compare = (s) => bw.indexOf(s) >= 0;
|
const ie11 = (() => 'ActiveXObject' in window)();
|
return compare('MSIE') || ie11;
|
}
|
|
/**
|
* Remove loading animate
|
* @param id parent element id or class
|
* @param timeout
|
*/
|
export function removeLoadingAnimate(id = '', timeout = 1500) {
|
if (id === '') {
|
return;
|
}
|
setTimeout(() => {
|
document.body.removeChild(document.getElementById(id));
|
}, timeout);
|
}
|
|
export const $ = (name) => document.querySelector(name);
|
|
export const getContainerSize = (dom) => ({ width: dom.getBoundingClientRect().width, height: dom.getBoundingClientRect().height });
|
|
export const getImg = (name) => `src/assets/images/${name}`;
|
|
export const downloadJSON = (jsonData: Object, fileName: String) => {
|
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(jsonData));
|
const downloadAnchorNode = document.createElement('a');
|
downloadAnchorNode.setAttribute('href', dataStr);
|
downloadAnchorNode.setAttribute('download', fileName + '.json');
|
document.body.appendChild(downloadAnchorNode);
|
downloadAnchorNode.click();
|
downloadAnchorNode.remove();
|
};
|
|
// export const downloadFile = (url, fileName) => {
|
// request({
|
// url,
|
// method: 'post',
|
// headers: {
|
// 'Content-Type': 'multipart/form-data',
|
// },
|
// })
|
// .then((res) => {
|
// const blob = new Blob([res.data], {
|
// type: 'application/octet-stream',
|
// }); // 2.获取请求返回的response对象中的blob 设置文件类型
|
|
// const url = window.URL.createObjectURL(blob); // 3.创建一个临时的url指向blob对象
|
|
// // 4.创建url之后可以模拟对此文件对象的一系列操作,例如:预览、下载
|
|
// const a = document.createElement('a');
|
|
// a.href = url;
|
|
// a.download = fileName;
|
|
// a.click();
|
|
// // 5.释放这个临时的对象url
|
|
// window.URL.revokeObjectURL(url);
|
// a.remove();
|
// })
|
// .catch((error) => {
|
// console.log(error);
|
// });
|
// };
|
|
/**
|
* 根据 ParentID,变成 Children 的结构,ID、Children、ParentID 可按需配置
|
* 默认配置为
|
* {
|
ID: 'ID',
|
Children: 'Children',
|
ParentID: 'ParentID',
|
}
|
* @param data
|
*/
|
export const convertListToTree = (
|
data: any[],
|
defaultProps = {
|
ID: 'ID',
|
Children: 'Children',
|
ParentID: 'ParentID',
|
}
|
) => {
|
if (!data || data?.length === 0) return [];
|
const map = {};
|
const result: any[] = [];
|
|
const ID = defaultProps.ID;
|
const Children = defaultProps.Children;
|
const ParentID = defaultProps.ParentID;
|
|
data.forEach((item) => {
|
item[Children] = [];
|
map[item[ID]] = item;
|
});
|
|
data.forEach((item) => {
|
const parent = map[item[ParentID]];
|
if (parent) {
|
parent[Children].push(map[item[ID]]);
|
} else {
|
result.push(map[item[ID]]);
|
}
|
});
|
|
return result;
|
};
|
|
/**
|
* 遍历树
|
* @param treeData
|
* @param callback
|
* @param parent 当前节点父亲
|
* @param markParent 是否在节上增加一属性,标记其父亲
|
*/
|
export const travelTree = <T>(
|
treeData: T[],
|
callback: (value: T, index?, array?, parent?) => any,
|
parent: any = null,
|
markParent = false,
|
childrenKey = 'Children'
|
) => {
|
if (!treeData || treeData.length === 0) return;
|
if (!parent) parent = treeData;
|
|
for (let index = 0; index < treeData.length; index++) {
|
const value = treeData[index] as any;
|
if (markParent) {
|
value.Parent = parent;
|
}
|
|
// callback 返回 true 时,跳出迭代
|
// 返回 0 时,结束继续深入当前Children
|
const callResult = callback(value, index, treeData, parent);
|
if (callResult) {
|
return true;
|
}
|
if (callResult === 0) {
|
continue;
|
}
|
|
if (value[childrenKey] && value[childrenKey].length !== 0) {
|
// 递归跳出
|
const callResult = travelTree(value[childrenKey], callback, value, markParent);
|
if (callResult) {
|
return true;
|
}
|
}
|
}
|
};
|
/**
|
* el-table cell-style,除了第指定列之外,其余均居中
|
* 常用于树形表格展示
|
*/
|
export const tableCellCenterExceptColumn =
|
(exceptIndex = 0) =>
|
(data) => {
|
const { columnIndex } = data;
|
if (columnIndex !== exceptIndex) {
|
return { textAlign: 'center' } as any;
|
}
|
};
|
|
/**
|
* 扁平化 Children 结构数组,展开 Children 内容
|
* @param tableData
|
* @returns
|
*/
|
export const flatten = (tableData: any[], removeChild?: boolean, children = 'Children'): any[] => {
|
const flattenedData: any[] = [];
|
|
for (const item of tableData) {
|
flattenedData.push(item);
|
if (item[children] && item[children].length !== 0) {
|
flattenedData.push(...flatten(item[children]));
|
}
|
if (removeChild) {
|
Reflect.deleteProperty(item, children);
|
}
|
}
|
|
return flattenedData;
|
};
|
|
/** 按 ID 查找树结构中的的某个结点 */
|
export const findTreeNode = <T extends { ID; Children?: T[] }>(treeArr: T[], ID: string): T | null => {
|
let result = null;
|
|
const find = (treeArr: T[], ID: string) => {
|
for (const item of treeArr) {
|
if (item.ID === ID) {
|
result = item;
|
return;
|
} else {
|
if (item.Children && item.Children.length !== 0) {
|
find(item.Children, ID);
|
}
|
}
|
}
|
};
|
find(treeArr, ID);
|
return result;
|
};
|
|
// 根据选择的 parent,生成 SortCode
|
export const setSortCode = (treeData: [], formValue: Ref<any>, parentID: string) => {
|
const parent = findTreeNode(treeData, parentID) as any;
|
|
let parentChildren = [];
|
if (parent) {
|
parentChildren = parent.Children;
|
// 没有 parent,为根
|
} else {
|
parentChildren = treeData;
|
}
|
let sort = 1;
|
if (parentChildren?.length > 0) {
|
const sorts = parentChildren.map((x) => {
|
return x.SortCode;
|
});
|
const maxSort = maxInArray(sorts);
|
sort = maxSort + 1;
|
}
|
formValue.value.SortCode = sort;
|
};
|
|
/**
|
* 将对象数组中的指定某两个属性,指定转化为 key-value 这种键值对对象
|
* @param arr 待转化数组
|
* @param defaultProps 默认配置
|
*/
|
export const getObjectMap = (
|
arr: any[],
|
defaultProps = {
|
key: 'ID',
|
value: 'Name',
|
}
|
) => {
|
if (!arr || arr.length === 0) return {};
|
const key = defaultProps.key;
|
const value = defaultProps.value;
|
const result = arr.reduce((acc, curr) => {
|
acc[curr[key]] = curr[value];
|
return acc;
|
}, {});
|
|
return result;
|
};
|
|
/**
|
* 指定一个键,映射到整个行,也可以映射多行
|
* @param arr
|
* @param defaultProps
|
* @returns
|
*/
|
export const getItemMap = <T>(arr: T[], defaultProps = 'ID', isMultiple = false) => {
|
if (!arr || arr.length === 0) return {};
|
|
const result = arr.reduce((acc, curr) => {
|
if (isMultiple) {
|
if (!acc[curr[defaultProps]]) {
|
acc[curr[defaultProps]] = [curr];
|
} else {
|
acc[curr[defaultProps]].push(curr);
|
}
|
} else {
|
acc[curr[defaultProps]] = curr;
|
}
|
return acc;
|
}, {}) as Record<string, T>;
|
|
return result;
|
};
|
|
/**
|
* 检查空数组或空对象
|
* @param obj
|
* @returns
|
*/
|
export const checkEmptyObject = (obj) => {
|
if (!obj) return true;
|
return Object.keys(obj).length === 0;
|
};
|
|
/**
|
* 使用场景:树型选择,可选择的根节点为特定类型,删除某一个叶子节点时,可能需要连锁往上都删掉
|
* 始终保持叶子节点为改节点
|
*
|
* 从父亲中删除一个节点,父亲删除完该节点之后,其Children 若为空
|
* 则继续找到父亲的父亲删除它
|
* @param removedNode
|
* @returns
|
*/
|
export const deleteSelectLeaf = (
|
removedNode,
|
defaultProps = {
|
ID: 'LogicalID',
|
}
|
) => {
|
const parent = removedNode.Parent;
|
if (parent) {
|
const ID = defaultProps.ID;
|
const parentArr = Array.isArray(parent) ? parent : parent.Children;
|
const foundIndex = parentArr.findIndex((item) => item[ID] === removedNode[ID]);
|
if (foundIndex !== -1) {
|
parentArr.splice(foundIndex, 1);
|
// 删完之后数组为空,继续往上递归删除
|
if (parentArr.length === 0 && !Array.isArray(parent)) {
|
deleteSelectLeaf(parent, defaultProps);
|
}
|
}
|
}
|
};
|
|
/**
|
*
|
* @param row 删除行
|
* @param label 删除对象
|
* @param deleteApi 删除 API
|
* @param callback 删除成功回调
|
*/
|
export const deleteCurrentRow = (
|
row: {
|
id: string;
|
title: string;
|
[key: string]: any;
|
},
|
label: String,
|
deleteApi,
|
callback: (id: string) => void,
|
req: any = request,
|
showRowName = true,
|
extraParams: Record<string, any> = {}
|
) => {
|
const tip = showRowName ? `确定删除${label}:【${row.title}】?` : `确定删除当前${label}?`;
|
ElMessageBox.confirm(tip, '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning',
|
}).then(async () => {
|
const rawExtraParams = {};
|
for (const key in extraParams) {
|
rawExtraParams[key] = unref(extraParams[key]);
|
}
|
const res = await deleteApi(
|
{
|
id: row.id,
|
...rawExtraParams,
|
},
|
req
|
);
|
ElMessage.success(`删除${label}成功`);
|
callback(row.id);
|
});
|
};
|
|
/**
|
* 隐藏主页面之外的所有页面
|
*/
|
export const toggleSideMenu = (isHide) => {
|
const tagsViewStore = useTagsViewRoutes();
|
const storesThemeConfig = useThemeConfig();
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
tagsViewStore.setCurrenFullscreen(isHide);
|
// 不显示关闭按钮
|
tagsViewStore.showCloseBtn = !isHide;
|
|
themeConfig.value.isTagsview = !isHide;
|
};
|
|
/**
|
* 最近 n 天的 startDate、endDate
|
* @param dates
|
*/
|
export const getRecentDateRange = (dates: number) => {
|
// 获取当前日期
|
const endDate = new Date();
|
const startDate = new Date();
|
startDate.setTime(startDate.getTime() - 3600 * 1000 * 24 * dates);
|
startDate.setHours(0, 0, 0, 0);
|
return [startDate, endDate];
|
};
|
|
/**
|
* 最近 n 天的 date
|
* @param dates
|
*/
|
export const getRecentDate = (dates: number) => {
|
// 获取当前日期
|
const recentDate = new Date();
|
recentDate.setTime(recentDate.getTime() - 3600 * 1000 * 24 * dates);
|
recentDate.setHours(0, 0, 0, 0);
|
return recentDate;
|
};
|
|
//#region ====================== 最近时间 ======================
|
export const getAWeek = () => {
|
return getRecentDateRange(7);
|
};
|
export const getHalfMonth = () => getRecentDateRange(15);
|
export const getAMonth = () => getRecentDateRange(30);
|
export const getThreeMonth = () => getRecentDateRange(90);
|
//#endregion
|
|
/**
|
* 解析 paras,keys 指定哪些是对象
|
* paras 参数的是一个对象,这个对象的所有value 都是需要待JSON解析的
|
* @param paras
|
* @param keys
|
*/
|
export const parseParas = (paras: Record<string, string>, keys: string[] = []) => {
|
if (!paras) return null;
|
for (const key in paras) {
|
if (Object.prototype.hasOwnProperty.call(paras, key)) {
|
const value = paras[key];
|
|
paras[key] = value === undefined || !keys.includes(key) ? value : JSON.parse(value);
|
}
|
}
|
return paras;
|
};
|
|
/**
|
* 解析一个可空(空字符串)的JSON
|
* @param jsonStr
|
*/
|
export const parseNullableJSON = (jsonStr: string) => {
|
return jsonStr ? JSON.parse(jsonStr) : null;
|
};
|
|
/**
|
* 字符串化一个可空的JSON(为 null 时,直接返回 空字符串)
|
* @param json
|
*/
|
export const stringifyNullableJSON = (json: Object | null) => {
|
return json ? JSON.stringify(json) : '';
|
};
|
|
export const stringifyParas = (paras: Record<string, any>) => {
|
if (!paras) return;
|
|
for (const key in paras) {
|
if (Object.prototype.hasOwnProperty.call(paras, key)) {
|
const value = paras[key];
|
paras[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
}
|
}
|
return paras;
|
};
|
|
export const toPercent = (num: number, havePercentSymbol = true, decimalPlaces = 1, defaultValue = '-') => {
|
if (num == null) return `${defaultValue} %`;
|
let percent = Number(num * 100).toFixed(decimalPlaces);
|
if (havePercentSymbol) {
|
percent += '%';
|
}
|
return percent;
|
};
|
|
/**
|
*
|
* @param {*} func 防抖函数
|
* @param {*} wait 等待时长(毫秒)
|
* @param {*} immediate 是否先立即触发一次
|
* @description 使用方法见{@link https://mp.weixin.qq.com/s/vfnqmE1EG8UCedduDJE7Eg}
|
* 触发了事件会在n毫秒后执行,但是如果在这n毫秒内又去触发,计时器又会重新计算n毫秒,等n毫秒后触发
|
*
|
* 事件会置后触发,新来的事件会覆盖之前的事件
|
* @returns
|
*/
|
export const debounce = (func, wait = 300, immediate = false) => {
|
if (!func) return undefined;
|
let timer = null;
|
return function (...arg) {
|
if (timer !== null) {
|
clearTimeout(timer);
|
} else if (immediate) {
|
func.apply(this, arg);
|
}
|
timer = setTimeout(() => {
|
func.apply(this, arg);
|
timer = null;
|
}, wait);
|
};
|
};
|
|
/**
|
*
|
* @param {*} func 节流函数
|
* @param {*} wait 等待时长(毫秒)
|
* @param {*} immediate 是否先立即触发一次
|
* @description 使用方法见{@link https://mp.weixin.qq.com/s/vfnqmE1EG8UCedduDJE7Eg}
|
* 触发了事件也是在n毫秒后执行,在这n毫秒内再去触发无效,直接return。直到n毫秒之后,可以再去触发一次有效。接受了一次触发,n毫秒内就不再接收其余的触发
|
*
|
* 在指定一段时间区间内只能执行一次,新来的事件无法触发
|
* @returns
|
*/
|
export const throttle = (func, wait = 300, immediate = false) => {
|
let last, timer;
|
return function (...arg) {
|
const now = +new Date();
|
|
if (last && now < last + wait) {
|
clearTimeout(timer);
|
timer = setTimeout(function () {
|
func.apply(this, arg);
|
}, wait); //停止触发 后wait毫秒立即执行
|
} else {
|
if (!immediate) {
|
if (last) {
|
func.apply(this, arg);
|
}
|
} else {
|
func.apply(this, arg);
|
}
|
last = now;
|
}
|
};
|
};
|
|
/**
|
* 判断 id 是否为空(后端要求,为 0 就是空)
|
* @param id
|
*/
|
export const idIsEmpty = (id: string) => {
|
return !id || id === '0';
|
};
|
|
export const arrayIsEmpty = (arr: any) => {
|
return !arr || arr.length === 0;
|
};
|
|
export const checkHaveChild = (item: any, prop = 'Children') => {
|
return item && item[prop] && item[prop].length > 0;
|
};
|
|
/**
|
* 普通对象转为 formData
|
* @param obj
|
*/
|
export const toFormData = (obj: any) => {
|
const formData = new FormData();
|
|
const addFormData = (subObj, prePrefix = '', isArray = false) => {
|
for (const key in subObj) {
|
if (Object.prototype.hasOwnProperty.call(subObj, key)) {
|
const value = subObj[key];
|
let currentKey = '';
|
if (prePrefix === '') {
|
currentKey = key;
|
} else if (isArray) {
|
currentKey = `${prePrefix}[${key}]`;
|
} else {
|
currentKey = `${prePrefix}.${key}`;
|
}
|
|
if (value != null && Array.isArray(value) && value.length > 0) {
|
addFormData(value, currentKey, true);
|
} else if (value != null && typeof value === 'object' && Object.values(value).length > 0) {
|
addFormData(value, currentKey, false);
|
} else {
|
formData.append(currentKey, value);
|
}
|
}
|
}
|
};
|
addFormData(obj);
|
return formData;
|
};
|
|
/**
|
* 保留指定位数小数,且小数位数不够时不会补零
|
* @param num
|
* @param fractionDigits
|
*/
|
export const toMaxFixed = (num: number, fractionDigits: number) => {
|
return parseFloat(num.toFixed(fractionDigits));
|
};
|
|
/**
|
* 检查数组对象是否为空,当数组中的对象每个值都是 undefined | null | '' 都会判定为空
|
* @param arr
|
* @returns
|
*/
|
export const checkArrObjEmpty = (arr: Array<any>, multiple = true) => {
|
if (!arr || arr.length === 0) return true;
|
const checkEmpty = (item) => {
|
return Object.values(item).every((objValue) => objValue == null || objValue === '');
|
};
|
return multiple ? arr.every(checkEmpty) : checkEmpty(arr[0]);
|
};
|
|
/**
|
* 保留指定精度小数位,且不补零
|
* @param num
|
* @param precision
|
* @returns
|
*/
|
export const toMyFixed = (num, precision) => {
|
if (num == null) return '';
|
return num.toFixed(precision).replace(/\.?0+$/, '');
|
};
|