툴팁의 hover와 키보드 focus 동작은 유지하면서 게임 정보에 반복되던 점선·파선 밑줄을 제거한다. 실제 Chromium에서 내정보, 장수 일람, 국가 전략, NPC 선택 흐름을 검증한다.
327 lines
11 KiB
Vue
327 lines
11 KiB
Vue
<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';
|
|
|
|
interface NationChief {
|
|
id: number;
|
|
name: string;
|
|
npcState: number;
|
|
}
|
|
|
|
interface NationInfo {
|
|
id: number;
|
|
name: string;
|
|
color: string;
|
|
level: number;
|
|
gold: number;
|
|
rice: number;
|
|
tech: number;
|
|
typeName: string;
|
|
typeInfo?: string;
|
|
typePros: string;
|
|
typeCons: string;
|
|
population: { cityCount: number; current: number; max: number };
|
|
crew: { generalCount: number; current: number; max: number };
|
|
power: number;
|
|
bill: number;
|
|
taxRate: number;
|
|
strategicCommandLimit: number;
|
|
diplomaticLimit: number;
|
|
prohibitScout: boolean;
|
|
prohibitWar: boolean;
|
|
techLevel: number;
|
|
techLimited: boolean;
|
|
topChiefs: Record<number, NationChief | undefined>;
|
|
impossibleStrategicCommands: Array<{
|
|
name: string;
|
|
remainingTurns: number;
|
|
availableYear: number;
|
|
availableMonth: number;
|
|
}>;
|
|
}
|
|
|
|
const props = defineProps<{
|
|
nation: NationInfo | null;
|
|
loading: boolean;
|
|
}>();
|
|
|
|
const number = (value: number): string => value.toLocaleString('ko-KR');
|
|
const displayChiefName = (chief: NationChief | undefined): string => {
|
|
if (!chief) return '-';
|
|
return chief.npcState > 0 && !/^[ⓜⓝ㉥]/u.test(chief.name) ? `ⓝ${chief.name}` : chief.name;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="nation-card" data-nation-basic-card>
|
|
<div v-if="props.loading" class="loading"><SkeletonLines :lines="6" /></div>
|
|
<div v-else-if="!props.nation" class="empty">국가 정보를 불러오지 못했습니다.</div>
|
|
<div v-else class="nation-grid">
|
|
<div
|
|
class="title"
|
|
:style="{ backgroundColor: props.nation.color, color: legacyLuminanceTextColor(props.nation.color) }"
|
|
>
|
|
{{ props.nation.name }}
|
|
</div>
|
|
|
|
<span class="head">성향</span>
|
|
<strong class="body type-body">
|
|
<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>
|
|
<strong class="body" :style="{ color: getNpcColor(props.nation.topChiefs[12]?.npcState ?? 1) }">
|
|
{{ displayChiefName(props.nation.topChiefs[12]) }}
|
|
</strong>
|
|
<span class="head">{{ formatOfficerLevelText(11, props.nation.level) }}</span>
|
|
<strong class="body" :style="{ color: getNpcColor(props.nation.topChiefs[11]?.npcState ?? 1) }">
|
|
{{ displayChiefName(props.nation.topChiefs[11]) }}
|
|
</strong>
|
|
|
|
<span class="head">총 주민</span>
|
|
<strong class="body">{{
|
|
props.nation.id === 0
|
|
? '해당 없음'
|
|
: `${number(props.nation.population.current)} / ${number(props.nation.population.max)}`
|
|
}}</strong>
|
|
<span class="head">총 병사</span>
|
|
<strong class="body">{{
|
|
props.nation.id === 0
|
|
? '해당 없음'
|
|
: `${number(props.nation.crew.current)} / ${number(props.nation.crew.max)}`
|
|
}}</strong>
|
|
|
|
<span class="head">국고</span>
|
|
<strong class="body">{{ props.nation.id === 0 ? '해당 없음' : number(props.nation.gold) }}</strong>
|
|
<span class="head">병량</span>
|
|
<strong class="body">{{ props.nation.id === 0 ? '해당 없음' : number(props.nation.rice) }}</strong>
|
|
|
|
<span class="head">지급률</span>
|
|
<strong class="body">{{ props.nation.id === 0 ? '해당 없음' : `${props.nation.bill}%` }}</strong>
|
|
<span class="head">세율</span>
|
|
<strong class="body">{{ props.nation.id === 0 ? '해당 없음' : `${props.nation.taxRate}%` }}</strong>
|
|
|
|
<span class="head">속령</span>
|
|
<strong class="body">{{
|
|
props.nation.id === 0 ? '해당 없음' : number(props.nation.population.cityCount)
|
|
}}</strong>
|
|
<span class="head">장수</span>
|
|
<strong class="body">{{
|
|
props.nation.id === 0 ? '해당 없음' : number(props.nation.crew.generalCount)
|
|
}}</strong>
|
|
|
|
<span class="head">국력</span>
|
|
<strong class="body">{{ props.nation.id === 0 ? '해당 없음' : number(props.nation.power) }}</strong>
|
|
<span class="head">기술력</span>
|
|
<strong class="body">
|
|
<template v-if="props.nation.id === 0">해당 없음</template>
|
|
<template v-else>
|
|
{{ props.nation.techLevel }}등급 /
|
|
<span :class="props.nation.techLimited ? 'tech-limited' : 'available'">{{
|
|
number(Math.floor(props.nation.tech))
|
|
}}</span>
|
|
</template>
|
|
</strong>
|
|
|
|
<span class="head">전략</span>
|
|
<strong
|
|
class="body strategic"
|
|
:class="{ 'has-tooltip': props.nation.impossibleStrategicCommands.length > 0 }"
|
|
:tabindex="props.nation.impossibleStrategicCommands.length > 0 ? 0 : undefined"
|
|
>
|
|
<template v-if="props.nation.id === 0">해당 없음</template>
|
|
<span v-else-if="props.nation.strategicCommandLimit" class="blocked"
|
|
>{{ number(props.nation.strategicCommandLimit) }}턴</span
|
|
>
|
|
<span v-else :class="props.nation.impossibleStrategicCommands.length > 0 ? 'warning' : 'available'"
|
|
>가능</span
|
|
>
|
|
<span
|
|
v-if="props.nation.impossibleStrategicCommands.length > 0"
|
|
class="cooldown-tooltip"
|
|
role="tooltip"
|
|
>
|
|
<span v-for="command in props.nation.impossibleStrategicCommands" :key="command.name">
|
|
{{ command.name }}: {{ number(command.remainingTurns) }}턴 뒤({{ command.availableYear }}년
|
|
{{ command.availableMonth }}월부터)
|
|
</span>
|
|
</span>
|
|
</strong>
|
|
<span class="head">외교</span>
|
|
<strong class="body">
|
|
<template v-if="props.nation.id === 0">해당 없음</template>
|
|
<span v-else-if="props.nation.diplomaticLimit" class="blocked"
|
|
>{{ number(props.nation.diplomaticLimit) }}턴</span
|
|
>
|
|
<span v-else class="available">가능</span>
|
|
</strong>
|
|
|
|
<span class="head">임관</span>
|
|
<strong class="body">
|
|
<template v-if="props.nation.id === 0">해당 없음</template>
|
|
<span v-else :class="props.nation.prohibitScout ? 'blocked' : 'available'">{{
|
|
props.nation.prohibitScout ? '금지' : '허가'
|
|
}}</span>
|
|
</strong>
|
|
<span class="head">전쟁</span>
|
|
<strong class="body">
|
|
<template v-if="props.nation.id === 0">해당 없음</template>
|
|
<span v-else :class="props.nation.prohibitWar ? 'blocked' : 'available'">{{
|
|
props.nation.prohibitWar ? '금지' : '허가'
|
|
}}</span>
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.nation-card {
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
min-width: 0;
|
|
height: 193px;
|
|
color: #fff;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.nation-grid {
|
|
display: grid;
|
|
box-sizing: border-box;
|
|
width: 100%;
|
|
height: 193px;
|
|
grid-template-columns: 14% 36% 14% 36%;
|
|
grid-template-rows: repeat(10, calc(192px / 10));
|
|
border-right: 1px solid gray;
|
|
border-bottom: 1px solid gray;
|
|
background-color: #172a52;
|
|
background-image: var(--sammo-texture-blue);
|
|
}
|
|
|
|
.nation-grid > * {
|
|
box-sizing: border-box;
|
|
min-width: 0;
|
|
border-top: 1px solid gray;
|
|
border-left: 1px solid gray;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
line-height: calc(193px / 10);
|
|
text-align: center;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.title {
|
|
grid-column: 1 / 5;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.head {
|
|
background-color: rgb(20 75 42 / 70%);
|
|
}
|
|
|
|
.body {
|
|
position: relative;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.type-body {
|
|
grid-column: 2 / 5;
|
|
}
|
|
|
|
.pros {
|
|
color: cyan;
|
|
}
|
|
|
|
.cons,
|
|
.tech-limited {
|
|
color: magenta;
|
|
}
|
|
|
|
.blocked {
|
|
color: red;
|
|
}
|
|
|
|
.available {
|
|
color: limegreen;
|
|
}
|
|
|
|
.warning {
|
|
color: yellow;
|
|
}
|
|
|
|
.strategic.has-tooltip {
|
|
overflow: visible;
|
|
}
|
|
|
|
.cooldown-tooltip {
|
|
position: absolute;
|
|
z-index: 20;
|
|
bottom: calc(100% + 3px);
|
|
left: 50%;
|
|
display: none;
|
|
width: max-content;
|
|
max-width: 280px;
|
|
transform: translateX(-50%);
|
|
border: 1px solid #888;
|
|
padding: 4px 7px;
|
|
background: #111;
|
|
color: #fff;
|
|
line-height: 1.35;
|
|
text-align: left;
|
|
white-space: normal;
|
|
}
|
|
|
|
.cooldown-tooltip > span {
|
|
display: block;
|
|
}
|
|
|
|
.has-tooltip:hover .cooldown-tooltip,
|
|
.has-tooltip:focus .cooldown-tooltip {
|
|
display: block;
|
|
}
|
|
|
|
.loading,
|
|
.empty {
|
|
box-sizing: border-box;
|
|
min-height: 193px;
|
|
padding: 8px;
|
|
}
|
|
|
|
.empty {
|
|
color: rgba(232, 221, 196, 0.6);
|
|
}
|
|
</style>
|