util.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. // +---------------------------------------------------------------------
  2. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  3. // +---------------------------------------------------------------------
  4. // | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
  5. // +---------------------------------------------------------------------
  6. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  7. // +---------------------------------------------------------------------
  8. // | Author: CRMEB Team <admin@crmeb.com>
  9. // +---------------------------------------------------------------------
  10. import Cookies from 'js-cookie';
  11. // cookie保存的天数
  12. import { forEach, hasOneOf, objEqual } from '@/libs/tools';
  13. import { cloneDeep } from 'lodash';
  14. const title = '';
  15. // 设置setCookies;
  16. // setToken
  17. export const setCookies = (key, val, cookieExpires) => {
  18. Cookies.set(key, val, { expires: cookieExpires || 1 });
  19. };
  20. // 获取getCookies;
  21. // getToken
  22. export const getCookies = (key) => {
  23. return Cookies.get(key);
  24. };
  25. /**
  26. * 从cookie中移除指定的键值对
  27. * @param {string} key - 要移除的键名
  28. * @returns {boolean} - 移除成功返回true,否则返回false
  29. */
  30. export const removeCookies = (key) => {
  31. return Cookies.remove(key);
  32. };
  33. /**
  34. * 判断一个菜单项是否有子菜单
  35. * @param {object} item - 菜单项对象
  36. * @returns {boolean} - 有子菜单返回true,否则返回false
  37. */
  38. export const hasChild = (item) => {
  39. return item.children && item.children.length !== 0;
  40. };
  41. /**
  42. * 判断当前用户是否有权限访问该菜单项
  43. * @param {object} item - 菜单项对象
  44. * @param {array} access - 用户拥有的权限列表
  45. * @returns {boolean} - 有权限返回true,否则返回false
  46. */
  47. const showThisMenuEle = (item, access) => {
  48. // 判断菜单项是否设置了权限
  49. if (item.meta && item.meta.access && item.meta.access.length) {
  50. // 判断用户是否拥有该菜单项的权限
  51. if (hasOneOf(item.meta.access, access)) return true;
  52. else return false;
  53. } else return true;
  54. };
  55. /**
  56. * @param {Array} list 通过路由列表得到菜单列表
  57. * @returns {Array}
  58. */
  59. export const getMenuByRouter = (list, access) => {
  60. let res = [];
  61. forEach(list, (item) => {
  62. if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
  63. let obj = {
  64. icon: (item.meta && item.meta.icon) || '',
  65. name: item.name,
  66. meta: item.meta,
  67. };
  68. if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) {
  69. obj.children = getMenuByRouter(item.children, access);
  70. }
  71. if (item.meta && item.meta.href) obj.href = item.meta.href;
  72. if (showThisMenuEle(item, access)) res.push(obj);
  73. }
  74. });
  75. return res;
  76. };
  77. /**
  78. * @param {Array} routeMetched 当前路由metched
  79. * @returns {Array}
  80. */
  81. export const getBreadCrumbList = (route, homeRoute) => {
  82. let homeItem = {
  83. ...homeRoute,
  84. };
  85. let routeMetched = route.matched;
  86. if (routeMetched.some((item) => item.name === homeRoute.name)) return [homeItem];
  87. let res = routeMetched
  88. .filter((item) => {
  89. return item.meta === undefined || !item.meta.hideInBread;
  90. })
  91. .map((item) => {
  92. let meta = { ...item.meta };
  93. if (meta.title && typeof meta.title === 'function') {
  94. meta.__titleIsFunction__ = true;
  95. meta.title = meta.title(route);
  96. }
  97. let obj = {
  98. icon: (item.meta && item.meta.icon) || '',
  99. name: item.name,
  100. meta: meta,
  101. };
  102. return obj;
  103. });
  104. res = res.filter((item) => {
  105. return !item.meta.hideInMenu;
  106. });
  107. return [{ ...homeItem, to: homeRoute.path }, ...res];
  108. };
  109. /**
  110. * 获取处理后的路由标题
  111. * @param {Object} route - 路由对象
  112. * @returns {Object} - 处理后的路由对象
  113. */
  114. export const getRouteTitleHandled = (route) => {
  115. // 克隆路由对象和元数据对象
  116. let router = { ...route };
  117. let meta = { ...route.meta };
  118. let title = '';
  119. // 判断元数据对象中是否存在标题属性
  120. if (meta.title) {
  121. // 如果标题属性是函数类型,则调用该函数并将结果赋值给title
  122. if (typeof meta.title === 'function') {
  123. meta.__titleIsFunction__ = true;
  124. title = meta.title(router);
  125. } else title = meta.title;
  126. }
  127. // 将处理后的标题赋值给元数据对象中的title属性
  128. meta.title = title;
  129. // 将处理后的元数据对象赋值给路由对象中的meta属性
  130. router.meta = meta;
  131. // 返回处理后的路由对象
  132. return router;
  133. };
  134. /**
  135. * 显示路由标题
  136. * @param {Object} item - 当前路由对象
  137. * @param {Object} vm - Vue实例对象
  138. * @returns {string|undefined} - 返回路由标题字符串或undefined
  139. */
  140. export const showTitle = (item, vm) => {
  141. // 从路由元数据中获取标题和标题是否为函数的标志
  142. let { title, __titleIsFunction__ } = item.meta;
  143. // 如果没有标题则直接返回
  144. if (!title) return;
  145. // 如果标题不存在,则从路由对象中获取名称作为标题
  146. title = (item.meta && item.meta.title) || item.name;
  147. // 返回标题字符串
  148. return title;
  149. };
  150. /**
  151. * @description 本地存储和获取标签导航列表
  152. */
  153. export const setTagNavListInLocalstorage = (list) => {
  154. localStorage.setItem('tagNaveListJavaPlat', JSON.stringify(list));
  155. };
  156. /**
  157. * @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项
  158. */
  159. export const getTagNavListFromLocalstorage = () => {
  160. const list = localStorage.getItem('tagNaveListJavaPlat');
  161. return list ? JSON.parse(list) : [];
  162. };
  163. /**
  164. * @param {Array} routers 路由列表数组
  165. * @description 用于找到路由列表中name为home的对象
  166. */
  167. export const getHomeRoute = (routers, homeName = 'home') => {
  168. let i = -1;
  169. let len = routers.length;
  170. let homeRoute = {};
  171. while (++i < len) {
  172. let item = routers[i];
  173. if (item.children && item.children.length) {
  174. let res = getHomeRoute(item.children, homeName);
  175. if (res.name) return res;
  176. } else {
  177. if (item.name === homeName) homeRoute = item;
  178. }
  179. }
  180. return homeRoute;
  181. };
  182. /**
  183. * @param {*} list 现有标签导航列表
  184. * @param {*} newRoute 新添加的路由原信息对象
  185. * @description 如果该newRoute已经存在则不再添加
  186. */
  187. export const getNewTagList = (list, newRoute) => {
  188. const { name, path, meta } = newRoute;
  189. let newList = [...list];
  190. if (newList.findIndex((item) => item.path === path) >= 0) return newList;
  191. else newList.push({ name, path, meta });
  192. return newList;
  193. };
  194. /**
  195. * @param {*} access 用户权限数组,如 ['super_admin', 'admin']
  196. * @param {*} route 路由列表
  197. */
  198. const hasAccess = (access, route) => {
  199. if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access);
  200. else return true;
  201. };
  202. /**
  203. * 权鉴
  204. * @param {*} name 即将跳转的路由name
  205. * @param {*} access 用户权限数组
  206. * @param {*} routes 路由列表
  207. * @description 用户是否可跳转到该页
  208. */
  209. export const canTurnTo = (name, access, routes) => {
  210. const routePermissionJudge = (list) => {
  211. return list.some((item) => {
  212. if (item.children && item.children.length) {
  213. return routePermissionJudge(item.children);
  214. } else if (item.name === name) {
  215. return hasAccess(access, item);
  216. }
  217. });
  218. };
  219. return routePermissionJudge(routes);
  220. };
  221. /**
  222. * @param {String} url
  223. * @description 从URL中解析参数
  224. */
  225. export const getParams = (url) => {
  226. const keyValueArr = url.split('?')[1].split('&');
  227. let paramObj = {};
  228. keyValueArr.forEach((item) => {
  229. const keyValue = item.split('=');
  230. paramObj[keyValue[0]] = keyValue[1];
  231. });
  232. return paramObj;
  233. };
  234. /**
  235. * @param {Array} list 标签列表
  236. * @param {String} name 当前关闭的标签的name
  237. */
  238. export const getNextRoute = (list, route) => {
  239. let res = {};
  240. if (list.length === 2) {
  241. res = getHomeRoute(list);
  242. } else {
  243. const index = list.findIndex((item) => routeEqual(item, route));
  244. if (index === list.length - 1) res = list[list.length - 2];
  245. else res = list[index + 1];
  246. }
  247. return res;
  248. };
  249. /**
  250. * @param {Number} times 回调函数需要执行的次数
  251. * @param {Function} callback 回调函数
  252. */
  253. export const doCustomTimes = (times, callback) => {
  254. let i = -1;
  255. while (++i < times) {
  256. callback(i);
  257. }
  258. };
  259. /**
  260. * @param {Object} file 从上传组件得到的文件对象
  261. * @returns {Promise} resolve参数是解析后的二维数组
  262. * @description 从Csv文件中解析出表格,解析成二维数组
  263. */
  264. export const getArrayFromFile = (file) => {
  265. let nameSplit = file.name.split('.');
  266. let format = nameSplit[nameSplit.length - 1];
  267. return new Promise((resolve, reject) => {
  268. let reader = new FileReader();
  269. reader.readAsText(file); // 以文本格式读取
  270. let arr = [];
  271. reader.onload = function (evt) {
  272. let data = evt.target.result; // 读到的数据
  273. let pasteData = data.trim();
  274. arr = pasteData
  275. .split(/[\n\u0085\u2028\u2029]|\r\n?/g)
  276. .map((row) => {
  277. return row.split('\t');
  278. })
  279. .map((item) => {
  280. return item[0].split(',');
  281. });
  282. if (format === 'csv') resolve(arr);
  283. else reject(new Error('[Format Error]:你上传的不是Csv文件'));
  284. };
  285. });
  286. };
  287. /**
  288. * @param {Array} array 表格数据二维数组
  289. * @returns {Object} { columns, tableData }
  290. * @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在表格中展示数据
  291. */
  292. export const getTableDataFromArray = (array) => {
  293. let columns = [];
  294. let tableData = [];
  295. if (array.length > 1) {
  296. let titles = array.shift();
  297. columns = titles.map((item) => {
  298. return {
  299. title: item,
  300. key: item,
  301. };
  302. });
  303. tableData = array.map((item) => {
  304. let res = {};
  305. item.forEach((col, i) => {
  306. res[titles[i]] = col;
  307. });
  308. return res;
  309. });
  310. }
  311. return {
  312. columns,
  313. tableData,
  314. };
  315. };
  316. /**
  317. * 查找元素的上一个指定标签名的父节点
  318. * @param {Element} ele - 要查找的元素
  319. * @param {string} tag - 指定的标签名
  320. * @returns {Element|undefined} - 返回找到的父节点或 undefined
  321. */
  322. export const findNodeUpper = (ele, tag) => {
  323. // 如果元素存在父节点
  324. if (ele.parentNode) {
  325. // 如果父节点的标签名与指定标签名相同
  326. if (ele.parentNode.tagName === tag.toUpperCase()) {
  327. // 返回父节点
  328. return ele.parentNode;
  329. } else {
  330. // 递归查找父节点的父节点
  331. return findNodeUpper(ele.parentNode, tag);
  332. }
  333. }
  334. };
  335. /**
  336. * 根据类名查找元素的上级节点
  337. * @param {HTMLElement} ele - 目标元素
  338. * @param {string[]} classes - 目标类名数组
  339. * @returns {HTMLElement|null} - 返回符合条件的上级节点,若不存在则返回 null
  340. */
  341. export const findNodeUpperByClasses = (ele, classes) => {
  342. // 获取目标元素的父节点
  343. let parentNode = ele.parentNode;
  344. if (parentNode) {
  345. // 获取父节点的类名列表
  346. let classList = parentNode.classList;
  347. if (
  348. // 判断父节点是否存在目标类名
  349. classList &&
  350. classes.every((className) => classList.contains(className))
  351. ) {
  352. // 返回符合条件的父节点
  353. return parentNode;
  354. } else {
  355. // 递归查找父节点的父节点
  356. return findNodeUpperByClasses(parentNode, classes);
  357. }
  358. }
  359. };
  360. /**
  361. * 从当前元素向下查找指定标签名的子元素
  362. * @param {HTMLElement} ele - 当前元素
  363. * @param {string} tag - 目标标签名
  364. * @returns {HTMLElement|undefined} - 返回找到的子元素或 undefined
  365. */
  366. export const findNodeDownward = (ele, tag) => {
  367. // 将目标标签名转换为大写字母
  368. const tagName = tag.toUpperCase();
  369. // 如果当前元素存在子节点
  370. if (ele.childNodes.length) {
  371. let i = -1;
  372. let len = ele.childNodes.length;
  373. // 遍历子节点
  374. while (++i < len) {
  375. let child = ele.childNodes[i];
  376. // 如果找到目标标签名的子元素,则返回该子元素
  377. if (child.tagName === tagName) return child;
  378. // 否则递归查找子元素的子元素
  379. else return findNodeDownward(child, tag);
  380. }
  381. }
  382. };
  383. /**
  384. * 根据用户权限判断是否可以查看某个页面或操作
  385. * @param {Array} access - 当前用户的权限数组
  386. * @param {Array} canViewAccess - 可以查看的权限数组
  387. * @returns {Boolean} - 是否可以查看
  388. */
  389. export const showByAccess = (access, canViewAccess) => {
  390. // 调用 hasOneOf 函数判断当前用户是否有查看权限
  391. return hasOneOf(canViewAccess, access);
  392. };
  393. /**
  394. * @description 根据name/params/query判断两个路由对象是否相等
  395. * @param {*} route1 路由对象
  396. * @param {*} route2 路由对象
  397. */
  398. export const routeEqual = (route1, route2) => {
  399. const params1 = route1.params || {};
  400. const params2 = route2.params || {};
  401. const query1 = route1.query || {};
  402. const query2 = route2.query || {};
  403. return route1.name === route2.name && objEqual(params1, params2) && objEqual(query1, query2);
  404. };
  405. /**
  406. * 判断打开的标签列表里是否已存在这个新添加的路由对象
  407. */
  408. export const routeHasExist = (tagNavList, routeItem) => {
  409. let len = tagNavList.length;
  410. let res = false;
  411. doCustomTimes(len, (index) => {
  412. if (routeEqual(tagNavList[index], routeItem)) res = true;
  413. });
  414. return res;
  415. };
  416. export const localSave = (key, value) => {
  417. localStorage.setItem(key, value);
  418. };
  419. export const localRead = (key) => {
  420. return localStorage.getItem(key) || '';
  421. };
  422. // scrollTop animation
  423. export const scrollTop = (el, from = 0, to, duration = 500, endCallback) => {
  424. if (!window.requestAnimationFrame) {
  425. window.requestAnimationFrame =
  426. window.webkitRequestAnimationFrame ||
  427. window.mozRequestAnimationFrame ||
  428. window.msRequestAnimationFrame ||
  429. function (callback) {
  430. return window.setTimeout(callback, 1000 / 60);
  431. };
  432. }
  433. const difference = Math.abs(from - to);
  434. const step = Math.ceil((difference / duration) * 50);
  435. const scroll = (start, end, step) => {
  436. if (start === end) {
  437. endCallback && endCallback();
  438. return;
  439. }
  440. let d = start + step > end ? end : start + step;
  441. if (start > end) {
  442. d = start - step < end ? end : start - step;
  443. }
  444. if (el === window) {
  445. window.scrollTo(d, d);
  446. } else {
  447. el.scrollTop = d;
  448. }
  449. window.requestAnimationFrame(() => scroll(d, end, step));
  450. };
  451. scroll(from, to, step);
  452. };
  453. /**
  454. * @description 根据当前跳转的路由设置显示在浏览器标签的title
  455. * @param {Object} routeItem 路由对象
  456. * @param {Object} vm Vue实例
  457. */
  458. export const setTitle = (routeItem, vm) => {
  459. let winTitle = localStorage.getItem('ADMIN_TITLE') || title;
  460. const handledRoute = getRouteTitleHandled(routeItem);
  461. const pageTitle = showTitle(handledRoute, vm);
  462. const resTitle = pageTitle ? `${winTitle} - ${pageTitle}` : winTitle;
  463. window.document.title = resTitle;
  464. };
  465. export const R = (menuList, newOpenMenus) => {
  466. menuList.forEach((item) => {
  467. let newMenu = {};
  468. for (let i in item) {
  469. if (i !== 'children') newMenu[i] = cloneDeep(item[i]);
  470. }
  471. newOpenMenus.push(newMenu);
  472. item.children && R(item.children, newOpenMenus);
  473. });
  474. return newOpenMenus;
  475. };
  476. /**
  477. * 获取当前菜单打开状态
  478. * @param {Object} to - 当前路由对象
  479. * @param {Array} menuList - 菜单列表
  480. * @returns {Array} - 当前菜单打开状态数组
  481. */
  482. export function getMenuopen(to, menuList) {
  483. // 初始化所有菜单数组
  484. const allMenus = [];
  485. // 遍历菜单列表
  486. menuList.forEach((menu) => {
  487. // 转换菜单结构
  488. const menus = transMenu(menu, []);
  489. // 添加当前菜单到所有菜单数组
  490. allMenus.push({
  491. path: menu.path,
  492. openNames: [],
  493. });
  494. // 添加子菜单到所有菜单数组
  495. menus.forEach((item) => allMenus.push(item));
  496. });
  497. // 查找当前菜单
  498. const currentMenu = allMenus.find((item) => item.path === to.path);
  499. // 返回当前菜单打开状态数组
  500. return currentMenu ? currentMenu.openNames : [];
  501. }
  502. /**
  503. * 将菜单转换为可展开的形式
  504. * @param {Object} menu - 菜单对象
  505. * @param {Array} openNames - 已打开的菜单路径数组
  506. * @returns {Array} - 可展开的菜单数组
  507. */
  508. function transMenu(menu, openNames) {
  509. // 判断当前菜单是否有子菜单
  510. if (menu.children && menu.children.length) {
  511. // 将当前菜单路径添加到已打开的菜单路径数组中
  512. const itemOpenNames = openNames.concat([menu.path]);
  513. // 递归处理子菜单
  514. return menu.children.reduce((all, item) => {
  515. // 将当前子菜单的路径和已打开的菜单路径数组添加到结果数组中
  516. all.push({
  517. path: item.path,
  518. openNames: itemOpenNames,
  519. });
  520. // 递归处理子菜单的子菜单
  521. const foundChildren = transMenu(item, itemOpenNames);
  522. // 将子菜单的子菜单添加到结果数组中
  523. return all.concat(foundChildren);
  524. }, []);
  525. } else {
  526. // 如果当前菜单没有子菜单,则直接将当前菜单添加到结果数组中
  527. return [menu].map((item) => {
  528. return {
  529. path: item.path,
  530. openNames: openNames,
  531. };
  532. });
  533. }
  534. }
  535. export function wss(wsSocketUrl) {
  536. let ishttps = document.location.protocol == 'https:';
  537. if (ishttps) {
  538. return wsSocketUrl.replace('ws:', 'wss:');
  539. } else {
  540. return wsSocketUrl.replace('wss:', 'ws:');
  541. }
  542. }