| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { GroupMember } from '../../types' |
| | | |
| | | import { computed, ref, watch } from 'vue' |
| | | |
| | | import { |
| | | buildGroupAvatar, |
| | | getCachedGroupAvatar, |
| | | setCachedGroupAvatar |
| | | } from '../../../utils/group' |
| | | import { getMemberDisplayName } from '../../../utils/user' |
| | | import { useFriendStore } from '../../store/friendStore' |
| | | import { useGroupStore } from '../../store/groupStore' |
| | | import { UserAvatar } from '../user' |
| | | |
| | | defineOptions({ name: 'ImGroupAvatar' }) |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | clickable?: boolean // æ¯å¦å¯ç¹å»ï¼é»è®¤ falseï¼å表éä»
å±ç¤ºï¼ |
| | | groupId: number // 群ç¼å·ï¼ç¨äºæ¥ store æ¿æå头å |
| | | name?: string // 群åï¼è²å¡å
åºæå |
| | | previewable?: boolean // æ¯å¦ç¹å¤´åç´æ¥æ¾å¤§é¢è§ï¼ç¾¤è¯¦æ
大头åä½ç¨ï¼ |
| | | previewZIndex?: number // é¢è§å± z-index |
| | | radius?: string // åè§ï¼CSS é¿åº¦ |
| | | size?: number // 尺寸ï¼pxï¼ï¼æ£æ¹å½¢ |
| | | url?: string // æå¡ç«¯å·²è®¾ç½®ç群头å URLï¼é空åç´æ¥ç¨ï¼ä¸æ¼å¾ |
| | | }>(), |
| | | { |
| | | clickable: false, |
| | | name: '', |
| | | previewable: false, |
| | | previewZIndex: 2000, |
| | | radius: '15%', |
| | | size: 42, |
| | | url: '' |
| | | } |
| | | ) |
| | | |
| | | const friendStore = useFriendStore() |
| | | const groupStore = useGroupStore() |
| | | const mergedUrl = ref('') |
| | | // ç«æä¿æ¤ï¼ä¸¢å¼è¿æ await ç»æ |
| | | let mergeToken = 0 |
| | | |
| | | /** æå®¹å¨ size à DPR ç® canvas å®é
åç´ ï¼é¿å
2x / 3x retina 屿¼å¾ç³ï¼DPR å°é¡¶ 3 鲿¢è¶
é«å辨çç»å¸è¿å¤§ */ |
| | | function getTargetSize(size: number): number { |
| | | const dpr = Math.min(window.devicePixelRatio || 1, 3) |
| | | return Math.max(Math.round(size * dpr), 64) |
| | | } |
| | | |
| | | /** store éæ´ç¾¤æåæ¯å¦ã宿´å è½½ãè¿ï¼åªå¨ä¸º true æ¶ææ¼å¾ï¼é¿å
åè¡¨åºæ¯æ¹éåæ¥å£ */ |
| | | const loadedMembers = computed<GroupMember[] | null>(() => { |
| | | const g = groupStore.getGroup(props.groupId) |
| | | if (!g?.membersLoaded || !g.members) { |
| | | return null |
| | | } |
| | | return g.members |
| | | }) |
| | | |
| | | /** å 9 个æåçæ¼å¾å
¥åï¼name èµ° getMemberDisplayName å£å¾ï¼å¥½å夿³¨ > 群æµç§° > ç宿µç§°ï¼ */ |
| | | const memberItems = computed(() => { |
| | | const members = loadedMembers.value |
| | | if (!members) { |
| | | return [] |
| | | } |
| | | return members.slice(0, 9).map((m) => ({ |
| | | avatar: m.avatar || '', |
| | | name: getMemberDisplayName(m, friendStore.getFriend(m.userId)) |
| | | })) |
| | | }) |
| | | |
| | | /** æåå¿«ç
§ç¾åï¼æ¼ (avatar, name) åæ®µï¼åå°ä¿®æ¹ä»»ä¸å段é½ä¼è®© watch éç® */ |
| | | const memberSignature = computed(() => |
| | | memberItems.value.map((it) => `${it.avatar}#${it.name}`).join('|') |
| | | ) |
| | | |
| | | /** èµ° buildGroupAvatar æ¼å¾å¹¶åå mergedUrlï¼mergeToken æ ¡éªé¿å
è await è¦çæ°ç»æ */ |
| | | async function applyMerge(key: string, targetSize: number): Promise<void> { |
| | | const myToken = ++mergeToken |
| | | const cached = getCachedGroupAvatar(key) |
| | | if (cached) { |
| | | mergedUrl.value = cached |
| | | return |
| | | } |
| | | const dataUrl = await buildGroupAvatar(memberItems.value, { targetSize }) |
| | | if (myToken !== mergeToken) { |
| | | return |
| | | } |
| | | if (dataUrl) { |
| | | setCachedGroupAvatar(key, dataUrl) |
| | | } |
| | | mergedUrl.value = dataUrl |
| | | } |
| | | |
| | | watch( |
| | | () => [props.url, props.groupId, props.size, memberSignature.value] as const, |
| | | ([url, groupId, size, signature]) => { |
| | | if (url) { |
| | | mergedUrl.value = '' |
| | | return |
| | | } |
| | | if (!signature) { |
| | | mergeToken++ |
| | | mergedUrl.value = '' |
| | | groupStore.loadGroupMemberList(groupId) |
| | | return |
| | | } |
| | | const targetSize = getTargetSize(size) |
| | | const key = `${groupId}:${targetSize}:${signature}` |
| | | applyMerge(key, targetSize) |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | |
| | | /** æç»å±ç¤º urlï¼æå¡ç«¯ url ä¼å
â æ¼å¾ â 空å符串ï¼è®© UserAvatar èµ°è²å¡ï¼ */ |
| | | const finalUrl = computed(() => props.url || mergedUrl.value) |
| | | </script> |
| | | |
| | | <template> |
| | | <!-- url é空走åå¾ï¼url 空æ¶åå 9 个æå头忼ä¹å®«æ ¼ dataURLï¼æåæªå¨ store ç¼åæ¶èµ°è²å¡å
åº --> |
| | | <UserAvatar |
| | | :url="finalUrl" |
| | | :name="name" |
| | | :size="size" |
| | | :radius="radius" |
| | | :clickable="clickable" |
| | | :previewable="previewable" |
| | | :preview-z-index="previewZIndex" |
| | | /> |
| | | </template> |