uview-pro 0.5.0 → 0.5.2

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 (52) hide show
  1. package/changelog.md +68 -24
  2. package/components/u-action-sheet/types.ts +1 -1
  3. package/components/u-alert-tips/u-alert-tips.vue +15 -15
  4. package/components/u-calendar/types.ts +3 -3
  5. package/components/u-calendar/u-calendar.vue +11 -11
  6. package/components/u-checkbox/types.ts +5 -1
  7. package/components/u-checkbox/u-checkbox.vue +41 -18
  8. package/components/u-checkbox-group/types.ts +2 -0
  9. package/components/u-checkbox-group/u-checkbox-group.vue +56 -15
  10. package/components/u-count-down/u-count-down.vue +4 -4
  11. package/components/u-empty/u-empty.vue +14 -14
  12. package/components/u-field/u-field.vue +18 -2
  13. package/components/u-form/u-form.vue +1 -1
  14. package/components/u-form-item/u-form-item.vue +12 -5
  15. package/components/u-full-screen/u-full-screen.vue +2 -2
  16. package/components/u-gap/u-gap.vue +3 -3
  17. package/components/u-input/types.ts +1 -1
  18. package/components/u-keyboard/types.ts +2 -2
  19. package/components/u-keyboard/u-keyboard.vue +3 -3
  20. package/components/u-link/types.ts +1 -1
  21. package/components/u-loadmore/types.ts +3 -3
  22. package/components/u-modal/types.ts +4 -4
  23. package/components/u-navbar/u-navbar.vue +8 -2
  24. package/components/u-no-network/types.ts +1 -1
  25. package/components/u-no-network/u-no-network.vue +5 -5
  26. package/components/u-pagination/types.ts +2 -2
  27. package/components/u-picker/types.ts +2 -2
  28. package/components/u-radio/types.ts +2 -0
  29. package/components/u-radio/u-radio.vue +16 -7
  30. package/components/u-radio-group/u-radio-group.vue +1 -1
  31. package/components/u-read-more/types.ts +2 -2
  32. package/components/u-search/types.ts +5 -3
  33. package/components/u-search/u-search.vue +1 -0
  34. package/components/u-section/types.ts +1 -1
  35. package/components/u-select/types.ts +2 -2
  36. package/components/u-tabbar/u-tabbar.vue +1 -1
  37. package/components/u-text/u-text.vue +7 -2
  38. package/components/u-toast/u-toast.vue +14 -11
  39. package/components/u-transition/types.ts +35 -0
  40. package/components/u-transition/u-transition.vue +437 -0
  41. package/components/u-upload/types.ts +1 -1
  42. package/components/u-upload/u-upload.vue +12 -12
  43. package/components/u-verification-code/types.ts +3 -3
  44. package/index.ts +2 -0
  45. package/libs/hooks/index.ts +2 -0
  46. package/libs/hooks/useDebounce.ts +15 -0
  47. package/libs/hooks/useThrottle.ts +16 -0
  48. package/locale/lang/en-US.ts +19 -19
  49. package/locale/lang/zh-CN.ts +19 -19
  50. package/package.json +9 -9
  51. package/types/components.d.ts +1 -0
  52. package/types/global.d.ts +14 -0
@@ -0,0 +1,437 @@
1
+ <template>
2
+ <view
3
+ v-if="rendered"
4
+ ref="rootRef"
5
+ class="u-transition"
6
+ :class="[customClass, animationClass]"
7
+ :style="$u.toStyle(animationStyle, customStyle)"
8
+ @animationstart="handleAnimationStart"
9
+ @animationend="handleAnimationEnd"
10
+ >
11
+ <slot />
12
+ </view>
13
+ </template>
14
+
15
+ <script lang="ts">
16
+ export default {
17
+ name: 'u-transition',
18
+ options: {
19
+ addGlobalClass: true,
20
+ // #ifndef MP-TOUTIAO
21
+ virtualHost: true,
22
+ // #endif
23
+ styleIsolation: 'shared'
24
+ }
25
+ };
26
+ </script>
27
+
28
+ <script setup lang="ts">
29
+ import { computed, nextTick, ref, watch } from 'vue';
30
+ import { TransitionProps } from './types';
31
+ import { $u } from '../..';
32
+ import type { TransitionDuration } from '../../types/global';
33
+
34
+ /**
35
+ * transition 过渡动画
36
+ * @description 统一的过渡与进出场动效封装,支持多种预设动画和自定义时长。
37
+ * @tutorial https://uviewpro.cn/zh/components/transition.html
38
+ * @property {Boolean} show 是否展示内容(默认true)
39
+ * @property {String} name 预设动画名,可选 fade/slide-up/slide-down/slide-left/slide-right/zoom-in/zoom-out(默认fade)
40
+ * @property {String} mode 进入/离开过渡模式,等同于原生 transition 的 mode(默认空)
41
+ * @property {Number|Object} duration 进入/离开动画时长,单位ms,支持 { enter, leave }(默认300)
42
+ * @property {String} timing-function 动画曲线(默认cubic-bezier(0.2,0.8,0.2,1))
43
+ * @property {Number} delay 动画延迟,单位ms(默认0)
44
+ * @property {Boolean} appear 首次渲染时是否执行动画(默认false)
45
+ * @property {String} custom-class 自定义 class
46
+ * @property {String|Object} custom-style 自定义样式
47
+ * @example <u-transition :show="visible" name="slide-up"><view>content</view></u-transition>
48
+ */
49
+
50
+ const props = defineProps(TransitionProps);
51
+
52
+ const emit = defineEmits([
53
+ 'before-enter',
54
+ 'enter',
55
+ 'after-enter',
56
+ 'enter-cancelled',
57
+ 'before-leave',
58
+ 'leave',
59
+ 'after-leave',
60
+ 'leave-cancelled'
61
+ ]);
62
+
63
+ const normalizeDuration = (duration: number | TransitionDuration) => {
64
+ if (typeof duration === 'number') {
65
+ return {
66
+ enter: duration,
67
+ leave: duration
68
+ };
69
+ }
70
+ return {
71
+ enter: duration?.enter ?? 300,
72
+ leave: duration?.leave ?? duration?.enter ?? 300
73
+ };
74
+ };
75
+
76
+ const rootRef = ref();
77
+ const rendered = ref<boolean>(props.show);
78
+ const animationPhase = ref<'enter' | 'leave' | ''>('');
79
+ const animating = ref(false);
80
+ const initialized = ref(false);
81
+
82
+ const transitionDuration = computed(() => normalizeDuration(props.duration));
83
+
84
+ const animationStyle = computed(() => {
85
+ const currentDuration =
86
+ animationPhase.value === 'leave' ? transitionDuration.value.leave : transitionDuration.value.enter;
87
+ return {
88
+ '--u-transition-duration-enter': `${transitionDuration.value.enter}ms`,
89
+ '--u-transition-duration-leave': `${transitionDuration.value.leave}ms`,
90
+ '--u-transition-delay': `${props.delay}ms`,
91
+ '--u-transition-timing': props.timingFunction,
92
+ animationDuration: `${currentDuration}ms`,
93
+ animationDelay: `${props.delay}ms`,
94
+ animationTimingFunction: props.timingFunction
95
+ };
96
+ });
97
+
98
+ const animationClass = computed(() => {
99
+ if (!animationPhase.value) {
100
+ return '';
101
+ }
102
+ return `u-transition-${props.name}-${animationPhase.value}`;
103
+ });
104
+
105
+ const getEl = () => rootRef.value as any;
106
+
107
+ const startEnter = () => {
108
+ if (animating.value && animationPhase.value === 'enter') {
109
+ return;
110
+ }
111
+ if (animating.value && animationPhase.value === 'leave') {
112
+ emit('leave-cancelled', getEl());
113
+ }
114
+ rendered.value = true;
115
+ animationPhase.value = 'enter';
116
+ animating.value = true;
117
+ emit('before-enter', getEl());
118
+ };
119
+
120
+ const startLeave = () => {
121
+ if (!rendered.value) {
122
+ return;
123
+ }
124
+ if (animating.value && animationPhase.value === 'leave') {
125
+ return;
126
+ }
127
+ if (animating.value && animationPhase.value === 'enter') {
128
+ emit('enter-cancelled', getEl());
129
+ }
130
+ animationPhase.value = 'leave';
131
+ animating.value = true;
132
+ emit('before-leave', getEl());
133
+ };
134
+
135
+ const handleAnimationStart = () => {
136
+ if (animationPhase.value === 'enter') {
137
+ emit('enter', getEl());
138
+ } else if (animationPhase.value === 'leave') {
139
+ emit('leave', getEl());
140
+ }
141
+ };
142
+
143
+ const handleAnimationEnd = () => {
144
+ if (animationPhase.value === 'enter') {
145
+ animating.value = false;
146
+ animationPhase.value = '';
147
+ emit('after-enter', getEl());
148
+ return;
149
+ }
150
+ if (animationPhase.value === 'leave') {
151
+ animating.value = false;
152
+ animationPhase.value = '';
153
+ rendered.value = false;
154
+ emit('after-leave', getEl());
155
+ }
156
+ };
157
+
158
+ // 根据mode处理动画顺序(主要用于快速切换时的时序控制)
159
+ const shouldWaitForAnimation = (newPhase: 'enter' | 'leave') => {
160
+ if (!animating.value) return false;
161
+
162
+ const currentPhase = animationPhase.value;
163
+
164
+ // 如果当前正在进行相反的动画,根据mode决定是否需要等待
165
+ if (props.mode === 'out-in' && currentPhase === 'leave' && newPhase === 'enter') {
166
+ return true; // 等待离开动画完成
167
+ }
168
+ if (props.mode === 'in-out' && currentPhase === 'enter' && newPhase === 'leave') {
169
+ return true; // 等待进入动画完成
170
+ }
171
+
172
+ return false;
173
+ };
174
+
175
+ watch(
176
+ () => props.show,
177
+ value => {
178
+ if (!initialized.value) {
179
+ initialized.value = true;
180
+ if (value) {
181
+ rendered.value = true;
182
+ if (props.appear) {
183
+ nextTick(() => startEnter());
184
+ }
185
+ } else {
186
+ rendered.value = false;
187
+ }
188
+ return;
189
+ }
190
+ if (value) {
191
+ if (shouldWaitForAnimation('enter')) {
192
+ // 根据mode等待当前动画完成后再开始进入动画
193
+ // 简单的方式:延迟到下一个tick检查
194
+ nextTick(() => {
195
+ if (!animating.value) {
196
+ startEnter();
197
+ }
198
+ });
199
+ } else {
200
+ startEnter();
201
+ }
202
+ } else {
203
+ if (shouldWaitForAnimation('leave')) {
204
+ // 根据mode等待当前动画完成后再开始离开动画
205
+ nextTick(() => {
206
+ if (!animating.value) {
207
+ startLeave();
208
+ }
209
+ });
210
+ } else {
211
+ startLeave();
212
+ }
213
+ }
214
+ },
215
+ { immediate: true }
216
+ );
217
+ </script>
218
+
219
+ <style lang="scss" scoped>
220
+ @import '../../libs/css/style.components.scss';
221
+
222
+ .u-transition {
223
+ // display: inline-flex;
224
+ // width: auto;
225
+ }
226
+
227
+ @mixin animation-base {
228
+ animation-fill-mode: both;
229
+ }
230
+
231
+ .u-transition-fade-enter {
232
+ @include animation-base;
233
+ animation-name: u-transition-fade-in;
234
+ }
235
+ .u-transition-fade-leave {
236
+ @include animation-base;
237
+ animation-name: u-transition-fade-out;
238
+ }
239
+
240
+ .u-transition-slide-up-enter {
241
+ @include animation-base;
242
+ animation-name: u-transition-slide-up-in;
243
+ }
244
+ .u-transition-slide-up-leave {
245
+ @include animation-base;
246
+ animation-name: u-transition-slide-up-out;
247
+ }
248
+
249
+ .u-transition-slide-down-enter {
250
+ @include animation-base;
251
+ animation-name: u-transition-slide-down-in;
252
+ }
253
+ .u-transition-slide-down-leave {
254
+ @include animation-base;
255
+ animation-name: u-transition-slide-down-out;
256
+ }
257
+
258
+ .u-transition-slide-left-enter {
259
+ @include animation-base;
260
+ animation-name: u-transition-slide-left-in;
261
+ }
262
+ .u-transition-slide-left-leave {
263
+ @include animation-base;
264
+ animation-name: u-transition-slide-left-out;
265
+ }
266
+
267
+ .u-transition-slide-right-enter {
268
+ @include animation-base;
269
+ animation-name: u-transition-slide-right-in;
270
+ }
271
+ .u-transition-slide-right-leave {
272
+ @include animation-base;
273
+ animation-name: u-transition-slide-right-out;
274
+ }
275
+
276
+ .u-transition-zoom-in-enter {
277
+ @include animation-base;
278
+ animation-name: u-transition-zoom-in-in;
279
+ }
280
+ .u-transition-zoom-in-leave {
281
+ @include animation-base;
282
+ animation-name: u-transition-zoom-in-out;
283
+ }
284
+
285
+ .u-transition-zoom-out-enter {
286
+ @include animation-base;
287
+ animation-name: u-transition-zoom-out-in;
288
+ }
289
+ .u-transition-zoom-out-leave {
290
+ @include animation-base;
291
+ animation-name: u-transition-zoom-out-out;
292
+ }
293
+
294
+ @keyframes u-transition-fade-in {
295
+ 0% {
296
+ opacity: 0;
297
+ }
298
+ 100% {
299
+ opacity: 1;
300
+ }
301
+ }
302
+
303
+ @keyframes u-transition-fade-out {
304
+ 0% {
305
+ opacity: 1;
306
+ }
307
+ 100% {
308
+ opacity: 0;
309
+ }
310
+ }
311
+
312
+ @keyframes u-transition-slide-up-in {
313
+ 0% {
314
+ opacity: 0;
315
+ transform: translate3d(0, 40rpx, 0);
316
+ }
317
+ 100% {
318
+ opacity: 1;
319
+ transform: translate3d(0, 0, 0);
320
+ }
321
+ }
322
+ @keyframes u-transition-slide-up-out {
323
+ 0% {
324
+ opacity: 1;
325
+ transform: translate3d(0, 0, 0);
326
+ }
327
+ 100% {
328
+ opacity: 0;
329
+ transform: translate3d(0, 40rpx, 0);
330
+ }
331
+ }
332
+
333
+ @keyframes u-transition-slide-down-in {
334
+ 0% {
335
+ opacity: 0;
336
+ transform: translate3d(0, -40rpx, 0);
337
+ }
338
+ 100% {
339
+ opacity: 1;
340
+ transform: translate3d(0, 0, 0);
341
+ }
342
+ }
343
+ @keyframes u-transition-slide-down-out {
344
+ 0% {
345
+ opacity: 1;
346
+ transform: translate3d(0, 0, 0);
347
+ }
348
+ 100% {
349
+ opacity: 0;
350
+ transform: translate3d(0, -40rpx, 0);
351
+ }
352
+ }
353
+
354
+ @keyframes u-transition-slide-left-in {
355
+ 0% {
356
+ opacity: 0;
357
+ transform: translate3d(40rpx, 0, 0);
358
+ }
359
+ 100% {
360
+ opacity: 1;
361
+ transform: translate3d(0, 0, 0);
362
+ }
363
+ }
364
+ @keyframes u-transition-slide-left-out {
365
+ 0% {
366
+ opacity: 1;
367
+ transform: translate3d(0, 0, 0);
368
+ }
369
+ 100% {
370
+ opacity: 0;
371
+ transform: translate3d(40rpx, 0, 0);
372
+ }
373
+ }
374
+
375
+ @keyframes u-transition-slide-right-in {
376
+ 0% {
377
+ opacity: 0;
378
+ transform: translate3d(-40rpx, 0, 0);
379
+ }
380
+ 100% {
381
+ opacity: 1;
382
+ transform: translate3d(0, 0, 0);
383
+ }
384
+ }
385
+ @keyframes u-transition-slide-right-out {
386
+ 0% {
387
+ opacity: 1;
388
+ transform: translate3d(0, 0, 0);
389
+ }
390
+ 100% {
391
+ opacity: 0;
392
+ transform: translate3d(-40rpx, 0, 0);
393
+ }
394
+ }
395
+
396
+ @keyframes u-transition-zoom-in-in {
397
+ 0% {
398
+ opacity: 0;
399
+ transform: scale(0.85);
400
+ }
401
+ 100% {
402
+ opacity: 1;
403
+ transform: scale(1);
404
+ }
405
+ }
406
+ @keyframes u-transition-zoom-in-out {
407
+ 0% {
408
+ opacity: 1;
409
+ transform: scale(1);
410
+ }
411
+ 100% {
412
+ opacity: 0;
413
+ transform: scale(0.85);
414
+ }
415
+ }
416
+
417
+ @keyframes u-transition-zoom-out-in {
418
+ 0% {
419
+ opacity: 0;
420
+ transform: scale(1.1);
421
+ }
422
+ 100% {
423
+ opacity: 1;
424
+ transform: scale(1);
425
+ }
426
+ }
427
+ @keyframes u-transition-zoom-out-out {
428
+ 0% {
429
+ opacity: 1;
430
+ transform: scale(1);
431
+ }
432
+ 100% {
433
+ opacity: 0;
434
+ transform: scale(1.1);
435
+ }
436
+ }
437
+ </style>
@@ -34,7 +34,7 @@ export const UploadProps = {
34
34
  /** 是否自定义上传按钮 */
35
35
  customBtn: { type: Boolean, default: false },
36
36
  /** 上传按钮文字 */
37
- uploadText: { type: String, default: () => t('upload.uploadText') },
37
+ uploadText: { type: String, default: () => t('uUpload.uploadText') },
38
38
  /** 上传地址 */
39
39
  action: { type: String, default: '' },
40
40
  /** 是否禁用 */
@@ -205,11 +205,11 @@ function selectFile() {
205
205
  if (!props.multiple && index >= 1) return;
206
206
  if (val.size > Number(props.maxSize)) {
207
207
  emit('on-oversize', val, lists.value, props.index);
208
- showToast(t('upload.overSize'));
208
+ showToast(t('uUpload.overSize'));
209
209
  } else {
210
210
  if (Number(props.maxCount) <= lists.value.length) {
211
211
  emit('on-exceed', val, lists.value, props.index);
212
- showToast(t('upload.overMaxCount'));
212
+ showToast(t('uUpload.overMaxCount'));
213
213
  return;
214
214
  }
215
215
  lists.value.push({ url: val.path, progress: 0, error: false, file: val });
@@ -248,7 +248,7 @@ function retry(index: number) {
248
248
  lists.value[index].error = false;
249
249
  lists.value[index].response = null;
250
250
  if (props.showTips) {
251
- uni.showLoading({ title: t('upload.reUpload') });
251
+ uni.showLoading({ title: t('uUpload.reUpload') });
252
252
  }
253
253
  uploadFile(index);
254
254
  }
@@ -301,7 +301,7 @@ async function uploadFile(index = 0) {
301
301
  }
302
302
  // 检查上传地址
303
303
  if (!props.action) {
304
- showToast(t('upload.noAction'), true);
304
+ showToast(t('uUpload.noAction'), true);
305
305
  return;
306
306
  }
307
307
  lists.value[index].error = false;
@@ -355,7 +355,7 @@ function uploadError(index: number, err: any) {
355
355
  lists.value[index].error = true;
356
356
  lists.value[index].response = null;
357
357
  emit('on-error', err, index, lists.value, props.index);
358
- showToast(t('upload.uploadFailed'));
358
+ showToast(t('uUpload.uploadFailed'));
359
359
  }
360
360
 
361
361
  /**
@@ -363,8 +363,8 @@ function uploadError(index: number, err: any) {
363
363
  */
364
364
  function deleteItem(index: number) {
365
365
  uni.showModal({
366
- title: t('upload.modalTitle'),
367
- content: t('upload.deleteConfirm'),
366
+ title: t('uUpload.modalTitle'),
367
+ content: t('uUpload.deleteConfirm'),
368
368
  success: async (res: any) => {
369
369
  if (res.confirm) {
370
370
  // 先检查是否有定义before-remove移除前钩子
@@ -385,11 +385,11 @@ function deleteItem(index: number) {
385
385
  })
386
386
  .catch(() => {
387
387
  // 如果进入promise的reject,终止删除操作
388
- showToast(t('upload.terminatedRemove'));
388
+ showToast(t('uUpload.terminatedRemove'));
389
389
  });
390
390
  } else if (beforeResponse === false) {
391
391
  // 返回false,终止删除
392
- showToast(t('upload.terminatedRemove'));
392
+ showToast(t('uUpload.terminatedRemove'));
393
393
  } else {
394
394
  // 如果返回true,执行删除操作
395
395
  handlerDeleteItem(index);
@@ -413,7 +413,7 @@ function handlerDeleteItem(index: number) {
413
413
  }
414
414
  lists.value.splice(index, 1);
415
415
  emit('on-remove', index, lists.value, props.index);
416
- showToast(t('upload.removeSuccess'));
416
+ showToast(t('uUpload.removeSuccess'));
417
417
  }
418
418
 
419
419
  /**
@@ -442,7 +442,7 @@ function doPreviewImage(url: string, index: number) {
442
442
  emit('on-preview', url, lists.value, props.index);
443
443
  },
444
444
  fail: () => {
445
- uni.showToast({ title: t('upload.previewFailed'), icon: 'none' });
445
+ uni.showToast({ title: t('uUpload.previewFailed'), icon: 'none' });
446
446
  }
447
447
  });
448
448
  }
@@ -471,7 +471,7 @@ function checkFileExt(file: any) {
471
471
  // 转为小写
472
472
  return ext.toLowerCase() === fileExt;
473
473
  });
474
- if (!noArrowExt) showToast(t('upload.notAllowedExt', { ext: fileExt }));
474
+ if (!noArrowExt) showToast(t('uUpload.notAllowedExt', { ext: fileExt }));
475
475
  return noArrowExt;
476
476
  }
477
477
 
@@ -13,11 +13,11 @@ export const VerificationCodeProps = {
13
13
  /** 倒计时时长,单位秒 */
14
14
  seconds: { type: [String, Number] as PropType<string | number>, default: 60 },
15
15
  /** 开始时按钮文字 */
16
- startText: { type: String, default: () => t('verificationCode.startText') },
16
+ startText: { type: String, default: () => t('uVerificationCode.startText') },
17
17
  /** 倒计时进行中按钮文字,X为剩余秒数 */
18
- changeText: { type: String, default: () => t('verificationCode.changeText') },
18
+ changeText: { type: String, default: () => t('uVerificationCode.changeText') },
19
19
  /** 结束时按钮文字 */
20
- endText: { type: String, default: () => t('verificationCode.endText') },
20
+ endText: { type: String, default: () => t('uVerificationCode.endText') },
21
21
  /** 是否保持倒计时不中断(如页面切换) */
22
22
  keepRunning: { type: Boolean, default: false },
23
23
  /** 唯一标识key,用于区分多个验证码组件 */
package/index.ts CHANGED
@@ -81,6 +81,8 @@ const install = (app: any, options?: UViewProOptions): void => {
81
81
  } else {
82
82
  // 默认初始化系统主题
83
83
  initTheme();
84
+ // 默认初始化内置语言包以保证可用
85
+ configProvider.initLocales();
84
86
  }
85
87
  } catch (error) {
86
88
  console.error('[install options] Error:', error);
@@ -4,3 +4,5 @@ export * from './useCompRelation';
4
4
  export * from './useTheme';
5
5
  export * from './useColor';
6
6
  export * from './useLocale';
7
+ export * from './useDebounce';
8
+ export * from './useThrottle';
@@ -0,0 +1,15 @@
1
+ export function useDebounce(delay: number = 500) {
2
+ let timeout: ReturnType<typeof setTimeout> | null = null;
3
+ // 防抖函数
4
+ function debounce(callback: () => void, debounceTime?: number) {
5
+ debounceTime = debounceTime || delay;
6
+ if (timeout) clearTimeout(timeout);
7
+ timeout = setTimeout(() => {
8
+ callback();
9
+ }, debounceTime);
10
+ }
11
+
12
+ return {
13
+ debounce
14
+ };
15
+ }
@@ -0,0 +1,16 @@
1
+ export function useThrottle(delay: number = 500) {
2
+ let previous: number = 0;
3
+ // 节流函数
4
+ function throttle(callback: () => void, throttleTime?: number) {
5
+ throttleTime = throttleTime || delay;
6
+ let now = Date.now();
7
+ if (now - previous > throttleTime) {
8
+ callback();
9
+ previous = now;
10
+ }
11
+ }
12
+
13
+ return {
14
+ throttle
15
+ };
16
+ }