Merge remote-tracking branch 'origin/main' into fix/main-lumen-button-coverage-20260817

This commit is contained in:
2026-08-17 10:38:55 +00:00
9 changed files with 243 additions and 51 deletions
@@ -163,6 +163,17 @@ const isActive = (link: MainNavigationLinkItem) => link.id === 'survey' && props
list-style: none;
}
.main-global-menu[data-menu-position='bottom'] .main-menu-popup__list {
top: auto;
bottom: calc(100% + 2px);
box-shadow: 0 -8px 18px rgb(0 0 0 / 45%);
}
.main-global-menu[data-menu-position='bottom'] .menu-caret {
border-top-width: 0;
border-bottom: 4px solid currentColor;
}
.main-menu-split .main-menu-popup__list {
right: 0;
left: auto;
@@ -286,28 +286,26 @@ onMounted(() => {
><strong>{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }}</strong>
<span>피살</span
><strong>{{ selectedGeneral.battleStats.deathCrew.toLocaleString('ko-KR') }}</strong>
<span>최근 전투</span><strong>{{ selectedGeneral.recentWar || '-' }}</strong>
<span class="battle-general-extra__recent-label">최근 전투</span>
<strong class="battle-general-extra__recent-value">
{{
formatServerDateTime(selectedGeneral.recentWar, {
format: 'monthDayTime',
fallback: '-',
})
}}
</strong>
</div>
<LegacyGeneralProgress :general="selectedGeneral" :show-primary="false" />
</template>
</GeneralBasicCard>
<div v-if="selectedGeneral" class="general-meta">
<div>
최근 :
{{
formatServerDateTime(selectedGeneral.turnTime, { format: 'hourMinute', fallback: '-' })
}}
</div>
<div>최근 전투: {{ selectedGeneral.recentWar || '-' }}</div>
<div>전투 횟수: {{ selectedGeneral.warnum }}</div>
</div>
</PanelCard>
</div>
<div class="stack">
<PanelCard title="장수 기록" subtitle="열전과 전투 기록">
<div class="log-grid">
<div v-for="type in logTypes" :key="type" class="log-block">
<div v-for="type in logTypes" :key="type" class="log-block" :data-log-type="type">
<div class="log-title">{{ logLabels[type] }}</div>
<SkeletonLines v-if="loading || logLoading" :lines="3" />
<template v-else>
@@ -356,14 +354,6 @@ onMounted(() => {
font: inherit;
}
.general-meta {
margin: 0;
padding: 6px 8px;
color: #ccc;
display: grid;
gap: 4px;
}
.battle-general-extra {
display: grid;
grid-template-columns: repeat(6, 1fr);
@@ -390,6 +380,15 @@ onMounted(() => {
white-space: nowrap;
}
.battle-general-extra > .battle-general-extra__recent-label {
grid-column: 1;
}
.battle-general-extra > .battle-general-extra__recent-value {
grid-column: 2 / -1;
text-align: left;
}
.log-grid {
display: contents;
}
@@ -397,7 +396,8 @@ onMounted(() => {
.log-block {
border: 1px solid #666;
padding: 0;
background: #000;
background-color: #302016;
background-image: var(--sammo-texture-walnut);
min-height: 0;
}
@@ -492,8 +492,8 @@ onMounted(() => {
margin: 0 auto;
padding: 0;
gap: 0;
height: 1268px;
overflow: hidden;
height: auto;
overflow: visible;
}
.battle-top {
height: 32px;
@@ -530,7 +530,6 @@ onMounted(() => {
@media (max-width: 991px) {
.battle-page {
width: 500px;
height: 1411px;
}
.battle-top {
grid-template-columns: 89px 89px 1fr 0 0;
@@ -4,10 +4,12 @@ import type { BattleSimRequestPayload, BattleSimResultPayload } from '@sammo-ts/
import PanelCard from '../components/ui/PanelCard.vue';
import SkeletonLines from '../components/ui/SkeletonLines.vue';
import BattleGeneralCard from '../components/battle/BattleGeneralCard.vue';
import { useGameFeedback } from '../composables/useGameFeedback';
import { trpc } from '../utils/trpc';
import { getNpcColor } from '../utils/npcColor';
import type { BattleSimOptions, GeneralDraft, InheritBuff } from '../utils/battleSimulatorTypes';
import { BattleSimulatorWorkerClient } from '../utils/battleSimulatorWorkerClient';
import { formatSeoulDateTime } from '../utils/legacyDateTime';
type GeneralExport = Omit<GeneralDraft, 'id'>;
@@ -67,8 +69,8 @@ const attackerGeneral = ref<GeneralDraft | null>(null);
const defenders = ref<GeneralDraft[]>([]);
const isSimulating = ref(false);
const statusMessage = ref<string | null>(null);
const simulationWorker = new BattleSimulatorWorkerClient();
const { info: showInfoToast, dismissToast } = useGameFeedback();
onBeforeUnmount(() => {
simulationWorker.dispose();
@@ -428,13 +430,6 @@ const normalizeGeneralExport = (raw: Record<string, unknown>): GeneralExport =>
inheritBuff: normalizeInheritBuff(raw.inheritBuff),
});
const formatBattleTime = (date: Date): string => {
const pad = (value: number) => String(value).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(
date.getMinutes()
)}`;
};
const buildGeneralPayload = (
general: GeneralDraft,
nationId: number,
@@ -492,7 +487,7 @@ const buildBattlePayload = (action: BattleSimRequestPayload['action']): BattleSi
if (!attackerGeneral.value) {
throw new Error('attacker_general_missing');
}
const now = formatBattleTime(new Date());
const now = formatSeoulDateTime(new Date());
const attackerNationPayload = {
nation: 1,
@@ -583,7 +578,10 @@ const runSimulation = async (action: BattleSimRequestPayload['action']) => {
if (action === 'battle') {
battleResult.value = null;
}
statusMessage.value = action === 'battle' ? '전투를 진행 중입니다.' : '수비자 순서를 계산 중입니다.';
const progressToastId = showInfoToast(
action === 'battle' ? '전투를 진행 중입니다.' : '수비자 순서를 계산 중입니다.',
0
);
try {
const payload = buildBattlePayload(action);
@@ -605,7 +603,7 @@ const runSimulation = async (action: BattleSimRequestPayload['action']) => {
error.value = resolveErrorMessage(err);
} finally {
isSimulating.value = false;
statusMessage.value = null;
dismissToast(progressToastId);
}
};
@@ -952,8 +950,8 @@ const summaryRows = computed(() => {
];
}
return [
{ label: '전투 일시', value: battleResult.value.datetime ?? '-' },
{ label: '전투 횟수', value: formatNumber(battleResult.value.avgWar) },
{ label: '전투 일시', value: formatSeoulDateTime(battleResult.value.datetime ?? '') || '-' },
{ label: '전투 횟수', value: formatNumber(battleResult.value.repeatCnt) },
{ label: '전투 페이즈', value: formatNumber(battleResult.value.phase) },
{
label: '준 피해',
@@ -1001,7 +999,6 @@ const shouldShowUI = computed(() => !loading.value && !!options.value);
<button v-for="index in 5" :key="`legacy-button-${index}`" type="button"></button>
</div>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="statusMessage" class="status">{{ statusMessage }}</div>
<PanelCard data-parity-id="world-settings" title="전역 설정">
<div v-if="loading">