Add legacy-compatible in-game information pages

This commit is contained in:
2026-07-26 04:04:01 +00:00
parent b27c529a3d
commit 2017da001b
13 changed files with 1637 additions and 572 deletions
@@ -0,0 +1,243 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import MapViewer from '../components/main/MapViewer.vue';
import { trpc } from '../utils/trpc';
type Result = Awaited<ReturnType<typeof trpc.world.getGlobalInfo.query>>;
type Layout = Awaited<ReturnType<typeof trpc.world.getMapLayout.query>>;
const data = ref<Result | null>(null);
const layout = ref<Layout | null>(null);
const error = ref('');
const state = (value: number) => ({ 0: '★', 1: '▲', 2: '', 7: '@' })[value] ?? 'ㆍ';
const stateClass = (value: number) => `state-${value}`;
const nationMap = computed(() => new Map(data.value?.nations.map((nation) => [nation.id, nation]) ?? []));
onMounted(async () => {
try {
[data.value, layout.value] = await Promise.all([
trpc.world.getGlobalInfo.query(),
trpc.world.getMapLayout.query(),
]);
} catch (cause) {
error.value = cause instanceof Error ? cause.message : '중원 정보를 불러오지 못했습니다.';
}
});
</script>
<template>
<main class="global-page legacy-bg0">
<table class="legacy-title">
<tbody>
<tr>
<td> <br /><RouterLink to="/">돌아가기</RouterLink></td>
</tr>
</tbody>
</table>
<p v-if="error" class="error">{{ error }}</p>
<section v-if="data" class="section">
<h2 class="blue">외교 현황</h2>
<div class="matrix-wrap">
<table class="matrix">
<thead>
<tr>
<th></th>
<th
v-for="nation in data.nations"
:key="nation.id"
class="vertical"
:style="{ backgroundColor: nation.color }"
>
{{ nation.name }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="me in data.nations" :key="me.id">
<th :style="{ backgroundColor: me.color }">{{ me.name }}</th>
<td
v-for="you in data.nations"
:key="you.id"
:class="[
stateClass(data.diplomacy[me.id]?.[you.id] ?? 2),
{ mine: me.id === data.myNationId || you.id === data.myNationId },
]"
>
{{ me.id === you.id ? '' : state(data.diplomacy[me.id]?.[you.id] ?? 2) }}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td :colspan="data.nations.length + 1">
불가침 : <b class="state-7">@</b>, 통상 : , 선포 : <b class="state-1"></b>, 교전 :
<b class="state-0"></b>
</td>
</tr>
</tfoot>
</table>
</div>
</section>
<section v-if="data?.conflict.length" class="section">
<h2 class="magenta">분쟁 현황</h2>
<div v-for="conflict in data.conflict" :key="conflict.cityId" class="conflict">
<strong>{{ conflict.cityName }}</strong>
<div>
<div v-for="(percent, id) in conflict.nations" :key="id" class="conflict-row">
<span :style="{ backgroundColor: nationMap.get(Number(id))?.color }">{{
nationMap.get(Number(id))?.name
}}</span
><em>{{ percent.toFixed(1) }}%</em
><i :style="{ width: `${percent}%`, backgroundColor: nationMap.get(Number(id))?.color }" />
</div>
</div>
</div>
</section>
<section v-if="data && layout" class="section map-section">
<h2 class="green">중원 지도</h2>
<div class="map-grid">
<MapViewer :map-data="data.map" :map-layout="layout" :loading="false" />
<div class="nation-list">
<div v-for="nation in data.nations" :key="nation.id">
<b :style="{ color: nation.color }">{{ nation.name }}</b> {{ nation.power.toLocaleString()
}}<br /><small>{{ nation.cities.join(', ') }}</small>
</div>
</div>
</div>
</section>
<table class="legacy-title footer">
<tbody>
<tr>
<td><RouterLink to="/">돌아가기</RouterLink></td>
</tr>
</tbody>
</table>
</main>
</template>
<style scoped>
.global-page {
width: 1000px;
margin: 0 auto;
font-size: 14px;
}
.legacy-title {
width: 100%;
border-collapse: collapse;
text-align: center;
}
.legacy-title td {
border: 1px solid #777;
padding: 4px;
}
.section {
margin-top: 21px;
}
.section h2 {
font-size: 16.8px;
font-weight: 400;
text-align: center;
margin: 0;
border-block: 1px solid #777;
padding: 2px;
}
.blue {
background: #00f;
}
.magenta {
background: #f0f;
}
.green {
background: green;
}
.matrix-wrap {
overflow-x: auto;
}
.matrix {
margin: auto;
min-width: 400px;
border-collapse: collapse;
text-align: center;
}
.matrix th {
font-weight: 400;
}
.matrix .vertical {
writing-mode: vertical-rl;
text-align: end;
padding: 1ch 0;
min-width: 1ch;
max-width: 3ch;
}
.matrix tbody th {
padding: 2px 1ch;
text-align: right;
min-width: 10ch;
}
.matrix td {
border-left: 1px solid gray;
border-top: 1px solid gray;
padding: 0;
}
.matrix td.mine {
background: #600;
}
.state-0 {
color: red;
}
.state-1 {
color: #f0f;
}
.state-7 {
color: #32cd32;
}
.conflict {
display: grid;
grid-template-columns: 16ch 1fr;
}
.conflict > strong {
text-align: right;
padding-right: 1ch;
align-self: center;
}
.conflict-row {
display: grid;
grid-template-columns: 16ch 6ch 1fr;
align-items: center;
}
.conflict-row span {
padding-left: 1ch;
}
.conflict-row em {
text-align: right;
padding-right: 0.5ch;
font-style: normal;
}
.conflict-row i {
height: 1.2em;
}
.map-grid {
display: grid;
grid-template-columns: 700px 300px;
}
.nation-list > div {
padding: 6px;
border-bottom: 1px solid #666;
}
.footer {
margin-top: 20px;
}
.error {
text-align: center;
color: #ff7373;
}
@media (max-width: 700px) {
.global-page {
width: 500px;
}
.map-grid {
grid-template-columns: 500px;
}
.nation-list {
width: 500px;
}
}
</style>