compUtils.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. import { useGlobSetting } from '/@/hooks/setting';
  2. import { merge, random } from 'lodash-es';
  3. import { isArray } from '/@/utils/is';
  4. import { FormSchema } from '/@/components/Form';
  5. import { reactive } from 'vue';
  6. import { getTenantId, getToken } from '/@/utils/auth';
  7. import { useUserStoreWithOut } from '/@/store/modules/user';
  8. import { Modal } from 'ant-design-vue';
  9. import { defHttp } from '@/utils/http/axios';
  10. const globSetting = useGlobSetting();
  11. const baseApiUrl = globSetting.domainUrl;
  12. /**
  13. * 获取文件服务访问路径
  14. * @param fileUrl 文件路径
  15. * @param prefix(默认http) 文件路径前缀 http/https
  16. */
  17. export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
  18. let result = fileUrl;
  19. try {
  20. if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
  21. //判断是否是数组格式
  22. const isArray = fileUrl.indexOf('[') != -1;
  23. if (!isArray) {
  24. const prefix = `${baseApiUrl}/sys/common/static/`;
  25. // 判断是否已包含前缀
  26. if (!fileUrl.startsWith(prefix)) {
  27. result = `${prefix}${fileUrl}`;
  28. }
  29. }
  30. }
  31. } catch (err) {}
  32. return result;
  33. };
  34. /**
  35. * 触发 window.resize
  36. */
  37. export function triggerWindowResizeEvent() {
  38. const event: any = document.createEvent('HTMLEvents');
  39. event.initEvent('resize', true, true);
  40. event.eventType = 'message';
  41. window.dispatchEvent(event);
  42. }
  43. /**
  44. * 获取随机数
  45. * @param length 数字位数
  46. */
  47. export const getRandom = (length: number = 1) => {
  48. return '-' + parseInt(String(Math.random() * 10000 + 1), length);
  49. };
  50. /**
  51. * 随机生成字符串
  52. * @param length 字符串的长度
  53. * @param chats 可选字符串区间(只会生成传入的字符串中的字符)
  54. * @return string 生成的字符串
  55. */
  56. export function randomString(length: number, chats?: string) {
  57. if (!length) length = 1;
  58. if (!chats) {
  59. // noinspection SpellCheckingInspection
  60. chats = '0123456789qwertyuioplkjhgfdsazxcvbnm';
  61. }
  62. let str = '';
  63. for (let i = 0; i < length; i++) {
  64. const num = random(0, chats.length - 1);
  65. str += chats[num];
  66. }
  67. return str;
  68. }
  69. /**
  70. * 将普通列表数据转化为tree结构
  71. * @param array tree数据
  72. * @param opt 配置参数
  73. * @param startPid 父节点
  74. */
  75. export const listToTree = (array, opt, startPid) => {
  76. const obj = {
  77. primaryKey: opt.primaryKey || 'key',
  78. parentKey: opt.parentKey || 'parentId',
  79. titleKey: opt.titleKey || 'title',
  80. startPid: opt.startPid || '',
  81. currentDept: opt.currentDept || 0,
  82. maxDept: opt.maxDept || 100,
  83. childKey: opt.childKey || 'children',
  84. };
  85. if (startPid) {
  86. obj.startPid = startPid;
  87. }
  88. return toTree(array, obj.startPid, obj.currentDept, obj);
  89. };
  90. /**
  91. * 递归构建tree
  92. * @param list
  93. * @param startPid
  94. * @param currentDept
  95. * @param opt
  96. * @returns {Array}
  97. */
  98. export const toTree = (array, startPid, currentDept, opt) => {
  99. if (opt.maxDept < currentDept) {
  100. return [];
  101. }
  102. let child = [];
  103. if (array && array.length > 0) {
  104. child = array
  105. .map((item) => {
  106. // 筛查符合条件的数据(主键 = startPid)
  107. if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) {
  108. // 满足条件则递归
  109. const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt);
  110. // 节点信息保存
  111. if (nextChild.length > 0) {
  112. item['isLeaf'] = false;
  113. item[opt.childKey] = nextChild;
  114. } else {
  115. item['isLeaf'] = true;
  116. }
  117. item['title'] = item[opt.titleKey];
  118. item['label'] = item[opt.titleKey];
  119. item['key'] = item[opt.primaryKey];
  120. item['value'] = item[opt.primaryKey];
  121. return item;
  122. }
  123. })
  124. .filter((item) => {
  125. return item !== undefined;
  126. });
  127. }
  128. return child;
  129. };
  130. /**
  131. * 表格底部合计工具方法
  132. * @param tableData 表格数据
  133. * @param fieldKeys 要计算合计的列字段
  134. */
  135. export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) {
  136. const totals: any = { _row: '合计', _index: '合计' };
  137. fieldKeys.forEach((key) => {
  138. totals[key] = tableData.reduce((prev, next) => {
  139. prev += next[key];
  140. return prev;
  141. }, 0);
  142. });
  143. return totals;
  144. }
  145. /**
  146. * 简单实现防抖方法
  147. *
  148. * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
  149. * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
  150. * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
  151. *
  152. * @param fn 要防抖的函数
  153. * @param delay 防抖的毫秒数
  154. * @returns {Function}
  155. */
  156. export function simpleDebounce(fn, delay = 100) {
  157. let timer: any | null = null;
  158. return function () {
  159. const args = arguments;
  160. if (timer) {
  161. clearTimeout(timer);
  162. }
  163. timer = setTimeout(() => {
  164. // @ts-ignore
  165. fn.apply(this, args);
  166. }, delay);
  167. };
  168. }
  169. /**
  170. * 日期格式化
  171. * @param date 日期
  172. * @param block 格式化字符串
  173. */
  174. export function dateFormat(date, block) {
  175. if (!date) {
  176. return '';
  177. }
  178. let format = block || 'yyyy-MM-dd';
  179. date = new Date(date);
  180. const map = {
  181. M: date.getMonth() + 1, // 月份
  182. d: date.getDate(), // 日
  183. h: date.getHours(), // 小时
  184. m: date.getMinutes(), // 分
  185. s: date.getSeconds(), // 秒
  186. q: Math.floor((date.getMonth() + 3) / 3), // 季度
  187. S: date.getMilliseconds(), // 毫秒
  188. };
  189. format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
  190. let v = map[t];
  191. if (v !== undefined) {
  192. if (all.length > 1) {
  193. v = `0${v}`;
  194. v = v.substr(v.length - 2);
  195. }
  196. return v;
  197. } else if (t === 'y') {
  198. return date
  199. .getFullYear()
  200. .toString()
  201. .substr(4 - all.length);
  202. }
  203. return all;
  204. });
  205. return format;
  206. }
  207. /**
  208. * 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari
  209. * 目前使用的地方:JVxeTable Span模式
  210. */
  211. export function getEventPath(event) {
  212. const target = event.target;
  213. const path = (event.composedPath && event.composedPath()) || event.path;
  214. if (path != null) {
  215. return path.indexOf(window) < 0 ? path.concat(window) : path;
  216. }
  217. if (target === window) {
  218. return [window];
  219. }
  220. const getParents = (node, memo) => {
  221. const parentNode = node.parentNode;
  222. if (!parentNode) {
  223. return memo;
  224. } else {
  225. return getParents(parentNode, memo.concat(parentNode));
  226. }
  227. };
  228. return [target].concat(getParents(target, []), window);
  229. }
  230. /**
  231. * 如果值不存在就 push 进数组,反之不处理
  232. * @param array 要操作的数据
  233. * @param value 要添加的值
  234. * @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值
  235. * @returns {boolean} 成功 push 返回 true,不处理返回 false
  236. */
  237. export function pushIfNotExist(array, value, key?) {
  238. for (const item of array) {
  239. if (key && item[key] === value[key]) {
  240. return false;
  241. } else if (item === value) {
  242. return false;
  243. }
  244. }
  245. array.push(value);
  246. return true;
  247. }
  248. /**
  249. * 过滤对象中为空的属性
  250. * @param obj
  251. * @returns {*}
  252. */
  253. export function filterObj(obj) {
  254. if (!(typeof obj == 'object')) {
  255. return;
  256. }
  257. for (const key in obj) {
  258. if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
  259. delete obj[key];
  260. }
  261. }
  262. return obj;
  263. }
  264. /**
  265. * 下划线转驼峰
  266. * @param string
  267. */
  268. export function underLine2CamelCase(string: string) {
  269. return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
  270. }
  271. /**
  272. * 查找树结构
  273. * @param treeList
  274. * @param fn 查找方法
  275. * @param childrenKey
  276. */
  277. export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') {
  278. for (let i = 0; i < treeList.length; i++) {
  279. const item = treeList[i];
  280. if (fn(item, i, treeList)) {
  281. return item;
  282. }
  283. const children = item[childrenKey];
  284. if (isArray(children)) {
  285. const findResult = findTree(children, fn, childrenKey);
  286. if (findResult) {
  287. return findResult;
  288. }
  289. }
  290. }
  291. return null;
  292. }
  293. /** 获取 mapFormSchema 方法 */
  294. export function bindMapFormSchema<T>(spanMap, spanTypeDef: T) {
  295. return function (s: FormSchema, spanType: T = spanTypeDef) {
  296. return merge(
  297. {
  298. disabledLabelWidth: true,
  299. } as FormSchema,
  300. spanMap[spanType],
  301. s
  302. );
  303. };
  304. }
  305. /**
  306. * 字符串是否为null或null字符串
  307. * @param str
  308. * @return {boolean}
  309. */
  310. export function stringIsNull(str) {
  311. // 两个 == 可以同时判断 null 和 undefined
  312. return str == null || str === 'null' || str === 'undefined';
  313. }
  314. /**
  315. * 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题
  316. * @param node
  317. */
  318. export function getAutoScrollContainer(node: HTMLElement) {
  319. let element: Nullable<HTMLElement> = node;
  320. while (element != null) {
  321. if (element.classList.contains('scrollbar__view')) {
  322. // 判断是否有滚动条
  323. if (element.clientHeight < element.scrollHeight) {
  324. // 有滚动条时,挂载到父级,解决滚动问题
  325. return node.parentElement;
  326. } else {
  327. // 无滚动条时,挂载到body上,解决下拉框遮盖问题
  328. return document.body;
  329. }
  330. } else {
  331. element = element.parentElement;
  332. }
  333. }
  334. // 不在弹窗内,走默认逻辑
  335. return node.parentElement;
  336. }
  337. /**
  338. * 判断子菜单是否全部隐藏
  339. * @param menuTreeItem
  340. */
  341. export function checkChildrenHidden(menuTreeItem) {
  342. //是否是聚合路由
  343. const alwaysShow = menuTreeItem.alwaysShow;
  344. if (alwaysShow) {
  345. return false;
  346. }
  347. if (!menuTreeItem.children) {
  348. return false;
  349. }
  350. return menuTreeItem.children?.find((item) => item.hideMenu == false) != null;
  351. }
  352. /**
  353. * 计算文件大小
  354. * @param fileSize
  355. * @param unit
  356. * @return 返回大小及后缀
  357. */
  358. export function calculateFileSize(fileSize, unit?) {
  359. let unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  360. if (unit && unit.length > 0) {
  361. unitArr = unit;
  362. }
  363. let size = fileSize;
  364. let unitIndex = 0;
  365. while (size >= 1024 && unitIndex < unitArr.length - 1) {
  366. size /= 1024;
  367. unitIndex++;
  368. }
  369. //保留两位小数,四舍五入
  370. size = Math.round(size * 100) / 100;
  371. return size + unitArr[unitIndex];
  372. }
  373. /**
  374. * 获取上传header
  375. */
  376. export function getHeaders() {
  377. const tenantId = getTenantId();
  378. return reactive({
  379. 'X-Access-Token': getToken(),
  380. 'X-Tenant-Id': tenantId ? tenantId : '0',
  381. });
  382. }
  383. /** 根据表达式获取相应的用户信息 */
  384. export function getUserInfoByExpression(expression) {
  385. if (!expression) {
  386. return expression;
  387. }
  388. const userStore = useUserStoreWithOut();
  389. const userInfo = userStore.getUserInfo;
  390. if (userInfo) {
  391. switch (expression) {
  392. case 'sysUserId':
  393. return userInfo.id;
  394. // 当前登录用户登录账号
  395. case 'sysUserCode':
  396. case 'sys_user_code':
  397. return userInfo.username;
  398. // 当前登录用户真实名称
  399. case 'sysUserName':
  400. return userInfo.realname;
  401. // 当前登录用户部门编号
  402. case 'sysOrgCode':
  403. case 'sys_org_code':
  404. return userInfo.orgCode;
  405. }
  406. }
  407. return expression;
  408. }
  409. /**
  410. * 替换表达式(#{xxx})为用户信息
  411. * @param expression
  412. */
  413. export function replaceUserInfoByExpression(expression: string | any[]) {
  414. if (!expression) {
  415. return expression;
  416. }
  417. const isString = typeof expression === 'string';
  418. const isArray = Array.isArray(expression);
  419. if (!isString && !isArray) {
  420. return expression;
  421. }
  422. const reg = /#{(.*?)}/g;
  423. const replace = (str) => {
  424. if (typeof str !== 'string') {
  425. return str;
  426. }
  427. const result = str.match(reg);
  428. if (result && result.length > 0) {
  429. result.forEach((item) => {
  430. const userInfo = getUserInfoByExpression(item.substring(2, item.length - 1));
  431. str = str.replace(item, userInfo);
  432. });
  433. }
  434. return str;
  435. };
  436. // @ts-ignore
  437. return isString ? replace(expression) : expression.map(replace);
  438. }
  439. /**
  440. * 设置租户缓存,当租户退出的时候
  441. *
  442. * @param tenantId
  443. */
  444. export async function userExitChangeLoginTenantId(tenantId) {
  445. const userStore = useUserStoreWithOut();
  446. //step 1 获取用户租户
  447. const url = '/sys/tenant/getCurrentUserTenant';
  448. let currentTenantId = null;
  449. const data = await defHttp.get({ url });
  450. if (data && data.list) {
  451. const arr = data.list;
  452. if (arr.length > 0) {
  453. //step 2.判断当前id是否存在用户租户中
  454. const filterTenantId = arr.filter((item) => item.id == tenantId);
  455. //存在说明不是退出的不是当前租户,还用用来的租户即可
  456. if (filterTenantId && filterTenantId.length > 0) {
  457. currentTenantId = tenantId;
  458. } else {
  459. //不存在默认第一个
  460. currentTenantId = arr[0].id;
  461. }
  462. }
  463. }
  464. userStore.setTenant(currentTenantId);
  465. //切换租户后要刷新首页
  466. window.location.reload();
  467. }
  468. /**
  469. * 我的租户模块需要开启多租户提示
  470. *
  471. * @param title 标题
  472. */
  473. export function tenantSaasMessage(title) {
  474. const tenantId = getTenantId();
  475. if (!tenantId) {
  476. Modal.confirm({
  477. title: title,
  478. content: '此菜单需要在多租户模式下使用,否则数据会出现混乱',
  479. okText: '确认',
  480. okType: 'danger',
  481. // @ts-ignore
  482. cancelButtonProps: { style: { display: 'none' } },
  483. });
  484. }
  485. }