feat: migrate legacy long-lived database records
This commit is contained in:
@@ -35,6 +35,7 @@ import NpcListView from '../views/NpcListView.vue';
|
||||
import NationListView from '../views/NationListView.vue';
|
||||
import GeneralListView from '../views/GeneralListView.vue';
|
||||
import TrafficView from '../views/TrafficView.vue';
|
||||
import PastPlaysView from '../views/PastPlaysView.vue';
|
||||
import { useSessionStore } from '../stores/session';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
@@ -321,6 +322,14 @@ const routes = [
|
||||
requiresGeneral: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/past-plays',
|
||||
name: 'past-plays',
|
||||
component: PastPlaysView,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/survey',
|
||||
name: 'survey',
|
||||
|
||||
@@ -281,6 +281,7 @@ onMounted(() => {
|
||||
<p class="join-subtitle">로그인 완료, 아직 장수가 없는 상태입니다.</p>
|
||||
</div>
|
||||
<div class="join-tabs">
|
||||
<RouterLink class="simulator-link" to="/past-plays">내 지난 플레이</RouterLink>
|
||||
<RouterLink class="simulator-link" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||
<button :class="{ active: activeTab === 'create' }" @click="activeTab = 'create'">장수 생성</button>
|
||||
<button :class="{ active: activeTab === 'possess' }" @click="activeTab = 'possess'">NPC 빙의</button>
|
||||
|
||||
@@ -151,6 +151,7 @@ watch(
|
||||
<a class="ghost" href="/xe/community" target="_blank" rel="noopener">게시판</a>
|
||||
<RouterLink class="ghost" to="/battle-simulator">전투 시뮬레이터</RouterLink>
|
||||
<RouterLink class="ghost" to="/my-page">내 정보&설정</RouterLink>
|
||||
<RouterLink class="ghost" to="/past-plays">내 지난 플레이</RouterLink>
|
||||
<RouterLink class="ghost" :class="{ highlight: tournamentStage === 1 }" to="/tournament"
|
||||
>토너먼트</RouterLink
|
||||
>
|
||||
|
||||
@@ -223,6 +223,7 @@ onMounted(() => {
|
||||
<main id="container" class="legacy-page bg0" :class="`screen-${screenMode}`">
|
||||
<div class="title-row">
|
||||
<span>내 정 보</span>
|
||||
<RouterLink class="legacy-button" to="/past-plays">지난 플레이</RouterLink>
|
||||
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink>
|
||||
<button class="legacy-button" type="button" @click="loadPage">새로고침</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Archive = Awaited<ReturnType<typeof trpc.archive.myPastPlays.query>>;
|
||||
|
||||
const archive = ref<Archive | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const loadArchive = async () => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
archive.value = await trpc.archive.myPastPlays.query();
|
||||
} catch (cause) {
|
||||
error.value = cause instanceof Error ? cause.message : '지난 플레이를 불러오지 못했습니다.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const yearMonth = (value: number): string => `${Math.floor(value / 100)}년 ${value % 100}월`;
|
||||
const valueOrDash = (value: number | string | null): string => (value === null || value === '' ? '-' : String(value));
|
||||
|
||||
onMounted(() => {
|
||||
void loadArchive();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main id="container" class="past-plays-page legacy-bg0">
|
||||
<header class="title-row legacy-bg1">
|
||||
<h1>내 지난 플레이 보기</h1>
|
||||
<nav>
|
||||
<RouterLink class="legacy-button" to="/">돌아가기</RouterLink>
|
||||
<button class="legacy-button" type="button" :disabled="loading" @click="loadArchive">새로고침</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<p class="page-note">종료된 기수에 보관된 내 장수 기록입니다.</p>
|
||||
<p v-if="error" class="error-row">{{ error }}</p>
|
||||
<p v-else-if="loading && !archive" class="empty-row">불러오는 중...</p>
|
||||
<p v-else-if="archive?.seasons.length === 0" class="empty-row">보관된 지난 플레이가 없습니다.</p>
|
||||
|
||||
<section v-for="season in archive?.seasons ?? []" :key="season.serverId" class="season-card">
|
||||
<div class="season-heading legacy-bg2">
|
||||
<strong>{{ season.serverId }}</strong>
|
||||
<span>
|
||||
{{ season.scenarioName ?? '시나리오 미상' }}
|
||||
<template v-if="season.season !== null"> · {{ season.season }}기</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<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 season.generals" :key="general.generalNo">
|
||||
<td class="general-name">{{ general.name }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="nation-name"
|
||||
:style="{ backgroundColor: general.nationColor, color: '#ffffff' }"
|
||||
>
|
||||
{{ general.nationName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ yearMonth(general.lastYearMonth) }}</td>
|
||||
<td>{{ valueOrDash(general.leadership) }}</td>
|
||||
<td>{{ valueOrDash(general.strength) }}</td>
|
||||
<td>{{ valueOrDash(general.intel) }}</td>
|
||||
<td>{{ valueOrDash(general.officerLevel) }}</td>
|
||||
<td>{{ valueOrDash(general.personal) }}</td>
|
||||
<td>{{ valueOrDash(general.special) }}</td>
|
||||
<td>{{ valueOrDash(general.special2) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.past-plays-page {
|
||||
width: min(1000px, 100%);
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
color: #eee;
|
||||
background: #151515;
|
||||
}
|
||||
|
||||
.title-row,
|
||||
.season-heading {
|
||||
border: 1px solid #555;
|
||||
background: #2b2b2b;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.title-row h1 {
|
||||
margin: 0;
|
||||
color: skyblue;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title-row nav {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.legacy-button {
|
||||
box-sizing: border-box;
|
||||
min-height: 28px;
|
||||
padding: 4px 9px;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0;
|
||||
color: #eee;
|
||||
background: #333;
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legacy-button:hover,
|
||||
.legacy-button:focus-visible {
|
||||
border-color: skyblue;
|
||||
color: skyblue;
|
||||
}
|
||||
|
||||
.legacy-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.page-note,
|
||||
.empty-row,
|
||||
.error-row {
|
||||
margin: 0;
|
||||
padding: 12px 10px;
|
||||
border-inline: 1px solid #555;
|
||||
border-bottom: 1px solid #555;
|
||||
}
|
||||
|
||||
.page-note {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.error-row {
|
||||
color: #ff8d8d;
|
||||
}
|
||||
|
||||
.season-card {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.season-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 7px 9px;
|
||||
}
|
||||
|
||||
.season-heading strong {
|
||||
color: skyblue;
|
||||
}
|
||||
|
||||
.season-heading span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 820px;
|
||||
border-collapse: collapse;
|
||||
table-layout: auto;
|
||||
background: #191919;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 6px 7px;
|
||||
border: 1px solid #555;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #ddd;
|
||||
background: #303030;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.general-name {
|
||||
color: skyblue;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nation-name {
|
||||
display: inline-block;
|
||||
min-width: 54px;
|
||||
padding: 2px 5px;
|
||||
border: 1px solid rgb(255 255 255 / 25%);
|
||||
text-shadow: 0 1px 1px #000;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.title-row,
|
||||
.season-heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user