Add nation general and secret office parity
This commit is contained in:
@@ -10,6 +10,7 @@ import NationInfoView from '../views/NationInfoView.vue';
|
||||
import GlobalInfoView from '../views/GlobalInfoView.vue';
|
||||
import CurrentCityView from '../views/CurrentCityView.vue';
|
||||
import NationGeneralsView from '../views/NationGeneralsView.vue';
|
||||
import NationSecretView from '../views/NationSecretView.vue';
|
||||
import NationPersonnelView from '../views/NationPersonnelView.vue';
|
||||
import NationStratFinanView from '../views/NationStratFinanView.vue';
|
||||
import ChiefCenterView from '../views/ChiefCenterView.vue';
|
||||
@@ -148,6 +149,12 @@ const routes = [
|
||||
requiresGeneral: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/nation/secret',
|
||||
name: 'nation-secret',
|
||||
component: NationSecretView,
|
||||
meta: { requiresAuth: true, requiresGeneral: true },
|
||||
},
|
||||
{
|
||||
path: '/nation/personnel',
|
||||
name: 'nation-personnel',
|
||||
|
||||
@@ -104,6 +104,10 @@ watch(
|
||||
<RouterLink class="ghost" to="/global-info">중원 정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/current-city">현재 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/generals">세력 장수</RouterLink>
|
||||
<RouterLink v-if="(boardAccess?.permission ?? -1) >= 1" class="ghost" to="/nation/secret"
|
||||
>암행부</RouterLink
|
||||
>
|
||||
<span v-else class="ghost disabled" aria-disabled="true">암행부</span>
|
||||
<RouterLink class="ghost" to="/nation/personnel">인사부</RouterLink>
|
||||
<RouterLink class="ghost" to="/troop">부대 편성</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/finance">내무부</RouterLink>
|
||||
|
||||
@@ -1,349 +1,226 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import PanelCard from '../components/ui/PanelCard.vue';
|
||||
import SkeletonLines from '../components/ui/SkeletonLines.vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { formatOfficerLevelText } from '../utils/nationFormat';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type GeneralListResponse = Awaited<ReturnType<typeof trpc.nation.getGeneralList.query>>;
|
||||
|
||||
type GeneralEntry = GeneralListResponse['generals'][number];
|
||||
|
||||
type SortKey =
|
||||
| 1
|
||||
| 2
|
||||
| 3
|
||||
| 4
|
||||
| 5
|
||||
| 6
|
||||
| 7
|
||||
| 8
|
||||
| 9
|
||||
| 10
|
||||
| 11
|
||||
| 12
|
||||
| 13
|
||||
| 14
|
||||
| 15;
|
||||
|
||||
const sortOptions: Array<{ key: SortKey; label: string }> = [
|
||||
{ key: 1, label: '관직' },
|
||||
{ key: 2, label: '공헌' },
|
||||
{ key: 3, label: '경험' },
|
||||
{ key: 4, label: '통솔' },
|
||||
{ key: 5, label: '무력' },
|
||||
{ key: 6, label: '지력' },
|
||||
{ key: 7, label: '자금' },
|
||||
{ key: 8, label: '군량' },
|
||||
{ key: 9, label: '병사' },
|
||||
{ key: 10, label: '벌점' },
|
||||
{ key: 11, label: '성격' },
|
||||
{ key: 12, label: '내특' },
|
||||
{ key: 13, label: '전특' },
|
||||
{ key: 14, label: '사관' },
|
||||
{ key: 15, label: 'NPC' },
|
||||
];
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.nation.getGeneralList.query>>;
|
||||
type General = Result['generals'][number];
|
||||
type Sort = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
|
||||
const data = ref<Result | null>(null);
|
||||
const error = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const data = ref<GeneralListResponse | null>(null);
|
||||
const sortKey = ref<SortKey>(1);
|
||||
const filterText = ref('');
|
||||
|
||||
const resolveErrorMessage = (value: unknown): string => {
|
||||
if (value instanceof Error) {
|
||||
return value.message;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return 'unknown_error';
|
||||
};
|
||||
|
||||
const loadGenerals = async () => {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
const sort = ref<Sort>(1);
|
||||
const options = [
|
||||
'관직',
|
||||
'계급',
|
||||
'명성',
|
||||
'통솔',
|
||||
'무력',
|
||||
'지력',
|
||||
'자금',
|
||||
'군량',
|
||||
'병사',
|
||||
'벌점',
|
||||
'성격',
|
||||
'내특',
|
||||
'전특',
|
||||
'사관',
|
||||
'NPC',
|
||||
];
|
||||
const visibleCrew = (general: General): number | null => ('crew' in general ? general.crew : null);
|
||||
const load = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
error.value = '';
|
||||
try {
|
||||
data.value = await trpc.nation.getGeneralList.query();
|
||||
} catch (err) {
|
||||
error.value = resolveErrorMessage(err);
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '세력 장수를 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const sortGenerals = (list: GeneralEntry[]): GeneralEntry[] => {
|
||||
const key = sortKey.value;
|
||||
const sorted = [...list].sort((lhs, rhs) => {
|
||||
switch (key) {
|
||||
case 1:
|
||||
return rhs.officerLevel - lhs.officerLevel;
|
||||
case 2:
|
||||
return rhs.dedication - lhs.dedication;
|
||||
case 3:
|
||||
return rhs.experience - lhs.experience;
|
||||
case 4:
|
||||
return rhs.stats.leadership - lhs.stats.leadership;
|
||||
case 5:
|
||||
return rhs.stats.strength - lhs.stats.strength;
|
||||
case 6:
|
||||
return rhs.stats.intelligence - lhs.stats.intelligence;
|
||||
case 7:
|
||||
return rhs.gold - lhs.gold;
|
||||
case 8:
|
||||
return rhs.rice - lhs.rice;
|
||||
case 9:
|
||||
return rhs.crew - lhs.crew;
|
||||
case 10:
|
||||
return 0;
|
||||
case 11:
|
||||
return (lhs.personality?.name ?? '').localeCompare(rhs.personality?.name ?? '');
|
||||
case 12:
|
||||
return (lhs.specialDomestic?.name ?? '').localeCompare(rhs.specialDomestic?.name ?? '');
|
||||
case 13:
|
||||
return (lhs.specialWar?.name ?? '').localeCompare(rhs.specialWar?.name ?? '');
|
||||
case 14:
|
||||
return rhs.belong - lhs.belong;
|
||||
case 15:
|
||||
return rhs.npcState - lhs.npcState;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (key === 11 || key === 12 || key === 13) {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const filteredGenerals = computed(() => {
|
||||
const list = data.value?.generals ?? [];
|
||||
const keyword = filterText.value.trim().toLowerCase();
|
||||
const filtered = keyword
|
||||
? list.filter((general) => {
|
||||
return (
|
||||
general.name.toLowerCase().includes(keyword) ||
|
||||
(general.cityName ?? '').toLowerCase().includes(keyword) ||
|
||||
(general.officerCityName ?? '').toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
: list;
|
||||
|
||||
return sortGenerals(filtered);
|
||||
});
|
||||
|
||||
const nationLevel = computed(() => data.value?.nation.level ?? 0);
|
||||
|
||||
const formatSpecial = (general: GeneralEntry): string => {
|
||||
const domestic = general.specialDomestic?.name ?? '-';
|
||||
const war = general.specialWar?.name ?? '-';
|
||||
return `${domestic} / ${war}`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
void loadGenerals();
|
||||
});
|
||||
const generals = computed(() =>
|
||||
[...(data.value?.generals ?? [])].sort((a, b) => {
|
||||
if (sort.value === 1) return b.officerLevel - a.officerLevel || a.id - b.id;
|
||||
if (sort.value === 2) return b.dedicationLevel - a.dedicationLevel || a.id - b.id;
|
||||
if (sort.value === 3) return b.experienceLevel - a.experienceLevel || a.id - b.id;
|
||||
if (sort.value === 4) return b.stats.leadership - a.stats.leadership || a.id - b.id;
|
||||
if (sort.value === 5) return b.stats.strength - a.stats.strength || a.id - b.id;
|
||||
if (sort.value === 6) return b.stats.intelligence - a.stats.intelligence || a.id - b.id;
|
||||
if (sort.value === 7) return b.gold - a.gold || a.id - b.id;
|
||||
if (sort.value === 8) return b.rice - a.rice || a.id - b.id;
|
||||
if (sort.value === 9) return (visibleCrew(b) ?? -1) - (visibleCrew(a) ?? -1) || a.id - b.id;
|
||||
if (sort.value === 10) return b.refreshScoreTotal - a.refreshScoreTotal || a.id - b.id;
|
||||
if (sort.value === 11) return (a.personality?.name ?? '').localeCompare(b.personality?.name ?? '');
|
||||
if (sort.value === 12) return (a.specialDomestic?.name ?? '').localeCompare(b.specialDomestic?.name ?? '');
|
||||
if (sort.value === 13) return (a.specialWar?.name ?? '').localeCompare(b.specialWar?.name ?? '');
|
||||
if (sort.value === 14) return b.belong - a.belong || a.id - b.id;
|
||||
if (sort.value === 15) return b.npcState - a.npcState || a.id - b.id;
|
||||
return a.id - b.id;
|
||||
})
|
||||
);
|
||||
const special = (general: General) => `${general.specialDomestic?.name ?? '-'} / ${general.specialWar?.name ?? '-'}`;
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="nation-page">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">세력 장수</h1>
|
||||
<p class="page-subtitle">세력 내 장수 현황 및 정렬</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<RouterLink class="ghost" to="/">메인</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/cities">세력 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/personnel">인사부</RouterLink>
|
||||
<button class="ghost" @click="loadGenerals">새로고침</button>
|
||||
</div>
|
||||
<main class="general-page legacy-bg0">
|
||||
<header>
|
||||
<strong>세력 장수</strong>
|
||||
<span
|
||||
><RouterLink to="/">돌아가기</RouterLink>
|
||||
<button :disabled="loading" @click="load">새로고침</button></span
|
||||
>
|
||||
</header>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<PanelCard title="세력 장수 목록" subtitle="국가 소속 장수들을 확인합니다.">
|
||||
<template #actions>
|
||||
<div class="toolbar-actions">
|
||||
<select v-model.number="sortKey" class="select-input">
|
||||
<option v-for="option in sortOptions" :key="option.key" :value="option.key">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<input v-model="filterText" class="filter-input" placeholder="이름/도시 검색" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="list-meta">총 {{ filteredGenerals.length }}명</div>
|
||||
|
||||
<SkeletonLines v-if="loading" :lines="6" />
|
||||
<div v-else class="table-scroll">
|
||||
<table class="nation-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>이름</th>
|
||||
<th>관직</th>
|
||||
<th>공헌</th>
|
||||
<th>경험</th>
|
||||
<th>통솔</th>
|
||||
<th>무력</th>
|
||||
<th>지력</th>
|
||||
<th>자금</th>
|
||||
<th>군량</th>
|
||||
<th>병사</th>
|
||||
<th>성격</th>
|
||||
<th>특기</th>
|
||||
<th>사관</th>
|
||||
<th>현재 도시</th>
|
||||
<th>관직 도시</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="general in filteredGenerals" :key="general.id">
|
||||
<td>
|
||||
<span v-if="general.npcState > 0" class="npc-tag">NPC</span>
|
||||
{{ general.name }}
|
||||
</td>
|
||||
<td>{{ formatOfficerLevelText(general.officerLevel, nationLevel) }}</td>
|
||||
<td>{{ general.dedication }}</td>
|
||||
<td>{{ general.experience }}</td>
|
||||
<td>{{ general.stats.leadership }}</td>
|
||||
<td>{{ general.stats.strength }}</td>
|
||||
<td>{{ general.stats.intelligence }}</td>
|
||||
<td>{{ general.gold }}</td>
|
||||
<td>{{ general.rice }}</td>
|
||||
<td>{{ general.crew }}</td>
|
||||
<td>{{ general.personality?.name ?? '-' }}</td>
|
||||
<td>{{ formatSpecial(general) }}</td>
|
||||
<td>{{ general.belong > 0 ? general.belong : '-' }}</td>
|
||||
<td>{{ general.cityName ?? '-' }}</td>
|
||||
<td>{{ general.officerCityName ?? '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</PanelCard>
|
||||
<section class="sort">
|
||||
정렬순서 :
|
||||
<select v-model.number="sort" aria-label="세력 장수 정렬">
|
||||
<option v-for="(label, index) in options" :key="label" :value="index + 1">{{ label }}</option>
|
||||
</select>
|
||||
<button>정렬하기</button>
|
||||
<small v-if="data">열람 등급 {{ data.viewer.permission }}</small>
|
||||
</section>
|
||||
<p v-if="error" class="state error" role="alert">{{ error }}</p>
|
||||
<p v-else-if="loading" class="state">불러오는 중...</p>
|
||||
<div v-else class="scroll">
|
||||
<table id="nation-general-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>이 름</th>
|
||||
<th>관 직</th>
|
||||
<th>통무지</th>
|
||||
<th>명성/계급</th>
|
||||
<th>자금</th>
|
||||
<th>군량</th>
|
||||
<th>도시</th>
|
||||
<th>부대</th>
|
||||
<th>병사</th>
|
||||
<th>성격</th>
|
||||
<th>특기</th>
|
||||
<th>사관</th>
|
||||
<th>벌점</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="general in generals" :key="general.id">
|
||||
<td :class="`npc-${general.npcState}`">{{ general.name }}</td>
|
||||
<td>{{ formatOfficerLevelText(general.officerLevel, data?.nation.level) }}</td>
|
||||
<td>
|
||||
{{ general.stats.leadership }}∥{{ general.stats.strength }}∥{{ general.stats.intelligence }}
|
||||
</td>
|
||||
<td>
|
||||
Lv {{ general.experienceLevel }}<br />{{
|
||||
general.dedicationLevel ? `${11 - general.dedicationLevel}품관` : '무품관'
|
||||
}}
|
||||
</td>
|
||||
<td>{{ general.gold.toLocaleString() }}</td>
|
||||
<td>{{ general.rice.toLocaleString() }}</td>
|
||||
<td>{{ general.cityName ?? '?' }}</td>
|
||||
<td>{{ general.troopName ?? '?' }}</td>
|
||||
<td>{{ visibleCrew(general)?.toLocaleString() ?? '?' }}</td>
|
||||
<td :title="general.personality?.info ?? ''">{{ general.personality?.name ?? '-' }}</td>
|
||||
<td
|
||||
:title="
|
||||
[general.specialDomestic?.info, general.specialWar?.info].filter(Boolean).join('\n')
|
||||
"
|
||||
>
|
||||
{{ special(general) }}
|
||||
</td>
|
||||
<td>{{ general.belong }}</td>
|
||||
<td>{{ general.refreshScoreTotal }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<footer><RouterLink to="/">돌아가기</RouterLink></footer>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.nation-page {
|
||||
.general-page {
|
||||
width: 1000px;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
margin: 8px auto 0;
|
||||
font:
|
||||
16px 'Times New Roman',
|
||||
serif;
|
||||
color: #fff;
|
||||
}
|
||||
header,
|
||||
.sort,
|
||||
footer,
|
||||
.state {
|
||||
position: relative;
|
||||
border: 1px solid #777;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
header {
|
||||
min-height: 39px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(232, 221, 196, 0.7);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: rgba(16, 16, 16, 0.6);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f5b7b1;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.select-input {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
background: rgba(16, 16, 16, 0.8);
|
||||
color: rgba(232, 221, 196, 0.9);
|
||||
padding: 6px 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
background: rgba(16, 16, 16, 0.8);
|
||||
color: rgba(232, 221, 196, 0.9);
|
||||
padding: 6px 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.list-meta {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(232, 221, 196, 0.6);
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
overflow-x: auto;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nation-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.nation-table th,
|
||||
.nation-table td {
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid rgba(201, 164, 90, 0.2);
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nation-table thead th {
|
||||
font-size: 0.7rem;
|
||||
color: rgba(232, 221, 196, 0.6);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.npc-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6rem;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
header span {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
}
|
||||
button,
|
||||
select {
|
||||
border: 1px solid #888;
|
||||
border-radius: 2px;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.sort small {
|
||||
float: right;
|
||||
margin-right: 6px;
|
||||
border: 1px solid rgba(201, 164, 90, 0.4);
|
||||
color: rgba(232, 221, 196, 0.8);
|
||||
color: #ccc;
|
||||
}
|
||||
.scroll {
|
||||
width: 1030px;
|
||||
margin-left: -15px;
|
||||
min-height: calc(100vh - 112px);
|
||||
overflow: auto;
|
||||
}
|
||||
table {
|
||||
width: 1030px;
|
||||
min-width: 1030px;
|
||||
border-collapse: separate;
|
||||
table-layout: fixed;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #777;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
th {
|
||||
height: 30px;
|
||||
background: #14241b url('/image/game/back_green.jpg');
|
||||
font-weight: 400;
|
||||
}
|
||||
tbody tr {
|
||||
height: 66px;
|
||||
background: rgb(0 0 0 / 18%);
|
||||
}
|
||||
.npc-1 {
|
||||
color: cyan;
|
||||
}
|
||||
.npc-2,
|
||||
.npc-3,
|
||||
.npc-4,
|
||||
.npc-5 {
|
||||
color: #aaa;
|
||||
}
|
||||
.error {
|
||||
color: #ff7373;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.general-page {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
type Result = Awaited<ReturnType<typeof trpc.nation.getSecretGeneralList.query>>;
|
||||
type Sort = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
const data = ref<Result | null>(null);
|
||||
const error = ref('');
|
||||
const loading = ref(false);
|
||||
const sort = ref<Sort>(7);
|
||||
const options = ['자금', '군량', '도시', '병종', '병사', '삭제턴', '턴', '부대'];
|
||||
const load = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
data.value = await trpc.nation.getSecretGeneralList.query();
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '암행부를 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
const generals = computed(() =>
|
||||
[...(data.value?.generals ?? [])].sort((a, b) => {
|
||||
if (sort.value === 1) return b.gold - a.gold || a.id - b.id;
|
||||
if (sort.value === 2) return b.rice - a.rice || a.id - b.id;
|
||||
if (sort.value === 3) return a.cityId - b.cityId || a.id - b.id;
|
||||
if (sort.value === 4) return b.crewTypeId - a.crewTypeId || a.id - b.id;
|
||||
if (sort.value === 5) return b.crew - a.crew || a.id - b.id;
|
||||
if (sort.value === 6) return a.killTurn - b.killTurn || a.id - b.id;
|
||||
if (sort.value === 7) return a.turnTime.localeCompare(b.turnTime) || a.id - b.id;
|
||||
return b.troopId - a.troopId || a.id - b.id;
|
||||
})
|
||||
);
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="secret-page">
|
||||
<table class="layout legacy-bg0 title">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>암 행 부<br /><RouterLink to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
정렬순서 :
|
||||
<select v-model.number="sort" aria-label="암행부 정렬">
|
||||
<option v-for="(label, index) in options" :key="label" :value="index + 1">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
<button>정렬하기</button> <button :disabled="loading" @click="load">새로고침</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-if="error" class="state error legacy-bg0" role="alert">{{ error }}</p>
|
||||
<p v-else-if="loading" class="state legacy-bg0">불러오는 중...</p>
|
||||
<template v-else-if="data">
|
||||
<table class="layout summary legacy-bg0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>전체 금</th>
|
||||
<td>{{ data.summary.gold.toLocaleString() }}</td>
|
||||
<th>전체 쌀</th>
|
||||
<td>{{ data.summary.rice.toLocaleString() }}</td>
|
||||
<th>평균 금</th>
|
||||
<td>{{ data.summary.averageGold.toFixed(2) }}</td>
|
||||
<th>평균 쌀</th>
|
||||
<td>{{ data.summary.averageRice.toFixed(2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>전체 병력/장수</th>
|
||||
<td>{{ data.summary.crew.toLocaleString() }}/{{ data.summary.generalCount }}</td>
|
||||
<template v-for="level in [90, 80, 60] as const" :key="level"
|
||||
><th>훈사 {{ level }} 병력/장수</th>
|
||||
<td>
|
||||
{{ data.summary.readiness[level].crew.toLocaleString() }}/{{
|
||||
data.summary.readiness[level].generals
|
||||
}}
|
||||
</td></template
|
||||
>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table id="secret-general-list" class="layout list legacy-bg0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>이 름</th>
|
||||
<th>통무지</th>
|
||||
<th>부 대</th>
|
||||
<th>자 금</th>
|
||||
<th>군 량</th>
|
||||
<th>도시</th>
|
||||
<th>守</th>
|
||||
<th>병 종</th>
|
||||
<th>병 사</th>
|
||||
<th>훈련</th>
|
||||
<th>사기</th>
|
||||
<th class="commands">명 령</th>
|
||||
<th>삭턴</th>
|
||||
<th>턴</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="general in generals" :key="general.id">
|
||||
<td>{{ general.name }}<br />Lv {{ general.experienceLevel }}</td>
|
||||
<td>
|
||||
{{ general.stats.leadership }}∥{{ general.stats.strength }}∥{{ general.stats.intelligence }}
|
||||
</td>
|
||||
<td>{{ general.troopName ?? '-' }}</td>
|
||||
<td>{{ general.gold }}</td>
|
||||
<td>{{ general.rice }}</td>
|
||||
<td>{{ general.cityName ?? '-' }}</td>
|
||||
<td>{{ general.defenceTrainText }}</td>
|
||||
<td>{{ general.crewTypeId }}</td>
|
||||
<td>{{ general.crew }}</td>
|
||||
<td>{{ general.train }}</td>
|
||||
<td>{{ general.atmos }}</td>
|
||||
<td class="turns">
|
||||
<template v-if="general.npcState >= 2">NPC 장수</template
|
||||
><template v-else
|
||||
><div v-for="(command, index) in general.reservedCommands" :key="index">
|
||||
{{ index + 1 }} : {{ command }}
|
||||
</div></template
|
||||
>
|
||||
</td>
|
||||
<td>{{ general.killTurn }}</td>
|
||||
<td>{{ general.turnTime.slice(11, 16) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<table class="layout legacy-bg0 footer">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><RouterLink to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.secret-page {
|
||||
width: 1000px;
|
||||
margin: 8px auto 0;
|
||||
font:
|
||||
16px 'Times New Roman',
|
||||
serif;
|
||||
color: #fff;
|
||||
}
|
||||
.layout {
|
||||
width: 1000px;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
td,
|
||||
th,
|
||||
.state {
|
||||
border: 1px solid #777;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
button,
|
||||
select {
|
||||
border: 1px solid #888;
|
||||
border-radius: 2px;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.summary {
|
||||
margin: 5px auto;
|
||||
}
|
||||
.summary th,
|
||||
.list th {
|
||||
background: #14241b url('/image/game/back_green.jpg');
|
||||
}
|
||||
.summary th {
|
||||
width: 120px;
|
||||
}
|
||||
.list {
|
||||
width: 1030px;
|
||||
margin-left: -15px;
|
||||
border-collapse: separate;
|
||||
}
|
||||
.list tbody tr {
|
||||
height: 39px;
|
||||
}
|
||||
.commands {
|
||||
width: 213px;
|
||||
}
|
||||
.turns {
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
}
|
||||
.error {
|
||||
color: #ff7373;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 5px;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.secret-page {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user