feat: 감찰부

This commit is contained in:
2022-07-16 15:35:12 +09:00
parent 52164f8da7
commit 6fd7c11ee5
10 changed files with 231 additions and 94 deletions
+10
View File
@@ -254,6 +254,7 @@ function pushGeneralHistoryLog(int $generalID, ?array $history, $year=null, $mon
}
/** @deprecated */
function getGeneralHistoryLogAll(int $generalID):array {
$db = DB::db();
@@ -263,6 +264,15 @@ function getGeneralHistoryLogAll(int $generalID):array {
);
}
function getGeneralHistoryLogWithLogID(int $generalID):array {
$db = DB::db();
return Util::convertPairArrayToDict($db->queryAllLists(
'SELECT `id`, `text` from general_record WHERE general_id = %i AND log_type = "history" order by id desc',
$generalID
));
}
function pushNationHistoryLog(int $nationID, ?array $history, ?int $year=null, ?int $month=null) {
if(!$history){
+2 -2
View File
@@ -18,12 +18,12 @@ class GetGeneralLog extends GetNationGeneralLog
$v = new Validator($this->args);
$v
->rule('required', [
'type',
'reqType',
])
->rule('int', [
'reqTo',
])
->rule('in', 'type', [
->rule('in', 'reqType', [
self::GENERAL_HISTORY,
self::GENERAL_ACTION,
self::BATTLE_RESULT,
+6 -5
View File
@@ -14,6 +14,7 @@ use function sammo\getBattleResultRecent;
use function sammo\getGeneralActionLogMore;
use function sammo\getGeneralActionLogRecent;
use function sammo\getGeneralHistoryLogAll;
use function sammo\getGeneralHistoryLogWithLogID;
class GetGeneralLog extends \sammo\BaseAPI
{
@@ -28,13 +29,13 @@ class GetGeneralLog extends \sammo\BaseAPI
$v
->rule('required', [
'generalID',
'type',
'reqType',
])
->rule('integer', [
'generalID',
'reqTo',
])
->rule('in', 'type', [
->rule('in', 'reqType', [
self::GENERAL_HISTORY,
self::GENERAL_ACTION,
self::BATTLE_RESULT,
@@ -76,7 +77,7 @@ class GetGeneralLog extends \sammo\BaseAPI
return '같은 나라의 장수가 아닙니다.';
}
$reqType = $this->args['type'];
$reqType = $this->args['reqType'];
if (
$reqType === self::GENERAL_ACTION &&
$testGeneralNPCType < 2 &&
@@ -103,7 +104,7 @@ class GetGeneralLog extends \sammo\BaseAPI
$userID = $session->userID;
$targetGeneralID = $this->getTargetGeneralID($session);
$reqType = $this->args['type'];
$reqType = $this->args['reqType'];
$reqTo = $this->args['reqTo'] ?? null;
$db = DB::db();
@@ -124,7 +125,7 @@ class GetGeneralLog extends \sammo\BaseAPI
'result' => true,
'reqType' => $reqType,
'generalID' => $targetGeneralID,
'log' => getGeneralHistoryLogAll($targetGeneralID)
'log' => getGeneralHistoryLogWithLogID($targetGeneralID)
];
}
+33
View File
@@ -0,0 +1,33 @@
.small_war_log {
display: inline-block;
}
.small_war_log .war_type_attack {
color: cyan;
}
.small_war_log .war_type_defense {
color: magenta;
}
.small_war_log .war_type_siege {
color: white;
}
.small_war_log .name_plate {
font-size: 0.75em;
}
.small_war_log .name_plate_cover {
color: yellow;
}
.small_war_log .crew_plate {
color: orangered;
font-size: 90%;
}
.hidden_but_copyable {
color: rgba(0, 0, 0, 0);
font-size: 0;
}
-29
View File
@@ -105,35 +105,6 @@ div.bar_out div.bar_in {
content: "";
}
.small_war_log {
display: inline-block;
}
.small_war_log .war_type_attack {
color: cyan;
}
.small_war_log .war_type_defense {
color: magenta;
}
.small_war_log .war_type_siege {
color: white;
}
.small_war_log .name_plate {
font-size: 0.75em;
}
.small_war_log .name_plate_cover {
color: yellow;
}
.small_war_log .crew_plate {
color: orangered;
font-size: 90%;
}
.t_date {
font-size: 0.75em;
}
+124 -28
View File
@@ -1,33 +1,61 @@
<template>
<BContainer v-if="asyncReady" id="container" :toast="{ root: true }">
<TopBackBar title="감찰부" :reloadable="true" @reload="reload" />
<TopBackBar title="감찰부" :reloadable="true" type="close" @reload="reload" />
<div class="row gx-0">
<BFormSelect v-model="targetGeneralID">
<BFormSelectOption v-for="general of orderedGeneralList" :key="general.no" :value="general.no">
<span
:style="{
color: getNpcColor(general.npc),
}"
>{{ general.name }}</span
>
</BFormSelectOption>
</BFormSelect>
<div class="col-4">
<BFormSelect v-model="orderBy">
<BFormSelectOption v-for="[orderKey, [orderName, ]] of Object.entries(textMap)" :key="orderKey" :value="orderKey">
{{ orderName }}
</BFormSelectOption>
</BFormSelect>
</div>
<div class="col-8">
<BFormSelect v-model="targetGeneralID">
<BFormSelectOption v-for="general of orderedGeneralList" :key="general.no" :value="general.no">
<span
:style="{
color: getNpcColor(general.npc),
}"
>{{ general.name }}({{ general.turntime.slice(-5) }}){{ textMap[orderBy][3](general) }}</span
>
</BFormSelectOption>
</BFormSelect>
</div>
</div>
<div class="row gx-0">
<GeneralBasicCard v-if="targetGeneral && nationInfo" class="col-12 col-md-6" :general="targetGeneral" :nation="nationInfo" />
<div v-if="targetGeneral && nationInfo && targetGeneralLogs" class="row gx-0 bg0">
<div class="col-12 col-md-6">
<div class="bg1 header-cell" style="color: skyblue">장수 정보</div>
<GeneralBasicCard :general="targetGeneral" :nation="nationInfo" />
<GeneralSupplementCard :general="targetGeneral" />
</div>
<div class="col-12 col-md-6">
<div class="bg1 header-cell">장수 열전</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-for="[id, log] of targetGeneralLogs.generalHistory" :key="id" v-html="log" />
</div>
<div class="col-12 col-md-6">
<div class="bg1 header-cell">전투 기록</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-for="[id, log] of targetGeneralLogs.battleDetail" :key="id" v-html="log" />
</div>
<div class="col-12 col-md-6">
<div class="bg1 header-cell">전투 결과</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-for="[id, log] of targetGeneralLogs.battleResult" :key="id" v-html="log" />
</div>
<div v-if="targetGeneralLogs.generalAction.size > 0" class="col-12 col-md-6">
<div class="bg1 header-cell">개인 기록</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-for="[id, log] of targetGeneralLogs.generalAction" :key="id" v-html="log" />
</div>
</div>
<BottomBar></BottomBar>
<BottomBar type="close"></BottomBar>
</BContainer>
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
mapName: string;
unitSet: string;
};
declare const queryValues: {
generalID: number | null;
};
@@ -44,16 +72,20 @@ import { unwrap } from "@/util/unwrap";
import { merge2DArrToObjectArr } from "@/util/merge2DArrToObjectArr";
import { getNpcColor } from "@/common_legacy";
import GeneralBasicCard from "./components/GeneralBasicCard.vue";
import GeneralSupplementCard from "@/components/GeneralSupplementCard.vue";
import type { NationStaticItem } from "./defs";
import type { GeneralLogType } from "./defs/API/General";
import { isString } from "lodash";
import { formatLog } from "./utilGame/formatLog";
const toasts = unwrap(useToast());
const generalList = ref(new Map<number, GeneralListItemP1>());
const textMap = {
recent_war: ["최근 전투", (gen: GeneralListItemP1) => gen.recent_war, false],
warnum: ["전투 횟수", (gen: GeneralListItemP1) => gen.warnum, false],
turntime: ["최근 턴", (gen: GeneralListItemP1) => gen.turntime, false],
name: ["이름", (gen: GeneralListItemP1) => `${gen.npc} ${gen.name}`, false],
recent_war: ["최근 전투", (gen: GeneralListItemP1) => gen.recent_war, false, (gen: GeneralListItemP1) => `[${gen.recent_war.slice(-5)}]`],
warnum: ["전투 횟수", (gen: GeneralListItemP1) => gen.warnum, false, (gen: GeneralListItemP1) => `[${gen.warnum}회]`],
turntime: ["최근 턴", (gen: GeneralListItemP1) => gen.turntime, false, (gen: GeneralListItemP1) => ''],
name: ["이름", (gen: GeneralListItemP1) => `${gen.npc} ${gen.name}`, false, (gen: GeneralListItemP1) => ''],
} as const;
const orderedGeneralList = ref<GeneralListItemP1[]>([]);
@@ -61,25 +93,81 @@ const orderBy = ref<keyof typeof textMap>("turntime");
const targetGeneral = ref<GeneralListItemP1>();
const targetGeneralID = ref(queryValues.generalID ?? undefined);
type GeneralLogs = {
[key in GeneralLogType]: Map<number, string>;
};
const targetGeneralLogs = ref<GeneralLogs>();
const nationInfo = ref<NationStaticItem>();
watch([generalList, targetGeneralID], ([generalList, targetGeneralID]) => {
if (targetGeneralID === undefined) return;
watch([generalList, targetGeneralID], async ([generalList, targetGeneralID]) => {
if (targetGeneralID === undefined){
targetGeneral.value = undefined;
return;
};
targetGeneral.value = generalList.get(targetGeneralID);
const logs: GeneralLogs = {
generalHistory: new Map(),
battleResult: new Map(),
battleDetail: new Map(),
generalAction: new Map(),
};
const waiter: Promise<unknown>[] = [];
for (const reqType of ["generalHistory", "battleResult", "battleDetail", "generalAction"] as const) {
waiter.push(
SammoAPI.Nation.GetGeneralLog({ generalID: targetGeneralID, reqType }).then(
(res) => {
const rawLogs: [number, string][] = Object.entries(res.log).map(([key, value]) => [
Number(key),
formatLog(value),
]);
rawLogs.sort(([keyLhs], [keyRhs]) => keyRhs - keyLhs);
for (const [idx, log] of rawLogs) {
logs[reqType].set(Number(idx), formatLog(log));
}
},
(err) => {
if (!isString(err)) {
toasts.danger({
title: "에러",
body: `${err}`,
});
}
}
)
);
}
await Promise.all(waiter);
console.log(logs);
targetGeneralLogs.value = logs;
});
watch([generalList, orderBy], ([generalList, orderBy]) => {
watch([generalList, orderBy], ([generalList, orderBy], [, oldOrderBy]) => {
console.log(generalList);
const list = Array.from(generalList.values());
if(orderBy != oldOrderBy){
targetGeneralID.value = undefined;
}
const idSet = new Set<number>(list.map((gen) => gen.no));
list.sort((a, b) => {
const [, getter, isAsc] = textMap[orderBy];
const [, getter, isAsc, ] = textMap[orderBy];
const aVal = getter(a);
const bVal = getter(b);
if (aVal === bVal) return 0;
return isAsc ? (aVal > bVal ? 1 : -1) : aVal < bVal ? 1 : -1;
});
orderedGeneralList.value = list;
targetGeneralID.value = list[0].no;
const oldTargetGeneralID = targetGeneralID.value;
if (!oldTargetGeneralID) {
targetGeneralID.value = list[0].no;
} else if (!idSet.has(oldTargetGeneralID)) {
targetGeneralID.value = list[0].no;
}
});
const asyncReady = ref(false);
@@ -128,3 +216,11 @@ onMounted(async () => {
await reload();
});
</script>
<style lang="scss" scoped>
.header-cell {
color: orange;
font-size: 1.3em;
text-align: center;
border: 1px gray solid;
}
</style>
+1 -1
View File
@@ -97,7 +97,7 @@ async function reload() {
}
function openBattleCenter(generalID: number){
window.open(`b_battleCenter.php?gen=${generalID}`)
window.open(`v_battleCenter.php?gen=${generalID}`)
}
onMounted(async () => {
+1 -1
View File
@@ -18,7 +18,7 @@ import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Bett
import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse } from "./defs/API/Command";
import type { ChiefResponse } from "./defs/API/NationCommand";
import type { inheritBuffType, InheritLogResponse } from "./defs/API/InheritAction";
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse, LiteNationInfoResponse, NationInfoResponse } from "./defs/API/Nation";
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse, NationInfoResponse } from "./defs/API/Nation";
import type { UploadImageResponse } from "./defs/API/Misc";
import type { GeneralLogType, GetGeneralLogResponse, JoinArgs } from "./defs/API/General";
import type {
+53 -27
View File
@@ -1,29 +1,29 @@
<template>
<div class="general-card-supplement row gx-0">
<div class="general-card-supplement row gx-0 bg2">
<div class="col-12 general-card-info">
<div class="part-title">추가 정보</div>
<div>명성</div>
<div class="part-title bg1">추가 정보</div>
<div class="bg1">명성</div>
<div>{{ formatHonor(general.experience) }} ({{ general.experience.toLocaleString() }})</div>
<div>계급</div>
<div class="bg1">계급</div>
<div>{{ general.dedLevelText }} ({{ general.dedication.toLocaleString() }}</div>
<div>봉급</div>
<div class="bg1">봉급</div>
<div>{{ general.bill.toLocaleString() }}</div>
<div>전투</div>
<div class="bg1">전투</div>
<div>{{ general.warnum.toLocaleString() }}</div>
<div>계략</div>
<div class="bg1">계략</div>
<div>{{ general.firenum.toLocaleString() }}</div>
<div>사관</div>
<div class="bg1">사관</div>
<div>{{ general.belong }}년차</div>
<div>승률</div>
<div class="bg1">승률</div>
<div>{{ ((general.killnum / Math.max(general.warnum, 1)) * 100).toFixed(2) }} %</div>
<div>승리</div>
<div class="bg1">승리</div>
<div>{{ general.killnum.toLocaleString() }}</div>
<div>패배</div>
<div class="bg1">패배</div>
<div>{{ general.deathnum.toPrecision() }}</div>
<div>살상률</div>
<div class="bg1">살상률</div>
<div>
{{
((general.killcrew / Math.max(general.deathcrew, 1)) * 100).toLocaleString(undefined, {
@@ -33,23 +33,26 @@
}}
%
</div>
<div>사살</div>
<div class="bg1">사살</div>
<div>{{ general.killcrew.toLocaleString() }}</div>
<div>피살</div>
<div class="bg1">피살</div>
<div>{{ general.deathcrew.toLocaleString() }}</div>
</div>
<div :class="[props.showCommandList ? 'col-7' : 'col', 'general-card-dex']">
<div class="part-title">숙련도</div>
<div class="part-title bg1">숙련도</div>
<template v-for="[dexType, dex, dexInfo] of dexList" :key="dexType">
<div>{{ dexType }}</div>
<div class="bg1">{{ dexType }}</div>
<div :style="{ color: dexInfo.color }">{{ dexInfo.name }}</div>
<div>{{ (dex / 1000).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) }}K</div>
<div>
<SammoBar :height="10" :percent="dex / 1_000_000" />
<div class="d-grid">
<div class="align-self-center"><SammoBar :height="10" :percent="dex / 1_000_000 * 100" /></div>
</div>
</template>
</div>
<div v-if="props.showCommandList && general.reservedCommand?.length > 0" class="col-5 general-card-turn">
<div
v-if="props.showCommandList && general.reservedCommand && general.reservedCommand.length > 0"
class="col-5 general-card-turn"
>
<div class="part-title">예약턴</div>
<div v-for="(turn, idx) in general.reservedCommand.slice(0, 5)" :key="idx">
{{ turn.brief }}
@@ -81,13 +84,14 @@ const dexList = computed((): [string, number, DexInfo][] => {
</script>
<style lang="scss" scoped>
.general-card-basic {
display: grid;
grid-template-columns: 64px repeat(3, 2fr 5fr);
grid-template-rows: repeat(9, calc(64px / 3));
.general-card-supplement {
text-align: center;
font-size: 14px;
}
.part-title {
grid-column: 1 / 7;
}
.general-icon {
width: 64px;
height: 64px;
@@ -122,9 +126,18 @@ const dexList = computed((): [string, number, DexInfo][] => {
display: grid;
grid-template-columns: repeat(3, 64px 1fr);
grid-template-rows: repeat(5, calc(64px / 3));
border-right: solid 1px gray;
.part-title {
grid-column: 1 / 4;
grid-column: 1 / 7;
}
.bg1 {
border-left: solid 1px gray;
}
> div {
border-bottom: solid 1px gray;
}
}
@@ -132,15 +145,28 @@ const dexList = computed((): [string, number, DexInfo][] => {
display: grid;
grid-template-columns: 64px 40px 60px 1fr;
grid-template-rows: repeat(6, calc(64px / 3));
border-right: solid 1px gray;
.part-title {
grid-column: 1 / 5;
}
.bg1 {
border-left: solid 1px gray;
}
> div {
border-bottom: solid 1px gray;
}
}
.general-card-turn {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(6, calc(64px / 3));
.bg1 {
border-left: solid 1px gray;
}
> div {
border-bottom: solid 1px gray;
}
}
</style>
+1 -1
View File
@@ -1,7 +1,7 @@
import "@scss/nationGeneral.scss";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham-dark.css";
import "@scss/battleLog.scss";
import { createApp } from 'vue'
import PageBattleCenter from '@/PageBattleCenter.vue';
import { BootstrapVue3, BToastPlugin } from 'bootstrap-vue-3';