front/src/components/MemberLevelBadge/index.vue
2026-10-07 17:34:36 +08:00

135 lines
3.1 KiB
Vue

<template>
<span
v-if="displayName"
class="member-level-badge"
:class="[`is-${size}`, tone ? `is-${tone.key}` : 'is-fallback']"
:style="badgeStyle"
:title="displayName"
>
<img
v-if="resolvedIconUrl"
class="member-level-badge__img"
:src="resolvedIconUrl"
alt=""
draggable="false"
/>
<span
v-else-if="tone"
class="member-level-badge__glyph"
aria-hidden="true"
v-html="glyphHtml"
/>
<span v-else class="member-level-badge__glyph member-level-badge__glyph--text" aria-hidden="true">
{{ displayName.slice(0, 1) }}
</span>
<span class="member-level-badge__name">{{ displayName }}</span>
</span>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import {
getMemberLevelTone,
renderMemberLevelGlyphSvg,
} from '@/utils/memberLevelIcon'
const props = withDefaults(
defineProps<{
levelCode?: string | null
levelName?: string | null
iconUrl?: string | null
size?: 'sm' | 'md'
}>(),
{
levelCode: '',
levelName: '',
iconUrl: '',
size: 'sm',
},
)
const displayName = computed(() => String(props.levelName || '').trim())
const tone = computed(() => getMemberLevelTone(props.levelCode, props.levelName))
const resolvedIconUrl = computed(() => String(props.iconUrl || '').trim())
const glyphHtml = computed(() =>
tone.value ? renderMemberLevelGlyphSvg(tone.value.key, props.size === 'md' ? 16 : 14) : '',
)
const badgeStyle = computed(() => {
if (!tone.value) return undefined
return {
'--mlb-color': tone.value.color,
'--mlb-bg': tone.value.bg,
'--mlb-border': tone.value.border,
} as Record<string, string>
})
</script>
<style scoped lang="less">
.member-level-badge {
display: inline-flex;
align-items: center;
gap: 5px;
max-width: 100%;
min-height: 22px;
padding: 2px 8px 2px 6px;
border-radius: 999px;
border: 1px solid var(--mlb-border, var(--color-border-2));
background: var(--mlb-bg, var(--color-fill-2));
color: var(--mlb-color, var(--app-primary-color));
vertical-align: middle;
line-height: 1.2;
white-space: nowrap;
}
.member-level-badge.is-md {
min-height: 26px;
gap: 6px;
padding: 3px 10px 3px 7px;
}
.member-level-badge__img,
.member-level-badge__glyph {
flex-shrink: 0;
width: 14px;
height: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.member-level-badge.is-md .member-level-badge__img,
.member-level-badge.is-md .member-level-badge__glyph {
width: 16px;
height: 16px;
}
.member-level-badge__img {
object-fit: contain;
border-radius: 50%;
}
.member-level-badge__glyph :deep(svg) {
display: block;
width: 100%;
height: 100%;
}
.member-level-badge__glyph--text {
font-size: 10px;
font-weight: 800;
}
.member-level-badge__name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.02em;
}
.member-level-badge.is-md .member-level-badge__name {
font-size: 13px;
}
</style>