uview-plus 3.5.20 → 3.5.25

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.
package/changelog.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 3.5.25(2025-08-28)
2
+ fix: 修复左侧与右侧弹出时页面打开闪现问题
3
+
4
+ ## 3.5.24(2025-08-28)
5
+ feat: 优化tooltip弹窗定位
6
+
7
+ feat: tooltip组件新增forcePosition支持强制精确定位
8
+
9
+ ## 3.5.23(2025-08-28)
10
+ improvment: 调整pdf阅读器默认高度
11
+
12
+ ## 3.5.22(2025-08-28)
13
+ feat: 新增goods-sku商品SKU选购组件
14
+
15
+ ## 3.5.21(2025-08-27)
16
+ fix: 修复popup组件手势控制参数注释
17
+
1
18
  ## 3.5.20(2025-08-27)
2
19
  improvement: 提升checkbox条件编译兼容性
3
20
 
@@ -0,0 +1,432 @@
1
+ <template>
2
+ <view class="up-goods-sku">
3
+ <view @click="open">
4
+ <slot name="trigger"></slot>
5
+ </view>
6
+ <up-popup
7
+ v-model:show="show"
8
+ mode="bottom"
9
+ :closeable="pageInline ? false : closeable"
10
+ :pageInline="pageInline"
11
+ :border-radius="20"
12
+ @close="close"
13
+ >
14
+ <view class="up-goods-sku-container" :style="{padding: pageInline ? '0px' : ''}">
15
+ <view class="up-goods-sku__header">
16
+ <slot name="header">
17
+ <view class="up-goods-sku__header__image">
18
+ <image :src="goodsInfo.image || goodsInfo.picture" mode="aspectFill"></image>
19
+ </view>
20
+ <view class="up-goods-sku__header__info">
21
+ <view class="up-goods-sku__header__info__price">
22
+ <text class="up-goods-sku__header__info__price__symbol">¥</text>
23
+ <text class="up-goods-sku__header__info__price__value">{{ price }}</text>
24
+ </view>
25
+ <view class="up-goods-sku__header__info__stock">库存 {{ stock }} 件</view>
26
+ <view class="up-goods-sku__header__info__selected">已选: {{ selectedSkuText }}</view>
27
+ </view>
28
+ </slot>
29
+ </view>
30
+
31
+ <scroll-view class="up-goods-sku__content" scroll-y>
32
+ <view v-for="(treeItem, index) in skuTree" :key="index" class="up-goods-sku__content__item">
33
+ <view class="up-goods-sku__content__item__title">{{ treeItem.label }}</view>
34
+ <view class="up-goods-sku__content__item__list">
35
+ <view
36
+ v-for="(leafItem, leafIndex) in treeItem.children"
37
+ :key="leafIndex"
38
+ class="up-goods-sku__content__item__list__item"
39
+ :class="{
40
+ 'up-goods-sku__content__item__list__item--active': isSelected(treeItem.name, leafItem.id),
41
+ 'up-goods-sku__content__item__list__item--disabled': isDisabled(treeItem.name, leafItem.id)
42
+ }"
43
+ @click="onSkuClick(treeItem.name, leafItem)"
44
+ >
45
+ <text>{{ leafItem.name }}</text>
46
+ </view>
47
+ </view>
48
+ </view>
49
+
50
+ <view class="up-goods-sku__content__count">
51
+ <view class="up-goods-sku__content__count__title">购买数量</view>
52
+ <view class="up-goods-sku__content__count__control">
53
+ <up-number-box
54
+ v-model="buyNum"
55
+ :min="1"
56
+ :max="maxBuyNum"
57
+ :disabled="!canBuy"
58
+ @change="onNumChange"
59
+ ></up-number-box>
60
+ </view>
61
+ </view>
62
+ </scroll-view>
63
+
64
+ <view class="up-goods-sku__footer">
65
+ <up-button
66
+ type="primary"
67
+ :disabled="!canBuy"
68
+ @click="onConfirm"
69
+ >
70
+ {{ confirmText }}
71
+ </up-button>
72
+ </view>
73
+ </view>
74
+ </up-popup>
75
+ </view>
76
+ </template>
77
+
78
+ <script>
79
+ export default {
80
+ name: 'up-goods-sku',
81
+ props: {
82
+ // 商品信息
83
+ goodsInfo: {
84
+ type: Object,
85
+ default: () => ({})
86
+ },
87
+ // SKU树形结构
88
+ skuTree: {
89
+ type: Array,
90
+ default: () => []
91
+ },
92
+ // SKU列表
93
+ skuList: {
94
+ type: Array,
95
+ default: () => []
96
+ },
97
+ // 最大购买数量
98
+ maxBuy: {
99
+ type: Number,
100
+ default: 999
101
+ },
102
+ // 确认按钮文字
103
+ confirmText: {
104
+ type: String,
105
+ default: '确定'
106
+ },
107
+ // 是否显示关闭弹窗按钮
108
+ closeable: {
109
+ type: Boolean,
110
+ default: true
111
+ },
112
+ // 是否页面内联模式
113
+ pageInline: {
114
+ type: Boolean,
115
+ default: false
116
+ }
117
+ },
118
+ data() {
119
+ return {
120
+ show: false,
121
+ // 已选择的SKU
122
+ selectedSku: {},
123
+ // 购买数量
124
+ buyNum: 1
125
+ }
126
+ },
127
+ computed: {
128
+ // 当前价格
129
+ price() {
130
+ const selectedSkuComb = this.getSelectedSkuComb()
131
+ if (selectedSkuComb) {
132
+ return selectedSkuComb.price || selectedSkuComb.price_fee
133
+ }
134
+ return this.goodsInfo.price || this.goodsInfo.price_fee || 0
135
+ },
136
+ // 当前库存
137
+ stock() {
138
+ const selectedSkuComb = this.getSelectedSkuComb()
139
+ if (selectedSkuComb) {
140
+ return selectedSkuComb.stock || selectedSkuComb.quantity
141
+ }
142
+ return this.goodsInfo.stock || this.goodsInfo.quantity || 0
143
+ },
144
+ // 最大购买数量
145
+ maxBuyNum() {
146
+ const stock = this.stock
147
+ return stock > this.maxBuy ? this.maxBuy : stock
148
+ },
149
+ // 是否可以购买
150
+ canBuy() {
151
+ const selectedSkuCount = Object.keys(this.selectedSku).length
152
+ const skuTreeCount = this.skuTree.length
153
+ return selectedSkuCount === skuTreeCount && this.buyNum > 0 && this.stock > 0
154
+ },
155
+ // 已选SKU文字描述
156
+ selectedSkuText() {
157
+ const selected = []
158
+ Object.keys(this.selectedSku).forEach(key => {
159
+ const value = this.selectedSku[key]
160
+ if (value) {
161
+ this.skuTree.forEach(treeItem => {
162
+ if (treeItem.name === key) {
163
+ treeItem.children.forEach(leafItem => {
164
+ if (leafItem.id === value) {
165
+ selected.push(leafItem.name)
166
+ }
167
+ })
168
+ }
169
+ })
170
+ }
171
+ })
172
+ return selected.join(', ')
173
+ }
174
+ },
175
+ watch: {
176
+ },
177
+ emits: ['open', 'confirm', 'close'],
178
+ created() {
179
+ if (this.pageInline) {
180
+ this.show = true;
181
+ }
182
+ },
183
+ methods: {
184
+ // 判断SKU是否被选中
185
+ isSelected(skuKey, skuValueId) {
186
+ return this.selectedSku[skuKey] === skuValueId
187
+ },
188
+ // 判断SKU是否禁用
189
+ isDisabled(skuKey, skuValueId) {
190
+ // 构造一个临时的已选中SKU对象
191
+ const tempSelected = { ...this.selectedSku, [skuKey]: skuValueId }
192
+
193
+ // 检查是否还有未选择的SKU维度
194
+ const selectedCount = Object.keys(tempSelected).filter(key => tempSelected[key]).length
195
+ const totalSkuCount = this.skuTree.length
196
+
197
+ // 如果所有SKU都已选择,则检查组合是否存在
198
+ if (selectedCount === totalSkuCount) {
199
+ return !this.getSkuComb(tempSelected)
200
+ }
201
+
202
+ // 检查当前选择的SKU是否会导致无法组成有效组合
203
+ for (let i = 0; i < this.skuList.length; i++) {
204
+ const sku = this.skuList[i]
205
+ let match = true
206
+
207
+ // 检查已选中的SKU是否匹配
208
+ for (const key in tempSelected) {
209
+ if (tempSelected[key] && sku[key] !== tempSelected[key]) {
210
+ match = false
211
+ break
212
+ }
213
+ }
214
+
215
+ if (match) {
216
+ return false
217
+ }
218
+ }
219
+
220
+ return true
221
+ },
222
+ // SKU点击事件
223
+ onSkuClick(skuKey, skuValue) {
224
+ // 如果是禁用状态,直接返回
225
+ if (this.isDisabled(skuKey, skuValue.id)) {
226
+ return
227
+ }
228
+
229
+ // 如果已选中,则取消选中
230
+ if (this.selectedSku[skuKey] === skuValue.id) {
231
+ this.$set(this.selectedSku, skuKey, '')
232
+ } else {
233
+ this.$set(this.selectedSku, skuKey, skuValue.id)
234
+ }
235
+ },
236
+ // 数量改变事件
237
+ onNumChange(e) {
238
+ this.buyNum = e.value
239
+ },
240
+ // 获取选中的SKU组合
241
+ getSelectedSkuComb() {
242
+ return this.getSkuComb(this.selectedSku)
243
+ },
244
+ // 根据已选SKU获取组合信息
245
+ getSkuComb(selectedSku) {
246
+ const selected = { ...selectedSku }
247
+
248
+ // 过滤掉空值
249
+ Object.keys(selected).forEach(key => {
250
+ if (!selected[key]) {
251
+ delete selected[key]
252
+ }
253
+ })
254
+
255
+ // 检查是否所有SKU都已选择
256
+ if (Object.keys(selected).length !== this.skuTree.length) {
257
+ return null
258
+ }
259
+
260
+ // 查找匹配的SKU组合
261
+ for (let i = 0; i < this.skuList.length; i++) {
262
+ const sku = this.skuList[i]
263
+ let match = true
264
+
265
+ for (const key in selected) {
266
+ if (sku[key] !== selected[key]) {
267
+ match = false
268
+ break
269
+ }
270
+ }
271
+
272
+ if (match) {
273
+ return sku
274
+ }
275
+ }
276
+
277
+ return null
278
+ },
279
+ // 重置选择
280
+ reset() {
281
+ this.selectedSku = {}
282
+ this.buyNum = 1
283
+ },
284
+ open() {
285
+ this.show = true;
286
+ this.$emit('open')
287
+ },
288
+ // 关闭弹窗
289
+ close() {
290
+ this.false = true;
291
+ this.$emit('close')
292
+ },
293
+ // 确认选择
294
+ onConfirm() {
295
+ if (!this.canBuy) {
296
+ return
297
+ }
298
+
299
+ const selectedSkuComb = this.getSelectedSkuComb()
300
+ this.$emit('confirm', {
301
+ sku: selectedSkuComb,
302
+ goodsInfo: this.goodsInfo,
303
+ num: this.buyNum,
304
+ selectedText: this.selectedSkuText
305
+ })
306
+ }
307
+ }
308
+ }
309
+ </script>
310
+
311
+ <style lang="scss" scoped>
312
+ .up-goods-sku {
313
+ background-color: #fff;
314
+ overflow: hidden;
315
+
316
+ .up-goods-sku-container {
317
+ padding: 4rpx 30rpx;
318
+ }
319
+
320
+ &__header {
321
+ display: flex;
322
+ flex-direction: row;
323
+ padding: 30rpx 0;
324
+ position: relative;
325
+
326
+ &__image {
327
+ width: 180rpx;
328
+ height: 180rpx;
329
+ border-radius: 10rpx;
330
+ overflow: hidden;
331
+ margin-right: 20rpx;
332
+
333
+ image {
334
+ width: 100%;
335
+ height: 100%;
336
+ }
337
+ }
338
+
339
+ &__info {
340
+ flex: 1;
341
+
342
+ &__price {
343
+ display: flex;
344
+ flex-direction: row;
345
+ align-items: baseline;
346
+ margin-bottom: 20rpx;
347
+
348
+ &__symbol {
349
+ font-size: 24rpx;
350
+ color: #fa3534;
351
+ margin-right: 4rpx;
352
+ }
353
+
354
+ &__value {
355
+ font-size: 36rpx;
356
+ color: #fa3534;
357
+ font-weight: bold;
358
+ }
359
+ }
360
+
361
+ &__stock {
362
+ font-size: 26rpx;
363
+ color: #999;
364
+ margin-bottom: 20rpx;
365
+ }
366
+
367
+ &__selected {
368
+ font-size: 26rpx;
369
+ color: #333;
370
+ }
371
+ }
372
+ }
373
+
374
+ &__content {
375
+ max-height: 600rpx;
376
+ padding: 0 30rpx 30rpx 0;
377
+
378
+ &__item {
379
+ margin-bottom: 30rpx;
380
+
381
+ &__title {
382
+ font-size: 28rpx;
383
+ color: #333;
384
+ margin-bottom: 20rpx;
385
+ }
386
+
387
+ &__list {
388
+ display: flex;
389
+ flex-direction: row;
390
+ flex-wrap: wrap;
391
+
392
+ &__item {
393
+ padding: 10rpx 20rpx;
394
+ border: 2rpx solid #eee;
395
+ border-radius: 10rpx;
396
+ margin-right: 20rpx;
397
+ margin-bottom: 20rpx;
398
+ font-size: 26rpx;
399
+ color: #333;
400
+
401
+ &--active {
402
+ border-color: #fa3534;
403
+ color: #fa3534;
404
+ }
405
+
406
+ &--disabled {
407
+ color: #ccc;
408
+ border-color: #eee;
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ &__count {
415
+ display: flex;
416
+ flex-direction: row;
417
+ align-items: center;
418
+ justify-content: space-between;
419
+ margin-top: 20rpx;
420
+
421
+ &__title {
422
+ font-size: 28rpx;
423
+ color: #333;
424
+ }
425
+ }
426
+ }
427
+
428
+ &__footer {
429
+ padding: 20rpx 0rpx 40rpx 0;
430
+ }
431
+ }
432
+ </style>
@@ -8,7 +8,7 @@ export default {
8
8
  // 组件高度
9
9
  height: {
10
10
  type: String,
11
- default: '700px'
11
+ default: '500px'
12
12
  },
13
13
  // pdfjs资源域名
14
14
  baseUrl: {
@@ -20,7 +20,7 @@
20
20
  <u-transition
21
21
  class="u-popup__content—transition"
22
22
  :style="contentStyleWrap"
23
- :show="show"
23
+ :show="pageInline ? true : show"
24
24
  :customStyle="transitionStyle"
25
25
  :mode="pageInline ? 'none' : position"
26
26
  :duration="duration"
@@ -96,8 +96,8 @@
96
96
  * @property {String } bgColor 背景色值(默认 '' )
97
97
  * @property {Boolean} zoom 当mode=center时 是否开启缩放(默认 true )
98
98
  * @property {Boolean} touchable 是否开启底部弹窗手势功能(默认 false )
99
- * @property {String | Number} minHeight 最小高度,单位任意,数值默认为px(默认 '200' )
100
- * @property {String | Number} maxHeight 最大高度,单位任意,数值默认为px(默认 '80%' )
99
+ * @property {String} minHeight 最小高度,单位任意,数值默认为px(默认 '200px' )
100
+ * @property {String} maxHeight 最大高度,单位任意,数值默认为px(默认 '80%' )
101
101
  * @property {Object} customStyle 组件的样式,对象形式
102
102
  * @event {Function} open 弹出层打开
103
103
  * @event {Function} close 弹出层收起
@@ -67,5 +67,10 @@ export const props = defineMixin({
67
67
  type: String,
68
68
  default: () => defProps.tooltip.triggerMode
69
69
  },
70
+ // 强制定位
71
+ forcePosition: {
72
+ type: Object,
73
+ default: () => defProps.tooltip.forcePosition
74
+ }
70
75
  }
71
76
  })
@@ -22,6 +22,7 @@ export default {
22
22
  overlay: true,
23
23
  showToast: true,
24
24
  popupBgColor: '',
25
- triggerMode: 'longpress'
25
+ triggerMode: 'longpress',
26
+ forcePosition: {}
26
27
  }
27
28
  }
@@ -29,9 +29,9 @@
29
29
  duration="300"
30
30
  :customStyle="{
31
31
  position: 'absolute',
32
- top: addUnit(tooltipTop),
32
+ top: addUnit(tooltipTop, 'px'),
33
33
  zIndex: zIndex,
34
- ...tooltipStyle
34
+ ...tooltipStyleCpu
35
35
  }"
36
36
  >
37
37
  <view
@@ -155,13 +155,14 @@
155
155
  screenGap: 12,
156
156
  // 三角形指示器的宽高,由于对元素进行了角度旋转,精确计算指示器位置时,需要用到其尺寸信息
157
157
  indicatorWidth: 14,
158
- tooltipStyle: {}
158
+ tooltipStyle: {},
159
+ calcReacted: false
159
160
  }
160
161
  },
161
162
  watch: {
162
163
  async propsChange() {
163
164
  await this.getElRect()
164
- this.getTooltipStyle();
165
+ // this.getTooltipStyle();
165
166
  }
166
167
  },
167
168
  computed: {
@@ -169,28 +170,19 @@
169
170
  // 当一些依赖参数变化时,需要重新计算气泡和指示器的位置信息
170
171
  propsChange() {
171
172
  return [this.text, this.buttons]
172
- }
173
- },
174
- mounted() {
175
- this.init()
176
- },
177
- emits: ["click"],
178
- methods: {
179
- addStyle,
180
- addUnit,
181
- async init() {
182
- await this.getElRect()
183
- this.getTooltipStyle();
184
173
  },
185
174
  // 计算气泡和指示器的位置信息
186
- getTooltipStyle() {
175
+ tooltipStyleCpu() {
176
+ if (!this.calcReacted) {
177
+ return {}
178
+ }
187
179
  const style = {},
188
180
  sysInfo = getWindowInfo()
189
181
  if (this.direction === 'left') {
190
182
  // 右侧显示逻辑
191
183
  style.transform = ``
192
184
  // 垂直居中对齐
193
- style.top = '-' + addUnit((this.tooltipInfo.height - this.indicatorWidth) / 2, 'px')
185
+ style.top = '-' + addUnit((this.textInfo.height - this.tooltipInfo.height) / 2, 'px')
194
186
  style.right = addUnit(this.textInfo.width + this.indicatorWidth, 'px')
195
187
  this.indicatorStyle = {}
196
188
  this.indicatorStyle.right = '-4px'
@@ -199,7 +191,7 @@
199
191
  // 右侧显示逻辑
200
192
  style.transform = ``
201
193
  // 垂直居中对齐
202
- style.top = addUnit((this.textInfo.height - this.tooltipInfo.height) / 2, 'px')
194
+ style.top = '-' + addUnit((this.textInfo.height - this.tooltipInfo.height) / 2, 'px')
203
195
  style.left = addUnit(this.textInfo.width + this.indicatorWidth, 'px')
204
196
  this.indicatorStyle = {}
205
197
  this.indicatorStyle.left = '-4px'
@@ -230,8 +222,21 @@
230
222
  this.indicatorStyle.top = '-4px'
231
223
  }
232
224
  }
233
- this.tooltipStyle = style
234
- return style
225
+ let styleMerge = {...style, ...this.forcePosition}
226
+ this.tooltipStyle = styleMerge
227
+ return styleMerge
228
+ }
229
+ },
230
+ mounted() {
231
+ this.init()
232
+ },
233
+ emits: ["click"],
234
+ methods: {
235
+ addStyle,
236
+ addUnit,
237
+ async init() {
238
+ await this.getElRect()
239
+ // this.getTooltipStyle();
235
240
  },
236
241
  // 点击触发事件
237
242
  async clickHander() {
@@ -292,6 +297,9 @@
292
297
  // 获取气泡尺寸之后,将其隐藏,为了让下次切换气泡显示与隐藏时,有淡入淡出的效果
293
298
  this.showTooltip = false
294
299
  this.textInfo = await this.queryRect(this.textId)
300
+ sleep(500).then(() => {
301
+ this.calcReacted = true
302
+ })
295
303
  resolve()
296
304
  })
297
305
  })
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定。",
5
- "version": "3.5.20",
5
+ "version": "3.5.25",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,100+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",