misp-ui-library-test 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/README.md +113 -0
  2. package/dist/theme-chalk/alert/index.scss +152 -0
  3. package/dist/theme-chalk/avatar/index.scss +51 -0
  4. package/dist/theme-chalk/backtop/index.scss +38 -0
  5. package/dist/theme-chalk/badge/index.scss +63 -0
  6. package/dist/theme-chalk/breadcrumb/index.scss +36 -0
  7. package/dist/theme-chalk/breadcrumb-item/index.scss +36 -0
  8. package/dist/theme-chalk/button/index.scss +157 -0
  9. package/dist/theme-chalk/card/index.scss +38 -0
  10. package/dist/theme-chalk/chart/index.scss +21 -0
  11. package/dist/theme-chalk/checkbox/index.scss +358 -0
  12. package/dist/theme-chalk/col/index.scss +131 -0
  13. package/dist/theme-chalk/container/index.scss +40 -0
  14. package/dist/theme-chalk/css/alert.css +484 -0
  15. package/dist/theme-chalk/css/avatar.css +409 -0
  16. package/dist/theme-chalk/css/backtop.css +401 -0
  17. package/dist/theme-chalk/css/badge.css +419 -0
  18. package/dist/theme-chalk/css/breadcrumb-item.css +395 -0
  19. package/dist/theme-chalk/css/breadcrumb.css +395 -0
  20. package/dist/theme-chalk/css/button.css +587 -0
  21. package/dist/theme-chalk/css/card.css +397 -0
  22. package/dist/theme-chalk/css/chart.css +386 -0
  23. package/dist/theme-chalk/css/checkbox.css +648 -0
  24. package/dist/theme-chalk/css/col.css +2111 -0
  25. package/dist/theme-chalk/css/container.css +404 -0
  26. package/dist/theme-chalk/css/dialog.css +505 -0
  27. package/dist/theme-chalk/css/divider.css +405 -0
  28. package/dist/theme-chalk/css/drawer.css +546 -0
  29. package/dist/theme-chalk/css/empty.css +394 -0
  30. package/dist/theme-chalk/css/form.css +561 -0
  31. package/dist/theme-chalk/css/icon.css +1534 -0
  32. package/dist/theme-chalk/css/image.css +400 -0
  33. package/dist/theme-chalk/css/input-number.css +424 -0
  34. package/dist/theme-chalk/css/input.css +721 -0
  35. package/dist/theme-chalk/css/link.css +424 -0
  36. package/dist/theme-chalk/css/loading.css +483 -0
  37. package/dist/theme-chalk/css/message-box.css +479 -0
  38. package/dist/theme-chalk/css/message.css +456 -0
  39. package/dist/theme-chalk/css/no-data.css +397 -0
  40. package/dist/theme-chalk/css/notification.css +431 -0
  41. package/dist/theme-chalk/css/page-header.css +396 -0
  42. package/dist/theme-chalk/css/pagination.css +565 -0
  43. package/dist/theme-chalk/css/popover.css +421 -0
  44. package/dist/theme-chalk/css/popper.css +454 -0
  45. package/dist/theme-chalk/css/progress.css +451 -0
  46. package/dist/theme-chalk/css/radio.css +626 -0
  47. package/dist/theme-chalk/css/rate.css +414 -0
  48. package/dist/theme-chalk/css/result.css +403 -0
  49. package/dist/theme-chalk/css/row.css +433 -0
  50. package/dist/theme-chalk/css/scrollbar.css +422 -0
  51. package/dist/theme-chalk/css/select.css +1897 -0
  52. package/dist/theme-chalk/css/skeleton-item.css +407 -0
  53. package/dist/theme-chalk/css/skeleton.css +390 -0
  54. package/dist/theme-chalk/css/slider.css +460 -0
  55. package/dist/theme-chalk/css/spinner.css +403 -0
  56. package/dist/theme-chalk/css/statistic.css +396 -0
  57. package/dist/theme-chalk/css/status-timeline-chart.css +388 -0
  58. package/dist/theme-chalk/css/step.css +496 -0
  59. package/dist/theme-chalk/css/steps.css +496 -0
  60. package/dist/theme-chalk/css/switch.css +507 -0
  61. package/dist/theme-chalk/css/tab-pane.css +457 -0
  62. package/dist/theme-chalk/css/table.css +461 -0
  63. package/dist/theme-chalk/css/tabs-navigation.css +925 -0
  64. package/dist/theme-chalk/css/tabs.css +457 -0
  65. package/dist/theme-chalk/css/tag.css +599 -0
  66. package/dist/theme-chalk/css/time-picker.css +683 -0
  67. package/dist/theme-chalk/css/timeline-item.css +459 -0
  68. package/dist/theme-chalk/css/timeline.css +459 -0
  69. package/dist/theme-chalk/css/tooltip.css +485 -0
  70. package/dist/theme-chalk/css/tree.css +473 -0
  71. package/dist/theme-chalk/css/upload.css +665 -0
  72. package/dist/theme-chalk/dialog/index.scss +168 -0
  73. package/dist/theme-chalk/divider/index.scss +46 -0
  74. package/dist/theme-chalk/drawer/index.scss +172 -0
  75. package/dist/theme-chalk/empty/index.scss +36 -0
  76. package/dist/theme-chalk/fonts/element-icons.ttf +0 -0
  77. package/dist/theme-chalk/fonts/element-icons.woff +0 -0
  78. package/dist/theme-chalk/form/index.scss +220 -0
  79. package/dist/theme-chalk/icon/index.scss +1171 -0
  80. package/dist/theme-chalk/image/index.scss +39 -0
  81. package/dist/theme-chalk/index.scss +127 -0
  82. package/dist/theme-chalk/input/index.scss +363 -0
  83. package/dist/theme-chalk/input-number/index.scss +71 -0
  84. package/dist/theme-chalk/link/index.scss +75 -0
  85. package/dist/theme-chalk/loading/index.scss +131 -0
  86. package/dist/theme-chalk/message/index.scss +100 -0
  87. package/dist/theme-chalk/message-box/index.scss +131 -0
  88. package/dist/theme-chalk/mixins.scss +203 -0
  89. package/dist/theme-chalk/no-data/index.scss +36 -0
  90. package/dist/theme-chalk/notification/index.scss +76 -0
  91. package/dist/theme-chalk/page-header/index.scss +36 -0
  92. package/dist/theme-chalk/pagination/index.scss +225 -0
  93. package/dist/theme-chalk/popover/index.scss +56 -0
  94. package/dist/theme-chalk/popper/index.scss +89 -0
  95. package/dist/theme-chalk/progress/index.scss +105 -0
  96. package/dist/theme-chalk/radio/index.scss +328 -0
  97. package/dist/theme-chalk/rate/index.scss +56 -0
  98. package/dist/theme-chalk/reset.scss +262 -0
  99. package/dist/theme-chalk/result/index.scss +46 -0
  100. package/dist/theme-chalk/row/index.scss +88 -0
  101. package/dist/theme-chalk/scrollbar/index.scss +67 -0
  102. package/dist/theme-chalk/select/index.scss +223 -0
  103. package/dist/theme-chalk/skeleton/index.scss +30 -0
  104. package/dist/theme-chalk/skeleton-item/index.scss +49 -0
  105. package/dist/theme-chalk/slider/index.scss +113 -0
  106. package/dist/theme-chalk/spinner/index.scss +40 -0
  107. package/dist/theme-chalk/statistic/index.scss +38 -0
  108. package/dist/theme-chalk/status-timeline-chart/index.scss +23 -0
  109. package/dist/theme-chalk/step/index.scss +160 -0
  110. package/dist/theme-chalk/steps/index.scss +160 -0
  111. package/dist/theme-chalk/switch/index.scss +153 -0
  112. package/dist/theme-chalk/tab-pane/index.scss +112 -0
  113. package/dist/theme-chalk/table/index.scss +110 -0
  114. package/dist/theme-chalk/tabs/index.scss +112 -0
  115. package/dist/theme-chalk/tabs-navigation/index.scss +631 -0
  116. package/dist/theme-chalk/tag/index.scss +138 -0
  117. package/dist/theme-chalk/themes/blue.scss +76 -0
  118. package/dist/theme-chalk/themes/dark.scss +99 -0
  119. package/dist/theme-chalk/themes/index.scss +260 -0
  120. package/dist/theme-chalk/themes/light.scss +99 -0
  121. package/dist/theme-chalk/time-picker/index.scss +332 -0
  122. package/dist/theme-chalk/timeline/index.scss +119 -0
  123. package/dist/theme-chalk/timeline-item/index.scss +119 -0
  124. package/dist/theme-chalk/tooltip/index.scss +122 -0
  125. package/dist/theme-chalk/tree/index.scss +125 -0
  126. package/dist/theme-chalk/upload/index.scss +348 -0
  127. package/dist/theme-chalk/variables.scss +93 -0
  128. package/dist/vue2-ui-library.cjs.temp.js.map +1 -0
  129. package/dist/vue2-ui-library.common.js +28 -0
  130. package/dist/vue2-ui-library.common.js.map +1 -0
  131. package/dist/vue2-ui-library.esm.js +29 -0
  132. package/dist/vue2-ui-library.umd.js +27 -0
  133. package/package.json +161 -0
  134. package/src/directives/clickoutside/index.js +111 -0
  135. package/src/directives/init/index.js +50 -0
  136. package/src/directives/resize/index.js +69 -0
  137. package/src/index.js +174 -0
  138. package/src/mixins/emitter.js +47 -0
  139. package/src/mixins/focus.js +42 -0
  140. package/src/mixins/locale.js +24 -0
  141. package/src/mixins/migrating.js +46 -0
  142. package/src/packages/alert/index.js +12 -0
  143. package/src/packages/alert/src/main.vue +98 -0
  144. package/src/packages/aside/index.js +12 -0
  145. package/src/packages/aside/src/main.vue +20 -0
  146. package/src/packages/auto-grid-layout/index.js +18 -0
  147. package/src/packages/auto-grid-layout/prop.js +92 -0
  148. package/src/packages/auto-grid-layout/src/components/GridItem.vue +759 -0
  149. package/src/packages/auto-grid-layout/src/components/GridLayout.vue +367 -0
  150. package/src/packages/auto-grid-layout/src/main.vue +287 -0
  151. package/src/packages/auto-grid-layout/src/utils/DOM.js +46 -0
  152. package/src/packages/auto-grid-layout/src/utils/interact.js +333 -0
  153. package/src/packages/auto-grid-layout/src/utils/responsiveUtils.js +75 -0
  154. package/src/packages/auto-grid-layout/src/utils/utils.js +339 -0
  155. package/src/packages/autocomplete/index.js +12 -0
  156. package/src/packages/autocomplete/src/main.vue +170 -0
  157. package/src/packages/avatar/index.js +12 -0
  158. package/src/packages/avatar/src/main.vue +87 -0
  159. package/src/packages/backtop/index.js +12 -0
  160. package/src/packages/backtop/src/main.vue +120 -0
  161. package/src/packages/badge/index.js +12 -0
  162. package/src/packages/badge/src/main.vue +53 -0
  163. package/src/packages/breadcrumb/index.js +12 -0
  164. package/src/packages/breadcrumb/src/main.vue +34 -0
  165. package/src/packages/breadcrumb-item/index.js +12 -0
  166. package/src/packages/breadcrumb-item/src/main.vue +41 -0
  167. package/src/packages/button/index.js +13 -0
  168. package/src/packages/button/src/main.vue +106 -0
  169. package/src/packages/calendar/index.js +12 -0
  170. package/src/packages/calendar/src/main.vue +173 -0
  171. package/src/packages/card/index.js +12 -0
  172. package/src/packages/card/src/main.vue +26 -0
  173. package/src/packages/carousel/index.js +12 -0
  174. package/src/packages/carousel/src/main.vue +186 -0
  175. package/src/packages/carousel-item/index.js +12 -0
  176. package/src/packages/carousel-item/src/main.vue +34 -0
  177. package/src/packages/cascader/index.js +12 -0
  178. package/src/packages/cascader/src/main.vue +232 -0
  179. package/src/packages/chart/index.js +7 -0
  180. package/src/packages/chart/src/axis-chart.js +700 -0
  181. package/src/packages/chart/src/main.vue +828 -0
  182. package/src/packages/chart/src/utils.js +21 -0
  183. package/src/packages/checkbox/index.js +44 -0
  184. package/src/packages/checkbox/src/main.vue +312 -0
  185. package/src/packages/col/index.js +12 -0
  186. package/src/packages/col/src/main.vue +85 -0
  187. package/src/packages/collapse/index.js +12 -0
  188. package/src/packages/collapse/src/main.vue +69 -0
  189. package/src/packages/collapse-item/index.js +12 -0
  190. package/src/packages/collapse-item/src/main.vue +75 -0
  191. package/src/packages/color-picker/index.js +12 -0
  192. package/src/packages/color-picker/src/main.vue +206 -0
  193. package/src/packages/container/index.js +12 -0
  194. package/src/packages/container/src/main.vue +33 -0
  195. package/src/packages/date-picker/index.js +12 -0
  196. package/src/packages/date-picker/src/main.vue +246 -0
  197. package/src/packages/descriptions/index.js +12 -0
  198. package/src/packages/descriptions/src/main.vue +89 -0
  199. package/src/packages/descriptions-item/index.js +12 -0
  200. package/src/packages/descriptions-item/src/main.vue +26 -0
  201. package/src/packages/dialog/index.js +12 -0
  202. package/src/packages/dialog/src/main.vue +336 -0
  203. package/src/packages/divider/index.js +12 -0
  204. package/src/packages/divider/src/main.vue +37 -0
  205. package/src/packages/drawer/index.js +7 -0
  206. package/src/packages/drawer/src/main.vue +310 -0
  207. package/src/packages/dropdown/index.js +12 -0
  208. package/src/packages/dropdown/src/main.vue +82 -0
  209. package/src/packages/dropdown-menu-item/index.js +12 -0
  210. package/src/packages/dropdown-menu-item/src/main.vue +29 -0
  211. package/src/packages/empty/index.js +12 -0
  212. package/src/packages/empty/src/img-empty.vue +12 -0
  213. package/src/packages/empty/src/main.vue +49 -0
  214. package/src/packages/footer/index.js +12 -0
  215. package/src/packages/footer/src/main.vue +20 -0
  216. package/src/packages/form/index.js +13 -0
  217. package/src/packages/form/src/form-field.vue +790 -0
  218. package/src/packages/form/src/form-item.vue +464 -0
  219. package/src/packages/form/src/label-wrap.vue +127 -0
  220. package/src/packages/form/src/main.vue +442 -0
  221. package/src/packages/form-item/index.js +11 -0
  222. package/src/packages/header/index.js +12 -0
  223. package/src/packages/header/src/main.vue +20 -0
  224. package/src/packages/icon/index.js +12 -0
  225. package/src/packages/icon/src/main.vue +48 -0
  226. package/src/packages/image/index.js +12 -0
  227. package/src/packages/image/src/main.vue +224 -0
  228. package/src/packages/input/index.js +13 -0
  229. package/src/packages/input/src/calcTextareaHeight.js +123 -0
  230. package/src/packages/input/src/main.vue +510 -0
  231. package/src/packages/input-number/index.js +12 -0
  232. package/src/packages/input-number/src/main.vue +121 -0
  233. package/src/packages/link/index.js +12 -0
  234. package/src/packages/link/src/main.vue +53 -0
  235. package/src/packages/loading/index.js +17 -0
  236. package/src/packages/loading/src/directive.js +54 -0
  237. package/src/packages/loading/src/main.vue +38 -0
  238. package/src/packages/loading/src/service.js +63 -0
  239. package/src/packages/main/index.js +12 -0
  240. package/src/packages/main/src/main.vue +12 -0
  241. package/src/packages/menu/index.js +12 -0
  242. package/src/packages/menu/src/main.vue +117 -0
  243. package/src/packages/menu-item/index.js +12 -0
  244. package/src/packages/menu-item/src/main.vue +61 -0
  245. package/src/packages/message/index.js +95 -0
  246. package/src/packages/message/src/main.vue +131 -0
  247. package/src/packages/message-box/index.js +75 -0
  248. package/src/packages/message-box/src/main.vue +207 -0
  249. package/src/packages/no-data/index.js +8 -0
  250. package/src/packages/no-data/src/main.vue +23 -0
  251. package/src/packages/notification/index.js +72 -0
  252. package/src/packages/notification/src/main.vue +139 -0
  253. package/src/packages/option/index.js +13 -0
  254. package/src/packages/page-header/index.js +12 -0
  255. package/src/packages/page-header/src/main.vue +27 -0
  256. package/src/packages/pagination/index.js +12 -0
  257. package/src/packages/pagination/src/main.vue +255 -0
  258. package/src/packages/popconfirm/index.js +12 -0
  259. package/src/packages/popconfirm/src/main.vue +89 -0
  260. package/src/packages/popover/index.js +12 -0
  261. package/src/packages/popover/src/main.vue +452 -0
  262. package/src/packages/progress/index.js +12 -0
  263. package/src/packages/progress/src/main.vue +236 -0
  264. package/src/packages/radio/index.js +44 -0
  265. package/src/packages/radio/src/main.vue +291 -0
  266. package/src/packages/rate/index.js +12 -0
  267. package/src/packages/rate/src/main.vue +129 -0
  268. package/src/packages/result/index.js +12 -0
  269. package/src/packages/result/src/main.vue +52 -0
  270. package/src/packages/row/index.js +12 -0
  271. package/src/packages/row/src/main.vue +57 -0
  272. package/src/packages/scrollbar/index.js +12 -0
  273. package/src/packages/scrollbar/src/bar.vue +116 -0
  274. package/src/packages/scrollbar/src/main.vue +124 -0
  275. package/src/packages/select/index.js +15 -0
  276. package/src/packages/select/src/clickoutside.js +20 -0
  277. package/src/packages/select/src/main.vue +1055 -0
  278. package/src/packages/select/src/navigation-mixin.js +49 -0
  279. package/src/packages/select/src/option.vue +249 -0
  280. package/src/packages/select/src/select-dropdown.vue +95 -0
  281. package/src/packages/select/src/utils.js +5 -0
  282. package/src/packages/skeleton/index.js +12 -0
  283. package/src/packages/skeleton/src/main.vue +76 -0
  284. package/src/packages/skeleton-item/index.js +12 -0
  285. package/src/packages/skeleton-item/src/img-placeholder.vue +13 -0
  286. package/src/packages/skeleton-item/src/main.vue +22 -0
  287. package/src/packages/slider/index.js +12 -0
  288. package/src/packages/slider/src/main.vue +176 -0
  289. package/src/packages/spinner/index.js +12 -0
  290. package/src/packages/spinner/src/main.vue +27 -0
  291. package/src/packages/statistic/index.js +12 -0
  292. package/src/packages/statistic/src/main.vue +100 -0
  293. package/src/packages/status-timeline-chart/index.js +7 -0
  294. package/src/packages/status-timeline-chart/src/constants.js +20 -0
  295. package/src/packages/status-timeline-chart/src/main.vue +499 -0
  296. package/src/packages/status-timeline-chart/src/option-builder.js +475 -0
  297. package/src/packages/step/index.js +12 -0
  298. package/src/packages/step/src/main.vue +183 -0
  299. package/src/packages/steps/index.js +12 -0
  300. package/src/packages/steps/src/main.vue +57 -0
  301. package/src/packages/submenu/index.js +12 -0
  302. package/src/packages/submenu/src/main.vue +68 -0
  303. package/src/packages/switch/index.js +12 -0
  304. package/src/packages/switch/src/main.vue +166 -0
  305. package/src/packages/tab-pane/index.js +12 -0
  306. package/src/packages/tab-pane/src/main.vue +74 -0
  307. package/src/packages/table/index.js +8 -0
  308. package/src/packages/table/src/components/PagePagination.vue +82 -0
  309. package/src/packages/table/src/main.vue +153 -0
  310. package/src/packages/tabs/index.js +12 -0
  311. package/src/packages/tabs/src/main.vue +167 -0
  312. package/src/packages/tabs-navigation/index.js +7 -0
  313. package/src/packages/tabs-navigation/src/main.vue +462 -0
  314. package/src/packages/tag/index.js +12 -0
  315. package/src/packages/tag/src/main.vue +194 -0
  316. package/src/packages/time-picker/index.js +17 -0
  317. package/src/packages/time-picker/src/date-utils.js +455 -0
  318. package/src/packages/time-picker/src/main.vue +321 -0
  319. package/src/packages/time-picker/src/pane/date-range.vue +193 -0
  320. package/src/packages/time-picker/src/pane/date-time-range.vue +362 -0
  321. package/src/packages/time-picker/src/pane/date-time.vue +362 -0
  322. package/src/packages/time-picker/src/pane/date.vue +441 -0
  323. package/src/packages/time-picker/src/pane/time.vue +260 -0
  324. package/src/packages/time-picker/src/time-input.vue +203 -0
  325. package/src/packages/time-picker/src/time-range-input.vue +170 -0
  326. package/src/packages/timeline/index.js +12 -0
  327. package/src/packages/timeline/src/main.vue +24 -0
  328. package/src/packages/timeline-item/index.js +12 -0
  329. package/src/packages/timeline-item/src/main.vue +78 -0
  330. package/src/packages/tooltip/index.js +12 -0
  331. package/src/packages/tooltip/src/main.js +367 -0
  332. package/src/packages/transfer/index.js +8 -0
  333. package/src/packages/transfer/src/main.vue +229 -0
  334. package/src/packages/transfer/src/transfer-panel.vue +245 -0
  335. package/src/packages/transitions/collapse-transition.js +75 -0
  336. package/src/packages/tree/index.js +12 -0
  337. package/src/packages/tree/src/main.vue +322 -0
  338. package/src/packages/tree/src/model/node.js +348 -0
  339. package/src/packages/tree/src/model/tree-store.js +354 -0
  340. package/src/packages/tree/src/model/util.js +10 -0
  341. package/src/packages/tree/src/tree-node.vue +262 -0
  342. package/src/packages/upload/index.js +9 -0
  343. package/src/packages/upload/src/main.vue +415 -0
  344. package/src/playground/mixins/element.js +98 -0
  345. package/src/playground/types/element.js +197 -0
  346. package/src/playground/types/layout.js +80 -0
  347. package/src/plugins/i18n.js +125 -0
  348. package/src/styles/fonts/element-icons.ttf +0 -0
  349. package/src/styles/fonts/element-icons.woff +0 -0
  350. package/src/styles/index.scss +138 -0
  351. package/src/styles/reset.scss +257 -0
  352. package/src/styles/themes/blue.scss +75 -0
  353. package/src/styles/themes/dark.scss +98 -0
  354. package/src/styles/themes/index.scss +34 -0
  355. package/src/styles/themes/light.scss +98 -0
  356. package/src/styles/themes/variables.scss +233 -0
  357. package/src/styles/variables.scss +146 -0
  358. package/src/utils/async-validator/index.js +659 -0
  359. package/src/utils/browser/index.js +39 -0
  360. package/src/utils/color.js +76 -0
  361. package/src/utils/dom/index.js +421 -0
  362. package/src/utils/dom/scrollbar-width.js +49 -0
  363. package/src/utils/draggable/core.js +379 -0
  364. package/src/utils/draggable/draggable.js +478 -0
  365. package/src/utils/draggable/index.js +61 -0
  366. package/src/utils/draggable/sortable.js +751 -0
  367. package/src/utils/lodash/index.js +2395 -0
  368. package/src/utils/moment/index.js +909 -0
  369. package/src/utils/playground/index.js +37 -0
  370. package/src/utils/popper/index.js +308 -0
  371. package/src/utils/popper/popper.js +1132 -0
  372. package/src/utils/popper/popup-manager.js +288 -0
  373. package/src/utils/popper/popup.js +344 -0
@@ -0,0 +1,2395 @@
1
+ /**
2
+ * @module @vue2-ui/lodash
3
+ * @description 基于 lodash 的自定义工具函数库(无外部依赖)
4
+ */
5
+
6
+ // ==================== 类型判断 ====================
7
+
8
+ /**
9
+ * 检查值是否为 null 或 undefined
10
+ * @param {*} value
11
+ * @returns {boolean}
12
+ * @example
13
+ * isNil(null) // true
14
+ * isNil(undefined) // true
15
+ * isNil(0) // false
16
+ * isNil('') // false
17
+ */
18
+ export function isNil(value) {
19
+ return value === null || value === undefined
20
+ }
21
+
22
+ /**
23
+ * 检查值是否为 undefined
24
+ * @param {*} value
25
+ * @returns {boolean}
26
+ * @example
27
+ * isUndefined(undefined) // true
28
+ * isUndefined(null) // false
29
+ * isUndefined(0) // false
30
+ * isUndefined('') // false
31
+ */
32
+ export function isUndefined(value) {
33
+ return value === undefined
34
+ }
35
+
36
+ /**
37
+ * 检查值是否为 null
38
+ * @param {*} value
39
+ * @returns {boolean}
40
+ * @example
41
+ * isNull(null) // true
42
+ * isNull(undefined) // false
43
+ * isNull(0) // false
44
+ * isNull('') // false
45
+ */
46
+ export function isNull(value) {
47
+ return value === null
48
+ }
49
+
50
+ /**
51
+ * 检查值是否为 boolean 类型
52
+ * @param {*} value
53
+ * @returns {boolean}
54
+ * @example
55
+ * isBoolean(true) // true
56
+ * isBoolean(false) // true
57
+ * isBoolean(1) // false
58
+ * isBoolean('true') // false
59
+ */
60
+ export function isBoolean(value) {
61
+ return typeof value === 'boolean'
62
+ }
63
+
64
+ /**
65
+ * 检查值是否为 number 类型
66
+ * @param {*} value
67
+ * @returns {boolean}
68
+ * @example
69
+ * isNumber(1) // true
70
+ * isNumber(NaN) // true
71
+ * isNumber('1') // false
72
+ */
73
+ export function isNumber(value) {
74
+ return typeof value === 'number'
75
+ }
76
+
77
+ /**
78
+ * 检查值是否为有限数字
79
+ * @param {*} value
80
+ * @returns {boolean}
81
+ * @example
82
+ * isFiniteNumber(1) // true
83
+ * isFiniteNumber(0) // true
84
+ * isFiniteNumber(NaN) // false
85
+ * isFiniteNumber(Infinity) // false
86
+ */
87
+ export function isFiniteNumber(value) {
88
+ return typeof value === 'number' && isFinite(value)
89
+ }
90
+
91
+ /**
92
+ * 检查值是否为整数
93
+ * @param {*} value
94
+ * @returns {boolean}
95
+ * @example
96
+ * isInteger(1) // true
97
+ * isInteger(0) // true
98
+ * isInteger(1.5) // false
99
+ * isInteger('1') // false
100
+ */
101
+ export function isInteger(value) {
102
+ return typeof value === 'number' && Number.isInteger(value)
103
+ }
104
+
105
+ /**
106
+ * 检查值是否为 NaN
107
+ * @param {*} value
108
+ * @returns {boolean}
109
+ * @example
110
+ * isNaN(NaN) // true
111
+ * isNaN(0) // false
112
+ * isNaN('NaN') // false
113
+ * isNaN(undefined) // false
114
+ */
115
+ export function isNaN(value) {
116
+ return Number.isNaN(value)
117
+ }
118
+
119
+ /**
120
+ * 检查值是否为字符串类型
121
+ * @param {*} value
122
+ * @returns {boolean}
123
+ * @example
124
+ * isString('hello') // true
125
+ * isString('') // true
126
+ * isString(123) // false
127
+ * isString(null) // false
128
+ */
129
+ export function isString(value) {
130
+ return typeof value === 'string'
131
+ }
132
+
133
+ /**
134
+ * 检查值是否为 Symbol 类型
135
+ * @param {*} value
136
+ * @returns {boolean}
137
+ * @example
138
+ * isSymbol(Symbol('foo')) // true
139
+ * isSymbol(Symbol()) // true
140
+ * isSymbol('foo') // false
141
+ * isSymbol(123) // false
142
+ */
143
+ export function isSymbol(value) {
144
+ return typeof value === 'symbol'
145
+ }
146
+
147
+ /**
148
+ * 检查值是否为函数
149
+ * @param {*} value
150
+ * @returns {boolean}
151
+ * @example
152
+ * isFunction(() => {}) // true
153
+ * isFunction(function() {}) // true
154
+ * isFunction(123) // false
155
+ * isFunction(null) // false
156
+ */
157
+ export function isFunction(value) {
158
+ return typeof value === 'function'
159
+ }
160
+
161
+ /**
162
+ * 检查值是否为数组
163
+ * @param {*} value
164
+ * @returns {boolean}
165
+ * @example
166
+ * isArray([1, 2, 3]) // true
167
+ * isArray([]) // true
168
+ * isArray('array') // false
169
+ * isArray({ length: 3 }) // false
170
+ */
171
+ export function isArray(value) {
172
+ return Array.isArray(value)
173
+ }
174
+
175
+ /**
176
+ * 检查值是否为类数组(有 length 属性且为数字)
177
+ * @param {*} value
178
+ * @returns {boolean}
179
+ * @example
180
+ * isArrayLike([1, 2, 3]) // true
181
+ * isArrayLike('hello') // true
182
+ * isArrayLike({ length: 3 }) // true
183
+ * isArrayLike(null) // false
184
+ */
185
+ export function isArrayLike(value) {
186
+ if (value === null || value === undefined) {
187
+ return false
188
+ }
189
+ const length = value.length
190
+ return typeof length === 'number' && length >= 0 && Number.isInteger(length)
191
+ }
192
+
193
+ /**
194
+ * 检查值是否为对象类型(排除 null 和数组)
195
+ * @param {*} value
196
+ * @returns {boolean}
197
+ * @example
198
+ * isObject({}) // true
199
+ * isObject([]) // true
200
+ * isObject(() => {}) // true
201
+ * isObject(null) // false
202
+ * isObject(123) // false
203
+ */
204
+ export function isObject(value) {
205
+ const type = typeof value
206
+ return value !== null && (type === 'object' || type === 'function')
207
+ }
208
+
209
+ /**
210
+ * 检查值是否为纯对象(Object 创建的普通对象)
211
+ * @param {*} value
212
+ * @returns {boolean}
213
+ * @example
214
+ * isPlainObject({}) // true
215
+ * isPlainObject({ a: 1 }) // true
216
+ * isPlainObject([]) // false
217
+ * isPlainObject(new Date()) // false
218
+ * isPlainObject(null) // false
219
+ */
220
+ export function isPlainObject(value) {
221
+ if (!isObject(value)) {
222
+ return false
223
+ }
224
+ const proto = Object.getPrototypeOf(value)
225
+ return proto === null || proto === Object.prototype
226
+ }
227
+
228
+ /**
229
+ * 检查值是否为类实例(如 Date、RegExp 等)
230
+ * @param {*} value
231
+ * @returns {boolean}
232
+ * @example
233
+ * isObjectLike({}) // true
234
+ * isObjectLike([]) // true
235
+ * isObjectLike(new Date()) // true
236
+ * isObjectLike('string') // false
237
+ * isObjectLike(null) // false
238
+ */
239
+ export function isObjectLike(value) {
240
+ return value !== null && typeof value === 'object'
241
+ }
242
+
243
+ /**
244
+ * 检查值是否为 Date 对象
245
+ * @param {*} value
246
+ * @returns {boolean}
247
+ * @example
248
+ * isDate(new Date()) // true
249
+ * isDate(new Date('2024-01-01')) // true
250
+ * isDate('2024-01-01') // false
251
+ * isDate(1234567890) // false
252
+ */
253
+ export function isDate(value) {
254
+ return value instanceof Date
255
+ }
256
+
257
+ /**
258
+ * 检查值是否为 RegExp 对象
259
+ * @param {*} value
260
+ * @returns {boolean}
261
+ * @example
262
+ * isRegExp(/abc/) // true
263
+ * isRegExp(new RegExp('abc')) // true
264
+ * isRegExp('abc') // false
265
+ * isRegExp(123) // false
266
+ */
267
+ export function isRegExp(value) {
268
+ return value instanceof RegExp
269
+ }
270
+
271
+ /**
272
+ * 检查值是否为 Promise 对象
273
+ * @param {*} value
274
+ * @returns {boolean}
275
+ * @example
276
+ * isPromise(Promise.resolve()) // true
277
+ * isPromise(new Promise(() => {})) // true
278
+ * isPromise(async () => {}) // false (函数)
279
+ * isPromise({ then: () => {} }) // true (thenable)
280
+ */
281
+ export function isPromise(value) {
282
+ return isObject(value) && isFunction(value.then)
283
+ }
284
+
285
+ /**
286
+ * 检查值是否为 Map 对象
287
+ * @param {*} value
288
+ * @returns {boolean}
289
+ * @example
290
+ * isMap(new Map()) // true
291
+ * isMap(new Map([['a', 1]])) // true
292
+ * isMap({}) // false
293
+ * isMap(new WeakMap()) // false
294
+ */
295
+ export function isMap(value) {
296
+ return value instanceof Map
297
+ }
298
+
299
+ /**
300
+ * 检查值是否为 Set 对象
301
+ * @param {*} value
302
+ * @returns {boolean}
303
+ * @example
304
+ * isSet(new Set()) // true
305
+ * isSet(new Set([1, 2, 3])) // true
306
+ * isSet([]) // false
307
+ * isSet(new WeakSet()) // false
308
+ */
309
+ export function isSet(value) {
310
+ return value instanceof Set
311
+ }
312
+
313
+ /**
314
+ * 检查值是否为 WeakMap 对象
315
+ * @param {*} value
316
+ * @returns {boolean}
317
+ * @example
318
+ * isWeakMap(new WeakMap()) // true
319
+ * isWeakMap(new Map()) // false
320
+ * isWeakMap({}) // false
321
+ */
322
+ export function isWeakMap(value) {
323
+ return value instanceof WeakMap
324
+ }
325
+
326
+ /**
327
+ * 检查值是否为 WeakSet 对象
328
+ * @param {*} value
329
+ * @returns {boolean}
330
+ * @example
331
+ * isWeakSet(new WeakSet()) // true
332
+ * isWeakSet(new Set()) // false
333
+ * isWeakSet([]) // false
334
+ */
335
+ export function isWeakSet(value) {
336
+ return value instanceof WeakSet
337
+ }
338
+
339
+ // ==================== 语言判断 ====================
340
+
341
+ /**
342
+ * - 检查文本是否包含韩文字符
343
+ * - 通过正则表达式匹配韩文音节(\uAC00-\uD7AF)和韩文字母(\u3130-\u318F)
344
+ * @param {string} text - 要检查的文本
345
+ * @returns {boolean} 如果包含韩文字符返回 true,否则返回 false
346
+ * @example
347
+ * isKorean('안녕하세요') // true
348
+ * isKorean('가나다') // true
349
+ * isKorean('hello') // false
350
+ * isKorean('你好') // false
351
+ * isKorean('123') // false
352
+ */
353
+ export function isKorean(text) {
354
+ const reg = /([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi;
355
+ return reg.test(text);
356
+ }
357
+
358
+
359
+ // ==================== 值判断 ====================
360
+
361
+ /**
362
+ * 检查值是否为空(null、undefined、空字符串、空数组、空对象)
363
+ * @param {*} value
364
+ * @returns {boolean}
365
+ * @example
366
+ * isEmpty(null) // true
367
+ * isEmpty(undefined) // true
368
+ * isEmpty('') // true
369
+ * isEmpty([]) // true
370
+ * isEmpty({}) // true
371
+ * isEmpty(0) // false
372
+ * isEmpty(' ') // false
373
+ */
374
+ export function isEmpty(value) {
375
+ if (value === null || value === undefined) {
376
+ return true
377
+ }
378
+ if (isArray(value) || isString(value)) {
379
+ return value.length === 0
380
+ }
381
+ if (isObjectLike(value)) {
382
+ return Object.keys(value).length === 0
383
+ }
384
+ return false
385
+ }
386
+
387
+ /**
388
+ * 检查两个值是否相等(支持浅比较)
389
+ * @param {*} a
390
+ * @param {*} b
391
+ * @returns {boolean}
392
+ * @example
393
+ * isEqual(1, 1) // true
394
+ * isEqual('a', 'a') // true
395
+ * isEqual([1, 2], [1, 2]) // true
396
+ * isEqual({a: 1}, {a: 1}) // true
397
+ * isEqual(1, '1') // false
398
+ * isEqual([1, 2], [1, 2, 3]) // false
399
+ */
400
+ export function isEqual(a, b) {
401
+ if (a === b) {
402
+ return true
403
+ }
404
+ if (a === null || b === null) {
405
+ return a === b
406
+ }
407
+ if (typeof a !== typeof b) {
408
+ return false
409
+ }
410
+
411
+ // 数组比较
412
+ if (isArray(a) && isArray(b)) {
413
+ if (a.length !== b.length) return false
414
+ for (let i = 0; i < a.length; i++) {
415
+ if (!isEqual(a[i], b[i])) return false
416
+ }
417
+ return true
418
+ }
419
+
420
+ // 对象比较
421
+ if (isPlainObject(a) && isPlainObject(b)) {
422
+ const keysA = Object.keys(a)
423
+ const keysB = Object.keys(b)
424
+ if (keysA.length !== keysB.length) return false
425
+ for (const key of keysA) {
426
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false
427
+ if (!isEqual(a[key], b[key])) return false
428
+ }
429
+ return true
430
+ }
431
+
432
+ return false
433
+ }
434
+
435
+ // ==================== 数组函数 ====================
436
+
437
+ /**
438
+ * 查找数组中第一个匹配的元素
439
+ * @template T
440
+ * @param {T[]} array
441
+ * @param {Function|*} predicate - 匹配函数或值
442
+ * @returns {*|undefined}
443
+ * @example
444
+ * find([1, 2, 3, 4], n => n % 2 === 0) // 2
445
+ * find([{name: 'a'}, {name: 'b'}], {name: 'b'}) // {name: 'b'}
446
+ */
447
+ export function find(array, predicate) {
448
+ if (!isArrayLike(array)) {
449
+ return undefined
450
+ }
451
+
452
+ const isFunc = isFunction(predicate)
453
+ const isObj = isPlainObject(predicate)
454
+
455
+ for (let i = 0; i < array.length; i++) {
456
+ const item = array[i]
457
+ if (isFunc) {
458
+ if (predicate(item, i, array)) return item
459
+ } else if (isObj) {
460
+ const keys = Object.keys(predicate)
461
+ const matches = keys.every(key => item[key] === predicate[key])
462
+ if (matches) return item
463
+ } else {
464
+ if (item === predicate) return item
465
+ }
466
+ }
467
+ return undefined
468
+ }
469
+
470
+ /**
471
+ * 查找数组中第一个匹配元素的索引
472
+ * @template T
473
+ * @param {T[]} array
474
+ * @param {*} value
475
+ * @param {number} [fromIndex=0]
476
+ * @returns {number}
477
+ * @example
478
+ * indexOf([1, 2, 3, 4], 3) // 2
479
+ * indexOf([1, 2, 3, 2], 2) // 1
480
+ * indexOf([1, 2, 3, 2], 2, 2) // 3
481
+ * indexOf([1, 2, 3], 5) // -1
482
+ */
483
+ export function indexOf(array, value, fromIndex = 0) {
484
+ const len = array ? array.length : 0
485
+ if (!len) return -1
486
+
487
+ const start = fromIndex < 0 ? Math.max(0, len + fromIndex) : fromIndex
488
+ for (let i = start; i < len; i++) {
489
+ if (array[i] === value) return i
490
+ }
491
+ return -1
492
+ }
493
+
494
+ /**
495
+ * 查找数组中最后匹配元素的索引
496
+ * @template T
497
+ * @param {T[]} array
498
+ * @param {*} value
499
+ * @param {number} [fromIndex=array.length-1]
500
+ * @returns {number}
501
+ * @example
502
+ * lastIndexOf([1, 2, 3, 2], 2) // 3
503
+ * lastIndexOf([1, 2, 3, 2], 2, 2) // 1
504
+ * lastIndexOf([1, 2, 3], 5) // -1
505
+ */
506
+ export function lastIndexOf(array, value, fromIndex) {
507
+ const len = array ? array.length : 0
508
+ if (!len) return -1
509
+
510
+ const start = fromIndex !== undefined
511
+ ? Math.min(fromIndex < 0 ? len + fromIndex : fromIndex, len - 1)
512
+ : len - 1
513
+
514
+ for (let i = start; i >= 0; i--) {
515
+ if (array[i] === value) return i
516
+ }
517
+ return -1
518
+ }
519
+
520
+ /**
521
+ * 过滤数组中的元素
522
+ * @template T
523
+ * @param {T[]} array
524
+ * @param {Function} predicate
525
+ * @returns {T[]}
526
+ * @example
527
+ * filter([1, 2, 3, 4], n => n % 2 === 0) // [2, 4]
528
+ * filter([{name: 'a', age: 20}, {name: 'b', age: 30}], o => o.age > 25) // [{name: 'b', age: 30}]
529
+ * filter([1, 2, 3], n => n > 1) // [2, 3]
530
+ */
531
+ export function filter(array, predicate) {
532
+ if (!isArrayLike(array)) {
533
+ return []
534
+ }
535
+
536
+ const result = []
537
+ for (let i = 0; i < array.length; i++) {
538
+ if (predicate(array[i], i, array)) {
539
+ result.push(array[i])
540
+ }
541
+ }
542
+ return result
543
+ }
544
+
545
+ /**
546
+ * 映射数组中的每个元素
547
+ * @template T, R
548
+ * @param {T[]} array
549
+ * @param {Function|string} iteratee - 映射函数或属性名
550
+ * @returns {R[]}
551
+ * @example
552
+ * map([1, 2, 3], n => n * 2) // [2, 4, 6]
553
+ * map([{name: 'a'}, {name: 'b'}], 'name') // ['a', 'b']
554
+ */
555
+ export function map(array, iteratee) {
556
+ if (!isArrayLike(array)) {
557
+ return []
558
+ }
559
+
560
+ const result = []
561
+ const isFunc = isFunction(iteratee)
562
+ const isStringKey = isString(iteratee)
563
+
564
+ for (let i = 0; i < array.length; i++) {
565
+ const item = array[i]
566
+ if (isFunc) {
567
+ result.push(iteratee(item, i, array))
568
+ } else if (isStringKey) {
569
+ result.push(item[iteratee])
570
+ } else {
571
+ result.push(item)
572
+ }
573
+ }
574
+ return result
575
+ }
576
+
577
+ /**
578
+ * 数组去重
579
+ * @template T
580
+ * @param {T[]} array
581
+ * @returns {T[]}
582
+ * @example
583
+ * uniq([1, 2, 2, 3, 3, 4]) // [1, 2, 3, 4]
584
+ * uniq(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']
585
+ * uniq([1, '1', 2]) // [1, '1', 2]
586
+ */
587
+ export function uniq(array) {
588
+ if (!isArrayLike(array)) {
589
+ return []
590
+ }
591
+ return [...new Set(array)]
592
+ }
593
+
594
+ /**
595
+ * 数组扁平化
596
+ * @template T
597
+ * @param {T[]} array
598
+ * @param {number} [depth=1] - 扁平化深度
599
+ * @returns {T[]}
600
+ * @example
601
+ * flatten([1, [2, 3]]) // [1, 2, 3]
602
+ * flatten([1, [2, [3, [4]]]], 2) // [1, 2, 3, [4]]
603
+ * flatten([1, [2, [3, [4]]]], Infinity) // [1, 2, 3, 4]
604
+ */
605
+ export function flatten(array, depth = 1) {
606
+ if (!isArrayLike(array)) {
607
+ return []
608
+ }
609
+
610
+ const result = []
611
+
612
+ function flattenDeep(arr, currentDepth) {
613
+ for (const item of arr) {
614
+ if (isArray(item) && currentDepth < depth) {
615
+ flattenDeep(item, currentDepth + 1)
616
+ } else {
617
+ result.push(item)
618
+ }
619
+ }
620
+ }
621
+
622
+ flattenDeep(array, 0)
623
+ return result
624
+ }
625
+
626
+ /**
627
+ * 深度扁平化数组
628
+ * @template T
629
+ * @param {T[]} array
630
+ * @returns {T[]}
631
+ * @example
632
+ * flattenDeep([1, [2, [3, [4]]]]) // [1, 2, 3, 4]
633
+ * flattenDeep([1, [2, 3], [[4, 5]]]) // [1, 2, 3, 4, 5]
634
+ * flattenDeep([1, 2, 3]) // [1, 2, 3]
635
+ */
636
+ export function flattenDeep(array) {
637
+ if (!isArrayLike(array)) {
638
+ return []
639
+ }
640
+ return flatten(array, Infinity)
641
+ }
642
+
643
+ /**
644
+ * 获取数组的第一个元素
645
+ * @template T
646
+ * @param {T[]} array
647
+ * @returns {*|undefined}
648
+ * @example
649
+ * first([1, 2, 3]) // 1
650
+ * first([]) // undefined
651
+ * first(null) // undefined
652
+ */
653
+ export function first(array) {
654
+ return array && array.length ? array[0] : undefined
655
+ }
656
+
657
+ /**
658
+ * 获取数组的最后一个元素
659
+ * @template T
660
+ * @param {T[]} array
661
+ * @returns {*|undefined}
662
+ * @example
663
+ * last([1, 2, 3]) // 3
664
+ * last([]) // undefined
665
+ * last(null) // undefined
666
+ */
667
+ export function last(array) {
668
+ return array && array.length ? array[array.length - 1] : undefined
669
+ }
670
+
671
+ /**
672
+ * 获取数组除最后一个元素外的所有元素
673
+ * @template T
674
+ * @param {T[]} array
675
+ * @returns {T[]}
676
+ * @example
677
+ * initial([1, 2, 3]) // [1, 2]
678
+ * initial([1]) // []
679
+ * initial([]) // []
680
+ */
681
+ export function initial(array) {
682
+ if (!array || !array.length) {
683
+ return []
684
+ }
685
+ return array.slice(0, -1)
686
+ }
687
+
688
+ /**
689
+ * 获取数组除第一个元素外的所有元素
690
+ * @template T
691
+ * @param {T[]} array
692
+ * @returns {T[]}
693
+ * @example
694
+ * rest([1, 2, 3]) // [2, 3]
695
+ * rest([1]) // []
696
+ * rest([]) // []
697
+ */
698
+ export function rest(array) {
699
+ if (!array || !array.length) {
700
+ return []
701
+ }
702
+ return array.slice(1)
703
+ }
704
+
705
+ /**
706
+ * 获取数组的前 n 个元素
707
+ * @template T
708
+ * @param {T[]} array
709
+ * @param {number} n
710
+ * @returns {T[]}
711
+ * @example
712
+ * take([1, 2, 3], 2) // [1, 2]
713
+ * take([1, 2, 3]) // [1]
714
+ * take([1, 2, 3], 10) // [1, 2, 3]
715
+ */
716
+ export function take(array, n = 1) {
717
+ if (!array || !array.length) {
718
+ return []
719
+ }
720
+ return array.slice(0, n)
721
+ }
722
+
723
+ /**
724
+ * 获取数组的后 n 个元素
725
+ * @template T
726
+ * @param {T[]} array
727
+ * @param {number} n
728
+ * @returns {T[]}
729
+ * @example
730
+ * takeRight([1, 2, 3], 2) // [2, 3]
731
+ * takeRight([1, 2, 3]) // [3]
732
+ * takeRight([1, 2, 3], 10) // [1, 2, 3]
733
+ */
734
+ export function takeRight(array, n = 1) {
735
+ if (!array || !array.length) {
736
+ return []
737
+ }
738
+ const start = Math.max(0, array.length - n)
739
+ return array.slice(start)
740
+ }
741
+
742
+ /**
743
+ * 连接多个数组
744
+ * @template T
745
+ * @param {T[]} array
746
+ * @param {...T[]} arrays
747
+ * @returns {T[]}
748
+ * @example
749
+ * concat([1], [2], [3]) // [1, 2, 3]
750
+ * concat([1, 2], 3, [4, 5]) // [1, 2, 3, 4, 5]
751
+ * concat([], 1) // [1]
752
+ */
753
+ export function concat(array, ...arrays) {
754
+ if (!isArrayLike(array)) {
755
+ array = []
756
+ }
757
+ return array.concat(...arrays.map(a => isArrayLike(a) ? a : [a]))
758
+ }
759
+
760
+ /**
761
+ * 获取数组的并集
762
+ * @template T
763
+ * @param {T[]} array1
764
+ * @param {T[]} array2
765
+ * @returns {T[]}
766
+ * @example
767
+ * union([1, 2, 3], [3, 4, 5]) // [1, 2, 3, 4, 5]
768
+ * union([1, 2], [2, 3]) // [1, 2, 3]
769
+ * union([1], []) // [1]
770
+ */
771
+ export function union(array1, array2) {
772
+ return uniq([...(isArrayLike(array1) ? array1 : []), ...(isArrayLike(array2) ? array2 : [])])
773
+ }
774
+
775
+ /**
776
+ * 获取数组的交集
777
+ * @template T
778
+ * @param {T[]} array1
779
+ * @param {T[]} array2
780
+ * @returns {T[]}
781
+ * @example
782
+ * intersection([1, 2, 3], [2, 3, 4]) // [2, 3]
783
+ * intersection([1, 2], [3, 4]) // []
784
+ * intersection([1, 2, 2], [2, 2, 3]) // [2]
785
+ */
786
+ export function intersection(array1, array2) {
787
+ if (!isArrayLike(array1) || !isArrayLike(array2)) {
788
+ return []
789
+ }
790
+ const set2 = new Set(array2)
791
+ return uniq(array1.filter(item => set2.has(item)))
792
+ }
793
+
794
+ /**
795
+ * 获取 array1 相对于 array2 的差集
796
+ * @template T
797
+ * @param {T[]} array1
798
+ * @param {T[]} array2
799
+ * @returns {T[]}
800
+ * @example
801
+ * difference([1, 2, 3], [2, 3]) // [1]
802
+ * difference([1, 2, 3], [1, 2, 3]) // []
803
+ * difference([1, 2, 3], [4, 5]) // [1, 2, 3]
804
+ */
805
+ export function difference(array1, array2) {
806
+ if (!isArrayLike(array1)) {
807
+ return []
808
+ }
809
+ if (!isArrayLike(array2)) {
810
+ return array1.slice()
811
+ }
812
+ const set2 = new Set(array2)
813
+ return array1.filter(item => !set2.has(item))
814
+ }
815
+
816
+ /**
817
+ * 将数组分成指定大小的块
818
+ * @template T
819
+ * @param {T[]} array
820
+ * @param {number} size - 每个块的大小
821
+ * @returns {T[][]}
822
+ * @example
823
+ * chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
824
+ * chunk([1, 2, 3, 4], 3) // [[1, 2, 3], [4]]
825
+ * chunk([1, 2, 3], 1) // [[1], [2], [3]]
826
+ */
827
+ export function chunk(array, size = 1) {
828
+ if (!isArrayLike(array) || size < 1) {
829
+ return []
830
+ }
831
+
832
+ const result = []
833
+ for (let i = 0; i < array.length; i += size) {
834
+ result.push(array.slice(i, i + size))
835
+ }
836
+ return result
837
+ }
838
+
839
+ /**
840
+ * 反转数组
841
+ * @template T
842
+ * @param {T[]} array
843
+ * @returns {T[]}
844
+ * @example
845
+ * reverse([1, 2, 3]) // [3, 2, 1]
846
+ * reverse(['a', 'b', 'c']) // ['c', 'b', 'a']
847
+ * reverse([1]) // [1]
848
+ */
849
+ export function reverse(array) {
850
+ if (!isArrayLike(array)) {
851
+ return []
852
+ }
853
+ return [...array].reverse()
854
+ }
855
+
856
+ /**
857
+ * 创建切片数组
858
+ * @template T
859
+ * @param {T[]} array
860
+ * @param {number} [start=0]
861
+ * @param {number} [end=array.length]
862
+ * @returns {T[]}
863
+ * @example
864
+ * slice([1, 2, 3, 4], 1, 3) // [2, 3]
865
+ * slice([1, 2, 3, 4], 2) // [3, 4]
866
+ * slice([1, 2, 3, 4]) // [1, 2, 3, 4]
867
+ */
868
+ export function slice(array, start = 0, end) {
869
+ if (!isArrayLike(array)) {
870
+ return []
871
+ }
872
+ return array.slice(start, end)
873
+ }
874
+
875
+ /**
876
+ * 压缩数组(移除 falsy 值)
877
+ * @template T
878
+ * @param {T[]} array
879
+ * @returns {T[]}
880
+ * @example
881
+ * compact([0, 1, false, 2, '', 3, null, 4]) // [1, 2, 3, 4]
882
+ * compact([1, 2, 3]) // [1, 2, 3]
883
+ * compact([null, undefined, 0]) // []
884
+ */
885
+ export function compact(array) {
886
+ if (!isArrayLike(array)) {
887
+ return []
888
+ }
889
+ return array.filter(item => item)
890
+ }
891
+
892
+ /**
893
+ * 创建新数组,移除数组开头的所有假值
894
+ * @template T
895
+ * @param {T[]} array
896
+ * @param {number} [n=1]
897
+ * @returns {T[]}
898
+ * @example
899
+ * drop([1, 2, 3], 1) // [2, 3]
900
+ * drop([1, 2, 3], 2) // [3]
901
+ * drop([1, 2, 3]) // [2, 3]
902
+ */
903
+ export function drop(array, n = 1) {
904
+ if (!isArrayLike(array)) {
905
+ return []
906
+ }
907
+ return array.slice(n)
908
+ }
909
+
910
+ /**
911
+ * 创建新数组,移除数组末尾的所有假值
912
+ * @template T
913
+ * @param {T[]} array
914
+ * @param {number} [n=1]
915
+ * @returns {T[]}
916
+ * @example
917
+ * dropRight([1, 2, 3], 1) // [1, 2]
918
+ * dropRight([1, 2, 3], 2) // [1]
919
+ * dropRight([1, 2, 3]) // [1, 2]
920
+ */
921
+ export function dropRight(array, n = 1) {
922
+ if (!isArrayLike(array)) {
923
+ return []
924
+ }
925
+ return array.slice(0, -n)
926
+ }
927
+
928
+ // ==================== 对象函数 ====================
929
+
930
+ /**
931
+ * 获取对象的所有可枚举属性(包括原型链)
932
+ * @param {Object} obj
933
+ * @returns {string[]}
934
+ * @example
935
+ * keys({a: 1, b: 2}) // ['a', 'b']
936
+ * keys([1, 2, 3]) // ['0', '1', '2']
937
+ * keys(null) // []
938
+ */
939
+ export function keys(obj) {
940
+ if (obj === null || obj === undefined) {
941
+ return []
942
+ }
943
+ if (!isObject(obj)) {
944
+ obj = Object(obj)
945
+ }
946
+ return Object.keys(obj)
947
+ }
948
+
949
+ /**
950
+ * 获取对象的所有可枚举属性值
951
+ * @param {Object} obj
952
+ * @returns {*[]}
953
+ * @example
954
+ * values({a: 1, b: 2}) // [1, 2]
955
+ * values({name: 'John', age: 30}) // ['John', 30]
956
+ * values(null) // []
957
+ */
958
+ export function values(obj) {
959
+ if (obj === null || obj === undefined) {
960
+ return []
961
+ }
962
+ return Object.keys(obj).map(key => obj[key])
963
+ }
964
+
965
+ /**
966
+ * 获取对象的所有可枚举属性和值的键值对
967
+ * @param {Object} obj
968
+ * @returns {Array<[string, *]>}
969
+ * @example
970
+ * entries({a: 1, b: 2}) // [['a', 1], ['b', 2]]
971
+ * entries({name: 'John'}) // [['name', 'John']]
972
+ * entries(null) // []
973
+ */
974
+ export function entries(obj) {
975
+ if (obj === null || obj === undefined) {
976
+ return []
977
+ }
978
+ return Object.entries(obj)
979
+ }
980
+
981
+ /**
982
+ * 获取对象的属性值
983
+ * @param {Object} obj
984
+ * @param {string|Array<string>} path - 属性路径
985
+ * @param {*} [defaultValue] - 默认值
986
+ * @returns {*}
987
+ * @example
988
+ * get({a: {b: {c: 1}}}, 'a.b.c') // 1
989
+ * get({a: {b: {c: 1}}}, 'a.b.c.d', 'default') // 'default'
990
+ */
991
+ export function get(obj, path, defaultValue) {
992
+ if (obj === null || obj === undefined) {
993
+ return defaultValue
994
+ }
995
+
996
+ const paths = isArray(path) ? path : String(path).split('.')
997
+
998
+ let result = obj
999
+ for (const key of paths) {
1000
+ if (result === null || result === undefined) {
1001
+ return defaultValue
1002
+ }
1003
+ result = result[key]
1004
+ }
1005
+
1006
+ return result === undefined ? defaultValue : result
1007
+ }
1008
+
1009
+ /**
1010
+ * 设置对象的属性值
1011
+ * @param {Object} obj
1012
+ * @param {string|Array<string>} path - 属性路径
1013
+ * @param {*} value - 要设置的值
1014
+ * @returns {Object}
1015
+ * @example
1016
+ * set({a: 1}, 'b', 2) // {a: 1, b: 2}
1017
+ * set({}, 'a.b.c', 1) // {a: {b: {c: 1}}}
1018
+ * set({a: {b: 1}}, 'a.c', 2) // {a: {b: 1, c: 2}}
1019
+ */
1020
+ export function set(obj, path, value) {
1021
+ if (obj === null || obj === undefined) {
1022
+ return obj
1023
+ }
1024
+
1025
+ const paths = isArray(path) ? path : String(path).split('.')
1026
+ let current = obj
1027
+
1028
+ for (let i = 0; i < paths.length - 1; i++) {
1029
+ const key = paths[i]
1030
+ if (!isObject(current[key])) {
1031
+ current[key] = {}
1032
+ }
1033
+ current = current[key]
1034
+ }
1035
+
1036
+ current[paths[paths.length - 1]] = value
1037
+ return obj
1038
+ }
1039
+
1040
+ /**
1041
+ * 删除对象的属性
1042
+ * @param {Object} obj
1043
+ * @param {string|Array<string>} path - 属性路径
1044
+ * @returns {boolean}
1045
+ * @example
1046
+ * unset({a: 1, b: 2}, 'a') // true
1047
+ * unset({a: {b: 1}}, 'a.b') // true
1048
+ * unset({a: 1}, 'b') // true
1049
+ */
1050
+ export function unset(obj, path) {
1051
+ if (obj === null || obj === undefined) {
1052
+ return true
1053
+ }
1054
+
1055
+ const paths = isArray(path) ? path : String(path).split('.')
1056
+ let current = obj
1057
+
1058
+ for (let i = 0; i < paths.length - 1; i++) {
1059
+ const key = paths[i]
1060
+ if (!isObject(current[key])) {
1061
+ return true
1062
+ }
1063
+ current = current[key]
1064
+ }
1065
+
1066
+ const lastKey = paths[paths.length - 1]
1067
+ return delete current[lastKey]
1068
+ }
1069
+
1070
+ /**
1071
+ * 检查对象是否有指定的属性
1072
+ * @param {Object} obj
1073
+ * @param {string|Array<string>} path - 属性路径
1074
+ * @returns {boolean}
1075
+ * @example
1076
+ * has({a: 1, b: 2}, 'a') // true
1077
+ * has({a: {b: 1}}, 'a.b') // true
1078
+ * has({a: 1}, 'b') // false
1079
+ */
1080
+ export function has(obj, path) {
1081
+ if (obj === null || obj === undefined) {
1082
+ return false
1083
+ }
1084
+
1085
+ const paths = isArray(path) ? path : String(path).split('.')
1086
+ let current = obj
1087
+
1088
+ for (const key of paths) {
1089
+ if (current === null || current === undefined) {
1090
+ return false
1091
+ }
1092
+ if (!Object.prototype.hasOwnProperty.call(current, key)) {
1093
+ return false
1094
+ }
1095
+ current = current[key]
1096
+ }
1097
+
1098
+ return true
1099
+ }
1100
+
1101
+ /**
1102
+ * 获取对象的属性数量
1103
+ * @param {Object} obj
1104
+ * @returns {number}
1105
+ * @example
1106
+ * size({a: 1, b: 2}) // 2
1107
+ * size([1, 2, 3]) // 3
1108
+ * size('hello') // 5
1109
+ * size(null) // 0
1110
+ */
1111
+ export function size(obj) {
1112
+ if (obj === null || obj === undefined) {
1113
+ return 0
1114
+ }
1115
+ if (isArrayLike(obj)) {
1116
+ return obj.length
1117
+ }
1118
+ return Object.keys(obj).length
1119
+ }
1120
+
1121
+ /**
1122
+ * 判断两个对象是否相等(深度比较)
1123
+ * @param {*} a
1124
+ * @param {*} b
1125
+ * @returns {boolean}
1126
+ * @example
1127
+ * isEqualDeep({a: 1}, {a: 1}) // true
1128
+ * isEqualDeep({a: {b: 1}}, {a: {b: 1}}) // true
1129
+ * isEqualDeep({a: 1}, {a: 2}) // false
1130
+ */
1131
+ export function isEqualDeep(a, b) {
1132
+ return isEqual(a, b)
1133
+ }
1134
+
1135
+ /**
1136
+ * 合并对象,覆盖源对象重复路径的值,支持 getter/setter
1137
+ * @param {Object} target - 目标对象
1138
+ * @param {...Object} sources - 一个或多个源对象
1139
+ * @returns {Object} 返回目标对象
1140
+ * @example
1141
+ * merge({a: 1}, {b: 2}) // {a: 1, b: 2}
1142
+ * merge({a: {b: 1}}, {a: {c: 2}}) // {a: {b: 1, c: 2}}
1143
+ * merge({a: [1, 2]}, {a: [3, 4]}) // {a: [1, 2, 3, 4]}
1144
+ */
1145
+ export function merge(target, ...sources) {
1146
+ if (!isObject(target)) {
1147
+ target = {}
1148
+ }
1149
+
1150
+ for (const source of sources) {
1151
+ if (!isObject(source)) {
1152
+ continue
1153
+ }
1154
+
1155
+ // 获取源对象所有可枚举的自有属性键
1156
+ const keys = Object.keys(source)
1157
+
1158
+ for (const key of keys) {
1159
+ // 获取完整的属性描述符(包含 value, writable, enumerable, configurable, get, set)
1160
+ const descriptor = Object.getOwnPropertyDescriptor(source, key)
1161
+
1162
+ // 如果描述符不存在(极少情况),跳过
1163
+ if (!descriptor) continue
1164
+
1165
+ const srcVal = source[key] // 用于逻辑判断的值(如果是访问器,这里会触发 getter)
1166
+ const tgtVal = target[key] // 目标对象当前的值
1167
+
1168
+ // 检查是否为访问器属性 (getter/setter)
1169
+ const isAccessor = 'get' in descriptor || 'set' in descriptor
1170
+
1171
+ if (isAccessor) {
1172
+ Object.defineProperty(target, key, descriptor)
1173
+ } else if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
1174
+ target[key] = merge(tgtVal, srcVal)
1175
+ } else if (isArray(srcVal) && isArray(tgtVal)) {
1176
+ target[key] = merge([], tgtVal, srcVal)
1177
+ } else {
1178
+ target[key] = srcVal
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ return target
1184
+ }
1185
+
1186
+ /**
1187
+ * 合并对象,将源对象的所有可枚举自有属性复制到目标对象
1188
+ * @param {Object} target - 目标对象
1189
+ * @param {...Object} sources - 一个或多个源对象
1190
+ * @returns {Object} 返回目标对象
1191
+ * @example
1192
+ * assign({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
1193
+ * assign({ a: 1 }, { a: 2 }, { b: 3 }) // { a: 2, b: 3 }
1194
+ */
1195
+ export function assign(target, ...sources) {
1196
+ if (target === null || target === undefined) {
1197
+ target = {}
1198
+ }
1199
+ return Object.assign(target, ...sources)
1200
+ }
1201
+
1202
+ /**
1203
+ * 创建对象的部分副本,只包含指定的属性
1204
+ * @param {Object} obj
1205
+ * @param {Array<string>} paths - 要 pick 的属性路径
1206
+ * @returns {Object}
1207
+ * @example
1208
+ * pick({a: 1, b: 2, c: 3}, ['a', 'c']) // {a: 1, c: 3}
1209
+ * pick({a: 1, b: 2}, ['a', 'd']) // {a: 1}
1210
+ * pick({a: {b: 1}}, ['a.b']) // {a: {b: 1}}
1211
+ */
1212
+ export function pick(obj, paths) {
1213
+ if (!isObject(obj)) {
1214
+ return {}
1215
+ }
1216
+
1217
+ const result = {}
1218
+ for (const path of paths) {
1219
+ if (has(obj, path)) {
1220
+ set(result, path, get(obj, path))
1221
+ }
1222
+ }
1223
+ return result
1224
+ }
1225
+
1226
+ /**
1227
+ * 创建对象的部分副本,排除指定的属性
1228
+ * @param {Object} obj
1229
+ * @param {Array<string>} paths - 要排除的属性路径
1230
+ * @returns {Object}
1231
+ * @example
1232
+ * omit({a: 1, b: 2, c: 3}, ['b']) // {a: 1, c: 3}
1233
+ * omit({a: 1, b: 2}, ['c']) // {a: 1, b: 2}
1234
+ * omit({a: 1, b: 2}, ['a', 'b']) // {}
1235
+ */
1236
+ export function omit(obj, paths) {
1237
+ if (!isObject(obj)) {
1238
+ return {}
1239
+ }
1240
+
1241
+ const result = {}
1242
+ const allKeys = keys(obj)
1243
+ const excludeKeys = new Set(paths)
1244
+
1245
+ for (const key of allKeys) {
1246
+ if (!excludeKeys.has(key)) {
1247
+ result[key] = obj[key]
1248
+ }
1249
+ }
1250
+ return result
1251
+ }
1252
+
1253
+ /**
1254
+ * 对象键名转换(kebab-case)
1255
+ * @param {Object} obj
1256
+ * @returns {Object}
1257
+ * @example
1258
+ * keysToKebabCase({firstName: 'John'}) // {'first-name': 'John'}
1259
+ * keysToKebabCase({a: {b: 1}}) // {a: {b: 1}}
1260
+ * keysToKebabCase('not an object') // 'not an object'
1261
+ */
1262
+ export function keysToKebabCase(obj) {
1263
+ if (!isObject(obj)) {
1264
+ return obj
1265
+ }
1266
+
1267
+ const result = {}
1268
+ for (const [key, value] of Object.entries(obj)) {
1269
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')
1270
+ result[kebabKey] = isObject(value) ? keysToKebabCase(value) : value
1271
+ }
1272
+ return result
1273
+ }
1274
+
1275
+ /**
1276
+ * 对象键名转换(camelCase)
1277
+ * @param {Object} obj
1278
+ * @returns {Object}
1279
+ * @example
1280
+ * keysToCamelCase({'first-name': 'John'}) // {firstName: 'John'}
1281
+ * keysToCamelCase({a_b: 1}) // {aB: 1}
1282
+ * keysToCamelCase('not an object') // 'not an object'
1283
+ */
1284
+ export function keysToCamelCase(obj) {
1285
+ if (!isObject(obj)) {
1286
+ return obj
1287
+ }
1288
+
1289
+ const result = {}
1290
+ for (const [key, value] of Object.entries(obj)) {
1291
+ const camelKey = key.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase())
1292
+ result[camelKey] = isObject(value) ? keysToCamelCase(value) : value
1293
+ }
1294
+ return result
1295
+ }
1296
+
1297
+ /**
1298
+ * 根据路径字符串获取对象中的值
1299
+ * @param {Object} object - 要查询的对象
1300
+ * @param {string} [prop=''] - 属性路径,支持点号分隔(如 'a.b.c')
1301
+ * @returns {*} 如果找到则返回对应的值,否则返回 null
1302
+ * @example
1303
+ * getValueByPath({ a: { b: { c: 1 } } }, 'a.b.c') // 1
1304
+ * getValueByPath({ a: 1 }, 'b') // null
1305
+ * getValueByPath(null, 'a') // null
1306
+ */
1307
+ export function getValueByPath(object, prop) {
1308
+ const pathStr = prop || '';
1309
+ const paths = pathStr.split('.');
1310
+ let current = object;
1311
+ let result = null;
1312
+ for (let i = 0, j = paths.length; i < j; i++) {
1313
+ const path = paths[i];
1314
+ if (!current) break;
1315
+
1316
+ if (i === j - 1) {
1317
+ result = current[path];
1318
+ break;
1319
+ }
1320
+ current = current[path];
1321
+ }
1322
+ return result;
1323
+ };
1324
+
1325
+ /**
1326
+ * 根据路径字符串获取对象中的属性信息
1327
+ * 支持点号分隔符 (.) 和方括号表示法 ([]),例如 'a.b.c' 或 'a[b][c]'。
1328
+ *
1329
+ * @param {Object} obj - 要查询的目标对象
1330
+ * @param {string} path - 属性路径字符串
1331
+ * @param {boolean} [strict=false] - 是否开启严格模式。
1332
+ * - 如果为 true,当路径无效或中间节点非对象时抛出错误。
1333
+ * - 如果为 false,当路径无效时会中断遍历并返回当前状态。
1334
+ * @returns {Object} 返回一个包含以下属性的对象:
1335
+ * - o: {Object|null} 最后一层有效的父对象。如果路径第一步就失败,则为 null 或原始 obj。
1336
+ * - k: {string|undefined} 最后一层的键名。
1337
+ * - v: {*} 最终获取到的值,如果路径无效则为 undefined 或 null。
1338
+ * @throws {Error} 当 strict 为 true 且路径无效时抛出错误
1339
+ */
1340
+ export function getPropByPath(obj, path, strict) {
1341
+ // 1. 基础校验
1342
+ if (obj === null || obj === undefined) {
1343
+ if (strict) {
1344
+ throw new Error('please transfer a valid prop path to form item!');
1345
+ }
1346
+ return { o: null, k: undefined, v: undefined };
1347
+ }
1348
+
1349
+ if (typeof path !== 'string' || !path) {
1350
+ if (strict) {
1351
+ throw new Error('please transfer a valid prop path to form item!');
1352
+ }
1353
+ return { o: obj, k: undefined, v: undefined };
1354
+ }
1355
+
1356
+ let tempObj = obj;
1357
+ // 标准化路径:a[b] -> a.b, .a.b -> a.b
1358
+ path = path.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
1359
+ const keyArr = path.split('.');
1360
+ const len = keyArr.length;
1361
+
1362
+ // 如果路径只有一个键,直接处理
1363
+ if (len === 1) {
1364
+ return {
1365
+ o: obj,
1366
+ k: keyArr[0],
1367
+ v: obj[keyArr[0]]
1368
+ };
1369
+ }
1370
+
1371
+ let i = 0;
1372
+ // 开始遍历
1373
+ for (; i < len - 1; ++i) {
1374
+ const key = keyArr[i];
1375
+ // 检查当前层级是否存在且为对象/数组(排除 null)
1376
+ // 注意:Array 也是 object,in 操作符对数组有效
1377
+ if (tempObj === null || tempObj === undefined || typeof tempObj !== 'object') {
1378
+ // 非严格模式:中断,返回当前状态
1379
+ // 此时 tempObj 是导致中断的那个非对象值(或 null/undefined)
1380
+ // 为了保持与原 API 兼容,我们返回中断前的状态或者 null
1381
+ if (strict) throw new Error(`Property '${key}' does not exist on ${tempObj} at path '${keyArr.slice(0, i).join('.')}'`);
1382
+ return {
1383
+ o: null,
1384
+ k: key,
1385
+ v: undefined
1386
+ };
1387
+ }
1388
+
1389
+ if (key in tempObj) {
1390
+ tempObj = tempObj[key];
1391
+ } else {
1392
+ // 键不存在,中断
1393
+ if (strict) throw new Error('please transfer a valid prop path to form item!');
1394
+ return {
1395
+ o: tempObj, // 父对象还存在
1396
+ k: key,
1397
+ v: undefined
1398
+ };
1399
+ }
1400
+ }
1401
+
1402
+ const lastKey = keyArr[i];
1403
+ if (tempObj === null || tempObj === undefined) {
1404
+ if (strict) throw new Error('please transfer a valid prop path to form item!');
1405
+ return { o: null, k: lastKey, v: undefined };
1406
+ }
1407
+
1408
+ return {
1409
+ o: tempObj,
1410
+ k: lastKey,
1411
+ v: tempObj[lastKey]
1412
+ };
1413
+ }
1414
+
1415
+ // ==================== 字符串函数 ====================
1416
+
1417
+ /**
1418
+ * 将字符串转换为 kebab-case
1419
+ * @param {string} str
1420
+ * @returns {string}
1421
+ * @example
1422
+ * kebabCase('helloWorld') // 'hello-world'
1423
+ * kebabCase('Hello World') // 'hello-world'
1424
+ * kebabCase('hello_world') // 'hello-world'
1425
+ */
1426
+ export function kebabCase(str) {
1427
+ if (!isString(str)) {
1428
+ return ''
1429
+ }
1430
+ return str
1431
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
1432
+ .replace(/[\s_]+/g, '-')
1433
+ .toLowerCase()
1434
+ .replace(/^-|-$/g, '')
1435
+ }
1436
+
1437
+ /**
1438
+ * 将字符串转换为 camelCase
1439
+ * @param {string} str
1440
+ * @returns {string}
1441
+ * @example
1442
+ * camelCase('hello-world') // 'helloWorld'
1443
+ * camelCase('Hello World') // 'helloWorld'
1444
+ * camelCase('hello_world') // 'helloWorld'
1445
+ */
1446
+ export function camelCase(str) {
1447
+ if (!isString(str)) {
1448
+ return ''
1449
+ }
1450
+ return str
1451
+ .replace(/[-_\s]+(.)?/g, (match, char) => char ? char.toUpperCase() : '')
1452
+ .replace(/^[A-Z]/, char => char.toLowerCase())
1453
+ }
1454
+
1455
+ /**
1456
+ * 将字符串转换为 PascalCase
1457
+ * @param {string} str
1458
+ * @returns {string}
1459
+ * @example
1460
+ * pascalCase('hello-world') // 'HelloWorld'
1461
+ * pascalCase('hello world') // 'HelloWorld'
1462
+ * pascalCase('hello_world') // 'HelloWorld'
1463
+ */
1464
+ export function pascalCase(str) {
1465
+ if (!isString(str)) {
1466
+ return ''
1467
+ }
1468
+ const camel = camelCase(str)
1469
+ return camel.charAt(0).toUpperCase() + camel.slice(1)
1470
+ }
1471
+
1472
+ /**
1473
+ * 将字符串转换为 snake_case
1474
+ * @param {string} str
1475
+ * @returns {string}
1476
+ * @example
1477
+ * snakeCase('helloWorld') // 'hello_world'
1478
+ * snakeCase('Hello World') // 'hello_world'
1479
+ * snakeCase('hello-world') // 'hello_world'
1480
+ */
1481
+ export function snakeCase(str) {
1482
+ if (!isString(str)) {
1483
+ return ''
1484
+ }
1485
+ return str
1486
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
1487
+ .replace(/[\s-]+/g, '_')
1488
+ .toLowerCase()
1489
+ }
1490
+
1491
+ /**
1492
+ * 将字符串转换为首字母大写
1493
+ * @param {string} str
1494
+ * @returns {string}
1495
+ * @example
1496
+ * capitalize('hello') // 'Hello'
1497
+ * capitalize('HELLO') // 'Hello'
1498
+ * capitalize('hello world') // 'Hello world'
1499
+ */
1500
+ export function capitalize(str) {
1501
+ if (!isString(str)) {
1502
+ return ''
1503
+ }
1504
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
1505
+ }
1506
+
1507
+ /**
1508
+ * 将字符串重复指定次数
1509
+ * @param {string} str
1510
+ * @param {number} n
1511
+ * @returns {string}
1512
+ * @example
1513
+ * repeat('abc', 3) // 'abcabcabc'
1514
+ * repeat('abc', 0) // ''
1515
+ * repeat('abc', 1) // 'abc'
1516
+ */
1517
+ export function repeat(str, n = 0) {
1518
+ if (!isString(str) || n < 1) {
1519
+ return ''
1520
+ }
1521
+ return str.repeat(Math.floor(n))
1522
+ }
1523
+
1524
+ /**
1525
+ * 将字符串填充到指定长度
1526
+ * @param {string} str
1527
+ * @param {number} length
1528
+ * @param {string} [chars=' ']
1529
+ * @returns {string}
1530
+ * @example
1531
+ * pad('abc', 8) // ' abc '
1532
+ * pad('abc', 8, '_-') // '_-abc_-_'
1533
+ * pad('abc', 3) // 'abc'
1534
+ */
1535
+ export function pad(str, length, chars = ' ') {
1536
+ if (!isString(str) || length <= str.length) {
1537
+ return str
1538
+ }
1539
+ const totalPad = length - str.length
1540
+ const padStart = Math.floor(totalPad / 2)
1541
+ const padEnd = totalPad - padStart
1542
+ return chars.repeat(padStart) + str + chars.repeat(padEnd)
1543
+ }
1544
+
1545
+ /**
1546
+ * 左侧填充字符串
1547
+ * @param {string} str
1548
+ * @param {number} length
1549
+ * @param {string} [chars=' ']
1550
+ * @returns {string}
1551
+ * @example
1552
+ * padStart('abc', 6) // ' abc'
1553
+ * padStart('abc', 6, '_-') // '_-_abc'
1554
+ * padStart('abc', 3) // 'abc'
1555
+ */
1556
+ export function padStart(str, length, chars = ' ') {
1557
+ if (!isString(str) || length <= str.length) {
1558
+ return str
1559
+ }
1560
+ return chars.repeat(length - str.length) + str
1561
+ }
1562
+
1563
+ /**
1564
+ * 右侧填充字符串
1565
+ * @param {string} str
1566
+ * @param {number} length
1567
+ * @param {string} [chars=' ']
1568
+ * @returns {string}
1569
+ * @example
1570
+ * padEnd('abc', 6) // 'abc '
1571
+ * padEnd('abc', 6, '_-') // 'abc_-_'
1572
+ * padEnd('abc', 3) // 'abc'
1573
+ */
1574
+ export function padEnd(str, length, chars = ' ') {
1575
+ if (!isString(str) || length <= str.length) {
1576
+ return str
1577
+ }
1578
+ return str + chars.repeat(length - str.length)
1579
+ }
1580
+
1581
+ /**
1582
+ * 去除字符串两端的空白
1583
+ * @param {string} str
1584
+ * @returns {string}
1585
+ * @example
1586
+ * trim(' abc ') // 'abc'
1587
+ * trim('abc') // 'abc'
1588
+ * trim(null) // ''
1589
+ */
1590
+ export function trim(str) {
1591
+ return isString(str) ? str.trim() : ''
1592
+ }
1593
+
1594
+ /**
1595
+ * 去除字符串左端的空白
1596
+ * @param {string} str
1597
+ * @returns {string}
1598
+ * @example
1599
+ * trimStart(' abc ') // 'abc '
1600
+ * trimStart('abc') // 'abc'
1601
+ * trimStart(null) // ''
1602
+ */
1603
+ export function trimStart(str) {
1604
+ return isString(str) ? str.trimStart() : ''
1605
+ }
1606
+
1607
+ /**
1608
+ * 去除字符串右端的空白
1609
+ * @param {string} str
1610
+ * @returns {string}
1611
+ * @example
1612
+ * trimEnd(' abc ') // ' abc'
1613
+ * trimEnd('abc') // 'abc'
1614
+ * trimEnd(null) // ''
1615
+ */
1616
+ export function trimEnd(str) {
1617
+ return isString(str) ? str.trimEnd() : ''
1618
+ }
1619
+
1620
+ /**
1621
+ * 将字符串截断到指定长度
1622
+ * @param {string} str
1623
+ * @param {Object} [options]
1624
+ * @param {number} [options.length=30] - 最大长度
1625
+ * @param {string} [options.omission='...'] - 截断标记
1626
+ * @param {boolean} [options.separator=false] - 是否在单词边界截断
1627
+ * @returns {string}
1628
+ * @example
1629
+ * truncate('hello world', {length: 8}) // 'he...'
1630
+ * truncate('hello world', {length: 8, omission: '→'}) // 'hello w→'
1631
+ * truncate('hello world', {length: 15}) // 'hello world'
1632
+ */
1633
+ export function truncate(str, options = {}) {
1634
+ if (!isString(str)) {
1635
+ return ''
1636
+ }
1637
+
1638
+ const {
1639
+ length: maxLength = 30,
1640
+ omission = '...',
1641
+ separator = false
1642
+ } = options
1643
+
1644
+ if (str.length <= maxLength) {
1645
+ return str
1646
+ }
1647
+
1648
+ let result = str.slice(0, maxLength - omission.length)
1649
+
1650
+ if (separator) {
1651
+ const separatorIndex = result.lastIndexOf(/\s/)
1652
+ if (separatorIndex > 0) {
1653
+ result = result.slice(0, separatorIndex)
1654
+ }
1655
+ }
1656
+
1657
+ return result + omission
1658
+ }
1659
+
1660
+ /**
1661
+ * 转义 HTML 字符
1662
+ * @param {string} str
1663
+ * @returns {string}
1664
+ * @example
1665
+ * escape('<script>alert("XSS")</script>') // '&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;'
1666
+ * escape('a & b') // 'a &amp; b'
1667
+ * escape('hello') // 'hello'
1668
+ */
1669
+ export function escape(str) {
1670
+ if (!isString(str)) {
1671
+ return ''
1672
+ }
1673
+ const escapes = {
1674
+ '&': '&amp;',
1675
+ '<': '&lt;',
1676
+ '>': '&gt;',
1677
+ '"': '&quot;',
1678
+ "'": '&#39;'
1679
+ }
1680
+ return str.replace(/[&<>"']/g, char => escapes[char])
1681
+ }
1682
+
1683
+ /**
1684
+ * 反转义 HTML 字符
1685
+ * @param {string} str
1686
+ * @returns {string}
1687
+ * @example
1688
+ * unescape('&lt;script&gt;') // '<script>'
1689
+ * unescape('&amp;') // '&'
1690
+ * unescape('hello') // 'hello'
1691
+ */
1692
+ export function unescape(str) {
1693
+ if (!isString(str)) {
1694
+ return ''
1695
+ }
1696
+ const escapes = {
1697
+ '&amp;': '&',
1698
+ '&lt;': '<',
1699
+ '&gt;': '>',
1700
+ '&quot;': '"',
1701
+ '&#39;': "'"
1702
+ }
1703
+ return str.replace(/&(amp|lt|gt|quot|#39);/g, match => escapes[match])
1704
+ }
1705
+
1706
+ /**
1707
+ * 转义正则表达式特殊字符
1708
+ * @param {*} [value=''] - 需要转义的字符串
1709
+ * @returns {string} 转义后的字符串,可安全用于 RegExp 构造函数
1710
+ * @example
1711
+ * escapeRegexpString('hello.world') // 'hello\\.world'
1712
+ * escapeRegexpString('(test)') // '\\(test\\)'
1713
+ * escapeRegexpString('a+b*c?') // 'a\\+b\\*c\\?'
1714
+ */
1715
+ export function escapeRegexpString(value = '') {
1716
+ return String(value).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
1717
+ }
1718
+
1719
+ // ==================== 数字函数 ====================
1720
+
1721
+ /**
1722
+ * 将数字四舍五入到指定的小数位数
1723
+ * @param {number} num
1724
+ * @param {number} [precision=0] - 小数位数
1725
+ * @returns {number}
1726
+ * @example
1727
+ * round(4.006) // 4
1728
+ * round(4.006, 2) // 4.01
1729
+ * round(4060, -2) // 4100
1730
+ */
1731
+ export function round(num, precision = 0) {
1732
+ if (!isFiniteNumber(num)) {
1733
+ return num
1734
+ }
1735
+ const factor = Math.pow(10, precision)
1736
+ return Math.round(num * factor) / factor
1737
+ }
1738
+
1739
+ /**
1740
+ * 将数字向上取整到指定的小数位数
1741
+ * @param {number} num
1742
+ * @param {number} [precision=0]
1743
+ * @returns {number}
1744
+ * @example
1745
+ * ceil(4.006) // 5
1746
+ * ceil(4.006, 2) // 4.01
1747
+ * ceil(4060, -2) // 4100
1748
+ */
1749
+ export function ceil(num, precision = 0) {
1750
+ if (!isFiniteNumber(num)) {
1751
+ return num
1752
+ }
1753
+ const factor = Math.pow(10, precision)
1754
+ return Math.ceil(num * factor) / factor
1755
+ }
1756
+
1757
+ /**
1758
+ * 将数字向下取整到指定的小数位数
1759
+ * @param {number} num
1760
+ * @param {number} [precision=0]
1761
+ * @returns {number}
1762
+ * @example
1763
+ * floor(4.006) // 4
1764
+ * floor(4.006, 2) // 4
1765
+ * floor(4060, -2) // 4000
1766
+ */
1767
+ export function floor(num, precision = 0) {
1768
+ if (!isFiniteNumber(num)) {
1769
+ return num
1770
+ }
1771
+ const factor = Math.pow(10, precision)
1772
+ return Math.floor(num * factor) / factor
1773
+ }
1774
+
1775
+ /**
1776
+ * 获取随机数
1777
+ * @param {number} [min=0]
1778
+ * @param {number} [max=1]
1779
+ * @returns {number}
1780
+ * @example
1781
+ * random(0, 5) // 0 到 5 之间的随机数
1782
+ * random(1.2, 5.5) // 1.2 到 5.5 之间的随机数
1783
+ * random() // 0 到 1 之间的随机数
1784
+ */
1785
+ export function random(min = 0, max = 1) {
1786
+ if (max < min) {
1787
+ [min, max] = [max, min]
1788
+ }
1789
+ return Math.random() * (max - min) + min
1790
+ }
1791
+
1792
+ /**
1793
+ * 获取范围内的随机整数
1794
+ * @param {number} [min=0]
1795
+ * @param {number} [max=1]
1796
+ * @returns {number}
1797
+ * @example
1798
+ * randomInt(0, 5) // 0 到 5 之间的随机整数
1799
+ * randomInt(3) // 0 到 3 之间的随机整数
1800
+ * randomInt() // 0 或 1
1801
+ */
1802
+ export function randomInt(min = 0, max = 1) {
1803
+ if (max < min) {
1804
+ [min, max] = [max, min]
1805
+ }
1806
+ min = Math.ceil(min)
1807
+ max = Math.floor(max)
1808
+ return Math.floor(Math.random() * (max - min + 1)) + min
1809
+ }
1810
+
1811
+ /**
1812
+ * 确保数字在范围内
1813
+ * @param {number} num
1814
+ * @param {number} min
1815
+ * @param {number} max
1816
+ * @returns {number}
1817
+ * @example
1818
+ * clamp(-10, 0, 100) // 0
1819
+ * clamp(50, 0, 100) // 50
1820
+ * clamp(150, 0, 100) // 100
1821
+ */
1822
+ export function clamp(num, min, max) {
1823
+ return Math.min(Math.max(num, min), max)
1824
+ }
1825
+
1826
+ /**
1827
+ * 检查值是否在范围内
1828
+ * @param {number} value
1829
+ * @param {number} min
1830
+ * @param {number} max
1831
+ * @param {Object} [options]
1832
+ * @param {boolean} [options.leftClosed=true]
1833
+ * @param {boolean} [options.rightClosed=true]
1834
+ * @returns {boolean}
1835
+ * @example
1836
+ * isInRange(5, 1, 10) // true
1837
+ * isInRange(1, 1, 10, {leftClosed: false}) // false
1838
+ * isInRange(10, 1, 10, {rightClosed: false}) // false
1839
+ */
1840
+ export function isInRange(value, min, max, options = {}) {
1841
+ const {
1842
+ leftClosed = true,
1843
+ rightClosed = true
1844
+ } = options
1845
+
1846
+ if (!isFiniteNumber(value) || !isFiniteNumber(min) || !isFiniteNumber(max)) {
1847
+ return false
1848
+ }
1849
+
1850
+ const [minVal, maxVal] = min <= max ? [min, max] : [max, min]
1851
+
1852
+ const leftCheck = leftClosed ? value >= minVal : value > minVal
1853
+ const rightCheck = rightClosed ? value <= maxVal : value < maxVal
1854
+
1855
+ return leftCheck && rightCheck
1856
+ }
1857
+
1858
+ // ==================== 函数函数 ====================
1859
+
1860
+ /**
1861
+ * 创建一个防抖函数
1862
+ * @param {Function} func
1863
+ * @param {number} wait - 等待毫秒数
1864
+ * @param {Object} [options]
1865
+ * @param {boolean} [options.immediate=false] - 是否立即执行
1866
+ * @returns {Function}
1867
+ * @example
1868
+ * const debouncedFn = debounce(() => console.log('executed'), 300)
1869
+ * debouncedFn() // 300ms 后执行
1870
+ * const immediateDebounce = debounce(fn, 300, {immediate: true}) // 立即执行
1871
+ */
1872
+ export function debounce(func, wait, options = {}) {
1873
+ const { immediate = false } = options
1874
+ let timeout = null
1875
+
1876
+ return function (...args) {
1877
+ const context = this
1878
+ const later = () => {
1879
+ timeout = null
1880
+ if (!immediate) {
1881
+ func.apply(context, args)
1882
+ }
1883
+ }
1884
+
1885
+ const callNow = immediate && !timeout
1886
+ clearTimeout(timeout)
1887
+ timeout = setTimeout(later, wait)
1888
+
1889
+ if (callNow) {
1890
+ func.apply(context, args)
1891
+ }
1892
+ }
1893
+ }
1894
+
1895
+ /**
1896
+ * 创建一个节流函数
1897
+ * @param {Function} func
1898
+ * @param {number} wait - 等待毫秒数
1899
+ * @param {Object} [options]
1900
+ * @param {boolean} [options.leading=true] - 是否执行开头
1901
+ * @param {boolean} [options.trailing=true] - 是否执行结尾
1902
+ * @returns {Function}
1903
+ * @example
1904
+ * const throttledFn = throttle(() => console.log('executed'), 1000)
1905
+ * throttledFn() // 立即执行,然后 1 秒内不再执行
1906
+ * const trailingFn = throttle(fn, 1000, {leading: false}) // 只执行结尾
1907
+ */
1908
+ export function throttle(func, wait, options = {}) {
1909
+ const { leading = true, trailing = true } = options
1910
+ let timeout = null
1911
+ let previous = 0
1912
+ let lastArgs = null
1913
+ let lastContext = null
1914
+
1915
+ const invoke = () => {
1916
+ previous = leading ? Date.now() : 0
1917
+ if (trailing && lastArgs) {
1918
+ func.apply(lastContext, lastArgs)
1919
+ lastArgs = null
1920
+ lastContext = null
1921
+ }
1922
+ }
1923
+
1924
+ return function (...args) {
1925
+ const now = Date.now()
1926
+
1927
+ if (!previous && !leading) {
1928
+ previous = now
1929
+ }
1930
+
1931
+ const remaining = wait - (now - previous)
1932
+ lastArgs = args
1933
+ lastContext = this
1934
+
1935
+ if (remaining <= 0) {
1936
+ clearTimeout(timeout)
1937
+ timeout = null
1938
+ previous = now
1939
+ func.apply(this, args)
1940
+ } else if (!timeout && trailing) {
1941
+ timeout = setTimeout(invoke, remaining)
1942
+ }
1943
+ }
1944
+ }
1945
+
1946
+ /**
1947
+ * 创建一个只执行一次的函数
1948
+ * @param {Function} func
1949
+ * @returns {Function}
1950
+ * @example
1951
+ * const initOnce = once(() => console.log('initialized'))
1952
+ * initOnce() // 'initialized'
1953
+ * initOnce() // 无输出
1954
+ */
1955
+ export function once(func) {
1956
+ let called = false
1957
+ let result
1958
+
1959
+ return function (...args) {
1960
+ if (!called) {
1961
+ called = true
1962
+ result = func.apply(this, args)
1963
+ }
1964
+ return result
1965
+ }
1966
+ }
1967
+
1968
+ /**
1969
+ * 创建一个取反结果的函数
1970
+ * @param {Function} predicate
1971
+ * @returns {Function}
1972
+ * @example
1973
+ * const isEven = n => n % 2 === 0
1974
+ * const isOdd = negate(isEven)
1975
+ * isOdd(3) // true
1976
+ * isOdd(2) // false
1977
+ */
1978
+ export function negate(predicate) {
1979
+ return function (...args) {
1980
+ return !predicate.apply(this, args)
1981
+ }
1982
+ }
1983
+
1984
+ /**
1985
+ * 空函数,什么都不做
1986
+ * @returns {void}
1987
+ * @example
1988
+ * noop() // undefined
1989
+ * const callback = options.callback || noop
1990
+ */
1991
+ export function noop() {}
1992
+
1993
+ /**
1994
+ * 返回传入的值(恒等函数)
1995
+ * @param {*} value
1996
+ * @returns {*}
1997
+ * @example
1998
+ * identity(1) // 1
1999
+ * identity({a: 1}) // {a: 1}
2000
+ * [1, 2, 3].map(identity) // [1, 2, 3]
2001
+ */
2002
+ export function identity(value) {
2003
+ return value
2004
+ }
2005
+
2006
+ /**
2007
+ * 创建一个返回固定值的函数
2008
+ * @param {*} value
2009
+ * @returns {Function}
2010
+ * @example
2011
+ * const returnZero = constant(0)
2012
+ * returnZero() // 0
2013
+ * const defaultOptions = constant({debug: false})
2014
+ */
2015
+ export function constant(value) {
2016
+ return () => value
2017
+ }
2018
+
2019
+ /**
2020
+ * 创建一个组合函数(从右到左执行)
2021
+ * @param {...Function} funcs
2022
+ * @returns {Function}
2023
+ * @example
2024
+ * const double = n => n * 2
2025
+ * const addOne = n => n + 1
2026
+ * const transform = compose(double, addOne)
2027
+ * transform(5) // 12 (先加 1 得 6,再乘 2 得 12)
2028
+ */
2029
+ export function compose(...funcs) {
2030
+ return function (arg) {
2031
+ return funcs.reduceRight((result, func) => func(result), arg)
2032
+ }
2033
+ }
2034
+
2035
+ /**
2036
+ * 创建一个组合函数(从左到右执行)
2037
+ * @param {...Function} funcs
2038
+ * @returns {Function}
2039
+ * @example
2040
+ * const double = n => n * 2
2041
+ * const addOne = n => n + 1
2042
+ * const transform = pipe(double, addOne)
2043
+ * transform(5) // 11 (先乘 2 得 10,再加 1 得 11)
2044
+ */
2045
+ export function pipe(...funcs) {
2046
+ return function (arg) {
2047
+ return funcs.reduce((result, func) => func(result), arg)
2048
+ }
2049
+ }
2050
+
2051
+ // ==================== 日期函数 ====================
2052
+
2053
+ /**
2054
+ * 格式化日期
2055
+ * @param {Date|string|number} date
2056
+ * @param {string} [format='YYYY-MM-DD HH:mm:ss']
2057
+ * @returns {string}
2058
+ * @example
2059
+ * formatDate(new Date('2024-01-15')) // '2024-01-15 00:00:00'
2060
+ * formatDate(new Date(), 'YYYY/MM/DD') // '2024/03/25'
2061
+ * formatDate(1705305600000) // '2024-01-15 00:00:00'
2062
+ */
2063
+ export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
2064
+ if (!date) {
2065
+ return ''
2066
+ }
2067
+
2068
+ const d = new Date(date)
2069
+ if (isNaN(d.getTime())) {
2070
+ return ''
2071
+ }
2072
+
2073
+ const year = d.getFullYear()
2074
+ const month = String(d.getMonth() + 1).padStart(2, '0')
2075
+ const day = String(d.getDate()).padStart(2, '0')
2076
+ const hours = String(d.getHours()).padStart(2, '0')
2077
+ const minutes = String(d.getMinutes()).padStart(2, '0')
2078
+ const seconds = String(d.getSeconds()).padStart(2, '0')
2079
+ const milliseconds = String(d.getMilliseconds()).padStart(3, '0')
2080
+
2081
+ return format
2082
+ .replace('YYYY', year)
2083
+ .replace('MM', month)
2084
+ .replace('DD', day)
2085
+ .replace('HH', hours)
2086
+ .replace('mm', minutes)
2087
+ .replace('ss', seconds)
2088
+ .replace('SSS', milliseconds)
2089
+ }
2090
+
2091
+ /**
2092
+ * 解析日期字符串
2093
+ * @param {string} str
2094
+ * @param {string} [format='YYYY-MM-DD HH:mm:ss']
2095
+ * @returns {Date|null}
2096
+ * @example
2097
+ * parseDate('2024-01-15 12:30:00') // Date 对象
2098
+ * parseDate('2024/01/15', 'YYYY/MM/DD') // Date 对象
2099
+ * parseDate('invalid') // null
2100
+ */
2101
+ export function parseDate(str, format = 'YYYY-MM-DD HH:mm:ss') {
2102
+ if (!str) {
2103
+ return null
2104
+ }
2105
+
2106
+ const parts = {}
2107
+ const pattern = format
2108
+ .replace('YYYY', '(?<year>\\d{4})')
2109
+ .replace('MM', '(?<month>\\d{2})')
2110
+ .replace('DD', '(?<day>\\d{2})')
2111
+ .replace('HH', '(?<hours>\\d{2})')
2112
+ .replace('mm', '(?<minutes>\\d{2})')
2113
+ .replace('ss', '(?<seconds>\\d{2})')
2114
+ .replace('SSS', '(?<milliseconds>\\d{3})')
2115
+
2116
+ const regex = new RegExp(pattern)
2117
+ const match = str.match(regex)
2118
+
2119
+ if (!match || !match.groups) {
2120
+ return null
2121
+ }
2122
+
2123
+ const { year, month, day, hours, minutes, seconds, milliseconds } = match.groups
2124
+
2125
+ return new Date(
2126
+ parseInt(year),
2127
+ parseInt(month) - 1,
2128
+ parseInt(day),
2129
+ parseInt(hours || 0),
2130
+ parseInt(minutes || 0),
2131
+ parseInt(seconds || 0),
2132
+ parseInt(milliseconds || 0)
2133
+ )
2134
+ }
2135
+
2136
+ /**
2137
+ * 获取两个日期之间的差异
2138
+ * @param {Date} date1
2139
+ * @param {Date} date2
2140
+ * @param {string} [unit='ms'] - 单位:ms, s, m, h, d
2141
+ * @returns {number}
2142
+ * @example
2143
+ * dateDiff(new Date('2024-01-02'), new Date('2024-01-01')) // 86400000
2144
+ * dateDiff(new Date('2024-01-02'), new Date('2024-01-01'), 'd') // 1
2145
+ * dateDiff(new Date('2024-01-02'), new Date('2024-01-01'), 'h') // 24
2146
+ */
2147
+ export function dateDiff(date1, date2, unit = 'ms') {
2148
+ const diff = new Date(date1).getTime() - new Date(date2).getTime()
2149
+
2150
+ switch (unit) {
2151
+ case 's': return Math.floor(diff / 1000)
2152
+ case 'm': return Math.floor(diff / 60000)
2153
+ case 'h': return Math.floor(diff / 3600000)
2154
+ case 'd': return Math.floor(diff / 86400000)
2155
+ default: return diff
2156
+ }
2157
+ }
2158
+
2159
+ // ==================== 深度克隆 ====================
2160
+
2161
+ /**
2162
+ * 深度克隆值
2163
+ * @template T
2164
+ * @param {T} obj
2165
+ * @param {WeakMap} [hash] 用于处理循环引用的缓存
2166
+ * @returns {T}
2167
+ * @example
2168
+ * cloneDeep({a: 1, b: {c: 2}}) // 深拷贝对象
2169
+ * cloneDeep([1, [2, [3]]]) // 深拷贝数组
2170
+ * cloneDeep(new Date()) // 深拷贝日期
2171
+ */
2172
+ export function cloneDeep(obj, hash = new WeakMap()) {
2173
+ if (obj === null || typeof obj !== 'object') {
2174
+ return obj
2175
+ }
2176
+
2177
+ if (hash.has(obj)) {
2178
+ return hash.get(obj)
2179
+ }
2180
+
2181
+ if (obj instanceof Date) {
2182
+ return new Date(obj.getTime())
2183
+ }
2184
+
2185
+ if (obj instanceof RegExp) {
2186
+ return new RegExp(obj.source, obj.flags)
2187
+ }
2188
+
2189
+ if (obj instanceof Map) {
2190
+ const result = new Map()
2191
+ hash.set(obj, result)
2192
+ for (const [key, value] of obj.entries()) {
2193
+ result.set(key, cloneDeep(value, hash))
2194
+ }
2195
+ return result
2196
+ }
2197
+
2198
+ if (obj instanceof Set) {
2199
+ const result = new Set()
2200
+ hash.set(obj, result)
2201
+ for (const value of obj.values()) {
2202
+ result.add(cloneDeep(value, hash))
2203
+ }
2204
+ return result
2205
+ }
2206
+
2207
+ if (Array.isArray(obj)) {
2208
+ const result = []
2209
+ hash.set(obj, result)
2210
+ return obj.map(item => cloneDeep(item, hash))
2211
+ }
2212
+
2213
+ if (obj.constructor === Object) {
2214
+ const result = Object.create(Object.getPrototypeOf(obj))
2215
+ hash.set(obj, result)
2216
+ for (const key in obj) {
2217
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
2218
+ result[key] = cloneDeep(obj[key], hash)
2219
+ }
2220
+ }
2221
+ return result
2222
+ }
2223
+
2224
+ return obj
2225
+ }
2226
+
2227
+ // ==================== 工具类函数 ====================
2228
+
2229
+ /**
2230
+ * 创建一个定时器 Promise
2231
+ * @param {number} ms
2232
+ * @returns {Promise<void>}
2233
+ * @example
2234
+ * await sleep(1000) // 等待 1 秒
2235
+ * sleep(500).then(() => console.log('done')) // 500ms 后打印
2236
+ */
2237
+ export function sleep(ms) {
2238
+ return new Promise(resolve => setTimeout(resolve, ms))
2239
+ }
2240
+
2241
+ /**
2242
+ * 尝试执行函数,失败时返回默认值
2243
+ * @template T
2244
+ * @param {Function} fn
2245
+ * @param {T} defaultValue
2246
+ * @returns {T}
2247
+ * @example
2248
+ * tryCatch(() => JSON.parse('{invalid}'), {}) // {}
2249
+ * tryCatch(() => 1 + 1, 0) // 2
2250
+ * tryCatch(() => { throw new Error() }, 'error') // 'error'
2251
+ */
2252
+ export function tryCatch(fn, defaultValue) {
2253
+ try {
2254
+ return fn()
2255
+ } catch (e) {
2256
+ return defaultValue
2257
+ }
2258
+ }
2259
+
2260
+ /**
2261
+ * 创建一个延迟执行的 Promise
2262
+ * @param {number} ms
2263
+ * @param {*} value
2264
+ * @returns {Promise<*>}
2265
+ * @example
2266
+ * await delay(1000, 'result') // 1 秒后返回 'result'
2267
+ * delay(500, {data: 1}).then(console.log) // 500ms 后打印对象
2268
+ */
2269
+ export function delay(ms, value) {
2270
+ return new Promise(resolve => setTimeout(() => resolve(value), ms))
2271
+ }
2272
+
2273
+ // ==================== 导出默认对象 ====================
2274
+
2275
+ export default {
2276
+ // 类型判断
2277
+ isNil,
2278
+ isUndefined,
2279
+ isNull,
2280
+ isBoolean,
2281
+ isNumber,
2282
+ isFiniteNumber,
2283
+ isInteger,
2284
+ isNaN: isNaN,
2285
+ isString,
2286
+ isSymbol,
2287
+ isFunction,
2288
+ isArray,
2289
+ isArrayLike,
2290
+ isObject,
2291
+ isPlainObject,
2292
+ isObjectLike,
2293
+ isDate,
2294
+ isRegExp,
2295
+ isPromise,
2296
+ isMap,
2297
+ isSet,
2298
+ isWeakMap,
2299
+ isWeakSet,
2300
+
2301
+ // 值判断
2302
+ isEmpty,
2303
+ isEqual,
2304
+
2305
+ // 数组函数
2306
+ find,
2307
+ indexOf,
2308
+ lastIndexOf,
2309
+ filter,
2310
+ map,
2311
+ uniq,
2312
+ flatten,
2313
+ flattenDeep,
2314
+ first,
2315
+ last,
2316
+ initial,
2317
+ rest,
2318
+ take,
2319
+ takeRight,
2320
+ concat,
2321
+ union,
2322
+ intersection,
2323
+ difference,
2324
+ chunk,
2325
+ reverse,
2326
+ slice,
2327
+ compact,
2328
+ drop,
2329
+ dropRight,
2330
+
2331
+ // 对象函数
2332
+ keys,
2333
+ values,
2334
+ entries,
2335
+ get,
2336
+ set,
2337
+ unset,
2338
+ has,
2339
+ size,
2340
+ merge,
2341
+ pick,
2342
+ omit,
2343
+ keysToKebabCase,
2344
+ keysToCamelCase,
2345
+
2346
+ // 字符串函数
2347
+ kebabCase,
2348
+ camelCase,
2349
+ pascalCase,
2350
+ snakeCase,
2351
+ capitalize,
2352
+ repeat,
2353
+ pad,
2354
+ padStart,
2355
+ padEnd,
2356
+ trim,
2357
+ trimStart,
2358
+ trimEnd,
2359
+ truncate,
2360
+ escape,
2361
+ unescape,
2362
+
2363
+ // 数字函数
2364
+ round,
2365
+ ceil,
2366
+ floor,
2367
+ random,
2368
+ randomInt,
2369
+ clamp,
2370
+ isInRange,
2371
+
2372
+ // 函数函数
2373
+ debounce,
2374
+ throttle,
2375
+ once,
2376
+ negate,
2377
+ noop,
2378
+ identity,
2379
+ constant,
2380
+ compose,
2381
+ pipe,
2382
+
2383
+ // 日期函数
2384
+ formatDate,
2385
+ parseDate,
2386
+ dateDiff,
2387
+
2388
+ // 深度克隆
2389
+ cloneDeep,
2390
+
2391
+ // 工具类函数
2392
+ sleep,
2393
+ tryCatch,
2394
+ delay
2395
+ }