feat(WIP): processing을 vue로 전환

This commit is contained in:
2021-12-18 04:36:06 +09:00
parent 4ca33cb007
commit 6c93d2fd85
24 changed files with 694 additions and 382 deletions
+13 -38
View File
@@ -77,55 +77,30 @@ $cssList = $commandObj->getCSSFiles();
<meta name="viewport" content="width=1024" />
<?= WebUtil::printJS('../d_shared/common_path.js') ?>
<?= WebUtil::printJS('d_shared/base_map.js') ?>
<?= WebUtil::printStaticValues([
'serverNick' => DB::prefix(),
'serverID' => UniqueConst::$serverID,
'commandName' => $commandObj->getName(),
'turnList' => $turnList,
'currentCity' => $general->getCityID(),
'currentNation' => $general->getNationID(),
'entryInfo' => [$isChiefTurn?'Nation':'General', $commandType]
])?>
<?= WebUtil::printStaticValues($commandObj->exportJSVars(), false) ?>
<script>
window.serverNick = '<?= DB::prefix() ?>';
window.serverID = '<?= UniqueConst::$serverID ?>';
window.command = '<?= $commandType ?>';
window.turnList = [<?= join(', ', $turnList) ?>];
window.isChiefTurn = <?= $isChiefTurn ? 'true' : 'false' ?>;
var jsPlugins = <?= Json::encode($jsList) ?>;
</script>
<?= WebUtil::printCSS('../e_lib/select2/select2.min.css') ?>
<?= WebUtil::printCSS('../e_lib/select2/select2-bootstrap4.css') ?>
<?= WebUtil::printCSS('../d_shared/common.css') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css" />
<?= WebUtil::printCSS('css/main.css') ?>
<?= WebUtil::printCSS('css/map.css') ?>
<?= WebUtil::printCSS('css/processing.css') ?>
<?php
foreach ($cssList as $css) {
print(WebUtil::printCSS($css));
}
?>
<?= WebUtil::printDist('ts', ['common', 'processing']) ?>
<?= WebUtil::printDist('vue', ['v_processing'], true) ?>
</head>
<body class="img_back">
<table class="tb_layout bg0" style="width:1000px;margin:auto;">
<tr>
<td class="bg1" style='text-align:center;'><?= $commandObj->getName() ?></td>
</tr>
<tr>
<td>
<input type=button value='돌아가기' onclick="history.back();"><br>
</td>
</tr>
</table>
<div class="tb_layout bg0" style="width:1000px;margin:auto;padding-bottom:2em;border:solid 1px gray;">
<?= $commandObj->getForm() ?>
</div>
<table class="tb_layout bg0" style="width:1000px;margin:auto;">
<tr>
<td>
<input type=button value='돌아가기' onclick="history.back();"><br>
<?= banner() ?>
</td>
</tr>
</table>
<div id="container"></div>
</body>
</html>
-11
View File
@@ -1,11 +0,0 @@
#amount {
width: 100px;
}
#colorType {
width: 150px;
}
.no-padding .select2-results__option {
padding: 0;
}
+21
View File
@@ -52,6 +52,18 @@ function bar($per, $h = 7)
return $str;
}
function JSOptionsForCities(callable $infoCall = null){
$result = [];
foreach (CityConst::all() as $city) {
$item = [$city->id, $city->name];
if($infoCall){
$item[2] = $infoCall($city);
}
$result[] = $item;
}
return $result;
}
function optionsForCities(callable $infoCall = null)
{
@@ -116,6 +128,15 @@ function closeButton()
return "<button type='button' class='btn btn-primary back_btn' onclick=window.close()>창 닫기</button><br>";
}
function JSCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): array{
$distanceList = searchDistance($cityNo, $maxDistance, true);
$result = [];
for ($dist = 1; $dist <= $maxDistance; $dist++) {
$result[$dist] = Util::array_get($distanceList[$dist], []);
}
return $result;
}
function printCitiesBasedOnDistance(int $cityNo, int $maxDistance = 1): string
{
+8
View File
@@ -225,6 +225,14 @@ function formatName(string $name, int $npc): string
return $name;
}
function getMapTheme(): string
{
$db = DB::db();
$gameStor = KVStorage::getStorage($db, 'game_env');
$mapTheme = $gameStor->map_theme ?? 'che';
return $mapTheme;
}
function getMapHtml(?string $mapTheme = null)
{
$templates = new \League\Plates\Engine(__DIR__ . '/templates');
+3
View File
@@ -478,6 +478,9 @@ abstract class BaseCommand{
public function getJSPlugins():array {
return [];
}
public function exportJSVars():array {
return [];
}
public function getCSSFiles():array {
return [];
}
+11 -1
View File
@@ -12,6 +12,7 @@ use \sammo\GameUnitConst;
use \sammo\LastTurn;
use \sammo\Command;
use function sammo\getMapTheme;
use function \sammo\printCitiesBasedOnDistance;
use function sammo\tryUniqueItemLottery;
@@ -176,6 +177,15 @@ class che_강행 extends Command\GeneralCommand
];
}
public function exportJSVars(): array
{
return [
'cities' => \sammo\JSOptionsForCities(),
'mapTheme' => getMapTheme(),
'distanceList' => \sammo\JSCitiesBasedOnDistance($this->generalObj->getCityID(), 3),
];
}
public function getForm(): string
{
$currentCityID = $this->generalObj->getCityID();
@@ -187,7 +197,7 @@ class che_강행 extends Command\GeneralCommand
선택된 도시로 강행합니다.<br>
최대 3칸내 도시로만 강행이 가능합니다.<br>
목록을 선택하거나 도시를 클릭하세요.<br>
<?= $currentCityName ?> => <select class='formInput' name="destCityID" id="destCityID" size='1' style='color:white;background-color:black;'><br>
<?= $currentCityName ?> => <div id="setDestCityIDForm"></div><br>
<?= \sammo\optionsForCities() ?><br>
</select> <input type=button id="commonSubmit" value="<?= $this->getName() ?>"><br>
<br>
+7
View File
@@ -184,6 +184,13 @@ class che_이동 extends Command\GeneralCommand
];
}
public function exportJSVars(): array
{
return [
'cities' => \sammo\JSOptionsForCities()
];
}
public function getForm(): string
{
$currentCityID = $this->generalObj->getCityID();
+37
View File
@@ -0,0 +1,37 @@
@import "./common/break_500px.scss";
@import "./common/bootstrap5.scss";
@import "./editor_component.scss";
@import "./common_legacy.scss";
#amount {
width: 100px;
}
#colorType {
width: 150px;
}
.no-padding .select2-results__option {
padding: 0;
}
@include media-breakpoint-up(md) {
#container {
width: 1000px;
margin: 0 auto;
}
}
@include media-breakpoint-down(md) {
body {
overflow-x: hidden;
}
#container {
width: 500px;
margin: 0 auto;
margin-bottom: 100px;
}
}
+2 -3
View File
@@ -7,7 +7,6 @@
"install": "install.ts",
"battle_simulator": "battle_simulator.ts",
"recent_map": "recent_map.ts",
"processing": "processing.ts",
"select_npc": "select_npc.ts",
"betting": "betting.ts",
"bossInfo": "bossInfo.ts",
@@ -27,7 +26,7 @@
"v_NPCControl": "v_NPCControl.ts",
"v_join": "v_join.ts",
"v_main": "v_main.ts",
"v_dipcenter": "v_dipcenter.ts"
"v_dipcenter": "v_dipcenter.ts",
"v_processing": "v_processing.ts"
}
}
+130
View File
@@ -0,0 +1,130 @@
<template>
<div
:id="uuid"
:class="['world_map', `map_theme_${mapTheme}`, 'draw_required']"
>
<div
class="map_title obj_tooltip"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-tooltip-class="map_title_tooltiptext"
>
<span class="map_title_text"> </span>
<span class="tooltiptext"></span>
</div>
<div class="map_body">
<div class="map_bglayer1"></div>
<div class="map_bglayer2"></div>
<div class="map_bgroad"></div>
<div class="map_button_stack">
<button
type="button"
class="btn btn-primary map_toggle_cityname btn-sm btn-minimum"
data-bs-toggle="button"
aria-pressed="false"
autocomplete="off"
>
도시명 표기</button
><br />
<button
type="button"
class="btn btn-secondary map_toggle_single_tap btn-sm btn-minimum"
data-bs-toggle="button"
aria-pressed="false"
autocomplete="off"
>
두번 도시 이동
</button>
</div>
</div>
<div class="city_tooltip">
<div class="city_name"></div>
<div class="nation_name"></div>
</div>
</div>
</template>
<script lang="ts">
import { reloadWorldMap, loadMapOption, MapCityParsed } from "@/map";
import { defineComponent, onMounted, PropType, ref } from "vue";
import { v4 as uuidv4 } from "uuid";
export type { MapCityParsed };
export default defineComponent({
props: {
mapTheme: {
type: String,
required: true,
},
isDetailMap: { type: Boolean, default: undefined },
clickableAll: { type: Boolean, default: undefined },
selectCallback: {
type: Function as PropType<loadMapOption["selectCallback"]>,
required: false,
},
hrefTemplate: { type: String, default: undefined },
useCachedMap: { type: Boolean, default: undefined },
year: { type: Number, default: undefined },
month: { type: Number, default: undefined },
aux: Object as PropType<loadMapOption["aux"]>,
neutralView: { type: Boolean, default: undefined },
showMe: { type: Boolean, default: undefined },
targetJson: { type: String, default: undefined },
reqType: {
type: String as PropType<loadMapOption["reqType"]>,
default: undefined,
},
dynamicMapTheme: { type: Boolean, default: undefined },
callback: {
type: Function as PropType<loadMapOption["callback"]>,
default: undefined,
},
startYear: { type: Number, default: undefined },
modelValue: {
type: Object as PropType<MapCityParsed>,
required: false,
},
},
emits: ["update:modelValue", "loaded"],
setup(props, { emit }) {
const uuid = uuidv4();
const modelValue = ref(props.modelValue);
onMounted(async () => {
const option: loadMapOption = {
isDetailMap: props.isDetailMap,
clickableAll: props.clickableAll,
selectCallback: (city) => {
modelValue.value = city;
emit("update:modelValue", city);
},
hrefTemplate: props.hrefTemplate,
useCachedMap: props.useCachedMap,
year: props.year,
month: props.month,
aux: props.aux,
neutralView: props.neutralView,
showMe: props.showMe,
targetJson: props.targetJson,
reqType: props.reqType,
dynamicMapTheme: props.dynamicMapTheme,
callback: (a, rawObject) => {
emit("loaded", [a, rawObject]);
},
startYear: props.startYear,
};
console.log(option);
await reloadWorldMap(option, `#${uuid}`);
});
return {
uuid,
};
},
});
</script>
+6 -8
View File
@@ -1,21 +1,19 @@
import { reloadWorldMap } from "@/map";
import { unwrap_any } from "@util/unwrap_any";
declare let vueReactive_destCityID: number|undefined;
export function defaultSelectCityByMap(): void {
const $target = $("#destCityID");
console.log('city', $target);
void reloadWorldMap({
isDetailMap: false,
clickableAll: true,
neutralView: true,
useCachedMap: true,
selectCallback: function (city) {
const currVal = unwrap_any<string>($target.val());
$target.val(city.id);
$target.trigger("change");
if ($target.val() === null) {
$target.val(currVal).trigger("change").blur();
if(vueReactive_destCityID === undefined){
console.error('아직 초기화 되지 않음');
return false;
}
vueReactive_destCityID = city.id;
return false;
}
});
+6 -8
View File
@@ -1,5 +1,6 @@
import { reloadWorldMap } from "@/map";
import { unwrap_any } from "@util/unwrap_any";
declare let vueReactive_destNationID: number|undefined;
export function defaultSelectNationByMap(): void{
const $target = $("#destNationID");
@@ -9,15 +10,12 @@ export function defaultSelectNationByMap(): void{
clickableAll: true,
neutralView: true,
useCachedMap: true,
selectCallback: (city)=>{
const currVal = unwrap_any<string>($target.val());
if(!city.nationID){
selectCallback: function (city) {
if(typeof vueReactive_destNationID === 'undefined'){
console.error('아직 초기화 되지 않음');
return false;
}
$target.val(city.nationID).trigger("change");
if ($target.val() === null) {
$target.val(currVal).trigger("change");
}
vueReactive_destNationID = city.nationID;
return false;
}
});
+13 -10
View File
@@ -1,11 +1,12 @@
import axios from 'axios';
import $ from 'jquery';
import { extend, isNumber } from 'lodash';
import { extend, isNumber, merge } from 'lodash';
import { convColorValue, convertDictById, stringFormat } from '@/common_legacy';
import { InvalidResponse } from '@/defs';
import { unwrap } from "@util/unwrap";
import { convertFormData } from '@util/convertFormData';
import { exportWindow } from '@util/exportWindow';
import { htmlReady } from './util/htmlReady';
declare const serverNick: string;
declare const serverID: string;
@@ -57,7 +58,7 @@ type MapCityParsedRegionLevelText = MapCityParsedClickable & {
text: string,
}
type MapCityParsed = MapCityParsedRegionLevelText;
export type MapCityParsed = MapCityParsedRegionLevelText;
type MapCityDrawable = {
cityList: MapCityParsed[],
@@ -107,7 +108,7 @@ function is_touch_device(): boolean {
return mq(query);
}
type loadMapOption = {
export type loadMapOption = {
isDetailMap?: boolean, //복잡 지도, 단순 지도
clickableAll?: boolean, //어떤 경우든 클릭을 가능하게 함. 해당 동작의 동작 가능성 여부와는 별도.
selectCallback?: (city: MapCityParsed) => void, //callback을 지정시 clickable과 관계 없이 해당 함수를 실행.
@@ -160,7 +161,7 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
startYear: undefined,
};
option = extend({}, defaultOption, option);
option = merge({}, defaultOption, option);
const useCachedMap = option.useCachedMap;
const isDetailMap = option.isDetailMap;
@@ -671,7 +672,6 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
const $hideCityNameBtn = $world_map.find('.map_toggle_cityname');
if (localStorage.getItem('sam.hideMapCityName') == 'yes') {
console.log('tryHide!');
$world_map.addClass('hide_cityname');
$hideCityNameBtn.addClass('active').attr('aria-pressed', 'true');
}
@@ -680,7 +680,6 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
//이전 상태 확인
const state = localStorage.getItem('sam.hideMapCityName') == 'no';
if (state) {
console.log('tryHide!');
$world_map.addClass('hide_cityname');
localStorage.setItem('sam.hideMapCityName', 'yes');
} else {
@@ -789,8 +788,12 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
}
exportWindow(reloadWorldMap, 'reloadWorldMap');
$(function ($) {
if (is_touch_device()) {
$('.map_body .map_toggle_single_tap').show();
htmlReady(function(){
if( is_touch_device()){
const target = document.querySelector('.map_body .map_toggle_single_tap') as HTMLElement | null;
if(!target){
return;
}
target.style.display = 'block';
}
})
});
-300
View File
@@ -1,300 +0,0 @@
import $ from 'jquery';
exportWindow($, '$');
import { exportWindow } from '@util/exportWindow';
import axios from 'axios';
import 'bootstrap';
import 'select2/dist/js/select2.full.js'
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
import { convertFormData } from '@util/convertFormData';
import { InvalidResponse } from '@/defs';
import { unwrap_any } from '@util/unwrap_any';
import { DataFormat, IdTextPair, OptionData } from 'select2';
import { unwrap } from "@util/unwrap";
import { defaultSelectCityByMap } from '@/defaultSelectCityByMap';
import { defaultSelectNationByMap } from '@/defaultSelectNationByMap';
import { colorSelect } from '@/colorSelect';
import { recruitCrewForm } from '@/recruitCrewForm';
declare const isChiefTurn: boolean;
declare const jsPlugins: string[];
declare global {
interface Window {
submitAction: () => Promise<void>;
turnList: number[],
command: string,
}
}
async function reserveTurn(turnList: number[], command: string, arg: Record<string, unknown>) {
const target = isChiefTurn ? 'j_set_chief_command.php' : 'j_set_general_command.php';
let data: InvalidResponse;
try {
const response = await axios({
url: target,
responseType: 'json',
method: 'post',
data: convertFormData({
action: command,
turnList: turnList,
arg: JSON.stringify(arg)
})
});
data = response.data;
}
catch (e) {
console.error(e);
alert(`에러가 발생했습니다: ${e}`);
return;
}
if (!data.result) {
alert(data.reason);
return;
}
if (!isChiefTurn) {
window.location.href = './';
} else {
window.location.href = 'b_chiefcenter.php';
}
}
$(function ($) {
setAxiosXMLHttpRequest();
//checkCommandArg 참고
const availableArgumentList = {
'string': [
'nationName', 'optionText', 'itemType', 'nationType', 'itemCode', 'commandType',
],
'int': [
'crewType', 'destGeneralID', 'destCityID', 'destNationID',
'amount', 'colorType',
'year', 'month',
'srcArmType', 'destArmType', //숙련전환 전용
],
'boolean': [
'isGold', 'buyRice',
],
'integerArray': [
'destNationIDList', 'destGeneralIDList', 'amountList'
]
}
type argTypes = keyof typeof availableArgumentList;
type argValues = string | number | boolean | number[];
const handlerList: Record<argTypes, ($obj: JQuery<HTMLInputElement>) => argValues> = {
'string': function ($obj: JQuery<HTMLInputElement>) {
return $.trim(unwrap_any<string>($obj.eq(0).val()));
},
'int': function ($obj: JQuery<HTMLInputElement>) {
return parseInt(unwrap_any<string>($obj.eq(0).val()));
},
'boolean': function ($obj: JQuery<HTMLInputElement>) {
switch (unwrap_any<string>($obj.eq(0).val()).toLowerCase()) {
case "true":
case "yes":
case "1":
return true;
case "false":
case "no":
case "0":
return false;
default:
throw new Error("Boolean.parse: Cannot convert string to boolean.");
}
},
'integerArray': function ($obj: JQuery<HTMLInputElement>) {
const result: number[] = [];
$obj.each(function () {
result.push(parseInt(unwrap_any<string>($(this).val())));
});
return result;
}
}
window.submitAction = async function (): Promise<void> {
const argument: Record<string, argValues> = {};
for (const typeName of Object.keys(availableArgumentList) as argTypes[]) {
const typeKeys = availableArgumentList[typeName];
for (const typeKey of typeKeys) {
let $obj = $('#' + typeKey) as JQuery<HTMLInputElement>;
if ($obj.length == 0) {
$obj = $('.' + typeKey);
if ($obj.length == 0) {
continue;
}
}
argument[typeKey] = handlerList[typeName]($obj);
}
}
console.log(argument);
await reserveTurn(window.turnList, window.command, argument);
};
$('#commonSubmit').on('click', window.submitAction);
const $colorType = $('#colorType');
if ($colorType.length) {
$colorType.select2({
theme: 'bootstrap4',
placeholder: "색상을 선택해 주세요.",
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
templateSelection: function (item) {
if ((item as DataFormat).disabled) {
return item.text;
}
const element = (item as OptionData).element;
if (!element) {
throw 'invalid type';
}
const bgcolor = unwrap(element.dataset.color);
const fgcolor = unwrap(element.dataset.fontColor);
// eslint-disable-next-line no-irregular-whitespace
return $(`<span><span style='background-color:${bgcolor};color:${fgcolor};'> </span>&nbsp;${item.text}</span>`);
},
templateResult: function (item) {
if ((item as DataFormat).disabled) {
return item.text;
}
const element = (item as OptionData).element;
if (!element) {
throw 'invalid type';
}
const bgcolor = unwrap(element.dataset.color);
const fgcolor = unwrap(element.dataset.fontColor);
return $(`<div style='padding: 0.75rem 0.375rem; background-color:${bgcolor};color:${fgcolor};'>${item.text}</div>`);
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'no-padding simple-select2-align-center bg-secondary text-secondary',
});
}
const $nationType = $('#nationType');
if ($nationType.length) {
$nationType.select2({
theme: 'bootstrap4',
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
});
}
const $destCityID = $('#destCityID');
if ($destCityID.length) {
$destCityID.select2({
theme: 'bootstrap4',
placeholder: "도시를 선택해 주세요.",
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
});
}
const $destNationID = $('#destNationID');
if ($destNationID.length) {
$destNationID.select2({
theme: 'bootstrap4',
placeholder: "국가를 선택해 주세요.",
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
});
}
const $destGeneralID = $('#destGeneralID');
if ($destGeneralID.length) {
$destGeneralID.select2({
theme: 'bootstrap4',
placeholder: "장수를 선택해 주세요.",
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
});
}
const $isGold = $('#isGold');
if ($isGold.length) {
$isGold.select2({
theme: 'bootstrap4',
placeholder: "분량을 지정해 주세요.",
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
minimumResultsForSearch: -1,
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
});
}
const $amount = $('#amount:not([type=hidden])');
if ($amount.length) {
$amount.select2({
theme: 'bootstrap4',
placeholder: "분량을 지정해 주세요.",
allowClear: false,
language: "ko",
containerCss: {
display: "inline-block !important",
color: 'white !important',
},
tags: true,
sorter: function (items) {
(items as IdTextPair[]).sort(function (lhs, rhs) {
return parseInt(lhs.id) - parseInt(rhs.id);
})
return items;
},
containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
dropdownCssClass: 'select2-only-number simple-select2-align-center bg-secondary text-secondary',
})
}
$(document).on('keypress', '.select2-only-number .select2-search__field', function (e) {
$(this).val(unwrap_any<string>($(this).val()).replace(/[^\d].+/, ""));
if ((e.which < 48 || e.which > 57)) {
e.preventDefault();
}
});
const pluginMap:Record<string, ()=>void> = {
'defaultSelectCityByMap': defaultSelectCityByMap,
'defaultSelectNationByMap': defaultSelectNationByMap,
'colorSelect': colorSelect,
'recruitCrewForm': recruitCrewForm,
};
for (const jsPlugin of jsPlugins) {
if (jsPlugin in pluginMap) {
pluginMap[jsPlugin]()
}
else{
console.error(`'${jsPlugin}' is not supported`);
}
}
});
@@ -0,0 +1,37 @@
<template>
<div v-for="(cityList, distance) in distanceList" :key="distance">
{{ distance }} 떨어진 도시:
<template v-for="(cityID, key) in cityList" :key="key">
<template v-if="key !== 0">, </template>
<a :style="{ color: colorMap[distance] ?? undefined, textDecoration:'underline' }" @click="$emit('selected', cityID)">{{
citiesMap.get(cityID)?.name
}}</a>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
export default defineComponent({
props: {
distanceList: {
type: Object as PropType<Record<number, number[]>>,
required: true,
},
citiesMap: {
type: Object as PropType<Map<number, { name: string }>>,
required: true,
},
},
emits: ['selected'],
data() {
return {
colorMap: {
1: "magenta",
2: "orange",
3: "yellow",
},
};
},
});
</script>
+89
View File
@@ -0,0 +1,89 @@
<template>
<v-multiselect
v-model="selectedCity"
:allow-empty="false"
:options="citiesForFind"
:group-select="false"
label="searchText"
track-by="value"
open-direction="bottom"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="턴 선택"
:maxHeight="400"
:searchable="searchMode"
>
<template v-slot:option="props">
{{ props.option.title }}
<span v-if="props.option.info">({{ props.option.info }})</span>
</template>
<template v-slot:singleLabel="props">
{{ props.option.simpleName }}
</template>
</v-multiselect>
</template>
<script lang="ts">
import { filter초성withAlphabet } from "@/util/filter초성withAlphabet";
import { defineComponent, PropType } from "vue";
type SelectedCity = {
value: number;
searchText: string;
title: string;
simpleName: string;
info?: string;
};
export default defineComponent({
props: {
modelValue: {
type: Number,
required: true,
},
cities: {
type: Map as PropType<Map<number, { name: string; info?: string }>>,
required: true,
},
},
emits: ["update:modelValue"],
watch: {
modelValue(val: number) {
const target = this.targets.get(val);
this.selectedCity = target;
},
selectedCity(val: SelectedCity){
this.$emit('update:modelValue', val.value);
}
},
data() {
const citiesForFind = [];
const targets = new Map<number, SelectedCity>();
let selectedCity;
for (const [value, { name, info }] of this.cities.entries()) {
const [filteredTextH, filteredTextA] = filter초성withAlphabet(name);
const obj: SelectedCity = {
value,
title: name,
info: info,
simpleName: name,
searchText: `${name} ${filteredTextH} ${filteredTextA}`
};
if (value == this.modelValue) {
selectedCity = obj;
}
citiesForFind.push(obj);
targets.set(value, obj);
}
return {
selectedCity,
searchMode: true,
citiesForFind,
targets,
};
},
});
</script>
+98
View File
@@ -0,0 +1,98 @@
<template>
<MapLegacyTemplate
:isDetailMap="false"
:clickableAll="true"
:neutralView="true"
:useCachedMap="true"
:mapTheme="mapTheme"
v-model="selectedCityObj"
/>
<div>
선택된 도시로 강행합니다.<br />
최대 3칸내 도시로만 강행이 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div class="row">
<div class="col">
<CitySelect :cities="citiesMap" :modelValue="selectedCityID" />
</div>
<div class="col">
<b-button @click="submit">{{ commandName }}</b-button>
</div>
</div>
<CityBasedOnDistance
:citiesMap="citiesMap"
:distanceList="distanceList"
@selected="selected"
/>
</template>
<script lang="ts">
import "@/../css/map.css";
import MapLegacyTemplate, {
MapCityParsed,
} from "@/components/MapLegacyTemplate.vue";
import CitySelect from "@/processing/CitySelect.vue";
import CityBasedOnDistance from "@/processing/CitiesBasedOnDistance.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import { Args } from "@/processing/args";
declare const mapTheme: string;
declare const cities: [number, string][];
declare const currentCity: number;
declare const distanceList: Record<number, number[]>;
declare const commandName: string;
export default defineComponent({
name: "che_강행",
components: {
MapLegacyTemplate,
CitySelect,
CityBasedOnDistance,
},
watch: {
selectedCityObj(city: MapCityParsed) {
this.selectedCityID = city.id;
},
},
setup() {
console.log("start!");
const citiesMap = new Map<
number,
{
name: string;
info?: string;
}
>();
for (const [id, name] of cities) {
citiesMap.set(id, { name });
}
console.log(citiesMap);
const selectedCityID = ref(currentCity);
function selected(cityID: number) {
selectedCityID.value = cityID;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destCityID: selectedCityID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
mapTheme: ref(mapTheme),
citiesMap: ref(citiesMap),
selectedCityID,
selectedCityObj: ref(undefined as MapCityParsed | undefined),
distanceList,
commandName,
selected,
submit,
};
},
});
</script>
+1
View File
@@ -0,0 +1 @@
export { default as che_강행 } from "./che_강행.vue";
+7
View File
@@ -0,0 +1,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
});
</script>
+7
View File
@@ -0,0 +1,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
});
</script>
+2
View File
@@ -0,0 +1,2 @@
export * as che_발령 from "./che_발령.vue";
export * as che_포상 from "./che_포상.vue";
+83
View File
@@ -0,0 +1,83 @@
import { isArray, isBoolean, isInteger, isString } from "lodash";
const stringArgs = [
'nationName', 'optionText', 'itemType', 'nationType', 'itemCode', 'commandType',
] as const;
const intArgs = [
'crewType', 'destGeneralID', 'destCityID', 'destNationID',
'amount', 'colorType',
'year', 'month',
'srcArmType', 'destArmType', //숙련전환 전용
] as const;
const booleanArgs = [
'isGold', 'buyRice',
] as const;
const integerArrayArgs = [
'destNationIDList', 'destGeneralIDList', 'amountList'
] as const;
type StringKeys = typeof stringArgs[number];
type IntKeys = typeof intArgs[number];
type BooleanKeys = typeof booleanArgs[number];
type IntegerArrayKeys = typeof integerArrayArgs[number];
export type Args = {
[key in StringKeys]?: string;
} & {
[key in IntKeys]?: number;
} & {
[key in BooleanKeys]?: boolean;
} & {
[key in IntegerArrayKeys]?: number[]
};
export function testSubmitArgs(args: Args): true | ['int' | 'string' | 'boolean' | 'int[]', keyof Args, number | string | boolean | number[]] {
for (const intKey of intArgs) {
const testVal = args[intKey];
if (testVal === undefined) {
continue;
}
if (!isInteger(testVal)) {
return ['int', intKey, testVal];
}
}
for (const stringKey of stringArgs) {
const testVal = args[stringKey];
if (testVal === undefined) {
continue;
}
if (!isString(testVal)) {
return ['string', stringKey, testVal];
}
}
for (const booleanKey of booleanArgs) {
const testVal = args[booleanKey];
if (testVal === undefined) {
continue;
}
if (!isBoolean(testVal)) {
return ['boolean', booleanKey, testVal];
}
}
for (const integerArrayKey of integerArrayArgs) {
const testVal = args[integerArrayKey];
if (testVal === undefined) {
continue;
}
if (!isArray(args[integerArrayKey])) {
return ['int[]', integerArrayKey, testVal];
}
for (const value of testVal) {
if (!isInteger(value)) {
return ['int[]', integerArrayKey, testVal];
}
}
}
return true;
}
+1 -3
View File
@@ -15,7 +15,5 @@ import "@/legacy/main";
import { auto500px } from './util/auto500px';
setAxiosXMLHttpRequest();
auto500px();
createApp(PartialReservedCommand).use(BootstrapVue3).component('v-multiselect', Multiselect).mount('#reservedCommandList');
auto500px();
+112
View File
@@ -0,0 +1,112 @@
import '@scss/processing.scss';
import $ from 'jquery';
exportWindow($, '$');
import { exportWindow } from '@util/exportWindow';
import axios from 'axios';
import { setAxiosXMLHttpRequest } from '@util/setAxiosXMLHttpRequest';
import { convertFormData } from '@util/convertFormData';
import { InvalidResponse } from '@/defs';
import { unwrap } from "@util/unwrap";
import { defaultSelectCityByMap } from '@/defaultSelectCityByMap';
import { defaultSelectNationByMap } from '@/defaultSelectNationByMap';
import { colorSelect } from '@/colorSelect';
import { recruitCrewForm } from '@/recruitCrewForm';
import BootstrapVue3 from 'bootstrap-vue-3'
import Multiselect from 'vue-multiselect';
import * as GeneralActions from "@/processing/General";
import * as NationActions from "@/processing/Nation";
import { App, createApp } from 'vue';
import { auto500px } from './util/auto500px';
import { isString } from 'lodash';
import { Args, testSubmitArgs } from './processing/args';
declare const turnList: number[];
setAxiosXMLHttpRequest();
async function submitCommand<T>(isChiefTurn: boolean, turnList: number[], command: string, args: Args): Promise<T> {
const target = isChiefTurn ? 'j_set_chief_command.php' : 'j_set_general_command.php';
try {
const testResult = testSubmitArgs(args);
if (testResult !== true) {
throw new TypeError(`Invalied Type ${testResult[0]}, ${testResult[2]} should be ${testResult[1]}`);
}
console.log('trySubmit', args);
const response = await axios({
url: target,
responseType: 'json',
method: 'post',
data: convertFormData({
action: command,
turnList: turnList,
arg: JSON.stringify(args)
})
});
const data = response.data as InvalidResponse;
if (!data.result) {
throw data.reason;
}
if (!isChiefTurn) {
window.location.href = './';
} else {
window.location.href = 'b_chiefcenter.php';
}
return data as unknown as T;
}
catch (e) {
console.error(e);
if (isString(e)) {
alert(e);
}
throw e;
}
}
declare const entryInfo: ['General', keyof typeof GeneralActions] | ['Nation', keyof typeof NationActions];
console.log('entry', entryInfo);
auto500px();
const app: App<Element> | undefined = (function () {
//NOTE: route를 쓴다?
const groupName = entryInfo[0];
if (groupName == 'General') {
const moduleName = entryInfo[1];
if (!(moduleName in GeneralActions)) {
console.error(`${moduleName}${groupName}에 없음`);
}
return createApp(GeneralActions[moduleName]);
}
if (groupName == 'Nation') {
const moduleName = entryInfo[1];
if (!(moduleName in GeneralActions)) {
console.error(`${moduleName}${groupName}에 없음`);
}
return createApp(NationActions[moduleName]);
}
console.error('알수')
return undefined;
}());
console.log(app);
if (app === undefined) {
console.error(`모듈이 지정되지 않음`, entryInfo);
}
else {
const div = unwrap(document.querySelector('#container'));
div.addEventListener('customSubmit', (e: Event) => {
const {detail} = e as unknown as CustomEvent<Args>;
void submitCommand(entryInfo[0] == 'Nation', turnList, entryInfo[1], detail);
}, true);
app.use(BootstrapVue3).component('v-multiselect', Multiselect).mount('#container');
}