im-ui-mobile 0.1.0 → 0.1.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 (85) hide show
  1. package/components/im-avatar/im-avatar.vue +7 -7
  2. package/components/im-badge/im-badge.vue +326 -0
  3. package/components/im-button/im-button.vue +71 -34
  4. package/components/im-card/im-card.vue +563 -0
  5. package/components/im-chat-item/im-chat-item.vue +5 -4
  6. package/components/im-col/im-col.vue +191 -0
  7. package/components/im-dialog/im-dialog.vue +543 -0
  8. package/components/im-double-tap-view/im-double-tap-view.vue +93 -0
  9. package/components/im-emoji-picker/im-emoji-picker.vue +1143 -0
  10. package/components/im-friend-item/im-friend-item.vue +1 -1
  11. package/components/im-group-item/im-group-item.vue +1 -1
  12. package/components/im-group-member-selector/im-group-member-selector.vue +5 -5
  13. package/components/im-group-rtc-join/im-group-rtc-join.vue +8 -8
  14. package/components/im-icon/im-icon.vue +593 -0
  15. package/components/im-image-upload/im-image-upload.vue +0 -2
  16. package/components/im-link/im-link.vue +628 -0
  17. package/components/im-loading/im-loading.vue +13 -4
  18. package/components/im-mention-picker/im-mention-picker.vue +8 -7
  19. package/components/im-message-action/im-message-action.vue +678 -0
  20. package/components/im-message-item/im-message-item.vue +28 -26
  21. package/components/im-message-list/im-message-list.vue +1108 -0
  22. package/components/im-modal/im-modal.vue +373 -0
  23. package/components/im-nav-bar/im-nav-bar.vue +689 -75
  24. package/components/im-parse/im-parse.vue +1054 -0
  25. package/components/im-popup/im-popup.vue +467 -0
  26. package/components/im-read-receipt/im-read-receipt.vue +10 -10
  27. package/components/im-row/im-row.vue +189 -0
  28. package/components/im-search/im-search.vue +762 -0
  29. package/components/im-sku/im-sku.vue +720 -0
  30. package/components/im-sku/utils/helper.ts +182 -0
  31. package/components/im-stepper/im-stepper.vue +585 -0
  32. package/components/im-stepper/utils/helper.ts +167 -0
  33. package/components/im-tabs/im-tabs.vue +1022 -0
  34. package/components/im-tabs/tabs-navigation.vue +489 -0
  35. package/components/im-tabs/utils/helper.ts +181 -0
  36. package/components/im-tabs-tab-pane/im-tabs-tab-pane.vue +145 -0
  37. package/components/im-upload/im-upload.vue +1236 -0
  38. package/components/im-voice-input/im-voice-input.vue +1 -1
  39. package/index.js +3 -5
  40. package/index.scss +19 -0
  41. package/libs/emoji-data.ts +229 -0
  42. package/libs/index.ts +16 -16
  43. package/package.json +1 -2
  44. package/styles/button.scss +33 -33
  45. package/theme.scss +2 -2
  46. package/types/components/badge.d.ts +42 -0
  47. package/types/components/button.d.ts +2 -1
  48. package/types/components/card.d.ts +122 -0
  49. package/types/components/col.d.ts +37 -0
  50. package/types/components/dialog.d.ts +125 -0
  51. package/types/components/double-tap-view.d.ts +31 -0
  52. package/types/components/emoji-picker.d.ts +121 -0
  53. package/types/components/group-rtc-join.d.ts +1 -1
  54. package/types/components/icon.d.ts +77 -0
  55. package/types/components/link.d.ts +55 -0
  56. package/types/components/loading.d.ts +1 -0
  57. package/types/components/message-action.d.ts +96 -0
  58. package/types/components/message-item.d.ts +2 -2
  59. package/types/components/message-list.d.ts +136 -0
  60. package/types/components/modal.d.ts +106 -0
  61. package/types/components/nav-bar.d.ts +125 -0
  62. package/types/components/parse.d.ts +90 -0
  63. package/types/components/popup.d.ts +58 -0
  64. package/types/components/row.d.ts +31 -0
  65. package/types/components/search.d.ts +54 -0
  66. package/types/components/sku.d.ts +195 -0
  67. package/types/components/stepper.d.ts +99 -0
  68. package/types/components/tabs-tab-pane.d.ts +27 -0
  69. package/types/components/tabs.d.ts +117 -0
  70. package/types/components/upload.d.ts +137 -0
  71. package/types/components.d.ts +19 -1
  72. package/types/index.d.ts +38 -1
  73. package/types/libs/index.d.ts +10 -10
  74. package/types/utils/base64.d.ts +5 -0
  75. package/types/utils/dom.d.ts +3 -0
  76. package/types/utils/enums.d.ts +4 -5
  77. package/types/utils/validator.d.ts +74 -0
  78. package/utils/base64.js +18 -0
  79. package/utils/dom.js +353 -1
  80. package/utils/enums.js +4 -5
  81. package/utils/validator.js +230 -0
  82. package/components/im-file-upload/im-file-upload.vue +0 -309
  83. package/plugins/uview-plus.js +0 -29
  84. package/types/components/arrow-bar.d.ts +0 -14
  85. package/types/components/file-upload.d.ts +0 -58
@@ -0,0 +1,585 @@
1
+ <!-- components/im-stepper/im-stepper.vue -->
2
+ <template>
3
+ <view class="im-stepper" :class="[
4
+ customClass,
5
+ `im-stepper-${theme}`,
6
+ `im-stepper-shape-${shape}`,
7
+ { 'im-stepper-disabled': disabled }
8
+ ]">
9
+ <!-- 减按钮 -->
10
+ <view v-if="showMinus" class="im-stepper-minus" :class="[
11
+ minusClass,
12
+ {
13
+ 'im-stepper-button-disabled': isMinusDisabled,
14
+ 'im-stepper-button-active': isMinusActive
15
+ }
16
+ ]" :style="[
17
+ buttonStyle,
18
+ isMinusDisabled ? disabledButtonStyle : {},
19
+ isMinusActive ? buttonActiveStyle : {}
20
+ ]"
21
+ @click="onMinusClick">
22
+ <im-icon v-if="minusIcon" :name="minusIcon" :size="iconSize" :color="buttonTextColorComputed" />
23
+ <text v-else-if="minusText" class="im-stepper-button-text" :style="{ color: buttonTextColorComputed }">
24
+ {{ minusText }}
25
+ </text>
26
+ <text v-else class="im-stepper-button-text" :style="{ color: buttonTextColorComputed }">
27
+
28
+ </text>
29
+ </view>
30
+
31
+ <!-- 输入框 -->
32
+ <input v-if="showInput" ref="inputRef" class="im-stepper-input" :class="inputClass" :value="displayValue"
33
+ :disabled="disabled || disableInput" :placeholder="placeholder" :style="inputStyle" type="number"
34
+ :readonly="!showInput" @input="onInput" @blur="onBlur" @focus="onFocus" @keypress="onKeyPress" />
35
+
36
+ <!-- 加按钮 -->
37
+ <view v-if="showPlus" class="im-stepper-plus" :class="[
38
+ plusClass,
39
+ {
40
+ 'im-stepper-button-disabled': isPlusDisabled,
41
+ 'im-stepper-button-active': isPlusActive
42
+ }
43
+ ]" :style="[
44
+ buttonStyle,
45
+ isPlusDisabled ? disabledButtonStyle : {},
46
+ isPlusActive ? buttonActiveStyle : {}
47
+ ]" @click="onPlusClick">
48
+ <im-icon v-if="plusIcon" :name="plusIcon" :size="iconSize" :color="buttonTextColorComputed" />
49
+ <text v-else-if="plusText" class="im-stepper-button-text" :style="{ color: buttonTextColorComputed }">
50
+ {{ plusText }}
51
+ </text>
52
+ <text v-else class="im-stepper-button-text" :style="{ color: buttonTextColorComputed }">
53
+ +
54
+ </text>
55
+ </view>
56
+ </view>
57
+ </template>
58
+
59
+ <script lang="ts" setup>
60
+ import type { StepperProps, StepperEmits } from '../../types/components/stepper'
61
+
62
+ const props = withDefaults(defineProps<StepperProps>(), {
63
+ modelValue: 0,
64
+ defaultValue: 0,
65
+ min: -Infinity,
66
+ max: Infinity,
67
+ step: 1,
68
+ inputWidth: 32,
69
+ buttonSize: 28,
70
+ disabled: false,
71
+ disableInput: false,
72
+ disableMinus: false,
73
+ disablePlus: false,
74
+ showLimit: true,
75
+ showInput: true,
76
+ showPlus: true,
77
+ showMinus: true,
78
+ longPress: true,
79
+ longPressInterval: 100,
80
+ autofocus: false,
81
+ placeholder: '',
82
+ integer: false,
83
+ minusIcon: '',
84
+ plusIcon: '',
85
+ minusText: '',
86
+ plusText: '',
87
+ theme: 'default',
88
+ shape: 'circle',
89
+ buttonColor: '#f2f3f5',
90
+ buttonActiveColor: '#e8e8e8',
91
+ buttonTextColor: '#323233',
92
+ inputBgColor: '#ffffff',
93
+ inputTextColor: '#323233',
94
+ disabledColor: '#c8c9cc'
95
+ })
96
+
97
+ const emit = defineEmits<StepperEmits>()
98
+
99
+ // 响应式数据
100
+ const inputValue = ref(props.modelValue || props.defaultValue)
101
+ const isMinusActive = ref(false)
102
+ const isPlusActive = ref(false)
103
+ const longPressTimer = ref<number | null>(null)
104
+ const inputRef = ref<HTMLInputElement | null>(null)
105
+
106
+ // 计算属性
107
+ const displayValue = computed(() => {
108
+ return formatValue(inputValue.value)
109
+ })
110
+
111
+ const isMinusDisabled = computed(() => {
112
+ return (
113
+ props.disabled ||
114
+ props.disableMinus ||
115
+ (props.showLimit && inputValue.value <= props.min)
116
+ )
117
+ })
118
+
119
+ const isPlusDisabled = computed(() => {
120
+ return (
121
+ props.disabled ||
122
+ props.disablePlus ||
123
+ (props.showLimit && inputValue.value >= props.max)
124
+ )
125
+ })
126
+
127
+ const buttonStyle = computed(() => {
128
+ const size = props.buttonSize
129
+ const sizeStr = typeof size === 'number' ? `${size}px` : size
130
+
131
+ return {
132
+ width: sizeStr,
133
+ height: sizeStr,
134
+ backgroundColor: props.buttonColor,
135
+ color: props.buttonTextColor
136
+ }
137
+ })
138
+
139
+ const buttonActiveStyle = computed(() => ({
140
+ backgroundColor: props.buttonActiveColor
141
+ }))
142
+
143
+ const disabledButtonStyle = computed(() => ({
144
+ backgroundColor: props.disabledColor,
145
+ cursor: 'not-allowed',
146
+ opacity: 0.5
147
+ }))
148
+
149
+ const inputStyle = computed(() => {
150
+ const width = props.inputWidth
151
+ const widthStr = typeof width === 'number' ? `${width}px` : width
152
+
153
+ return {
154
+ width: widthStr,
155
+ backgroundColor: props.inputBgColor,
156
+ color: props.inputTextColor
157
+ }
158
+ })
159
+
160
+ const buttonTextColorComputed = computed(() => {
161
+ if (isMinusDisabled.value || isPlusDisabled.value) {
162
+ return props.disabledColor
163
+ }
164
+ return props.buttonTextColor
165
+ })
166
+
167
+ const iconSize = computed(() => {
168
+ const size = typeof props.buttonSize === 'number'
169
+ ? props.buttonSize * 0.6
170
+ : 16
171
+ return Math.max(size, 12)
172
+ })
173
+
174
+ // 格式化值
175
+ const formatValue = (value: number | string) => {
176
+ let num = Number(value)
177
+
178
+ // 处理NaN
179
+ if (isNaN(num)) {
180
+ num = props.defaultValue
181
+ }
182
+
183
+ // 处理整数模式
184
+ if (props.integer) {
185
+ num = Math.round(num)
186
+ }
187
+
188
+ // 处理最小值
189
+ if (num < props.min) {
190
+ num = props.min
191
+ }
192
+
193
+ // 处理最大值
194
+ if (num > props.max) {
195
+ num = props.max
196
+ }
197
+
198
+ // 处理小数精度
199
+ const decimal = (props.step.toString().split('.')[1] || '').length
200
+ if (decimal > 0) {
201
+ num = Number(num.toFixed(decimal))
202
+ }
203
+
204
+ return num
205
+ }
206
+
207
+ // 设置值
208
+ const setValue = (value: number | string, emitChange = true) => {
209
+ const oldValue = inputValue.value
210
+ const newValue = formatValue(value)
211
+
212
+ if (newValue !== oldValue) {
213
+ inputValue.value = newValue
214
+
215
+ if (emitChange) {
216
+ emit('update:modelValue', newValue)
217
+ emit('change', newValue)
218
+ }
219
+ }
220
+
221
+ return newValue
222
+ }
223
+
224
+ // 减少值
225
+ const minus = () => {
226
+ if (isMinusDisabled.value) {
227
+ emit('overlimit', 'minus')
228
+ return
229
+ }
230
+
231
+ const newValue = setValue(inputValue.value - props.step)
232
+ emit('minus', newValue)
233
+ }
234
+
235
+ // 增加值
236
+ const plus = () => {
237
+ if (isPlusDisabled.value) {
238
+ emit('overlimit', 'plus')
239
+ return
240
+ }
241
+
242
+ const newValue = setValue(inputValue.value + props.step)
243
+ emit('plus', newValue)
244
+ }
245
+
246
+ // 处理减按钮点击
247
+ const onMinusClick = () => {
248
+ if (props.disabled || isMinusDisabled.value) return
249
+ minus()
250
+ }
251
+
252
+ // 处理加按钮点击
253
+ const onPlusClick = () => {
254
+ if (props.disabled || isPlusDisabled.value) return
255
+ plus()
256
+ }
257
+
258
+ // 处理输入框输入
259
+ const onInput = (event: Event) => {
260
+ const target = event.target as HTMLInputElement
261
+ const value = target.value
262
+
263
+ // 过滤非数字字符
264
+ let cleanValue = value.replace(/[^\d.-]/g, '')
265
+
266
+ // 处理整数模式
267
+ if (props.integer) {
268
+ cleanValue = cleanValue.replace(/\./g, '')
269
+ }
270
+
271
+ // 处理多个小数点
272
+ const parts = cleanValue.split('.')
273
+ if (parts.length > 2) {
274
+ cleanValue = parts[0] + '.' + parts.slice(1).join('')
275
+ }
276
+
277
+ // 处理负号
278
+ if (cleanValue.startsWith('-') && props.min >= 0) {
279
+ cleanValue = cleanValue.substring(1)
280
+ }
281
+
282
+ // 更新输入框值
283
+ target.value = cleanValue
284
+
285
+ // 设置值
286
+ const newValue = setValue(cleanValue || 0, false)
287
+ emit('input', newValue)
288
+ }
289
+
290
+ // 处理输入框失去焦点
291
+ const onBlur = (event: Event) => {
292
+ const target = event.target as HTMLInputElement
293
+ const value = target.value
294
+
295
+ if (value === '') {
296
+ setValue(props.defaultValue)
297
+ } else {
298
+ setValue(value)
299
+ }
300
+
301
+ emit('blur', inputValue.value)
302
+ }
303
+
304
+ // 处理输入框获得焦点
305
+ const onFocus = () => {
306
+ emit('focus', inputValue.value)
307
+ }
308
+
309
+ // 处理键盘输入
310
+ const onKeyPress = (event: KeyboardEvent) => {
311
+ // 阻止输入非数字字符
312
+ const allowedKeys = [
313
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
314
+ '.', '-', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight',
315
+ 'Tab', 'Enter'
316
+ ]
317
+
318
+ if (!allowedKeys.includes(event.key) && !event.ctrlKey && !event.metaKey) {
319
+ event.preventDefault()
320
+ }
321
+ }
322
+
323
+ // // 长按处理
324
+ // const startLongPress = (type: 'minus' | 'plus') => {
325
+ // if (!props.longPress) return
326
+
327
+ // const action = type === 'minus' ? minus : plus
328
+
329
+ // // 立即执行一次
330
+ // action()
331
+
332
+ // // 设置定时器
333
+ // longPressTimer.value = setInterval(() => {
334
+ // action()
335
+ // }, props.longPressInterval) as unknown as number
336
+ // }
337
+
338
+ // const stopLongPress = () => {
339
+ // if (longPressTimer.value) {
340
+ // clearInterval(longPressTimer.value)
341
+ // longPressTimer.value = null
342
+ // }
343
+ // }
344
+
345
+ // // 减按钮触摸开始
346
+ // const onMinusTouchStart = () => {
347
+ // if (props.disabled || isMinusDisabled.value || !props.longPress) return
348
+ // isMinusActive.value = true
349
+ // startLongPress('minus')
350
+ // }
351
+
352
+ // // 减按钮触摸结束
353
+ // const onMinusTouchEnd = () => {
354
+ // isMinusActive.value = false
355
+ // stopLongPress()
356
+ // }
357
+
358
+ // // 加按钮触摸开始
359
+ // const onPlusTouchStart = () => {
360
+ // if (props.disabled || isPlusDisabled.value || !props.longPress) return
361
+ // isPlusActive.value = true
362
+ // startLongPress('plus')
363
+ // }
364
+
365
+ // // 加按钮触摸结束
366
+ // const onPlusTouchEnd = () => {
367
+ // isPlusActive.value = false
368
+ // stopLongPress()
369
+ // }
370
+
371
+ // 监听外部值变化
372
+ watch(() => props.modelValue, (newVal) => {
373
+ if (newVal !== undefined && newVal !== inputValue.value) {
374
+ setValue(newVal, false)
375
+ }
376
+ })
377
+
378
+ // 监听最小值最大值变化
379
+ watch([() => props.min, () => props.max], () => {
380
+ setValue(inputValue.value, false)
381
+ })
382
+
383
+ // 初始化
384
+ onMounted(() => {
385
+ // 设置初始值
386
+ if (props.modelValue !== undefined) {
387
+ setValue(props.modelValue, false)
388
+ } else if (props.defaultValue !== undefined) {
389
+ setValue(props.defaultValue, false)
390
+ }
391
+
392
+ // 自动聚焦
393
+ if (props.autofocus && inputRef.value) {
394
+ nextTick(() => {
395
+ inputRef.value?.focus()
396
+ })
397
+ }
398
+ })
399
+
400
+ // 暴露方法
401
+ defineExpose({
402
+ focus: () => {
403
+ inputRef.value?.focus()
404
+ },
405
+ blur: () => {
406
+ inputRef.value?.blur()
407
+ },
408
+ minus,
409
+ plus,
410
+ getValue: () => inputValue.value,
411
+ setValue
412
+ })
413
+ </script>
414
+
415
+ <style lang="scss" scoped>
416
+ .im-stepper {
417
+ display: inline-flex;
418
+ align-items: center;
419
+ font-size: 0;
420
+ user-select: none;
421
+
422
+ // 默认主题
423
+ &.im-stepper-default {
424
+
425
+ .im-stepper-minus,
426
+ .im-stepper-plus {
427
+ border: 1px solid #ebedf0;
428
+ }
429
+
430
+ .im-stepper-input {
431
+ border-top: 1px solid #ebedf0;
432
+ border-bottom: 1px solid #ebedf0;
433
+ margin: 0 2px;
434
+ }
435
+ }
436
+
437
+ // 圆角主题
438
+ &.im-stepper-round {
439
+ .im-stepper-minus {
440
+ border-radius: 20px 0 0 20px;
441
+ }
442
+
443
+ .im-stepper-plus {
444
+ border-radius: 0 20px 20px 0;
445
+ }
446
+
447
+ .im-stepper-input {
448
+ border-radius: 0;
449
+ }
450
+ }
451
+
452
+ // 方形主题
453
+ &.im-stepper-square {
454
+
455
+ .im-stepper-minus,
456
+ .im-stepper-plus,
457
+ .im-stepper-input {
458
+ border-radius: 0;
459
+ }
460
+ }
461
+
462
+ // 按钮形状
463
+ &.im-stepper-shape-circle {
464
+
465
+ .im-stepper-minus,
466
+ .im-stepper-plus {
467
+ border-radius: 50%;
468
+ }
469
+ }
470
+
471
+ &.im-stepper-shape-square {
472
+
473
+ .im-stepper-minus,
474
+ .im-stepper-plus {
475
+ border-radius: 4px;
476
+ }
477
+ }
478
+
479
+ // 禁用状态
480
+ &.im-stepper-disabled {
481
+ opacity: 0.5;
482
+ cursor: not-allowed;
483
+
484
+ .im-stepper-minus,
485
+ .im-stepper-plus,
486
+ .im-stepper-input {
487
+ cursor: not-allowed;
488
+ }
489
+ }
490
+ }
491
+
492
+ .im-stepper-minus,
493
+ .im-stepper-plus {
494
+ display: flex;
495
+ align-items: center;
496
+ justify-content: center;
497
+ box-sizing: border-box;
498
+ cursor: pointer;
499
+ transition: all 0.2s;
500
+
501
+ &:active {
502
+ opacity: 0.8;
503
+ }
504
+
505
+ &.im-stepper-button-disabled {
506
+ cursor: not-allowed;
507
+ opacity: 0.5;
508
+
509
+ &:active {
510
+ opacity: 0.5;
511
+ }
512
+ }
513
+
514
+ &.im-stepper-button-active {
515
+ opacity: 0.8;
516
+ }
517
+ }
518
+
519
+ .im-stepper-button-text {
520
+ font-size: 16px;
521
+ font-weight: 500;
522
+ line-height: 1;
523
+ }
524
+
525
+ .im-stepper-input {
526
+ min-height: 28px;
527
+ box-sizing: border-box;
528
+ border: none;
529
+ font-size: 14px;
530
+ text-align: center;
531
+ vertical-align: middle;
532
+ outline: none;
533
+ -webkit-appearance: none;
534
+ appearance: none;
535
+
536
+ &:disabled {
537
+ background-color: #f2f3f5;
538
+ color: #c8c9cc;
539
+ cursor: not-allowed;
540
+ }
541
+
542
+ // 隐藏数字输入框的箭头
543
+ &::-webkit-outer-spin-button,
544
+ &::-webkit-inner-spin-button {
545
+ -webkit-appearance: none;
546
+ margin: 0;
547
+ }
548
+
549
+ &[type="number"] {
550
+ -moz-appearance: textfield;
551
+ }
552
+ }
553
+
554
+ // 响应式适配
555
+ @media (max-width: 768px) {
556
+ .im-stepper {
557
+ .im-stepper-button-text {
558
+ font-size: 14px;
559
+ }
560
+
561
+ .im-stepper-input {
562
+ font-size: 12px;
563
+ }
564
+ }
565
+ }
566
+
567
+ // 深色模式适配
568
+ @media (prefers-color-scheme: dark) {
569
+ .im-stepper {
570
+ &.im-stepper-default {
571
+
572
+ .im-stepper-minus,
573
+ .im-stepper-plus {
574
+ border-color: #333;
575
+ }
576
+
577
+ .im-stepper-input {
578
+ border-color: #333;
579
+ background-color: #1a1a1a;
580
+ color: #fff;
581
+ }
582
+ }
583
+ }
584
+ }
585
+ </style>