refactor(game-ui): share Ref-style general panel
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
import { basename, resolve } from 'node:path';
|
import { basename, resolve } from 'node:path';
|
||||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
import { expect, test, type Locator, type Page, type Route } from '@playwright/test';
|
||||||
import { gameBasePath, gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
import { gameBasePath, gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
||||||
|
|
||||||
const response = (data: unknown) => ({ result: { data } });
|
const response = (data: unknown) => ({ result: { data } });
|
||||||
@@ -20,6 +20,23 @@ const persistParityArtifact = async (page: Page, name: string, geometry: unknown
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readGeneralPanelImages = async (panel: Locator) =>
|
||||||
|
panel.evaluate((element) =>
|
||||||
|
[...element.querySelectorAll<HTMLElement>('.general-image')].map((image) => {
|
||||||
|
const rect = image.getBoundingClientRect();
|
||||||
|
const style = getComputedStyle(image);
|
||||||
|
return {
|
||||||
|
label: image.getAttribute('aria-label'),
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
backgroundImage: style.backgroundImage,
|
||||||
|
backgroundSize: style.backgroundSize,
|
||||||
|
pointerEvents: style.pointerEvents,
|
||||||
|
userSelect: style.userSelect,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
type FixtureState = {
|
type FixtureState = {
|
||||||
permission: 'head' | 'member';
|
permission: 'head' | 'member';
|
||||||
myset: number;
|
myset: number;
|
||||||
@@ -545,9 +562,15 @@ test('재야 메인은 국가 틀과 성격·특기 표기명을 Chromium에 표
|
|||||||
|
|
||||||
const generalCard = page.locator('.general-card');
|
const generalCard = page.locator('.general-card');
|
||||||
await expect(generalCard).toContainText('성격안전');
|
await expect(generalCard).toContainText('성격안전');
|
||||||
await expect(generalCard).toContainText('전투특기신산');
|
await expect(generalCard).toContainText('특기상재 / 신산');
|
||||||
await expect(generalCard).toContainText('내정특기상재');
|
|
||||||
await expect(generalCard).not.toContainText('che_');
|
await expect(generalCard).not.toContainText('che_');
|
||||||
|
await expect(generalCard).toHaveAttribute('data-general-basic-card', '');
|
||||||
|
const mainImages = await readGeneralPanelImages(generalCard);
|
||||||
|
expect(mainImages).toHaveLength(2);
|
||||||
|
expect(mainImages[0]).toMatchObject({ width: 64, height: 64, pointerEvents: 'none', userSelect: 'none' });
|
||||||
|
expect(mainImages[0]?.backgroundImage).toContain('/icons/default.jpg');
|
||||||
|
expect(mainImages[1]).toMatchObject({ width: 64, height: 64, pointerEvents: 'none', userSelect: 'none' });
|
||||||
|
expect(mainImages[1]?.backgroundImage).toContain('/game/crewtype1.png');
|
||||||
|
|
||||||
const geometry = await nationCard.evaluate((element) => {
|
const geometry = await nationCard.evaluate((element) => {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
@@ -587,9 +610,9 @@ test('메인 카드의 국가·수도·관직·계급·병종은 Ref 출력명
|
|||||||
await expect(nationCard).toContainText('국가 등급주자사');
|
await expect(nationCard).toContainText('국가 등급주자사');
|
||||||
|
|
||||||
const generalCard = page.locator('.general-card');
|
const generalCard = page.locator('.general-card');
|
||||||
await expect(generalCard.locator('.general-title')).toContainText('검증장수 · 간의대부');
|
await expect(generalCard.locator('.general-title')).toContainText('검증장수 【 간의대부 | 건강 】');
|
||||||
await expect(generalCard).toContainText('병종보병');
|
await expect(generalCard).toContainText('병종보병');
|
||||||
await expect(generalCard).toContainText('계급29품관');
|
await expect(generalCard).toContainText('계급 29품관');
|
||||||
|
|
||||||
const cityCard = page.locator('.city-card');
|
const cityCard = page.locator('.city-card');
|
||||||
await expect(cityCard.locator('.title')).toContainText('【중원 | 특】 업');
|
await expect(cityCard.locator('.title')).toContainText('【중원 | 특】 업');
|
||||||
@@ -651,6 +674,14 @@ test('내 정보&설정 keeps the legacy 1000px/500px geometry and saves in plac
|
|||||||
await install(page, state);
|
await install(page, state);
|
||||||
await page.setViewportSize({ width: 1000, height: 900 });
|
await page.setViewportSize({ width: 1000, height: 900 });
|
||||||
await page.goto('my-page');
|
await page.goto('my-page');
|
||||||
|
await expect(page.locator('.general-table')).toHaveAttribute('data-general-basic-card', '');
|
||||||
|
const myPageImages = await readGeneralPanelImages(page.locator('.general-table'));
|
||||||
|
expect(myPageImages.map(({ width, height }) => ({ width, height }))).toEqual([
|
||||||
|
{ width: 64, height: 64 },
|
||||||
|
{ width: 64, height: 64 },
|
||||||
|
]);
|
||||||
|
expect(myPageImages[0]?.backgroundImage).toContain('/icons/default.jpg');
|
||||||
|
expect(myPageImages[1]?.backgroundImage).toContain('/game/crewtype1.png');
|
||||||
await expect(page.locator('.legacy-general-details')).toContainText('계급 29품관');
|
await expect(page.locator('.legacy-general-details')).toContainText('계급 29품관');
|
||||||
await expect(page.locator('.legacy-general-details')).toContainText('병종 보병');
|
await expect(page.locator('.legacy-general-details')).toContainText('병종 보병');
|
||||||
await expect(page.locator('.item-group')).toContainText('명마');
|
await expect(page.locator('.item-group')).toContainText('명마');
|
||||||
@@ -1109,10 +1140,15 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('8');
|
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('8');
|
||||||
await page.getByRole('button', { name: '다음 ▶' }).click();
|
await page.getByRole('button', { name: '다음 ▶' }).click();
|
||||||
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('7');
|
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('7');
|
||||||
await expect(page.locator('.battle-general-name')).toContainText('검증장수 (간의대부)');
|
await expect(page.locator('.battle-general-name')).toContainText('검증장수 【 간의대부 | 건강 】');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('병종보병');
|
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
||||||
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
||||||
|
await expect(page.locator('.battle-general-card')).toHaveAttribute('data-general-basic-card', '');
|
||||||
|
const battleImages = await readGeneralPanelImages(page.locator('.battle-general-card'));
|
||||||
|
expect(battleImages).toHaveLength(2);
|
||||||
|
expect(battleImages[0]?.backgroundImage).toContain('/icons/default.jpg');
|
||||||
|
expect(battleImages[1]?.backgroundImage).toContain('/game/crewtype1.png');
|
||||||
await expect(page.locator('.battle-general-card [role="progressbar"]')).toHaveCount(14);
|
await expect(page.locator('.battle-general-card [role="progressbar"]')).toHaveCount(14);
|
||||||
await expect(page.locator('.battle-general-card [aria-label*="1,275,975 (EX+)"]')).toHaveCount(5);
|
await expect(page.locator('.battle-general-card [aria-label*="1,275,975 (EX+)"]')).toHaveCount(5);
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import SkeletonLines from '../ui/SkeletonLines.vue';
|
import SkeletonLines from '../ui/SkeletonLines.vue';
|
||||||
import LegacyProgressBar from '../ui/LegacyProgressBar.vue';
|
import LegacyProgressBar from '../ui/LegacyProgressBar.vue';
|
||||||
import { formatSeoulHourMinute } from '../../utils/legacyDateTime';
|
import { formatSeoulHourMinute } from '../../utils/legacyDateTime';
|
||||||
import { legacyExperiencePercent, ratioPercent } from '../../utils/legacyProgress';
|
import { legacyExperiencePercent, ratioPercent } from '../../utils/legacyProgress';
|
||||||
|
import { DEFAULT_GENERAL_ICON_URL, resolveGeneralIconBackgroundImage } from '../../utils/generalIcon';
|
||||||
|
import { configuredGameAssetUrl } from '../../utils/imageAssets';
|
||||||
|
|
||||||
interface GeneralStats {
|
interface GeneralStats {
|
||||||
leadership: number;
|
leadership: number;
|
||||||
@@ -19,9 +22,18 @@ interface GeneralProgression {
|
|||||||
statUpgradeLimit?: number;
|
statUpgradeLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ItemDisplayNames {
|
||||||
|
horse?: string | null;
|
||||||
|
weapon?: string | null;
|
||||||
|
book?: string | null;
|
||||||
|
item?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface GeneralInfo {
|
interface GeneralInfo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
picture?: string | null;
|
||||||
|
imageServer?: number | null;
|
||||||
npcState: number;
|
npcState: number;
|
||||||
officerLevel: number;
|
officerLevel: number;
|
||||||
officerLevelText: string;
|
officerLevelText: string;
|
||||||
@@ -35,17 +47,36 @@ interface GeneralInfo {
|
|||||||
experience: number;
|
experience: number;
|
||||||
dedication: number;
|
dedication: number;
|
||||||
age?: number;
|
age?: number;
|
||||||
turnTime?: string;
|
turnTime?: string | null;
|
||||||
|
troopId?: number;
|
||||||
crewTypeId?: number;
|
crewTypeId?: number;
|
||||||
crewTypeName?: string;
|
crewTypeName?: string;
|
||||||
traits?: { personal: string; specialWar: string; specialDomestic: string };
|
traits?: { personal: string; specialWar: string; specialDomestic: string };
|
||||||
progression?: GeneralProgression;
|
progression?: GeneralProgression;
|
||||||
|
itemNames?: ItemDisplayNames;
|
||||||
|
equipmentNames?: ItemDisplayNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
general: GeneralInfo | null;
|
defineProps<{
|
||||||
loading: boolean;
|
general: GeneralInfo | null;
|
||||||
}>();
|
loading: boolean;
|
||||||
|
nationColor?: string | null;
|
||||||
|
defenceText?: string | null;
|
||||||
|
killTurn?: number | null;
|
||||||
|
remainingMinutes?: number | null;
|
||||||
|
troopText?: string | null;
|
||||||
|
penaltyText?: string | number | null;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
nationColor: '#173d27',
|
||||||
|
defenceText: null,
|
||||||
|
killTurn: null,
|
||||||
|
remainingMinutes: null,
|
||||||
|
troopText: null,
|
||||||
|
penaltyText: null,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const statRows = computed(() => {
|
const statRows = computed(() => {
|
||||||
const general = props.general;
|
const general = props.general;
|
||||||
@@ -72,137 +103,320 @@ const statRows = computed(() => {
|
|||||||
const experiencePercent = computed(() =>
|
const experiencePercent = computed(() =>
|
||||||
legacyExperiencePercent(props.general?.experience ?? 0, props.general?.progression?.experienceLevel ?? 0)
|
legacyExperiencePercent(props.general?.experience ?? 0, props.general?.progression?.experienceLevel ?? 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const itemNames = computed<ItemDisplayNames>(() => props.general?.itemNames ?? props.general?.equipmentNames ?? {});
|
||||||
|
|
||||||
|
const generalIconBackground = computed(() => resolveGeneralIconBackgroundImage(props.general ?? {}));
|
||||||
|
|
||||||
|
const crewTypeIconBackground = computed(() => {
|
||||||
|
const crewTypeId = props.general?.crewTypeId;
|
||||||
|
if (crewTypeId === undefined || !Number.isFinite(crewTypeId)) {
|
||||||
|
return `url(${JSON.stringify(DEFAULT_GENERAL_ICON_URL)})`;
|
||||||
|
}
|
||||||
|
const crewTypeUrl = `${configuredGameAssetUrl()}/crewtype${Math.trunc(crewTypeId)}.png`;
|
||||||
|
return `url(${JSON.stringify(crewTypeUrl)}), url(${JSON.stringify(DEFAULT_GENERAL_ICON_URL)})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const injuryInfo = computed(() => {
|
||||||
|
const injury = props.general?.injury ?? 0;
|
||||||
|
if (injury > 60) return { text: '위독', color: '#ff4d4f' };
|
||||||
|
if (injury > 40) return { text: '심각', color: '#ff00ff' };
|
||||||
|
if (injury > 20) return { text: '중상', color: '#ff9f1a' };
|
||||||
|
if (injury > 0) return { text: '경상', color: '#ffff00' };
|
||||||
|
return { text: '건강', color: '#ffffff' };
|
||||||
|
});
|
||||||
|
|
||||||
|
const isBrightColor = (color: string): boolean => {
|
||||||
|
const normalized = /^#[0-9a-f]{6}$/iu.test(color) ? color.slice(1) : '173d27';
|
||||||
|
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
||||||
|
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
||||||
|
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
||||||
|
return (red * 299 + green * 587 + blue * 114) / 1000 >= 150;
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleStyle = computed(() => {
|
||||||
|
const backgroundColor = props.nationColor || '#173d27';
|
||||||
|
return {
|
||||||
|
backgroundColor,
|
||||||
|
color: isBrightColor(backgroundColor) ? '#000000' : '#ffffff',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const ageColor = computed(() => {
|
||||||
|
const age = props.general?.age;
|
||||||
|
if (age === undefined) return '#ffffff';
|
||||||
|
if (age < 53) return '#32cd32';
|
||||||
|
if (age < 70) return '#ffff00';
|
||||||
|
return '#ff4d4f';
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayTroop = computed(() => props.troopText ?? (props.general?.troopId ? String(props.general.troopId) : '-'));
|
||||||
|
const displayPenalty = computed(() => {
|
||||||
|
const penalty = props.penaltyText ?? '-';
|
||||||
|
const dedication = props.general?.progression?.dedicationText ?? '무품관';
|
||||||
|
return `${penalty} · 계급 ${dedication}`;
|
||||||
|
});
|
||||||
|
const displayDefence = computed(() => props.defenceText ?? '-');
|
||||||
|
const specialText = computed(() => {
|
||||||
|
const traits = props.general?.traits;
|
||||||
|
return traits ? `${traits.specialDomestic || '-'} / ${traits.specialWar || '-'}` : '-';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="general-card">
|
<div class="general-card" data-general-basic-card>
|
||||||
<div v-if="props.loading">
|
<div v-if="props.loading" class="general-loading">
|
||||||
<SkeletonLines :lines="5" />
|
<SkeletonLines :lines="5" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!props.general" class="empty">장수 정보를 불러오지 못했습니다.</div>
|
<div v-else-if="!props.general" class="empty">장수 정보를 불러오지 못했습니다.</div>
|
||||||
<div v-else class="general-body">
|
<template v-else>
|
||||||
<div class="general-title">
|
<div class="general-basic-grid general-body">
|
||||||
{{ props.general.name }} · {{ props.general.officerLevelText }} · {{ props.general.age ?? '-' }}세 ·
|
<span
|
||||||
다음 턴
|
class="general-image general-icon"
|
||||||
{{ props.general.turnTime ? formatSeoulHourMinute(props.general.turnTime) : '-' }}
|
role="img"
|
||||||
</div>
|
:aria-label="`${props.general.name} 초상`"
|
||||||
|
:style="{ backgroundImage: generalIconBackground }"
|
||||||
|
/>
|
||||||
|
<div class="general-title battle-general-name" :style="titleStyle">
|
||||||
|
{{ props.general.name }} 【 {{ props.general.officerLevelText }} |
|
||||||
|
<span :style="{ color: injuryInfo.color }">{{ injuryInfo.text }}</span> 】 다음 턴
|
||||||
|
{{ props.general.turnTime ? formatSeoulHourMinute(props.general.turnTime) : '-' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="stat-progress-grid">
|
|
||||||
<template v-for="stat of statRows" :key="stat.key">
|
<template v-for="stat of statRows" :key="stat.key">
|
||||||
<span class="cell-label">{{ stat.label }}</span>
|
<span class="cell-label">{{ stat.label }}</span>
|
||||||
<strong>{{ stat.value }}</strong>
|
<strong class="stat-value">
|
||||||
<div class="bar-cell" :data-stat-progress="stat.key">
|
<span>{{ stat.value }}</span>
|
||||||
<LegacyProgressBar
|
<span class="bar-cell" :data-stat-progress="stat.key">
|
||||||
:percent="stat.percent"
|
<LegacyProgressBar
|
||||||
:label="`${stat.label} 성장 ${stat.accumulated} / ${stat.limit}`"
|
:percent="stat.percent"
|
||||||
/>
|
:label="`${stat.label} 성장 ${stat.accumulated} / ${stat.limit}`"
|
||||||
</div>
|
/>
|
||||||
|
</span>
|
||||||
|
</strong>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="legacy-grid">
|
<span class="cell-label">명마</span><strong>{{ itemNames.horse ?? '-' }}</strong>
|
||||||
<span>자금</span><strong>{{ props.general.gold.toLocaleString() }}</strong> <span>군량</span
|
<span class="cell-label">무기</span><strong>{{ itemNames.weapon ?? '-' }}</strong>
|
||||||
><strong>{{ props.general.rice.toLocaleString() }}</strong> <span>병력</span
|
<span class="cell-label">서적</span><strong>{{ itemNames.book ?? '-' }}</strong>
|
||||||
><strong>{{ props.general.crew.toLocaleString() }}</strong> <span>훈련</span
|
|
||||||
><strong>{{ props.general.train }}</strong> <span>사기</span><strong>{{ props.general.atmos }}</strong>
|
|
||||||
<span>부상</span><strong>{{ props.general.injury }}</strong> <span>병종</span
|
|
||||||
><strong>{{ props.general.crewTypeName ?? '-' }}</strong> <span>성격</span
|
|
||||||
><strong>{{ props.general.traits?.personal ?? '-' }}</strong> <span>전투특기</span
|
|
||||||
><strong>{{ props.general.traits?.specialWar ?? '-' }}</strong> <span>내정특기</span
|
|
||||||
><strong>{{ props.general.traits?.specialDomestic ?? '-' }}</strong> <span>계급</span
|
|
||||||
><strong>{{ props.general.progression?.dedicationText ?? '무품관' }}</strong> <span>공헌</span
|
|
||||||
><strong>{{ props.general.dedication.toLocaleString() }}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-row">
|
<span
|
||||||
<span class="cell-label">Lv</span>
|
class="general-image general-crew-type-icon"
|
||||||
<strong>{{ props.general.progression?.experienceLevel ?? 0 }}</strong>
|
role="img"
|
||||||
<div class="bar-cell" data-experience-progress>
|
:aria-label="`${props.general.crewTypeName ?? '병종'} 이미지`"
|
||||||
|
:style="{ backgroundImage: crewTypeIconBackground }"
|
||||||
|
/>
|
||||||
|
<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>{{ props.general.crewTypeName ?? '-' }}</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>{{ 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 level-label">Lv</span>
|
||||||
|
<strong class="level-value">{{ props.general.progression?.experienceLevel ?? 0 }}</strong>
|
||||||
|
<span class="experience-bar" data-experience-progress>
|
||||||
<LegacyProgressBar
|
<LegacyProgressBar
|
||||||
:percent="experiencePercent"
|
:percent="experiencePercent"
|
||||||
:label="`경험 레벨 진행 ${experiencePercent.toFixed(1)}%`"
|
:label="`경험 레벨 진행 ${experiencePercent.toFixed(1)}%`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</span>
|
||||||
<span class="experience-total">명성 {{ props.general.experience.toLocaleString() }}</span>
|
<span class="cell-label age-label">연령</span>
|
||||||
|
<strong class="age-value" :style="{ color: ageColor }">{{ props.general.age ?? '-' }}세</strong>
|
||||||
|
|
||||||
|
<span class="cell-label defence-label">수비</span>
|
||||||
|
<strong class="defence-value">{{ displayDefence }}</strong>
|
||||||
|
<span class="cell-label kill-label">삭턴</span>
|
||||||
|
<strong class="kill-value">{{ props.killTurn === null ? '-' : `${props.killTurn} 턴` }}</strong>
|
||||||
|
<span class="cell-label execute-label">실행</span>
|
||||||
|
<strong class="execute-value">{{
|
||||||
|
props.remainingMinutes === null ? '-' : `${props.remainingMinutes}분 남음`
|
||||||
|
}}</strong>
|
||||||
|
|
||||||
|
<span class="cell-label troop-label">부대</span>
|
||||||
|
<strong class="troop-value">{{ displayTroop }}</strong>
|
||||||
|
<span class="cell-label penalty-label">벌점</span>
|
||||||
|
<strong class="penalty-value">{{ displayPenalty }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<slot name="details" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.general-title {
|
.general-card {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 20px;
|
width: 100%;
|
||||||
min-height: 20px;
|
min-width: 0;
|
||||||
padding: 1px 6px;
|
|
||||||
border-bottom: 1px solid #777;
|
|
||||||
background: #173d27;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-progress-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(30px, 1fr) minmax(34px, 1fr) 45px);
|
|
||||||
grid-auto-rows: 21px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-progress-grid > *,
|
|
||||||
.legacy-grid > * {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 21px;
|
|
||||||
min-height: 0;
|
|
||||||
border-right: 1px solid #666;
|
|
||||||
border-bottom: 1px solid #666;
|
|
||||||
padding: 2px 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: #172a52;
|
||||||
|
background-image: var(--sammo-texture-blue);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-basic-grid {
|
||||||
|
display: grid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
grid-template-columns: 64px repeat(3, minmax(30px, 2fr) minmax(60px, 5fr));
|
||||||
|
grid-template-rows: repeat(9, calc(64px / 3));
|
||||||
|
border-right: 1px solid #777;
|
||||||
|
border-bottom: 1px solid #777;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-basic-grid > * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
border-top: 1px solid #777;
|
||||||
|
border-left: 1px solid #777;
|
||||||
|
padding: 1px 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-label,
|
.general-basic-grid > strong {
|
||||||
.legacy-grid > span {
|
font-weight: 500;
|
||||||
background: rgb(20 75 42 / 70%);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-progress-grid > strong,
|
.cell-label {
|
||||||
.legacy-grid > strong {
|
background-color: rgb(20 75 42 / 70%);
|
||||||
text-align: right;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar-cell {
|
.general-image {
|
||||||
|
display: block;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
padding: 0;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-icon {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-title {
|
||||||
|
grid-column: 2 / 8;
|
||||||
|
grid-row: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(22px, auto) minmax(26px, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-cell,
|
||||||
|
.experience-bar {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 0 1px;
|
padding: 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legacy-grid {
|
.general-crew-type-icon {
|
||||||
display: grid;
|
grid-column: 1;
|
||||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
grid-row: 4 / 7;
|
||||||
grid-auto-rows: 21px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-row {
|
.level-label {
|
||||||
display: grid;
|
grid-column: 1;
|
||||||
box-sizing: border-box;
|
grid-row: 7;
|
||||||
grid-template-columns: 32px 38px minmax(120px, 1fr) 112px;
|
|
||||||
height: 20px;
|
|
||||||
min-height: 20px;
|
|
||||||
border-bottom: 1px solid #666;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-row > * {
|
.level-value {
|
||||||
display: grid;
|
grid-column: 2;
|
||||||
align-content: center;
|
grid-row: 7;
|
||||||
box-sizing: border-box;
|
}
|
||||||
border-right: 1px solid #666;
|
|
||||||
padding: 1px 4px;
|
.experience-bar {
|
||||||
text-align: center;
|
grid-column: 3 / 6;
|
||||||
|
grid-row: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-label {
|
||||||
|
grid-column: 6;
|
||||||
|
grid-row: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-value {
|
||||||
|
grid-column: 7;
|
||||||
|
grid-row: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defence-label {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defence-value {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kill-label {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kill-value {
|
||||||
|
grid-column: 5;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.execute-label {
|
||||||
|
grid-column: 6;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.execute-value {
|
||||||
|
grid-column: 7;
|
||||||
|
grid-row: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troop-label {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troop-value {
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
grid-row: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.penalty-label {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.penalty-value {
|
||||||
|
grid-column: 5 / 8;
|
||||||
|
grid-row: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-loading,
|
||||||
|
.empty {
|
||||||
|
min-height: 192px;
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ type GeneralProgress = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<{ general: GeneralProgress }>();
|
const props = withDefaults(defineProps<{ general: GeneralProgress; showPrimary?: boolean }>(), {
|
||||||
|
showPrimary: true,
|
||||||
|
});
|
||||||
|
|
||||||
const statRows = computed(() =>
|
const statRows = computed(() =>
|
||||||
[
|
[
|
||||||
@@ -50,7 +52,7 @@ const experiencePercent = computed(() =>
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="legacy-general-progress">
|
<div class="legacy-general-progress">
|
||||||
<div class="stat-grid">
|
<div v-if="props.showPrimary" class="stat-grid">
|
||||||
<template v-for="stat of statRows" :key="stat.key">
|
<template v-for="stat of statRows" :key="stat.key">
|
||||||
<span class="cell-label">{{ stat.label }}</span>
|
<span class="cell-label">{{ stat.label }}</span>
|
||||||
<strong>{{ stat.value }}</strong>
|
<strong>{{ stat.value }}</strong>
|
||||||
@@ -60,7 +62,7 @@ const experiencePercent = computed(() =>
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="experience-row">
|
<div v-if="props.showPrimary" class="experience-row">
|
||||||
<span class="cell-label">Lv</span>
|
<span class="cell-label">Lv</span>
|
||||||
<strong>{{ props.general.progression.experienceLevel }}</strong>
|
<strong>{{ props.general.progression.experienceLevel }}</strong>
|
||||||
<LegacyProgressBar
|
<LegacyProgressBar
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useRoute } from 'vue-router';
|
|||||||
import PanelCard from '../components/ui/PanelCard.vue';
|
import PanelCard from '../components/ui/PanelCard.vue';
|
||||||
import SkeletonLines from '../components/ui/SkeletonLines.vue';
|
import SkeletonLines from '../components/ui/SkeletonLines.vue';
|
||||||
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
|
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
|
||||||
|
import GeneralBasicCard from '../components/main/GeneralBasicCard.vue';
|
||||||
import { trpc } from '../utils/trpc';
|
import { trpc } from '../utils/trpc';
|
||||||
import { getNpcColor } from '../utils/npcColor';
|
import { getNpcColor } from '../utils/npcColor';
|
||||||
import { formatLog } from '../utils/formatLog';
|
import { formatLog } from '../utils/formatLog';
|
||||||
import { resolveGeneralIconUrl } from '../utils/generalIcon';
|
|
||||||
|
|
||||||
type BattleCenterResponse = Awaited<ReturnType<typeof trpc.nation.getBattleCenter.query>>;
|
type BattleCenterResponse = Awaited<ReturnType<typeof trpc.nation.getBattleCenter.query>>;
|
||||||
type GeneralEntry = BattleCenterResponse['generals'][number];
|
type GeneralEntry = BattleCenterResponse['generals'][number];
|
||||||
@@ -136,8 +136,6 @@ const formatGeneralLabel = (general: GeneralEntry): string => {
|
|||||||
return `${name} (${time})`;
|
return `${name} (${time})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generalImageUrl = (general: GeneralEntry): string => resolveGeneralIconUrl(general);
|
|
||||||
|
|
||||||
let logRequestId = 0;
|
let logRequestId = 0;
|
||||||
|
|
||||||
const loadLogs = async (generalId: number) => {
|
const loadLogs = async (generalId: number) => {
|
||||||
@@ -266,47 +264,29 @@ onMounted(() => {
|
|||||||
</PanelCard>
|
</PanelCard>
|
||||||
|
|
||||||
<PanelCard title="장수 정보">
|
<PanelCard title="장수 정보">
|
||||||
<SkeletonLines v-if="loading" :lines="5" />
|
<GeneralBasicCard
|
||||||
<div v-else-if="selectedGeneral" class="battle-general-card">
|
class="battle-general-card"
|
||||||
<div class="battle-general-name">
|
:general="selectedGeneral"
|
||||||
{{ selectedGeneral.name }} ({{ selectedGeneral.officerLevelText }})
|
:loading="loading"
|
||||||
</div>
|
:nation-color="data?.nation.color"
|
||||||
<span
|
>
|
||||||
class="battle-general-portrait"
|
<template v-if="selectedGeneral" #details>
|
||||||
role="img"
|
<div class="battle-general-extra">
|
||||||
:aria-label="`${selectedGeneral.name} 초상`"
|
<span>명성</span
|
||||||
:style="{ backgroundImage: `url(${generalImageUrl(selectedGeneral)})` }"
|
><strong>{{ selectedGeneral.experience.toLocaleString('ko-KR') }}</strong>
|
||||||
/>
|
<span>계급</span><strong>{{ selectedGeneral.progression.dedicationText }}</strong>
|
||||||
<div class="battle-general-grid">
|
<span>전투</span><strong>{{ selectedGeneral.warnum }}회</strong> <span>승리</span
|
||||||
<span>통솔</span><strong>{{ selectedGeneral.stats.leadership }}</strong> <span>무력</span
|
><strong>{{ selectedGeneral.battleStats.kills }}</strong> <span>패배</span
|
||||||
><strong>{{ selectedGeneral.stats.strength }}</strong> <span>지력</span
|
><strong>{{ selectedGeneral.battleStats.deaths }}</strong> <span>계략</span
|
||||||
><strong>{{ selectedGeneral.stats.intelligence }}</strong> <span>자금</span
|
><strong>{{ selectedGeneral.battleStats.fire }}</strong> <span>사살</span
|
||||||
><strong>{{ selectedGeneral.gold }}</strong> <span>군량</span
|
><strong>{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }}</strong>
|
||||||
><strong>{{ selectedGeneral.rice }}</strong> <span>병력</span
|
<span>피살</span
|
||||||
><strong>{{ selectedGeneral.crew }}</strong> <span>훈련</span
|
><strong>{{ selectedGeneral.battleStats.deathCrew.toLocaleString('ko-KR') }}</strong>
|
||||||
><strong>{{ selectedGeneral.train }}</strong> <span>사기</span
|
<span>최근 전투</span><strong>{{ selectedGeneral.recentWar || '-' }}</strong>
|
||||||
><strong>{{ selectedGeneral.atmos }}</strong> <span>부상</span
|
</div>
|
||||||
><strong>{{ selectedGeneral.injury }}</strong> <span>경험</span
|
<LegacyGeneralProgress :general="selectedGeneral" :show-primary="false" />
|
||||||
><strong>{{ selectedGeneral.experience }}</strong> <span>공헌</span
|
</template>
|
||||||
><strong>{{ selectedGeneral.dedication }}</strong> <span>전투</span
|
</GeneralBasicCard>
|
||||||
><strong>{{ selectedGeneral.warnum }}회</strong>
|
|
||||||
</div>
|
|
||||||
<div class="battle-general-extra">
|
|
||||||
<span>명성</span><strong>{{ selectedGeneral.experience.toLocaleString('ko-KR') }}</strong>
|
|
||||||
<span>계급</span><strong>{{ selectedGeneral.progression.dedicationText }}</strong>
|
|
||||||
<span>나이</span><strong>{{ selectedGeneral.age }}세</strong> <span>병종</span
|
|
||||||
><strong>{{ selectedGeneral.crewTypeName }}</strong> <span>승리</span
|
|
||||||
><strong>{{ selectedGeneral.battleStats.kills }}</strong> <span>패배</span
|
|
||||||
><strong>{{ selectedGeneral.battleStats.deaths }}</strong> <span>사살</span
|
|
||||||
><strong>{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }}</strong>
|
|
||||||
<span>피살</span
|
|
||||||
><strong>{{ selectedGeneral.battleStats.deathCrew.toLocaleString('ko-KR') }}</strong>
|
|
||||||
<span>전투 특기</span><strong>{{ selectedGeneral.traits.specialWar }}</strong>
|
|
||||||
<span>내정 특기</span><strong>{{ selectedGeneral.traits.specialDomestic }}</strong>
|
|
||||||
<span>성격</span><strong>{{ selectedGeneral.traits.personal }}</strong>
|
|
||||||
</div>
|
|
||||||
<LegacyGeneralProgress :general="selectedGeneral" />
|
|
||||||
</div>
|
|
||||||
<div v-if="selectedGeneral" class="general-meta">
|
<div v-if="selectedGeneral" class="general-meta">
|
||||||
<div>최근 턴: {{ selectedGeneral.turnTime ? selectedGeneral.turnTime.slice(-5) : '-' }}</div>
|
<div>최근 턴: {{ selectedGeneral.turnTime ? selectedGeneral.turnTime.slice(-5) : '-' }}</div>
|
||||||
<div>최근 전투: {{ selectedGeneral.recentWar || '-' }}</div>
|
<div>최근 전투: {{ selectedGeneral.recentWar || '-' }}</div>
|
||||||
@@ -375,39 +355,7 @@ onMounted(() => {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.battle-general-card {
|
|
||||||
min-height: 292px;
|
|
||||||
position: relative;
|
|
||||||
background-color: #172a52;
|
|
||||||
background-image: var(--sammo-texture-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-portrait {
|
|
||||||
display: block;
|
|
||||||
width: 64px;
|
|
||||||
height: 80px;
|
|
||||||
float: left;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-name {
|
|
||||||
min-height: 24px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 1px solid #777;
|
|
||||||
background: rgba(220, 220, 220, 0.85);
|
|
||||||
color: #111;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(6, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-extra {
|
.battle-general-extra {
|
||||||
clear: both;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(6, 1fr);
|
grid-template-columns: repeat(6, 1fr);
|
||||||
}
|
}
|
||||||
@@ -433,24 +381,6 @@ onMounted(() => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.battle-general-grid > * {
|
|
||||||
min-height: 24px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
border-right: 1px solid #777;
|
|
||||||
border-bottom: 1px solid #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-grid > span {
|
|
||||||
background-color: rgba(20, 75, 42, 0.7);
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-grid > strong {
|
|
||||||
text-align: right;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-grid {
|
.log-grid {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,12 @@ watch(
|
|||||||
<NationBasicCard :nation="nation" :loading="loading" />
|
<NationBasicCard :nation="nation" :loading="loading" />
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
<PanelCard title="장수 스탯" data-main-target="general">
|
<PanelCard title="장수 스탯" data-main-target="general">
|
||||||
<GeneralBasicCard :general="general" :loading="loading" />
|
<GeneralBasicCard
|
||||||
|
:general="general"
|
||||||
|
:loading="loading"
|
||||||
|
:nation-color="nation?.color"
|
||||||
|
:troop-text="general?.troopId ? String(general.troopId) : '-'"
|
||||||
|
/>
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
<PanelCard title="도시 정보" data-main-target="city">
|
<PanelCard title="도시 정보" data-main-target="city">
|
||||||
<CityBasicCard :city="city" :loading="loading" />
|
<CityBasicCard :city="city" :loading="loading" />
|
||||||
@@ -356,7 +361,12 @@ watch(
|
|||||||
<NationBasicCard :nation="nation" :loading="loading" />
|
<NationBasicCard :nation="nation" :loading="loading" />
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
<PanelCard title="장수 스탯" data-main-target="general">
|
<PanelCard title="장수 스탯" data-main-target="general">
|
||||||
<GeneralBasicCard :general="general" :loading="loading" />
|
<GeneralBasicCard
|
||||||
|
:general="general"
|
||||||
|
:loading="loading"
|
||||||
|
:nation-color="nation?.color"
|
||||||
|
:troop-text="general?.troopId ? String(general.troopId) : '-'"
|
||||||
|
/>
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
<MainNationMenu
|
<MainNationMenu
|
||||||
class="nation-menu-middle"
|
class="nation-menu-middle"
|
||||||
@@ -605,12 +615,14 @@ button {
|
|||||||
.layout-desktop > [data-main-target='nation'] {
|
.layout-desktop > [data-main-target='nation'] {
|
||||||
grid-column: 1 / 6;
|
grid-column: 1 / 6;
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
|
align-self: stretch;
|
||||||
min-height: 193px;
|
min-height: 193px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-desktop > [data-main-target='general'] {
|
.layout-desktop > [data-main-target='general'] {
|
||||||
grid-column: 6 / 11;
|
grid-column: 6 / 11;
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
|
align-self: stretch;
|
||||||
min-height: 193px;
|
min-height: 193px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { isDefenceTrainPenaltyWaivedByScenarioEffect } from '@sammo-ts/logic';
|
|||||||
import { useSessionStore } from '../stores/session';
|
import { useSessionStore } from '../stores/session';
|
||||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||||
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
|
import LegacyGeneralProgress from '../components/ui/LegacyGeneralProgress.vue';
|
||||||
|
import GeneralBasicCard from '../components/main/GeneralBasicCard.vue';
|
||||||
import { useGameFeedback } from '../composables/useGameFeedback';
|
import { useGameFeedback } from '../composables/useGameFeedback';
|
||||||
|
|
||||||
const SCREEN_MODE_KEY = 'sam.screenMode';
|
const SCREEN_MODE_KEY = 'sam.screenMode';
|
||||||
@@ -374,91 +375,43 @@ onMounted(() => {
|
|||||||
<section class="top-grid">
|
<section class="top-grid">
|
||||||
<div class="general-column">
|
<div class="general-column">
|
||||||
<div class="section-title sky">장수 정보</div>
|
<div class="section-title sky">장수 정보</div>
|
||||||
<div v-if="loading || !data" class="loading">불러오는 중...</div>
|
<GeneralBasicCard
|
||||||
<div v-else class="general-table">
|
class="general-table"
|
||||||
<div class="portrait-cell">
|
:general="data?.general ?? null"
|
||||||
<span
|
:loading="loading"
|
||||||
class="portrait-image"
|
:nation-color="data?.nation?.color"
|
||||||
role="img"
|
:defence-text="form.defence_train === 999 ? '수비 안함' : `수비 함(훈사${form.defence_train})`"
|
||||||
:style="{ backgroundImage: `url(${resolveGeneralIconUrl(data.general)})` }"
|
:troop-text="data?.general.troopId ? String(data.general.troopId) : '-'"
|
||||||
></span>
|
:penalty-text="penalties.length || '-'"
|
||||||
<strong>{{ data.general.name }}</strong>
|
>
|
||||||
</div>
|
<template v-if="data" #details>
|
||||||
<dl>
|
<div class="legacy-general-details">
|
||||||
<div>
|
<div>
|
||||||
<dt>통솔</dt>
|
명망
|
||||||
<dd>{{ data.general.stats.leadership }}</dd>
|
<strong
|
||||||
|
>Lv {{ data.general.progression?.experienceLevel ?? 0 }} ({{
|
||||||
|
data.general.experience
|
||||||
|
}})</strong
|
||||||
|
>
|
||||||
|
· 계급
|
||||||
|
<strong
|
||||||
|
>{{ data.general.progression?.dedicationText ?? '무품관' }} ({{
|
||||||
|
data.general.dedication
|
||||||
|
}})</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>전투 0 · 계략 0 · 사관 7년</div>
|
||||||
|
<div>승률 0% · 승리 0 · 패배 0</div>
|
||||||
|
<div>살상률 0% · 사살 0 · 피살 0</div>
|
||||||
|
<div>
|
||||||
|
소속 {{ data.nation?.name ?? '재야' }} · 도시 {{ data.city?.name ?? '-' }} · 병종
|
||||||
|
{{ data.general.crewTypeName ?? '-' }} · 내정특기
|
||||||
|
{{ data.general.traits?.specialDomestic ?? '-' }} · 부상 {{ data.general.injury }}
|
||||||
|
</div>
|
||||||
|
<LegacyGeneralProgress :general="data.general" :show-primary="false" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</template>
|
||||||
<dt>무력</dt>
|
</GeneralBasicCard>
|
||||||
<dd>{{ data.general.stats.strength }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>지력</dt>
|
|
||||||
<dd>{{ data.general.stats.intelligence }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>소속</dt>
|
|
||||||
<dd>{{ data.nation?.name ?? '재야' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>도시</dt>
|
|
||||||
<dd>{{ data.city?.name ?? '-' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>금/쌀</dt>
|
|
||||||
<dd>{{ data.general.gold }} / {{ data.general.rice }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>병력</dt>
|
|
||||||
<dd>{{ data.general.crew }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>훈련/사기</dt>
|
|
||||||
<dd>{{ data.general.train }} / {{ data.general.atmos }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>경험/공헌</dt>
|
|
||||||
<dd>{{ data.general.experience }} / {{ data.general.dedication }}</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>성격/특기</dt>
|
|
||||||
<dd>
|
|
||||||
{{ data.general.traits?.personal ?? '-' }} /
|
|
||||||
{{ data.general.traits?.specialWar ?? '-' }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<dt>나이/다음턴</dt>
|
|
||||||
<dd>{{ data.general.age ?? '-' }}세 / {{ data.general.turnTime?.slice(11, 16) ?? '-' }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div v-if="data" class="legacy-general-details">
|
|
||||||
<div>
|
|
||||||
명망
|
|
||||||
<strong
|
|
||||||
>Lv {{ data.general.progression?.experienceLevel ?? 0 }} ({{
|
|
||||||
data.general.experience
|
|
||||||
}})</strong
|
|
||||||
>
|
|
||||||
· 계급
|
|
||||||
<strong
|
|
||||||
>{{ data.general.progression?.dedicationText ?? '무품관' }} ({{
|
|
||||||
data.general.dedication
|
|
||||||
}})</strong
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>전투 0 · 계략 0 · 사관 7년</div>
|
|
||||||
<div>승률 0% · 승리 0 · 패배 0</div>
|
|
||||||
<div>살상률 0% · 사살 0 · 피살 0</div>
|
|
||||||
<div>
|
|
||||||
병종 {{ data.general.crewTypeName ?? '-' }} · 내정특기
|
|
||||||
{{ data.general.traits?.specialDomestic ?? '-' }} · 부상 {{ data.general.injury }} · 부대
|
|
||||||
{{ data.general.troopId || '-' }} · 벌점 {{ penalties.length || '-' }}
|
|
||||||
</div>
|
|
||||||
<LegacyGeneralProgress :general="data.general" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-column">
|
<div class="settings-column">
|
||||||
@@ -786,28 +739,6 @@ button:disabled {
|
|||||||
.sky {
|
.sky {
|
||||||
color: skyblue;
|
color: skyblue;
|
||||||
}
|
}
|
||||||
.general-table {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 150px 1fr;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #172a52;
|
|
||||||
background-image: var(--sammo-texture-blue);
|
|
||||||
}
|
|
||||||
.portrait-cell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 10px;
|
|
||||||
border-right: 1px solid #777;
|
|
||||||
}
|
|
||||||
.portrait-image {
|
|
||||||
display: block;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
.legacy-general-info-compat {
|
.legacy-general-info-compat {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -824,23 +755,6 @@ button:disabled {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
dl {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
dl > div {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 80px 1fr;
|
|
||||||
border-bottom: 1px solid #777;
|
|
||||||
}
|
|
||||||
dt,
|
|
||||||
dd {
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 5px;
|
|
||||||
border-right: 1px solid #777;
|
|
||||||
}
|
|
||||||
dt {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
.settings-column {
|
.settings-column {
|
||||||
padding: 10px 18px;
|
padding: 10px 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user