compUtils.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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. const globSetting = useGlobSetting();
  6. const baseApiUrl = globSetting.domainUrl;
  7. /**
  8. * 获取文件服务访问路径
  9. * @param fileUrl 文件路径
  10. * @param prefix(默认http) 文件路径前缀 http/https
  11. */
  12. export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
  13. let result = fileUrl;
  14. try {
  15. if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
  16. //判断是否是数组格式
  17. const isArray = fileUrl.indexOf('[') != -1;
  18. if (!isArray) {
  19. const prefix = `${baseApiUrl}/sys/common/static/`;
  20. // 判断是否已包含前缀
  21. if (!fileUrl.startsWith(prefix)) {
  22. result = `${prefix}${fileUrl}`;
  23. }
  24. }
  25. }
  26. } catch (err) {}
  27. return result;
  28. };
  29. /**
  30. * 触发 window.resize
  31. */
  32. export function triggerWindowResizeEvent() {
  33. const event: any = document.createEvent('HTMLEvents');
  34. event.initEvent('resize', true, true);
  35. event.eventType = 'message';
  36. window.dispatchEvent(event);
  37. }
  38. /**
  39. * 获取随机数
  40. * @param length 数字位数
  41. */
  42. export const getRandom = (length = 1) => {
  43. return '-' + parseInt(String(Math.random() * 10000 + 1), length);
  44. };
  45. /**
  46. * 随机生成字符串
  47. * @param length 字符串的长度
  48. * @param chats 可选字符串区间(只会生成传入的字符串中的字符)
  49. * @return string 生成的字符串
  50. */
  51. export function randomString(length: number, chats?: string) {
  52. if (!length) length = 1;
  53. if (!chats) {
  54. // noinspection SpellCheckingInspection
  55. chats = '0123456789qwertyuioplkjhgfdsazxcvbnm';
  56. }
  57. let str = '';
  58. for (let i = 0; i < length; i++) {
  59. const num = random(0, chats.length - 1);
  60. str += chats[num];
  61. }
  62. return str;
  63. }
  64. /**
  65. * 将普通列表数据转化为tree结构
  66. * @param array tree数据
  67. * @param opt 配置参数
  68. * @param startPid 父节点
  69. */
  70. export const listToTree = (array, opt, startPid) => {
  71. const obj = {
  72. primaryKey: opt.primaryKey || 'key',
  73. parentKey: opt.parentKey || 'parentId',
  74. titleKey: opt.titleKey || 'title',
  75. startPid: opt.startPid || '',
  76. currentDept: opt.currentDept || 0,
  77. maxDept: opt.maxDept || 100,
  78. childKey: opt.childKey || 'children',
  79. };
  80. if (startPid) {
  81. obj.startPid = startPid;
  82. }
  83. return toTree(array, obj.startPid, obj.currentDept, obj);
  84. };
  85. /**
  86. * 递归构建tree
  87. * @param list
  88. * @param startPid
  89. * @param currentDept
  90. * @param opt
  91. * @returns {Array}
  92. */
  93. export const toTree = (array, startPid, currentDept, opt) => {
  94. if (opt.maxDept < currentDept) {
  95. return [];
  96. }
  97. let child = [];
  98. if (array && array.length > 0) {
  99. child = array
  100. .map((item) => {
  101. // 筛查符合条件的数据(主键 = startPid)
  102. if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) {
  103. // 满足条件则递归
  104. const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt);
  105. // 节点信息保存
  106. if (nextChild.length > 0) {
  107. item['isLeaf'] = false;
  108. item[opt.childKey] = nextChild;
  109. } else {
  110. item['isLeaf'] = true;
  111. }
  112. item['title'] = item[opt.titleKey];
  113. item['label'] = item[opt.titleKey];
  114. item['key'] = item[opt.primaryKey];
  115. item['value'] = item[opt.primaryKey];
  116. return item;
  117. }
  118. })
  119. .filter((item) => {
  120. return item !== undefined;
  121. });
  122. }
  123. return child;
  124. };
  125. /**
  126. * 表格底部合计工具方法
  127. * @param tableData 表格数据
  128. * @param fieldKeys 要计算合计的列字段
  129. */
  130. export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) {
  131. const totals: any = { _row: '合计', _index: '合计' };
  132. fieldKeys.forEach((key) => {
  133. totals[key] = tableData.reduce((prev, next) => {
  134. prev += next[key];
  135. return prev;
  136. }, 0);
  137. });
  138. return totals;
  139. }
  140. /**
  141. * 简单实现防抖方法
  142. *
  143. * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
  144. * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
  145. * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
  146. *
  147. * @param fn 要防抖的函数
  148. * @param delay 防抖的毫秒数
  149. * @returns {Function}
  150. */
  151. export function simpleDebounce(fn, delay = 100) {
  152. let timer: any | null = null;
  153. return function () {
  154. const args = arguments;
  155. if (timer) {
  156. clearTimeout(timer);
  157. }
  158. timer = setTimeout(() => {
  159. // @ts-ignore
  160. fn.apply(this, args);
  161. }, delay);
  162. };
  163. }
  164. /**
  165. * 日期格式化
  166. * @param date 日期
  167. * @param block 格式化字符串
  168. */
  169. export function dateFormat(date, block) {
  170. if (!date) {
  171. return '';
  172. }
  173. let format = block || 'yyyy-MM-dd';
  174. date = new Date(date);
  175. const map = {
  176. M: date.getMonth() + 1, // 月份
  177. d: date.getDate(), // 日
  178. h: date.getHours(), // 小时
  179. m: date.getMinutes(), // 分
  180. s: date.getSeconds(), // 秒
  181. q: Math.floor((date.getMonth() + 3) / 3), // 季度
  182. S: date.getMilliseconds(), // 毫秒
  183. };
  184. format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
  185. let v = map[t];
  186. if (v !== undefined) {
  187. if (all.length > 1) {
  188. v = `0${v}`;
  189. v = v.substr(v.length - 2);
  190. }
  191. return v;
  192. } else if (t === 'y') {
  193. return date
  194. .getFullYear()
  195. .toString()
  196. .substr(4 - all.length);
  197. }
  198. return all;
  199. });
  200. return format;
  201. }
  202. /**
  203. * 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari
  204. * 目前使用的地方:JVxeTable Span模式
  205. */
  206. export function getEventPath(event) {
  207. const target = event.target;
  208. const path = (event.composedPath && event.composedPath()) || event.path;
  209. if (path != null) {
  210. return path.indexOf(window) < 0 ? path.concat(window) : path;
  211. }
  212. if (target === window) {
  213. return [window];
  214. }
  215. const getParents = (node, memo) => {
  216. const parentNode = node.parentNode;
  217. if (!parentNode) {
  218. return memo;
  219. } else {
  220. return getParents(parentNode, memo.concat(parentNode));
  221. }
  222. };
  223. return [target].concat(getParents(target, []), window);
  224. }
  225. /**
  226. * 如果值不存在就 push 进数组,反之不处理
  227. * @param array 要操作的数据
  228. * @param value 要添加的值
  229. * @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值
  230. * @returns {boolean} 成功 push 返回 true,不处理返回 false
  231. */
  232. export function pushIfNotExist(array, value, key?) {
  233. for (const item of array) {
  234. if (key && item[key] === value[key]) {
  235. return false;
  236. } else if (item === value) {
  237. return false;
  238. }
  239. }
  240. array.push(value);
  241. return true;
  242. }
  243. /**
  244. * 过滤对象中为空的属性
  245. * @param obj
  246. * @returns {*}
  247. */
  248. export function filterObj(obj) {
  249. if (!(typeof obj == 'object')) {
  250. return;
  251. }
  252. for (const key in obj) {
  253. if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
  254. delete obj[key];
  255. }
  256. }
  257. return obj;
  258. }
  259. /**
  260. * 下划线转驼峰
  261. * @param string
  262. */
  263. export function underLine2CamelCase(string: string) {
  264. return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
  265. }
  266. /**
  267. * 查找树结构
  268. * @param treeList
  269. * @param fn 查找方法
  270. * @param childrenKey
  271. */
  272. export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') {
  273. for (let i = 0; i < treeList.length; i++) {
  274. const item = treeList[i];
  275. if (fn(item, i, treeList)) {
  276. return item;
  277. }
  278. const children = item[childrenKey];
  279. if (isArray(children)) {
  280. const findResult = findTree(children, fn, childrenKey);
  281. if (findResult) {
  282. return findResult;
  283. }
  284. }
  285. }
  286. return null;
  287. }
  288. /** 获取 mapFormSchema 方法 */
  289. export function bindMapFormSchema<T>(spanMap, spanTypeDef: T) {
  290. return function (s: FormSchema, spanType: T = spanTypeDef) {
  291. return merge(
  292. {
  293. disabledLabelWidth: true,
  294. } as FormSchema,
  295. spanMap[spanType],
  296. s
  297. );
  298. };
  299. }
  300. /**
  301. * 字符串是否为null或null字符串
  302. * @param str
  303. * @return {boolean}
  304. */
  305. export function stringIsNull(str) {
  306. // 两个 == 可以同时判断 null 和 undefined
  307. return str == null || str === 'null' || str === 'undefined';
  308. }
  309. /**
  310. * 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题
  311. * @param node
  312. */
  313. export function getAutoScrollContainer(node: HTMLElement) {
  314. let element: Nullable<HTMLElement> = node;
  315. while (element != null) {
  316. if (element.classList.contains('scrollbar__view')) {
  317. // 判断是否有滚动条
  318. if (element.clientHeight < element.scrollHeight) {
  319. // 有滚动条时,挂载到父级,解决滚动问题
  320. return node.parentElement;
  321. } else {
  322. // 无滚动条时,挂载到body上,解决下拉框遮盖问题
  323. return document.body;
  324. }
  325. } else {
  326. element = element.parentElement;
  327. }
  328. }
  329. // 不在弹窗内,走默认逻辑
  330. return node.parentElement;
  331. }
  332. /**
  333. * 判断子菜单是否全部隐藏
  334. * @param menuTreeItem
  335. */
  336. export function checkChildrenHidden(menuTreeItem) {
  337. //是否是聚合路由
  338. const alwaysShow = menuTreeItem.alwaysShow;
  339. if (alwaysShow) {
  340. return false;
  341. }
  342. if (!menuTreeItem.children) {
  343. return false;
  344. }
  345. return menuTreeItem.children?.find((item) => item.hideMenu == false) != null;
  346. }