oxy-uni-ui 1.2.3 → 2.1.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 (246) hide show
  1. package/attributes.json +1 -1
  2. package/components/common/abstracts/variable.scss +512 -343
  3. package/components/common/util.ts +185 -32
  4. package/components/composables/index.ts +1 -0
  5. package/components/composables/usePopover.ts +24 -20
  6. package/components/composables/useVirtualScroll.ts +48 -21
  7. package/components/composables/useWindowResize.ts +35 -0
  8. package/components/oxy-action-sheet/index.scss +24 -11
  9. package/components/oxy-action-sheet/oxy-action-sheet.vue +27 -19
  10. package/components/oxy-action-sheet/types.ts +7 -0
  11. package/components/oxy-backtop/index.scss +4 -4
  12. package/components/oxy-backtop/oxy-backtop.vue +9 -6
  13. package/components/oxy-backtop/types.ts +7 -7
  14. package/components/oxy-badge/index.scss +4 -4
  15. package/components/oxy-badge/oxy-badge.vue +3 -3
  16. package/components/oxy-badge/types.ts +2 -2
  17. package/components/oxy-button/index.scss +5 -5
  18. package/components/oxy-button/oxy-button.vue +5 -1
  19. package/components/oxy-calendar/index.scss +15 -15
  20. package/components/oxy-calendar/oxy-calendar.vue +1 -0
  21. package/components/oxy-calendar/types.ts +5 -0
  22. package/components/oxy-calendar-view/month/index.scss +4 -4
  23. package/components/oxy-calendar-view/month/types.ts +36 -0
  24. package/components/oxy-calendar-view/monthPanel/index.scss +7 -8
  25. package/components/oxy-calendar-view/monthPanel/month-panel.vue +14 -8
  26. package/components/oxy-calendar-view/year/index.scss +5 -5
  27. package/components/oxy-calendar-view/yearPanel/index.scss +4 -4
  28. package/components/oxy-calendar-view/yearPanel/year-panel.vue +21 -5
  29. package/components/oxy-card/index.scss +2 -2
  30. package/components/oxy-cell/index.scss +8 -8
  31. package/components/oxy-checkbox/index.scss +12 -12
  32. package/components/oxy-checkbox-group/index.scss +2 -2
  33. package/components/oxy-circle/oxy-circle.vue +10 -7
  34. package/components/oxy-circle/types.ts +5 -5
  35. package/components/oxy-col/oxy-col.vue +2 -2
  36. package/components/oxy-col-picker/index.scss +4 -4
  37. package/components/oxy-col-picker/oxy-col-picker.vue +6 -5
  38. package/components/oxy-col-picker/types.ts +7 -2
  39. package/components/oxy-collapse/index.scss +2 -2
  40. package/components/oxy-collapse-item/oxy-collapse-item.vue +3 -3
  41. package/components/oxy-corner/index.scss +33 -33
  42. package/components/oxy-count-to/oxy-count-to.vue +3 -3
  43. package/components/oxy-curtain/index.scss +15 -15
  44. package/components/oxy-curtain/oxy-curtain.vue +4 -2
  45. package/components/oxy-curtain/types.ts +6 -1
  46. package/components/oxy-date-strip/oxy-date-strip.vue +2 -2
  47. package/components/oxy-date-strip/types.ts +1 -1
  48. package/components/oxy-date-strip-item/index.scss +3 -3
  49. package/components/oxy-datetime-picker/index.scss +11 -11
  50. package/components/oxy-datetime-picker/oxy-datetime-picker.vue +1 -0
  51. package/components/oxy-datetime-picker/types.ts +5 -0
  52. package/components/oxy-drop-menu/index.scss +5 -5
  53. package/components/oxy-drop-menu/oxy-drop-menu.vue +3 -3
  54. package/components/oxy-drop-menu-item/index.scss +3 -3
  55. package/components/oxy-drop-menu-item/oxy-drop-menu-item.vue +4 -3
  56. package/components/oxy-drop-menu-item/types.ts +5 -0
  57. package/components/oxy-echarts/types.ts +6 -0
  58. package/components/oxy-fab/index.scss +8 -8
  59. package/components/oxy-fab/oxy-fab.vue +22 -3
  60. package/components/oxy-file-list/index.scss +30 -29
  61. package/components/oxy-file-list/oxy-file-list.vue +2 -2
  62. package/components/oxy-floating-panel/oxy-floating-panel.vue +13 -9
  63. package/components/oxy-floating-panel/{type.ts → types.ts} +8 -8
  64. package/components/oxy-footer/index.scss +19 -0
  65. package/components/oxy-footer/oxy-footer.vue +78 -0
  66. package/components/oxy-footer/types.ts +17 -0
  67. package/components/oxy-form-item/types.ts +22 -1
  68. package/components/oxy-gap/oxy-gap.vue +2 -2
  69. package/components/oxy-gap/types.ts +2 -2
  70. package/components/oxy-grid/oxy-grid.vue +1 -1
  71. package/components/oxy-grid/types.ts +1 -1
  72. package/components/oxy-grid-item/index.scss +1 -1
  73. package/components/oxy-grid-item/oxy-grid-item.vue +7 -5
  74. package/components/oxy-grid-item/types.ts +1 -1
  75. package/components/oxy-guidance/index.scss +75 -0
  76. package/components/oxy-guidance/oxy-guidance.vue +201 -0
  77. package/components/oxy-guidance/types.ts +33 -0
  78. package/components/oxy-icon/oxy-icon.vue +2 -2
  79. package/components/oxy-icon/types.ts +1 -1
  80. package/components/oxy-img/oxy-img.vue +4 -4
  81. package/components/oxy-img/types.ts +3 -3
  82. package/components/oxy-img-cropper/index.scss +23 -23
  83. package/components/oxy-img-cropper/oxy-img-cropper.vue +97 -52
  84. package/components/oxy-img-cropper/types.ts +2 -2
  85. package/components/oxy-img-lazy/oxy-img-lazy.vue +3 -3
  86. package/components/oxy-img-lazy/types.ts +3 -3
  87. package/components/oxy-index-anchor/index.scss +2 -2
  88. package/components/oxy-index-anchor/oxy-index-anchor.vue +2 -2
  89. package/components/oxy-index-anchor/{type.ts → types.ts} +3 -0
  90. package/components/oxy-index-bar/index.scss +3 -3
  91. package/components/oxy-index-bar/oxy-index-bar.vue +3 -3
  92. package/components/oxy-index-bar/{type.ts → types.ts} +2 -2
  93. package/components/oxy-input/index.scss +1 -1
  94. package/components/oxy-input-number/index.scss +5 -5
  95. package/components/oxy-input-number/oxy-input-number.vue +2 -2
  96. package/components/oxy-input-number/types.ts +3 -2
  97. package/components/oxy-keyboard/index.scss +5 -5
  98. package/components/oxy-keyboard/key/index.scss +3 -3
  99. package/components/oxy-keyboard/key/index.vue +2 -2
  100. package/components/oxy-keyboard/key/types.ts +15 -0
  101. package/components/oxy-keyboard/oxy-keyboard.vue +1 -0
  102. package/components/oxy-keyboard/types.ts +5 -0
  103. package/components/oxy-link/index.scss +2 -2
  104. package/components/oxy-list/oxy-list.vue +4 -3
  105. package/components/oxy-loading/oxy-loading.vue +8 -4
  106. package/components/oxy-loading/types.ts +1 -1
  107. package/components/oxy-loadmore/index.scss +3 -3
  108. package/components/oxy-long-press-menu/index.scss +93 -0
  109. package/components/oxy-long-press-menu/oxy-long-press-menu.vue +338 -0
  110. package/components/oxy-long-press-menu/types.ts +34 -0
  111. package/components/oxy-message-box/index.scss +12 -11
  112. package/components/oxy-message-box/oxy-message-box.vue +9 -2
  113. package/components/oxy-message-box/types.ts +9 -0
  114. package/components/oxy-navbar/index.scss +2 -2
  115. package/components/oxy-navbar/oxy-navbar.vue +58 -13
  116. package/components/oxy-navbar/types.ts +8 -1
  117. package/components/oxy-navbar-capsule/types.ts +3 -0
  118. package/components/oxy-notice-bar/index.scss +3 -3
  119. package/components/oxy-notice-bar/oxy-notice-bar.vue +9 -5
  120. package/components/oxy-notice-bar/types.ts +3 -3
  121. package/components/oxy-notify/index.ts +1 -0
  122. package/components/oxy-notify/oxy-notify.vue +3 -2
  123. package/components/oxy-notify/types.ts +7 -0
  124. package/components/oxy-pagination/index.scss +6 -5
  125. package/components/oxy-password-input/oxy-password-input.vue +2 -2
  126. package/components/oxy-password-input/types.ts +1 -1
  127. package/components/oxy-picker/index.scss +45 -2
  128. package/components/oxy-picker/oxy-picker.vue +100 -14
  129. package/components/oxy-picker/types.ts +29 -1
  130. package/components/oxy-picker-view/index.scss +4 -4
  131. package/components/oxy-picker-view/oxy-picker-view.vue +4 -4
  132. package/components/oxy-popover/index.scss +13 -13
  133. package/components/oxy-popup/index.scss +4 -4
  134. package/components/oxy-popup/oxy-popup.vue +35 -2
  135. package/components/oxy-popup/types.ts +8 -1
  136. package/components/oxy-progress/index.scss +3 -3
  137. package/components/oxy-qrcode/draw.ts +398 -0
  138. package/components/oxy-qrcode/index.scss +2 -0
  139. package/components/oxy-qrcode/oxy-qrcode.vue +124 -0
  140. package/components/oxy-qrcode/qrcode.ts +936 -0
  141. package/components/oxy-qrcode/types.ts +42 -0
  142. package/components/oxy-radio/index.scss +25 -19
  143. package/components/oxy-radio-group/index.scss +2 -2
  144. package/components/oxy-rate/types.ts +4 -4
  145. package/components/oxy-resize/index.scss +2 -2
  146. package/components/oxy-resize/oxy-resize.vue +4 -4
  147. package/components/oxy-resize/types.ts +3 -0
  148. package/components/oxy-rich-text/index.scss +37 -36
  149. package/components/oxy-rich-text/mp-html/card/card.vue +3 -3
  150. package/components/oxy-rich-text/mp-html/mp-html.vue +33 -24
  151. package/components/oxy-rich-text/mp-html/node/node.vue +30 -19
  152. package/components/oxy-rich-text/oxy-rich-text.vue +31 -31
  153. package/components/oxy-rich-text/types.ts +6 -1
  154. package/components/oxy-row/oxy-row.vue +3 -3
  155. package/components/oxy-row/types.ts +1 -1
  156. package/components/oxy-search/index.scss +7 -7
  157. package/components/oxy-segmented/index.scss +19 -16
  158. package/components/oxy-segmented/oxy-segmented.vue +23 -3
  159. package/components/oxy-select/index.scss +213 -89
  160. package/components/oxy-select/oxy-select.vue +106 -58
  161. package/components/oxy-select/types.ts +13 -1
  162. package/components/oxy-select-picker/index.scss +7 -7
  163. package/components/oxy-select-picker/oxy-select-picker.vue +1 -0
  164. package/components/oxy-select-picker/types.ts +2 -0
  165. package/components/oxy-sidebar-item/index.scss +2 -2
  166. package/components/oxy-signature/oxy-signature.vue +18 -10
  167. package/components/oxy-signature/types.ts +106 -13
  168. package/components/oxy-skeleton/index.scss +1 -1
  169. package/components/oxy-skeleton/oxy-skeleton.vue +6 -6
  170. package/components/oxy-skeleton/types.ts +1 -1
  171. package/components/oxy-slider/index.scss +6 -6
  172. package/components/oxy-sort-button/index.scss +8 -8
  173. package/components/oxy-splitter/index.scss +19 -0
  174. package/components/oxy-splitter/oxy-splitter.vue +409 -0
  175. package/components/oxy-splitter/types.ts +75 -0
  176. package/components/oxy-splitter-panel/index.scss +366 -0
  177. package/components/oxy-splitter-panel/oxy-splitter-panel.vue +432 -0
  178. package/components/oxy-splitter-panel/types.ts +63 -0
  179. package/components/oxy-status-tip/index.scss +4 -4
  180. package/components/oxy-status-tip/oxy-status-tip.vue +5 -5
  181. package/components/oxy-status-tip/types.ts +3 -3
  182. package/components/oxy-step/index.scss +16 -16
  183. package/components/oxy-sticky/oxy-sticky.vue +6 -6
  184. package/components/oxy-stream-render/oxy-stream-render.vue +230 -4
  185. package/components/oxy-stream-render/types.ts +4 -1
  186. package/components/oxy-swipe-action/oxy-swipe-action.vue +27 -2
  187. package/components/oxy-swiper/oxy-swiper.vue +6 -6
  188. package/components/oxy-swiper/types.ts +5 -5
  189. package/components/oxy-swiper-nav/index.scss +3 -3
  190. package/components/oxy-switch/index.scss +10 -10
  191. package/components/oxy-switch/oxy-switch.vue +2 -2
  192. package/components/oxy-switch/types.ts +1 -1
  193. package/components/oxy-tab/index.scss +11 -1
  194. package/components/oxy-tabbar/index.scss +2 -2
  195. package/components/oxy-tabbar/oxy-tabbar.vue +39 -10
  196. package/components/oxy-table/index.scss +8 -8
  197. package/components/oxy-table/oxy-table.vue +8 -6
  198. package/components/oxy-table/types.ts +2 -2
  199. package/components/oxy-table-col/index.scss +3 -3
  200. package/components/oxy-table-col/oxy-table-col.vue +3 -3
  201. package/components/oxy-table-col/types.ts +2 -2
  202. package/components/oxy-tabs/index.scss +52 -22
  203. package/components/oxy-tabs/oxy-tabs.vue +53 -19
  204. package/components/oxy-tabs/types.ts +15 -3
  205. package/components/oxy-tag/index.scss +111 -36
  206. package/components/oxy-text/index.scss +5 -1
  207. package/components/oxy-text/oxy-text.vue +76 -7
  208. package/components/oxy-text/types.ts +12 -0
  209. package/components/oxy-textarea/index.scss +6 -6
  210. package/components/oxy-toast/oxy-toast.vue +24 -8
  211. package/components/oxy-tooltip/index.scss +9 -9
  212. package/components/oxy-tree/index.scss +51 -15
  213. package/components/oxy-tree/oxy-tree.vue +13 -9
  214. package/components/oxy-tree/types.ts +12 -9
  215. package/components/oxy-upload/index.scss +23 -23
  216. package/components/oxy-upload/types.ts +2 -2
  217. package/components/oxy-verification-code/index.scss +6 -0
  218. package/components/oxy-verification-code/oxy-verification-code.vue +187 -0
  219. package/components/oxy-verification-code/types.ts +82 -0
  220. package/components/oxy-video-preview/index.scss +4 -4
  221. package/components/oxy-virtual-scroll/index.scss +5 -5
  222. package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +11 -7
  223. package/components/oxy-virtual-scroll/types.ts +14 -14
  224. package/components/oxy-voice-player/index.scss +937 -0
  225. package/components/oxy-voice-player/oxy-voice-player.vue +821 -0
  226. package/components/oxy-voice-player/types.ts +567 -0
  227. package/components/oxy-waterfall/oxy-waterfall.vue +6 -6
  228. package/components/oxy-waterfall/types.ts +6 -6
  229. package/components/oxy-watermark/oxy-watermark.vue +35 -13
  230. package/components/oxy-watermark/types.ts +14 -14
  231. package/global.d.ts +4 -0
  232. package/locale/lang/ar-SA.ts +3 -0
  233. package/locale/lang/en-US.ts +3 -0
  234. package/locale/lang/zh-CN.ts +3 -0
  235. package/package.json +97 -1
  236. package/tags.json +1 -1
  237. package/web-types.json +1 -1
  238. package/components/oxy-number-keyboard/index.scss +0 -78
  239. package/components/oxy-number-keyboard/key/index.scss +0 -81
  240. package/components/oxy-number-keyboard/key/index.vue +0 -78
  241. package/components/oxy-number-keyboard/key/types.ts +0 -11
  242. package/components/oxy-number-keyboard/oxy-number-keyboard.vue +0 -151
  243. package/components/oxy-number-keyboard/types.ts +0 -83
  244. package/components/oxy-tree/components/tree-node-content.vue +0 -72
  245. package/components/oxy-tree/index.ts +0 -51
  246. package/oxy-uni-ui.zip +0 -0
@@ -0,0 +1,338 @@
1
+ <template>
2
+ <view :id="elId" :data-id="elId" :class="`oxy-long-press-menu ${mode === 'selection' ? 'is-selectable' : ''} ${customClass}`" :style="customStyle">
3
+ <!-- 触发区域 -->
4
+ <!-- mode='normal': 使用 longpress 事件 -->
5
+ <!-- mode='selection': 不使用 longpress,依靠 renderjs/js 监听 selection -->
6
+ <view
7
+ class="oxy-long-press-menu__trigger"
8
+ @longpress="mode === 'normal' ? onLongPress($event) : null"
9
+ :class="{ 'is-active': isVisible && mode === 'normal' }"
10
+ >
11
+ <slot></slot>
12
+ </view>
13
+
14
+ <!-- 遮罩层 (Normal模式使用,Selection模式不需要遮罩,依靠原生行为关闭) -->
15
+ <view v-if="isVisible && mode === 'normal'" class="oxy-long-press-menu__mask" @click="hideMenu" @touchmove.stop.prevent></view>
16
+
17
+ <!-- 气泡菜单 -->
18
+ <view v-if="isVisible" class="oxy-long-press-menu__popover" :style="popoverStyle" @click.stop>
19
+ <view
20
+ v-if="visibleArrow"
21
+ :class="`oxy-long-press-menu__arrow oxy-long-press-menu__${arrowBottom ? 'arrow-down' : 'arrow-up'}`"
22
+ :style="arrowStyle"
23
+ ></view>
24
+ <view class="oxy-long-press-menu__content">
25
+ <view v-for="(item, index) in menus" :key="index" class="oxy-long-press-menu__item" @click="onMenuClick(item)">
26
+ <text class="oxy-long-press-menu__text">{{ item.text }}</text>
27
+ </view>
28
+ </view>
29
+ </view>
30
+
31
+ <!-- #ifdef APP-PLUS || H5 -->
32
+ <view :prop="selectionProp" :change:prop="selection.propObserver"></view>
33
+ <!-- #endif -->
34
+ </view>
35
+ </template>
36
+
37
+ <script>
38
+ import { uuid, getRect, unitConvert } from '../common/util'
39
+
40
+ export default {
41
+ name: 'oxy-long-press-menu',
42
+ props: {
43
+ customClass: {
44
+ type: String,
45
+ default: ''
46
+ },
47
+ customStyle: {
48
+ type: String,
49
+ default: ''
50
+ },
51
+ mode: {
52
+ type: String,
53
+ default: 'normal'
54
+ },
55
+ menus: {
56
+ type: Array,
57
+ default: () => []
58
+ },
59
+ content: {
60
+ type: String,
61
+ default: ''
62
+ },
63
+ visibleArrow: {
64
+ type: Boolean,
65
+ default: true
66
+ }
67
+ },
68
+ data() {
69
+ return {
70
+ elId: `long-press-menu${uuid()}`,
71
+ isVisible: false,
72
+ popoverStyle: {},
73
+ arrowStyle: {},
74
+ rect: null,
75
+ selectedText: '', // 存储选中的文本
76
+ selectionProp: '', // 用于与 renderjs 通信
77
+ arrowBottom: true
78
+ }
79
+ },
80
+ watch: {
81
+ // 监听外部 content 变化
82
+ content(val) {
83
+ if (this.mode === 'normal') {
84
+ this.selectedText = val
85
+ }
86
+ }
87
+ },
88
+ created() {
89
+ if (this.mode === 'normal') {
90
+ this.selectedText = this.content
91
+ }
92
+ },
93
+ methods: {
94
+ // --- RenderJS 通信方法 ---
95
+ // 接收 renderjs 发来的选中区域信息
96
+ onSelectionChange(data) {
97
+ if (this.mode !== 'selection') return
98
+
99
+ if (!data) {
100
+ // 清除选中
101
+ if (this.mode === 'selection') {
102
+ this.isVisible = false
103
+ }
104
+ return
105
+ }
106
+
107
+ const { text, rect } = data
108
+ if (text && rect) {
109
+ this.selectedText = text
110
+ this.showMenu(rect)
111
+ }
112
+ },
113
+
114
+ // --- 现有逻辑 ---
115
+ onLongPress(e) {
116
+ if (this.mode !== 'normal') return
117
+
118
+ // 获取触发元素的位置
119
+ getRect('.oxy-long-press-menu__trigger', false, this)
120
+ .then((rect) => {
121
+ if (rect) {
122
+ this.showMenu(rect)
123
+ }
124
+ })
125
+ .catch(() => {})
126
+ this.$emit('longpress', e)
127
+ },
128
+ showMenu(rect) {
129
+ this.rect = rect
130
+ // 计算气泡位置
131
+ // 默认显示在元素上方
132
+ this.arrowBottom = true // 箭头在底部(气泡在上方)
133
+ let top = rect.top - (this.mode !== 'normal' ? 60 : 10) // 预估气泡高度 + 箭头 + 间距
134
+ let left = rect.left + rect.width / 2
135
+ // 如果上方空间不足,显示在下方
136
+ if (top < 50) {
137
+ // 顶部留一些安全距离
138
+ top = rect.bottom + (this.mode !== 'normal' ? 20 : 60)
139
+ this.arrowBottom = false // 箭头在顶部(气泡在下方)
140
+ }
141
+ this.popoverStyle = {
142
+ top: unitConvert(top, 0, { output: 'px' }),
143
+ left: unitConvert(left, 0, { output: 'px' }),
144
+ animation: 'fadeIn 0.2s ease-out'
145
+ }
146
+ this.arrowStyle = {
147
+ left: unitConvert(rect.width / 2, 0, { output: 'px' })
148
+ }
149
+ this.isVisible = true
150
+ this.$nextTick(() => {
151
+ getRect('.oxy-long-press-menu__popover', false, this)
152
+ .then((prect) => {
153
+ if (prect) {
154
+ const windowWidth = uni.getSystemInfoSync().windowWidth
155
+ let pLeft = left - prect.width / 2
156
+ let arrowLeft = rect.width / 2
157
+ let arrowRight = 'auto'
158
+ if (pLeft < 0) {
159
+ pLeft = rect.left
160
+ } else if (pLeft + prect.width > windowWidth) {
161
+ pLeft = rect.left + rect.width - prect.width
162
+ arrowLeft = 'auto'
163
+ arrowRight = rect.width / 2
164
+ } else {
165
+ arrowLeft = '50%'
166
+ }
167
+ this.popoverStyle.left = unitConvert(pLeft, 0, { output: 'px' })
168
+ this.arrowStyle.left = unitConvert(arrowLeft, 0, { output: 'px' })
169
+ this.arrowStyle.right = unitConvert(arrowRight, 0, { output: 'px' })
170
+ }
171
+ })
172
+ .catch(() => {})
173
+ })
174
+ // 振动反馈 (仅 Normal 模式或首次显示时)
175
+ if (this.mode === 'normal') {
176
+ uni.vibrateShort()
177
+ }
178
+ },
179
+ hideMenu() {
180
+ this.isVisible = false
181
+ // 通知 renderjs 清除状态 (可选)
182
+ this.selectionProp = 'clear-' + Date.now()
183
+ },
184
+ onMenuClick(item) {
185
+ // 不要立即 hideMenu,因为可能要操作 selection
186
+ // 但对于 copy,我们通常会操作完后隐藏
187
+
188
+ // 内置复制功能
189
+ if (item.value === 'copy') {
190
+ const textToCopy = this.selectedText || this.content
191
+ if (textToCopy) {
192
+ uni.setClipboardData({
193
+ data: textToCopy,
194
+ success: () => {
195
+ uni.showToast({
196
+ title: '已复制',
197
+ icon: 'none'
198
+ })
199
+ }
200
+ })
201
+ }
202
+ }
203
+
204
+ this.$emit('menu-click', { ...item, content: this.selectedText })
205
+ this.hideMenu()
206
+ }
207
+ }
208
+ }
209
+ </script>
210
+
211
+ <!-- #ifdef APP-PLUS || H5 -->
212
+ <script module="selection" lang="renderjs">
213
+ module.exports = {
214
+ data() {
215
+ return {
216
+ isSelecting: false,
217
+ ownerId: null,
218
+ selectionDebounce: null
219
+ }
220
+ },
221
+ mounted() {
222
+ // 获取组件ID
223
+ this.ownerId = this.$el.getAttribute('data-id')
224
+
225
+ // 监听 selectionchange
226
+ document.addEventListener('selectionchange', this.handleSelectionChange)
227
+ // 监听 touchend 用于确定选择结束
228
+ document.addEventListener('touchend', this.handleTouchEnd)
229
+ // 同时也监听当前元素的 touchend,以防冒泡问题
230
+ this.$el.addEventListener('touchend', this.handleTouchEnd)
231
+ document.addEventListener('mouseup', this.handleTouchEnd)
232
+
233
+ // 阻止默认上下文菜单(防止系统菜单冲突)
234
+ this.$el.addEventListener('contextmenu', (e) => {
235
+ // 在 selectable 模式下,如果选区在本组件内,阻止默认菜单
236
+ // 在 normal 模式下,也阻止默认菜单
237
+ e.preventDefault()
238
+ return false
239
+ })
240
+ },
241
+ beforeDestroy() {
242
+ document.removeEventListener('selectionchange', this.handleSelectionChange)
243
+ document.removeEventListener('touchend', this.handleTouchEnd)
244
+ if (this.$el) {
245
+ this.$el.removeEventListener('touchend', this.handleTouchEnd)
246
+ }
247
+ document.removeEventListener('mouseup', this.handleTouchEnd)
248
+ },
249
+ methods: {
250
+ propObserver(newValue, oldValue) {
251
+ if (newValue && newValue.startsWith('clear')) {
252
+ const selection = window.getSelection()
253
+ if (selection) {
254
+ selection.removeAllRanges()
255
+ }
256
+ }
257
+ },
258
+ handleSelectionChange() {
259
+ // 清除之前的定时器
260
+ if (this.selectionDebounce) clearTimeout(this.selectionDebounce)
261
+
262
+ // 增加防抖检测,以防 touchend 被系统吞掉或未触发
263
+ // 600ms 后如果没有新的 selectionchange,尝试检查选区
264
+ this.selectionDebounce = setTimeout(() => {
265
+ this.checkSelection()
266
+ }, 600)
267
+ },
268
+ handleTouchEnd() {
269
+ // 立即清除防抖,改用短延时检查
270
+ if (this.selectionDebounce) clearTimeout(this.selectionDebounce)
271
+
272
+ // 延时一下确保 selection 状态已更新
273
+ setTimeout(() => {
274
+ this.checkSelection()
275
+ }, 100)
276
+ },
277
+ checkSelection() {
278
+ const selection = window.getSelection()
279
+ // 如果没有选区或选区为空,清除菜单
280
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
281
+ this.$ownerInstance.callMethod('onSelectionChange', null)
282
+ return
283
+ }
284
+
285
+ const text = selection.toString()
286
+ if (!text || !text.trim()) {
287
+ this.$ownerInstance.callMethod('onSelectionChange', null)
288
+ return
289
+ }
290
+
291
+ // 检查选区是否在这个组件内
292
+ if (!this.isSelectionInThisElement(selection)) {
293
+ // 不属于本组件的选区,应该清除本组件的菜单
294
+ // 这样点击外部或者选择了其他组件的文字时,当前菜单会自动消失
295
+ this.$ownerInstance.callMethod('onSelectionChange', null)
296
+ return
297
+ }
298
+
299
+ // 获取选区坐标
300
+ const range = selection.getRangeAt(0)
301
+ const rect = range.getBoundingClientRect()
302
+
303
+ // 转换为纯对象传递
304
+ this.$ownerInstance.callMethod('onSelectionChange', {
305
+ text: text,
306
+ rect: {
307
+ top: rect.top,
308
+ bottom: rect.bottom,
309
+ left: rect.left,
310
+ right: rect.right,
311
+ width: rect.width,
312
+ height: rect.height
313
+ }
314
+ })
315
+ },
316
+ isSelectionInThisElement(selection) {
317
+ if (!selection) selection = window.getSelection()
318
+ if (selection.rangeCount === 0) return false
319
+
320
+ const range = selection.getRangeAt(0)
321
+ let container = range.commonAncestorContainer
322
+
323
+ // 如果容器是文本节点,取其父元素
324
+ if (container.nodeType === 3) {
325
+ container = container.parentNode
326
+ }
327
+
328
+ // 使用 contains 判断选区是否在当前组件根元素内
329
+ return this.$el.contains(container)
330
+ }
331
+ }
332
+ }
333
+ </script>
334
+ <!-- #endif -->
335
+
336
+ <style lang="scss" scoped>
337
+ @import './index.scss';
338
+ </style>
@@ -0,0 +1,34 @@
1
+ import type { PropType } from 'vue'
2
+ import { baseProps, makeStringProp } from '../common/props'
3
+
4
+ export type LongPressMenuType = 'normal' | 'selection'
5
+
6
+ export type MenusItem = {
7
+ [key: string]: any
8
+ text: string
9
+ value: string
10
+ }
11
+
12
+ export const longPressMenuProps = {
13
+ ...baseProps,
14
+ /**
15
+ * 模式,'normal' (长按直接弹) | 'selection' (选文后弹 - 仅H5/App)
16
+ * 类型: string
17
+ * 默认值: 'normal'
18
+ */
19
+ mode: makeStringProp<LongPressMenuType>('normal'),
20
+ /**
21
+ * 菜单项
22
+ * 类型: array
23
+ * 默认值: [{ text: '复制', value: 'copy' }]
24
+ */
25
+ menus: {
26
+ type: Array as PropType<MenusItem[]>,
27
+ default: () => [{ text: '复制', value: 'copy' }]
28
+ },
29
+ /**
30
+ * 要复制的内容,如果不传则不自动处理复制
31
+ * 类型: string
32
+ */
33
+ content: makeStringProp('')
34
+ }
@@ -31,7 +31,6 @@
31
31
  overflow: hidden;
32
32
 
33
33
  @include e(container) {
34
- width: $-message-box-width;
35
34
  box-sizing: border-box;
36
35
  }
37
36
 
@@ -40,7 +39,7 @@
40
39
  padding: $-message-box-padding;
41
40
 
42
41
  @include when(no-title) {
43
- padding: 25px 24px 0px;
42
+ padding: 56rpx 48rpx 0rpx;
44
43
  }
45
44
  }
46
45
 
@@ -48,10 +47,10 @@
48
47
  text-align: center;
49
48
  font-size: $-message-box-title-fs;
50
49
  color: $-message-box-title-color;
51
- line-height: 20px;
50
+ line-height: 48rpx;
52
51
  font-weight: 500;
53
- padding-top: 5px;
54
- padding-bottom: 10px;
52
+ padding-top: 16rpx;
53
+ padding-bottom: 24rpx;
55
54
  }
56
55
 
57
56
  @include e(content) {
@@ -60,7 +59,7 @@
60
59
  font-size: $-message-box-content-fs;
61
60
  text-align: center;
62
61
  overflow: auto;
63
- line-height: 20px;
62
+ line-height: 48rpx;
64
63
 
65
64
  &::-webkit-scrollbar {
66
65
  width: $-message-box-content-scrollbar-width;
@@ -74,8 +73,8 @@
74
73
  }
75
74
 
76
75
  @include e(input-error) {
77
- min-height: 18px;
78
- margin-top: 2px;
76
+ min-height: 40rpx;
77
+ margin-top: 8rpx;
79
78
  color: $-message-box-input-error-color;
80
79
  text-align: left;
81
80
 
@@ -85,17 +84,19 @@
85
84
  }
86
85
 
87
86
  @include e(actions) {
88
- padding: 24px;
87
+ padding: 48rpx;
89
88
  }
90
89
 
91
90
  @include edeep(actions-btn) {
92
91
  &:not(:last-child) {
93
- margin-right: 16px;
92
+ margin-right: 32rpx;
94
93
  }
95
94
  }
96
95
 
97
96
  @include e(flex) {
98
97
  display: flex;
98
+ justify-content: center;
99
+ align-items: center;
99
100
  }
100
101
 
101
102
  @include e(block) {
@@ -103,6 +104,6 @@
103
104
  }
104
105
 
105
106
  @include e(cancel) {
106
- margin-right: 16px;
107
+ margin-right: 32rpx;
107
108
  }
108
109
  }
@@ -5,6 +5,7 @@
5
5
  v-model="messageState.show"
6
6
  :close-on-click-modal="messageState.closeOnClickModal"
7
7
  :lazy-render="messageState.lazyRender"
8
+ :max-width="popupWidth"
8
9
  custom-class="oxy-message-box"
9
10
  @click-modal="toggleModal('modal')"
10
11
  :z-index="messageState.zIndex"
@@ -62,7 +63,7 @@ import OxyInput from '../oxy-input/oxy-input.vue'
62
63
  import { computed, inject, reactive, ref, watch } from 'vue'
63
64
  import { messageBoxProps, type MessageOptionsWithCallBack, type MessageResult } from './types'
64
65
  import { defaultOptions, getMessageDefaultOptionKey } from '.'
65
- import { deepAssign, isDef, isFunction, isUndefined, omitBy } from '../common/util'
66
+ import { deepAssign, isDef, isFunction, isUndefined, omitBy, resolveSizeWithScreenWidth } from '../common/util'
66
67
  import { useTranslate } from '../composables/useTranslate'
67
68
  import type { ButtonProps } from '../oxy-button/types'
68
69
 
@@ -78,6 +79,10 @@ const bodyClass = computed(() => {
78
79
  return `oxy-message-box__body ${!messageState.title ? 'is-no-title' : ''} ${messageState.type === 'prompt' ? 'is-prompt' : ''}`
79
80
  })
80
81
 
82
+ const popupWidth = computed(() => {
83
+ return resolveSizeWithScreenWidth(messageState.width || props.width, { defaultUnit: 'rpx' })
84
+ })
85
+
81
86
  const messageOptionKey = getMessageDefaultOptionKey(props.selector)
82
87
  const messageOption = inject(messageOptionKey, ref<MessageOptionsWithCallBack>(defaultOptions)) // message选项
83
88
 
@@ -96,7 +101,8 @@ const messageState = reactive<MessageOptionsWithCallBack>({
96
101
  inputError: '', // 输入框错误提示文案
97
102
  showErr: false, // 是否显示错误提示
98
103
  zIndex: 99, // 弹窗层级
99
- lazyRender: true // 弹层内容懒渲染
104
+ lazyRender: true, // 弹层内容懒渲染
105
+ width: props.width // 弹框宽度
100
106
  })
101
107
 
102
108
  /**
@@ -280,6 +286,7 @@ function reset(option: MessageOptionsWithCallBack) {
280
286
  messageState.showErr = option.showErr
281
287
  messageState.zIndex = option.zIndex
282
288
  messageState.lazyRender = option.lazyRender
289
+ messageState.width = isDef(option.width) ? option.width : props.width
283
290
  messageState.confirmButtonProps = option.confirmButtonProps
284
291
  messageState.cancelButtonProps = option.cancelButtonProps
285
292
  }
@@ -77,6 +77,10 @@ export type MessageOptions = {
77
77
  * 弹层内容懒渲染,触发展示时才渲染内容
78
78
  */
79
79
  lazyRender?: boolean
80
+ /**
81
+ * 弹框宽度,支持带单位的值(如 `640rpx`、`480px`、`80%`)
82
+ */
83
+ width?: string
80
84
  /**
81
85
  * 确认前钩子
82
86
  */
@@ -125,6 +129,11 @@ export const messageBoxProps = {
125
129
  * 指定唯一标识
126
130
  */
127
131
  selector: makeStringProp(''),
132
+ /**
133
+ * 弹框宽度,支持带单位的值(如 `640rpx`、`480px`、`80%`)
134
+ * @default '80%'
135
+ */
136
+ width: makeStringProp('80%'),
128
137
  /**
129
138
  * 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
130
139
  */
@@ -58,7 +58,7 @@
58
58
  font-size: $-navbar-desc-font-size;
59
59
  display: flex;
60
60
  align-items: center;
61
- padding: 0 12px;
61
+ padding: 0 24rpx;
62
62
 
63
63
  @include when(disabled) {
64
64
  opacity: $-navbar-disabled-opacity;
@@ -90,4 +90,4 @@
90
90
  width: 100%;
91
91
  z-index: 500;
92
92
  }
93
- }
93
+ }
@@ -1,7 +1,10 @@
1
1
  <template>
2
- <view :style="{ height: addUnit(height) }">
2
+ <view :style="placeholderStyle">
3
3
  <view :class="`oxy-navbar ${customClass} ${fixed ? 'is-fixed' : ''} ${bordered ? 'is-border' : ''}`" :style="rootStyle">
4
- <view class="oxy-navbar__content">
4
+ <view
5
+ class="oxy-navbar__content"
6
+ :style="{ width: fixed && avoidMenuButtonMode === 'full' ? unitConvert(navWidth, 0, { output: 'px' }) : '100%' }"
7
+ >
5
8
  <view class="oxy-navbar__capsule" v-if="$slots.capsule">
6
9
  <slot name="capsule" />
7
10
  </view>
@@ -19,7 +22,12 @@
19
22
  <slot name="title" />
20
23
  <block v-if="!$slots.title && title">{{ title }}</block>
21
24
  </view>
22
- <view :class="`oxy-navbar__right ${rightDisabled ? 'is-disabled' : ''}`" @click="handleClickRight" v-if="$slots.right || rightText">
25
+ <view
26
+ :class="`oxy-navbar__right ${rightDisabled ? 'is-disabled' : ''}`"
27
+ @click="handleClickRight"
28
+ v-if="$slots.right || rightText"
29
+ :style="rightStyle"
30
+ >
23
31
  <slot name="right" />
24
32
 
25
33
  <view v-if="!$slots.right && rightText" class="oxy-navbar__text" hover-class="oxy-navbar__text--hover" :hover-stay-time="70">
@@ -44,41 +52,63 @@ export default {
44
52
  <script lang="ts" setup>
45
53
  import OxyIcon from '../oxy-icon/oxy-icon.vue'
46
54
  import { type CSSProperties, computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
47
- import { getRect, addUnit, isDef, objToStyle } from '../common/util'
55
+ import { getRect, isDef, objToStyle, unitConvert } from '../common/util'
48
56
  import { navbarProps } from './types'
49
57
 
50
58
  const props = defineProps(navbarProps)
51
59
  const emit = defineEmits(['click-left', 'click-right'])
52
60
 
53
- const height = ref<number | ''>('') // 占位高度
61
+ const placeholderHeight = ref<number | ''>('') // 占位高度(rpx 语义)
62
+ const navWidth = ref<number | 'auto'>('auto') // 导航栏宽度
54
63
 
55
64
  const { statusBarHeight } = uni.getSystemInfoSync()
56
65
 
57
66
  watch(
58
- [() => props.fixed, () => props.placeholder],
67
+ [() => props.fixed, () => props.placeholder, () => props.safeAreaInsetTop, () => props.customStyle],
59
68
  () => {
60
69
  setPlaceholderHeight()
61
70
  },
62
- { deep: true, immediate: false }
71
+ { immediate: false }
63
72
  )
64
73
 
74
+ const placeholderStyle = computed(() => {
75
+ if (!props.fixed || !props.placeholder) {
76
+ return {}
77
+ }
78
+ return {
79
+ height: `${Number(placeholderHeight.value) || 0}rpx`
80
+ }
81
+ })
82
+
65
83
  const rootStyle = computed(() => {
66
84
  const style: CSSProperties = {}
67
85
  if (props.fixed && isDef(props.zIndex)) {
68
86
  style['z-index'] = props.zIndex
69
87
  }
70
88
  if (props.safeAreaInsetTop) {
71
- style['padding-top'] = addUnit(statusBarHeight || 0)
89
+ style['padding-top'] = unitConvert(statusBarHeight || 0, 0, { output: 'px' })
72
90
  }
73
91
  return `${objToStyle(style)}${props.customStyle}`
74
92
  })
75
93
 
94
+ const rightStyle = computed(() => {
95
+ const style: CSSProperties = {}
96
+ if (props.fixed && props.avoidMenuButtonMode === 'right' && navWidth.value !== 'auto') {
97
+ style['right'] = `calc(100% - ${unitConvert(navWidth.value, 0, { output: 'px' })})`
98
+ }
99
+ return style
100
+ })
101
+
76
102
  onMounted(() => {
77
103
  if (props.fixed && props.placeholder) {
78
- nextTick(() => {
79
- setPlaceholderHeight()
80
- })
104
+ setPlaceholderHeight()
105
+ }
106
+ // #ifdef MP-WEIXIN
107
+ if (props.fixed && props.avoidMenuButtonMode) {
108
+ const menuBtnInfo = uni.getMenuButtonBoundingClientRect()
109
+ navWidth.value = menuBtnInfo.left
81
110
  }
111
+ // #endif
82
112
  })
83
113
 
84
114
  function handleClickLeft() {
@@ -97,11 +127,26 @@ const { proxy } = getCurrentInstance() as any
97
127
 
98
128
  function setPlaceholderHeight() {
99
129
  if (!props.fixed || !props.placeholder) {
130
+ placeholderHeight.value = ''
100
131
  return
101
132
  }
102
133
 
103
- getRect('.oxy-navbar', false, proxy).then((res) => {
104
- height.value = res.height as number
134
+ nextTick(() => {
135
+ getRect('.oxy-navbar', false, proxy)
136
+ .then((res) => {
137
+ const rectHeight = Number(res.height) || 0
138
+ const rectTop = Number(res.top)
139
+ const rectBottom = Number(res.bottom)
140
+ const occupiedHeight =
141
+ rectHeight > 0 ? rectHeight : !Number.isNaN(rectTop) && !Number.isNaN(rectBottom) ? Math.max(rectBottom - rectTop, 0) : 0
142
+ const pxPer100Rpx = uni.upx2px(100)
143
+ const occupiedHeightInRpx = pxPer100Rpx ? (occupiedHeight / pxPer100Rpx) * 100 : occupiedHeight * 2
144
+
145
+ placeholderHeight.value = occupiedHeight ? occupiedHeightInRpx : ''
146
+ })
147
+ .catch(() => {
148
+ placeholderHeight.value = ''
149
+ })
105
150
  })
106
151
  }
107
152
  </script>
@@ -46,7 +46,14 @@ export const navbarProps = {
46
46
  /**
47
47
  * 是否禁用右侧按钮,禁用时透明度降低,且无法点击
48
48
  */
49
- rightDisabled: makeBooleanProp(false)
49
+ rightDisabled: makeBooleanProp(false),
50
+ /**
51
+ * 胶囊避让模式:'full' 整体缩小宽度,'right' 仅右侧避让,不传则不避让
52
+ */
53
+ avoidMenuButtonMode: {
54
+ type: String,
55
+ default: ''
56
+ }
50
57
  }
51
58
 
52
59
  export type NavbarProps = ExtractPropTypes<typeof navbarProps>
@@ -2,6 +2,9 @@ import type { ExtractPropTypes } from 'vue'
2
2
  import { baseProps } from '../common/props'
3
3
 
4
4
  export const navbarCapsuleProps = {
5
+ /**
6
+ * 继承通用样式属性
7
+ */
5
8
  ...baseProps
6
9
  }
7
10