fix: 전용 장수 아이콘 뒤 기본 이미지 중첩 제거

This commit is contained in:
2026-09-29 08:43:13 +00:00
parent aea5056834
commit 295ecb39a3
5 changed files with 88 additions and 34 deletions
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { useDefaultGeneralIcon } from '../../utils/generalIcon';
type SelectionMetric = {
label: string;
@@ -12,7 +13,7 @@ type SelectionItem = {
subtitle: string;
searchText: string;
accent?: 'current' | 'assigned' | 'available';
iconBackground?: string;
iconUrl?: string;
badges?: string[];
stats?: SelectionMetric[];
details?: SelectionMetric[];
@@ -168,11 +169,13 @@ onBeforeUnmount(() => {
:aria-pressed="selectedId === item.id"
@click="select(item.id)"
>
<span
v-if="item.iconBackground"
<img
v-if="item.iconUrl"
class="personnel-picker-portrait"
:style="{ backgroundImage: item.iconBackground }"
:src="item.iconUrl"
alt=""
aria-hidden="true"
@error="useDefaultGeneralIcon"
/>
<span v-else class="personnel-picker-city-icon" aria-hidden="true">城</span>
@@ -366,9 +369,7 @@ small {
background-color: #0b0c0a;
}
.personnel-picker-portrait {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
object-fit: cover;
}
.personnel-picker-city-icon,
.vacancy-icon {
@@ -43,11 +43,6 @@ export const resolveGeneralIconUrl = (
return `${trimTrailingSlashes(baseUrl)}/${encodedPicture}`;
};
export const resolveGeneralIconBackgroundImage = (source: GeneralIconSource, options?: GeneralIconOptions): string => {
const resolved = resolveGeneralIconUrl(source, options);
return `url(${JSON.stringify(resolved)}), url(${JSON.stringify(DEFAULT_GENERAL_ICON_URL)})`;
};
export const resolveMessageGeneralIconUrl = (
icon: string | null | undefined,
userIconBaseUrl = configuredUserIconPublicUrl()
@@ -8,7 +8,7 @@ import { JosaUtil } from '@sammo-ts/common/util/JosaUtil';
import PermissionMultiSelect from '../components/personnel/PermissionMultiSelect.vue';
import PersonnelSelectionDialog from '../components/personnel/PersonnelSelectionDialog.vue';
import { useGameFeedback } from '../composables/useGameFeedback';
import { resolveGeneralIconBackgroundImage } from '../utils/generalIcon';
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
import { sortGeneralsByTypeThenName } from '../utils/generalOrder';
import { trpc } from '../utils/trpc';
import { cityLevelMap, formatOfficerLevelText, getNationChiefLevel, regionMap } from '../utils/nationFormat';
@@ -25,7 +25,7 @@ type SelectionDialogItem = {
subtitle: string;
searchText: string;
accent: 'current' | 'assigned' | 'available';
iconBackground?: string;
iconUrl?: string;
badges: string[];
stats?: Array<{ label: string; value: string }>;
details: Array<{ label: string; value: string }>;
@@ -77,7 +77,7 @@ const chiefAssignments = computed(() => data.value?.chiefAssignments ?? {});
const cityNameMap = computed(() => new Map((data.value?.cityAssignments ?? []).map((city) => [city.id, city.name])));
const generalMap = computed(() => new Map((data.value?.generals ?? []).map((general) => [general.id, general])));
const imageBackground = (general: GeneralEntry | undefined): string => resolveGeneralIconBackgroundImage(general ?? {});
const imageUrl = (general: GeneralEntry | undefined): string => resolveGeneralIconUrl(general ?? {});
const officerLocked = (value: number, level: number): boolean => (value & (1 << level)) !== 0;
const chiefLocked = (level: number): boolean => officerLocked(data.value?.nation.chiefSet ?? 0, level);
const cityOfficerLocked = (city: PersonnelResponse['cityAssignments'][number], level: number): boolean =>
@@ -197,7 +197,7 @@ const generalSelectionItem = (general: GeneralEntry, currentGeneralId: number):
.filter(Boolean)
.join(' '),
accent: isCurrent ? 'current' : isAssigned ? 'assigned' : 'available',
iconBackground: imageBackground(general),
iconUrl: imageUrl(general),
badges,
stats: [
{ label: '통솔', value: general.stats.leadership.toLocaleString('ko-KR') },
@@ -355,10 +355,12 @@ onMounted(() => void loadPersonnel());
<span class="chief-entry-role">{{
formatOfficerLevelText(level, nationLevel)
}}</span>
<span
<img
class="general-icon"
:style="{ backgroundImage: imageBackground(chiefAssignments[level]) }"
:src="imageUrl(chiefAssignments[level])"
alt=""
aria-hidden="true"
@error="useDefaultGeneralIcon"
/>
<span class="chief-entry-copy">
<strong>{{ chiefAssignments[level]?.name ?? '공석' }}</strong>
@@ -736,9 +738,7 @@ select {
grid-area: icon;
width: 64px;
height: 64px;
background-repeat: no-repeat;
background-position: center;
background-size: 64px 64px;
object-fit: fill;
}
.chief-entry-copy {
display: grid;
@@ -943,7 +943,6 @@ select {
.general-icon {
width: 48px;
height: 48px;
background-size: 48px 48px;
}
.chief-entry-copy strong {
font-size: var(--sammo-font-size-personnel-mobile-name);