refac: 메인 페이지 바 표시 형식 변경

- 상 하단 분리 대신 하단의 position:fixed bar로 변경
- iPhone에서 좌우 스크롤이 유도되는 것은 완전 해결되지 않음
- 모든 컨텐츠가 viewport 밖을 넘어서지 않도록 않도록 해야 할 것으로
- 예: 500px viewport, 498px content
This commit is contained in:
2023-07-02 17:05:14 +00:00
parent 9f6c7d9489
commit 660258f715
+211 -212
View File
@@ -1,177 +1,180 @@
<template> <template>
<div id="outBlock" :class="`sam-color-${nationStaticInfo?.color.substring(1, 7) ?? '000000'}`"> <!-- eslint-disable-next-line vue/max-attributes-per-line -->
<div id="outBlock2"> <BContainer
<!-- eslint-disable-next-line vue/max-attributes-per-line --> v-if="asyncReady"
<BContainer v-if="asyncReady" id="container" :position="'position-relative'" :toast="{ root: true }" class="bg0"> id="container"
<main class="gx-0"> :position="'position-relative'"
<div class="commonToolbar"> :toast="{ root: true }"
<GlobalMenu :class="['bg0', nationColorClass]"
v-if="globalMenu && globalInfo" >
:globalInfo="globalInfo" <main class="gx-0">
:modelValue="globalMenu" <div class="commonToolbar">
variant="sammo-base2" <GlobalMenu
@reqCall="reqMenuCall" v-if="globalMenu && globalInfo"
/> :globalInfo="globalInfo"
:modelValue="globalMenu"
variant="sammo-base2"
@reqCall="reqMenuCall"
/>
</div>
<GameInfo
v-if="frontInfo"
:frontInfo="frontInfo"
:serverLocked="serverLocked"
:serverName="serverName"
:lastExecuted="lastExecuted"
/>
<div class="s-border-t px-2 py-2 onlineNations">접속중인 국가: {{ globalInfo.onlineNations }}</div>
<div class="s-border-t px-2 py-2 onlineUsers"> 접속자 {{ nationInfo?.onlineGen }}</div>
<div class="s-border-t py-2 nationNotice">
<div class="px-2"> 국가방침 </div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="nationInfo" class="nationNoticeBody" v-html="nationInfo.notice?.msg ?? ''" />
</div>
<div id="ingameBoard">
<!-- TODO: 운영자 툴바는 어디에?-->
<div class="mapView">
<MapViewer
v-if="map"
:serverNick="serverNick"
:serverID="serverID"
:mapName="unwrap(gameConstStore?.gameConst.mapName)"
:isDetailMap="true"
:cityPosition="cityPosition"
:imagePath="imagePath"
:formatCityInfo="formatCityInfoText"
:mapData="map"
:disallowClick="false"
:genHref="genMapCityHref"
@city-click="onCityClick"
/>
</div>
<div class="reservedCommandZone">
<PartialReservedCommand id="reservedCommandPanel" ref="reservedCommandPanel" />
</div>
<div id="actionMiniPlate" class="gx-0 row">
<div class="col">
<div class="gx-1 row">
<div class="col-8 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="tryRefresh"> </button>
</div>
<div class="col-4 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
</div>
</div>
</div> </div>
<GameInfo </div>
v-if="frontInfo" <div v-if="frontInfo" class="cityInfo">
:frontInfo="frontInfo" <CityBasicCard :city="frontInfo.city" />
:serverLocked="serverLocked" </div>
:serverName="serverName" <div v-if="frontInfo" class="nationInfo">
<NationBasicCard :nation="frontInfo.nation" :global="globalInfo" />
</div>
<div v-if="frontInfo && generalInfo && nationStaticInfo" class="generalInfo">
<GeneralBasicCard
:general="generalInfo"
:nation="nationStaticInfo"
:troopInfo="frontInfo.general.troopInfo"
:turnTerm="globalInfo.turnterm"
:lastExecuted="lastExecuted" :lastExecuted="lastExecuted"
/> />
<div class="s-border-t px-2 py-2 onlineNations">접속중인 국가: {{ globalInfo.onlineNations }}</div> </div>
<div class="s-border-t px-2 py-2 onlineUsers"> 접속자 {{ nationInfo?.onlineGen }}</div>
<div class="s-border-t py-2 nationNotice">
<div class="px-2"> 국가방침 </div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="nationInfo" class="nationNoticeBody" v-html="nationInfo.notice?.msg ?? ''" />
</div>
<div id="ingameBoard">
<!-- TODO: 운영자 툴바는 어디에?-->
<div class="mapView">
<MapViewer
v-if="map"
:serverNick="serverNick"
:serverID="serverID"
:mapName="unwrap(gameConstStore?.gameConst.mapName)"
:isDetailMap="true"
:cityPosition="cityPosition"
:imagePath="imagePath"
:formatCityInfo="formatCityInfoText"
:mapData="map"
:disallowClick="false"
:genHref="genMapCityHref"
@city-click="onCityClick"
/>
</div>
<div class="reservedCommandZone">
<PartialReservedCommand id="reservedCommandPanel" ref="reservedCommandPanel" />
</div>
<div id="actionMiniPlate" class="gx-0 row">
<div class="col">
<div class="gx-1 row">
<div class="col-8 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="tryRefresh"> </button>
</div>
<div class="col-4 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
</div>
</div>
</div>
</div>
<div v-if="frontInfo" class="cityInfo">
<CityBasicCard :city="frontInfo.city" />
</div>
<div v-if="frontInfo" class="nationInfo">
<NationBasicCard :nation="frontInfo.nation" :global="globalInfo" />
</div>
<div v-if="frontInfo && generalInfo && nationStaticInfo" class="generalInfo">
<GeneralBasicCard
:general="generalInfo"
:nation="nationStaticInfo"
:troopInfo="frontInfo.general.troopInfo"
:turnTerm="globalInfo.turnterm"
:lastExecuted="lastExecuted"
/>
</div>
<div class="generalCommandToolbar"> <div class="generalCommandToolbar">
<MainControlBar <MainControlBar
v-if="generalInfo"
:permission="generalInfo.permission"
:showSecret="showSecret"
:myLevel="generalInfo.officerLevel"
:nationLevel="nationStaticInfo?.level ?? 0"
:nationColor="nationStaticInfo?.color.substring(1, 7) ?? '000000'"
:isTournamentApplicationOpen="globalInfo.isTournamentApplicationOpen"
:isBettingActive="globalInfo.isBettingActive"
/>
</div>
<div id="actionMiniPlateSub" class="gx-0 row">
<div class="col">
<div class="gx-1 row">
<div class="col-3 d-grid">
<button type="button" class="btn btn-dark" @click="scrollToSelector('#reservedCommandPanel')">
명령으로
</button>
</div>
<div class="col-5 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="tryRefresh"> </button>
</div>
<div class="col-4 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
</div>
</div>
</div>
</div>
</div>
<div class="RecordZone row gx-0">
<div class="PublicRecord col col-12 col-lg-6">
<div class="bg1 center s-border-tb title">장수 동향</div>
<template v-for="[idx, rawText] of globalRecords.toArray()" :key="idx">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
</template>
</div>
<div class="GeneralLog col col-12 col-lg-6">
<div class="bg1 center s-border-tb title">개인 기록</div>
<template v-for="[idx, rawText] of generalRecords.toArray()" :key="idx">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
</template>
</div>
<div class="WorldHistory col col-12">
<div class="bg1 center s-border-tb title">중원 정세</div>
<template v-for="[idx, rawText] of worldHistory.toArray()" :key="idx">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
</template>
</div>
</div>
<div class="commonToolbar">
<GlobalMenu
v-if="globalMenu && globalInfo"
:globalInfo="globalInfo"
:modelValue="globalMenu"
variant="sammo-base2"
/>
</div>
<MessagePanel
v-if="generalInfo" v-if="generalInfo"
ref="msgPanel" :permission="generalInfo.permission"
:generalID="generalInfo.no" :showSecret="showSecret"
:generalName="generalInfo.name" :myLevel="generalInfo.officerLevel"
:nationID="generalInfo.nation" :nationLevel="nationStaticInfo?.level ?? 0"
:permissionLevel="generalInfo.permission" :nationColor="nationStaticInfo?.color.substring(1, 7) ?? '000000'"
:isTournamentApplicationOpen="globalInfo.isTournamentApplicationOpen"
:isBettingActive="globalInfo.isBettingActive"
/> />
<div class="commonToolbar"> </div>
<GlobalMenu <div id="actionMiniPlateSub" class="gx-0 row">
v-if="globalMenu && globalInfo" <div class="col">
:globalInfo="globalInfo" <div class="gx-1 row">
:modelValue="globalMenu" <div class="col-3 d-grid">
variant="sammo-base2" <button type="button" class="btn btn-dark" @click="scrollToSelector('#reservedCommandPanel')">
/> 명령으로
</button>
</div>
<div class="col-5 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="tryRefresh"> </button>
</div>
<div class="col-4 d-grid">
<button type="button" class="btn btn-sammo-base2" @click="moveLobby">로비로</button>
</div>
</div>
</div> </div>
</main> </div>
</BContainer> </div>
<div v-else>서버 갱신 중입니다.</div> <div class="RecordZone row gx-0">
</div> <div class="PublicRecord col col-12 col-lg-6">
<GameBottomBar <div class="bg1 center s-border-tb title">장수 동향</div>
v-if="frontInfo && globalMenu" <template v-for="[idx, rawText] of globalRecords.toArray()" :key="idx">
id="mobileBottomBar" <!-- eslint-disable-next-line vue/no-v-html -->
:frontInfo="frontInfo" <div :v-data-idx="idx" v-html="formatLog(rawText)" />
:globalMenu="globalMenu" </template>
@refresh="tryRefresh" </div>
/> <div class="GeneralLog col col-12 col-lg-6">
<BModal v-model="showVersionInfo" title="게임 정보"> <div class="bg1 center s-border-tb title">개인 기록</div>
{{ gameConstStore?.gameConst.title }}<br> <template v-for="[idx, rawText] of generalRecords.toArray()" :key="idx">
{{ gameConstStore?.version }}<br> <!-- eslint-disable-next-line vue/no-v-html -->
<!-- eslint-disable-next-line vue/no-v-html --> <div :v-data-idx="idx" v-html="formatLog(rawText)" />
<span v-html="gameConstStore?.gameConst.banner"></span> </template>
</BModal> </div>
</div> <div class="WorldHistory col col-12">
<div class="bg1 center s-border-tb title">중원 정세</div>
<template v-for="[idx, rawText] of worldHistory.toArray()" :key="idx">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
</template>
</div>
</div>
<div class="commonToolbar">
<GlobalMenu
v-if="globalMenu && globalInfo"
:globalInfo="globalInfo"
:modelValue="globalMenu"
variant="sammo-base2"
/>
</div>
<MessagePanel
v-if="generalInfo"
ref="msgPanel"
:generalID="generalInfo.no"
:generalName="generalInfo.name"
:nationID="generalInfo.nation"
:permissionLevel="generalInfo.permission"
/>
<div class="commonToolbar">
<GlobalMenu
v-if="globalMenu && globalInfo"
:globalInfo="globalInfo"
:modelValue="globalMenu"
variant="sammo-base2"
/>
</div>
</main>
</BContainer>
<div v-else>서버 갱신 중입니다.</div>
<GameBottomBar
v-if="frontInfo && globalMenu"
id="mobileBottomBar"
:class="nationColorClass"
:frontInfo="frontInfo"
:globalMenu="globalMenu"
@refresh="tryRefresh"
/>
<BModal v-model="showVersionInfo" title="게임 정보">
{{ gameConstStore?.gameConst.title }}<br />
{{ gameConstStore?.version }}<br />
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="gameConstStore?.gameConst.banner"></span>
</BModal>
</template> </template>
<script lang="ts"> <script lang="ts">
declare const staticValues: { declare const staticValues: {
@@ -229,15 +232,15 @@ const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store; gameConstStore.value = store;
}); });
const menuCallList: Record<string, ()=>unknown> = { const menuCallList: Record<string, () => unknown> = {
showVersion:()=>{ showVersion: () => {
console.log('version'); console.log("version");
showVersionInfo.value = !showVersionInfo.value; showVersionInfo.value = !showVersionInfo.value;
}, }
} };
function reqMenuCall(key:string){ function reqMenuCall(key: string) {
if(!(key in menuCallList)){ if (!(key in menuCallList)) {
console.error(`메뉴 호출 실패: ${key}`); console.error(`메뉴 호출 실패: ${key}`);
return; return;
} }
@@ -318,7 +321,7 @@ async function tryRefresh() {
} }
toasts.danger({ toasts.danger({
title: "갱신 실패", title: "갱신 실패",
body: response.reason, body: response.reason
}); });
return; return;
} }
@@ -328,10 +331,10 @@ async function tryRefresh() {
//TODO: 서버와 클라이언트 버전이 다르다면 갱신 필요 //TODO: 서버와 클라이언트 버전이 다르다면 갱신 필요
} catch (e) { } catch (e) {
responseLock = false; responseLock = false;
if(isString(e)){ if (isString(e)) {
toasts.danger({ toasts.danger({
title: "에러", title: "에러",
body: e, body: e
}); });
} }
console.error(e); console.error(e);
@@ -352,7 +355,7 @@ onMounted(async () => {
if (isString(e)) { if (isString(e)) {
toasts.danger({ toasts.danger({
title: "메뉴 갱신 실패", title: "메뉴 갱신 실패",
body: `${e}`, body: `${e}`
}); });
} }
console.error(e); console.error(e);
@@ -372,6 +375,15 @@ const generalRecords = ref(new Denque<[number, string]>());
const globalRecords = ref(new Denque<[number, string]>()); const globalRecords = ref(new Denque<[number, string]>());
const worldHistory = ref(new Denque<[number, string]>()); const worldHistory = ref(new Denque<[number, string]>());
const nationColorClass = ref('sam-color-000000');
watch(nationStaticInfo, (newNation)=>{
if(!newNation){
nationColorClass.value = 'sam-color-000000';
return;
}
nationColorClass.value = `sam-color-${newNation.color.substring(1, 7)}`;
});
let generalInfoLock = false; let generalInfoLock = false;
watch(refreshCounter, async () => { watch(refreshCounter, async () => {
@@ -382,7 +394,7 @@ watch(refreshCounter, async () => {
generalInfoLock = true; generalInfoLock = true;
const response = await SammoAPI.General.GetFrontInfo({ const response = await SammoAPI.General.GetFrontInfo({
lastGeneralRecordID: lastGeneralRecordID.value, lastGeneralRecordID: lastGeneralRecordID.value,
lastWorldHistoryID: lastWorldHistoryID.value, lastWorldHistoryID: lastWorldHistoryID.value
}); });
generalInfoLock = false; generalInfoLock = false;
@@ -405,14 +417,14 @@ watch(refreshCounter, async () => {
level: rawNation.level, level: rawNation.level,
capital: rawNation.capital, capital: rawNation.capital,
gennum: rawNation.gennum, gennum: rawNation.gennum,
power: rawNation.power, power: rawNation.power
}; };
const recentRecord = response.recentRecord; const recentRecord = response.recentRecord;
const recordIter = [ const recordIter = [
["flushGeneral", generalRecords, "general", lastGeneralRecordID], ["flushGeneral", generalRecords, "general", lastGeneralRecordID],
["flushGlobal", globalRecords, "global", lastGeneralRecordID], ["flushGlobal", globalRecords, "global", lastGeneralRecordID],
["flushHistory", worldHistory, "history", lastWorldHistoryID], ["flushHistory", worldHistory, "history", lastWorldHistoryID]
] as const; ] as const;
let haveNewRecord = false; let haveNewRecord = false;
@@ -438,21 +450,20 @@ watch(refreshCounter, async () => {
recordRef.value.unshift([id, formatLog(record)]); recordRef.value.unshift([id, formatLog(record)]);
} }
} }
if (refreshCounter.value <= 1){ if (refreshCounter.value <= 1) {
console.log('초기화 완료'); console.log("초기화 완료");
} } else if (haveNewRecord) {
else if (haveNewRecord) {
toasts.info( toasts.info(
{ {
title: "갱신 완료", title: "갱신 완료",
body: `동향 변경이 있습니다.`, body: `동향 변경이 있습니다.`
}, },
{ delay: 100 * 15 } { delay: 100 * 15 }
); );
} else { } else {
toasts.success( toasts.success(
{ {
title: "갱신 완료", title: "갱신 완료"
}, },
{ delay: 100 * 5 } { delay: 100 * 5 }
); );
@@ -464,10 +475,10 @@ watch(refreshCounter, async () => {
toasts.warning( toasts.warning(
{ {
title: "설문조사 안내", title: "설문조사 안내",
body: `새로운 설문조사가 있습니다.`, body: `새로운 설문조사가 있습니다.`
}, },
{ {
delay: 1000 * 60, delay: 1000 * 60
} }
); );
} }
@@ -476,7 +487,7 @@ watch(refreshCounter, async () => {
console.error(e); console.error(e);
toasts.danger({ toasts.danger({
title: "최근 정보 갱신 실패", title: "최근 정보 갱신 실패",
body: `${e}`, body: `${e}`
}); });
} }
}); });
@@ -506,13 +517,13 @@ watch(refreshCounter, async () => {
try { try {
map.value = await SammoAPI.Global.GetMap({ map.value = await SammoAPI.Global.GetMap({
neutralView: 0, neutralView: 0,
showMe: 1, showMe: 1
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toasts.danger({ toasts.danger({
title: "지도 갱신 실패", title: "지도 갱신 실패",
body: `${e}`, body: `${e}`
}); });
} }
}); });
@@ -525,6 +536,7 @@ watch(refreshCounter, async () => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@scss/common/break_500px.scss"; @import "@scss/common/break_500px.scss";
:deep() { :deep() {
@import "@scss/gameEvent.scss"; @import "@scss/gameEvent.scss";
@import "@scss/battleLog.scss"; @import "@scss/battleLog.scss";
@@ -534,12 +546,6 @@ watch(refreshCounter, async () => {
display: none; display: none;
} }
#outBlock {
display: flex;
flex-flow: column;
justify-content: space-between;
}
.generalInfo { .generalInfo {
width: 500px; width: 500px;
} }
@@ -556,30 +562,23 @@ watch(refreshCounter, async () => {
} }
} }
.generalCommandToolbar{ .generalCommandToolbar {
white-space: nowrap; white-space: nowrap;
} }
@include media-500px { @include media-500px {
#outBlock { #mobileBottomBar {
position: absolute; display: block;
top: 0; position: fixed;
left: 0; z-index: 99;
bottom: 0;
width: 100%; width: 100%;
height: 100%; left: 0;
#mobileBottomBar {
display: block;
}
}
#outBlock2 {
flex-grow: 1;
overflow-y: scroll;
} }
#container { #container {
width: 500px; width: 500px;
margin-bottom: 45px;
} }
#ingameBoard { #ingameBoard {