util.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import lodashCloneDeep from 'lodash-es/cloneDeep'
  2. /**
  3. * @function 深拷贝
  4. * @param value {*}
  5. * @return {*}
  6. */
  7. export const cloneDeep = (value) => {
  8. return lodashCloneDeep(value)
  9. }
  10. /**
  11. * @function 2个对象值进行比较
  12. * @param objectA
  13. * @param objectB
  14. * @return {boolean}
  15. */
  16. export const objectEqual = (objectA = {}, objectB = {}) => {
  17. if (objectA === objectB) return true
  18. const keysA = Object.keys(objectA)
  19. const keysB = Object.keys(objectB)
  20. if (keysA.length !== keysB.length) return false
  21. const keysLength = keysA.length
  22. for (let i = 0; i < keysLength; i++) {
  23. const keyA = keysA[i]
  24. if (!keysB.includes(keyA)) return false
  25. const valueA = objectA[keyA]
  26. const valueB = objectB[keyA]
  27. if (typeof valueA === 'object' && typeof valueB === 'object') {
  28. if (!objectEqual(valueA, valueB)) return false
  29. } else {
  30. if (valueA !== valueB) return false
  31. }
  32. }
  33. // 排除所有不想等的情况
  34. return true
  35. }
  36. /**
  37. * 节流
  38. * @param fn {Function} 方法
  39. * @param delay {Number} 延迟执行时间
  40. * @return {function(): void}
  41. */
  42. export function throttle (fn, delay) {
  43. let pre = 0
  44. return function () {
  45. const args = arguments
  46. const now = Date.now()
  47. if (now - pre > delay) {
  48. pre = now
  49. fn.apply(this, args)
  50. }
  51. }
  52. }
  53. /**
  54. *
  55. * @param fn
  56. * @param wait
  57. * @param immediate
  58. * @return {function(): *}
  59. */
  60. export const debounce = (fn, wait, immediate) => {
  61. let timeout, result
  62. const debounced = function () {
  63. const context = this
  64. const args = arguments
  65. if (timeout) clearTimeout(timeout)
  66. if (immediate) {
  67. // 如果已经执行过,不再执行
  68. const callNow = !timeout
  69. timeout = setTimeout(() => {
  70. timeout = null
  71. }, wait)
  72. if (callNow) result = fn.apply(context, args)
  73. } else {
  74. timeout = setTimeout(() => {
  75. fn.apply(context, args)
  76. }, wait)
  77. }
  78. return result
  79. }
  80. debounced.cancel = () => {
  81. clearTimeout(timeout)
  82. timeout = null
  83. }
  84. return debounced
  85. }
  86. /**
  87. * 大写第一位字符串
  88. * @param str {String} 字符串
  89. * @return {string}
  90. */
  91. export const toUpperFirstCase = (str) => {
  92. const first = str.charAt(0)
  93. const upperFirst = first.toUpperCase()
  94. return upperFirst + str.slice(1)
  95. }
  96. /**
  97. * 平铺数组转换为树状结构
  98. * @param list {Array} 数组列表
  99. * @param parentKey {String} 父节点名称
  100. * @param root {Number} 根节点值
  101. * @return {boolean}
  102. */
  103. export const toTreeData = (list, parentKey = 'parentId', root = 0) => {
  104. const cloneData = cloneDeep(list)
  105. const tree = cloneData.filter((father) => {
  106. const branchArr = cloneData.filter((child) => {
  107. return father.id === child[parentKey]
  108. })
  109. if (branchArr.length > 0) {
  110. father.children = branchArr
  111. }
  112. return father[parentKey] === root
  113. })
  114. tree.forEach(row => {
  115. row[parentKey] = ''
  116. })
  117. return tree
  118. }
  119. /**
  120. * 判断值是否为undefined或null
  121. * @param value {*} 待判断值
  122. * @return {boolean}
  123. */
  124. export const isEmpty = (value) => {
  125. return value === undefined || value === null
  126. }
  127. /**
  128. * 判断值是否为undefined或null或空字符串
  129. * @param value {*} 待判断值
  130. * @return {boolean}
  131. */
  132. export const isInputEmpty = (value) => {
  133. return value === undefined || value === null || value === ''
  134. }
  135. /**
  136. * 判断值是否不为undefined或null
  137. * @param value {*} 待判断值
  138. * @return {boolean}
  139. */
  140. export const isNotEmpty = (value) => {
  141. return !isEmpty(value)
  142. }
  143. /**
  144. * 是否为没有空对象
  145. * @param value {Object} 待判断对象
  146. * @return {boolean}
  147. */
  148. export const isEmptyObject = (value) => {
  149. return Object.keys(value).length === 0
  150. }
  151. /**
  152. * 判断值是否为数组
  153. * @param value {*}
  154. * @return {boolean}
  155. */
  156. export const isArray = (value) => {
  157. return Object.prototype.toString.call(value) === '[object Array]'
  158. }
  159. /**
  160. * 判断是否对象
  161. * @param value
  162. * @return {boolean}
  163. */
  164. export const isObject = (value) => {
  165. return Object.prototype.toString.call(value) === '[object Object]'
  166. }
  167. /**
  168. * 判断是否为字符串
  169. * @param value
  170. * @return {boolean}
  171. */
  172. export const isString = (value) => {
  173. return Object.prototype.toString.call(value) === '[object String]'
  174. }
  175. /**
  176. * 判断是否为数字
  177. * @param value
  178. * @return {boolean}
  179. */
  180. export const isNumber = (value) => {
  181. return Object.prototype.toString.call(value) === '[object Number]'
  182. }
  183. /**
  184. * 判断值是否为JSON String
  185. * @param value
  186. * @return {boolean}
  187. */
  188. export const isJson = (value) => {
  189. try {
  190. const obj = JSON.parse(value)
  191. return !!(typeof obj === 'object' && obj)
  192. } catch (e) {
  193. return false
  194. }
  195. }
  196. /**
  197. * 下载文件
  198. * @param href {String} base64地址或远程地址
  199. * @param filename {String} 文件名
  200. */
  201. export const downloadFile = (href, filename) => {
  202. if (href && filename) {
  203. const a = document.createElement('a')
  204. a.download = filename // 指定下载的文件名
  205. a.href = href // URL对象
  206. a.click() // 模拟点击
  207. URL.revokeObjectURL(a.href) // 释放URL 对象
  208. }
  209. }
  210. /**
  211. * 删除一条数据后返回下一条数据
  212. * @param itemList 列表
  213. * @param index 待删除数据
  214. * @return {{}}
  215. */
  216. export const getNextItem = (itemList, index) => {
  217. let result = {}
  218. if (index === itemList.length - 1) {
  219. if (index !== 0) {
  220. result = itemList[index - 1]
  221. }
  222. } else {
  223. result = itemList[index + 1]
  224. }
  225. return result
  226. }
  227. /**
  228. * @description: 耗时格式化
  229. * @param {*} time 毫秒
  230. * @return {*} timeStr eg: 22时22分22秒
  231. */
  232. export const durationTimeFormat = (time) => {
  233. if (!time && typeof time !== 'number') return ''
  234. const result = []
  235. const seconds = Math.ceil(time / 1000)
  236. const minutes = Math.floor(seconds / 60)
  237. const hours = Math.floor(minutes / 60)
  238. const days = Math.floor(hours / 24)
  239. const months = Math.floor(days / 30)
  240. const years = Math.floor(months / 12)
  241. if (years) {
  242. result.push(years + '年')
  243. }
  244. if (months % 12) {
  245. result.push(months % 12 + '月')
  246. }
  247. if (days % 365 % 30) {
  248. result.push(days % 365 % 30 + '天')
  249. }
  250. if (hours % 24) {
  251. result.push(hours % 24 + '小时')
  252. }
  253. if (minutes % 60) {
  254. result.push(minutes % 60 + '分')
  255. }
  256. if (seconds % 60) {
  257. result.push(seconds % 60 + '秒')
  258. }
  259. return result.join('')
  260. }
  261. /**
  262. * 根据url获取query中key对应的值
  263. * @param key
  264. * @param url
  265. * @return {string|null}
  266. */
  267. export const getQueryString = (key, url) => {
  268. if (isEmpty(url)) url = window.location.href
  269. const reg = new RegExp('(^|&|\\?)' + key + '=([^&]*)(&|$|#)', 'i')
  270. const r = url.match(reg)
  271. if (r != null) return decodeURIComponent(r[2])
  272. return null
  273. }
  274. /**
  275. * 拼接url和query的值
  276. * @param url String
  277. * @param query Object
  278. * @return {string}
  279. */
  280. export const setUrlQuery = (url, query) => {
  281. if (!url) return ''
  282. if (query) {
  283. const queryArray = []
  284. // eslint-disable-next-line
  285. for (const key in query) {
  286. if (Object.prototype.hasOwnProperty.call(query, key)) {
  287. queryArray.push(`${key}=${query[key]}`)
  288. }
  289. }
  290. if (url.includes('?')) {
  291. url = `${url}&${queryArray.join('&')}`
  292. } else {
  293. url = `${url}?${queryArray.join('&')}`
  294. }
  295. }
  296. return url
  297. }
  298. export const getLabelByValue = (value, options, optionProps) => {
  299. return options.find(option => option[optionProps.value] === value)?.[optionProps.label]
  300. }
  301. export const getFieldValue = (target, propertyName) => {
  302. if (isNotEmpty(propertyName)) {
  303. const keys = propertyName.split('.')
  304. for (let i = 0; i < keys.length; i++) {
  305. if (i === keys.length - 1 && isNotEmpty(target[keys[i]])) {
  306. return target[keys[i]]
  307. } else if (isObject(target[keys[i]])) {
  308. target = target[keys[i]]
  309. } else if (isArray(target[keys[i]])) {
  310. if (isNaN(Number(keys[i]))) target = target[keys[i]]
  311. } else {
  312. return undefined
  313. }
  314. }
  315. } else {
  316. return undefined
  317. }
  318. }
  319. /**
  320. * 向对象添加一个property
  321. * @param target {Object} 目标对象
  322. * @param propertyName {String} 属性名
  323. * @param value
  324. */
  325. export const setFieldValue = (target, propertyName, value, hasArray = false) => {
  326. if (isNotEmpty(propertyName)) {
  327. const keys = propertyName.split('.')
  328. const len = keys.length - 1
  329. keys.reduce((cur, key, index) => {
  330. if (index < len) {
  331. if (!cur[key]) {
  332. // 判断下一个key是否为数字
  333. if (hasArray && !isNaN(Number(keys[index + 1]))) {
  334. cur[key] = []
  335. } else {
  336. cur[key] = {}
  337. }
  338. }
  339. }
  340. if (index === len) {
  341. cur[key] = value
  342. }
  343. return cur[key]
  344. }, target)
  345. }
  346. }
  347. /**
  348. * 判断值是否为Map类型
  349. * @param value
  350. * @return {boolean}
  351. */
  352. const isMap = (value) => {
  353. return value instanceof Map
  354. }
  355. /**
  356. * 根据key获取mapping中的value
  357. * @param key
  358. * @param mapping
  359. * @return {*}
  360. */
  361. export const getValueByKey = (key, mapping) => {
  362. if (isMap(mapping)) {
  363. return mapping.get(key)
  364. } else {
  365. return mapping[key]
  366. }
  367. }
  368. /**
  369. * 根据value获取mapping中的key
  370. * @param value
  371. * @param mapping
  372. * @return {string|*}
  373. */
  374. export const getKeyByValue = (value, mapping) => {
  375. if (isMap(mapping)) {
  376. // eslint-disable-next-line no-unused-vars
  377. for (const [key, mapValue] of mapping) {
  378. if (mapValue === value) {
  379. return key
  380. }
  381. }
  382. } else {
  383. // eslint-disable-next-line no-unused-vars
  384. for (const key in mapping) {
  385. if (mapping[key] === value) {
  386. return key
  387. }
  388. }
  389. }
  390. }
  391. /**
  392. * 值根据映射关系和方向进行转换
  393. * @param value {*} 待转换值
  394. * @param mapping {Object} 映射关系
  395. * @param type {String} 转换方向 form-转为值 to-转为键
  396. */
  397. export const getValueMapping = (value, mapping = {}, valueType) => {
  398. let result
  399. if (valueType === 'value') {
  400. // form 此时 value 为 value
  401. Object.keys(mapping).forEach(key => {
  402. const mapValue = mapping[key]
  403. if (mapValue === value) {
  404. result = key
  405. }
  406. })
  407. // 未找到映射关系返回原值
  408. } else {
  409. // form 此时 value 为 key
  410. const mapValue = mapping[value]
  411. if (isNotEmpty(mapValue)) {
  412. result = mapValue
  413. }
  414. }
  415. return result
  416. }
  417. export const depthFirstSearchTree = (tree, value, key, children = 'children', depth = 0) => {
  418. depth++
  419. if (!tree) return
  420. if (getFieldValue(tree, key) === value) {
  421. const { children, ...useObject } = tree
  422. return [useObject]
  423. }
  424. // 最深至搜索10层
  425. if (depth > 9) return
  426. if (!tree[children]) return
  427. const loop = tree[children].length
  428. for (let i = 0; i < loop; i++) {
  429. const result = depthFirstSearchTree(tree[children][i], value, key, children, depth)
  430. if (result) {
  431. const { children, ...useObject } = tree
  432. result.unshift(useObject)
  433. return result
  434. }
  435. }
  436. }
  437. export const getUsingConfig = (...args) => {
  438. for (let i = 0; i < args.length; i++) {
  439. const value = args[i]
  440. if (isNotEmpty(value)) {
  441. return value
  442. }
  443. }
  444. }
  445. export const getEquipmentType = () => {
  446. if ((navigator.userAgent.match(/(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i))) {
  447. return 'mobile'
  448. } else {
  449. return 'pc'
  450. }
  451. }
  452. /**
  453. * 数字添加千分位分隔符
  454. * @param {Number} number 数字
  455. * @param {String} separator 分隔符
  456. * @return {string}
  457. */
  458. export const addThousandSeparator = (number, separator = '') => {
  459. const arr = (number + '').split('.')
  460. arr[0] = arr[0].replace(/(\d)(?=(?:\d{3})+$)/g, `$1${separator}`)
  461. return arr.join('.')
  462. }
  463. export const getValueByTemplate = (template, object) => {
  464. // 不能完全匹配template的数据 直接返回原始值
  465. if (typeof template !== 'string') {
  466. return template
  467. }
  468. return template?.replace(/\${([^}]+)}/g, (_, key) => {
  469. const val = getFieldValue(object, key)
  470. return isNotEmpty(val) ? val : `\${${key}}`
  471. })
  472. }