Sync source to CNB / Sync Git ref (push) Skipped
- 房间列表:头像、名称、可见性、类型、语音标记、前置权限、创建时间 - 创建/编辑房间:名称、简介、公开或私有、普通或聚落、前置条件判定与所需权限 - 房间头像:上传、更换、移除,支持 PNG/JPEG/WebP,5 MB 限制 - 删除房间带二次确认 - 新增 RoomAvatar 与 RoomAvatarEditor 组件,头像按附件 id 缓存 Blob URL - 补充聊天室相关 API:房间列表、创建、更新、删除、头像更新、附件上传、头像获取 - 管理台导航接入聊天室入口 - 中英文各补 37 个文案
64 lines
1.5 KiB
Vue
64 lines
1.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
import { loadCachedRoomAvatar } from './room-avatar-cache'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
roomId: number
|
|
avatarAttachmentId?: number | null
|
|
name: string
|
|
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
}>(),
|
|
{ avatarAttachmentId: null, size: 'md' },
|
|
)
|
|
|
|
const avatarUrl = ref<string | null>(null)
|
|
const loading = ref(false)
|
|
|
|
const sizeClass = computed(
|
|
() =>
|
|
({
|
|
sm: 'size-9 rounded-lg text-lg',
|
|
md: 'size-10 rounded-lg text-xl',
|
|
lg: 'size-12 rounded-lg text-2xl',
|
|
xl: 'size-20 rounded-xl text-3xl',
|
|
})[props.size],
|
|
)
|
|
|
|
watch(
|
|
() => [props.roomId, props.avatarAttachmentId] as const,
|
|
async ([roomId, attachmentId]) => {
|
|
avatarUrl.value = null
|
|
if (!attachmentId) {
|
|
loading.value = false
|
|
return
|
|
}
|
|
loading.value = true
|
|
const url = await loadCachedRoomAvatar(roomId, attachmentId)
|
|
if (props.roomId === roomId && props.avatarAttachmentId === attachmentId) {
|
|
avatarUrl.value = url
|
|
}
|
|
loading.value = false
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="grid shrink-0 place-items-center overflow-hidden border border-solid border-surface-5 bg-brand/10 font-bold text-brand"
|
|
:class="sizeClass"
|
|
>
|
|
<img
|
|
v-if="avatarUrl"
|
|
:src="avatarUrl"
|
|
:alt="name"
|
|
class="h-full w-full object-cover"
|
|
decoding="async"
|
|
/>
|
|
<span v-else-if="loading" class="text-[10px] text-secondary">…</span>
|
|
<span v-else class="select-none">#</span>
|
|
</div>
|
|
</template>
|