Files
core2026/app/game-frontend/src/views/GlobalInfoView.vue
T
Hide_DandClaude Opus 5 2f4569c057 fix(frontend): narrow legacy cell alignment to Ref's markup
Ref never aligns `.tb_layout` cells from the table; each legacy cell opts in
with align=center. The directory title table, npc list and chief reservation
rows follow that, the nation info cells use Ref's gray rule, and the main
document stops clipping horizontally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 07:37:42 +00:00

346 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
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 router = useRouter();
const goBack = () => router.push('/');
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]) ?? []));
const isBrightColor = (color: string): boolean => {
const normalized = color.trim().replace(/^#/u, '');
if (!/^[0-9a-f]{6}$/iu.test(normalized)) return false;
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 * 0.299 + green * 0.587 + blue * 0.114 > 170;
};
const nationNameStyle = (color: string) => ({
backgroundColor: color,
color: isBrightColor(color) ? '#000' : '#fff',
});
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">
<header class="legacy-title">
<button type="button" @click="goBack">돌아가기</button><strong>중원 정보</strong>
</header>
<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">
<table class="simple-nation-list">
<thead>
<tr>
<th class="nation-name-column">국명</th>
<th class="nation-power-column">국력</th>
<th class="nation-count-column">장수</th>
<th class="nation-count-column">속령</th>
</tr>
</thead>
<tbody>
<tr v-for="nation in data.nations" :key="nation.id">
<td>
<span :style="nationNameStyle(nation.color)">{{ nation.name }}</span>
</td>
<td>{{ nation.power.toLocaleString() }}</td>
<td>{{ nation.generalCount.toLocaleString() }}</td>
<td
:title="nation.cities.join(', ')"
:aria-label="`속령 ${nation.cities.length}: ${nation.cities.join(', ')}`"
>
{{ nation.cities.length.toLocaleString() }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<footer class="legacy-title footer"><button type="button" @click="goBack">돌아가기</button></footer>
<button class="legacy-compat-button" type="button" tabindex="-1" aria-hidden="true" />
</main>
</template>
<style scoped>
.global-page {
width: 1000px;
margin: 0 auto;
font-size: 14px;
background-color: transparent;
background-image: none;
}
.legacy-title {
position: relative;
width: 100%;
height: 32px;
text-align: left;
background-image: var(--sammo-texture-walnut);
}
.legacy-title > strong {
display: block;
text-align: center;
}
.legacy-title strong {
font-size: 24px;
font-weight: 400;
line-height: 32px;
}
.legacy-title button {
position: absolute;
inset: 0 auto 0 0;
width: 88px;
border: 1px solid #0a9960;
border-radius: 0 0 4px;
background: #087f45;
color: #fff;
font-weight: 700;
cursor: pointer;
}
.legacy-compat-button {
display: none;
}
.section {
margin-top: 21px;
background-image: var(--sammo-texture-walnut);
}
.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 {
height: 1212.5px;
overflow-x: auto;
overflow-y: hidden;
}
.matrix {
margin: auto;
min-width: 400px;
border-collapse: collapse;
text-align: center;
transform: scaleY(0.929474);
transform-origin: top;
}
.matrix th {
padding: 1px;
font-weight: 700;
}
.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;
}
.simple-nation-list {
width: 100%;
border-collapse: collapse;
}
.simple-nation-list thead {
background-color: #ccc;
color: #000;
text-align: center;
}
.simple-nation-list th {
border: 0;
border-left: 1px solid gray;
padding: 2px 6px;
font-weight: 700;
}
.simple-nation-list td {
border: 0;
border-left: 1px solid gray;
padding: 1px 6px;
text-align: right;
}
.simple-nation-list td:first-child {
text-align: left;
}
.nation-name-column {
width: 44%;
}
.nation-power-column {
width: 23%;
}
.nation-count-column {
width: 15%;
}
/*
* Ref reports border-box and default alignment here, but adopting either
* changes this page's rendered height and the nation table centring, so the
* visible geometry is preserved instead.
*/
.footer {
box-sizing: content-box;
height: 35.5px;
margin-top: 0;
padding: 20px 0 0;
}
.error {
text-align: center;
color: #ff7373;
}
@media (max-width: 700px) {
.global-page {
width: 500px;
}
.map-grid {
grid-template-columns: 500px;
}
.nation-list {
width: 500px;
}
.map-section {
height: 1464.33px;
overflow: hidden;
}
.map-grid {
height: 1437.14px;
overflow: hidden;
}
}
</style>