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,467 @@
1
+ <template>
2
+ <!-- 遮罩层 -->
3
+ <view v-if="showOverlay" class="im-popup__overlay" :class="[overlayClass, { 'im-popup__overlay--show': visible }]"
4
+ :style="overlayStyle" @tap="handleOverlayTap" @touchmove.stop.prevent="handleTouchMove" />
5
+
6
+ <!-- 弹窗内容 -->
7
+ <view class="im-popup" :class="[
8
+ `im-popup--${position}`,
9
+ `im-popup--${animation}`,
10
+ customClass,
11
+ { 'im-popup--hide': !visible },
12
+ { 'im-popup--show': !!visible },
13
+ { 'im-popup--safe-area': safeAreaInsetBottom }
14
+ ]" :style="popupStyle" @tap.stop>
15
+ <!-- 关闭按钮 -->
16
+ <view v-if="showClose" class="im-popup__close" :class="`im-popup__close--${closePosition}`" @tap="handleClose">
17
+ <slot name="close-icon">
18
+ <text class="im-popup__close-icon">×</text>
19
+ </slot>
20
+ </view>
21
+
22
+ <!-- 弹窗内容 -->
23
+ <view class="im-popup__content">
24
+ <slot></slot>
25
+ </view>
26
+ </view>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
31
+ import { validator } from '../../index'
32
+
33
+ // 定义 Props
34
+ interface Props {
35
+ // 显示控制
36
+ show?: boolean
37
+ modelValue?: boolean
38
+
39
+ // 位置和动画
40
+ position?: 'center' | 'top' | 'bottom' | 'left' | 'right'
41
+ animation?: 'fade' | 'slide' | 'zoom' | 'none'
42
+ duration?: number
43
+
44
+ // 遮罩层
45
+ showOverlay?: boolean
46
+ overlayColor?: string
47
+ overlayOpacity?: number
48
+ closeOnClickOverlay?: boolean
49
+ overlayClass?: string
50
+
51
+ // 样式
52
+ width?: string
53
+ height?: string
54
+ backgroundColor?: string
55
+ borderRadius?: number | string
56
+ zIndex?: number
57
+
58
+ // 行为
59
+ lockScroll?: boolean
60
+ showClose?: boolean
61
+ closePosition?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
62
+ safeAreaInsetBottom?: boolean
63
+
64
+ // 自定义类名
65
+ customClass?: string
66
+ }
67
+
68
+ // 定义 Emits
69
+ interface Emits {
70
+ (e: 'update:show', value: boolean): void
71
+ (e: 'update:modelValue', value: boolean): void
72
+ (e: 'open'): void
73
+ (e: 'opened'): void
74
+ (e: 'close'): void
75
+ (e: 'closed'): void
76
+ (e: 'click-overlay'): void
77
+ (e: 'before-enter'): void
78
+ (e: 'after-enter'): void
79
+ (e: 'before-leave'): void
80
+ (e: 'after-leave'): void
81
+ }
82
+
83
+ // 定义 Props 默认值
84
+ const props = withDefaults(defineProps<Props>(), {
85
+ show: false,
86
+ modelValue: false,
87
+
88
+ position: 'center',
89
+ animation: 'fade',
90
+ duration: 300,
91
+
92
+ showOverlay: true,
93
+ overlayColor: '#000',
94
+ overlayOpacity: 0.5,
95
+ closeOnClickOverlay: true,
96
+ overlayClass: '',
97
+
98
+ width: '600rpx',
99
+ height: 'auto',
100
+ backgroundColor: '#fff',
101
+ borderRadius: '20rpx',
102
+ zIndex: 1000,
103
+
104
+ lockScroll: true,
105
+ showClose: false,
106
+ closePosition: 'top-right',
107
+ safeAreaInsetBottom: true,
108
+
109
+ customClass: ''
110
+ })
111
+
112
+ const emit = defineEmits<Emits>()
113
+
114
+ // 内部状态
115
+ const internalShow = ref(false)
116
+
117
+ // 使用外部传入的 show 状态
118
+ const visible = computed(() => props.show || props.modelValue || internalShow.value)
119
+
120
+ // 动画状态
121
+ const animating = ref(false)
122
+
123
+ // 计算样式
124
+ const overlayStyle = computed(() => {
125
+ return {
126
+ backgroundColor: props.overlayColor,
127
+ opacity: visible.value ? props.overlayOpacity : 0,
128
+ transitionDuration: `${props.duration}ms`,
129
+ zIndex: props.zIndex
130
+ }
131
+ })
132
+
133
+ const popupStyle = computed(() => {
134
+ let borderRadius = props.borderRadius
135
+
136
+ if (validator.isNumber(borderRadius)) {
137
+ borderRadius = `${borderRadius}rpx`
138
+ }
139
+
140
+ const style: any = {
141
+ transitionDuration: `${props.duration}ms`,
142
+ zIndex: (props.zIndex || 1000) + 1,
143
+ backgroundColor: props.backgroundColor,
144
+ borderRadius: borderRadius,
145
+ borderTopLeftRadius: borderRadius,
146
+ borderTopRightRadius: borderRadius,
147
+ borderBottomLeftRadius: borderRadius,
148
+ borderBottomRightRadius: borderRadius
149
+ }
150
+
151
+ // 根据位置设置不同的尺寸
152
+ if (props.position === 'center') {
153
+ style.width = props.width
154
+ style.height = props.height
155
+ } else if (props.position === 'top' || props.position === 'bottom') {
156
+ style.width = '100%'
157
+ style.height = props.height
158
+ } else {
159
+ style.width = props.width
160
+ style.height = '100%'
161
+ }
162
+
163
+
164
+ // 圆角
165
+ switch (props.position) {
166
+ case 'top':
167
+ style.borderTopLeftRadius = '0'
168
+ style.borderTopRightRadius = '0'
169
+ break
170
+ case 'right':
171
+ style.borderTopRightRadius = '0'
172
+ style.borderBottomRightRadius = '0'
173
+ break
174
+ case 'bottom':
175
+ style.borderBottomLeftRadius = '0'
176
+ style.borderBottomRightRadius = '0'
177
+ break
178
+ case 'left':
179
+ style.borderTopLeftRadius = '0'
180
+ style.borderBottomLeftRadius = '0'
181
+ break
182
+ }
183
+
184
+ return style
185
+ })
186
+
187
+ // 处理方法
188
+ const handleOverlayTap = () => {
189
+ emit('click-overlay')
190
+ if (props.closeOnClickOverlay) {
191
+ closePopup()
192
+ }
193
+ }
194
+
195
+ const handleClose = () => {
196
+ closePopup()
197
+ }
198
+
199
+ const handleTouchMove = (e: Event) => {
200
+ e.preventDefault()
201
+ }
202
+
203
+ // 打开弹窗
204
+ const openPopup = () => {
205
+ if (animating.value) return
206
+
207
+ emit('open')
208
+ emit('before-enter')
209
+
210
+ if (props.lockScroll) {
211
+ lockScroll()
212
+ }
213
+
214
+ setTimeout(() => {
215
+ emit('after-enter')
216
+ emit('opened')
217
+ }, 50)
218
+ }
219
+
220
+ // 关闭弹窗
221
+ const closePopup = () => {
222
+ if (animating.value) return
223
+
224
+ emit('close')
225
+ emit('before-leave')
226
+ animating.value = true
227
+
228
+ internalShow.value = false
229
+ // 直接更新外部状态
230
+ emit('update:show', false)
231
+ emit('update:modelValue', false)
232
+
233
+ setTimeout(() => {
234
+ emit('after-leave')
235
+ emit('closed')
236
+ animating.value = false
237
+
238
+ if (props.lockScroll) {
239
+ unlockScroll()
240
+ }
241
+ }, props.duration)
242
+ }
243
+
244
+ // 滚动锁定
245
+ const lockScroll = () => {
246
+ if (typeof document !== 'undefined') {
247
+ const body = document.body || document.documentElement
248
+ body.style.overflow = 'hidden'
249
+ }
250
+ }
251
+
252
+ const unlockScroll = () => {
253
+ if (typeof document !== 'undefined') {
254
+ const body = document.body || document.documentElement
255
+ body.style.overflow = ''
256
+ }
257
+ }
258
+
259
+ // 监听 Props 变化
260
+ watch(visible, (val, oldVal) => {
261
+ if (val && !oldVal) {
262
+ // 从 false 变为 true,打开弹窗
263
+ openPopup()
264
+ } else if (!val && oldVal) {
265
+ // 从 true 变为 false,关闭弹窗
266
+ animating.value = true
267
+ emit('close')
268
+ emit('before-leave')
269
+
270
+ setTimeout(() => {
271
+ emit('after-leave')
272
+ emit('closed')
273
+ animating.value = false
274
+
275
+ if (props.lockScroll) {
276
+ unlockScroll()
277
+ }
278
+ }, props.duration)
279
+ }
280
+ }, { immediate: true })
281
+
282
+ // 生命周期
283
+ onMounted(() => {
284
+ // 初始状态处理
285
+ if (visible.value) {
286
+ openPopup()
287
+ }
288
+ })
289
+
290
+ onUnmounted(() => {
291
+ unlockScroll()
292
+ })
293
+
294
+ // 暴露方法
295
+ defineExpose({
296
+ open: () => {
297
+ internalShow.value = true
298
+ emit('update:show', true)
299
+ emit('update:modelValue', true)
300
+ },
301
+ close: closePopup,
302
+ toggle: () => {
303
+ if (visible.value) {
304
+ closePopup()
305
+ } else {
306
+ internalShow.value = true
307
+ emit('update:show', true)
308
+ emit('update:modelValue', true)
309
+ }
310
+ },
311
+ state: internalShow.value
312
+ })
313
+ </script>
314
+
315
+ <style lang="scss" scoped>
316
+ .im-popup {
317
+ position: fixed;
318
+ box-sizing: border-box;
319
+ transition-property: transform, opacity, width, height;
320
+ transition-timing-function: ease-out;
321
+ overflow: hidden;
322
+
323
+ // 位置样式
324
+ &--center {
325
+ top: 50%;
326
+ left: 50%;
327
+ transform: translate(-50%, -50%) scale(0.8);
328
+ opacity: 0;
329
+ }
330
+
331
+ &--top {
332
+ top: 0;
333
+ left: 0;
334
+ right: 0;
335
+ transform: translateY(-100%);
336
+ }
337
+
338
+ &--bottom {
339
+ bottom: 0;
340
+ left: 0;
341
+ right: 0;
342
+ transform: translateY(100%);
343
+ }
344
+
345
+ &--left {
346
+ top: 0;
347
+ left: 0;
348
+ bottom: 0;
349
+ transform: translateX(-100%);
350
+ }
351
+
352
+ &--right {
353
+ top: 0;
354
+ right: 0;
355
+ bottom: 0;
356
+ transform: translateX(100%);
357
+ }
358
+
359
+ // 显示状态
360
+
361
+ &--hide {
362
+ display: none;
363
+ }
364
+
365
+ &--show {
366
+ &.im-popup--center {
367
+ transform: translate(-50%, -50%) scale(1);
368
+ opacity: 1;
369
+ }
370
+
371
+ &.im-popup--top {
372
+ transform: translateY(0);
373
+ }
374
+
375
+ &.im-popup--bottom {
376
+ transform: translateY(0);
377
+ }
378
+
379
+ &.im-popup--left {
380
+ transform: translateX(0);
381
+ }
382
+
383
+ &.im-popup--right {
384
+ transform: translateX(0);
385
+ }
386
+ }
387
+
388
+ // 动画类型
389
+ &--fade {
390
+ transition-property: opacity;
391
+ }
392
+
393
+ &--slide {
394
+ transition-property: transform;
395
+ }
396
+
397
+ &--zoom {
398
+ transition-property: transform, opacity;
399
+ }
400
+
401
+ // 内容容器
402
+ &__content {
403
+ width: 100%;
404
+ height: 100%;
405
+ }
406
+
407
+ // 安全区域适配
408
+ &--safe-area {
409
+ padding-bottom: constant(safe-area-inset-bottom);
410
+ padding-bottom: env(safe-area-inset-bottom);
411
+ }
412
+
413
+ // 关闭按钮
414
+ &__close {
415
+ position: absolute;
416
+ width: 60rpx;
417
+ height: 60rpx;
418
+ display: flex;
419
+ align-items: center;
420
+ justify-content: center;
421
+ z-index: 10;
422
+
423
+ &--top-right {
424
+ top: 20rpx;
425
+ right: 20rpx;
426
+ }
427
+
428
+ &--top-left {
429
+ top: 20rpx;
430
+ left: 20rpx;
431
+ }
432
+
433
+ &--bottom-right {
434
+ bottom: 20rpx;
435
+ right: 20rpx;
436
+ }
437
+
438
+ &--bottom-left {
439
+ bottom: 20rpx;
440
+ left: 20rpx;
441
+ }
442
+
443
+ &-icon {
444
+ font-size: 40rpx;
445
+ color: #999;
446
+ line-height: 1;
447
+ font-weight: 300;
448
+ }
449
+ }
450
+ }
451
+
452
+ // 遮罩层
453
+ .im-popup__overlay {
454
+ position: fixed;
455
+ top: 0;
456
+ left: 0;
457
+ width: 100%;
458
+ height: 100%;
459
+ transition: opacity 0.3s ease;
460
+ opacity: 0;
461
+ pointer-events: none;
462
+
463
+ &--show {
464
+ pointer-events: auto;
465
+ }
466
+ }
467
+ </style>
@@ -1,15 +1,15 @@
1
1
  <template>
2
- <u-popup ref="popup" mode="bottom">
2
+ <im-popup ref="popup" position="bottom">
3
3
  <view class="read-receipt">
4
4
  <view class="uni-padding-wrap uni-common-mt">
5
- <u-tabs :current="current" :list="items" :show-bar="false" @change="onClickItem"></u-tabs>
5
+ <im-tabs :current="current" :items="items" :show-bar="false" @change="onClickItem" />
6
6
  </view>
7
7
  <view class="content">
8
8
  <view v-if="current === 0">
9
9
  <im-virtual-list :data="readedMembers">
10
10
  <template v-slot="{ item }">
11
11
  <view class="member-item">
12
- <im-avatar :name="item.showNickName" :online="item.online" :url="item.headImage"
12
+ <im-avatar :title="item.showNickName" :online="item.online" :url="item.avatar"
13
13
  :size="90" />
14
14
  <view class="member-name">{{ item.showNickName }}</view>
15
15
  </view>
@@ -20,7 +20,7 @@
20
20
  <im-virtual-list :data="unreadMembers">
21
21
  <template v-slot="{ item }">
22
22
  <view class="member-item">
23
- <im-avatar :name="item.showNickName" :online="item.online" :url="item.headImage"
23
+ <im-avatar :title="item.showNickName" :online="item.online" :url="item.avatar"
24
24
  :size="90" />
25
25
  <view class="member-name">{{ item.showNickName }}</view>
26
26
  </view>
@@ -29,7 +29,7 @@
29
29
  </view>
30
30
  </view>
31
31
  </view>
32
- </u-popup>
32
+ </im-popup>
33
33
  </template>
34
34
 
35
35
  <script setup lang="ts">
@@ -44,7 +44,7 @@ interface Props {
44
44
  id: number;
45
45
  sendId: number;
46
46
  };
47
- groupMembers: any[];
47
+ groupMembers?: any[];
48
48
  }
49
49
 
50
50
  interface Member {
@@ -54,7 +54,7 @@ interface Member {
54
54
 
55
55
  const props = defineProps<Props>();
56
56
 
57
- const items = ref([{ name: '已读' }, { name: '未读' }]);
57
+ const items = ref([{ title: '已读' }, { title: '未读' }]);
58
58
  const current = ref(0);
59
59
  const readedMembers = ref<Member[]>([]);
60
60
  const unreadMembers = ref<Member[]>([]);
@@ -102,8 +102,8 @@ const loadReadedUser = async (userIds: number[]) => {
102
102
  const chatInfo: Chat = {
103
103
  type: 'GROUP',
104
104
  targetId: props.msgInfo.groupId,
105
- showName: '',
106
- headImage: '',
105
+ displayName: '',
106
+ avatar: '',
107
107
  isDnd: false,
108
108
  lastContent: '',
109
109
  unreadCount: 0,
@@ -119,7 +119,7 @@ const loadReadedUser = async (userIds: number[]) => {
119
119
  const msgInfo: Message = {
120
120
  id: props.msgInfo.id,
121
121
  groupId: props.msgInfo.groupId,
122
- readedCount: readedMembers.value.length,
122
+ readCount: readedMembers.value.length,
123
123
  type: 0,
124
124
  content: ''
125
125
  };
@@ -0,0 +1,189 @@
1
+ <template>
2
+ <view
3
+ class="im-row"
4
+ :class="[
5
+ `im-row--align-${align}`,
6
+ `im-row--justify-${justify}`,
7
+ { 'im-row--wrap': wrap },
8
+ { 'im-row--gutter': hasGutter }
9
+ ]"
10
+ :style="rowStyle"
11
+ >
12
+ <slot></slot>
13
+ </view>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { computed, useSlots, provide, reactive, watchEffect } from 'vue'
18
+
19
+ // 定义 Props
20
+ interface Props {
21
+ // 布局配置
22
+ gutter?: number | string
23
+ align?: 'top' | 'middle' | 'bottom' | 'stretch'
24
+ justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between'
25
+ wrap?: boolean
26
+
27
+ // 样式配置
28
+ width?: string
29
+ height?: string
30
+ margin?: string
31
+ padding?: string
32
+ bgColor?: string
33
+ borderColor?: string
34
+ borderRadius?: string
35
+ shadow?: boolean
36
+
37
+ // 间距配置
38
+ rowGap?: number | string
39
+ columnGap?: number | string
40
+ }
41
+
42
+ // 定义 Emits
43
+ interface Emits {
44
+ (e: 'click', event: any): void
45
+ }
46
+
47
+ // 定义 Props 默认值
48
+ const props = withDefaults(defineProps<Props>(), {
49
+ gutter: 0,
50
+ align: 'top',
51
+ justify: 'start',
52
+ wrap: false,
53
+
54
+ width: 'auto',
55
+ height: 'auto',
56
+ margin: '0',
57
+ padding: '0',
58
+ bgColor: 'transparent',
59
+ borderColor: 'transparent',
60
+ borderRadius: '0',
61
+ shadow: false,
62
+
63
+ rowGap: 0,
64
+ columnGap: 0
65
+ })
66
+
67
+ const emit = defineEmits<Emits>()
68
+
69
+ // 获取插槽
70
+ const slots = useSlots()
71
+
72
+ // 计算样式
73
+ const rowStyle = computed(() => {
74
+ const style: any = {
75
+ width: props.width,
76
+ height: props.height,
77
+ margin: props.margin,
78
+ padding: props.padding,
79
+ backgroundColor: props.bgColor,
80
+ borderColor: props.borderColor,
81
+ borderRadius: props.borderRadius
82
+ }
83
+
84
+ // 设置阴影
85
+ if (props.shadow) {
86
+ style.boxShadow = '0 4rpx 12rpx rgba(0, 0, 0, 0.08)'
87
+ }
88
+
89
+ // 设置间距
90
+ if (props.rowGap || props.columnGap) {
91
+ const rowGap = typeof props.rowGap === 'number' ? `${props.rowGap}rpx` : props.rowGap
92
+ const columnGap = typeof props.columnGap === 'number' ? `${props.columnGap}rpx` : props.columnGap
93
+
94
+ if (props.wrap) {
95
+ style.gap = `${rowGap} ${columnGap}`
96
+ } else {
97
+ style.rowGap = rowGap
98
+ style.columnGap = columnGap
99
+ }
100
+ }
101
+
102
+ // 设置边框
103
+ if (props.borderColor !== 'transparent') {
104
+ style.borderWidth = '2rpx'
105
+ style.borderStyle = 'solid'
106
+ }
107
+
108
+ return style
109
+ })
110
+
111
+ // 计算是否有 gutter
112
+ const hasGutter = computed(() => {
113
+ return Number(props.gutter) > 0
114
+ })
115
+
116
+ // 提供 context 给子组件
117
+ const rowContext = reactive({
118
+ gutter: props.gutter
119
+ })
120
+
121
+ provide('imRowContext', rowContext)
122
+
123
+ // 监听 gutter 变化
124
+ watchEffect(() => {
125
+ rowContext.gutter = props.gutter
126
+ })
127
+
128
+ // 点击事件
129
+ const handleClick = (event: any) => {
130
+ emit('click', event)
131
+ }
132
+ </script>
133
+
134
+ <style lang="scss" scoped>
135
+ .im-row {
136
+ display: flex;
137
+ flex-direction: row;
138
+ box-sizing: border-box;
139
+
140
+ // 垂直对齐方式
141
+ &--align-top {
142
+ align-items: flex-start;
143
+ }
144
+
145
+ &--align-middle {
146
+ align-items: center;
147
+ }
148
+
149
+ &--align-bottom {
150
+ align-items: flex-end;
151
+ }
152
+
153
+ &--align-stretch {
154
+ align-items: stretch;
155
+ }
156
+
157
+ // 水平对齐方式
158
+ &--justify-start {
159
+ justify-content: flex-start;
160
+ }
161
+
162
+ &--justify-end {
163
+ justify-content: flex-end;
164
+ }
165
+
166
+ &--justify-center {
167
+ justify-content: center;
168
+ }
169
+
170
+ &--justify-space-around {
171
+ justify-content: space-around;
172
+ }
173
+
174
+ &--justify-space-between {
175
+ justify-content: space-between;
176
+ }
177
+
178
+ // 换行
179
+ &--wrap {
180
+ flex-wrap: wrap;
181
+ }
182
+
183
+ // 间距
184
+ &--gutter {
185
+ margin-left: calc(var(--im-row-gutter, 0rpx) / -2);
186
+ margin-right: calc(var(--im-row-gutter, 0rpx) / -2);
187
+ }
188
+ }
189
+ </style>