feat: 플레이 감사 최종 국가 표본 별도 조회 구현

This commit is contained in:
2026-09-16 03:30:05 +00:00
parent b12a899526
commit e1a05e27e7
6 changed files with 254 additions and 5 deletions
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { trpc } from '../../utils/trpc';
type Snapshot = Awaited<ReturnType<typeof trpc.playAudit.nationSnapshot.query>>;
defineProps<{ data: Snapshot }>();
const format = (value: number | null) =>
value === null ? '자료 없음' : value.toLocaleString('ko-KR', { maximumFractionDigits: 2 });
const groups = [
['human', '유저'],
['npc', 'NPC'],
['troopNpc', '부대장 NPC'],
] as const;
</script>
<template>
<p v-if="!data.collected">선택한 시점의 최종 표본이 없습니다.</p>
<p v-else-if="!data.nation">최종 표본에 해당 국가가 없습니다.</p>
<template v-else>
<h3>{{ data.nation.name }} · {{ data.sample?.year }} {{ data.sample?.month }} 최종 표본</h3>
<p>
최종 표본은 월말 시계열과 별도로 표시합니다. 아래 정산은 표본을 수집할 때까지 해당 월에 관측한 값입니다.
</p>
<p v-if="!data.sample?.settlementsComplete">정산 수집이 불완전한 월입니다.</p>
<dl class="nation-values">
<div>
<dt>국고 / </dt>
<dd>{{ format(data.nation.gold) }} / {{ format(data.nation.rice) }}</dd>
</div>
<div>
<dt>기술력 / 적용 세율</dt>
<dd>{{ format(data.nation.tech) }} / {{ format(data.nation.appliedRate) }}%</dd>
</div>
<div>
<dt>실제 / 수입</dt>
<dd>{{ format(data.nation.incomeGold) }} / {{ format(data.nation.incomeRice) }}</dd>
</div>
<div>
<dt>지급 / </dt>
<dd>{{ format(data.nation.paidGold) }} / {{ format(data.nation.paidRice) }}</dd>
</div>
</dl>
<div class="table-scroll" tabindex="0" aria-label="최종 국가 장수 집계">
<table>
<thead>
<tr>
<th>장수 집단</th>
<th>인원</th>
<th>보유 / 합계</th>
<th>평균 / </th>
<th>평균 숙련 ( / / / / )</th>
</tr>
</thead>
<tbody>
<tr v-for="[key, label] in groups" :key="key">
<th scope="row">{{ label }}</th>
<td>{{ data.nation.populations[key].count }}</td>
<td>
{{ format(data.nation.populations[key].gold) }} /
{{ format(data.nation.populations[key].rice) }}
</td>
<td>
{{ format(data.nation.populations[key].averageGold) }} /
{{ format(data.nation.populations[key].averageRice) }}
</td>
<td>{{ Object.values(data.nation.populations[key].averageDex).map(format).join(' / ') }}</td>
</tr>
</tbody>
</table>
</div>
</template>
</template>
<style scoped>
h3 {
font-size: var(--sammo-font-size-normal);
font-weight: bold;
}
.nation-values {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.nation-values dt {
font-weight: bold;
}
.nation-values dd {
margin: 0;
}
.table-scroll {
overflow-x: auto;
}
table {
width: 100%;
min-width: 680px;
border-collapse: collapse;
}
th,
td {
border: 1px solid gray;
padding: 6px;
text-align: left;
}
</style>
+19 -2
View File
@@ -3,6 +3,7 @@ import { computed, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import PanelCard from '../components/ui/PanelCard.vue';
import AuditNationSeries from '../components/playAudit/AuditNationSeries.vue';
import AuditNationSnapshot from '../components/playAudit/AuditNationSnapshot.vue';
import { usePageExit } from '../composables/usePageExit';
import { trpc } from '../utils/trpc';
@@ -11,6 +12,7 @@ type Nations = Awaited<ReturnType<typeof trpc.playAudit.nations.query>>;
type Generals = Awaited<ReturnType<typeof trpc.playAudit.generals.query>>;
type Cities = Awaited<ReturnType<typeof trpc.playAudit.cities.query>>;
type Series = Awaited<ReturnType<typeof trpc.playAudit.nationSeries.query>>;
type NationSnapshot = Awaited<ReturnType<typeof trpc.playAudit.nationSnapshot.query>>;
const route = useRoute();
const router = useRouter();
const { pageExitLabel, exitPage } = usePageExit();
@@ -19,6 +21,7 @@ const nations = ref<Nations | null>(null);
const generals = ref<Generals | null>(null);
const cities = ref<Cities | null>(null);
const series = ref<Series | null>(null);
const nationSnapshot = ref<NationSnapshot | null>(null);
const authorized = ref(false);
const profileName = ref('');
const loading = ref(false);
@@ -54,7 +57,13 @@ const scopeLabel = computed(() =>
: `${route.query.year}${route.query.month}${route.query.at === 'final' ? '최종 표본' : '월말'}`
);
const result = computed(() =>
tab.value === 'generals' ? generals.value : tab.value === 'cities' ? cities.value : series.value
tab.value === 'generals'
? generals.value
: tab.value === 'cities'
? cities.value
: nationSnapshot.value
? { ...nationSnapshot.value, nextCursor: null }
: series.value
);
const readQuery = () => {
tab.value = ['nations', 'generals', 'cities'].includes(String(route.query.tab))
@@ -83,6 +92,7 @@ const load = async (append = false) => {
generals.value = null;
cities.value = null;
series.value = null;
nationSnapshot.value = null;
}
const filter = { at: at.value, nationId: nationId.value === '' ? undefined : Number(nationId.value), limit: 50 };
try {
@@ -111,6 +121,12 @@ const load = async (append = false) => {
...response,
items: append ? [...(cities.value?.items ?? []), ...response.items] : response.items,
};
} else if (nationId.value !== '' && moment.value === 'final') {
const response = await trpc.playAudit.nationSnapshot.query({
nationId: Number(nationId.value),
at: { year: year.value, month: month.value, kind: 'FINAL' },
});
if (request === generation) nationSnapshot.value = response;
} else if (nationId.value !== '') {
const response = await trpc.playAudit.nationSeries.query({
nationId: Number(nationId.value),
@@ -292,7 +308,7 @@ onMounted(async () => {
required
/></label>
<label>월<input v-model.number="month" type="number" min="1" max="12" required /></label>
<template v-if="tab === 'nations'">
<template v-if="tab === 'nations' && moment !== 'final'">
<label
>시작 연도<input
v-model.number="fromYear"
@@ -335,6 +351,7 @@ onMounted(async () => {
국가를 선택하고 조회해 주세요. 멸망한 국가는 해당 월말 기준의 국가 목록에서 선택할 수 있습니다.
</p>
<AuditNationSeries v-if="series && tab === 'nations'" :data="series" />
<AuditNationSnapshot v-if="nationSnapshot && tab === 'nations'" :data="nationSnapshot" />
<template v-if="generals && tab === 'generals'">
<p v-if="!generals.collected">선택한 시점의 표본이 없습니다.</p>
<p v-else-if="!generals.items.length">조건에 맞는 장수가 없습니다.</p>