feat: restore global nation and general directories
This commit is contained in:
@@ -32,6 +32,8 @@ import TroopView from '../views/TroopView.vue';
|
||||
import YearbookView from '../views/YearbookView.vue';
|
||||
import NationBettingView from '../views/NationBettingView.vue';
|
||||
import NpcListView from '../views/NpcListView.vue';
|
||||
import NationListView from '../views/NationListView.vue';
|
||||
import GeneralListView from '../views/GeneralListView.vue';
|
||||
import { useSessionStore } from '../stores/session';
|
||||
|
||||
const routes = [
|
||||
@@ -106,6 +108,18 @@ const routes = [
|
||||
component: GlobalInfoView,
|
||||
meta: { requiresAuth: true, requiresGeneral: true },
|
||||
},
|
||||
{
|
||||
path: '/nation-list',
|
||||
name: 'nation-list',
|
||||
component: NationListView,
|
||||
meta: { requiresAuth: true, requiresGeneral: true },
|
||||
},
|
||||
{
|
||||
path: '/general-list',
|
||||
name: 'general-list',
|
||||
component: GeneralListView,
|
||||
meta: { requiresAuth: true, requiresGeneral: true },
|
||||
},
|
||||
{
|
||||
path: '/current-city',
|
||||
name: 'current-city',
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { formatOfficerLevelText } from '../utils/nationFormat';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Directory = Awaited<ReturnType<typeof trpc.world.getGeneralDirectory.query>>;
|
||||
type General = Directory['generals'][number];
|
||||
type SortKey = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
|
||||
|
||||
const sortOptions: Array<{ value: SortKey; label: string }> = [
|
||||
{ value: 1, label: '국가' },
|
||||
{ value: 2, label: '통솔' },
|
||||
{ value: 3, label: '무력' },
|
||||
{ value: 4, label: '지력' },
|
||||
{ value: 5, label: '명성' },
|
||||
{ value: 6, label: '계급' },
|
||||
{ value: 7, label: '관직' },
|
||||
{ value: 8, label: '삭턴' },
|
||||
{ value: 9, label: '벌점' },
|
||||
{ value: 10, label: 'Lv' },
|
||||
{ value: 11, label: '성격' },
|
||||
{ value: 12, label: '내특' },
|
||||
{ value: 13, label: '전특' },
|
||||
{ value: 14, label: '연령' },
|
||||
{ value: 15, label: 'NPC' },
|
||||
];
|
||||
|
||||
const sort = ref<SortKey>(9);
|
||||
const generals = ref<General[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
|
||||
const loadDirectory = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const result = await trpc.world.getGeneralDirectory.query({ sort: sort.value });
|
||||
generals.value = result.generals;
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '장수일람을 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const imageUrl = (general: General): string => {
|
||||
const picture = general.picture ?? 'default.jpg';
|
||||
return general.imageServer ? `${import.meta.env.BASE_URL}d_pic/${picture}` : `/image/general/${picture}`;
|
||||
};
|
||||
const injuredStat = (value: number, injury: number): number => Math.trunc((value * (100 - injury)) / 100);
|
||||
|
||||
onMounted(() => {
|
||||
void loadDirectory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="directory-page" data-page="general-directory">
|
||||
<table class="directory-table title-table legacy-bg0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>장 수 일 람<br /><RouterLink class="legacy-button" to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<form class="sort-form" @submit.prevent="loadDirectory">
|
||||
<label for="viewType">정렬순서 : </label>
|
||||
<select id="viewType" v-model.number="sort" name="type" size="1">
|
||||
<option v-for="option in sortOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<button type="submit">정렬하기</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p v-if="error" class="directory-error" role="alert">{{ error }}</p>
|
||||
<table class="directory-table general-table legacy-bg0">
|
||||
<colgroup>
|
||||
<col style="width: 64px" />
|
||||
<col style="width: 140px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 80px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 140px" />
|
||||
<col style="width: 55px" />
|
||||
<col style="width: 55px" />
|
||||
<col style="width: 75px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 45px" />
|
||||
<col style="width: 70px" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="header-cell">얼 굴</td>
|
||||
<td class="header-cell">이 름</td>
|
||||
<td class="header-cell">연령</td>
|
||||
<td class="header-cell">성격</td>
|
||||
<td class="header-cell">특기</td>
|
||||
<td class="header-cell">레 벨</td>
|
||||
<td class="header-cell">국 가</td>
|
||||
<td class="header-cell">명 성</td>
|
||||
<td class="header-cell">계 급</td>
|
||||
<td class="header-cell">관 직</td>
|
||||
<td class="header-cell">통솔</td>
|
||||
<td class="header-cell">무력</td>
|
||||
<td class="header-cell">지력</td>
|
||||
<td class="header-cell">삭턴</td>
|
||||
<td class="header-cell">벌점</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="15" class="loading-cell">불러오는 중...</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="general in generals"
|
||||
v-else
|
||||
:key="general.id"
|
||||
:data-general-id="general.id"
|
||||
:data-general-wounded="general.injury"
|
||||
:data-general-leadership="general.leadership"
|
||||
:data-general-leadership-bonus="general.leadershipBonus"
|
||||
:data-general-strength="general.strength"
|
||||
:data-general-intel="general.intelligence"
|
||||
:data-is-npc="general.npcState >= 2"
|
||||
:data-npc-type="general.npcState"
|
||||
>
|
||||
<td class="center">
|
||||
<img class="general-icon" width="64" height="64" :src="imageUrl(general)" alt="" />
|
||||
</td>
|
||||
<td class="center">
|
||||
<span :style="{ color: getNpcColor(general.npcState) }">{{ general.name }}</span>
|
||||
<template v-if="general.ownerName"><br /><small>({{ general.ownerName }})</small></template>
|
||||
</td>
|
||||
<td class="center">{{ general.age }}세</td>
|
||||
<td class="center">
|
||||
<span :title="general.personality.info">{{ general.personality.name }}</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span :title="general.specialDomestic.info">{{ general.specialDomestic.name }}</span> /
|
||||
<span :title="general.specialWar.info">{{ general.specialWar.name }}</span>
|
||||
</td>
|
||||
<td class="center">Lv {{ general.experienceLevel }}</td>
|
||||
<td class="center">{{ general.nationName }}</td>
|
||||
<td class="center">{{ general.honorText }}</td>
|
||||
<td class="center">{{ general.dedicationText }}</td>
|
||||
<td class="center">{{ formatOfficerLevelText(general.officerLevel, general.nationLevel) }}</td>
|
||||
<td class="center">
|
||||
<span :class="{ wounded: general.injury > 0 }">{{
|
||||
general.injury > 0
|
||||
? injuredStat(general.leadership, general.injury)
|
||||
: general.leadership
|
||||
}}</span
|
||||
><span v-if="general.leadershipBonus > 0" class="leadership-bonus"
|
||||
>+{{ general.leadershipBonus }}</span
|
||||
>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span :class="{ wounded: general.injury > 0 }">{{
|
||||
general.injury > 0 ? injuredStat(general.strength, general.injury) : general.strength
|
||||
}}</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span :class="{ wounded: general.injury > 0 }">{{
|
||||
general.injury > 0
|
||||
? injuredStat(general.intelligence, general.injury)
|
||||
: general.intelligence
|
||||
}}</span>
|
||||
</td>
|
||||
<td class="center">{{ general.killturn }}</td>
|
||||
<td class="center">{{ general.refreshScoreTotal }}<br />【{{ general.refreshText }}】</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="directory-table title-table legacy-bg0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><RouterLink class="legacy-button" to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><small>삼국지 모의전투 HiDCHe</small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.directory-page {
|
||||
width: 1000px;
|
||||
margin: 0 auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.directory-table {
|
||||
width: 1000px;
|
||||
border-collapse: collapse;
|
||||
table-layout: auto;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.directory-table td {
|
||||
border: 1px solid gray;
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
.title-table {
|
||||
text-align: center;
|
||||
}
|
||||
.directory-page > .title-table:first-child {
|
||||
height: 80.875px;
|
||||
}
|
||||
.title-table td {
|
||||
padding: 1px;
|
||||
}
|
||||
.legacy-button {
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.sort-form {
|
||||
margin: 0;
|
||||
}
|
||||
.sort-form select,
|
||||
.sort-form button {
|
||||
font-size: 14px;
|
||||
}
|
||||
.header-cell {
|
||||
height: 18px;
|
||||
text-align: center;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
}
|
||||
.general-icon {
|
||||
display: inline;
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
max-width: none;
|
||||
height: 64px;
|
||||
object-fit: fill;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.wounded {
|
||||
color: red;
|
||||
}
|
||||
.leadership-bonus {
|
||||
color: cyan;
|
||||
}
|
||||
.loading-cell {
|
||||
height: 64px;
|
||||
text-align: center;
|
||||
}
|
||||
.directory-error {
|
||||
width: 998px;
|
||||
margin: 0;
|
||||
border: 1px solid gray;
|
||||
padding: 8px 0;
|
||||
text-align: center;
|
||||
color: #ff7373;
|
||||
}
|
||||
</style>
|
||||
@@ -102,6 +102,8 @@ watch(
|
||||
<RouterLink class="ghost" to="/nation/info">세력 정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/cities">세력 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/global-info">중원 정보</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation-list">세력일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/general-list">장수일람</RouterLink>
|
||||
<RouterLink class="ghost" to="/current-city">현재 도시</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/generals">세력 장수</RouterLink>
|
||||
<RouterLink class="ghost" to="/nation/personnel">인사부</RouterLink>
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { formatOfficerLevelText } from '../utils/nationFormat';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Directory = Awaited<ReturnType<typeof trpc.world.getNationDirectory.query>>;
|
||||
type Nation = Directory[number];
|
||||
|
||||
const nations = ref<Directory>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
|
||||
const nationLevelText: Record<number, string> = {
|
||||
7: '황제',
|
||||
6: '왕',
|
||||
5: '공',
|
||||
4: '주목',
|
||||
3: '주자사',
|
||||
2: '군벌',
|
||||
1: '호족',
|
||||
0: '방랑군',
|
||||
};
|
||||
const whiteTextColors = new Set([
|
||||
'',
|
||||
'#330000',
|
||||
'#ff0000',
|
||||
'#800000',
|
||||
'#a0522d',
|
||||
'#ff6347',
|
||||
'#808000',
|
||||
'#008000',
|
||||
'#2e8b57',
|
||||
'#008080',
|
||||
'#6495ed',
|
||||
'#0000ff',
|
||||
'#000080',
|
||||
'#483d8b',
|
||||
'#7b68ee',
|
||||
'#800080',
|
||||
'#a9a9a9',
|
||||
'#000000',
|
||||
]);
|
||||
|
||||
const loadDirectory = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
nations.value = await trpc.world.getNationDirectory.query();
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '세력일람을 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const headerTextColor = (color: string): string =>
|
||||
whiteTextColors.has(color.toLowerCase()) ? '#ffffff' : '#000000';
|
||||
|
||||
const officerName = (nation: Nation, officerLevel: number) =>
|
||||
nation.officers.find((officer) => officer.officerLevel === officerLevel)?.general;
|
||||
|
||||
const roamingCityName = (nation: Nation): string => {
|
||||
const chief = officerName(nation, 12);
|
||||
return nation.cities.find((city) => city.id === chief?.cityId)?.name ?? '-';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
void loadDirectory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="directory-page" data-page="nation-directory">
|
||||
<table class="directory-table title-table legacy-bg0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>세 력 일 람<br /><RouterLink class="legacy-button" to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p v-if="error" class="directory-error" role="alert">{{ error }}</p>
|
||||
<p v-else-if="loading" class="directory-loading">불러오는 중...</p>
|
||||
|
||||
<template v-for="nation in nations" :key="nation.id">
|
||||
<table
|
||||
v-if="nation.id !== 0"
|
||||
class="directory-table nation-table legacy-bg2"
|
||||
:data-nation-id="nation.id"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
colspan="8"
|
||||
class="center nation-title"
|
||||
:style="{ color: headerTextColor(nation.color), backgroundColor: nation.color }"
|
||||
>
|
||||
【 {{ nation.name }} 】
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">성 향</td>
|
||||
<td class="value-wide type-name">{{ Array.from(nation.type.name).join(' ') }}</td>
|
||||
<td class="label-cell">작 위</td>
|
||||
<td class="value-wide">{{ nationLevelText[nation.level] ?? '-' }}</td>
|
||||
<td class="label-cell">국 력</td>
|
||||
<td class="value-wide">{{ nation.power }}</td>
|
||||
<td class="label-cell">장수 / 속령</td>
|
||||
<td class="value-wide">{{ nation.generalCount }} / {{ nation.cityCount }}</td>
|
||||
</tr>
|
||||
<tr v-for="row in 2" :key="row">
|
||||
<template v-for="column in 4" :key="column">
|
||||
<td class="label-cell">
|
||||
{{ formatOfficerLevelText(13 - ((row - 1) * 4 + column), nation.level) }}
|
||||
</td>
|
||||
<td class="value-wide">
|
||||
<span
|
||||
v-if="officerName(nation, 13 - ((row - 1) * 4 + column))"
|
||||
:style="{
|
||||
color: getNpcColor(
|
||||
officerName(nation, 13 - ((row - 1) * 4 + column))?.npcState ?? 0
|
||||
),
|
||||
}"
|
||||
>
|
||||
{{ officerName(nation, 13 - ((row - 1) * 4 + column))?.name }}
|
||||
</span>
|
||||
<template v-else>-</template>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label-cell">외교권자</td>
|
||||
<td colspan="5">{{ nation.ambassadorNames.join(', ') }}</td>
|
||||
<td class="label-cell">조언자</td>
|
||||
<td class="value-wide">{{ nation.auditorCount }}명</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<template v-if="nation.level > 0">
|
||||
속령 일람 :
|
||||
<template v-for="city in nation.cities" :key="city.id">
|
||||
<span :class="{ capital: city.capital }">{{ city.capital ? `[${city.name}]` : city.name }}</span
|
||||
>,
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>현재 위치 : <span class="roaming-city">{{ roamingCityName(nation) }}</span></template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
장수 일람 :
|
||||
<template v-for="general in nation.generals" :key="general.id">
|
||||
<span :style="{ color: getNpcColor(general.npcState) }">{{ general.name }}</span
|
||||
>,
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br v-if="nation.id !== 0" />
|
||||
|
||||
<table v-else class="directory-table neutral-table legacy-bg2" data-nation-id="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="5" class="center">【 재 야 】</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="neutral-spacer"> </td>
|
||||
<td class="neutral-label">장 수</td>
|
||||
<td class="neutral-value">{{ nation.generalCount }}</td>
|
||||
<td class="neutral-label">속 령</td>
|
||||
<td class="neutral-value">{{ nation.cityCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
속령 일람 :
|
||||
<template v-for="city in nation.cities" :key="city.id">{{ city.name }}, </template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
장수 일람 :
|
||||
<template v-for="general in nation.generals" :key="general.id">
|
||||
<span :style="{ color: getNpcColor(general.npcState) }">{{ general.name }}</span
|
||||
>,
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<table class="directory-table title-table footer-table legacy-bg0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><RouterLink class="legacy-button" to="/">창 닫기</RouterLink></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><small>삼국지 모의전투 HiDCHe</small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.directory-page {
|
||||
width: 1000px;
|
||||
margin: 0 auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.directory-table {
|
||||
width: 1000px;
|
||||
border-collapse: collapse;
|
||||
table-layout: auto;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.directory-table td {
|
||||
border: 1px solid gray;
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
.title-table {
|
||||
text-align: center;
|
||||
}
|
||||
.directory-page > .title-table:first-child {
|
||||
height: 55.6875px;
|
||||
}
|
||||
.title-table td {
|
||||
padding: 1px;
|
||||
}
|
||||
.legacy-button {
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.nation-table {
|
||||
background-color: #172a52;
|
||||
}
|
||||
.nation-title {
|
||||
height: 19px;
|
||||
}
|
||||
.label-cell {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
}
|
||||
.value-wide {
|
||||
width: 170px;
|
||||
text-align: center;
|
||||
}
|
||||
.type-name {
|
||||
color: yellow;
|
||||
}
|
||||
.capital {
|
||||
color: cyan;
|
||||
}
|
||||
.roaming-city {
|
||||
color: yellow;
|
||||
}
|
||||
.neutral-spacer {
|
||||
width: 498px;
|
||||
text-align: center;
|
||||
}
|
||||
.neutral-label,
|
||||
.neutral-value {
|
||||
width: 123px;
|
||||
text-align: center;
|
||||
}
|
||||
.neutral-label {
|
||||
background-color: #14241b;
|
||||
background-image: url('/image/game/back_green.jpg');
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.footer-table {
|
||||
margin-top: 0;
|
||||
}
|
||||
.directory-error,
|
||||
.directory-loading {
|
||||
width: 998px;
|
||||
margin: 0;
|
||||
border: 1px solid gray;
|
||||
padding: 8px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.directory-error {
|
||||
color: #ff7373;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user