im-ui-mobile 0.0.82 → 0.0.84

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/components/{im-head-image/im-head-image.vue → im-avatar/im-avatar.vue} +2 -2
  3. package/components/im-button/im-button.vue +603 -38
  4. package/components/im-cell/im-cell.vue +462 -0
  5. package/components/{im-bar-group/im-bar-group.vue → im-cell-group/im-cell-group.vue} +4 -4
  6. package/components/im-cell-switch/im-cell-switch.vue +223 -0
  7. package/components/im-chat-item/im-chat-item.vue +4 -5
  8. package/components/{im-long-press-menu/im-long-press-menu.vue → im-context-menu/im-context-menu.vue} +2 -2
  9. package/components/im-file-upload/im-file-upload.vue +9 -2
  10. package/components/im-friend-item/im-friend-item.vue +2 -2
  11. package/components/im-group-item/im-group-item.vue +2 -2
  12. package/components/im-group-member-selector/im-group-member-selector.vue +3 -3
  13. package/components/im-group-rtc-join/im-group-rtc-join.vue +3 -3
  14. package/components/im-image-upload/im-image-upload.vue +3 -2
  15. package/components/{im-chat-at-box/im-chat-at-box.vue → im-mention-picker/im-mention-picker.vue} +5 -5
  16. package/components/{im-chat-message-item/im-chat-message-item.vue → im-message-item/im-message-item.vue} +19 -21
  17. package/components/{im-chat-group-readed/im-chat-group-readed.vue → im-read-receipt/im-read-receipt.vue} +5 -5
  18. package/components/im-virtual-list/im-virtual-list.vue +444 -0
  19. package/components/{im-chat-record/im-chat-record.vue → im-voice-input/im-voice-input.vue} +11 -11
  20. package/index.js +7 -13
  21. package/package.json +4 -4
  22. package/types/components/{head-image.d.ts → avatar.d.ts} +4 -4
  23. package/types/components/button.d.ts +73 -3
  24. package/types/components/{bar-group.d.ts → cell-group.d.ts} +4 -4
  25. package/types/components/cell-switch.d.ts +31 -0
  26. package/types/components/cell.d.ts +46 -0
  27. package/types/components/context-menu.d.ts +23 -0
  28. package/types/components/{chat-at-box.d.ts → mention-picker.d.ts} +6 -6
  29. package/types/components/{chat-message-item.d.ts → message-item.d.ts} +6 -6
  30. package/types/components/{chat-group-readed.d.ts → read-receipt.d.ts} +6 -6
  31. package/types/components/virtual-list.d.ts +148 -0
  32. package/types/components/{chat-record.d.ts → voice-input.d.ts} +4 -4
  33. package/types/components.d.ts +9 -10
  34. package/types/index.d.ts +4 -2
  35. package/utils/config.js +17 -0
  36. package/utils/datetime.js +1 -1
  37. package/utils/emoji.js +9 -4
  38. package/components/im-arrow-bar/im-arrow-bar.vue +0 -59
  39. package/components/im-sample/im-sample.vue +0 -192
  40. package/components/im-switch-bar/im-switch-bar.vue +0 -62
  41. package/types/components/long-press-menu.d.ts +0 -23
  42. package/types/components/switch-bar.d.ts +0 -19
@@ -0,0 +1,462 @@
1
+ <template>
2
+ <view class="im-cell" :class="[
3
+ `im-cell--${type}`,
4
+ `im-cell--${size}`,
5
+ {
6
+ 'im-cell--disabled': disabled,
7
+ 'im-cell--border': border,
8
+ 'im-cell--hover': hover && !disabled,
9
+ 'im-cell--clickable': clickable && !disabled
10
+ }
11
+ ]" :style="cellStyle" @click="handleClick" @longpress="handleLongPress">
12
+ <!-- 左侧内容 -->
13
+ <view v-if="hasLeftContent" class="im-cell__left">
14
+ <!-- 图标 -->
15
+ <view v-if="icon || $slots.icon" class="im-cell__icon">
16
+ <slot name="icon">
17
+ <image v-if="icon" :src="icon" class="im-cell__icon-image"
18
+ :class="[`im-cell__icon--${iconPosition}`]" mode="aspectFit" />
19
+ </slot>
20
+ </view>
21
+
22
+ <!-- 头像 -->
23
+ <view v-if="avatar || $slots.avatar" class="im-cell__avatar">
24
+ <slot name="avatar">
25
+ <view class="im-cell__avatar-container">
26
+ <image v-if="avatar" :src="avatar" class="im-cell__avatar-image" mode="aspectFill" />
27
+ <text v-else class="im-cell__avatar-fallback">
28
+ {{ avatarFallback }}
29
+ </text>
30
+ </view>
31
+ </slot>
32
+ </view>
33
+
34
+ <!-- 标题和描述 -->
35
+ <view class="im-cell__content">
36
+ <view class="im-cell__title">
37
+ <slot name="title">
38
+ <text v-if="title" class="im-cell__title-text" :class="[`im-cell__title--${titleSize}`]">
39
+ {{ title }}
40
+ </text>
41
+ </slot>
42
+ </view>
43
+
44
+ <view v-if="description || $slots.description" class="im-cell__description">
45
+ <slot name="description">
46
+ <text v-if="description" class="im-cell__description-text"
47
+ :class="[`im-cell__description--${descriptionSize}`]">
48
+ {{ description }}
49
+ </text>
50
+ </slot>
51
+ </view>
52
+ </view>
53
+ </view>
54
+
55
+ <!-- 右侧内容 -->
56
+ <view v-if="hasRightContent" class="im-cell__right">
57
+ <!-- 值 -->
58
+ <view v-if="value || $slots.value" class="im-cell__value">
59
+ <slot name="value">
60
+ <text v-if="value" class="im-cell__value-text">
61
+ {{ value }}
62
+ </text>
63
+ </slot>
64
+ </view>
65
+
66
+ <!-- 标签 -->
67
+ <view v-if="label || $slots.label" class="im-cell__label">
68
+ <slot name="label">
69
+ <view v-if="label" class="im-cell__label-badge">
70
+ <text class="im-cell__label-text">
71
+ {{ label }}
72
+ </text>
73
+ </view>
74
+ </slot>
75
+ </view>
76
+
77
+ <!-- 角标 -->
78
+ <view v-if="badge || $slots.badge" class="im-cell__badge">
79
+ <slot name="badge">
80
+ <view v-if="badge" class="im-cell__badge-dot">
81
+ <text v-if="badge !== true" class="im-cell__badge-text">
82
+ {{ formatBadge(badge) }}
83
+ </text>
84
+ </view>
85
+ </slot>
86
+ </view>
87
+
88
+ <!-- 箭头 -->
89
+ <view v-if="arrow" class="im-cell__arrow">
90
+ <slot name="arrow">
91
+ <text class="im-cell__arrow-icon">›</text>
92
+ </slot>
93
+ </view>
94
+ </view>
95
+ </view>
96
+ </template>
97
+
98
+ <script setup lang="ts">
99
+ import { computed, withDefaults } from 'vue'
100
+
101
+ // 定义类型
102
+ type CellType = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
103
+ type CellSize = 'small' | 'medium' | 'large'
104
+ type TitleSize = 'small' | 'medium' | 'large'
105
+ type IconPosition = 'left' | 'right'
106
+
107
+ interface Props {
108
+ // 基础属性
109
+ type?: CellType
110
+ size?: CellSize
111
+ disabled?: boolean
112
+ border?: boolean
113
+ hover?: boolean
114
+ clickable?: boolean
115
+
116
+ // 左侧内容
117
+ icon?: string
118
+ iconPosition?: IconPosition
119
+ avatar?: string
120
+ title?: string
121
+ titleSize?: TitleSize
122
+ description?: string
123
+ descriptionSize?: TitleSize
124
+
125
+ // 右侧内容
126
+ value?: string
127
+ label?: string
128
+ badge?: boolean | number | string
129
+ arrow?: boolean
130
+
131
+ // 样式
132
+ padding?: string
133
+ margin?: string
134
+ bgColor?: string
135
+ textColor?: string
136
+ }
137
+
138
+ // 定义 Emits
139
+ interface Emits {
140
+ (e: 'click', event: PointerEvent): void
141
+ (e: 'longpress', event: TouchEvent): void
142
+ }
143
+
144
+ // Props 和 Emits
145
+ const props = withDefaults(defineProps<Props>(), {
146
+ type: 'default',
147
+ size: 'medium',
148
+ disabled: false,
149
+ border: true,
150
+ hover: true,
151
+ clickable: true,
152
+ iconPosition: 'left',
153
+ titleSize: 'medium',
154
+ descriptionSize: 'small',
155
+ arrow: false,
156
+ padding: '',
157
+ margin: '',
158
+ bgColor: '',
159
+ textColor: ''
160
+ })
161
+
162
+ const emit = defineEmits<Emits>()
163
+
164
+ // 计算属性
165
+ const hasLeftContent = computed(() => {
166
+ return props.icon || props.avatar || props.title || props.description ||
167
+ slots.icon || slots.avatar || slots.title || slots.description
168
+ })
169
+
170
+ const hasRightContent = computed(() => {
171
+ return props.value || props.label || props.badge || props.arrow ||
172
+ slots.value || slots.label || slots.badge || slots.arrow
173
+ })
174
+
175
+ const avatarFallback = computed(() => {
176
+ return props.title ? props.title.charAt(0).toUpperCase() : ''
177
+ })
178
+
179
+ const cellStyle = computed(() => {
180
+ const style: Record<string, string> = {}
181
+
182
+ if (props.padding) style.padding = props.padding
183
+ if (props.margin) style.margin = props.margin
184
+ if (props.bgColor) style.backgroundColor = props.bgColor
185
+ if (props.textColor) style.color = props.textColor
186
+
187
+ return style
188
+ })
189
+
190
+ // 获取插槽
191
+ const slots = useSlots()
192
+
193
+ // 方法
194
+ const formatBadge = (badge: boolean | number | string): string => {
195
+ if (typeof badge === 'boolean') return ''
196
+ if (typeof badge === 'number') {
197
+ return badge > 99 ? '99+' : badge.toString()
198
+ }
199
+ return badge
200
+ }
201
+
202
+ const handleClick = (event: PointerEvent) => {
203
+ if (props.disabled) return
204
+ emit('click', event)
205
+
206
+ // 添加点击反馈
207
+ if (props.clickable) {
208
+ // 可以在这里添加触感反馈
209
+ if (uni?.vibrateShort) {
210
+ uni.vibrateShort()
211
+ }
212
+ }
213
+ }
214
+
215
+ const handleLongPress = (event: TouchEvent) => {
216
+ if (props.disabled) return
217
+ emit('longpress', event)
218
+ }
219
+ </script>
220
+
221
+ <style lang="scss" scoped>
222
+ .im-cell {
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: space-between;
226
+ background-color: #ffffff;
227
+ position: relative;
228
+ transition: all 0.3s ease;
229
+
230
+ // 尺寸
231
+ &--small {
232
+ padding: 16rpx 24rpx;
233
+ min-height: 80rpx;
234
+ }
235
+
236
+ &--medium {
237
+ padding: 24rpx 32rpx;
238
+ min-height: 96rpx;
239
+ }
240
+
241
+ &--large {
242
+ padding: 32rpx 40rpx;
243
+ min-height: 120rpx;
244
+ }
245
+
246
+ // 边框
247
+ &--border {
248
+ &::after {
249
+ content: '';
250
+ position: absolute;
251
+ bottom: 0;
252
+ left: 32rpx;
253
+ right: 0;
254
+ height: 2rpx;
255
+ background-color: #f0f0f0;
256
+ transform: scaleY(0.5);
257
+ }
258
+
259
+ &:last-child::after {
260
+ display: none;
261
+ }
262
+ }
263
+
264
+ // 悬停效果
265
+ &--hover:active {
266
+ background-color: #f5f7fa;
267
+ }
268
+
269
+ // 禁用状态
270
+ &--disabled {
271
+ opacity: 0.5;
272
+ cursor: not-allowed;
273
+ }
274
+
275
+ // 类型样式
276
+ &--primary &__title-text {
277
+ color: #409eff;
278
+ }
279
+
280
+ &--success &__title-text {
281
+ color: #67c23a;
282
+ }
283
+
284
+ &--warning &__title-text {
285
+ color: #e6a23c;
286
+ }
287
+
288
+ &--danger &__title-text {
289
+ color: #f56c6c;
290
+ }
291
+
292
+ &--info &__title-text {
293
+ color: #909399;
294
+ }
295
+
296
+ // 左侧内容
297
+ &__left {
298
+ display: flex;
299
+ align-items: center;
300
+ flex: 1;
301
+ min-width: 0;
302
+ }
303
+
304
+ &__icon {
305
+ margin-right: 16rpx;
306
+ flex-shrink: 0;
307
+
308
+ &-image {
309
+ width: 40rpx;
310
+ height: 40rpx;
311
+
312
+ &--right {
313
+ margin-right: 0;
314
+ margin-left: 16rpx;
315
+ }
316
+ }
317
+ }
318
+
319
+ &__avatar {
320
+ margin-right: 16rpx;
321
+ flex-shrink: 0;
322
+
323
+ &-container {
324
+ width: 64rpx;
325
+ height: 64rpx;
326
+ border-radius: 50%;
327
+ background: linear-gradient(135deg, #409eff, #67c23a);
328
+ display: flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ overflow: hidden;
332
+ }
333
+
334
+ &-image {
335
+ width: 100%;
336
+ height: 100%;
337
+ }
338
+
339
+ &-fallback {
340
+ color: white;
341
+ font-size: 28rpx;
342
+ font-weight: bold;
343
+ }
344
+ }
345
+
346
+ &__content {
347
+ flex: 1;
348
+ min-width: 0;
349
+ }
350
+
351
+ &__title {
352
+ margin-bottom: 4rpx;
353
+
354
+ &-text {
355
+ font-size: 32rpx;
356
+ color: #303133;
357
+ overflow: hidden;
358
+ text-overflow: ellipsis;
359
+ white-space: nowrap;
360
+
361
+ &--small {
362
+ font-size: 28rpx;
363
+ }
364
+
365
+ &--medium {
366
+ font-size: 32rpx;
367
+ }
368
+
369
+ &--large {
370
+ font-size: 36rpx;
371
+ }
372
+ }
373
+ }
374
+
375
+ &__description {
376
+ &-text {
377
+ font-size: 24rpx;
378
+ color: #909399;
379
+ overflow: hidden;
380
+ text-overflow: ellipsis;
381
+ white-space: nowrap;
382
+
383
+ &--small {
384
+ font-size: 20rpx;
385
+ }
386
+
387
+ &--medium {
388
+ font-size: 24rpx;
389
+ }
390
+
391
+ &--large {
392
+ font-size: 28rpx;
393
+ }
394
+ }
395
+ }
396
+
397
+ // 右侧内容
398
+ &__right {
399
+ display: flex;
400
+ align-items: center;
401
+ flex-shrink: 0;
402
+ margin-left: 16rpx;
403
+ }
404
+
405
+ &__value {
406
+ margin-right: 8rpx;
407
+
408
+ &-text {
409
+ font-size: 28rpx;
410
+ color: #606266;
411
+ white-space: nowrap;
412
+ }
413
+ }
414
+
415
+ &__label {
416
+ margin-right: 8rpx;
417
+
418
+ &-badge {
419
+ background-color: #f0f9eb;
420
+ border: 1rpx solid #e1f3d8;
421
+ border-radius: 20rpx;
422
+ padding: 4rpx 12rpx;
423
+ }
424
+
425
+ &-text {
426
+ font-size: 20rpx;
427
+ color: #67c23a;
428
+ }
429
+ }
430
+
431
+ &__badge {
432
+ margin-right: 8rpx;
433
+
434
+ &-dot {
435
+ background-color: #fa3534;
436
+ border-radius: 50%;
437
+ // min-width: 32rpx;
438
+ // height: 32rpx;
439
+ display: flex;
440
+ align-items: center;
441
+ justify-content: center;
442
+ padding: 2rpx 4rpx;
443
+ }
444
+
445
+ &-text {
446
+ color: white;
447
+ font-size: 16rpx;
448
+ font-weight: bold;
449
+ }
450
+ }
451
+
452
+ &__arrow {
453
+ margin-left: 8rpx;
454
+
455
+ &-icon {
456
+ font-size: 64rpx;
457
+ color: #c0c4cc;
458
+ transform: scaleY(1.5);
459
+ }
460
+ }
461
+ }
462
+ </style>
@@ -1,17 +1,17 @@
1
1
  <template>
2
- <view class="bar-group">
2
+ <view class="cell-group">
3
3
  <slot></slot>
4
4
  </view>
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
8
  defineOptions({
9
- name: "bar-group"
9
+ name: "cell-group"
10
10
  });
11
11
  </script>
12
12
 
13
13
  <style lang="scss" scoped>
14
- .bar-group {
15
- margin: 20rpx 0;
14
+ .cell-group {
15
+ margin-bottom: 20rpx;
16
16
  }
17
17
  </style>
@@ -0,0 +1,223 @@
1
+ <template>
2
+ <ImCell :type="type" :size="size" :disabled="disabled || switchDisabled" :border="border" :hover="hover"
3
+ :clickable="clickable" :icon="icon" :iconPosition="iconPosition" :avatar="avatar" :title="title"
4
+ :titleSize="titleSize" :description="description" :descriptionSize="descriptionSize" :value="value"
5
+ :label="label" :badge="badge" :padding="padding" :margin="margin" :bgColor="bgColor" :textColor="textColor"
6
+ @click="handleCellClick" @longpress="handleLongPress">
7
+ <!-- 传递所有插槽 -->
8
+ <template v-if="$slots.icon" #icon>
9
+ <slot name="icon" />
10
+ </template>
11
+
12
+ <template v-if="$slots.avatar" #avatar>
13
+ <slot name="avatar" />
14
+ </template>
15
+
16
+ <template v-if="$slots.title" #title>
17
+ <slot name="title" />
18
+ </template>
19
+
20
+ <template v-if="$slots.description" #description>
21
+ <slot name="description" />
22
+ </template>
23
+
24
+ <template v-if="$slots.arrow" #arrow>
25
+ <slot name="arrow" />
26
+ </template>
27
+
28
+ <template v-if="$slots.label" #label>
29
+ <slot name="label" />
30
+ </template>
31
+
32
+ <template v-if="$slots.badge" #badge>
33
+ <slot name="badge" />
34
+ </template>
35
+
36
+ <!-- 自定义右侧内容,添加 Switch -->
37
+ <template #value>
38
+ <view class="im-cell-switch__wrapper">
39
+ <switch v-if="showSwitch" :checked="checked" :disabled="disabled || switchDisabled" :color="switchColor"
40
+ :backgroundColor="switchBackgroundColor" @change="handleSwitchChange" />
41
+ <text v-else class="im-cell-switch__loading">加载中...</text>
42
+ </view>
43
+ </template>
44
+ </ImCell>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { ref, computed, watch } from 'vue'
49
+ import ImCell from '../im-cell/im-cell.vue'
50
+
51
+ // 定义 Props 接口
52
+ interface Props {
53
+ // 继承自 ImCell 的 Props
54
+ type?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'
55
+ size?: 'small' | 'medium' | 'large'
56
+ disabled?: boolean
57
+ border?: boolean
58
+ hover?: boolean
59
+ clickable?: boolean
60
+ icon?: string
61
+ iconPosition?: 'left' | 'right'
62
+ avatar?: string
63
+ title?: string
64
+ titleSize?: 'small' | 'medium' | 'large'
65
+ description?: string
66
+ descriptionSize?: 'small' | 'medium' | 'large'
67
+ value?: string
68
+ label?: string
69
+ badge?: boolean | number | string
70
+ padding?: string
71
+ margin?: string
72
+ bgColor?: string
73
+ textColor?: string
74
+
75
+ // Switch 特有 Props
76
+ checked?: boolean
77
+ switchDisabled?: boolean
78
+ switchColor?: string
79
+ switchBackgroundColor?: string
80
+ loading?: boolean
81
+ async?: boolean
82
+ beforeChange?: (newValue: boolean) => Promise<boolean> | boolean
83
+ }
84
+
85
+ // 定义 Emits 接口
86
+ interface Emits {
87
+ (e: 'update:checked', value: boolean): void
88
+ (e: 'change', value: boolean): void
89
+ (e: 'click', event: PointerEvent): void
90
+ (e: 'longpress', event: TouchEvent): void
91
+ }
92
+
93
+ // Props 和 Emits
94
+ const props = withDefaults(defineProps<Props>(), {
95
+ type: 'default',
96
+ size: 'medium',
97
+ disabled: false,
98
+ border: true,
99
+ hover: true,
100
+ clickable: true,
101
+ iconPosition: 'left',
102
+ titleSize: 'medium',
103
+ descriptionSize: 'small',
104
+ switchDisabled: false,
105
+ switchColor: '#409EFF',
106
+ switchBackgroundColor: '#DCDFE6',
107
+ loading: false,
108
+ async: false
109
+ })
110
+
111
+ const emit = defineEmits<Emits>()
112
+
113
+ // 响应式数据
114
+ const isLoading = ref(false)
115
+ const internalValue = ref(props.checked)
116
+
117
+ // 计算属性
118
+ const showSwitch = computed(() => {
119
+ return !props.loading && !isLoading.value
120
+ })
121
+
122
+ // 监听外部值变化
123
+ watch(() => props.checked, (newValue) => {
124
+ internalValue.value = newValue
125
+ })
126
+
127
+ // 事件处理
128
+ const handleSwitchChange = async (event: any) => {
129
+ const newValue = event.detail.value
130
+
131
+ // 如果设置了异步切换
132
+ if (props.async || props.beforeChange) {
133
+ await handleAsyncChange(newValue)
134
+ } else {
135
+ updateValue(newValue)
136
+ }
137
+ }
138
+
139
+ const handleAsyncChange = async (newValue: boolean) => {
140
+ isLoading.value = true
141
+
142
+ try {
143
+ let canChange = true
144
+
145
+ // 执行 beforeChange 回调
146
+ if (props.beforeChange) {
147
+ const result = props.beforeChange(newValue)
148
+ if (result instanceof Promise) {
149
+ canChange = await result
150
+ } else {
151
+ canChange = result
152
+ }
153
+ }
154
+
155
+ if (canChange) {
156
+ updateValue(newValue)
157
+ } else {
158
+ // 恢复原值
159
+ internalValue.value = !newValue
160
+ }
161
+ } catch (error) {
162
+ console.error('切换失败:', error)
163
+ // 恢复原值
164
+ internalValue.value = !newValue
165
+ } finally {
166
+ isLoading.value = false
167
+ }
168
+ }
169
+
170
+ const updateValue = (value: boolean) => {
171
+ internalValue.value = value
172
+ emit('update:checked', value)
173
+ emit('change', value)
174
+ }
175
+
176
+ const handleCellClick = (event: PointerEvent) => {
177
+ if (props.disabled || props.switchDisabled) return
178
+ emit('click', event)
179
+ }
180
+
181
+ const handleLongPress = (event: TouchEvent) => {
182
+ if (props.disabled || props.switchDisabled) return
183
+ emit('longpress', event)
184
+ }
185
+ </script>
186
+
187
+ <style lang="scss" scoped>
188
+ .im-cell-switch {
189
+ &__wrapper {
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ min-width: 80rpx;
194
+ margin-left: 8rpx;
195
+ }
196
+
197
+ &__loading {
198
+ font-size: 24rpx;
199
+ color: #909399;
200
+ }
201
+ }
202
+
203
+ // 覆盖 Switch 样式
204
+ :deep(switch) {
205
+ transform: scale(0.8);
206
+ transform-origin: center;
207
+
208
+ &.wx-switch-input {
209
+ width: 80rpx;
210
+ height: 40rpx;
211
+
212
+ &::before {
213
+ width: 76rpx;
214
+ height: 36rpx;
215
+ }
216
+
217
+ &::after {
218
+ width: 34rpx;
219
+ height: 34rpx;
220
+ }
221
+ }
222
+ }
223
+ </style>