AvatarStack.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <template>
  2. <view class="nana-avatar-stack">
  3. <template
  4. v-for="(img, i) of urls"
  5. :key="i"
  6. >
  7. <Avatar v-if="i === 0"
  8. :innerStyle="{
  9. ...imageStyle,
  10. marginLeft: 0,
  11. zIndex: 0,
  12. }"
  13. :url="urls[i]"
  14. :size="size"
  15. @click="handleClick(i)"
  16. />
  17. <Avatar v-else-if="i < maxCount"
  18. :innerStyle="{
  19. ...imageStyle,
  20. zIndex: i,
  21. }"
  22. :url="urls[i]"
  23. :size="size"
  24. @click="handleClick(i)"
  25. />
  26. <view v-else-if="showOverflowCount && i === maxCount"
  27. :style="{
  28. ...imageStyle,
  29. ...themeStyles.overflowCount.value,
  30. zIndex: maxCount,
  31. }"
  32. @click="handleClick(i)"
  33. >
  34. <text :style="themeStyles.overflowCountText.value">+{{urls.length - i}}</text>
  35. </view>
  36. </template>
  37. </view>
  38. </template>
  39. <script setup lang="ts">
  40. import { computed } from 'vue';
  41. import { propGetThemeVar, useTheme, type ViewStyle } from '../theme/ThemeDefine';
  42. import Avatar from './Avatar.vue';
  43. import { DynamicColor, DynamicVar } from '../theme/ThemeTools';
  44. export interface AvatarStackProp {
  45. /**
  46. * 默认头像
  47. */
  48. defaultAvatar?: string,
  49. /**
  50. * 头像的图标URL
  51. */
  52. urls: string[],
  53. /**
  54. * 最大显示多少个头像,超过后显示数字
  55. * @default 5
  56. */
  57. maxCount?: number,
  58. /**
  59. * 超过最大显示后是否显示数字
  60. * @default true
  61. */
  62. showOverflowCount?: boolean,
  63. /**
  64. * 设置头像之间的距离
  65. * @default size / 3
  66. */
  67. imageMargin?: number,
  68. /**
  69. * 头像的大小
  70. * @default 70
  71. */
  72. size?: number,
  73. /**
  74. * 头像是否是圆形的
  75. * @default true
  76. */
  77. round?: boolean,
  78. /**
  79. * 头像是圆角的大小,仅在 round=false 时有效
  80. * @default 50%
  81. */
  82. radius?: number|string,
  83. /**
  84. * 是否为头像添加边框
  85. * @default false
  86. */
  87. border?: boolean,
  88. /**
  89. * 头像边框宽度
  90. * @default 1.5
  91. */
  92. borderWidth?: number,
  93. /**
  94. * 头像边框颜色
  95. * @default Color.white
  96. */
  97. borderColor?: string,
  98. /**
  99. * 超出显示文字背景样式
  100. */
  101. overflowCountStyle?: ViewStyle,
  102. /**
  103. * 超出显示文字自定义样式
  104. */
  105. overflowCountTextStyle?: ViewStyle,
  106. /**
  107. * 是否可以点击放大预览
  108. * @default false
  109. */
  110. preview?: boolean,
  111. }
  112. const emit = defineEmits([ 'click' ]);
  113. const themeContext = useTheme();
  114. const props = withDefaults(defineProps<AvatarStackProp>(), {
  115. defaultAvatar: '',
  116. urls: () => [],
  117. maxCount: () => propGetThemeVar('AvatarStackMaxCount', 5),
  118. showOverflowCount: () => propGetThemeVar('AvatarStackShowOverflowCount', true),
  119. imageMargin: 0,
  120. size: () => propGetThemeVar('AvatarStackSize', 70),
  121. round: () => propGetThemeVar('AvatarStackRound', true),
  122. radius: () => propGetThemeVar('AvatarStackRadius', '50%'),
  123. border: () => propGetThemeVar('AvatarStackBorder', false),
  124. borderWidth: () => propGetThemeVar('AvatarStackBorderWidth', 10),
  125. borderColor: () => propGetThemeVar('AvatarStackBorderColor', 'white'),
  126. });
  127. const imageStyle = computed(() => {
  128. const size = themeContext.resolveThemeSize(props.size, 0);
  129. return {
  130. marginLeft: props.imageMargin ? themeContext.resolveThemeSize(props.imageMargin) : `calc(-${size} / 3)`,
  131. borderRadius: props.round ? '50%' : themeContext.resolveThemeSize(props.radius),
  132. border: props.border ? `${themeContext.resolveThemeSize(props.borderWidth)} solid ${themeContext.resolveThemeColor(props.borderColor)}` : undefined,
  133. width: size,
  134. height: size,
  135. }
  136. });
  137. const themeStyles = themeContext.useThemeStyles({
  138. overflowCount: {
  139. backgroundColor: DynamicColor('AvatarStackOverflowCountBackgroundColor', 'white'),
  140. flexDirection: 'row',
  141. alignItems: 'center',
  142. justifyContent: 'center',
  143. display: 'flex',
  144. },
  145. overflowCountText: {
  146. fontSize: DynamicVar('AvatarStackOverflowCountTextFontSize', 12),
  147. color: DynamicColor('AvatarStackOverflowCountTextColor', 'text.content'),
  148. },
  149. })
  150. function handleClick(i: number) {
  151. if (props.preview) {
  152. uni.previewImage({
  153. urls: props.urls,
  154. current: i,
  155. })
  156. }
  157. emit('click', i);
  158. }
  159. </script>
  160. <style>
  161. .nana-avatar-stack {
  162. display: flex;
  163. flex-direction: row;
  164. align-items: center;
  165. }
  166. </style>