import { useGlobSetting } from '/@/hooks/setting'; import { merge, random } from 'lodash-es'; import { isArray } from '/@/utils/is'; import { FormSchema } from '/@/components/Form'; import { reactive } from "vue"; import { getTenantId, getToken } from "/@/utils/auth"; import { useUserStoreWithOut } from "/@/store/modules/user"; import dayjs from 'dayjs'; import Big from 'big.js'; import { Modal } from "ant-design-vue"; import { defHttp } from "@/utils/http/axios"; import { useI18n } from "@/hooks/web/useI18n"; const globSetting = useGlobSetting(); const baseApiUrl = globSetting.domainUrl; /** * 获取文件服务访问路径 * @param fileUrl 文件路径 * @param prefix(默认http) 文件路径前缀 http/https */ export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => { let result = fileUrl; try { if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) { //判断是否是数组格式 let isArray = fileUrl.indexOf('[') != -1; if (!isArray) { let prefix = `${baseApiUrl}/sys/common/static/`; // 判断是否已包含前缀 if (!fileUrl.startsWith(prefix)) { result = `${prefix}${fileUrl}`; } } } } catch (err) {} return result; }; /** * 触发 window.resize */ export function triggerWindowResizeEvent() { let event: any = document.createEvent('HTMLEvents'); event.initEvent('resize', true, true); event.eventType = 'message'; window.dispatchEvent(event); } /** * 获取随机数 * @param length 数字位数 */ export const getRandom = (length: number = 1) => { return '-' + parseInt(String(Math.random() * 10000 + 1), length); }; /** * 随机生成字符串 * @param length 字符串的长度 * @param chats 可选字符串区间(只会生成传入的字符串中的字符) * @return string 生成的字符串 */ export function randomString(length: number, chats?: string) { if (!length) length = 1; if (!chats) { // noinspection SpellCheckingInspection chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'; } let str = ''; for (let i = 0; i < length; i++) { let num = random(0, chats.length - 1); str += chats[num]; } return str; } /** * 将普通列表数据转化为tree结构 * @param array tree数据 * @param opt 配置参数 * @param startPid 父节点 */ export const listToTree = (array, opt, startPid) => { const obj = { primaryKey: opt.primaryKey || 'key', parentKey: opt.parentKey || 'parentId', titleKey: opt.titleKey || 'title', startPid: opt.startPid || '', currentDept: opt.currentDept || 0, maxDept: opt.maxDept || 100, childKey: opt.childKey || 'children', }; if (startPid) { obj.startPid = startPid; } return toTree(array, obj.startPid, obj.currentDept, obj); }; /** * 递归构建tree * @param list * @param startPid * @param currentDept * @param opt * @returns {Array} */ export const toTree = (array, startPid, currentDept, opt) => { if (opt.maxDept < currentDept) { return []; } let child = []; if (array && array.length > 0) { child = array .map((item) => { // 筛查符合条件的数据(主键 = startPid) if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) { // 满足条件则递归 const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt); // 节点信息保存 if (nextChild.length > 0) { item['isLeaf'] = false; item[opt.childKey] = nextChild; } else { item['isLeaf'] = true; } item['title'] = item[opt.titleKey]; item['label'] = item[opt.titleKey]; item['key'] = item[opt.primaryKey]; item['value'] = item[opt.primaryKey]; return item; } }) .filter((item) => { return item !== undefined; }); } return child; }; /** * 表格底部合计工具方法 * @param tableData 表格数据 * @param fieldKeys 要计算合计的列字段 */ export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) { let totals: any = { _row: '合计', _index: '合计' }; fieldKeys.forEach((key) => { totals[key] = tableData.reduce((prev, next) => { // update-begin--author:liaozhiyang---date:20240118---for:【QQYUN-7891】PR 合计工具方法,转换为Nuber类型再计算 const value = Number(next[key]); if (!Number.isNaN(value)) { // update-begin--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度 prev = Big(prev).plus(value).toString(); // update-end--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度 } // update-end--author:liaozhiyang---date:20240118---for:【issues/7830】PR 合计工具方法,转换为Nuber类型再计算 return prev; }, 0); // update-begin--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度 totals[key] = +totals[key]; // update-end--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度 }); return totals; } /** * 简单实现防抖方法 * * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。 * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。 * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。 * * @param fn 要防抖的函数 * @param delay 防抖的毫秒数 * @returns {Function} */ export function simpleDebounce(fn, delay = 100) { let timer: any | null = null; return function () { let args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(() => { // @ts-ignore fn.apply(this, args); }, delay); }; } /** * 日期格式化 * @param date 日期 * @param block 格式化字符串 */ export function dateFormat(date, block) { if (!date) { return ''; } let format = block || 'yyyy-MM-dd'; date = new Date(date); const map = { M: date.getMonth() + 1, // 月份 d: date.getDate(), // 日 h: date.getHours(), // 小时 m: date.getMinutes(), // 分 s: date.getSeconds(), // 秒 q: Math.floor((date.getMonth() + 3) / 3), // 季度 S: date.getMilliseconds(), // 毫秒 }; format = format.replace(/([yMdhmsqS])+/g, (all, t) => { let v = map[t]; if (v !== undefined) { if (all.length > 1) { v = `0${v}`; v = v.substr(v.length - 2); } return v; } else if (t === 'y') { return date .getFullYear() .toString() .substr(4 - all.length); } return all; }); return format; } /** * 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari * 目前使用的地方:JVxeTable Span模式 */ export function getEventPath(event) { let target = event.target; let path = (event.composedPath && event.composedPath()) || event.path; if (path != null) { return path.indexOf(window) < 0 ? path.concat(window) : path; } if (target === window) { return [window]; } let getParents = (node, memo) => { const parentNode = node.parentNode; if (!parentNode) { return memo; } else { return getParents(parentNode, memo.concat(parentNode)); } }; return [target].concat(getParents(target, []), window); } /** * 如果值不存在就 push 进数组,反之不处理 * @param array 要操作的数据 * @param value 要添加的值 * @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值 * @returns {boolean} 成功 push 返回 true,不处理返回 false */ export function pushIfNotExist(array, value, key?) { for (let item of array) { if (key && item[key] === value[key]) { return false; } else if (item === value) { return false; } } array.push(value); return true; } /** * 过滤对象中为空的属性 * @param obj * @returns {*} */ export function filterObj(obj) { if (!(typeof obj == 'object')) { return; } for (let key in obj) { if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) { delete obj[key]; } } return obj; } /** * 下划线转驼峰 * @param string */ export function underLine2CamelCase(string: string) { return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** * 查找树结构 * @param treeList * @param fn 查找方法 * @param childrenKey */ export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') { for (let i = 0; i < treeList.length; i++) { let item = treeList[i]; if (fn(item, i, treeList)) { return item; } let children = item[childrenKey]; if (isArray(children)) { let findResult = findTree(children, fn, childrenKey); if (findResult) { return findResult; } } } return null; } /** 获取 mapFormSchema 方法 */ export function bindMapFormSchema(spanMap, spanTypeDef: T) { return function (s: FormSchema, spanType: T = spanTypeDef) { return merge( { disabledLabelWidth: true, } as FormSchema, spanMap[spanType], s ); }; } /** * 字符串是否为null或null字符串 * @param str * @return {boolean} */ export function stringIsNull(str) { // 两个 == 可以同时判断 null 和 undefined return str == null || str === 'null' || str === 'undefined'; } /** * 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题 * @param node */ export function getAutoScrollContainer(node: HTMLElement) { let element: Nullable = node while (element != null) { if (element.classList.contains('scrollbar__view')) { // 判断是否有滚动条 if (element.clientHeight < element.scrollHeight) { // 有滚动条时,挂载到父级,解决滚动问题 return node.parentElement } else { // 无滚动条时,挂载到body上,解决下拉框遮盖问题 return document.body } } else { element = element.parentElement } } // 不在弹窗内,走默认逻辑 return node.parentElement } /** * 判断子菜单是否全部隐藏 * @param menuTreeItem */ export function checkChildrenHidden(menuTreeItem){ //是否是聚合路由 let alwaysShow=menuTreeItem.alwaysShow; if(alwaysShow){ return false; } if(!menuTreeItem.children){ return false } return menuTreeItem.children?.find((item) => item.hideMenu == false) != null; } /** * 计算文件大小 * @param fileSize * @param unit * @return 返回大小及后缀 */ export function calculateFileSize(fileSize, unit?) { let unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; if (unit && unit.length > 0) { unitArr = unit; } let size = fileSize; let unitIndex = 0; while (size >= 1024 && unitIndex < unitArr.length - 1) { size /= 1024; unitIndex++; } //保留两位小数,四舍五入 size = Math.round(size * 100) / 100; return size + unitArr[unitIndex]; } /** * 获取上传header */ export function getHeaders() { let tenantId = getTenantId(); return reactive({ 'X-Access-Token': getToken(), 'X-Tenant-Id': tenantId ? tenantId : '0', }); } /** 根据表达式获取相应的用户信息 */ export function getUserInfoByExpression(expression) { if (!expression) { return expression; } // 当前日期 if (expression === 'sys_date' || expression === 'sysDate') { return dayjs().format('YYYY-MM-DD'); } // 当前时间 if (expression === 'sys_time' || expression === 'sysTime') { return dayjs().format('HH:mm:ss'); } const userStore = useUserStoreWithOut(); let userInfo = userStore.getUserInfo; if (userInfo) { switch (expression) { case 'sysUserId': return userInfo.id; // 当前登录用户登录账号 case 'sysUserCode': case 'sys_user_code': return userInfo.username; // 当前登录用户真实名称 case 'sysUserName': return userInfo.realname; // 当前登录用户部门编号 case 'sysOrgCode': case 'sys_org_code': return userInfo.orgCode; } } return expression; } /** * 替换表达式(#{xxx})为用户信息 * @param expression */ export function replaceUserInfoByExpression(expression: string | any[]) { if (!expression) { return expression; } const isString = typeof expression === 'string'; const isArray = Array.isArray(expression) if (!isString && !isArray) { return expression; } const reg = /#{(.*?)}/g; const replace = (str) => { if (typeof str !== 'string') { return str; } let result = str.match(reg); if (result && result.length > 0) { result.forEach((item) => { let userInfo = getUserInfoByExpression(item.substring(2, item.length - 1)); str = str.replace(item, userInfo); }); } return str; }; // @ts-ignore return isString ? replace(expression) : expression.map(replace); } /** * 设置租户缓存,当租户退出的时候 * * @param tenantId */ export async function userExitChangeLoginTenantId(tenantId){ const userStore = useUserStoreWithOut(); //step 1 获取用户租户 const url = '/sys/tenant/getCurrentUserTenant' let currentTenantId = null; const data = await defHttp.get({ url }); if(data && data.list){ let arr = data.list; if(arr.length>0){ //step 2.判断当前id是否存在用户租户中 let filterTenantId = arr.filter((item) => item.id == tenantId); //存在说明不是退出的不是当前租户,还用用来的租户即可 if(filterTenantId && filterTenantId.length>0){ currentTenantId = tenantId; }else{ //不存在默认第一个 currentTenantId = arr[0].id } } } let loginTenantId = getTenantId(); userStore.setTenant(currentTenantId); //update-begin---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新--- //租户为空,说明没有租户了,需要刷新页面。或者当前租户和退出的租户一致则需要刷新浏览器 if(!currentTenantId || tenantId == loginTenantId){ window.location.reload(); } //update-end---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新--- } /** * 我的租户模块需要开启多租户提示 * * @param title 标题 */ export function tenantSaasMessage(title){ let tenantId = getTenantId(); if(!tenantId){ Modal.confirm({ title:title, content: '此菜单需要在多租户模式下使用,否则数据会出现混乱', okText: '确认', okType: 'danger', // @ts-ignore cancelButtonProps: { style: { display: 'none' } }, }) } } /** * 判断日期和当前时间是否为同一天 * @param dateStr */ export function sameDay(dateStr) { if (!dateStr) { return false; } // 获取当前日期 let currentDate = new Date(); let currentDay = currentDate.getDate(); let currentMonth = currentDate.getMonth(); let currentYear = currentDate.getFullYear(); //创建另一个日期进行比较 let otherDate = new Date(dateStr); let otherDay = otherDate.getDate(); let otherMonth = otherDate.getMonth(); let otherYear = otherDate.getFullYear(); //比较日期 if (currentDay === otherDay && currentMonth === otherMonth && currentYear === otherYear) { return true; } else { return false; } } /** * 翻译菜单名称 * 2024-02-28 * liaozhiyang * @param data */ export function translateTitle(data) { if (data?.length) { const { t } = useI18n(); data.forEach((item) => { if (item.slotTitle) { if (item.slotTitle.includes("t('") && t) { item.slotTitle = new Function('t', `return ${item.slotTitle}`)(t); } } if (item.children?.length) { translateTitle(item.children); } }); } return data; } /** * * 深度冻结对象 * @param obj Object or Array */ export function freezeDeep(obj: Recordable | Recordable[]) { if (obj != null) { if (Array.isArray(obj)) { obj.forEach(item => freezeDeep(item)) } else if (typeof obj === 'object') { Object.values(obj).forEach(value => { freezeDeep(value) }) } Object.freeze(obj) } return obj }