feat,refac: vue3로 작성한 새 연감 페이지 (#217)
- 새 지도 렌더러 사용 - 새 연감 데이터 사용 - 500px 모드 지원 - Vue3 활용 Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/217
This commit was merged in pull request #217.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<BContainer v-if="asyncReady" id="container" :toast="{ root: true }" class="bg0 pageHistory">
|
||||
<TopBackBar title="연감" type="close"></TopBackBar>
|
||||
<div class="center row mx-0 s-border-tb">
|
||||
<div class="col-md-1 col-2 year-selector text-end align-self-center">연월 선택:</div>
|
||||
<BButton class="col-md-1 col-2" @click="queryYearMonth -= 1">◀ 이전달</BButton>
|
||||
<div class="col-md-3 col-5 d-grid">
|
||||
<BFormSelect v-model="queryYearMonth" :options="generateYearMonthList()" />
|
||||
</div>
|
||||
<BButton class="col-md-1 col-2" @click="queryYearMonth += 1">다음달 ▶</BButton>
|
||||
</div>
|
||||
<div v-if="history" class="mx-0">
|
||||
<div class="row g-0">
|
||||
<div class="map_position">
|
||||
<MapViewer
|
||||
:server-nick="serverNick"
|
||||
:serverID="queryServerID"
|
||||
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
|
||||
:model-value="history.map"
|
||||
:is-detail-map="false"
|
||||
:city-position="cityPosition"
|
||||
:format-city-info="formatCityInfoText"
|
||||
:image-path="imagePath"
|
||||
:disallow-click="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="nation_position"><SimpleNationList :nations="history.nations" /></div>
|
||||
</div>
|
||||
<div class="world_history">
|
||||
<div class="bg1 center s-border-tb"><b>중원 정세</b></div>
|
||||
<div class="content">
|
||||
<template v-for="(item, idx) in history.global_history" :key="idx">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="formatLog(item)"></span>
|
||||
<br />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="general_public_record">
|
||||
<div class="bg1 center s-border-tb"><b>장수 동향</b></div>
|
||||
<div class="content">
|
||||
<template v-for="(item, idx) in history.global_action" :key="idx">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="formatLog(item)"></span>
|
||||
<br />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BottomBar type="close"></BottomBar>
|
||||
</BContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
declare const staticValues: {
|
||||
fisrtYearMonth: number;
|
||||
lastYearMonth: number;
|
||||
currentYearMonth: number;
|
||||
serverNick: string;
|
||||
serverID: string;
|
||||
};
|
||||
declare const query: {
|
||||
yearMonth: number | null;
|
||||
serverID: string;
|
||||
};
|
||||
|
||||
declare const getCityPosition: () => CityPositionMap;
|
||||
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, provide, ref, watch } from "vue";
|
||||
import { BContainer, BButton, BFormSelect } from "bootstrap-vue-3";
|
||||
import TopBackBar from "@/components/TopBackBar.vue";
|
||||
import BottomBar from "@/components/BottomBar.vue";
|
||||
import type { HistoryObj } from "./defs/API/Global";
|
||||
import { SammoAPI } from "./SammoAPI";
|
||||
import { joinYearMonth } from "./util/joinYearMonth";
|
||||
import { parseYearMonth } from "./util/parseYearMonth";
|
||||
import { formatLog } from "./utilGame/formatLog";
|
||||
import SimpleNationList from "./components/SimpleNationList.vue";
|
||||
import MapViewer, { type CityPositionMap, type MapCityParsedRaw, type MapCityParsed} from "./components/MapViewer.vue";
|
||||
import { getGameConstStore, type GameConstStore } from "./GameConstStore";
|
||||
import { unwrap } from "@/util/unwrap";
|
||||
|
||||
const queryYearMonth = ref<number>();
|
||||
const queryServerID = query.serverID;
|
||||
const serverNick = staticValues.serverNick;
|
||||
|
||||
const lastYearMonth = ref(staticValues.lastYearMonth);
|
||||
const firstYearMonth = ref(staticValues.fisrtYearMonth);
|
||||
const currentYearMonth = ref(staticValues.currentYearMonth);
|
||||
|
||||
const asyncReady = ref<boolean>(false);
|
||||
const gameConstStore = ref<GameConstStore>();
|
||||
provide("gameConstStore", gameConstStore);
|
||||
const storeP = getGameConstStore().then((store) => {
|
||||
gameConstStore.value = store;
|
||||
});
|
||||
|
||||
void Promise.all([storeP]).then(() => {
|
||||
asyncReady.value = true;
|
||||
});
|
||||
|
||||
const cityPosition = getCityPosition();
|
||||
const formatCityInfoText = formatCityInfo;
|
||||
const imagePath = window.pathConfig.gameImage;
|
||||
|
||||
const history = ref<HistoryObj>();
|
||||
|
||||
function generateYearMonthList(): { text: string; value: number }[] {
|
||||
const result: { text: string; value: number }[] = [];
|
||||
let yearMonth = firstYearMonth.value;
|
||||
while (yearMonth <= lastYearMonth.value) {
|
||||
const [year, month] = parseYearMonth(yearMonth);
|
||||
const info: string[] = [];
|
||||
if (queryYearMonth.value === yearMonth) {
|
||||
info.push("선택");
|
||||
}
|
||||
result.push({ text: `${year}년 ${month}월 ${info.length > 0 ? `(${info.join(", ")})` : ""}`, value: yearMonth });
|
||||
yearMonth += 1;
|
||||
}
|
||||
const [year, month] = parseYearMonth(yearMonth);
|
||||
const info: string[] = [];
|
||||
if (queryYearMonth.value === yearMonth) {
|
||||
info.push("선택");
|
||||
}
|
||||
info.push("현재");
|
||||
result.push({ text: `${year}년 ${month}월 ${info.length > 0 ? `(${info.join(", ")})` : ""}`, value: yearMonth });
|
||||
return result;
|
||||
}
|
||||
|
||||
watch(queryYearMonth, async (yearMonth) => {
|
||||
if (yearMonth === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (yearMonth < firstYearMonth.value) {
|
||||
queryYearMonth.value = firstYearMonth.value;
|
||||
return;
|
||||
}
|
||||
if (yearMonth > lastYearMonth.value + 1) {
|
||||
queryYearMonth.value = lastYearMonth.value + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (yearMonth > lastYearMonth.value) {
|
||||
try {
|
||||
const result = await SammoAPI.Global.GetCurrentHistory();
|
||||
history.value = result.data;
|
||||
console.log(result);
|
||||
const newLastYearMonth = joinYearMonth(result.data.year, result.data.month) - 1;
|
||||
if (newLastYearMonth > lastYearMonth.value) {
|
||||
lastYearMonth.value = newLastYearMonth;
|
||||
currentYearMonth.value = newLastYearMonth + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const [year, month] = parseYearMonth(yearMonth);
|
||||
try {
|
||||
const result = await SammoAPI.Global.GetHistory[queryServerID][year][month]();
|
||||
history.value = result.data;
|
||||
console.log(result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
queryYearMonth.value = (() => {
|
||||
if (query.yearMonth === null) {
|
||||
return staticValues.currentYearMonth;
|
||||
}
|
||||
if (query.yearMonth > staticValues.lastYearMonth + 1) {
|
||||
return staticValues.currentYearMonth;
|
||||
}
|
||||
if (query.yearMonth < staticValues.fisrtYearMonth) {
|
||||
return staticValues.currentYearMonth;
|
||||
}
|
||||
return query.yearMonth;
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
@@ -28,6 +28,7 @@
|
||||
"v_NPCControl": "v_NPCControl.ts",
|
||||
"v_join": "v_join.ts",
|
||||
"v_main": "v_main.ts",
|
||||
"v_history": "v_history.ts",
|
||||
"v_nationStratFinan": "v_nationStratFinan.ts",
|
||||
"v_processing": "v_processing.ts",
|
||||
"v_nationBetting": "v_nationBetting.ts",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="bg0" style="padding-top: 20px">
|
||||
<button type="button" class="btn btn-sammo-base2 back_btn" @click="back">돌아가기</button>
|
||||
<button type="button" class="btn btn-sammo-base2 back_btn" @click="back">
|
||||
{{ props.type == "close" ? "창 닫기" : "돌아가기" }}
|
||||
</button>
|
||||
<div />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<table class="simple_nation_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 44%">국명</th>
|
||||
<th style="width: 23%">국력</th>
|
||||
<th style="width: 15%">장수</th>
|
||||
<th style="width: 15%">속령</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="nation of nations" :key="nation.nation">
|
||||
<td>
|
||||
<span
|
||||
:style="{
|
||||
color: isBrightColor(nation.color) ? '#000' : '#fff',
|
||||
backgroundColor: nation.color,
|
||||
}"
|
||||
>{{ nation.name }}</span
|
||||
>
|
||||
</td>
|
||||
<td style="text-align: right">{{ nation.power.toLocaleString() }}</td>
|
||||
<td style="text-align: right">{{ nation.gennum.toLocaleString() }}</td>
|
||||
<td v-b-tooltip.hover style="text-align: right" :title="(nation.cities ?? []).join(', ')">
|
||||
{{ (nation.cities ?? []).length }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot></tfoot>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SimpleNationObj } from "@/defs";
|
||||
import type { PropType } from "vue";
|
||||
import { isBrightColor } from "@/util/isBrightColor";
|
||||
|
||||
defineProps({
|
||||
nations: {
|
||||
type: Array as PropType<SimpleNationObj[]>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.simple_nation_list {
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
background-color: #cccccc;
|
||||
color: black;
|
||||
text-align: center;
|
||||
}
|
||||
th {
|
||||
border: 0;
|
||||
border-left: 1px solid gray;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
td {
|
||||
border: 0;
|
||||
border-left: 1px solid gray;
|
||||
padding: 1px 6px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="['bg0', 'back_bar', teleportZone?'back_bar_teleport':undefined]">
|
||||
<button type="button" class="btn btn-sammo-base2 back_btn" @click="back">돌아가기</button
|
||||
<div :class="['bg0', 'back_bar', teleportZone ? 'back_bar_teleport' : undefined]">
|
||||
<button type="button" class="btn btn-sammo-base2 back_btn" @click="back">
|
||||
{{ props.type == "close" ? "창 닫기" : "돌아가기" }}</button
|
||||
><button v-if="reloadable" type="button" class="btn btn-sammo-base2 reload_btn" @click="reload">갱신</button>
|
||||
<div v-else />
|
||||
<h2 class="title">
|
||||
@@ -94,7 +95,7 @@ function reload() {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.teleport-zone{
|
||||
.teleport-zone {
|
||||
height: 24pt;
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -1,6 +1,6 @@
|
||||
import type { Args } from "@/processing/args";
|
||||
import type { ValidResponse, InvalidResponse } from "@/util/callSammoAPI";
|
||||
import type { GameObjClassKey } from "./GameObj";
|
||||
import type { GameIActionKey, GameObjClassKey } from "./GameObj";
|
||||
|
||||
export type { ValidResponse, InvalidResponse };
|
||||
export type BasicGeneralListResponse = {
|
||||
@@ -247,4 +247,17 @@ export type MapResult = {
|
||||
|
||||
theme?: string,
|
||||
history?: string[],
|
||||
}
|
||||
|
||||
|
||||
export type SimpleNationObj = {
|
||||
capital: number,
|
||||
cities: string[],
|
||||
color: string,
|
||||
gennum: number,
|
||||
level: number,
|
||||
name: string,
|
||||
nation: number,
|
||||
power: number,
|
||||
type: GameIActionKey
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
const regex = /<([RBGMCLSODYW]1?|1|\/)>/g;
|
||||
|
||||
//TODO: <R>에서 더 확장해서, <R|G>, <R|N> 형태로 뒤에 타입을 지정할 수 있도록 한다.
|
||||
|
||||
const convertMap: Record<string, string> = {
|
||||
"R": 'color: red;',
|
||||
"B": 'color: blue;',
|
||||
"G": 'color: green;',
|
||||
"M": 'color: magenta;',
|
||||
"C": 'color: cyan;',
|
||||
"L": 'color: limegreen;',
|
||||
"S": 'color: skyblue;',
|
||||
"O": 'color: orangered;',
|
||||
"D": 'color: orangered;',
|
||||
"Y": 'color: yellow;',
|
||||
"W": 'color: white;',
|
||||
"1": 'font-size: 0.9em;',
|
||||
}
|
||||
|
||||
const convertMap2: Record<string, string> = {
|
||||
"1": 'font-size: 0.9em;',
|
||||
}
|
||||
|
||||
export function formatLog(text?: string): string {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let matchRes;
|
||||
let lastIndex = 0;
|
||||
const result = [];
|
||||
while ((matchRes = regex.exec(text)) !== null) {
|
||||
const {
|
||||
0: partAll,
|
||||
1: subPart,
|
||||
index,
|
||||
} = matchRes;
|
||||
if (lastIndex != index) {
|
||||
result.push(text.slice(lastIndex, index));
|
||||
}
|
||||
|
||||
if (subPart == '/') {
|
||||
result.push(`</span>`);
|
||||
}
|
||||
else if (subPart.length == 2) {
|
||||
result.push(`<span style="${convertMap[subPart[0]] ?? ''}${convertMap2[subPart[1]] ?? ''}">`);
|
||||
}
|
||||
else {
|
||||
result.push(`<span style="${convertMap[subPart] ?? ''}">`);
|
||||
}
|
||||
|
||||
lastIndex = index + partAll.length;
|
||||
}
|
||||
|
||||
if (lastIndex != text.length) {
|
||||
result.push(text.slice(lastIndex));
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import { clamp } from "lodash";
|
||||
|
||||
export const TECH_LEVEL_YEAR_GAP = 5;
|
||||
export const TECH_LEVEL_STEP = 1000;
|
||||
|
||||
export function isTechLimited(startYear: number, year: number, tech: number, maxTechLevel: number): boolean {
|
||||
const relMaxTech = getMaxRelativeTechLevel(startYear, year, maxTechLevel);
|
||||
const techLevel = convTechLevel(tech, maxTechLevel);
|
||||
|
||||
return techLevel >= relMaxTech;
|
||||
}
|
||||
|
||||
export function convTechLevel(tech: number, maxTechLevel: number): number{
|
||||
return clamp(Math.floor(tech / TECH_LEVEL_STEP), 0, maxTechLevel);
|
||||
}
|
||||
|
||||
|
||||
export function getMaxRelativeTechLevel(startYear: number, year: number, maxTechLevel: number): number {
|
||||
const relYear = year - startYear;
|
||||
return clamp(Math.floor(relYear / TECH_LEVEL_YEAR_GAP) + 1, 1, maxTechLevel);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import "@scss/history.scss";
|
||||
import { createApp } from 'vue'
|
||||
import PageHistory from '@/PageHistory.vue';
|
||||
import { BootstrapVue3, BToastPlugin } from 'bootstrap-vue-3'
|
||||
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
|
||||
import { auto500px } from "./util/auto500px";
|
||||
import { htmlReady } from "./util/htmlReady";
|
||||
import { insertCustomCSS } from "./util/customCSS";
|
||||
|
||||
setAxiosXMLHttpRequest();
|
||||
auto500px();
|
||||
|
||||
htmlReady(() => {
|
||||
insertCustomCSS();
|
||||
});
|
||||
createApp(PageHistory).use(BootstrapVue3).use(BToastPlugin).mount('#app');
|
||||
Reference in New Issue
Block a user