other.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import {nextTick, defineAsyncComponent} from 'vue';
  2. import type {App} from 'vue';
  3. import * as svg from '@element-plus/icons-vue';
  4. import router from '/@/router/index';
  5. import pinia from '/@/stores/index';
  6. import {storeToRefs} from 'pinia';
  7. import {useThemeConfig} from '/@/stores/themeConfig';
  8. import {i18n} from '/@/i18n/index';
  9. import {Local} from '/@/utils/storage';
  10. import {verifyUrl} from '/@/utils/toolsValidate';
  11. import request from '/@/utils/request';
  12. import {useMessage} from '/@/hooks/message';
  13. // @ts-ignore
  14. import * as CryptoJS from 'crypto-js';
  15. import {validateNull} from './validate';
  16. import {RouteItem, RouteItems, RouteToFrom} from "/@/types/global";
  17. // 引入组件
  18. const SvgIcon = defineAsyncComponent(() => import('/@/components/SvgIcon/index.vue'));
  19. /**
  20. * 导出全局注册 element plus svg 图标
  21. * @param app vue 实例
  22. * @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
  23. */
  24. export function elSvg(app: App) {
  25. const icons = svg as any;
  26. for (const i in icons) {
  27. app.component(`ele-${icons[i].name}`, icons[i]);
  28. }
  29. app.component('SvgIcon', SvgIcon);
  30. }
  31. /**
  32. * 设置浏览器标题国际化
  33. * @method const title = useTitle(); ==> title()
  34. */
  35. export function useTitle() {
  36. const stores = useThemeConfig(pinia);
  37. const {themeConfig} = storeToRefs(stores);
  38. nextTick(() => {
  39. let globalTitle: string = themeConfig.value.globalTitle;
  40. let webTitle = setMenuI18n(router.currentRoute.value);
  41. document.title = `${webTitle} - ${globalTitle}` || globalTitle;
  42. });
  43. }
  44. /**
  45. * 设置菜单国际化
  46. * @param {object} item - 菜单项对象
  47. * @param {string} item.enName - 英文名称
  48. * @param {string} item.name - 名称
  49. * @returns {string} - 国际化后的名称
  50. */
  51. export function setMenuI18n(item: any) {
  52. let name = i18n.global.t(item.name)
  53. if (name !== item.name) {
  54. return name;
  55. }
  56. return i18n.global.locale.value === 'en' ? item.meta.enName : item.meta.title;
  57. }
  58. /**
  59. * 图片懒加载
  60. * @param el dom 目标元素
  61. * @param arr 列表数据
  62. * @description data-xxx 属性用于存储页面或应用程序的私有自定义数据
  63. */
  64. export const lazyImg = (el: string, arr: EmptyArrayType) => {
  65. const io = new IntersectionObserver((res) => {
  66. res.forEach((v: any) => {
  67. if (v.isIntersecting) {
  68. const {img, key} = v.target.dataset;
  69. v.target.src = img;
  70. v.target.onload = () => {
  71. io.unobserve(v.target);
  72. arr[key]['loading'] = false;
  73. };
  74. }
  75. });
  76. });
  77. nextTick(() => {
  78. document.querySelectorAll(el).forEach((img) => io.observe(img));
  79. });
  80. };
  81. /**
  82. * 全局组件大小
  83. * @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
  84. */
  85. export const globalComponentSize = (): string => {
  86. const stores = useThemeConfig(pinia);
  87. const {themeConfig} = storeToRefs(stores);
  88. return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize;
  89. };
  90. /**
  91. * 对象深克隆
  92. * @param obj 源对象
  93. * @returns 克隆后的对象
  94. */
  95. export function deepClone(obj: EmptyObjectType) {
  96. let newObj: EmptyObjectType;
  97. try {
  98. newObj = obj.push ? [] : {};
  99. } catch (error) {
  100. newObj = {};
  101. }
  102. for (let attr in obj) {
  103. if (obj[attr] && typeof obj[attr] === 'object') {
  104. newObj[attr] = deepClone(obj[attr]);
  105. } else {
  106. newObj[attr] = obj[attr];
  107. }
  108. }
  109. return newObj;
  110. }
  111. /**
  112. * 判断是否是移动端
  113. */
  114. export function isMobile() {
  115. if (
  116. navigator.userAgent.match(
  117. /('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i
  118. )
  119. ) {
  120. return true;
  121. } else {
  122. return false;
  123. }
  124. }
  125. /**
  126. * 判断数组对象中所有属性是否为空,为空则删除当前行对象
  127. * @description @感谢大黄
  128. * @param list 数组对象
  129. * @returns 删除空值后的数组对象
  130. */
  131. export function handleEmpty(list: EmptyArrayType) {
  132. const arr = [] as any[];
  133. for (const i in list) {
  134. const d = [] as any[];
  135. for (const j in list[i]) {
  136. d.push(list[i][j]);
  137. }
  138. const leng = d.filter((item) => item === '').length;
  139. if (leng !== d.length) {
  140. arr.push(list[i]);
  141. }
  142. }
  143. return arr;
  144. }
  145. /**
  146. * 打开外部链接
  147. * @param val 当前点击项菜单
  148. */
  149. export function handleOpenLink(val: RouteItem) {
  150. router.push(val.path);
  151. if (verifyUrl(<string>val.meta?.isLink)) window.open(val.meta?.isLink);
  152. else window.open(`${val.meta?.isLink}`);
  153. }
  154. /**
  155. * 打开小窗口
  156. */
  157. export const openWindow = (url: string, title: string, w: number, h: number) => {
  158. // @ts-ignore
  159. const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
  160. // @ts-ignore
  161. const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
  162. const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
  163. const height = window.innerHeight
  164. ? window.innerHeight
  165. : document.documentElement.clientHeight
  166. ? document.documentElement.clientHeight
  167. : screen.height;
  168. const left = width / 2 - w / 2 + dualScreenLeft;
  169. const top = height / 2 - h / 2 + dualScreenTop;
  170. return window.open(
  171. url,
  172. title,
  173. 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' +
  174. w +
  175. ', height=' +
  176. h +
  177. ', top=' +
  178. top +
  179. ', left=' +
  180. left
  181. );
  182. };
  183. /**
  184. *加密处理
  185. */
  186. export function encryption(src: string) {
  187. const key = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_PWD_ENC_KEY);
  188. const iv = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_PWD_ENC_IV);
  189. // 加密
  190. var encrypted = CryptoJS.AES.encrypt(src, key, {
  191. key: key,
  192. iv: iv,
  193. mode: CryptoJS.mode.CBC,
  194. padding: CryptoJS.pad.Pkcs7,
  195. });
  196. return encrypted.toString();
  197. }
  198. /**
  199. * 解密
  200. * @param {*} params 参数列表
  201. * @returns 明文
  202. */
  203. export function decryption(src: string) {
  204. const key = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_PWD_ENC_KEY);
  205. const iv = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_PWD_ENC_IV);
  206. // 解密逻辑
  207. var decryptd = CryptoJS.AES.decrypt(src, key, {
  208. key: key,
  209. iv: iv,
  210. mode: CryptoJS.mode.CBC,
  211. padding: CryptoJS.pad.Pkcs7,
  212. });
  213. return decryptd.toString(CryptoJS.enc.Utf8);
  214. }
  215. /**
  216. * Base64 加密
  217. * @param {*} src 明文
  218. * @returns 密文
  219. */
  220. export function base64Encrypt(src: string) {
  221. const encodedWord = CryptoJS.enc.Utf8.parse(src);
  222. return CryptoJS.enc.Base64.stringify(encodedWord);
  223. }
  224. /**
  225. *
  226. * @param url 目标下载接口
  227. * @param query 查询参数
  228. * @param fileName 文件名称
  229. * @returns {*}
  230. */
  231. export function downBlobFile(url: any, query: any, fileName: string) {
  232. return request({
  233. url: url,
  234. method: 'get',
  235. responseType: 'blob',
  236. params: query,
  237. }).then((response) => {
  238. handleBlobFile(response, fileName);
  239. });
  240. }
  241. /**
  242. * blob 文件刘处理
  243. * @param response 响应结果
  244. * @returns
  245. */
  246. export function handleBlobFile(response: any, fileName: string) {
  247. // 处理返回的文件流
  248. const blob = response;
  249. if (blob && blob.size === 0) {
  250. useMessage().error('内容为空,无法下载');
  251. return;
  252. }
  253. const link = document.createElement('a');
  254. // 兼容一下 入参不是 File Blob 类型情况
  255. var binaryData = [] as any;
  256. binaryData.push(response);
  257. link.href = window.URL.createObjectURL(new Blob(binaryData));
  258. link.download = fileName;
  259. document.body.appendChild(link);
  260. link.click();
  261. window.setTimeout(function () {
  262. // @ts-ignore
  263. URL.revokeObjectURL(blob);
  264. document.body.removeChild(link);
  265. }, 0);
  266. }
  267. /**
  268. * @description 生成唯一 uuid
  269. * @return string
  270. */
  271. export function generateUUID() {
  272. if (typeof crypto === 'object') {
  273. if (typeof crypto.randomUUID === 'function') {
  274. return crypto.randomUUID();
  275. }
  276. if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
  277. const callback = (c: any) => {
  278. const num = Number(c);
  279. return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
  280. };
  281. return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback);
  282. }
  283. }
  284. let timestamp = new Date().getTime();
  285. let performanceNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
  286. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  287. let random = Math.random() * 16;
  288. if (timestamp > 0) {
  289. random = (timestamp + random) % 16 | 0;
  290. timestamp = Math.floor(timestamp / 16);
  291. } else {
  292. random = (performanceNow + random) % 16 | 0;
  293. performanceNow = Math.floor(performanceNow / 16);
  294. }
  295. return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
  296. });
  297. }
  298. /**
  299. * 统一批量导出
  300. * @method elSvg 导出全局注册 element plus svg 图标
  301. * @method useTitle 设置浏览器标题国际化
  302. * @method setTagsViewNameI18n 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
  303. * @method lazyImg 图片懒加载
  304. * @method globalComponentSize() element plus 全局组件大小
  305. * @method deepClone 对象深克隆
  306. * @method isMobile 判断是否是移动端
  307. * @method handleEmpty 判断数组对象中所有属性是否为空,为空则删除当前行对象
  308. * @method handleOpenLink 打开外部链接
  309. */
  310. const other = {
  311. elSvg: (app: App) => {
  312. elSvg(app);
  313. },
  314. useTitle: () => {
  315. useTitle();
  316. },
  317. setMenuI18n(item: RouteItems) {
  318. return setMenuI18n(item)
  319. },
  320. lazyImg: (el: string, arr: EmptyArrayType) => {
  321. lazyImg(el, arr);
  322. },
  323. globalComponentSize: () => {
  324. return globalComponentSize();
  325. },
  326. deepClone: (obj: EmptyObjectType) => {
  327. return deepClone(obj);
  328. },
  329. isMobile: () => {
  330. return isMobile();
  331. },
  332. handleEmpty: (list: EmptyArrayType) => {
  333. return handleEmpty(list);
  334. },
  335. handleOpenLink: (val: RouteItem) => {
  336. handleOpenLink(val);
  337. },
  338. encryption: (src: string) => {
  339. return encryption(src);
  340. },
  341. decryption: (src: string) => {
  342. return decryption(src);
  343. },
  344. base64Encrypt: (data: any) => {
  345. return base64Encrypt(data);
  346. },
  347. downBlobFile: (url: any, query: any, fileName: string) => {
  348. return downBlobFile(url, query, fileName);
  349. },
  350. toUnderline: (str: string) => {
  351. return toUnderline(str);
  352. },
  353. openWindow: (url: string, title: string, w: number, h: number) => {
  354. return openWindow(url, title, w, h);
  355. },
  356. getQueryString: (url: string, paraName: string) => {
  357. return getQueryString(url, paraName);
  358. },
  359. adaptationUrl: (url?: string) => {
  360. return adaptationUrl(url);
  361. },
  362. resolveAllEunuchNodeId: (json: any[], idArr: any[], temp: any[] = []) => {
  363. return resolveAllEunuchNodeId(json, idArr, temp);
  364. },
  365. getNonDuplicateID: () => {
  366. return getNonDuplicateID();
  367. },
  368. addUnit: (value: string | number, unit = 'px') => {
  369. return addUnit(value, unit);
  370. },
  371. };
  372. export function getQueryString(url: string, paraName: string) {
  373. const arrObj = url.split('?');
  374. if (arrObj.length > 1) {
  375. const arrPara = arrObj[1].split('&');
  376. let arr;
  377. for (let i = 0; i < arrPara.length; i++) {
  378. arr = arrPara[i].split('=');
  379. // eslint-disable-next-line eqeqeq
  380. if (arr != null && arr[0] == paraName) {
  381. return arr[1];
  382. }
  383. }
  384. return '';
  385. } else {
  386. return '';
  387. }
  388. }
  389. /**
  390. * 列表结构转树结构
  391. * @param data
  392. * @param id
  393. * @param parentId
  394. * @param children
  395. * @param rootId
  396. * @returns {*}
  397. */
  398. export function handleTree(data: any, id: any, parentId: any, children: any, rootId: any) {
  399. id = id || 'id';
  400. parentId = parentId || 'parentId';
  401. children = children || 'children';
  402. rootId =
  403. rootId ||
  404. Math.min.apply(
  405. Math,
  406. data.map((item: any) => {
  407. return item[parentId];
  408. })
  409. ) ||
  410. 0;
  411. //对源数据深度克隆
  412. const cloneData = JSON.parse(JSON.stringify(data));
  413. //循环所有项
  414. const treeData = cloneData.filter((father: any) => {
  415. const branchArr = cloneData.filter((child: any) => {
  416. //返回每一项的子级数组
  417. return father[id] === child[parentId];
  418. });
  419. branchArr.length > 0 ? (father[children] = branchArr) : '';
  420. //返回第一层
  421. return father[parentId] === rootId;
  422. });
  423. return treeData !== '' ? treeData : data;
  424. }
  425. /**
  426. * 解析所有太监节点ID
  427. * @returns
  428. */
  429. const resolveAllEunuchNodeId = (json: any[], idArr: any[], temp: any[] = []) => {
  430. for (const item of json) {
  431. if (item.children && item.children.length !== 0) {
  432. resolveAllEunuchNodeId(item.children, idArr, temp);
  433. } else {
  434. temp.push(...idArr.filter((id) => id === item.id));
  435. }
  436. }
  437. return temp;
  438. };
  439. /**
  440. *
  441. * @param str 驼峰转下划线
  442. * @returns 下划线
  443. */
  444. export function toUnderline(str: string) {
  445. return str.replace(/([A-Z])/g, '_$1').toLowerCase();
  446. }
  447. /**
  448. * 自动适配不同的后端架构
  449. * 1. 例如 /act/oa/task ,在微服务架构保持不变,在单体架构编程 /admin/oa/task
  450. * 2. 特殊 /gen/xxx ,在微服务架构、单体架构编程 都需保持不变
  451. *
  452. * @param originUrl 原始路径
  453. */
  454. const adaptationUrl = (originUrl?: string) => {
  455. // 微服务架构 不做路径转换,为空不做路径转换
  456. const isMicro = import.meta.env.VITE_IS_MICRO;
  457. if (validateNull(isMicro) || isMicro === 'true') {
  458. return originUrl;
  459. }
  460. // 验证码服务
  461. if (originUrl?.startsWith('/code/')) {
  462. return `/admin${originUrl}`;
  463. }
  464. // 如果是代码生成服务,不做路径转换
  465. if (originUrl?.startsWith('/gen')) {
  466. return originUrl;
  467. }
  468. // 转为 /admin 路由前缀的请求
  469. return `/admin/${originUrl?.split('/').splice(2).join('/')}`;
  470. };
  471. /**
  472. * @description 获取不重复的id
  473. * @param length { Number } id的长度
  474. * @return { String } id
  475. */
  476. const getNonDuplicateID = (length = 8) => {
  477. let idStr = Date.now().toString(36);
  478. idStr += Math.random().toString(36).substring(3, length);
  479. return idStr;
  480. };
  481. /**
  482. * @description 添加单位
  483. * @param {String | Number} value 值 100
  484. * @param {String} unit 单位 px em rem
  485. */
  486. const addUnit = (value: string | number, unit = 'px') => {
  487. return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
  488. };
  489. // 统一批量导出
  490. export default other;