import {nextTick} from 'vue'; import router from '/@/router/index'; import pinia from '/@/stores/index'; import {storeToRefs} from 'pinia'; import {useThemeConfig} from '/@/stores/themeConfig'; import {i18n} from '/@/i18n/index'; import {Local} from '/@/utils/storage'; import {verifyUrl} from '/@/utils/toolsValidate'; import request from '/@/utils/request'; import {useMessage} from '/@/hooks/message'; import * as CryptoJS from 'crypto-js'; import { sm4, sm2 } from 'sm-crypto' import {validateNull} from './validate'; /** * 设置浏览器标题国际化 * @method const title = useTitle(); ==> title() */ export function useTitle() { const stores = useThemeConfig(pinia); const {themeConfig} = storeToRefs(stores); nextTick(() => { let globalTitle: string = themeConfig.value.globalTitle; let webTitle = setTagsViewNameI18n(router.currentRoute.value); document.title = `${webTitle} - ${globalTitle}` || globalTitle; }); } /** * 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化 * @param params 路由 query、params 中的 tagsViewName * @returns 返回当前 tagsViewName 名称 */ export function setTagsViewNameI18n(item: any) { let tagsViewName: string = ''; const {query, params} = item; //修复tagsViewName匹配到其他含下列单词的路由 const pattern = /^\{("(zh-cn|en|zh-tw)":"[^,]+",?){1,3}}$/; if (query?.tagsViewName || params?.tagsViewName) { if (pattern.test(query?.tagsViewName) || pattern.test(params?.tagsViewName)) { // 国际化 const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName)); tagsViewName = urlTagsParams[i18n.global.locale.value]; } else { // 非国际化 tagsViewName = query?.tagsViewName || params?.tagsViewName; } } else { let name='' if (item.name && typeof item.name === 'string' && item.name.indexOf("_") >= 0) { name=item.name.split("_")[0] }else{ name=item.name || '' } // 非自定义 tagsView 名称 tagsViewName = i18n.global.t(name); } return tagsViewName; } /** * 图片懒加载 * @param el dom 目标元素 * @param arr 列表数据 * @description data-xxx 属性用于存储页面或应用程序的私有自定义数据 */ export const lazyImg = (el: string, arr: EmptyArrayType) => { const io = new IntersectionObserver((res) => { res.forEach((v: any) => { if (v.isIntersecting) { const {img, key} = v.target.dataset; v.target.src = img; v.target.onload = () => { io.unobserve(v.target); arr[key]['loading'] = false; }; } }); }); nextTick(() => { document.querySelectorAll(el).forEach((img) => io.observe(img)); }); }; /** * 全局组件大小 * @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize` */ export const globalComponentSize = (): string => { const stores = useThemeConfig(pinia); const {themeConfig} = storeToRefs(stores); return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize; }; /** * 对象深克隆 * @param obj 源对象 * @returns 克隆后的对象 */ export function deepClone(obj: EmptyObjectType) { let newObj: EmptyObjectType; try { newObj = obj.push ? [] : {}; } catch (error) { newObj = {}; } for (let attr in obj) { if (obj[attr] && typeof obj[attr] === 'object') { newObj[attr] = deepClone(obj[attr]); } else { newObj[attr] = obj[attr]; } } return newObj; } /** * 判断是否是移动端 */ export function isMobile() { if ( navigator.userAgent.match( /('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i ) ) { return true; } else { return false; } } /** * 判断数组对象中所有属性是否为空,为空则删除当前行对象 * @description @感谢大黄 * @param list 数组对象 * @returns 删除空值后的数组对象 */ export function handleEmpty(list: EmptyArrayType) { const arr = [] as any[]; for (const i in list) { const d = [] as any[]; for (const j in list[i]) { d.push(list[i][j]); } const leng = d.filter((item) => item === '').length; if (leng !== d.length) { arr.push(list[i]); } } return arr; } /** * 打开外部链接 * @param val 当前点击项菜单 */ export function handleOpenLink(val: RouteItem) { router.push(val.path); if (verifyUrl(val.meta?.isLink)) window.open(val.meta?.isLink); else window.open(`${val.meta?.isLink}`); } /** * 打开小窗口 */ export const openWindow = (url: string, title: string, w: number, h: number) => { // @ts-ignore const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; // @ts-ignore const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; const left = width / 2 - w / 2 + dualScreenLeft; const top = height / 2 - h / 2 + dualScreenTop; return window.open( url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left ); }; /** *加密处理 */ export function encryption(src: string, keyWord: string) { const key = CryptoJS.enc.Utf8.parse(keyWord); // 加密 var encrypted = CryptoJS.AES.encrypt(src, key, { iv: key, mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.NoPadding, }); return encrypted.toString(); } /** * 解密 * @param {*} params 参数列表 * @returns 明文 */ export function decryption(src: string, keyWord: string) { const key = CryptoJS.enc.Utf8.parse(keyWord); // 解密逻辑 var decryptd = CryptoJS.AES.decrypt(src, key, { iv: key, mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.NoPadding, }); return decryptd.toString(CryptoJS.enc.Utf8); } /** * SM4加密处理 */ export function sm4Encryption(src: string, keyWord: string) { return sm4.encrypt(src, keyWord); } /** * SM4解密处理 * @param {*} params 参数列表 * @returns 明文 */ export function sm4Decryption(src: string, keyWord: string) { return sm4.decrypt(src, keyWord); } /** * SM2 加密(登录密码等,与后端 SM2 私钥解密配套) * @param msg 明文 * @param publicKey 后端提供的 SM2 公钥(十六进制,04 开头 130 字符或 128 字符压缩格式) * @returns 十六进制密文,格式 C1C3C2 */ export function sm2Encrypt(msg: string, publicKey: string): string { if (!msg || !publicKey) return msg; return sm2.doEncrypt(msg, publicKey, 1); } /** * Base64 加密 * @param {*} src 明文 * @returns 密文 */ export function base64Encrypt(src: string) { const encodedWord = CryptoJS.enc.Utf8.parse(src); return CryptoJS.enc.Base64.stringify(encodedWord); } /** * * @param url 目标下载接口 * @param query 查询参数 * @param fileName 文件名称 * @returns {*} */ export function downBlobFile(url: any, query: any, fileName: string) { return request({ url: url, method: 'get', responseType: 'blob', params: query, }).then((response) => { handleBlobFile(response, fileName); }); } /** * blob 文件刘处理 * @param response 响应结果 * @returns */ export function handleBlobFile(response: any, fileName: string) { // 处理返回的文件流,支持 axios 响应结构 { data: blob } 或直接 blob const blob = response?.data || response; if (blob && blob.size === 0) { useMessage().error('内容为空,无法下载'); return; } const link = document.createElement('a'); // 兼容一下 入参不是 File Blob 类型情况 var binaryData = [] as any; binaryData.push(blob); link.href = window.URL.createObjectURL(new Blob(binaryData)); link.download = fileName; document.body.appendChild(link); link.click(); window.setTimeout(function () { // @ts-ignore URL.revokeObjectURL(blob); document.body.removeChild(link); }, 0); } /** * @description 生成唯一 uuid * @return string */ export function generateUUID() { if (typeof crypto === 'object') { if (typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { const callback = (c: any) => { const num = Number(c); return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16); }; return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback); } } let timestamp = new Date().getTime(); let performanceNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { let random = Math.random() * 16; if (timestamp > 0) { random = (timestamp + random) % 16 | 0; timestamp = Math.floor(timestamp / 16); } else { random = (performanceNow + random) % 16 | 0; performanceNow = Math.floor(performanceNow / 16); } return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16); }); } /** * 统一批量导出 * @method useTitle 设置浏览器标题国际化 * @method setTagsViewNameI18n 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化 * @method lazyImg 图片懒加载 * @method globalComponentSize() element plus 全局组件大小 * @method deepClone 对象深克隆 * @method isMobile 判断是否是移动端 * @method handleEmpty 判断数组对象中所有属性是否为空,为空则删除当前行对象 * @method handleOpenLink 打开外部链接 */ const other = { useTitle: () => { useTitle(); }, setTagsViewNameI18n(route: RouteToFrom) { return setTagsViewNameI18n(route); }, lazyImg: (el: string, arr: EmptyArrayType) => { lazyImg(el, arr); }, globalComponentSize: () => { return globalComponentSize(); }, deepClone: (obj: EmptyObjectType) => { return deepClone(obj); }, isMobile: () => { return isMobile(); }, handleEmpty: (list: EmptyArrayType) => { return handleEmpty(list); }, handleOpenLink: (val: RouteItem) => { handleOpenLink(val); }, encryption: (src: string, keyWord: string) => { return encryption(src, keyWord); }, decryption: (src: string, keyWord: string) => { return decryption(src, keyWord); }, sm2Encrypt: (msg: string, publicKey: string) => { return sm2Encrypt(msg, publicKey); }, base64Encrypt: (data: any) => { return base64Encrypt(data); }, downBlobFile: (url: any, query: any, fileName: string) => { return downBlobFile(url, query, fileName); }, toUnderline: (str: string) => { return toUnderline(str); }, openWindow: (url: string, title: string, w: number, h: number) => { return openWindow(url, title, w, h); }, getQueryString: (url: string, paraName: string) => { return getQueryString(url, paraName); }, adaptationUrl: (url?: string) => { return adaptationUrl(url); }, resolveAllEunuchNodeId: (json: any[], idArr: any[], temp: any[] = []) => { return resolveAllEunuchNodeId(json, idArr, temp); }, getNonDuplicateID: () => { return getNonDuplicateID(); }, addUnit: (value: string | number, unit = 'px') => { return addUnit(value, unit); }, validateNull: (value: any) => { return validateNull(value); }, getNumberRadixNum: (input: Number) => { return getNumberRadixNum(input); } }; export function getNumberRadixNum(input: Number) { let strings = input.toString().split("."); if (strings.length <= 1) { return 0; } return strings[1].toString().length; } export function getQueryString(url: string, paraName: string) { const arrObj = url.split('?'); if (arrObj.length > 1) { const arrPara = arrObj[1].split('&'); let arr; for (let i = 0; i < arrPara.length; i++) { arr = arrPara[i].split('='); // eslint-disable-next-line eqeqeq if (arr != null && arr[0] == paraName) { return arr[1]; } } return ''; } else { return ''; } } /** * 列表结构转树结构 * @param data * @param id * @param parentId * @param children * @param rootId * @returns {*} */ export function handleTree(data: any, id: any, parentId: any, children: any, rootId: any) { id = id || 'id'; parentId = parentId || 'parentId'; children = children || 'children'; rootId = rootId || Math.min.apply( Math, data.map((item: any) => { return item[parentId]; }) ) || 0; //对源数据深度克隆 const cloneData = JSON.parse(JSON.stringify(data)); //循环所有项 const treeData = cloneData.filter((father: any) => { const branchArr = cloneData.filter((child: any) => { //返回每一项的子级数组 return father[id] === child[parentId]; }); branchArr.length > 0 ? (father[children] = branchArr) : ''; //返回第一层 return father[parentId] === rootId; }); return treeData !== '' ? treeData : data; } /** * 解析所有太监节点ID * @returns */ const resolveAllEunuchNodeId = (json: any[], idArr: any[], temp: any[] = []) => { for (const item of json) { if (item.children && item.children.length !== 0) { resolveAllEunuchNodeId(item.children, idArr, temp); } else { temp.push(...idArr.filter((id) => id === item.id)); } } return temp; }; /** * * @param str 驼峰转下划线 * @returns 下划线 */ export function toUnderline(str: string) { return str.replace(/([A-Z])/g, '_$1').toLowerCase(); } /** * 自动适配不同的后端架构 * 1. 例如 /act/oa/task ,在微服务架构保持不变,在单体架构编程 /admin/oa/task * 2. 特殊 /gen/xxx ,在微服务架构、单体架构编程 都需保持不变 * * @param originUrl 原始路径 */ export function adaptationUrl(originUrl?: string) { // 微服务架构 不做路径转换,为空不做路径转换 const isMicro = import.meta.env.VITE_IS_MICRO; if (validateNull(isMicro) || isMicro === 'true') { return originUrl; } // 转为 /admin 路由前缀的请求 return `/admin/${originUrl?.split('/').splice(2).join('/')}`; } /** * @description 获取不重复的id * @param length { Number } id的长度 * @return { String } id */ const getNonDuplicateID = (length = 8) => { let idStr = Date.now().toString(36); idStr += Math.random().toString(36).substring(3, length); return idStr; }; /** * @description 添加单位 * @param {String | Number} value 值 100 * @param {String} unit 单位 px em rem */ const addUnit = (value: string | number, unit = 'px') => { return !Object.is(Number(value), NaN) ? `${value}${unit}` : value; }; // 统一批量导出 export default other;