im-ui-mobile 0.1.38 → 0.1.40

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.
@@ -0,0 +1,377 @@
1
+ <template>
2
+ <view class="im-avatar" :class="[
3
+ `im-avatar--${size}`,
4
+ {
5
+ 'im-avatar--square': shape === 'square',
6
+ 'im-avatar--rounded': shape === 'rounded',
7
+ 'im-avatar--circle': shape === 'circle',
8
+ 'im-avatar--with-border': border,
9
+ 'im-avatar--clickable': clickable
10
+ }
11
+ ]" :style="[customStyle, avatarStyle]" @click="handleClick" @longpress="handleLongPress">
12
+ <!-- 图片头像 -->
13
+ <image v-if="url" :class="['im-avatar__image',
14
+ {
15
+ 'im-avatar__image--square': shape === 'square',
16
+ 'im-avatar__image--rounded': shape === 'rounded',
17
+ 'im-avatar__image--circle': shape === 'circle',
18
+ 'im-avatar__image--with-border': border,
19
+ }
20
+ ]" :src="url" :mode="imageMode" :show-menu-by-longpress="showMenuByLongpress" @error="handleImageError" />
21
+
22
+ <!-- 文字头像(无图片时显示) -->
23
+ <text v-else-if="title" class="im-avatar__text" :style="textStyle">
24
+ {{ displayText }}
25
+ </text>
26
+
27
+ <!-- 默认图标(无图片和文字时显示) -->
28
+ <im-icon v-else-if="icon" :name="icon" class="im-avatar__icon" :color="iconColor" :size="iconSize" />
29
+
30
+ <!-- 在线状态指示器 -->
31
+ <view v-if="showStatus && status" class="im-avatar__status" :class="`im-avatar__status--${status}`" />
32
+
33
+ <!-- 加载状态 -->
34
+ <im-loading v-if="loading" :class="`im-avatar__loading--${shape}`" :size="32" text="" :mask="true" />
35
+ </view>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { computed } from 'vue'
40
+ import ImIcon from '../im-icon/im-icon.vue'
41
+ import type { AvatarProps, AvatarEmits } from '../../types/components/avatar'
42
+
43
+ // 默认Props
44
+ const props = withDefaults(defineProps<AvatarProps>(), {
45
+ size: 'medium',
46
+ shape: 'circle',
47
+ border: false,
48
+ borderColor: '#dcdfe6',
49
+ bgColor: '#409eff',
50
+ textColor: '#ffffff',
51
+ fontSize: undefined,
52
+ fontWeight: 500,
53
+ icon: '👤',
54
+ iconColor: '#909399',
55
+ iconSize: undefined,
56
+ status: 'offline',
57
+ showStatus: false,
58
+ clickable: false,
59
+ loading: false,
60
+ showMenuByLongpress: false,
61
+ imageMode: 'aspectFill',
62
+ customStyle: () => ({})
63
+ })
64
+
65
+ // 定义事件
66
+ const emit = defineEmits<AvatarEmits>()
67
+
68
+ // 计算属性
69
+ const displayText = computed(() => {
70
+ if (!props.title) return ''
71
+ // 取最后一个字符或前两个字符
72
+ if (props.title.length <= 2) {
73
+ return props.title
74
+ }
75
+ // 中文取最后两个字符,英文取首字母
76
+ const isChinese = /[\u4e00-\u9fa5]/.test(props.title)
77
+ if (isChinese) {
78
+ return props.title.slice(-2)
79
+ } else {
80
+ const words = props.title.split(' ').filter(word => word.length > 0)
81
+ if (words.length >= 2) {
82
+ return words[0][0] + words[1][0]
83
+ } else {
84
+ return props.title.slice(0, 2)
85
+ }
86
+ }
87
+ })
88
+
89
+ const avatarStyle = computed(() => {
90
+ const style: Record<string, string> = {}
91
+
92
+ if (props.bgColor && !props.url) {
93
+ style.backgroundColor = props.bgColor
94
+ }
95
+
96
+ if (props.border) {
97
+ style.border = `2rpx solid ${props.borderColor}`
98
+ }
99
+
100
+ return style
101
+ })
102
+
103
+ const textStyle = computed(() => {
104
+ const style: Record<string, string> = {
105
+ color: props.textColor,
106
+ fontWeight: String(props.fontWeight)
107
+ }
108
+
109
+ // 根据尺寸设置字体大小
110
+ if (props.fontSize) {
111
+ style.fontSize = `${props.fontSize}rpx`
112
+ }
113
+
114
+ return style
115
+ })
116
+
117
+ // 事件处理
118
+ const handleClick = () => {
119
+ if (props.clickable && !props.loading) {
120
+ emit('click', props.id)
121
+ }
122
+ }
123
+
124
+ const handleLongPress = () => {
125
+ if (props.clickable && !props.loading) {
126
+ emit('longpress', props.id)
127
+ }
128
+ }
129
+
130
+ const handleImageError = (error: any) => {
131
+ emit('error', error)
132
+ }
133
+ </script>
134
+
135
+ <style lang="scss" scoped>
136
+ @import '../../styles/variables.scss';
137
+
138
+ .im-avatar {
139
+ position: relative;
140
+ display: inline-flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ box-sizing: border-box;
144
+ vertical-align: middle;
145
+ transition: all 0.3s ease;
146
+ user-select: none;
147
+ background-color: #ffffff;
148
+
149
+ // 形状
150
+ &--circle {
151
+ border-radius: 50%;
152
+ }
153
+
154
+ &--square {
155
+ border-radius: 8rpx;
156
+ }
157
+
158
+ &--rounded {
159
+ border-radius: 20%;
160
+ }
161
+
162
+ &--with-border {
163
+ box-shadow: 0 6rpx 12rpx rgba(0, 0, 0, 0.3);
164
+ }
165
+
166
+ &--clickable {
167
+ cursor: pointer;
168
+
169
+ &:active {
170
+ opacity: 0.8;
171
+ transform: scale(0.95);
172
+ }
173
+ }
174
+
175
+ // 尺寸
176
+ &--mini {
177
+ width: 64rpx;
178
+ height: 64rpx;
179
+ font-size: 24rpx;
180
+
181
+ .im-avatar__text {
182
+ font-size: 24rpx;
183
+ }
184
+
185
+ .im-avatar__icon {
186
+ font-size: 32rpx;
187
+ }
188
+ }
189
+
190
+ &--small {
191
+ width: 80rpx;
192
+ height: 80rpx;
193
+ font-size: 28rpx;
194
+
195
+ .im-avatar__text {
196
+ font-size: 28rpx;
197
+ }
198
+
199
+ .im-avatar__icon {
200
+ font-size: 40rpx;
201
+ }
202
+ }
203
+
204
+ &--medium {
205
+ width: 100rpx;
206
+ height: 100rpx;
207
+ font-size: 32rpx;
208
+
209
+ .im-avatar__text {
210
+ font-size: 32rpx;
211
+ }
212
+
213
+ .im-avatar__icon {
214
+ font-size: 48rpx;
215
+ }
216
+ }
217
+
218
+ &--large {
219
+ width: 120rpx;
220
+ height: 120rpx;
221
+ font-size: 36rpx;
222
+
223
+ .im-avatar__text {
224
+ font-size: 36rpx;
225
+ }
226
+
227
+ .im-avatar__icon {
228
+ font-size: 56rpx;
229
+ }
230
+ }
231
+
232
+ &--xlarge {
233
+ width: 160rpx;
234
+ height: 160rpx;
235
+ font-size: 48rpx;
236
+
237
+ .im-avatar__text {
238
+ font-size: 48rpx;
239
+ }
240
+
241
+ .im-avatar__icon {
242
+ font-size: 72rpx;
243
+ }
244
+ }
245
+
246
+ &--xxlarge {
247
+ width: 200rpx;
248
+ height: 200rpx;
249
+ font-size: 60rpx;
250
+
251
+ .im-avatar__text {
252
+ font-size: 60rpx;
253
+ }
254
+
255
+ .im-avatar__icon {
256
+ font-size: 88rpx;
257
+ }
258
+ }
259
+
260
+ // 子元素
261
+ &__image {
262
+ width: 100%;
263
+ height: 100%;
264
+ object-fit: cover;
265
+
266
+ // 形状
267
+ &--circle {
268
+ border-radius: 50%;
269
+ }
270
+
271
+ &--square {
272
+ border-radius: 8rpx;
273
+ }
274
+
275
+ &--rounded {
276
+ border-radius: 20%;
277
+ }
278
+
279
+ &--with-border {
280
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
281
+ }
282
+ }
283
+
284
+ &__text {
285
+ font-weight: 500;
286
+ line-height: 1;
287
+ text-transform: uppercase;
288
+ }
289
+
290
+ // &__icon {
291
+ // line-height: 1;
292
+ // }
293
+
294
+ // 状态指示器
295
+ &__status {
296
+ position: absolute;
297
+ right: 0;
298
+ bottom: 0;
299
+ width: 20rpx;
300
+ height: 20rpx;
301
+ border: 2rpx solid white;
302
+ border-radius: 50%;
303
+
304
+ &--online {
305
+ background-color: #07c160;
306
+ }
307
+
308
+ &--offline {
309
+ background-color: #c8c9cc;
310
+ }
311
+
312
+ &--busy {
313
+ background-color: #ff976a;
314
+ }
315
+
316
+ &--away {
317
+ background-color: #ffd700;
318
+ }
319
+
320
+ // 根据尺寸调整位置和大小
321
+ .im-avatar--mini &,
322
+ .im-avatar--small & {
323
+ width: 16rpx;
324
+ height: 16rpx;
325
+ right: -2rpx;
326
+ bottom: -2rpx;
327
+ }
328
+
329
+ .im-avatar--large &,
330
+ .im-avatar--xlarge &,
331
+ .im-avatar--xxlarge & {
332
+ width: 24rpx;
333
+ height: 24rpx;
334
+ right: 4rpx;
335
+ bottom: 4rpx;
336
+ }
337
+ }
338
+
339
+ // 加载状态
340
+ &__loading {
341
+
342
+ // 形状
343
+ &--circle {
344
+ border-radius: 50%;
345
+ }
346
+
347
+ &--square {
348
+ border-radius: 8rpx;
349
+ }
350
+
351
+ &--rounded {
352
+ border-radius: 20%;
353
+ }
354
+ }
355
+ }
356
+
357
+ // 暗黑模式适配
358
+ @media (prefers-color-scheme: dark) {
359
+ .im-avatar {
360
+ &--with-border {
361
+ border-color: #5c5c5c;
362
+ }
363
+
364
+ &__status {
365
+ border-color: #2c2c2c;
366
+ }
367
+
368
+ &__badge {
369
+
370
+ &--dot,
371
+ &--number {
372
+ border-color: #2c2c2c;
373
+ }
374
+ }
375
+ }
376
+ }
377
+ </style>
@@ -1,124 +1,31 @@
1
1
  <template>
2
- <!-- 角标 -->
3
- <im-badge v-if="badge && url" :value="badgeText" :dot="badgeDot">
4
- <view class="im-avatar" :class="[
5
- `im-avatar--${size}`,
6
- {
7
- 'im-avatar--square': shape === 'square',
8
- 'im-avatar--rounded': shape === 'rounded',
9
- 'im-avatar--circle': shape === 'circle',
10
- 'im-avatar--with-border': border,
11
- 'im-avatar--clickable': clickable
12
- }
13
- ]" :style="[customStyle, avatarStyle]" @click="handleClick" @longpress="handleLongPress">
14
- <!-- 图片头像 -->
15
- <image v-if="url" :class="['im-avatar__image',
16
- {
17
- 'im-avatar__image--square': shape === 'square',
18
- 'im-avatar__image--rounded': shape === 'rounded',
19
- 'im-avatar__image--circle': shape === 'circle',
20
- 'im-avatar__image--with-border': border,
21
- }
22
- ]" :src="url" :mode="imageMode" :show-menu-by-longpress="showMenuByLongpress" @error="handleImageError" />
23
- </view>
2
+ <!-- 有角标 -->
3
+ <im-badge v-if="badge" :value="badgeText" :dot="badgeDot" :max="badgeMax" :bg-color="badgeColor"
4
+ :text-color="badgeTextColor" :size="badgeSize">
5
+ <Avatar :id="id" :size="size" :shape="shape" :url="url" :title="title" :name="name" :icon="icon"
6
+ :bg-color="bgColor" :text-color="textColor" :font-size="fontSize" :font-weight="fontWeight"
7
+ :icon-color="iconColor" :icon-size="iconSize" :border="border" :border-color="borderColor" :radius="radius"
8
+ :status="status" :show-status="showStatus" :online="online" :clickable="clickable" :loading="loading"
9
+ :show-menu-by-longpress="showMenuByLongpress" :image-mode="imageMode" :custom-style="customStyle"
10
+ @click="handleClick" @longpress="handleLongPress" @error="handleImageError" />
24
11
  </im-badge>
25
- <view v-else class="im-avatar" :class="[
26
- `im-avatar--${size}`,
27
- {
28
- 'im-avatar--square': shape === 'square',
29
- 'im-avatar--rounded': shape === 'rounded',
30
- 'im-avatar--circle': shape === 'circle',
31
- 'im-avatar--with-border': border,
32
- 'im-avatar--clickable': clickable
33
- }
34
- ]" :style="[customStyle, avatarStyle]" @click="handleClick" @longpress="handleLongPress">
35
- <!-- 图片头像 -->
36
- <image v-if="url" :class="['im-avatar__image',
37
- {
38
- 'im-avatar__image--square': shape === 'square',
39
- 'im-avatar__image--rounded': shape === 'rounded',
40
- 'im-avatar__image--circle': shape === 'circle',
41
- 'im-avatar__image--with-border': border,
42
- }
43
- ]" :src="url" :mode="imageMode" :show-menu-by-longpress="showMenuByLongpress" @error="handleImageError" />
44
-
45
- <!-- 文字头像(无图片时显示) -->
46
- <text v-else-if="title" class="im-avatar__text" :style="textStyle">
47
- {{ displayText }}
48
- </text>
49
-
50
- <!-- 默认图标(无图片和文字时显示) -->
51
- <text v-else-if="icon" class="im-avatar__icon">
52
- <im-icon :name="icon" :color="iconColor" :size="iconSize" />
53
- </text>
54
-
55
- <!-- 在线状态指示器 -->
56
- <view v-if="showStatus && status" class="im-avatar__status" :class="`im-avatar__status--${status}`" />
57
-
58
- <!-- 角标 -->
59
- <view v-if="badge" :class="[]">
60
- <im-badge :value="badgeText" :dot="badgeDot" :class="[`im-avatar__badge`
61
- // #ifdef MP-WEIXIN
62
- , `im-avatar__badge--${size}`
63
- // #endif
64
- ]" :size="badgeSize" />
65
- </view>
66
-
67
- <!-- 加载状态 -->
68
- <im-loading v-if="loading" class="im-avatar__loading" :size="32" text="" :mask="true" />
69
- </view>
12
+ <!-- 无角标 -->
13
+ <Avatar v-else :id="id" :size="size" :shape="shape" :url="url" :title="title" :name="name" :icon="icon"
14
+ :bg-color="bgColor" :text-color="textColor" :font-size="fontSize" :font-weight="fontWeight"
15
+ :icon-color="iconColor" :icon-size="iconSize" :border="border" :border-color="borderColor" :radius="radius"
16
+ :status="status" :show-status="showStatus" :online="online" :clickable="clickable" :loading="loading"
17
+ :show-menu-by-longpress="showMenuByLongpress" :image-mode="imageMode" :custom-style="customStyle"
18
+ @click="handleClick" @longpress="handleLongPress" @error="handleImageError" />
70
19
  </template>
71
20
 
72
21
  <script setup lang="ts">
73
22
  import { computed } from 'vue'
74
- import ImIcon from '../im-icon/im-icon.vue'
75
23
  import { validator } from '../../index'
76
-
77
- // 定义组件Props
78
- interface Props {
79
- // 基础属性
80
- id?: string | number
81
- url?: string // 头像图片地址
82
- title?: string // 显示的文字(无图片时)
83
- size?: 'mini' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' // 尺寸
84
- shape?: 'circle' | 'square' | 'rounded' // 形状
85
- border?: boolean // 是否显示边框
86
- borderColor?: string // 边框颜色
87
-
88
- // 样式控制
89
- bgColor?: string // 背景颜色(无图片时)
90
- textColor?: string // 文字颜色(无图片时)
91
- fontSize?: number // 文字大小(无图片时)
92
- fontWeight?: number | string // 文字粗细
93
-
94
- // 图标设置
95
- icon?: string // 图标字符(无图片和文字时)
96
- iconColor?: string // 图标颜色
97
- iconSize?: number // 图标大小
98
-
99
- // 状态指示
100
- status?: 'online' | 'offline' | 'busy' | 'away' // 在线状态
101
- showStatus?: boolean // 是否显示状态指示器
102
- badge?: string | number | boolean // 角标内容
103
- badgeDot?: boolean // 是否显示圆点角标
104
- badgeMax?: number // 角标最大值
105
- badgeColor?: string // 角标背景色
106
- badgeTextColor?: string // 角标文字颜色
107
-
108
- // 交互功能
109
- clickable?: boolean // 是否可点击
110
- loading?: boolean // 加载状态
111
- showMenuByLongpress?: boolean // 是否允许长按显示菜单
112
-
113
- // 图片相关
114
- imageMode?: 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | 'heightFix' | 'top' | 'bottom' | 'center' | 'left' | 'right' | 'top left' | 'top right' | 'bottom left' | 'bottom right'
115
-
116
- // 自定义样式
117
- customStyle?: Record<string, string | number>
118
- }
24
+ import Avatar from './avatar.vue'
25
+ import type { AvatarProps, AvatarEmits } from '../../types/components/avatar'
119
26
 
120
27
  // 默认Props
121
- const props = withDefaults(defineProps<Props>(), {
28
+ const props = withDefaults(defineProps<AvatarProps>(), {
122
29
  size: 'medium',
123
30
  shape: 'circle',
124
31
  border: false,
@@ -145,59 +52,22 @@ const props = withDefaults(defineProps<Props>(), {
145
52
  })
146
53
 
147
54
  // 定义事件
148
- const emit = defineEmits<{
149
- click: [id?: string | number]
150
- longpress: [id?: string | number]
151
- error: [error: Error]
152
- }>()
153
-
154
- // 计算属性
155
- const displayText = computed(() => {
156
- if (!props.title) return ''
157
- // 取最后一个字符或前两个字符
158
- if (props.title.length <= 2) {
159
- return props.title
160
- }
161
- // 中文取最后两个字符,英文取首字母
162
- const isChinese = /[\u4e00-\u9fa5]/.test(props.title)
163
- if (isChinese) {
164
- return props.title.slice(-2)
165
- } else {
166
- const words = props.title.split(' ').filter(word => word.length > 0)
167
- if (words.length >= 2) {
168
- return words[0][0] + words[1][0]
169
- } else {
170
- return props.title.slice(0, 2)
171
- }
172
- }
173
- })
174
-
175
- const avatarStyle = computed(() => {
176
- const style: Record<string, string> = {}
177
-
178
- if (props.bgColor && !props.url) {
179
- style.backgroundColor = props.bgColor
180
- }
55
+ const emit = defineEmits<AvatarEmits>()
181
56
 
182
- if (props.border) {
183
- style.border = `2rpx solid ${props.borderColor}`
184
- }
57
+ const badgeText = computed(() => {
58
+ if (!props.badge) return ''
185
59
 
186
- return style
187
- })
60
+ if (validator.isNumber(String(props.badge))) {
61
+ const badgeNum = Number(props.badge)
188
62
 
189
- const textStyle = computed(() => {
190
- const style: Record<string, string> = {
191
- color: props.textColor,
192
- fontWeight: String(props.fontWeight)
193
- }
63
+ if (badgeNum > props.badgeMax) {
64
+ return `${props.badgeMax}+`
65
+ }
194
66
 
195
- // 根据尺寸设置字体大小
196
- if (props.fontSize) {
197
- style.fontSize = `${props.fontSize}rpx`
67
+ return String(props.badge)
198
68
  }
199
69
 
200
- return style
70
+ return String(props.badge)
201
71
  })
202
72
 
203
73
  const badgeSize = computed(() => {
@@ -211,357 +81,22 @@ const badgeSize = computed(() => {
211
81
  case 'xlarge':
212
82
  case 'xxlarge':
213
83
  return 'large'
214
- }
215
- })
216
-
217
- const badgeText = computed(() => {
218
- if (!props.badge) return ''
219
-
220
- if (validator.isNumber(String(props.badge))) {
221
- const badgeNum = Number(props.badge)
222
-
223
- if (badgeNum > props.badgeMax) {
224
- return `${props.badgeMax}+`
225
- }
84
+ default:
85
+ return props.size
226
86
 
227
- return String(props.badge)
228
87
  }
229
-
230
- // const badgeNum = Number(props.badge)
231
-
232
- // if (isNaN(badgeNum)) {
233
- // return String(props.badge)
234
- // }
235
-
236
- // if (badgeNum > props.badgeMax) {
237
- // return `${props.badgeMax}+`
238
- // }
239
-
240
- return String(props.badge)
241
88
  })
242
89
 
243
90
  // 事件处理
244
91
  const handleClick = () => {
245
- if (props.clickable && !props.loading) {
246
- emit('click', props.id)
247
- }
92
+ emit('click', props.id)
248
93
  }
249
94
 
250
95
  const handleLongPress = () => {
251
- if (props.clickable && !props.loading) {
252
- emit('longpress', props.id)
253
- }
96
+ emit('longpress', props.id)
254
97
  }
255
98
 
256
99
  const handleImageError = (error: any) => {
257
100
  emit('error', error)
258
101
  }
259
- </script>
260
-
261
- <style lang="scss" scoped>
262
- @import '../../styles/variables.scss';
263
-
264
- .im-avatar {
265
- position: relative;
266
- display: inline-flex;
267
- align-items: center;
268
- justify-content: center;
269
- box-sizing: border-box;
270
- vertical-align: middle;
271
- transition: all 0.3s ease;
272
- user-select: none;
273
- background-color: #ffffff;
274
-
275
- // 形状
276
- &--circle {
277
- border-radius: 50%;
278
- }
279
-
280
- &--square {
281
- border-radius: 8rpx;
282
- }
283
-
284
- &--rounded {
285
- border-radius: 20%;
286
- }
287
-
288
- &--with-border {
289
- box-shadow: 0 6rpx 12rpx rgba(0, 0, 0, 0.3);
290
- }
291
-
292
- &--clickable {
293
- cursor: pointer;
294
-
295
- &:active {
296
- opacity: 0.8;
297
- transform: scale(0.95);
298
- }
299
- }
300
-
301
- // 尺寸
302
- &--mini {
303
- width: 64rpx;
304
- height: 64rpx;
305
- font-size: 24rpx;
306
-
307
- .im-avatar__text {
308
- font-size: 24rpx;
309
- }
310
-
311
- .im-avatar__icon {
312
- font-size: 32rpx;
313
- }
314
- }
315
-
316
- &--small {
317
- width: 80rpx;
318
- height: 80rpx;
319
- font-size: 28rpx;
320
-
321
- .im-avatar__text {
322
- font-size: 28rpx;
323
- }
324
-
325
- .im-avatar__icon {
326
- font-size: 40rpx;
327
- }
328
- }
329
-
330
- &--medium {
331
- width: 100rpx;
332
- height: 100rpx;
333
- font-size: 32rpx;
334
-
335
- .im-avatar__text {
336
- font-size: 32rpx;
337
- }
338
-
339
- .im-avatar__icon {
340
- font-size: 48rpx;
341
- }
342
- }
343
-
344
- &--large {
345
- width: 120rpx;
346
- height: 120rpx;
347
- font-size: 36rpx;
348
-
349
- .im-avatar__text {
350
- font-size: 36rpx;
351
- }
352
-
353
- .im-avatar__icon {
354
- font-size: 56rpx;
355
- }
356
- }
357
-
358
- &--xlarge {
359
- width: 160rpx;
360
- height: 160rpx;
361
- font-size: 48rpx;
362
-
363
- .im-avatar__text {
364
- font-size: 48rpx;
365
- }
366
-
367
- .im-avatar__icon {
368
- font-size: 72rpx;
369
- }
370
- }
371
-
372
- &--xxlarge {
373
- width: 200rpx;
374
- height: 200rpx;
375
- font-size: 60rpx;
376
-
377
- .im-avatar__text {
378
- font-size: 60rpx;
379
- }
380
-
381
- .im-avatar__icon {
382
- font-size: 88rpx;
383
- }
384
- }
385
-
386
- // 子元素
387
- &__image {
388
- width: 100%;
389
- height: 100%;
390
- object-fit: cover;
391
-
392
- // 形状
393
- &--circle {
394
- border-radius: 50%;
395
- }
396
-
397
- &--square {
398
- border-radius: 8rpx;
399
- }
400
-
401
- &--rounded {
402
- border-radius: 20%;
403
- }
404
-
405
- &--with-border {
406
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
407
- }
408
- }
409
-
410
- &__text {
411
- font-weight: 500;
412
- line-height: 1;
413
- text-transform: uppercase;
414
- }
415
-
416
- &__icon {
417
- line-height: 1;
418
- }
419
-
420
- // 状态指示器
421
- &__status {
422
- position: absolute;
423
- right: 0;
424
- bottom: 0;
425
- width: 20rpx;
426
- height: 20rpx;
427
- border: 2rpx solid white;
428
- border-radius: 50%;
429
-
430
- &--online {
431
- background-color: #07c160;
432
- }
433
-
434
- &--offline {
435
- background-color: #c8c9cc;
436
- }
437
-
438
- &--busy {
439
- background-color: #ff976a;
440
- }
441
-
442
- &--away {
443
- background-color: #ffd700;
444
- }
445
-
446
- // 根据尺寸调整位置和大小
447
- .im-avatar--mini &,
448
- .im-avatar--small & {
449
- width: 16rpx;
450
- height: 16rpx;
451
- right: -2rpx;
452
- bottom: -2rpx;
453
- }
454
-
455
- .im-avatar--large &,
456
- .im-avatar--xlarge &,
457
- .im-avatar--xxlarge & {
458
- width: 24rpx;
459
- height: 24rpx;
460
- right: 4rpx;
461
- bottom: 4rpx;
462
- }
463
- }
464
-
465
- // 角标
466
- &__badge {
467
-
468
- // 尺寸
469
-
470
- // #ifdef MP-WEIXIN
471
- &--mini,
472
- // #endif
473
- .im-avatar--mini & {
474
- top: -30rpx;
475
- right: -6rpx;
476
- }
477
-
478
- .im-avatar--small & {
479
- top: -38rpx;
480
- right: -8rpx;
481
- }
482
-
483
- .im-avatar--medium & {
484
- top: -48rpx;
485
- right: -14rpx;
486
- }
487
-
488
- .im-avatar--large & {
489
- top: -56rpx;
490
- right: -20rpx;
491
- }
492
-
493
- .im-avatar--xlarge & {
494
- top: -72rpx;
495
- right: -25rpx;
496
- }
497
-
498
- .im-avatar--xxlarge & {
499
- top: -88rpx;
500
- right: -30rpx;
501
- }
502
-
503
- // #ifdef MP-WEIXIN
504
-
505
- // #endif
506
- }
507
-
508
- // 加载状态
509
- &__loading {
510
- // position: absolute;
511
- // top: 0;
512
- // left: 0;
513
- // width: 100%;
514
- // height: 100%;
515
- // display: flex;
516
- // align-items: center;
517
- // justify-content: center;
518
- // z-index: 2;
519
-
520
- // 形状
521
- .im-avatar--circle & {
522
- border-radius: 50%;
523
- }
524
-
525
- // &--circle {
526
- // border-radius: 50%;
527
- // }
528
-
529
- .im-avatar--square & {
530
- border-radius: 8rpx;
531
- }
532
-
533
- // &--square {
534
- // border-radius: 8rpx;
535
- // }
536
-
537
- .im-avatar--rounded & {
538
- border-radius: 20%;
539
- }
540
-
541
- // &--rounded {
542
- // border-radius: 20%;
543
- // }
544
- }
545
- }
546
-
547
- // 暗黑模式适配
548
- @media (prefers-color-scheme: dark) {
549
- .im-avatar {
550
- &--with-border {
551
- border-color: #5c5c5c;
552
- }
553
-
554
- &__status {
555
- border-color: #2c2c2c;
556
- }
557
-
558
- &__badge {
559
-
560
- &--dot,
561
- &--number {
562
- border-color: #2c2c2c;
563
- }
564
- }
565
- }
566
- }
567
- </style>
102
+ </script>
@@ -179,7 +179,7 @@ defineExpose({
179
179
  position: absolute;
180
180
  top: calc(-1 * var(--badge-offset-y, 0px));
181
181
  right: calc(-1 * var(--badge-offset-x, 0px));
182
- transform: translate(50%, -50%);
182
+ transform: translate(40%, -40%);
183
183
  z-index: 10;
184
184
  box-sizing: border-box;
185
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-ui-mobile",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "A Vue3.0 + Typescript instant messaging component library for Uniapp",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -31,7 +31,7 @@ declare interface AvatarProps {
31
31
  online?: boolean // 兼容 online 属性
32
32
 
33
33
  // 角标
34
- badge?: string | number
34
+ badge?: string | number | boolean
35
35
  badgeDot?: boolean
36
36
  badgeMax?: number
37
37
  badgeColor?: string
@@ -1,5 +1,6 @@
1
1
  declare module 'vue' {
2
2
  export interface GlobalComponents {
3
+ ['im-avatar']: typeof import('./components/avatar')['Avatar']
3
4
  ['im-cell']: typeof import('./components/cell')['Cell']
4
5
  ['im-cell-group']: typeof import('./components/cell-group')['CellGroup']
5
6
  ['im-button']: typeof import('./components/button')['Button']
@@ -13,7 +14,6 @@ declare module 'vue' {
13
14
  ['im-group-item']: typeof import('./components/group-item')['GroupItem']
14
15
  ['im-group-member-selector']: typeof import('./components/group-member-selector')['GroupMemberSelector']
15
16
  ['im-group-rtc-join']: typeof import('./components/group-rtc-join')['GroupRtcJoin']
16
- ['im-avatar']: typeof import('./components/avatar')['Avatar']
17
17
  ['im-loading']: typeof import('./components/loading')['Loading']
18
18
  ['im-context-menu']: typeof import('./components/context-menu')['ContextMenu']
19
19
  ['im-virtual-list']: typeof import('./components/virtual-list')['VirtualList']
@@ -1,123 +0,0 @@
1
- <template>
2
- <view class="avatar none-pointer-events" @click="showUserInfo" :title="title">
3
- <image v-if="url" class="avatar-image" :src="url" :style="avatarImageStyle" lazy-load="true"
4
- mode="aspectFill" />
5
- <view v-else-if="title" class="avatar-text" :style="avatarTextStyle">
6
- {{ title?.substring(0, 1).toUpperCase() }}
7
- </view>
8
- <view v-if="online" class="online" title="用户当前在线" />
9
- <slot />
10
- </view>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- import { computed } from 'vue'
15
-
16
- interface Props {
17
- id?: number;
18
- size?: number | string;
19
- url?: string;
20
- title?: string;
21
- radius?: string;
22
- online?: boolean;
23
- }
24
-
25
- const props = withDefaults(defineProps<Props>(), {
26
- size: 'default',
27
- title: '',
28
- radius: "50%",
29
- online: false
30
- });
31
-
32
- interface Emits {
33
- (e: 'click', id: number): void
34
- }
35
- const emit = defineEmits<Emits>()
36
-
37
- const colors = [
38
- "#5daa31", "#c7515a", "#e03697", "#85029b",
39
- "#c9b455", "#326eb6"
40
- ];
41
-
42
- const showUserInfo = () => {
43
- if (props.id && props.id > 0) {
44
- // TODO:组件化
45
- // uni.navigateTo({ url: "/pages/common/user-info?id=" + props.id });
46
- emit('click', props.id)
47
- }
48
- };
49
-
50
- const _size = computed(() => {
51
- if (typeof props.size === 'number') {
52
- return props.size;
53
- } else if (typeof props.size === 'string') {
54
- const sizeMap: Record<string, number> = {
55
- 'default': 96,
56
- 'small': 84,
57
- 'smaller': 72,
58
- 'mini': 60,
59
- 'minier': 48,
60
- 'lage': 108,
61
- 'lager': 120,
62
- };
63
- return sizeMap[props.size] || 96;
64
- }
65
- return 96;
66
- });
67
-
68
- const avatarImageStyle = computed(() => {
69
- return `width:${_size.value}rpx;height:${_size.value}rpx;`;
70
- });
71
-
72
- const avatarTextStyle = computed(() => {
73
- return `width: ${_size.value}rpx;
74
- height:${_size.value}rpx;
75
- background: linear-gradient(145deg,#ffffff20 25%,#00000060),${textColor.value};
76
- font-size:${_size.value * 0.45}rpx;
77
- border-radius: ${props.radius};`;
78
- });
79
-
80
- const textColor = computed(() => {
81
- if (!props.title) {
82
- return '#fff';
83
- }
84
- let hash = 0;
85
- for (let i = 0; i < props.title.length; i++) {
86
- hash += props.title.charCodeAt(i);
87
- }
88
- return colors[hash % colors.length];
89
- });
90
- </script>
91
-
92
- <style scoped lang="scss">
93
- .avatar {
94
- position: relative;
95
- cursor: pointer;
96
-
97
- .avatar-image {
98
- position: relative;
99
- overflow: hidden;
100
- border-radius: 50%;
101
- vertical-align: bottom;
102
- }
103
-
104
- .avatar-text {
105
- color: white;
106
- border-radius: 50%;
107
- display: flex;
108
- align-items: center;
109
- justify-content: center;
110
- }
111
-
112
- .online {
113
- position: absolute;
114
- right: -10%;
115
- bottom: 0;
116
- width: 24rpx;
117
- height: 24rpx;
118
- background: rgb(108, 198, 108);
119
- border-radius: 50%;
120
- border: 6rpx solid white;
121
- }
122
- }
123
- </style>
@@ -1,24 +0,0 @@
1
- import { AllowedComponentProps, VNodeProps } from '../common'
2
-
3
- declare interface AvatarProps {
4
- id?: number
5
- size?: number | string
6
- url?: string
7
- name?: string
8
- radius?: string
9
- online?: boolean
10
- }
11
-
12
- declare interface _Avatar {
13
- new(): {
14
- $props: AllowedComponentProps & VNodeProps & AvatarProps
15
- }
16
- }
17
-
18
- export declare const Avatar: _Avatar
19
-
20
- export default Avatar
21
-
22
- export type {
23
- AvatarProps
24
- }