uview-plus 3.5.17 → 3.5.20

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,12 @@
1
+ ## 3.5.20(2025-08-27)
2
+ improvement: 提升checkbox条件编译兼容性
3
+
4
+ ## 3.5.19(2025-08-27)
5
+ feat: popup新增iOS风格手势控制上下高度及下滑关闭
6
+
7
+ ## 3.5.18(2025-08-26)
8
+ feat: 新增coupon优惠券组件
9
+
1
10
  ## 3.5.17(2025-08-26)
2
11
  fix: 分支合并冲突修复
3
12
 
@@ -199,11 +199,12 @@
199
199
  if (!this.parent) {
200
200
  error('up-checkbox必须搭配up-checkbox-group组件使用')
201
201
  }
202
+ let value = '';
202
203
  // #ifdef VUE2
203
- const value = this.parentData.value
204
+ value = this.parentData.value
204
205
  // #endif
205
206
  // #ifdef VUE3
206
- const value = this.parentData.modelValue
207
+ value = this.parentData.modelValue
207
208
  // #endif
208
209
  // 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
209
210
  if (this.checked) {
@@ -0,0 +1,406 @@
1
+ <template>
2
+ <view class="up-coupon" :class="[`up-coupon--${shape}`, `up-coupon--${type}`, `up-coupon--${size}`, {'up-coupon--disabled': disabled}]"
3
+ :style="[couponStyle]" @click="handleClick">
4
+ <view class="up-coupon__content">
5
+ <!-- 左侧金额区域 -->
6
+ <view class="up-coupon__amount">
7
+ <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'left'">
8
+ <text class="up-coupon__amount-unit" v-if="unitPosition === 'left'">{{ unit }}</text>
9
+ </slot>
10
+ <slot name="amount" :amount="amount">
11
+ <text class="up-coupon__amount-value">{{ amount }}</text>
12
+ </slot>
13
+ <slot name="unit" :unit="unit" :unitPosition="unitPosition" v-if="unitPosition === 'right'">
14
+ <text class="up-coupon__amount-unit" v-if="unitPosition === 'right'">{{ unit }}</text>
15
+ </slot>
16
+ <slot name="limit" :limit="limit">
17
+ <text class="up-coupon__amount-limit" v-if="limit">{{ limit }}</text>
18
+ </slot>
19
+ </view>
20
+
21
+ <!-- 中间描述区域 -->
22
+ <view class="up-coupon__info">
23
+ <slot name="title" :title="title">
24
+ <text class="up-coupon__info-title">{{ title }}</text>
25
+ </slot>
26
+ <slot name="desc" :desc="desc">
27
+ <text class="up-coupon__info-desc" v-if="desc">{{ desc }}</text>
28
+ </slot>
29
+ <slot name="time" :time="time">
30
+ <text class="up-coupon__info-time" v-if="time">{{ time }}</text>
31
+ </slot>
32
+ </view>
33
+
34
+ <!-- 右侧操作区域 -->
35
+ <view class="up-coupon__action u-padding-right-20">
36
+ <slot name="action" :actionText="actionText" :circle="circle">
37
+ <up-tag type="error" :bgColor="type ? 'transparent' : '#eb433d'"
38
+ :borderColor="type ? '#eee' : '#eb433d'" borderRadius="6px"
39
+ size="medium" class="up-coupon__action-text"
40
+ :shape="circle ? 'circle': 'circle'">{{ actionText }}</up-tag>
41
+ </slot>
42
+ </view>
43
+ </view>
44
+
45
+ <!-- 红包绳子效果 -->
46
+ <view v-if="shape === 'envelope'" class="up-coupon__rope"></view>
47
+
48
+ <!-- 默认插槽,可用于添加额外内容 -->
49
+ <slot></slot>
50
+ </view>
51
+ </template>
52
+
53
+ <script>
54
+ export default {
55
+ name: 'up-coupon',
56
+ props: {
57
+ // 金额
58
+ amount: {
59
+ type: [String, Number],
60
+ default: ''
61
+ },
62
+ // 金额单位
63
+ unit: {
64
+ type: String,
65
+ default: '¥'
66
+ },
67
+ // 单位位置
68
+ unitPosition: {
69
+ type: String,
70
+ default: 'left'
71
+ },
72
+ // 使用限制
73
+ limit: {
74
+ type: String,
75
+ default: ''
76
+ },
77
+ // 标题
78
+ title: {
79
+ type: String,
80
+ default: '优惠券'
81
+ },
82
+ // 描述
83
+ desc: {
84
+ type: String,
85
+ default: ''
86
+ },
87
+ // 有效期
88
+ time: {
89
+ type: String,
90
+ default: ''
91
+ },
92
+ // 操作按钮文字
93
+ actionText: {
94
+ type: String,
95
+ default: '使用'
96
+ },
97
+ // 形状:coupon-优惠券, envelope-红包, card-卡片
98
+ shape: {
99
+ type: String,
100
+ default: 'coupon'
101
+ },
102
+ // 尺寸:small, medium, large
103
+ size: {
104
+ type: String,
105
+ default: 'medium'
106
+ },
107
+ // 是否圆形按钮
108
+ circle: {
109
+ type: Boolean,
110
+ default: false
111
+ },
112
+ // 是否禁用
113
+ disabled: {
114
+ type: Boolean,
115
+ default: false
116
+ },
117
+ // 背景颜色
118
+ bgColor: {
119
+ type: String,
120
+ default: ''
121
+ },
122
+ // 文字颜色
123
+ color: {
124
+ type: String,
125
+ default: ''
126
+ },
127
+ // 内置背景类型
128
+ type: {
129
+ type: String,
130
+ default: ''
131
+ },
132
+ },
133
+ computed: {
134
+ couponStyle() {
135
+ const style = {};
136
+ if (this.bgColor) style.background = this.bgColor;
137
+ if (this.color) style.color = this.color;
138
+ return style;
139
+ },
140
+ dotCount() {
141
+ // 根据尺寸计算锯齿数量
142
+ const map = {
143
+ small: 8,
144
+ medium: 10,
145
+ large: 12
146
+ };
147
+ return map[this.size] || 10;
148
+ }
149
+ },
150
+ methods: {
151
+ handleClick() {
152
+ if (this.disabled) return;
153
+ this.$emit('click');
154
+ }
155
+ }
156
+ }
157
+ </script>
158
+
159
+ <style lang="scss" scoped>
160
+ .up-coupon {
161
+ position: relative;
162
+ overflow: hidden;
163
+ border-radius: 8rpx;
164
+ background: #ffebf0;
165
+ color: $u-main-color;
166
+
167
+ &--coupon {
168
+ border-radius: 16rpx;
169
+ overflow: hidden;
170
+
171
+ &::before {
172
+ content: '';
173
+ position: absolute;
174
+ left: -24rpx;
175
+ top: 50%;
176
+ transform: translateY(-50%);
177
+ width: 48rpx;
178
+ height: 48rpx;
179
+ background-color: #fff;
180
+ border-radius: 50%;
181
+ }
182
+
183
+ &::after {
184
+ content: '';
185
+ position: absolute;
186
+ right: -24rpx;
187
+ top: 50%;
188
+ transform: translateY(-50%);
189
+ width: 48rpx;
190
+ height: 48rpx;
191
+ background-color: #fff;
192
+ border-radius: 50%;
193
+ }
194
+ }
195
+
196
+ &--envelope {
197
+ border-radius: 16rpx;
198
+
199
+ &::before {
200
+ content: '';
201
+ position: absolute;
202
+ left: 0;
203
+ top: 0;
204
+ right: 0;
205
+ height: 20rpx;
206
+ background: repeating-linear-gradient(-45deg, #ffd000, #ffd000 10rpx, #ffa000 10rpx, #ffa000 20rpx);
207
+ }
208
+ }
209
+
210
+ &--card {
211
+ border-radius: 16rpx;
212
+ }
213
+
214
+ width: 100%;
215
+
216
+ &--small {
217
+ // width: 520rpx;
218
+ height: 160rpx;
219
+ }
220
+
221
+ &--medium {
222
+ // width: 600rpx;
223
+ height: 180rpx;
224
+ }
225
+
226
+ &--large {
227
+ // width: 700rpx;
228
+ height: 220rpx;
229
+ }
230
+
231
+ &--disabled {
232
+ opacity: 0.5;
233
+ }
234
+
235
+ &__content {
236
+ display: flex;
237
+ flex-direction: row;
238
+ align-items: center;
239
+ justify-content: space-between;
240
+ height: 100%;
241
+ padding: 0 30rpx;
242
+ position: relative;
243
+ z-index: 2;
244
+ }
245
+
246
+ &__amount {
247
+ display: flex;
248
+ flex-direction: column;
249
+ align-items: flex-start;
250
+ padding-left: 10rpx;
251
+ padding-right: 30rpx;
252
+ border-right: 1px dashed #ccc;
253
+
254
+ &-unit {
255
+ font-size: 24rpx;
256
+ font-weight: normal;
257
+ }
258
+
259
+ &-value {
260
+ font-size: 56rpx;
261
+ font-weight: bold;
262
+ color: red;
263
+ line-height: 1;
264
+ margin: 10rpx 0;
265
+ }
266
+
267
+ &-limit {
268
+ font-size: 24rpx;
269
+ opacity: 0.9;
270
+ }
271
+ }
272
+
273
+ &__info {
274
+ flex: 1;
275
+ display: flex;
276
+ flex-direction: column;
277
+ align-items: flex-start;
278
+ padding-left: 30rpx;
279
+
280
+ &-title {
281
+ font-size: 32rpx;
282
+ font-weight: bold;
283
+ margin-bottom: 10rpx;
284
+ }
285
+
286
+ &-desc {
287
+ font-size: 24rpx;
288
+ opacity: 0.9;
289
+ margin-bottom: 10rpx;
290
+ }
291
+
292
+ &-time {
293
+ font-size: 20rpx;
294
+ opacity: 0.8;
295
+ }
296
+ }
297
+
298
+ &__action {
299
+ display: flex;
300
+ flex-direction: row;
301
+ align-items: center;
302
+ justify-content: center;
303
+ }
304
+
305
+ &__dots {
306
+ position: absolute;
307
+ left: 0;
308
+ top: 0;
309
+ bottom: 0;
310
+ width: 100%;
311
+ display: flex;
312
+ flex-direction: column;
313
+ justify-content: space-between;
314
+ padding: 30rpx 0;
315
+ z-index: 1;
316
+ }
317
+
318
+ &__dot {
319
+ width: 32rpx;
320
+ height: 32rpx;
321
+ background-color: #fff;
322
+ border-radius: 50%;
323
+ margin: 0 -16rpx;
324
+ z-index: 3;
325
+ }
326
+
327
+ &__rope {
328
+ position: absolute;
329
+ top: -40rpx;
330
+ left: 50%;
331
+ transform: translateX(-50%);
332
+ width: 80rpx;
333
+ height: 80rpx;
334
+ background: linear-gradient(to right, #ffd000, #ffa000);
335
+ border-radius: 40rpx 40rpx 0 0;
336
+ z-index: 1;
337
+
338
+ &::before {
339
+ content: '';
340
+ position: absolute;
341
+ top: 0;
342
+ left: -20rpx;
343
+ width: 20rpx;
344
+ height: 40rpx;
345
+ background: linear-gradient(to bottom, #ffd000, #ffa000);
346
+ border-radius: 10rpx 0 0 10rpx;
347
+ }
348
+
349
+ &::after {
350
+ content: '';
351
+ position: absolute;
352
+ top: 0;
353
+ right: -20rpx;
354
+ width: 20rpx;
355
+ height: 40rpx;
356
+ background: linear-gradient(to bottom, #ffd000, #ffa000);
357
+ border-radius: 0 10rpx 10rpx 0;
358
+ }
359
+ }
360
+
361
+ // 不同主题样式
362
+ &--primary {
363
+ background: linear-gradient(90deg, #43afff, #3b8cff);
364
+ color: #fff;
365
+ .up-coupon__amount {
366
+ border-right: 1px dashed #eee;
367
+ }
368
+ .up-coupon__amount-value {
369
+ color: #fff;
370
+ }
371
+ }
372
+
373
+ &--success {
374
+ background: linear-gradient(90deg, #67dda9, #19be6b);
375
+ color: #fff !important;
376
+ .up-coupon__amount {
377
+ border-right: 1px dashed #eee;
378
+ }
379
+ .up-coupon__amount-value {
380
+ color: #fff;
381
+ }
382
+ }
383
+
384
+ &--warning {
385
+ background: linear-gradient(90deg, #ff9739, #ff6a39);
386
+ color: #fff;
387
+ .up-coupon__amount {
388
+ border-right: 1px dashed #eee;
389
+ }
390
+ .up-coupon__amount-value {
391
+ color: #fff;
392
+ }
393
+ }
394
+
395
+ &--error {
396
+ background: linear-gradient(90deg, #ff7070, #ff4747);
397
+ color: #fff;
398
+ .up-coupon__amount {
399
+ border-right: 1px dashed #eee;
400
+ }
401
+ .up-coupon__amount-value {
402
+ color: #fff;
403
+ }
404
+ }
405
+ }
406
+ </style>
@@ -21,10 +21,13 @@ export default {
21
21
  safeAreaInsetBottom: true,
22
22
  safeAreaInsetTop: false,
23
23
  closeIconPos: 'top-right',
24
- round: 0,
24
+ round: '20px',
25
25
  zoom: true,
26
26
  bgColor: '',
27
27
  overlayOpacity: 0.5,
28
- pageInline: false
28
+ pageInline: false,
29
+ touchable: false,
30
+ minHeight: '200px',
31
+ maxHeight: '600px'
29
32
  }
30
33
  }
@@ -81,6 +81,21 @@ export const props = defineMixin({
81
81
  pageInline:{
82
82
  type: Boolean,
83
83
  default: () => defProps.popup.pageInline
84
+ },
85
+ // 是否页开启手势滑动
86
+ touchable:{
87
+ type: Boolean,
88
+ default: () => defProps.popup.touchable
89
+ },
90
+ // 手势滑动最小高度
91
+ minHeight:{
92
+ type: [String],
93
+ default: () => defProps.popup.minHeight
94
+ },
95
+ // 手势滑动最大高度
96
+ maxHeight:{
97
+ type: [String],
98
+ default: () => defProps.popup.maxHeight
84
99
  }
85
100
  }
86
101
  })
@@ -18,6 +18,8 @@
18
18
  :opacity="overlayOpacity"
19
19
  ></u-overlay>
20
20
  <u-transition
21
+ class="u-popup__content—transition"
22
+ :style="contentStyleWrap"
21
23
  :show="show"
22
24
  :customStyle="transitionStyle"
23
25
  :mode="pageInline ? 'none' : position"
@@ -33,6 +35,18 @@
33
35
  @touchmove.stop.prevent="noop"
34
36
  >
35
37
  <u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
38
+ <!-- 增加触摸区域,用于处理手势 -->
39
+ <view
40
+ v-if="touchable && mode === 'bottom'"
41
+ class="u-popup__content__touch-area"
42
+ @touchstart="onTouchStart"
43
+ @touchmove="onTouchMove"
44
+ @touchend="onTouchEnd"
45
+ @touchcancel="onTouchEnd"
46
+ >
47
+ <!-- iOS风格的横条指示器 -->
48
+ <view class="u-popup__content__indicator"></view>
49
+ </view>
36
50
  <slot></slot>
37
51
  <view
38
52
  v-if="closeable"
@@ -78,9 +92,12 @@
78
92
  * @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离 (默认 true )
79
93
  * @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) (默认 false )
80
94
  * @property {String} closeIconPos 自定义关闭图标位置(默认 'top-right' )
81
- * @property {String | Number} round 圆角值(默认 0
95
+ * @property {String | Number} round 圆角值(默认 20px
82
96
  * @property {String } bgColor 背景色值(默认 '' )
83
97
  * @property {Boolean} zoom 当mode=center时 是否开启缩放(默认 true )
98
+ * @property {Boolean} touchable 是否开启底部弹窗手势功能(默认 false )
99
+ * @property {String | Number} minHeight 最小高度,单位任意,数值默认为px(默认 '200' )
100
+ * @property {String | Number} maxHeight 最大高度,单位任意,数值默认为px(默认 '80%' )
84
101
  * @property {Object} customStyle 组件的样式,对象形式
85
102
  * @event {Function} open 弹出层打开
86
103
  * @event {Function} close 弹出层收起
@@ -91,7 +108,13 @@
91
108
  mixins: [mpMixin, mixin, props],
92
109
  data() {
93
110
  return {
94
- overlayDuration: this.duration + 50
111
+ overlayDuration: this.duration + 50,
112
+ // 触摸相关数据
113
+ touchStartY: 0,
114
+ touchStartHeight: 0,
115
+ isTouching: false,
116
+ // 当前弹窗高度
117
+ currentHeight: 'auto'
95
118
  }
96
119
  },
97
120
  watch: {
@@ -145,6 +168,23 @@
145
168
  })
146
169
  }
147
170
  },
171
+ contentStyleWrap() {
172
+ const style = {}
173
+
174
+ // 处理手势滑动时的高度变化
175
+ if (this.mode === 'bottom' && this.touchable) {
176
+ if (this.currentHeight !== 'auto') {
177
+ style.height = this.currentHeight
178
+ }
179
+ if (this.maxHeight) {
180
+ style.maxHeight = addUnit(this.maxHeight)
181
+ }
182
+ if (this.minHeight) {
183
+ style.minHeight = addUnit(this.minHeight)
184
+ }
185
+ }
186
+ return style;
187
+ },
148
188
  contentStyle() {
149
189
  const style = {}
150
190
  // 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置
@@ -171,6 +211,7 @@
171
211
  style.borderRadius = value
172
212
  }
173
213
  }
214
+
174
215
  return deepMerge(style, addStyle(this.customStyle))
175
216
  },
176
217
  position() {
@@ -242,8 +283,61 @@
242
283
  this.retryComputedComponentRect(grandChild)
243
284
  }
244
285
  }
245
- }
286
+ },
246
287
  // #endif
288
+
289
+ // 触摸开始
290
+ onTouchStart(e) {
291
+ if (!this.touchable || this.mode !== 'bottom') return;
292
+ this.isTouching = true;
293
+ this.touchStartY = e.touches[0].clientY;
294
+ // 保存当前高度
295
+ this.touchStartHeight = this.$el.querySelector('.u-popup__content—transition').offsetHeight;
296
+ },
297
+
298
+ // 触摸移动
299
+ onTouchMove(e) {
300
+ if (!this.isTouching || !this.touchable || this.mode !== 'bottom') return;
301
+ const touchY = e.touches[0].clientY;
302
+ const deltaY = touchY - this.touchStartY;
303
+
304
+ // 只处理向上滑动(减小高度)和向下滑动(增加高度)
305
+ if (deltaY !== 0) {
306
+ const newHeight = this.touchStartHeight - deltaY;
307
+ const minHeight = parseFloat(addUnit(this.minHeight)) || 200;
308
+ const maxHeight = this.maxHeight ?
309
+ (this.maxHeight.toString().includes('%') ?
310
+ getWindowInfo().windowHeight * (parseFloat(this.maxHeight) / 100) :
311
+ parseFloat(addUnit(this.maxHeight))) :
312
+ getWindowInfo().windowHeight * 0.8;
313
+
314
+ // 限制高度在最小值和最大值之间
315
+ if (newHeight >= minHeight && newHeight <= maxHeight) {
316
+ this.currentHeight = newHeight + 'px';
317
+ }
318
+ }
319
+
320
+ // 阻止默认滚动行为
321
+ e.preventDefault();
322
+ },
323
+
324
+ // 触摸结束
325
+ onTouchEnd(e) {
326
+ if (!this.isTouching || !this.touchable || this.mode !== 'bottom') return;
327
+ this.isTouching = false;
328
+
329
+ const touchY = e.changedTouches[0].clientY;
330
+ const deltaY = touchY - this.touchStartY;
331
+ const velocity = Math.abs(deltaY) / (e.timeStamp - e.changedTouches[0].timestamp); // 简单的速度计算
332
+
333
+ // 快速向下滑动时关闭弹窗
334
+ if (deltaY > 100 || (deltaY > 30 && velocity > 0.5)) {
335
+ this.close();
336
+ } else {
337
+ // 恢复到自适应高度
338
+ // this.currentHeight = 'auto';
339
+ }
340
+ }
247
341
  }
248
342
  }
249
343
  </script>
@@ -304,6 +398,27 @@
304
398
  border-bottom-left-radius: 10px;
305
399
  border-bottom-right-radius: 10px;
306
400
  }
401
+
402
+ &__touch-area {
403
+ // position: absolute;
404
+ top: 0;
405
+ left: 0;
406
+ right: 0;
407
+ height: 42rpx;
408
+ z-index: 10;
409
+ /* #ifndef APP-NVUE */
410
+ display: flex;
411
+ /* #endif */
412
+ justify-content: center;
413
+ align-items: center;
414
+ }
415
+
416
+ &__indicator {
417
+ width: 100px;
418
+ height: 5px;
419
+ border-radius: 100px;
420
+ background-color: #c0c4cc;
421
+ }
307
422
 
308
423
  &__close {
309
424
  position: absolute;
@@ -334,4 +449,4 @@
334
449
  }
335
450
  }
336
451
  }
337
- </style>
452
+ </style>
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.17",
5
+ "version": "3.5.20",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,100+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",