Complete dynasty list legacy parity
This commit is contained in:
@@ -240,6 +240,7 @@ const routes = [
|
||||
component: YearbookView,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
requiresGeneral: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const lightTextBackgrounds = new Set([
|
||||
'',
|
||||
'#330000',
|
||||
'#FF0000',
|
||||
'#800000',
|
||||
'#A0522D',
|
||||
'#FF6347',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2E8B57',
|
||||
'#008080',
|
||||
'#6495ED',
|
||||
'#0000FF',
|
||||
'#000080',
|
||||
'#483D8B',
|
||||
'#7B68EE',
|
||||
'#800080',
|
||||
'#A9A9A9',
|
||||
'#000000',
|
||||
]);
|
||||
|
||||
export const legacyNationTextColor = (backgroundColor: string): '#FFFFFF' | '#000000' =>
|
||||
lightTextBackgrounds.has(backgroundColor.toUpperCase()) ? '#FFFFFF' : '#000000';
|
||||
@@ -1,66 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { formatLog } from '../utils/formatLog';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type EmperorDetail = {
|
||||
id: number;
|
||||
serverId: string;
|
||||
winnerNationId: number | null;
|
||||
phase: string;
|
||||
nationCount: string;
|
||||
nationName: string;
|
||||
nationHist: string;
|
||||
genCount: string;
|
||||
personalHist: string;
|
||||
specialHist: string;
|
||||
name: string;
|
||||
type: string;
|
||||
color: string;
|
||||
year: number;
|
||||
month: number;
|
||||
power: number;
|
||||
gennum: number;
|
||||
citynum: number;
|
||||
pop: string;
|
||||
poprate: string;
|
||||
gold: number;
|
||||
rice: number;
|
||||
l12name: string;
|
||||
l11name: string;
|
||||
l10name: string;
|
||||
l9name: string;
|
||||
l8name: string;
|
||||
l7name: string;
|
||||
l6name: string;
|
||||
l5name: string;
|
||||
tiger: string;
|
||||
eagle: string;
|
||||
gen: string;
|
||||
history: string[];
|
||||
};
|
||||
|
||||
type NationEntry = {
|
||||
nation: number;
|
||||
isWinner: boolean;
|
||||
name: string;
|
||||
color: string;
|
||||
type: string;
|
||||
level: number | null;
|
||||
tech: number | null;
|
||||
maxPower: number | null;
|
||||
maxCrew: number | null;
|
||||
maxCities: number[];
|
||||
generals: number[];
|
||||
history: string[];
|
||||
date: string;
|
||||
generalsFull: Array<{ generalNo: number; name: string; lastYearMonth: number | null }>;
|
||||
};
|
||||
|
||||
type DynastyDetailPayload = {
|
||||
emperor: EmperorDetail;
|
||||
nations: NationEntry[];
|
||||
};
|
||||
type DynastyDetailPayload = Awaited<ReturnType<typeof trpc.dynasty.getDetail.query>>;
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -72,191 +18,398 @@ const emperorId = computed(() => {
|
||||
const idParam = route.params.id;
|
||||
const raw = Array.isArray(idParam) ? idParam[0] : idParam;
|
||||
const parsed = Number(raw);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
||||
});
|
||||
|
||||
const dynastyApi = trpc as unknown as {
|
||||
dynasty: {
|
||||
getDetail: {
|
||||
query: (input: { emperorId: number }) => Promise<DynastyDetailPayload>;
|
||||
};
|
||||
};
|
||||
const closePage = async (): Promise<void> => {
|
||||
if (window.opener) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
await router.push('/');
|
||||
};
|
||||
|
||||
const loadDetail = async () => {
|
||||
const loadDetail = async (): Promise<void> => {
|
||||
if (emperorId.value === null) {
|
||||
data.value = null;
|
||||
errorMessage.value = '잘못된 왕조 번호입니다.';
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
try {
|
||||
data.value = await dynastyApi.dynasty.getDetail.query({
|
||||
emperorId: emperorId.value,
|
||||
});
|
||||
data.value = await trpc.dynasty.getDetail.query({ emperorId: emperorId.value });
|
||||
} catch (error) {
|
||||
data.value = null;
|
||||
errorMessage.value = error instanceof Error ? error.message : '왕조 정보를 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(emperorId, () => {
|
||||
void loadDetail();
|
||||
});
|
||||
const formatArchiveDate = (value: string): string =>
|
||||
new Intl.DateTimeFormat('sv-SE', {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'medium',
|
||||
timeZone: 'UTC',
|
||||
}).format(new Date(value));
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDetail();
|
||||
});
|
||||
watch(emperorId, loadDetail);
|
||||
onMounted(loadDetail);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="main-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">왕조 상세</h1>
|
||||
<p class="page-subtitle">통일 세력과 멸망한 국가의 기록입니다.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="ghost" @click="router.push('/dynasty')">목록으로</button>
|
||||
<button class="ghost" @click="loadDetail">데이터 새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
<main id="dynasty-detail-container" class="dynasty-page">
|
||||
<table class="legacy-table legacy-bg0 title-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
역 대 왕 조<br />
|
||||
<button class="native-button" type="button" @click="closePage">창 닫기</button>
|
||||
<span class="all-link">
|
||||
<RouterLink to="/dynasty"><button class="native-button" type="button">전체보기</button></RouterLink>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="errorMessage" class="error">{{ errorMessage }}</div>
|
||||
<div v-else-if="loading" class="placeholder">불러오는 중...</div>
|
||||
<div v-else-if="!data" class="placeholder">표시할 데이터가 없습니다.</div>
|
||||
<div v-if="errorMessage" class="legacy-message error" role="alert">{{ errorMessage }}</div>
|
||||
<div v-else-if="loading && !data" class="legacy-message" role="status">불러오는 중...</div>
|
||||
|
||||
<section v-if="data" class="grid gap-4">
|
||||
<article class="bg-zinc-900 border border-zinc-800 rounded p-4">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<span class="w-3 h-3 rounded-full" :style="{ backgroundColor: data.emperor.color }" />
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">{{ data.emperor.name }}</h2>
|
||||
<p class="text-xs text-zinc-400">{{ data.emperor.phase }} · {{ data.emperor.year }}년 {{ data.emperor.month }}월</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-2 text-sm text-zinc-300">
|
||||
<div>국력: {{ data.emperor.power }}</div>
|
||||
<div>도시: {{ data.emperor.citynum }}개</div>
|
||||
<div>장수: {{ data.emperor.gennum }}명</div>
|
||||
<div>인구: {{ data.emperor.pop }}</div>
|
||||
<div>인구율: {{ data.emperor.poprate }}</div>
|
||||
<div>금/쌀: {{ data.emperor.gold }} / {{ data.emperor.rice }}</div>
|
||||
</div>
|
||||
</article>
|
||||
<template v-if="data">
|
||||
<table class="legacy-table legacy-bg0 emperor-table">
|
||||
<colgroup>
|
||||
<col class="short-label" />
|
||||
<col class="picture-column" />
|
||||
<col class="long-value" />
|
||||
<col class="short-label" />
|
||||
<col class="picture-column" />
|
||||
<col class="long-value" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="phase-heading centered" colspan="6">
|
||||
<span class="large-text">{{ data.emperor.phase }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">국 가 수<br />(최종 / 최대)</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.nationCount }}</td>
|
||||
<td class="legacy-bg1 centered">장 수 수<br />(최종 / 최대)</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.genCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">등 장 국 가</td>
|
||||
<td colspan="5">{{ data.emperor.nationName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">국가별 성향</td>
|
||||
<td colspan="5">{{ data.emperor.nationHist }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">장 수 성 격</td>
|
||||
<td colspan="5">{{ data.emperor.personalHist }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">장 수 특 기</td>
|
||||
<td colspan="5">{{ data.emperor.specialHist }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="nation-heading centered"
|
||||
colspan="6"
|
||||
:style="{
|
||||
backgroundColor: data.emperor.color,
|
||||
color: legacyNationTextColor(data.emperor.color),
|
||||
}"
|
||||
>
|
||||
<span class="large-text">
|
||||
{{ data.emperor.name }} ({{ data.emperor.year }}年 {{ data.emperor.month }}月)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">국 력</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.power }}</td>
|
||||
<td class="legacy-bg1 centered">성 향</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">장 수</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.gennum }}</td>
|
||||
<td class="legacy-bg1 centered">속 령</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.citynum }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">총 인 구</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.pop }}</td>
|
||||
<td class="legacy-bg1 centered">인 구 율</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.poprate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">국 고</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.gold }}</td>
|
||||
<td class="legacy-bg1 centered">병 량</td>
|
||||
<td class="centered" colspan="2">{{ data.emperor.rice }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">황 제</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l12name }}</td>
|
||||
<td class="legacy-bg1 centered">승 상</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l11name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">위 장 군</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l10name }}</td>
|
||||
<td class="legacy-bg1 centered">사 공</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l9name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">표 기 장 군</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l8name }}</td>
|
||||
<td class="legacy-bg1 centered">태 위</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l7name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">거 기 장 군</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l6name }}</td>
|
||||
<td class="legacy-bg1 centered">사 도</td>
|
||||
<td> </td>
|
||||
<td>{{ data.emperor.l5name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">오 호 장 군</td>
|
||||
<td colspan="5">{{ data.emperor.tiger }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">건 안 칠 자</td>
|
||||
<td colspan="5">{{ data.emperor.eagle }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">장 수 들<br />(공헌도 순서)</td>
|
||||
<td colspan="5">{{ data.emperor.gen }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">역 사 기 록</td>
|
||||
<td colspan="5">
|
||||
<!-- 레거시 색상 tag를 동일한 span 구조로 변환한다. -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-for="(entry, index) in data.emperor.history" :key="index" v-html="formatLog(entry)" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<article class="bg-zinc-900 border border-zinc-800 rounded p-4">
|
||||
<h3 class="text-base font-semibold mb-2">왕조 구성</h3>
|
||||
<div class="grid sm:grid-cols-2 gap-2 text-sm text-zinc-300">
|
||||
<div>세력 수: {{ data.emperor.nationCount }}</div>
|
||||
<div>장수 수: {{ data.emperor.genCount }}</div>
|
||||
<div>세력 목록: {{ data.emperor.nationName }}</div>
|
||||
<div>세력 유형: {{ data.emperor.nationHist }}</div>
|
||||
</div>
|
||||
</article>
|
||||
<table
|
||||
v-for="nation in data.nations"
|
||||
:key="nation.nation"
|
||||
class="legacy-table legacy-bg2 old-nation-table"
|
||||
>
|
||||
<colgroup>
|
||||
<col class="old-label" />
|
||||
<col class="old-value" />
|
||||
<col class="old-label" />
|
||||
<col class="old-value" />
|
||||
<col class="old-label" />
|
||||
<col class="old-value" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<td
|
||||
class="centered"
|
||||
colspan="6"
|
||||
:style="{ backgroundColor: nation.color, color: legacyNationTextColor(nation.color) }"
|
||||
>
|
||||
【 {{ nation.name }} 】
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="centered">
|
||||
<tr>
|
||||
<td class="legacy-bg1">성향</td>
|
||||
<td>{{ nation.typeName }}</td>
|
||||
<td class="legacy-bg1">-</td>
|
||||
<td>-</td>
|
||||
<td class="legacy-bg1">일자</td>
|
||||
<td>{{ formatArchiveDate(nation.date) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1">최종 작위</td>
|
||||
<td>{{ nation.levelName }}</td>
|
||||
<td class="legacy-bg1">최종 장수 수</td>
|
||||
<td>{{ nation.generals.length }}명</td>
|
||||
<td class="legacy-bg1">기술력</td>
|
||||
<td>{{ nation.tech ?? '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1">최대 영토 수</td>
|
||||
<td>{{ nation.maxCities.length }}</td>
|
||||
<td class="legacy-bg1">최대 병력 수</td>
|
||||
<td>{{ nation.maxCrew ?? 0 }}명</td>
|
||||
<td class="legacy-bg1">최대 국력</td>
|
||||
<td>{{ nation.maxPower ?? 0 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 top-cell">최대영토</td>
|
||||
<td colspan="5">{{ nation.maxCities.join(', ') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 top-cell">장수명단</td>
|
||||
<td colspan="5">
|
||||
<template v-for="general in nation.generalsFull" :key="general.generalNo">
|
||||
{{ general.name }},
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 top-cell">국가열전</td>
|
||||
<td class="legacy-bg0 nation-history" colspan="5">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-for="(entry, index) in nation.history" :key="index" v-html="formatLog(entry)" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<article class="bg-zinc-900 border border-zinc-800 rounded p-4">
|
||||
<h3 class="text-base font-semibold mb-2">관직</h3>
|
||||
<ul class="grid sm:grid-cols-2 gap-2 text-sm text-zinc-300">
|
||||
<li v-if="data.emperor.l12name">승상: {{ data.emperor.l12name }}</li>
|
||||
<li v-if="data.emperor.l11name">태사: {{ data.emperor.l11name }}</li>
|
||||
<li v-if="data.emperor.l10name">태부: {{ data.emperor.l10name }}</li>
|
||||
<li v-if="data.emperor.l9name">태위: {{ data.emperor.l9name }}</li>
|
||||
<li v-if="data.emperor.l8name">사도: {{ data.emperor.l8name }}</li>
|
||||
<li v-if="data.emperor.l7name">사공: {{ data.emperor.l7name }}</li>
|
||||
<li v-if="data.emperor.l6name">상서령: {{ data.emperor.l6name }}</li>
|
||||
<li v-if="data.emperor.l5name">상서: {{ data.emperor.l5name }}</li>
|
||||
</ul>
|
||||
<div v-if="!data.emperor.l12name && !data.emperor.l11name" class="text-xs text-zinc-500">등록된 관직 정보가 없습니다.</div>
|
||||
</article>
|
||||
|
||||
<article class="bg-zinc-900 border border-zinc-800 rounded p-4">
|
||||
<h3 class="text-base font-semibold mb-2">장수 기록</h3>
|
||||
<div class="text-sm text-zinc-300 space-y-2">
|
||||
<div>
|
||||
<span class="text-xs text-zinc-400">호랑이(전공):</span>
|
||||
<div>{{ data.emperor.tiger || '기록 없음' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-zinc-400">독수리(방화):</span>
|
||||
<div>{{ data.emperor.eagle || '기록 없음' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-zinc-400">헌납 순위:</span>
|
||||
<div>{{ data.emperor.gen || '기록 없음' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="bg-zinc-900 border border-zinc-800 rounded p-4">
|
||||
<h3 class="text-base font-semibold mb-2">통일 세력 연표</h3>
|
||||
<div v-if="data.emperor.history.length === 0" class="text-xs text-zinc-500">연표가 없습니다.</div>
|
||||
<ul v-else class="space-y-2 text-sm text-zinc-300">
|
||||
<li v-for="(entry, index) in data.emperor.history" :key="`${data.emperor.id}-hist-${index}`">
|
||||
{{ entry }}
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<section class="grid gap-4">
|
||||
<header class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold">국가 기록</h3>
|
||||
<span class="text-xs text-zinc-400">총 {{ data.nations.length }}개</span>
|
||||
</header>
|
||||
|
||||
<article
|
||||
v-for="nation in data.nations"
|
||||
:key="nation.nation"
|
||||
class="bg-zinc-900 border border-zinc-800 rounded p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="w-3 h-3 rounded-full" :style="{ backgroundColor: nation.color }" />
|
||||
<div>
|
||||
<h4 class="text-base font-semibold">
|
||||
{{ nation.name }}
|
||||
<span
|
||||
v-if="nation.isWinner"
|
||||
class="ml-2 inline-flex items-center rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-[10px] text-amber-200"
|
||||
>
|
||||
통일 세력
|
||||
</span>
|
||||
</h4>
|
||||
<p class="text-xs text-zinc-400">{{ nation.type || '미상' }} · {{ new Date(nation.date).toLocaleString() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-2 text-sm text-zinc-300 mb-3">
|
||||
<div>레벨: {{ nation.level ?? '-' }}</div>
|
||||
<div>기술: {{ nation.tech ?? '-' }}</div>
|
||||
<div>최대 국력: {{ nation.maxPower ?? '-' }}</div>
|
||||
<div>최대 병력: {{ nation.maxCrew ?? '-' }}</div>
|
||||
<div>최대 도시: {{ nation.maxCities.join(', ') || '-' }}</div>
|
||||
<div>장수 수: {{ nation.generals.length }}</div>
|
||||
</div>
|
||||
<div class="text-sm text-zinc-300">
|
||||
<h5 class="text-xs text-zinc-400 mb-1">장수 목록</h5>
|
||||
<div v-if="nation.generalsFull.length === 0" class="text-xs text-zinc-500">기록된 장수가 없습니다.</div>
|
||||
<ul v-else class="flex flex-wrap gap-2">
|
||||
<li
|
||||
v-for="general in nation.generalsFull"
|
||||
:key="general.generalNo"
|
||||
class="bg-zinc-950 border border-zinc-800 rounded px-2 py-1 text-xs"
|
||||
>
|
||||
{{ general.name }}<span v-if="general.lastYearMonth"> ({{ general.lastYearMonth }})</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="text-sm text-zinc-300 mt-3">
|
||||
<h5 class="text-xs text-zinc-400 mb-1">국가 연표</h5>
|
||||
<div v-if="nation.history.length === 0" class="text-xs text-zinc-500">연표가 없습니다.</div>
|
||||
<ul v-else class="space-y-1">
|
||||
<li v-for="(entry, index) in nation.history" :key="`${nation.nation}-hist-${index}`">
|
||||
{{ entry }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
<table class="legacy-table legacy-bg0 footer-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<button class="native-button" type="button" @click="closePage">창 닫기</button><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="banner">
|
||||
삼국지 모의전투 TypeScript core2026 / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:global(body) {
|
||||
min-width: 1016px;
|
||||
}
|
||||
|
||||
.dynasty-page {
|
||||
width: 1000px;
|
||||
margin: 8px auto 0;
|
||||
color: #fff;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.legacy-table {
|
||||
width: 1000px;
|
||||
border-spacing: 2px;
|
||||
}
|
||||
|
||||
.legacy-table td {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.title-table {
|
||||
height: 47px;
|
||||
}
|
||||
|
||||
.all-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.emperor-table {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.short-label {
|
||||
width: 98px;
|
||||
}
|
||||
|
||||
.picture-column {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.long-value {
|
||||
width: 332px;
|
||||
}
|
||||
|
||||
.phase-heading {
|
||||
background: skyblue;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.large-text {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.old-nation-table {
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
.old-label {
|
||||
width: 98px;
|
||||
}
|
||||
|
||||
.old-value {
|
||||
width: 238px;
|
||||
}
|
||||
|
||||
.top-cell {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.nation-history {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.native-button {
|
||||
appearance: auto;
|
||||
box-sizing: border-box;
|
||||
border: 2px outset buttonborder;
|
||||
border-radius: 0;
|
||||
padding: 1px 6px;
|
||||
background: buttonface;
|
||||
color: buttontext;
|
||||
font-family: Arial;
|
||||
font-size: 13.3333px;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.legacy-message {
|
||||
margin-top: 10px;
|
||||
border: 1px solid gray;
|
||||
padding: 12px;
|
||||
background: #302016;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.legacy-message.error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.footer-table {
|
||||
min-height: 50px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type DynastyEntry = {
|
||||
id: number;
|
||||
serverId: string;
|
||||
phase: string;
|
||||
name: string;
|
||||
year: number;
|
||||
month: number;
|
||||
color: string;
|
||||
type: string;
|
||||
power: number;
|
||||
gennum: number;
|
||||
citynum: number;
|
||||
};
|
||||
|
||||
type DynastyListPayload = {
|
||||
entries: DynastyEntry[];
|
||||
};
|
||||
type DynastyListPayload = Awaited<ReturnType<typeof trpc.dynasty.getList.query>>;
|
||||
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const entries = ref<DynastyEntry[]>([]);
|
||||
const data = ref<DynastyListPayload | null>(null);
|
||||
|
||||
const loadDynasty = async () => {
|
||||
const closePage = async (): Promise<void> => {
|
||||
if (window.opener) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
await router.push('/');
|
||||
};
|
||||
|
||||
const loadDynasty = async (): Promise<void> => {
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
try {
|
||||
const result = (await trpc.dynasty.getList.query()) as DynastyListPayload;
|
||||
entries.value = result.entries;
|
||||
data.value = await trpc.dynasty.getList.query();
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '왕조일람을 불러오지 못했습니다.';
|
||||
} finally {
|
||||
@@ -37,50 +32,225 @@ const loadDynasty = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDynasty();
|
||||
});
|
||||
onMounted(loadDynasty);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="main-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">왕조일람</h1>
|
||||
<p class="page-subtitle">천하통일로 정리된 왕조 기록을 확인합니다.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="ghost" @click="loadDynasty">목록 새로고침</button>
|
||||
</div>
|
||||
</header>
|
||||
<main id="dynasty-list-container" class="dynasty-page">
|
||||
<table class="legacy-table legacy-bg0 title-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
역 대 왕 조<br />
|
||||
<button class="native-button" type="button" @click="closePage">창 닫기</button><br />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="errorMessage" class="error">{{ errorMessage }}</div>
|
||||
<div v-else-if="loading" class="placeholder">불러오는 중...</div>
|
||||
<div v-else-if="entries.length === 0" class="placeholder">표시할 왕조 기록이 없습니다.</div>
|
||||
<div v-if="errorMessage" class="legacy-message error" role="alert">{{ errorMessage }}</div>
|
||||
<div v-else-if="loading && !data" class="legacy-message" role="status">불러오는 중...</div>
|
||||
|
||||
<section v-else class="grid gap-4">
|
||||
<RouterLink
|
||||
v-for="entry in entries"
|
||||
<template v-if="data">
|
||||
<table v-if="data.current" class="legacy-table legacy-bg0 current-table spaced-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="current-heading" colspan="8">
|
||||
<span class="large-text">현재 ({{ data.current.year }}年 {{ data.current.month }}月)</span>
|
||||
<RouterLink to="/yearbook"><button class="native-button" type="button">역사 보기</button></RouterLink>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="data.entries.length === 0" class="legacy-message">표시할 왕조 기록이 없습니다.</div>
|
||||
|
||||
<table
|
||||
v-for="entry in data.entries"
|
||||
:key="entry.id"
|
||||
class="block bg-zinc-900 border border-zinc-800 rounded p-4 hover:border-zinc-600 transition"
|
||||
:to="`/dynasty/${entry.id}`"
|
||||
class="legacy-table legacy-bg0 dynasty-table spaced-table"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="w-3 h-3 rounded-full" :style="{ backgroundColor: entry.color }" />
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">{{ entry.name }}</h2>
|
||||
<p class="text-xs text-zinc-400">
|
||||
{{ entry.phase }} · {{ entry.year }}년 {{ entry.month }}월
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-3 gap-2 text-sm text-zinc-300">
|
||||
<div>세력 유형: {{ entry.type || '미상' }}</div>
|
||||
<div>도시: {{ entry.citynum }}개</div>
|
||||
<div>장수: {{ entry.gennum }}명</div>
|
||||
<div class="sm:col-span-3">국력: {{ entry.power }}</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</section>
|
||||
<colgroup>
|
||||
<col class="label-column" />
|
||||
<col class="value-column" />
|
||||
<col class="label-column" />
|
||||
<col class="value-column" />
|
||||
<col class="label-column" />
|
||||
<col class="value-column" />
|
||||
<col class="label-column" />
|
||||
<col class="value-column" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="phase-heading" colspan="8">
|
||||
<span class="large-text">{{ entry.phase }}</span>
|
||||
<RouterLink :to="`/dynasty/${entry.id}`">
|
||||
<button class="native-button" type="button">자세히</button>
|
||||
</RouterLink>
|
||||
<RouterLink v-if="entry.serverId" :to="{ path: '/yearbook', query: { serverID: entry.serverId } }">
|
||||
<button class="native-button" type="button">역사 보기</button>
|
||||
</RouterLink>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="nation-heading"
|
||||
colspan="8"
|
||||
:style="{ backgroundColor: entry.color, color: legacyNationTextColor(entry.color) }"
|
||||
>
|
||||
<span class="large-text">{{ entry.name }} ({{ entry.year }}年 {{ entry.month }}月)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">국 력</td>
|
||||
<td class="centered">{{ entry.power }}</td>
|
||||
<td class="legacy-bg1 centered">장 수</td>
|
||||
<td class="centered">{{ entry.gennum }}</td>
|
||||
<td class="legacy-bg1 centered">속 령</td>
|
||||
<td class="centered">{{ entry.citynum }}</td>
|
||||
<td class="legacy-bg1 centered">성 향</td>
|
||||
<td class="centered">{{ entry.type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">황 제</td>
|
||||
<td class="centered">{{ entry.l12name }}</td>
|
||||
<td class="legacy-bg1 centered">승 상</td>
|
||||
<td class="centered">{{ entry.l11name }}</td>
|
||||
<td class="legacy-bg1 centered">표 기 장 군</td>
|
||||
<td class="centered">{{ entry.l10name }}</td>
|
||||
<td class="legacy-bg1 centered">사 공</td>
|
||||
<td class="centered">{{ entry.l9name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="legacy-bg1 centered">거 기 장 군</td>
|
||||
<td class="centered">{{ entry.l8name }}</td>
|
||||
<td class="legacy-bg1 centered">태 위</td>
|
||||
<td class="centered">{{ entry.l7name }}</td>
|
||||
<td class="legacy-bg1 centered">위 장 군</td>
|
||||
<td class="centered">{{ entry.l6name }}</td>
|
||||
<td class="legacy-bg1 centered">사 도</td>
|
||||
<td class="centered">{{ entry.l5name }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<table class="legacy-table legacy-bg0 footer-table spaced-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<button class="native-button" type="button" @click="closePage">창 닫기</button><br />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="banner">
|
||||
삼국지 모의전투 TypeScript core2026 / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:global(body) {
|
||||
min-width: 1016px;
|
||||
}
|
||||
|
||||
.dynasty-page {
|
||||
width: 1000px;
|
||||
margin: 8px auto 0;
|
||||
color: #fff;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.legacy-table {
|
||||
width: 1000px;
|
||||
border-spacing: 2px;
|
||||
}
|
||||
|
||||
.legacy-table td {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.spaced-table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title-table {
|
||||
height: 47px;
|
||||
}
|
||||
|
||||
.current-table {
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
.dynasty-table {
|
||||
height: 139px;
|
||||
}
|
||||
|
||||
.current-heading,
|
||||
.phase-heading,
|
||||
.nation-heading,
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.current-heading {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.phase-heading {
|
||||
background: skyblue;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.large-text {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.label-column {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.value-column {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.native-button {
|
||||
appearance: auto;
|
||||
box-sizing: border-box;
|
||||
border: 2px outset buttonborder;
|
||||
border-radius: 0;
|
||||
padding: 1px 6px;
|
||||
background: buttonface;
|
||||
color: buttontext;
|
||||
font-family: Arial;
|
||||
font-size: 13.3333px;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.legacy-message {
|
||||
margin-top: 10px;
|
||||
border: 1px solid gray;
|
||||
padding: 12px;
|
||||
background: #302016;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.legacy-message.error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.footer-table {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import MapViewer from '../components/main/MapViewer.vue';
|
||||
import { formatLog } from '../utils/formatLog';
|
||||
@@ -25,12 +25,18 @@ type HistoryData = {
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const range = ref<YearbookRange | null>(null);
|
||||
const mapLayout = ref<MapLayout | null>(null);
|
||||
const history = ref<HistoryData | null>(null);
|
||||
const selectedYearMonth = ref<number | null>(null);
|
||||
const serverID = computed(() => {
|
||||
const value = route.query.serverID;
|
||||
const raw = Array.isArray(value) ? value[0] : value;
|
||||
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
|
||||
});
|
||||
|
||||
const parseYearMonth = (value: number): { year: number; month: number } => ({
|
||||
year: Math.floor(value / 12),
|
||||
@@ -66,7 +72,7 @@ const loadHistory = async (): Promise<void> => {
|
||||
errorMessage.value = '';
|
||||
try {
|
||||
const { year, month } = parseYearMonth(selectedYearMonth.value);
|
||||
const result = await trpc.yearbook.getHistory.query({ year, month });
|
||||
const result = await trpc.yearbook.getHistory.query({ year, month, serverID: serverID.value });
|
||||
if ('data' in result) {
|
||||
history.value = result.data;
|
||||
}
|
||||
@@ -96,7 +102,7 @@ onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [loadedRange, loadedLayout] = await Promise.all([
|
||||
trpc.yearbook.getRange.query(),
|
||||
trpc.yearbook.getRange.query(serverID.value ? { serverID: serverID.value } : undefined),
|
||||
trpc.public.getMapLayout.query(),
|
||||
]);
|
||||
range.value = loadedRange;
|
||||
|
||||
Reference in New Issue
Block a user