im-ui-mobile 0.0.98 → 0.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 (40) hide show
  1. package/components/im-avatar/im-avatar.vue +121 -121
  2. package/components/im-button/im-button.vue +630 -626
  3. package/components/im-cell/im-cell.vue +10 -15
  4. package/components/im-cell-group/im-cell-group.vue +16 -16
  5. package/components/im-chat-item/im-chat-item.vue +213 -213
  6. package/components/im-context-menu/im-context-menu.vue +138 -138
  7. package/components/im-file-upload/im-file-upload.vue +309 -309
  8. package/components/im-friend-item/im-friend-item.vue +82 -75
  9. package/components/im-group-item/im-group-item.vue +62 -62
  10. package/components/im-group-member-selector/im-group-member-selector.vue +202 -202
  11. package/components/im-group-rtc-join/im-group-rtc-join.vue +112 -112
  12. package/components/im-image-upload/im-image-upload.vue +94 -94
  13. package/components/im-loading/im-loading.vue +64 -64
  14. package/components/im-mention-picker/im-mention-picker.vue +191 -191
  15. package/components/im-message-item/im-message-item.vue +555 -555
  16. package/components/im-nav-bar/im-nav-bar.vue +98 -98
  17. package/components/im-read-receipt/im-read-receipt.vue +174 -174
  18. package/components/im-virtual-list/im-virtual-list.vue +52 -51
  19. package/components/im-voice-input/im-voice-input.vue +305 -305
  20. package/libs/index.ts +2 -3
  21. package/package.json +58 -58
  22. package/styles/button.scss +1 -1
  23. package/theme.scss +61 -62
  24. package/types/components.d.ts +0 -1
  25. package/types/index.d.ts +94 -94
  26. package/types/libs/index.d.ts +206 -204
  27. package/types/utils/datetime.d.ts +9 -9
  28. package/types/utils/dom.d.ts +11 -11
  29. package/types/utils/emoji.d.ts +8 -8
  30. package/types/utils/enums.d.ts +73 -73
  31. package/types/utils/messageType.d.ts +35 -35
  32. package/types/utils/recorderApp.d.ts +9 -9
  33. package/types/utils/recorderH5.d.ts +9 -9
  34. package/types/utils/requester.d.ts +15 -15
  35. package/types/utils/url.d.ts +5 -5
  36. package/types/utils/useDynamicRefs.d.ts +9 -9
  37. package/types/utils/websocket.d.ts +34 -34
  38. package/utils/enums.js +1 -1
  39. package/components/im-virtual-scroller/im-virtual-scroller.vue +0 -54
  40. package/types/components/virtual-scroller.d.ts +0 -20
@@ -1,627 +1,631 @@
1
- <template>
2
- <button class="im-button" :class="[
3
- `im-button--${type}`,
4
- `im-button--${size}`,
5
- `im-button--${shape}`,
6
- {
7
- 'im-button--disabled': disabled,
8
- 'im-button--loading': loading,
9
- 'im-button--block': block,
10
- 'im-button--plain': plain,
11
- 'im-button--hairline': hairline,
12
- 'im-button--native': native
13
- }
14
- ]" :style="buttonStyles" :disabled="disabled || loading" :loading="loading && showLoading" :form-type="formType"
15
- :open-type="openType" :hover-class="disabled || loading ? '' : hoverClass" :hover-start-time="hoverStartTime"
16
- :hover-stay-time="hoverStayTime" :app-parameter="appParameter" :lang="lang" :session-from="sessionFrom"
17
- :send-message-title="sendMessageTitle" :send-message-path="sendMessagePath" :send-message-img="sendMessageImg"
18
- :show-message-card="showMessageCard" @click="handleClick" @getuserinfo="onGetUserInfo" @contact="onContact"
19
- @getphonenumber="onGetPhoneNumber" @error="onError" @launchapp="onLaunchApp" @opensetting="onOpenSetting"
20
- @chooseavatar="onChooseAvatar">
21
- <!-- 加载状态 -->
22
- <view v-if="loading && showLoading" class="im-button__loading">
23
- <view class="im-button__loading-spinner" :style="loadingStyle">
24
- <view v-for="n in loadingDotCount" :key="n" class="im-button__loading-dot" :style="{
25
- animationDelay: `${(n - 1) * 0.15}s`,
26
- backgroundColor: loadingColor
27
- }" />
28
- </view>
29
- <text v-if="loadingText" class="im-button__loading-text">
30
- {{ loadingText }}
31
- </text>
32
- </view>
33
-
34
- <!-- 图标 -->
35
- <view v-if="(icon || $slots.icon) && !loading" class="im-button__icon">
36
- <slot name="icon">
37
- <image v-if="icon" :src="icon" class="im-button__icon-image"
38
- :class="[`im-button__icon--${iconPosition}`]" mode="aspectFit" />
39
- </slot>
40
- </view>
41
-
42
- <!-- 文本内容 -->
43
- <text v-if="!loading || !hideTextWhenLoading" class="im-button__text" :class="[`im-button__text--${textSize}`]"
44
- :style="textStyles">
45
- <slot>{{ text }}</slot>
46
- </text>
47
-
48
- <!-- 角标 -->
49
- <view v-if="badge || $slots.badge" class="im-button__badge">
50
- <slot name="badge">
51
- <view v-if="badge" class="im-button__badge-content" :class="{
52
- 'im-button__badge--dot': badge === true,
53
- 'im-button__badge--text': badge !== true
54
- }">
55
- <text v-if="badge !== true" class="im-button__badge-text">
56
- {{ formatBadge(badge) }}
57
- </text>
58
- </view>
59
- </slot>
60
- </view>
61
- </button>
62
- </template>
63
-
64
- <script setup lang="ts">
65
- import { computed, withDefaults } from 'vue'
66
-
67
- // 定义类型
68
- type ButtonType = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
69
- type ButtonSize = 'mini' | 'small' | 'medium' | 'large'
70
- type ButtonShape = 'square' | 'round' | 'circle'
71
- type ButtonFormType = 'submit' | 'reset'
72
- type ButtonOpenType =
73
- | 'contact'
74
- | 'share'
75
- | 'getPhoneNumber'
76
- | 'getUserInfo'
77
- | 'launchApp'
78
- | 'openSetting'
79
- | 'feedback'
80
- | 'chooseAvatar'
81
- type IconPosition = 'left' | 'right'
82
- type TextSize = 'small' | 'medium' | 'large'
83
-
84
- interface Props {
85
- // 基础属性
86
- text?: string
87
- type?: ButtonType
88
- size?: ButtonSize
89
- shape?: ButtonShape
90
- disabled?: boolean
91
- loading?: boolean
92
- loadingText?: string
93
- loadingColor?: string
94
- showLoading?: boolean
95
- hideTextWhenLoading?: boolean
96
- block?: boolean
97
- plain?: boolean
98
- hairline?: boolean
99
- native?: boolean
100
-
101
- // 图标
102
- icon?: string
103
- iconPosition?: IconPosition
104
- iconSize?: string | number
105
-
106
- // 样式
107
- color?: string
108
- bgColor?: string
109
- borderColor?: string
110
- textColor?: string
111
- textSize?: TextSize
112
- width?: string
113
- height?: string
114
- padding?: string
115
- margin?: string
116
- radius?: string
117
- shadow?: string
118
-
119
- // 角标
120
- badge?: boolean | number | string
121
-
122
- // 表单相关
123
- formType?: ButtonFormType
124
-
125
- // 开放能力
126
- openType?: ButtonOpenType
127
- appParameter?: string
128
- lang?: 'en' | 'zh_CN' | 'zh_TW'
129
- sessionFrom?: string
130
- sendMessageTitle?: string
131
- sendMessagePath?: string
132
- sendMessageImg?: string
133
- showMessageCard?: boolean
134
-
135
- // 交互效果
136
- hoverClass?: string
137
- hoverStartTime?: number
138
- hoverStayTime?: number
139
- }
140
-
141
- // 定义 Emits
142
- interface Emits {
143
- (e: 'click', event: PointerEvent): void
144
- (e: 'getuserinfo', detail: any): void
145
- (e: 'contact', detail: any): void
146
- (e: 'getphonenumber', detail: any): void
147
- (e: 'error', detail: any): void
148
- (e: 'launchapp', detail: any): void
149
- (e: 'opensetting', detail: any): void
150
- (e: 'chooseavatar', detail: any): void
151
- }
152
-
153
- // Props 和 Emits
154
- const props = withDefaults(defineProps<Props>(), {
155
- type: 'default',
156
- size: 'medium',
157
- shape: 'round',
158
- disabled: false,
159
- loading: false,
160
- loadingColor: '#ffffff',
161
- showLoading: true,
162
- hideTextWhenLoading: false,
163
- block: false,
164
- plain: false,
165
- hairline: false,
166
- native: false,
167
- iconPosition: 'left',
168
- iconSize: '32rpx',
169
- textSize: 'medium',
170
- color: '',
171
- bgColor: '',
172
- borderColor: '',
173
- textColor: '',
174
- width: '',
175
- height: '',
176
- padding: '',
177
- margin: '',
178
- radius: '',
179
- shadow: '',
180
- hoverClass: 'im-button--hover',
181
- hoverStartTime: 20,
182
- hoverStayTime: 70,
183
- lang: 'zh_CN'
184
- })
185
-
186
- const emit = defineEmits<Emits>()
187
-
188
- // 计算属性
189
- const buttonStyles = computed(() => {
190
- const styles: Record<string, string> = {}
191
-
192
- // 自定义颜色
193
- if (props.color && !props.plain) {
194
- styles.backgroundColor = props.color
195
- styles.borderColor = props.color
196
- }
197
-
198
- if (props.bgColor) {
199
- styles.backgroundColor = props.bgColor
200
- }
201
-
202
- if (props.borderColor) {
203
- styles.borderColor = props.borderColor
204
- }
205
-
206
- if (props.textColor) {
207
- styles.color = props.textColor
208
- }
209
-
210
- // 尺寸和布局
211
- if (props.width) {
212
- styles.width = props.width
213
- }
214
-
215
- if (props.height) {
216
- styles.height = props.height
217
- }
218
-
219
- if (props.padding) {
220
- styles.padding = props.padding
221
- }
222
-
223
- if (props.margin) {
224
- styles.margin = props.margin
225
- }
226
-
227
- if (props.radius) {
228
- styles.borderRadius = props.radius
229
- }
230
-
231
- if (props.shadow) {
232
- styles.boxShadow = props.shadow
233
- }
234
-
235
- if (props.block) {
236
- styles.width = '100%'
237
- }
238
-
239
- return styles
240
- })
241
-
242
- const textStyles = computed(() => {
243
- const styles: Record<string, string> = {}
244
-
245
- if (props.textColor) {
246
- styles.color = props.textColor
247
- }
248
-
249
- return styles
250
- })
251
-
252
- const loadingStyle = computed(() => {
253
- return {
254
- color: props.loadingColor
255
- }
256
- })
257
-
258
- const loadingDotCount = computed(() => {
259
- // 根据按钮大小决定点数
260
- switch (props.size) {
261
- case 'mini': return 3
262
- case 'small': return 4
263
- case 'medium': return 5
264
- case 'large': return 6
265
- default: return 5
266
- }
267
- })
268
-
269
- // 方法
270
- const formatBadge = (badge: boolean | number | string): string => {
271
- if (typeof badge === 'boolean') return ''
272
- if (typeof badge === 'number') {
273
- return badge > 99 ? '99+' : badge.toString()
274
- }
275
- return badge
276
- }
277
-
278
- // 事件处理
279
- const handleClick = (event: PointerEvent) => {
280
- if (props.disabled || props.loading) {
281
- return
282
- }
283
- emit('click', event)
284
- }
285
-
286
- const onGetUserInfo = (detail: any) => {
287
- emit('getuserinfo', detail)
288
- }
289
-
290
- const onContact = (detail: any) => {
291
- emit('contact', detail)
292
- }
293
-
294
- const onGetPhoneNumber = (detail: any) => {
295
- emit('getphonenumber', detail)
296
- }
297
-
298
- const onError = (detail: any) => {
299
- emit('error', detail)
300
- }
301
-
302
- const onLaunchApp = (detail: any) => {
303
- emit('launchapp', detail)
304
- }
305
-
306
- const onOpenSetting = (detail: any) => {
307
- emit('opensetting', detail)
308
- }
309
-
310
- const onChooseAvatar = (detail: any) => {
311
- emit('chooseavatar', detail)
312
- }
313
- </script>
314
-
315
- <style lang="scss" scoped>
316
- .im-button {
317
- position: relative;
318
- display: inline-flex;
319
- align-items: center;
320
- justify-content: center;
321
- border: 2rpx solid transparent;
322
- font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
323
- font-weight: 500;
324
- text-align: center;
325
- white-space: nowrap;
326
- vertical-align: middle;
327
- user-select: none;
328
- cursor: pointer;
329
- transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
330
- outline: none;
331
- box-sizing: border-box;
332
-
333
- // 禁用状态
334
- &--disabled {
335
- opacity: 0.6;
336
- cursor: not-allowed;
337
- }
338
-
339
- // 加载状态
340
- &--loading {
341
- cursor: not-allowed;
342
- }
343
-
344
- // 块级按钮
345
- &--block {
346
- display: flex;
347
- width: 100%;
348
- }
349
-
350
- // 朴素按钮
351
- &--plain {
352
- background-color: transparent !important;
353
- }
354
-
355
- // 细边框
356
- &--hairline {
357
- position: relative;
358
- border-width: 0;
359
-
360
- &::after {
361
- content: '';
362
- position: absolute;
363
- top: 0;
364
- left: 0;
365
- right: 0;
366
- bottom: 0;
367
- border: 2rpx solid currentColor;
368
- border-radius: inherit;
369
- pointer-events: none;
370
- transform: scale(0.5);
371
- transform-origin: center;
372
- }
373
- }
374
-
375
- // 原生按钮
376
- &--native {
377
- padding: 0;
378
- background-color: transparent;
379
- border: none;
380
- line-height: normal;
381
-
382
- &::after {
383
- display: none;
384
- }
385
- }
386
-
387
- // 悬停效果
388
- &--hover {
389
- opacity: 0.8;
390
- transform: translateY(-1rpx);
391
- }
392
-
393
- // 尺寸
394
- &--mini {
395
- height: 48rpx;
396
- padding: 0 16rpx;
397
- font-size: 24rpx;
398
- border-radius: 20rpx;
399
- }
400
-
401
- &--small {
402
- height: 64rpx;
403
- padding: 0 24rpx;
404
- font-size: 28rpx;
405
- border-radius: 32rpx;
406
- }
407
-
408
- &--medium {
409
- height: 80rpx;
410
- padding: 0 32rpx;
411
- font-size: 32rpx;
412
- border-radius: 40rpx;
413
- }
414
-
415
- &--large {
416
- height: 96rpx;
417
- padding: 0 40rpx;
418
- font-size: 36rpx;
419
- border-radius: 48rpx;
420
- }
421
-
422
- // 形状
423
- &--square {
424
- border-radius: 8rpx;
425
- }
426
-
427
- &--round {
428
- // 圆角已经在尺寸中定义
429
- }
430
-
431
- &--circle {
432
- border-radius: 50%;
433
- padding: 0;
434
- width: var(--button-height);
435
- min-width: var(--button-height);
436
- }
437
-
438
- // 类型
439
- &--default {
440
- color: #606266;
441
- background-color: #ffffff;
442
- border-color: #dcdfe6;
443
-
444
- &.im-button--plain {
445
- color: #606266;
446
- border-color: #dcdfe6;
447
- }
448
- }
449
-
450
- &--primary {
451
- color: #ffffff;
452
- background-color: #409eff;
453
- border-color: #409eff;
454
-
455
- &.im-button--plain {
456
- color: #409eff;
457
- background-color: transparent;
458
- border-color: #409eff;
459
- }
460
- }
461
-
462
- &--success {
463
- color: #ffffff;
464
- background-color: #67c23a;
465
- border-color: #67c23a;
466
-
467
- &.im-button--plain {
468
- color: #67c23a;
469
- background-color: transparent;
470
- border-color: #67c23a;
471
- }
472
- }
473
-
474
- &--warning {
475
- color: #ffffff;
476
- background-color: #e6a23c;
477
- border-color: #e6a23c;
478
-
479
- &.im-button--plain {
480
- color: #e6a23c;
481
- background-color: transparent;
482
- border-color: #e6a23c;
483
- }
484
- }
485
-
486
- &--danger {
487
- color: #ffffff;
488
- background-color: #f56c6c;
489
- border-color: #f56c6c;
490
-
491
- &.im-button--plain {
492
- color: #f56c6c;
493
- background-color: transparent;
494
- border-color: #f56c6c;
495
- }
496
- }
497
-
498
- &--info {
499
- color: #ffffff;
500
- background-color: #909399;
501
- border-color: #909399;
502
-
503
- &.im-button--plain {
504
- color: #909399;
505
- background-color: transparent;
506
- border-color: #909399;
507
- }
508
- }
509
-
510
- // 内部元素
511
- &__loading {
512
- display: flex;
513
- align-items: center;
514
- justify-content: center;
515
-
516
- &-spinner {
517
- display: inline-flex;
518
- align-items: center;
519
- justify-content: center;
520
- margin-right: 8rpx;
521
- }
522
-
523
- &-dot {
524
- width: 6rpx;
525
- height: 6rpx;
526
- border-radius: 50%;
527
- margin: 0 2rpx;
528
- animation: button-loading 1.4s infinite ease-in-out both;
529
-
530
- @keyframes button-loading {
531
-
532
- 0%,
533
- 80%,
534
- 100% {
535
- transform: scale(0.6);
536
- opacity: 0.6;
537
- }
538
-
539
- 40% {
540
- transform: scale(1);
541
- opacity: 1;
542
- }
543
- }
544
- }
545
-
546
- &-text {
547
- font-size: inherit;
548
- color: inherit;
549
- }
550
- }
551
-
552
- &__icon {
553
- display: flex;
554
- align-items: center;
555
- justify-content: center;
556
- margin-right: 8rpx;
557
-
558
- &--right {
559
- margin-right: 0;
560
- margin-left: 8rpx;
561
- order: 1;
562
- }
563
-
564
- &-image {
565
- width: v-bind(iconSize);
566
- height: v-bind(iconSize);
567
- }
568
- }
569
-
570
- &__text {
571
- display: inline-block;
572
- line-height: 1;
573
- overflow: hidden;
574
- text-overflow: ellipsis;
575
- white-space: nowrap;
576
-
577
- &--small {
578
- font-size: 24rpx;
579
- }
580
-
581
- &--medium {
582
- font-size: 32rpx;
583
- }
584
-
585
- &--large {
586
- font-size: 36rpx !important;
587
- }
588
- }
589
-
590
- &__badge {
591
- position: absolute;
592
- top: -10rpx;
593
- right: -10rpx;
594
- z-index: 1;
595
-
596
- &-content {
597
- display: flex;
598
- align-items: center;
599
- justify-content: center;
600
- }
601
-
602
- &--dot {
603
- width: 16rpx;
604
- height: 16rpx;
605
- background-color: #fa3534;
606
- border-radius: 50%;
607
- border: 2rpx solid #ffffff;
608
- }
609
-
610
- &--text {
611
- min-width: 32rpx;
612
- height: 32rpx;
613
- padding: 0 8rpx;
614
- background-color: #fa3534;
615
- border-radius: 16rpx;
616
- border: 2rpx solid #ffffff;
617
- }
618
-
619
- &-text {
620
- color: #ffffff;
621
- font-size: 20rpx;
622
- font-weight: bold;
623
- line-height: 1;
624
- }
625
- }
626
- }
1
+ <template>
2
+ <button class="im-button" :class="[
3
+ `im-button--${type}`,
4
+ `im-button--${size}`,
5
+ `im-button--${shape}`,
6
+ {
7
+ 'im-button--disabled': disabled,
8
+ 'im-button--loading': loading,
9
+ 'im-button--block': block,
10
+ 'im-button--plain': plain,
11
+ 'im-button--hairline': hairline,
12
+ 'im-button--native': native
13
+ }
14
+ ]" :style="buttonStyles" :disabled="disabled || loading" :loading="loading && showLoading" :form-type="formType"
15
+ :open-type="openType" :hover-class="disabled || loading ? '' : hoverClass" :hover-start-time="hoverStartTime"
16
+ :hover-stay-time="hoverStayTime" :app-parameter="appParameter" :lang="lang" :session-from="sessionFrom"
17
+ :send-message-title="sendMessageTitle" :send-message-path="sendMessagePath" :send-message-img="sendMessageImg"
18
+ :show-message-card="showMessageCard" @click="handleClick" @getuserinfo="onGetUserInfo" @contact="onContact"
19
+ @getphonenumber="onGetPhoneNumber" @error="onError" @launchapp="onLaunchApp" @opensetting="onOpenSetting"
20
+ @chooseavatar="onChooseAvatar">
21
+ <!-- 加载状态 -->
22
+ <view v-if="loading && showLoading" class="im-button__loading">
23
+ <view class="im-button__loading-spinner" :style="loadingStyle">
24
+ <view v-for="n in loadingDotCount" :key="n" class="im-button__loading-dot" :style="{
25
+ animationDelay: `${(n - 1) * 0.15}s`,
26
+ backgroundColor: loadingColor
27
+ }" />
28
+ </view>
29
+ <text v-if="loadingText" class="im-button__loading-text">
30
+ {{ loadingText }}
31
+ </text>
32
+ </view>
33
+
34
+ <!-- 图标 -->
35
+ <view v-if="(icon || $slots.icon) && !loading" class="im-button__icon">
36
+ <slot name="icon">
37
+ <image v-if="icon" :src="icon" class="im-button__icon-image"
38
+ :class="[`im-button__icon--${iconPosition}`]" mode="aspectFit" />
39
+ </slot>
40
+ </view>
41
+
42
+ <!-- 文本内容 -->
43
+ <text v-if="!loading || !hideTextWhenLoading" class="im-button__text" :class="[`im-button__text--${size}`]"
44
+ :style="textStyles">
45
+ <slot>{{ text }}</slot>
46
+ </text>
47
+
48
+ <!-- 角标 -->
49
+ <view v-if="badge || $slots.badge" class="im-button__badge">
50
+ <slot name="badge">
51
+ <view v-if="badge" class="im-button__badge-content" :class="{
52
+ 'im-button__badge--dot': badge === true,
53
+ 'im-button__badge--text': badge !== true
54
+ }">
55
+ <text v-if="badge !== true" class="im-button__badge-text">
56
+ {{ formatBadge(badge) }}
57
+ </text>
58
+ </view>
59
+ </slot>
60
+ </view>
61
+ </button>
62
+ </template>
63
+
64
+ <script setup lang="ts">
65
+ import { computed, withDefaults } from 'vue'
66
+
67
+ // 定义类型
68
+ type ButtonType = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
69
+ type ButtonSize = 'mini' | 'small' | 'medium' | 'large'
70
+ type ButtonShape = 'square' | 'round' | 'circle'
71
+ type ButtonFormType = 'submit' | 'reset'
72
+ type ButtonOpenType =
73
+ | 'contact'
74
+ | 'share'
75
+ | 'getPhoneNumber'
76
+ | 'getUserInfo'
77
+ | 'launchApp'
78
+ | 'openSetting'
79
+ | 'feedback'
80
+ | 'chooseAvatar'
81
+ type IconPosition = 'left' | 'right'
82
+ type TextSize = 'small' | 'medium' | 'large'
83
+
84
+ interface Props {
85
+ // 基础属性
86
+ text?: string
87
+ type?: ButtonType
88
+ size?: ButtonSize
89
+ shape?: ButtonShape
90
+ disabled?: boolean
91
+ loading?: boolean
92
+ loadingText?: string
93
+ loadingColor?: string
94
+ showLoading?: boolean
95
+ hideTextWhenLoading?: boolean
96
+ block?: boolean
97
+ plain?: boolean
98
+ hairline?: boolean
99
+ native?: boolean
100
+
101
+ // 图标
102
+ icon?: string
103
+ iconPosition?: IconPosition
104
+ iconSize?: string | number
105
+
106
+ // 样式
107
+ color?: string
108
+ bgColor?: string
109
+ borderColor?: string
110
+ textColor?: string
111
+ textSize?: TextSize
112
+ width?: string
113
+ height?: string
114
+ padding?: string
115
+ margin?: string
116
+ radius?: string
117
+ shadow?: string
118
+
119
+ // 角标
120
+ badge?: boolean | number | string
121
+
122
+ // 表单相关
123
+ formType?: ButtonFormType
124
+
125
+ // 开放能力
126
+ openType?: ButtonOpenType
127
+ appParameter?: string
128
+ lang?: 'en' | 'zh_CN' | 'zh_TW'
129
+ sessionFrom?: string
130
+ sendMessageTitle?: string
131
+ sendMessagePath?: string
132
+ sendMessageImg?: string
133
+ showMessageCard?: boolean
134
+
135
+ // 交互效果
136
+ hoverClass?: string
137
+ hoverStartTime?: number
138
+ hoverStayTime?: number
139
+ }
140
+
141
+ // 定义 Emits
142
+ interface Emits {
143
+ (e: 'click', event: PointerEvent): void
144
+ (e: 'getuserinfo', detail: any): void
145
+ (e: 'contact', detail: any): void
146
+ (e: 'getphonenumber', detail: any): void
147
+ (e: 'error', detail: any): void
148
+ (e: 'launchapp', detail: any): void
149
+ (e: 'opensetting', detail: any): void
150
+ (e: 'chooseavatar', detail: any): void
151
+ }
152
+
153
+ // Props 和 Emits
154
+ const props = withDefaults(defineProps<Props>(), {
155
+ type: 'default',
156
+ size: 'medium',
157
+ shape: 'round',
158
+ disabled: false,
159
+ loading: false,
160
+ loadingColor: '#ffffff',
161
+ showLoading: true,
162
+ hideTextWhenLoading: false,
163
+ block: false,
164
+ plain: false,
165
+ hairline: false,
166
+ native: false,
167
+ iconPosition: 'left',
168
+ iconSize: '32rpx',
169
+ textSize: 'medium',
170
+ color: '',
171
+ bgColor: '',
172
+ borderColor: '',
173
+ textColor: '',
174
+ width: '',
175
+ height: '',
176
+ padding: '',
177
+ margin: '',
178
+ radius: '',
179
+ shadow: '',
180
+ hoverClass: 'im-button--hover',
181
+ hoverStartTime: 20,
182
+ hoverStayTime: 70,
183
+ lang: 'zh_CN'
184
+ })
185
+
186
+ const emit = defineEmits<Emits>()
187
+
188
+ // 计算属性
189
+ const buttonStyles = computed(() => {
190
+ const styles: Record<string, string> = {}
191
+
192
+ // 自定义颜色
193
+ if (props.color && !props.plain) {
194
+ styles.backgroundColor = props.color
195
+ styles.borderColor = props.color
196
+ }
197
+
198
+ if (props.bgColor) {
199
+ styles.backgroundColor = props.bgColor
200
+ }
201
+
202
+ if (props.borderColor) {
203
+ styles.borderColor = props.borderColor
204
+ }
205
+
206
+ if (props.textColor) {
207
+ styles.color = props.textColor
208
+ }
209
+
210
+ // 尺寸和布局
211
+ if (props.width) {
212
+ styles.width = props.width
213
+ }
214
+
215
+ if (props.height) {
216
+ styles.height = props.height
217
+ }
218
+
219
+ if (props.padding) {
220
+ styles.padding = props.padding
221
+ }
222
+
223
+ if (props.margin) {
224
+ styles.margin = props.margin
225
+ }
226
+
227
+ if (props.radius) {
228
+ styles.borderRadius = props.radius
229
+ }
230
+
231
+ if (props.shadow) {
232
+ styles.boxShadow = props.shadow
233
+ }
234
+
235
+ if (props.block) {
236
+ styles.width = '100%'
237
+ }
238
+
239
+ return styles
240
+ })
241
+
242
+ const textStyles = computed(() => {
243
+ const styles: Record<string, string> = {}
244
+
245
+ if (props.textColor) {
246
+ styles.color = props.textColor
247
+ }
248
+
249
+ return styles
250
+ })
251
+
252
+ const loadingStyle = computed(() => {
253
+ return {
254
+ color: props.loadingColor
255
+ }
256
+ })
257
+
258
+ const loadingDotCount = computed(() => {
259
+ // 根据按钮大小决定点数
260
+ switch (props.size) {
261
+ case 'mini': return 3
262
+ case 'small': return 4
263
+ case 'medium': return 5
264
+ case 'large': return 6
265
+ default: return 5
266
+ }
267
+ })
268
+
269
+ // 方法
270
+ const formatBadge = (badge: boolean | number | string): string => {
271
+ if (typeof badge === 'boolean') return ''
272
+ if (typeof badge === 'number') {
273
+ return badge > 99 ? '99+' : badge.toString()
274
+ }
275
+ return badge
276
+ }
277
+
278
+ // 事件处理
279
+ const handleClick = (event: PointerEvent) => {
280
+ if (props.disabled || props.loading) {
281
+ return
282
+ }
283
+ emit('click', event)
284
+ }
285
+
286
+ const onGetUserInfo = (detail: any) => {
287
+ emit('getuserinfo', detail)
288
+ }
289
+
290
+ const onContact = (detail: any) => {
291
+ emit('contact', detail)
292
+ }
293
+
294
+ const onGetPhoneNumber = (detail: any) => {
295
+ emit('getphonenumber', detail)
296
+ }
297
+
298
+ const onError = (detail: any) => {
299
+ emit('error', detail)
300
+ }
301
+
302
+ const onLaunchApp = (detail: any) => {
303
+ emit('launchapp', detail)
304
+ }
305
+
306
+ const onOpenSetting = (detail: any) => {
307
+ emit('opensetting', detail)
308
+ }
309
+
310
+ const onChooseAvatar = (detail: any) => {
311
+ emit('chooseavatar', detail)
312
+ }
313
+ </script>
314
+
315
+ <style lang="scss" scoped>
316
+ .im-button {
317
+ position: relative;
318
+ display: inline-flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ border: 2rpx solid transparent;
322
+ font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
323
+ font-weight: 500;
324
+ text-align: center;
325
+ white-space: nowrap;
326
+ vertical-align: middle;
327
+ user-select: none;
328
+ cursor: pointer;
329
+ transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
330
+ outline: none;
331
+ box-sizing: border-box;
332
+
333
+ // 禁用状态
334
+ &--disabled {
335
+ opacity: 0.6;
336
+ cursor: not-allowed;
337
+ }
338
+
339
+ // 加载状态
340
+ &--loading {
341
+ cursor: not-allowed;
342
+ }
343
+
344
+ // 块级按钮
345
+ &--block {
346
+ display: flex;
347
+ width: 100%;
348
+ }
349
+
350
+ // 朴素按钮
351
+ &--plain {
352
+ background-color: transparent !important;
353
+ }
354
+
355
+ // 细边框
356
+ &--hairline {
357
+ position: relative;
358
+ border-width: 0;
359
+
360
+ &::after {
361
+ content: '';
362
+ position: absolute;
363
+ top: 0;
364
+ left: 0;
365
+ right: 0;
366
+ bottom: 0;
367
+ border: 2rpx solid currentColor;
368
+ border-radius: inherit;
369
+ pointer-events: none;
370
+ transform: scale(0.5);
371
+ transform-origin: center;
372
+ }
373
+ }
374
+
375
+ // 原生按钮
376
+ &--native {
377
+ padding: 0;
378
+ background-color: transparent;
379
+ border: none;
380
+ line-height: normal;
381
+
382
+ &::after {
383
+ display: none;
384
+ }
385
+ }
386
+
387
+ // 悬停效果
388
+ &--hover {
389
+ opacity: 0.8;
390
+ transform: translateY(-1rpx);
391
+ }
392
+
393
+ // 尺寸
394
+ &--mini {
395
+ height: 32rpx;
396
+ padding: 0 16rpx;
397
+ font-size: $im-font-size-mini;
398
+ border-radius: 20rpx;
399
+ }
400
+
401
+ &--small {
402
+ height: 48rpx;
403
+ padding: 0 24rpx;
404
+ font-size: $im-font-size-small;
405
+ border-radius: 32rpx;
406
+ }
407
+
408
+ &--medium {
409
+ height: 60rpx;
410
+ padding: 0 32rpx;
411
+ font-size: $im-font-size;
412
+ border-radius: 40rpx;
413
+ }
414
+
415
+ &--large {
416
+ height: 80rpx;
417
+ padding: 0 40rpx;
418
+ font-size: $im-font-size-large;
419
+ border-radius: 48rpx;
420
+ }
421
+
422
+ // 形状
423
+ &--square {
424
+ border-radius: 8rpx;
425
+ }
426
+
427
+ &--round {
428
+ // 圆角已经在尺寸中定义
429
+ }
430
+
431
+ &--circle {
432
+ border-radius: 50%;
433
+ padding: 0;
434
+ width: var(--button-height);
435
+ min-width: var(--button-height);
436
+ }
437
+
438
+ // 类型
439
+ &--default {
440
+ color: #606266;
441
+ background-color: #ffffff;
442
+ border-color: #dcdfe6;
443
+
444
+ &.im-button--plain {
445
+ color: #606266;
446
+ border-color: #dcdfe6;
447
+ }
448
+ }
449
+
450
+ &--primary {
451
+ color: #ffffff;
452
+ background-color: #409eff;
453
+ border-color: #409eff;
454
+
455
+ &.im-button--plain {
456
+ color: #409eff;
457
+ background-color: transparent;
458
+ border-color: #409eff;
459
+ }
460
+ }
461
+
462
+ &--success {
463
+ color: #ffffff;
464
+ background-color: #67c23a;
465
+ border-color: #67c23a;
466
+
467
+ &.im-button--plain {
468
+ color: #67c23a;
469
+ background-color: transparent;
470
+ border-color: #67c23a;
471
+ }
472
+ }
473
+
474
+ &--warning {
475
+ color: #ffffff;
476
+ background-color: #e6a23c;
477
+ border-color: #e6a23c;
478
+
479
+ &.im-button--plain {
480
+ color: #e6a23c;
481
+ background-color: transparent;
482
+ border-color: #e6a23c;
483
+ }
484
+ }
485
+
486
+ &--danger {
487
+ color: #ffffff;
488
+ background-color: #f56c6c;
489
+ border-color: #f56c6c;
490
+
491
+ &.im-button--plain {
492
+ color: #f56c6c;
493
+ background-color: transparent;
494
+ border-color: #f56c6c;
495
+ }
496
+ }
497
+
498
+ &--info {
499
+ color: #ffffff;
500
+ background-color: #909399;
501
+ border-color: #909399;
502
+
503
+ &.im-button--plain {
504
+ color: #909399;
505
+ background-color: transparent;
506
+ border-color: #909399;
507
+ }
508
+ }
509
+
510
+ // 内部元素
511
+ &__loading {
512
+ display: flex;
513
+ align-items: center;
514
+ justify-content: center;
515
+
516
+ &-spinner {
517
+ display: inline-flex;
518
+ align-items: center;
519
+ justify-content: center;
520
+ margin-right: 8rpx;
521
+ }
522
+
523
+ &-dot {
524
+ width: 6rpx;
525
+ height: 6rpx;
526
+ border-radius: 50%;
527
+ margin: 0 2rpx;
528
+ animation: button-loading 1.4s infinite ease-in-out both;
529
+
530
+ @keyframes button-loading {
531
+
532
+ 0%,
533
+ 80%,
534
+ 100% {
535
+ transform: scale(0.6);
536
+ opacity: 0.6;
537
+ }
538
+
539
+ 40% {
540
+ transform: scale(1);
541
+ opacity: 1;
542
+ }
543
+ }
544
+ }
545
+
546
+ &-text {
547
+ font-size: inherit;
548
+ color: inherit;
549
+ }
550
+ }
551
+
552
+ &__icon {
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ margin-right: 8rpx;
557
+
558
+ &--right {
559
+ margin-right: 0;
560
+ margin-left: 8rpx;
561
+ order: 1;
562
+ }
563
+
564
+ &-image {
565
+ width: v-bind(iconSize);
566
+ height: v-bind(iconSize);
567
+ }
568
+ }
569
+
570
+ &__text {
571
+ display: inline-block;
572
+ line-height: 1;
573
+ overflow: hidden;
574
+ text-overflow: ellipsis;
575
+ white-space: nowrap;
576
+
577
+ &--mini {
578
+ font-size: $im-font-size-mini;
579
+ }
580
+
581
+ &--small {
582
+ font-size: $im-font-size-small;
583
+ }
584
+
585
+ &--medium {
586
+ font-size: $im-font-size;
587
+ }
588
+
589
+ &--large {
590
+ font-size: $im-font-size-large;
591
+ }
592
+ }
593
+
594
+ &__badge {
595
+ position: absolute;
596
+ top: -10rpx;
597
+ right: -10rpx;
598
+ z-index: 1;
599
+
600
+ &-content {
601
+ display: flex;
602
+ align-items: center;
603
+ justify-content: center;
604
+ }
605
+
606
+ &--dot {
607
+ width: 16rpx;
608
+ height: 16rpx;
609
+ background-color: #fa3534;
610
+ border-radius: 50%;
611
+ border: 2rpx solid #ffffff;
612
+ }
613
+
614
+ &--text {
615
+ min-width: 32rpx;
616
+ height: 32rpx;
617
+ padding: 0 8rpx;
618
+ background-color: #fa3534;
619
+ border-radius: 16rpx;
620
+ border: 2rpx solid #ffffff;
621
+ }
622
+
623
+ &-text {
624
+ color: #ffffff;
625
+ font-size: 20rpx;
626
+ font-weight: bold;
627
+ line-height: 1;
628
+ }
629
+ }
630
+ }
627
631
  </style>