feat: 내정보 상세 설명 툴팁 이관

말·무기·서적·도구·병종·특기 설명과 국가 성향 정보를 API에 투영하고 Tippy.js 기반 공용 툴팁으로 표시한다. 실제 Chromium의 hover·focus와 모바일 경계를 회귀 테스트한다.
This commit is contained in:
2026-08-21 15:55:44 +00:00
parent 9e0f1f6003
commit a0e64a6f74
10 changed files with 708 additions and 30 deletions
@@ -3,6 +3,7 @@ import { computed } from 'vue';
import SkeletonLines from '../ui/SkeletonLines.vue';
import LegacyProgressBar from '../ui/LegacyProgressBar.vue';
import RichTooltip from '../ui/RichTooltip.vue';
import { formatLocalTimeSeconds } from '../../utils/legacyDateTime';
import { legacyExperiencePercent, ratioPercent } from '../../utils/legacyProgress';
import { DEFAULT_GENERAL_ICON_URL, resolveGeneralIconBackgroundImage } from '../../utils/generalIcon';
@@ -30,6 +31,28 @@ interface ItemDisplayNames {
item?: string | null;
}
interface ItemDisplayInfo {
horse?: string | null;
weapon?: string | null;
book?: string | null;
item?: string | null;
}
interface CrewTypeDisplayInfo {
name: string;
info: string[];
requirements: string[];
stats: {
attack: number;
defence: number;
speed: number;
avoid: number;
magicCoef: number;
cost: number;
rice: number;
};
}
interface GeneralTroopDisplay {
name: string;
status: 'inactive' | 'present' | 'away';
@@ -73,9 +96,12 @@ interface GeneralInfo {
refreshScore?: GeneralRefreshScore;
crewTypeId?: number;
crewTypeName?: string;
crewTypeInfo?: CrewTypeDisplayInfo | null;
traits?: { personal: string; specialWar: string; specialDomestic: string };
traitInfo?: { personal: string; specialWar: string; specialDomestic: string };
progression?: GeneralProgression;
itemNames?: ItemDisplayNames;
itemInfo?: ItemDisplayInfo;
equipmentNames?: ItemDisplayNames;
}
@@ -243,9 +269,36 @@ const specialText = computed(() => {
</strong>
</template>
<span class="cell-label">명마</span><strong>{{ itemNames.horse ?? '-' }}</strong>
<span class="cell-label">무기</span><strong>{{ itemNames.weapon ?? '-' }}</strong>
<span class="cell-label">서적</span><strong>{{ itemNames.book ?? '-' }}</strong>
<span class="cell-label">명마</span>
<strong>
<RichTooltip
:title="itemNames.horse ?? ''"
:description="props.general.itemInfo?.horse"
test-id="horse"
>
{{ itemNames.horse ?? '-' }}
</RichTooltip>
</strong>
<span class="cell-label">무기</span>
<strong>
<RichTooltip
:title="itemNames.weapon ?? ''"
:description="props.general.itemInfo?.weapon"
test-id="weapon"
>
{{ itemNames.weapon ?? '-' }}
</RichTooltip>
</strong>
<span class="cell-label">서적</span>
<strong>
<RichTooltip
:title="itemNames.book ?? ''"
:description="props.general.itemInfo?.book"
test-id="book"
>
{{ itemNames.book ?? '-' }}
</RichTooltip>
</strong>
<span
class="general-image general-crew-type-icon"
@@ -255,15 +308,91 @@ const specialText = computed(() => {
/>
<span class="cell-label">자금</span><strong>{{ props.general.gold.toLocaleString('ko-KR') }}</strong>
<span class="cell-label">군량</span><strong>{{ props.general.rice.toLocaleString('ko-KR') }}</strong>
<span class="cell-label">도구</span><strong>{{ itemNames.item ?? '-' }}</strong>
<span class="cell-label">도구</span>
<strong>
<RichTooltip
:title="itemNames.item ?? ''"
:description="props.general.itemInfo?.item"
test-id="item"
>
{{ itemNames.item ?? '-' }}
</RichTooltip>
</strong>
<span class="cell-label">병종</span><strong>{{ props.general.crewTypeName ?? '-' }}</strong>
<span class="cell-label">병종</span>
<strong>
<RichTooltip
:title="props.general.crewTypeName ?? ''"
:description="props.general.crewTypeInfo?.info"
test-id="crew-type"
>
{{ props.general.crewTypeName ?? '-' }}
<template v-if="props.general.crewTypeInfo" #content>
<span class="rich-tooltip-content__title">{{ props.general.crewTypeInfo.name }}</span>
<span
v-for="(line, index) in props.general.crewTypeInfo.info"
:key="`crew-info:${index}`"
class="rich-tooltip-content__line"
>
{{ line }}
</span>
<span class="rich-tooltip-content__section">전투 정보</span>
<span class="rich-tooltip-content__meta">
공격 {{ props.general.crewTypeInfo.stats.attack }} · 방어
{{ props.general.crewTypeInfo.stats.defence }} · 속도
{{ props.general.crewTypeInfo.stats.speed }} · 회피
{{ props.general.crewTypeInfo.stats.avoid }}% · 계략
{{ props.general.crewTypeInfo.stats.magicCoef }}%
</span>
<span class="rich-tooltip-content__meta">
병사 100 기준 {{ props.general.crewTypeInfo.stats.cost }} ·
{{ props.general.crewTypeInfo.stats.rice }}
</span>
<template v-if="props.general.crewTypeInfo.requirements.length">
<span class="rich-tooltip-content__section">생성 조건</span>
<span
v-for="(requirement, index) in props.general.crewTypeInfo.requirements"
:key="`crew-requirement:${index}`"
class="rich-tooltip-content__line"
>
{{ requirement }}
</span>
</template>
</template>
</RichTooltip>
</strong>
<span class="cell-label">병사</span><strong>{{ props.general.crew.toLocaleString('ko-KR') }}</strong>
<span class="cell-label">성격</span><strong>{{ props.general.traits?.personal ?? '-' }}</strong>
<span class="cell-label">성격</span>
<strong>
<RichTooltip
:title="props.general.traits?.personal ?? ''"
:description="props.general.traitInfo?.personal"
test-id="personality"
>
{{ props.general.traits?.personal ?? '-' }}
</RichTooltip>
</strong>
<span class="cell-label">훈련</span><strong>{{ props.general.train }}</strong>
<span class="cell-label">사기</span><strong>{{ props.general.atmos }}</strong>
<span class="cell-label">특기</span><strong :title="specialText">{{ specialText }}</strong>
<span class="cell-label">특기</span>
<strong class="special-value" :aria-label="specialText">
<RichTooltip
:title="`내정특기 · ${props.general.traits?.specialDomestic ?? '-'}`"
:description="props.general.traitInfo?.specialDomestic"
test-id="special-domestic"
>
{{ props.general.traits?.specialDomestic ?? '-' }}
</RichTooltip>
/
<RichTooltip
:title="`전투특기 · ${props.general.traits?.specialWar ?? '-'}`"
:description="props.general.traitInfo?.specialWar"
test-id="special-war"
>
{{ props.general.traits?.specialWar ?? '-' }}
</RichTooltip>
</strong>
<span class="cell-label level-label">Lv</span>
<strong class="level-value">{{ props.general.progression?.experienceLevel ?? 0 }}</strong>
@@ -1,5 +1,6 @@
<script setup lang="ts">
import SkeletonLines from '../ui/SkeletonLines.vue';
import RichTooltip from '../ui/RichTooltip.vue';
import { legacyLuminanceTextColor } from '../../utils/legacyNationColor';
import { formatOfficerLevelText } from '../../utils/nationFormat';
import { getNpcColor } from '../../utils/npcColor';
@@ -19,6 +20,7 @@ interface NationInfo {
rice: number;
tech: number;
typeName: string;
typeInfo?: string;
typePros: string;
typeCons: string;
population: { cityCount: number; current: number; max: number };
@@ -67,9 +69,37 @@ const displayChiefName = (chief: NationChief | undefined): string => {
<span class="head">성향</span>
<strong class="body type-body">
{{ props.nation.typeName }} (<span class="pros">{{ props.nation.typePros }}</span>
<span class="cons">{{ props.nation.typeCons }}</span
>)
<RichTooltip
v-if="props.nation.typeInfo"
:title="`국가 성향 · ${props.nation.typeName}`"
:description="props.nation.typeInfo"
test-id="nation-type"
>
{{ props.nation.typeName }} (<span class="pros">{{ props.nation.typePros }}</span>
<span class="cons">{{ props.nation.typeCons }}</span
>)
<template #content="{ descriptionLines }">
<span class="rich-tooltip-content__title">국가 성향 · {{ props.nation.typeName }}</span>
<span
v-for="(line, index) in descriptionLines"
:key="`nation-type-info:${index}`"
class="rich-tooltip-content__line"
>
{{ line }}
</span>
<span class="rich-tooltip-content__line rich-tooltip-content__pros">
장점 {{ props.nation.typePros || '-' }}
</span>
<span class="rich-tooltip-content__line rich-tooltip-content__cons">
단점 {{ props.nation.typeCons || '-' }}
</span>
</template>
</RichTooltip>
<template v-else>
{{ props.nation.typeName }} (<span class="pros">{{ props.nation.typePros }}</span>
<span class="cons">{{ props.nation.typeCons }}</span
>)
</template>
</strong>
<span class="head">{{ formatOfficerLevelText(12, props.nation.level) }}</span>