oxy-uni-ui 1.2.0 → 2.0.0

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 (306) hide show
  1. package/attributes.json +1 -1
  2. package/components/common/abstracts/variable.scss +396 -321
  3. package/components/common/path.ts +9 -0
  4. package/components/common/util.ts +200 -5
  5. package/components/composables/index.ts +1 -0
  6. package/components/composables/useGlobalLoading.ts +42 -0
  7. package/components/composables/useGlobalMessage.ts +48 -0
  8. package/components/composables/useGlobalToast.ts +84 -0
  9. package/components/composables/usePopover.ts +24 -20
  10. package/components/composables/useVirtualScroll.ts +13 -11
  11. package/components/composables/useWindowResize.ts +35 -0
  12. package/components/oxy-action-sheet/index.scss +24 -11
  13. package/components/oxy-action-sheet/oxy-action-sheet.vue +27 -19
  14. package/components/oxy-action-sheet/types.ts +7 -0
  15. package/components/oxy-backtop/index.scss +3 -3
  16. package/components/oxy-backtop/oxy-backtop.vue +9 -6
  17. package/components/oxy-backtop/types.ts +7 -7
  18. package/components/oxy-badge/index.scss +4 -4
  19. package/components/oxy-badge/oxy-badge.vue +3 -3
  20. package/components/oxy-badge/types.ts +2 -2
  21. package/components/oxy-button/index.scss +5 -5
  22. package/components/oxy-button/oxy-button.vue +5 -1
  23. package/components/oxy-calendar/index.scss +11 -11
  24. package/components/oxy-calendar/oxy-calendar.vue +1 -0
  25. package/components/oxy-calendar/types.ts +5 -0
  26. package/components/oxy-calendar-view/month/index.scss +4 -4
  27. package/components/oxy-calendar-view/month/types.ts +36 -0
  28. package/components/oxy-calendar-view/monthPanel/index.scss +7 -7
  29. package/components/oxy-calendar-view/monthPanel/month-panel.vue +14 -8
  30. package/components/oxy-calendar-view/year/index.scss +4 -4
  31. package/components/oxy-calendar-view/yearPanel/index.scss +4 -4
  32. package/components/oxy-calendar-view/yearPanel/year-panel.vue +21 -5
  33. package/components/oxy-card/index.scss +2 -2
  34. package/components/oxy-cell/index.scss +8 -8
  35. package/components/oxy-cell/oxy-cell.vue +15 -2
  36. package/components/oxy-cell/types.ts +4 -0
  37. package/components/oxy-checkbox/index.scss +8 -8
  38. package/components/oxy-checkbox/oxy-checkbox.vue +2 -2
  39. package/components/oxy-checkbox-group/index.scss +2 -2
  40. package/components/oxy-circle/oxy-circle.vue +10 -7
  41. package/components/oxy-circle/types.ts +5 -5
  42. package/components/oxy-col/oxy-col.vue +2 -2
  43. package/components/oxy-col-picker/index.scss +4 -4
  44. package/components/oxy-col-picker/oxy-col-picker.vue +9 -5
  45. package/components/oxy-col-picker/types.ts +12 -3
  46. package/components/oxy-collapse/index.scss +2 -2
  47. package/components/oxy-collapse-item/oxy-collapse-item.vue +3 -3
  48. package/components/oxy-corner/index.scss +32 -32
  49. package/components/oxy-corner/oxy-corner.vue +15 -3
  50. package/components/oxy-corner/types.ts +15 -1
  51. package/components/oxy-count-to/oxy-count-to.vue +3 -3
  52. package/components/oxy-curtain/index.scss +15 -15
  53. package/components/oxy-curtain/oxy-curtain.vue +4 -2
  54. package/components/oxy-curtain/types.ts +6 -1
  55. package/components/oxy-date-strip/index.scss +10 -0
  56. package/components/oxy-date-strip/oxy-date-strip.vue +198 -0
  57. package/components/oxy-date-strip/types.ts +98 -0
  58. package/components/oxy-date-strip/utils.ts +67 -0
  59. package/components/oxy-date-strip-item/index.scss +94 -0
  60. package/components/oxy-date-strip-item/oxy-date-strip-item.vue +102 -0
  61. package/components/oxy-date-strip-item/types.ts +53 -0
  62. package/components/oxy-datetime-picker/index.scss +11 -11
  63. package/components/oxy-datetime-picker/oxy-datetime-picker.vue +4 -1
  64. package/components/oxy-datetime-picker/types.ts +10 -1
  65. package/components/oxy-drop-menu/index.scss +3 -3
  66. package/components/oxy-drop-menu/oxy-drop-menu.vue +3 -3
  67. package/components/oxy-drop-menu-item/index.scss +1 -1
  68. package/components/oxy-drop-menu-item/oxy-drop-menu-item.vue +4 -3
  69. package/components/oxy-drop-menu-item/types.ts +5 -0
  70. package/components/oxy-echarts/index.scss +17 -0
  71. package/components/oxy-echarts/index.ts +1 -0
  72. package/components/oxy-echarts/oxy-echarts.vue +32 -0
  73. package/components/oxy-echarts/types.ts +18 -0
  74. package/components/oxy-fab/index.scss +8 -8
  75. package/components/oxy-fab/oxy-fab.vue +22 -3
  76. package/components/oxy-file-list/index.scss +42 -15
  77. package/components/oxy-file-list/oxy-file-list.vue +208 -34
  78. package/components/oxy-file-list/types.ts +58 -2
  79. package/components/oxy-floating-panel/oxy-floating-panel.vue +13 -9
  80. package/components/oxy-floating-panel/{type.ts → types.ts} +8 -8
  81. package/components/oxy-footer/index.scss +19 -0
  82. package/components/oxy-footer/oxy-footer.vue +78 -0
  83. package/components/oxy-footer/types.ts +17 -0
  84. package/components/oxy-form-item/types.ts +22 -1
  85. package/components/oxy-gap/oxy-gap.vue +2 -2
  86. package/components/oxy-gap/types.ts +2 -2
  87. package/components/oxy-global-loading/oxy-global-loading.vue +53 -0
  88. package/components/oxy-global-message/oxy-global-message.vue +64 -0
  89. package/components/oxy-global-toast/oxy-global-toast.vue +53 -0
  90. package/components/oxy-grid/oxy-grid.vue +1 -1
  91. package/components/oxy-grid/types.ts +1 -1
  92. package/components/oxy-grid-item/index.scss +1 -1
  93. package/components/oxy-grid-item/oxy-grid-item.vue +7 -5
  94. package/components/oxy-grid-item/types.ts +1 -1
  95. package/components/oxy-guidance/index.scss +75 -0
  96. package/components/oxy-guidance/oxy-guidance.vue +201 -0
  97. package/components/oxy-guidance/types.ts +33 -0
  98. package/components/oxy-icon/oxy-icon.vue +2 -2
  99. package/components/oxy-icon/types.ts +1 -1
  100. package/components/oxy-img/oxy-img.vue +4 -4
  101. package/components/oxy-img/types.ts +3 -3
  102. package/components/oxy-img-cropper/index.scss +12 -12
  103. package/components/oxy-img-cropper/oxy-img-cropper.vue +97 -52
  104. package/components/oxy-img-cropper/types.ts +2 -2
  105. package/components/oxy-img-lazy/index.scss +17 -0
  106. package/components/oxy-img-lazy/oxy-img-lazy.vue +332 -0
  107. package/components/oxy-img-lazy/types.ts +69 -0
  108. package/components/oxy-index-anchor/index.scss +2 -2
  109. package/components/oxy-index-anchor/oxy-index-anchor.vue +2 -2
  110. package/components/oxy-index-anchor/{type.ts → types.ts} +3 -0
  111. package/components/oxy-index-bar/index.scss +3 -3
  112. package/components/oxy-index-bar/oxy-index-bar.vue +3 -3
  113. package/components/oxy-index-bar/{type.ts → types.ts} +2 -2
  114. package/components/oxy-input/index.scss +1 -1
  115. package/components/oxy-input-number/index.scss +5 -5
  116. package/components/oxy-input-number/oxy-input-number.vue +2 -2
  117. package/components/oxy-input-number/types.ts +3 -2
  118. package/components/oxy-keyboard/index.scss +5 -5
  119. package/components/oxy-keyboard/key/index.scss +3 -3
  120. package/components/oxy-keyboard/key/index.vue +2 -2
  121. package/components/oxy-keyboard/key/types.ts +15 -0
  122. package/components/oxy-keyboard/oxy-keyboard.vue +1 -0
  123. package/components/oxy-keyboard/types.ts +5 -0
  124. package/components/oxy-link/index.scss +57 -0
  125. package/components/oxy-link/oxy-link.vue +130 -0
  126. package/components/oxy-link/types.ts +81 -0
  127. package/components/oxy-list/index.scss +7 -1
  128. package/components/oxy-list/oxy-list.vue +4 -3
  129. package/components/oxy-list/types.ts +1 -1
  130. package/components/oxy-loading/oxy-loading.vue +8 -4
  131. package/components/oxy-loading/types.ts +1 -1
  132. package/components/oxy-loadmore/index.scss +3 -3
  133. package/components/oxy-long-press-menu/index.scss +93 -0
  134. package/components/oxy-long-press-menu/oxy-long-press-menu.vue +338 -0
  135. package/components/oxy-long-press-menu/types.ts +34 -0
  136. package/components/oxy-message-box/index.scss +12 -11
  137. package/components/oxy-message-box/oxy-message-box.vue +11 -3
  138. package/components/oxy-message-box/types.ts +14 -0
  139. package/components/oxy-navbar/index.scss +2 -2
  140. package/components/oxy-navbar/oxy-navbar.vue +58 -13
  141. package/components/oxy-navbar/types.ts +8 -1
  142. package/components/oxy-navbar-capsule/types.ts +3 -0
  143. package/components/oxy-notice-bar/index.scss +3 -3
  144. package/components/oxy-notice-bar/oxy-notice-bar.vue +9 -5
  145. package/components/oxy-notice-bar/types.ts +3 -3
  146. package/components/oxy-notify/index.ts +1 -0
  147. package/components/oxy-notify/oxy-notify.vue +3 -2
  148. package/components/oxy-notify/types.ts +7 -0
  149. package/components/oxy-pagination/index.scss +1 -1
  150. package/components/oxy-password-input/oxy-password-input.vue +2 -2
  151. package/components/oxy-password-input/types.ts +1 -1
  152. package/components/oxy-picker/index.scss +45 -2
  153. package/components/oxy-picker/oxy-picker.vue +103 -14
  154. package/components/oxy-picker/types.ts +33 -1
  155. package/components/oxy-picker-view/index.scss +3 -3
  156. package/components/oxy-picker-view/oxy-picker-view.vue +4 -4
  157. package/components/oxy-popover/index.scss +9 -9
  158. package/components/oxy-popup/index.scss +2 -2
  159. package/components/oxy-popup/oxy-popup.vue +35 -2
  160. package/components/oxy-popup/types.ts +8 -1
  161. package/components/oxy-progress/index.scss +3 -3
  162. package/components/oxy-qrcode/draw.ts +398 -0
  163. package/components/oxy-qrcode/index.scss +2 -0
  164. package/components/oxy-qrcode/oxy-qrcode.vue +124 -0
  165. package/components/oxy-qrcode/qrcode.ts +936 -0
  166. package/components/oxy-qrcode/types.ts +42 -0
  167. package/components/oxy-radio/index.scss +13 -13
  168. package/components/oxy-radio/oxy-radio.vue +1 -1
  169. package/components/oxy-radio-group/index.scss +2 -2
  170. package/components/oxy-rate/types.ts +4 -4
  171. package/components/oxy-resize/index.scss +2 -2
  172. package/components/oxy-resize/oxy-resize.vue +4 -4
  173. package/components/oxy-resize/types.ts +3 -0
  174. package/components/oxy-rich-text/icon/emjio.svg +1 -0
  175. package/components/oxy-rich-text/icon/quote.svg +1 -0
  176. package/components/oxy-rich-text/icon/text.svg +1 -0
  177. package/components/oxy-rich-text/icon/title.svg +1 -0
  178. package/components/oxy-rich-text/index.scss +160 -0
  179. package/components/oxy-rich-text/mp-html/card/card.vue +122 -0
  180. package/components/oxy-rich-text/mp-html/card/index.js +7 -0
  181. package/components/oxy-rich-text/mp-html/editable/config.js +15 -0
  182. package/components/oxy-rich-text/mp-html/editable/index.js +553 -0
  183. package/components/oxy-rich-text/mp-html/emoji/index.js +203 -0
  184. package/components/oxy-rich-text/mp-html/highlight/config.js +5 -0
  185. package/components/oxy-rich-text/mp-html/highlight/index.js +96 -0
  186. package/components/oxy-rich-text/mp-html/highlight/prism.css +1 -0
  187. package/components/oxy-rich-text/mp-html/highlight/prism.min.js +7 -0
  188. package/components/oxy-rich-text/mp-html/img-cache/index.js +138 -0
  189. package/components/oxy-rich-text/mp-html/latex/index.js +80 -0
  190. package/components/oxy-rich-text/mp-html/latex/katex.css +1 -0
  191. package/components/oxy-rich-text/mp-html/latex/katex.min.js +1 -0
  192. package/components/oxy-rich-text/mp-html/markdown/index.js +50 -0
  193. package/components/oxy-rich-text/mp-html/markdown/marked.min.js +71 -0
  194. package/components/oxy-rich-text/mp-html/mp-html.d.ts +184 -0
  195. package/components/oxy-rich-text/mp-html/mp-html.vue +684 -0
  196. package/components/oxy-rich-text/mp-html/node/node.vue +1172 -0
  197. package/components/oxy-rich-text/mp-html/parser.js +1428 -0
  198. package/components/oxy-rich-text/mp-html/search/index.js +132 -0
  199. package/components/oxy-rich-text/mp-html/style/index.js +129 -0
  200. package/components/oxy-rich-text/mp-html/style/parser.js +175 -0
  201. package/components/oxy-rich-text/mp-html/template/index.js +67 -0
  202. package/components/oxy-rich-text/mp-html/txv-video/index.js +46 -0
  203. package/components/oxy-rich-text/oxy-rich-text.vue +642 -0
  204. package/components/oxy-rich-text/types.ts +76 -0
  205. package/components/oxy-row/oxy-row.vue +3 -3
  206. package/components/oxy-row/types.ts +1 -1
  207. package/components/oxy-search/index.scss +3 -3
  208. package/components/oxy-segmented/index.scss +16 -16
  209. package/components/oxy-segmented/oxy-segmented.vue +23 -3
  210. package/components/oxy-select/index.scss +331 -0
  211. package/components/oxy-select/oxy-select.vue +456 -0
  212. package/components/oxy-select/types.ts +83 -0
  213. package/components/oxy-select-picker/index.scss +7 -7
  214. package/components/oxy-select-picker/oxy-select-picker.vue +4 -0
  215. package/components/oxy-select-picker/types.ts +7 -1
  216. package/components/oxy-sidebar-item/index.scss +1 -1
  217. package/components/oxy-signature/oxy-signature.vue +18 -10
  218. package/components/oxy-signature/types.ts +106 -13
  219. package/components/oxy-skeleton/oxy-skeleton.vue +6 -6
  220. package/components/oxy-skeleton/types.ts +1 -1
  221. package/components/oxy-slider/index.scss +3 -3
  222. package/components/oxy-sort-button/index.scss +8 -8
  223. package/components/oxy-status-tip/index.scss +4 -4
  224. package/components/oxy-status-tip/oxy-status-tip.vue +5 -5
  225. package/components/oxy-status-tip/types.ts +3 -3
  226. package/components/oxy-step/index.scss +14 -14
  227. package/components/oxy-sticky/oxy-sticky.vue +6 -6
  228. package/components/oxy-stream-render/index.scss +6 -0
  229. package/components/oxy-stream-render/oxy-stream-render.vue +204 -0
  230. package/components/oxy-stream-render/types.ts +8 -0
  231. package/components/oxy-swipe-action/oxy-swipe-action.vue +27 -2
  232. package/components/oxy-swiper/oxy-swiper.vue +6 -6
  233. package/components/oxy-swiper/types.ts +5 -5
  234. package/components/oxy-switch/index.scss +8 -8
  235. package/components/oxy-switch/oxy-switch.vue +2 -2
  236. package/components/oxy-switch/types.ts +1 -1
  237. package/components/oxy-tab/index.scss +11 -1
  238. package/components/oxy-tabbar/index.scss +1 -1
  239. package/components/oxy-tabbar/oxy-tabbar.vue +39 -10
  240. package/components/oxy-table/index.scss +5 -5
  241. package/components/oxy-table/oxy-table.vue +8 -6
  242. package/components/oxy-table/types.ts +2 -2
  243. package/components/oxy-table-col/oxy-table-col.vue +3 -3
  244. package/components/oxy-table-col/types.ts +2 -2
  245. package/components/oxy-tabs/index.scss +43 -15
  246. package/components/oxy-tabs/oxy-tabs.vue +53 -19
  247. package/components/oxy-tabs/types.ts +15 -3
  248. package/components/oxy-tag/index.scss +15 -15
  249. package/components/oxy-text/index.scss +5 -1
  250. package/components/oxy-text/oxy-text.vue +76 -7
  251. package/components/oxy-text/types.ts +12 -0
  252. package/components/oxy-textarea/index.scss +6 -6
  253. package/components/oxy-toast/oxy-toast.vue +24 -8
  254. package/components/oxy-tooltip/index.scss +9 -9
  255. package/components/oxy-tree/index.scss +61 -9
  256. package/components/oxy-tree/oxy-tree.vue +102 -17
  257. package/components/oxy-tree/types.ts +23 -10
  258. package/components/oxy-upload/index.scss +21 -21
  259. package/components/oxy-upload/types.ts +2 -2
  260. package/components/oxy-verification-code/index.scss +6 -0
  261. package/components/oxy-verification-code/oxy-verification-code.vue +187 -0
  262. package/components/oxy-verification-code/types.ts +82 -0
  263. package/components/oxy-video-preview/index.scss +4 -4
  264. package/components/oxy-virtual-scroll/index.scss +4 -4
  265. package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +11 -7
  266. package/components/oxy-virtual-scroll/types.ts +14 -14
  267. package/components/oxy-voice-player/index.scss +908 -0
  268. package/components/oxy-voice-player/oxy-voice-player.vue +821 -0
  269. package/components/oxy-voice-player/types.ts +567 -0
  270. package/components/oxy-waterfall/index.scss +18 -0
  271. package/components/oxy-waterfall/oxy-waterfall.vue +218 -0
  272. package/components/oxy-waterfall/types.ts +90 -0
  273. package/components/oxy-waterfall-item/index.scss +8 -0
  274. package/components/oxy-waterfall-item/oxy-waterfall-item.vue +89 -0
  275. package/components/oxy-waterfall-item/types.ts +16 -0
  276. package/components/oxy-watermark/oxy-watermark.vue +35 -13
  277. package/components/oxy-watermark/types.ts +14 -14
  278. package/global.d.ts +9 -0
  279. package/index.ts +3 -0
  280. package/locale/lang/ar-SA.ts +3 -0
  281. package/locale/lang/en-US.ts +29 -0
  282. package/locale/lang/zh-CN.ts +29 -0
  283. package/package.json +97 -1
  284. package/tags.json +1 -1
  285. package/uni-echarts/changelog.md +2 -0
  286. package/uni-echarts/components/index.js +1 -0
  287. package/uni-echarts/components/uni-echarts/events.js +95 -0
  288. package/uni-echarts/components/uni-echarts/types.d.ts +183 -0
  289. package/uni-echarts/components/uni-echarts/types.js +1 -0
  290. package/uni-echarts/components/uni-echarts/uni-echarts.vue +530 -0
  291. package/uni-echarts/components/uni-echarts/uni-echarts.vue.d.ts +19 -0
  292. package/uni-echarts/global.d.ts +7 -0
  293. package/uni-echarts/index.d.ts +440 -0
  294. package/uni-echarts/index.js +2 -0
  295. package/uni-echarts/package.json +105 -0
  296. package/uni-echarts/shared-core.d.ts +269 -0
  297. package/uni-echarts/shared-core.js +900 -0
  298. package/web-types.json +1 -1
  299. package/components/oxy-number-keyboard/index.scss +0 -78
  300. package/components/oxy-number-keyboard/key/index.scss +0 -81
  301. package/components/oxy-number-keyboard/key/index.vue +0 -78
  302. package/components/oxy-number-keyboard/key/types.ts +0 -11
  303. package/components/oxy-number-keyboard/oxy-number-keyboard.vue +0 -151
  304. package/components/oxy-number-keyboard/types.ts +0 -83
  305. package/components/oxy-tree/components/tree-node-content.vue +0 -72
  306. package/components/oxy-tree/index.ts +0 -51
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 获取当前页面路径
3
+ * @returns 当前页面路径
4
+ */
5
+ export function getCurrentPath() {
6
+ const pages = getCurrentPages()
7
+ const currentPage = pages[pages.length - 1]
8
+ return currentPage?.route || ''
9
+ }
@@ -16,13 +16,199 @@ function s4() {
16
16
  .substring(1)
17
17
  }
18
18
 
19
+ type UnitConvertOutput = 'number' | 'px'
20
+ type DefaultUnit = 'px' | 'rpx'
21
+
22
+ /**
23
+ * 通用单位转换函数。
24
+ *
25
+ * 支持输入:
26
+ * - `number`、纯数字字符串(如 `'12'`)
27
+ * - 带单位字符串:`px` / `rpx` / `%` / `em`
28
+ * - CSS 关键字(仅在 `output='px'` 且 `preserveCssKeyword=true` 时原样透传)
29
+ *
30
+ * 转换规则:
31
+ * - `output='number'`(默认):返回数值(px 语义)。
32
+ * - `rpx` -> `uni.upx2px`
33
+ * - `px` -> 原数值
34
+ * - `%` -> 按 `base` 计算
35
+ * - `output='px'`:返回可直接用于内联样式的字符串。
36
+ * - 数字/纯数字字符串 -> `${value}px`
37
+ * - 可识别单位按规则换算后输出 `${n}px`
38
+ * - 关键字(如 `auto`)按配置透传
39
+ *
40
+ * @param value 待转换的值。
41
+ * @param base 百分比计算基准,仅在输入为 `%` 时生效。
42
+ * @param options 转换配置:
43
+ * - `output`:`'number' | 'px'`
44
+ * - `preserveCssKeyword`:`output='px'` 时是否透传 CSS 关键字,默认 `true`
45
+ * @returns `output='number'` 时返回 `number`;`output='px'` 时返回 `string`。
46
+ */
47
+ export function unitConvert<T extends UnitConvertOutput = 'number'>(
48
+ value: string | number | null | undefined,
49
+ base: number = 0,
50
+ options: { output?: T; preserveCssKeyword?: boolean } = {}
51
+ ): T extends 'px' ? string : number {
52
+ const output = options.output ?? 'number'
53
+ const preserveCssKeyword = options.preserveCssKeyword ?? true
54
+
55
+ const toNumber = (input: string | number | null | undefined): number => {
56
+ // 如果是字符串数字
57
+ if (isNumeric(input)) {
58
+ return Number(input)
59
+ }
60
+ // 如果有单位
61
+ if (isString(input)) {
62
+ const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
63
+ const results = reg.exec(input)
64
+ if (!input || !results) {
65
+ return 0
66
+ }
67
+ const unit = results[3]
68
+ const _value = parseFloat(input)
69
+ if (unit === 'rpx') {
70
+ return uni.upx2px(_value)
71
+ }
72
+ if (unit === 'px') {
73
+ return _value * 1
74
+ }
75
+ if (unit == '%') {
76
+ return (_value / 100) * base
77
+ }
78
+ // 如果是其他单位,可以继续添加对应的转换逻辑
79
+ }
80
+ return 0
81
+ }
82
+
83
+ if (output === 'px') {
84
+ if (value === null || value === undefined || value === '') {
85
+ return '0px' as T extends 'px' ? string : number
86
+ }
87
+
88
+ if (isNumber(value)) {
89
+ return `${value}px` as T extends 'px' ? string : number
90
+ }
91
+
92
+ if (isString(value)) {
93
+ const trimmed = value.trim()
94
+
95
+ if (!trimmed) {
96
+ return '0px' as T extends 'px' ? string : number
97
+ }
98
+
99
+ if (
100
+ preserveCssKeyword &&
101
+ (trimmed === 'auto' || trimmed === 'inherit' || trimmed === 'initial' || trimmed === 'unset' || trimmed.endsWith('%'))
102
+ ) {
103
+ return trimmed as T extends 'px' ? string : number
104
+ }
105
+
106
+ if (isNumeric(trimmed)) {
107
+ return `${Number(trimmed)}px` as T extends 'px' ? string : number
108
+ }
109
+
110
+ const convertedValue = toNumber(trimmed)
111
+
112
+ if (convertedValue || /^-?0(\.0+)?(rpx|px)?$/.test(trimmed)) {
113
+ return `${convertedValue}px` as T extends 'px' ? string : number
114
+ }
115
+
116
+ return trimmed as T extends 'px' ? string : number
117
+ }
118
+
119
+ return `${toNumber(value)}px` as T extends 'px' ? string : number
120
+ }
121
+
122
+ return toNumber(value) as T extends 'px' ? string : number
123
+ }
124
+
19
125
  /**
20
- * @description 对num自动填充px
21
- * @param {Number} num
22
- * @return {string} num+px
126
+ * 为“无单位值”补充默认单位。
127
+ *
128
+ * 行为说明:
129
+ * - `number` -> `${value}${defaultUnit}`
130
+ * - 纯数字字符串 -> `${value}${defaultUnit}`
131
+ * - `null/undefined/''` -> `0${defaultUnit}`
132
+ * - 已带单位或其他字符串(如 `300rpx`、`20px`、`50%`、`auto`)原样返回
133
+ *
134
+ * 典型用途:
135
+ * - 统一“number 与纯数字字符串”的默认单位语义(如统一按 `rpx`)。
136
+ *
137
+ * @param value 输入值。
138
+ * @param defaultUnit 默认单位,支持 `'px' | 'rpx'`,默认 `'rpx'`。
139
+ * @returns 补全单位后的字符串结果。
23
140
  */
24
- export function addUnit(num: number | string) {
25
- return Number.isNaN(Number(num)) ? `${num}` : `${num}px`
141
+ export function withDefaultUnit(value: number | string | null | undefined, defaultUnit: DefaultUnit = 'rpx') {
142
+ if (value === null || value === undefined) {
143
+ return `0${defaultUnit}`
144
+ }
145
+
146
+ if (isNumber(value)) {
147
+ return `${value}${defaultUnit}`
148
+ }
149
+
150
+ const trimmed = value.trim()
151
+
152
+ if (!trimmed) {
153
+ return `0${defaultUnit}`
154
+ }
155
+
156
+ if (isNumeric(trimmed)) {
157
+ return `${trimmed}${defaultUnit}`
158
+ }
159
+
160
+ return trimmed
161
+ }
162
+
163
+ /**
164
+ * 先补默认单位,再执行 `unitConvert`,用于得到稳定的数值结果。
165
+ *
166
+ * 等价逻辑:
167
+ * `unitConvert(withDefaultUnit(value, defaultUnit), base)`
168
+ *
169
+ * 适用场景:
170
+ * - 需要做数值比较(如滚动阈值判断),同时希望 `number`/纯数字字符串有明确默认单位语义。
171
+ *
172
+ * @param value 输入值。
173
+ * @param options 配置项:
174
+ * - `defaultUnit`:无单位值补充的默认单位,默认 `'rpx'`
175
+ * - `base`:百分比换算基准,默认 `0`
176
+ * @returns 转换后的数值(px 语义)。
177
+ */
178
+ export function unitConvertWithDefault(value: number | string | null | undefined, options: { defaultUnit?: DefaultUnit; base?: number } = {}) {
179
+ const { defaultUnit = 'rpx', base = 0 } = options
180
+ return unitConvert(withDefaultUnit(value, defaultUnit), base)
181
+ }
182
+
183
+ /**
184
+ * 将长度值解析为样式字符串,仅对 `%` 做屏宽换算。
185
+ *
186
+ * 行为说明:
187
+ * - `number`/纯数字字符串:按 `defaultUnit` 补单位(默认补 `rpx`)
188
+ * - `%`:按 `uni.getSystemInfoSync().windowWidth` 换算后转为 `px`
189
+ * - 其他带单位值(如 `px/rpx`):原样返回
190
+ *
191
+ * @param value 输入长度值。
192
+ * @param options 配置项:
193
+ * - `defaultUnit`:无单位值的默认单位,默认 `'rpx'`
194
+ * @returns 解析后的样式值字符串;当输入为空时返回空字符串。
195
+ */
196
+ export function resolveSizeWithScreenWidth(value: number | string | null | undefined, options: { defaultUnit?: DefaultUnit } = {}) {
197
+ if (value === '' || value === undefined || value === null) {
198
+ return ''
199
+ }
200
+
201
+ const { defaultUnit = 'rpx' } = options
202
+ const normalized = withDefaultUnit(value, defaultUnit)
203
+ const trimmed = normalized.trim()
204
+
205
+ if (trimmed.endsWith('%')) {
206
+ const { windowWidth = 0 } = uni.getSystemInfoSync()
207
+ const percentInPx = unitConvert(trimmed, windowWidth)
208
+ return `${percentInPx * 2}rpx`
209
+ }
210
+
211
+ return normalized
26
212
  }
27
213
 
28
214
  /**
@@ -335,6 +521,15 @@ export function isNumber(value: any): value is number {
335
521
  return getType(value) === 'number'
336
522
  }
337
523
 
524
+ /**
525
+ * 检查一个值是否为数字类型或表示数字的字符串
526
+ * @param value 要检查的值,可以是 string 类型或 number 类型
527
+ * @returns 如果值是数字类型或表示数字的字符串,则返回 true;否则返回 false
528
+ */
529
+ export function isNumeric(value: any): boolean {
530
+ return /^(-)?\d+(\.\d+)?$/.test(value)
531
+ }
532
+
338
533
  /**
339
534
  * 检查给定值是否为 Promise 对象。
340
535
  * @param {unknown} value 要检查的值
@@ -10,3 +10,4 @@ export { useTouch } from './useTouch'
10
10
  export { useTranslate } from './useTranslate'
11
11
  export { useUpload } from './useUpload'
12
12
  export { useVirtualScroll } from './useVirtualScroll'
13
+ export { useWindowResize } from './useWindowResize'
@@ -0,0 +1,42 @@
1
+ import type { ToastOptions } from '../oxy-toast/types'
2
+ import { deepMerge } from '../common/util'
3
+ import { getCurrentPath } from '../common/path'
4
+
5
+ import { defineStore } from 'pinia'
6
+
7
+ interface GlobalLoading {
8
+ loadingOptions: ToastOptions
9
+ currentPage: string
10
+ }
11
+
12
+ const defaultOptions: ToastOptions = {
13
+ show: false
14
+ }
15
+ export const useGlobalLoading = defineStore('global-loading', {
16
+ state: (): GlobalLoading => ({
17
+ loadingOptions: defaultOptions,
18
+ currentPage: ''
19
+ }),
20
+ getters: {},
21
+ actions: {
22
+ // 加载提示
23
+ loading(option: ToastOptions | string) {
24
+ this.currentPage = getCurrentPath()
25
+ this.loadingOptions = deepMerge(
26
+ {
27
+ iconName: 'loading',
28
+ duration: 0,
29
+ cover: true,
30
+ position: 'middle',
31
+ show: true
32
+ },
33
+ typeof option === 'string' ? { msg: option } : option
34
+ ) as ToastOptions
35
+ },
36
+ // 关闭Toast
37
+ close() {
38
+ this.loadingOptions = defaultOptions
39
+ this.currentPage = ''
40
+ }
41
+ }
42
+ })
@@ -0,0 +1,48 @@
1
+ import type { MessageOptions, MessageResult } from '../oxy-message-box/types'
2
+ import { deepMerge, isString } from '../common/util'
3
+ import { defineStore } from 'pinia'
4
+ import { getCurrentPath } from '../common/path'
5
+
6
+ export type GlobalMessageOptions = MessageOptions & {
7
+ success?: (res: MessageResult) => void
8
+ fail?: (res: MessageResult) => void
9
+ }
10
+
11
+ interface GlobalMessage {
12
+ messageOptions: GlobalMessageOptions | null
13
+ currentPage: string
14
+ }
15
+
16
+ export const useGlobalMessage = defineStore('global-message', {
17
+ state: (): GlobalMessage => ({
18
+ messageOptions: null,
19
+ currentPage: ''
20
+ }),
21
+ actions: {
22
+ show(option: GlobalMessageOptions | string) {
23
+ this.currentPage = getCurrentPath()
24
+ this.messageOptions = {
25
+ ...(isString(option) ? { title: option } : option)
26
+ }
27
+ },
28
+ alert(option: GlobalMessageOptions | string) {
29
+ const messageOptions = deepMerge({ type: 'alert' }, isString(option) ? { title: option } : option) as MessageOptions
30
+ messageOptions.showCancelButton = false
31
+ this.show(messageOptions)
32
+ },
33
+ confirm(option: GlobalMessageOptions | string) {
34
+ const messageOptions = deepMerge({ type: 'confirm' }, isString(option) ? { title: option } : option) as MessageOptions
35
+ messageOptions.showCancelButton = true
36
+ this.show(messageOptions)
37
+ },
38
+ prompt(option: GlobalMessageOptions | string) {
39
+ const messageOptions = deepMerge({ type: 'prompt' }, isString(option) ? { title: option } : option) as MessageOptions
40
+ messageOptions.showCancelButton = true
41
+ this.show(messageOptions)
42
+ },
43
+ close() {
44
+ this.messageOptions = null
45
+ this.currentPage = ''
46
+ }
47
+ }
48
+ })
@@ -0,0 +1,84 @@
1
+ import type { ToastOptions } from '../oxy-toast/types'
2
+ import { deepMerge } from '../common/util'
3
+ import { defineStore } from 'pinia'
4
+
5
+ import { getCurrentPath } from '../common/path'
6
+
7
+ interface GlobalToast {
8
+ toastOptions: ToastOptions
9
+ currentPage: string
10
+ }
11
+
12
+ const defaultOptions: ToastOptions = {
13
+ duration: 2000,
14
+ show: false
15
+ }
16
+ export const useGlobalToast = defineStore('global-toast', {
17
+ state: (): GlobalToast => ({
18
+ toastOptions: defaultOptions,
19
+ currentPage: ''
20
+ }),
21
+ getters: {},
22
+ actions: {
23
+ // 打开Toast
24
+ show(option: ToastOptions | string) {
25
+ this.currentPage = getCurrentPath()
26
+ const options = deepMerge(defaultOptions, typeof option === 'string' ? { msg: option } : option) as ToastOptions
27
+ this.toastOptions = deepMerge(options, {
28
+ show: true,
29
+ position: options.position || 'middle'
30
+ }) as ToastOptions
31
+ },
32
+ // 成功提示
33
+ success(option: ToastOptions | string) {
34
+ this.show(
35
+ deepMerge(
36
+ {
37
+ iconName: 'success',
38
+ duration: 1500
39
+ },
40
+ typeof option === 'string' ? { msg: option } : option
41
+ ) as ToastOptions
42
+ )
43
+ },
44
+ // 关闭提示
45
+ error(option: ToastOptions | string) {
46
+ this.show(
47
+ deepMerge(
48
+ {
49
+ iconName: 'error',
50
+ direction: 'vertical'
51
+ },
52
+ typeof option === 'string' ? { msg: option } : option
53
+ ) as ToastOptions
54
+ )
55
+ },
56
+ // 常规提示
57
+ info(option: ToastOptions | string) {
58
+ this.show(
59
+ deepMerge(
60
+ {
61
+ iconName: 'info'
62
+ },
63
+ typeof option === 'string' ? { msg: option } : option
64
+ ) as ToastOptions
65
+ )
66
+ },
67
+ // 警告提示
68
+ warning(option: ToastOptions | string) {
69
+ this.show(
70
+ deepMerge(
71
+ {
72
+ iconName: 'warning'
73
+ },
74
+ typeof option === 'string' ? { msg: option } : option
75
+ ) as ToastOptions
76
+ )
77
+ },
78
+ // 关闭Toast
79
+ close() {
80
+ this.toastOptions = defaultOptions
81
+ this.currentPage = ''
82
+ }
83
+ }
84
+ })
@@ -1,5 +1,5 @@
1
1
  import { getCurrentInstance, ref } from 'vue'
2
- import { getRect, isObj } from '../common/util'
2
+ import { getRect, isObj, unitConvert } from '../common/util'
3
3
 
4
4
  export function usePopover(visibleArrow = true) {
5
5
  const { proxy } = getCurrentInstance() as any
@@ -14,6 +14,7 @@ export function usePopover(visibleArrow = true) {
14
14
  const width = ref<number>(0)
15
15
  const height = ref<number>(0)
16
16
  const top = ref<number>(0)
17
+ const toPx = (value: number) => unitConvert(value, 0, { output: 'px' })
17
18
 
18
19
  function noop() {}
19
20
 
@@ -107,64 +108,67 @@ export function usePopover(visibleArrow = true) {
107
108
 
108
109
  const placements = new Map([
109
110
  // 上
110
- ['top', [`left: ${verticalX}px; bottom: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
111
+ ['top', [`left: ${toPx(verticalX)}; bottom: ${toPx(verticalY)}; transform: translateX(-50%);`, 'left: 50%;']],
111
112
  [
112
113
  'top-start',
113
114
  [
114
- `left: ${offsetX}px; bottom: ${verticalY}px;`,
115
- `left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`
115
+ `left: ${toPx(offsetX)}; bottom: ${toPx(verticalY)};`,
116
+ `left: ${toPx((popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX)};`
116
117
  ]
117
118
  ],
118
119
  [
119
120
  'top-end',
120
121
  [
121
- `right: ${offsetX}px; bottom: ${verticalY}px;`,
122
- `right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
122
+ `right: ${toPx(offsetX)}; bottom: ${toPx(verticalY)};`,
123
+ `right: ${toPx((popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX)}; transform: translateX(50%);`
123
124
  ]
124
125
  ],
125
126
  // 下
126
- ['bottom', [`left: ${verticalX}px; top: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
127
+ ['bottom', [`left: ${toPx(verticalX)}; top: ${toPx(verticalY)}; transform: translateX(-50%);`, 'left: 50%;']],
127
128
  [
128
129
  'bottom-start',
129
- [`left: ${offsetX}px; top: ${verticalY}px;`, `left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`]
130
+ [
131
+ `left: ${toPx(offsetX)}; top: ${toPx(verticalY)};`,
132
+ `left: ${toPx((popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX)};`
133
+ ]
130
134
  ],
131
135
  [
132
136
  'bottom-end',
133
137
  [
134
- `right: ${offsetX}px; top: ${verticalY}px;`,
135
- `right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
138
+ `right: ${toPx(offsetX)}; top: ${toPx(verticalY)};`,
139
+ `right: ${toPx((popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX)}; transform: translateX(50%);`
136
140
  ]
137
141
  ],
138
142
  // 左
139
- ['left', [`right: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
143
+ ['left', [`right: ${toPx(horizontalX)}; top: ${toPx(horizontalY)}; transform: translateY(-50%);`, 'top: 50%']],
140
144
  [
141
145
  'left-start',
142
146
  [
143
- `right: ${horizontalX}px; top: ${offsetY}px;`,
144
- `top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
147
+ `right: ${toPx(horizontalX)}; top: ${toPx(offsetY)};`,
148
+ `top: ${toPx((popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY)};`
145
149
  ]
146
150
  ],
147
151
  [
148
152
  'left-end',
149
153
  [
150
- `right: ${horizontalX}px; bottom: ${offsetY}px;`,
151
- `bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
154
+ `right: ${toPx(horizontalX)}; bottom: ${toPx(offsetY)};`,
155
+ `bottom: ${toPx((popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY)}; transform: translateY(50%);`
152
156
  ]
153
157
  ],
154
158
  // 右
155
- ['right', [`left: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
159
+ ['right', [`left: ${toPx(horizontalX)}; top: ${toPx(horizontalY)}; transform: translateY(-50%);`, 'top: 50%']],
156
160
  [
157
161
  'right-start',
158
162
  [
159
- `left: ${horizontalX}px; top: ${offsetY}px;`,
160
- `top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
163
+ `left: ${toPx(horizontalX)}; top: ${toPx(offsetY)};`,
164
+ `top: ${toPx((popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY)};`
161
165
  ]
162
166
  ],
163
167
  [
164
168
  'right-end',
165
169
  [
166
- `left: ${horizontalX}px; bottom: ${offsetY}px;`,
167
- `bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
170
+ `left: ${toPx(horizontalX)}; bottom: ${toPx(offsetY)};`,
171
+ `bottom: ${toPx((popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY)}; transform: translateY(50%);`
168
172
  ]
169
173
  ]
170
174
  ])
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { ref, computed, watch, nextTick, type Ref } from 'vue'
6
6
  import { VirtualScrollEngine } from '../oxy-virtual-scroll/virtual-scroll'
7
+ import { unitConvert, unitConvertWithDefault } from '../common/util'
7
8
 
8
9
  export interface UseVirtualScrollOptions {
9
10
  data: Ref<any[]>
@@ -41,6 +42,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
41
42
 
42
43
  // 响应式数据
43
44
  const scrollTop = ref<number>(0)
45
+ const _scrollTop = ref<number>(0)
44
46
  const showBackTopBtn = ref<boolean>(false)
45
47
  const virtualData = ref<any[]>([])
46
48
  const startIndex = ref<number>(0)
@@ -51,7 +53,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
51
53
  const displayData = computed<any[]>(() => data.value)
52
54
 
53
55
  const totalHeight = computed(() => {
54
- return displayData.value.length * parseFloat(itemHeight.value)
56
+ return displayData.value.length * unitConvertWithDefault(itemHeight.value, { defaultUnit: 'rpx' })
55
57
  })
56
58
 
57
59
  // 监听数据变化
@@ -76,8 +78,8 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
76
78
  }
77
79
 
78
80
  virtualEngine.value = new VirtualScrollEngine({
79
- containerHeight: parseFloat(height.value),
80
- itemHeight: parseFloat(itemHeight.value),
81
+ containerHeight: unitConvert(height.value),
82
+ itemHeight: unitConvertWithDefault(itemHeight.value, { defaultUnit: 'rpx' }),
81
83
  data: displayData.value
82
84
  })
83
85
  updateVisibleData()
@@ -88,8 +90,8 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
88
90
  if (!virtual.value) return
89
91
 
90
92
  virtualEngine.value = new VirtualScrollEngine({
91
- containerHeight: parseFloat(height.value),
92
- itemHeight: parseFloat(itemHeight.value),
93
+ containerHeight: unitConvert(height.value),
94
+ itemHeight: unitConvertWithDefault(itemHeight.value, { defaultUnit: 'rpx' }),
93
95
  data: displayData.value
94
96
  })
95
97
  }
@@ -99,7 +101,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
99
101
  if (!virtual.value) return // 非虚拟模式不处理
100
102
 
101
103
  if (virtualEngine.value) {
102
- const { visibleData, offsetY } = virtualEngine.value.updateVisibleData(scrollTop.value || 0)
104
+ const { visibleData, offsetY } = virtualEngine.value.updateVisibleData(_scrollTop.value || 0)
103
105
  virtualData.value = visibleData
104
106
  virtualOffsetY.value = offsetY
105
107
  }
@@ -107,8 +109,8 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
107
109
 
108
110
  // 滚动事件处理
109
111
  function onScroll(scrollTopValue: number) {
110
- scrollTop.value = scrollTopValue
111
- showBackTopBtn.value = scrollTopValue > parseFloat(backToTopThreshold.value)
112
+ _scrollTop.value = scrollTopValue
113
+ showBackTopBtn.value = scrollTopValue > unitConvertWithDefault(backToTopThreshold.value, { defaultUnit: 'rpx' })
112
114
  updateVisibleData()
113
115
  }
114
116
 
@@ -127,14 +129,14 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
127
129
 
128
130
  // 滚动到指定位置
129
131
  function scrollToPosition(position: number | string) {
130
- scrollTop.value = typeof position === 'number' ? position : parseFloat(position)
132
+ scrollTop.value = unitConvertWithDefault(position, { defaultUnit: 'rpx' })
131
133
  }
132
134
 
133
135
  // 滚动到指定元素
134
136
  function scrollToElement(item: any) {
135
137
  const index = data.value.findIndex((o) => item[idKey.value] && o[idKey.value] && o[idKey.value] === item[idKey.value])
136
138
  if (index > 0) {
137
- const scrollDistance = parseFloat(itemHeight.value) * index
139
+ const scrollDistance = unitConvertWithDefault(itemHeight.value, { defaultUnit: 'rpx' }) * index
138
140
  scrollToPosition(scrollDistance)
139
141
  }
140
142
  }
@@ -143,7 +145,7 @@ export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualSc
143
145
  function scrollToElementById(id: string | number) {
144
146
  const index = data.value.findIndex((o) => id && o[idKey.value] && o[idKey.value] === id)
145
147
  if (index > 0) {
146
- const scrollDistance = parseFloat(itemHeight.value) * index
148
+ const scrollDistance = unitConvertWithDefault(itemHeight.value, { defaultUnit: 'rpx' }) * index
147
149
  scrollToPosition(scrollDistance)
148
150
  }
149
151
  }
@@ -0,0 +1,35 @@
1
+ import { onBeforeUnmount, onMounted } from 'vue'
2
+
3
+ export interface WindowResizeResult {
4
+ size?: {
5
+ windowWidth?: number
6
+ windowHeight?: number
7
+ }
8
+ windowWidth?: number
9
+ windowHeight?: number
10
+ }
11
+
12
+ export type WindowResizeCallback = (result?: WindowResizeResult) => void | Promise<void>
13
+
14
+ interface UseWindowResizeOptions {
15
+ immediate?: boolean
16
+ }
17
+
18
+ export function useWindowResize(callback: WindowResizeCallback, options: UseWindowResizeOptions = {}) {
19
+ const { immediate = false } = options
20
+
21
+ onMounted(() => {
22
+ if (immediate) {
23
+ callback()
24
+ }
25
+ if (typeof uni.onWindowResize === 'function') {
26
+ uni.onWindowResize(callback)
27
+ }
28
+ })
29
+
30
+ onBeforeUnmount(() => {
31
+ if (typeof uni.offWindowResize === 'function') {
32
+ uni.offWindowResize(callback)
33
+ }
34
+ })
35
+ }