feat: 변경된 베팅 방식에 따라 결과 표기
This commit was merged in pull request #205.
This commit is contained in:
+12
-278
@@ -2,90 +2,7 @@
|
|||||||
<MyToast v-model="toasts" />
|
<MyToast v-model="toasts" />
|
||||||
<div id="container" class="pageNationBetting bg0">
|
<div id="container" class="pageNationBetting bg0">
|
||||||
<TopBackBar :title="title" />
|
<TopBackBar :title="title" />
|
||||||
<div v-if="bettingInfo !== undefined">
|
<BettingDetail v-if="targetBettingID !== undefined" :bettingID="targetBettingID" @reqToast="addToast" />
|
||||||
<template v-for="(info, idx) in [bettingInfo.bettingInfo]" :key="idx">
|
|
||||||
<div class="bg2">
|
|
||||||
{{ info.name }}
|
|
||||||
<span v-if="info.finished">(종료)</span>
|
|
||||||
<span
|
|
||||||
v-else-if="(yearMonth ?? 0) <= info.closeYearMonth"
|
|
||||||
>({{ parseYearMonth(info.closeYearMonth)[0] }}년 {{ parseYearMonth(info.closeYearMonth)[1] }}월까지)</span>
|
|
||||||
<span v-else>(베팅 마감)</span>
|
|
||||||
(총액: {{ bettingAmount.toLocaleString() }})
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row bettingCandidates gx-1 gy-1">
|
|
||||||
<div
|
|
||||||
class="col-4 col-md-2"
|
|
||||||
v-for="(candidate, idx) in bettingInfo.bettingInfo.candidates"
|
|
||||||
:key="idx"
|
|
||||||
@click="toggleCandidate(idx)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'bettingCandidate',
|
|
||||||
pickedBetType.has(idx) ? 'picked' : undefined,
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<div class="title bg1">{{ candidate.title }}</div>
|
|
||||||
<div class="info" v-if="candidate.isHtml" v-html="candidate.info"></div>
|
|
||||||
<div
|
|
||||||
class="pickRate"
|
|
||||||
>선택율: {{ ((partialBet.get(idx) ?? 0) / pureBettingAmount * 100).toFixed(1) }}%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!info.finished && (yearMonth ?? 0) <= info.closeYearMonth" class="row gx-0">
|
|
||||||
<div
|
|
||||||
class="col-6 col-md-3 align-self-center"
|
|
||||||
>잔여 {{ info.reqInheritancePoint ? '포인트' : '금' }} : {{ bettingInfo.remainPoint.toLocaleString() }}</div>
|
|
||||||
<div
|
|
||||||
class="col-6 col-md-3 align-self-center"
|
|
||||||
>사용 포인트: {{ sum(Array.from(myBettings.values())).toLocaleString() }}</div>
|
|
||||||
<div class="col-6 col-md-3 align-self-center">대상: {{ getTypeStr(pickedBetTypeKey) }}</div>
|
|
||||||
<div class="col-4 col-md-2 d-grid">
|
|
||||||
<b-form-input
|
|
||||||
class="d-grid"
|
|
||||||
type="number"
|
|
||||||
v-model="betPoint"
|
|
||||||
:min="10"
|
|
||||||
:max="1000"
|
|
||||||
:step="10"
|
|
||||||
></b-form-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-2 col-md-1 d-grid">
|
|
||||||
<b-button class="d-grid" @click="submitBet">베팅</b-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="bg2">배당 순위</div>
|
|
||||||
<div class="row" :style="{
|
|
||||||
borderBottom: 'gray solid 1px'
|
|
||||||
}">
|
|
||||||
<div class="col-5 text-center">대상</div>
|
|
||||||
<div class="col-2 text-center">베팅액</div>
|
|
||||||
<div class="col-3 text-center">내 베팅</div>
|
|
||||||
<div class="col-2 text-center">배율</div>
|
|
||||||
</div>
|
|
||||||
<div class="row" v-for="[betType, amount] of detailBet" :key="betType">
|
|
||||||
<div
|
|
||||||
class="col-5"
|
|
||||||
:style="{
|
|
||||||
fontWeight: myBettings.has(betType) ? 'bold' : undefined
|
|
||||||
}"
|
|
||||||
>{{ getTypeStr(betType) }}</div>
|
|
||||||
<div class="col-2 text-end">{{ amount.toLocaleString() }}</div>
|
|
||||||
<div
|
|
||||||
class="col-3 text-center"
|
|
||||||
>{{ myBettings.has(betType) ? `(${myBettings.get(betType)?.toLocaleString()} -> ${((myBettings.get(betType) ?? 0) * bettingAmount / amount / ((info.isExclusive && info.selectCnt > 1) ? 1 : 2)).toLocaleString()})` : '' }}</div>
|
|
||||||
<div
|
|
||||||
class="col-2 text-end"
|
|
||||||
>{{ (bettingAmount / amount / ((info.isExclusive && info.selectCnt > 1) ? 1 : 2)).toFixed(1) }}배</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-if="bettingList === undefined">로딩 중...</div>
|
<div v-if="bettingList === undefined">로딩 중...</div>
|
||||||
<div class="bettingList" v-else>
|
<div class="bettingList" v-else>
|
||||||
<div class="bg2">베팅 목록</div>
|
<div class="bg2">베팅 목록</div>
|
||||||
@@ -93,10 +10,12 @@
|
|||||||
class="bettingItem"
|
class="bettingItem"
|
||||||
v-for="info of Object.values(bettingList).reverse()"
|
v-for="info of Object.values(bettingList).reverse()"
|
||||||
:key="info.id"
|
:key="info.id"
|
||||||
@click="loadBetting(info.id)"
|
@click="targetBettingID = info.id"
|
||||||
>
|
>
|
||||||
[{{ parseYearMonth(info.openYearMonth)[0] }}년 {{ parseYearMonth(info.openYearMonth)[1] }}월] {{ info.name }}
|
[{{ parseYearMonth(info.openYearMonth)[0] }}년 {{ parseYearMonth(info.openYearMonth)[1] }}월] {{ info.name }}
|
||||||
<span v-if="info.finished">(종료)</span>
|
<span
|
||||||
|
v-if="info.finished"
|
||||||
|
>(종료)</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="(yearMonth ?? 0) <= info.closeYearMonth"
|
v-else-if="(yearMonth ?? 0) <= info.closeYearMonth"
|
||||||
>({{ parseYearMonth(info.closeYearMonth)[0] }}년 {{ parseYearMonth(info.closeYearMonth)[1] }}월까지)</span>
|
>({{ parseYearMonth(info.closeYearMonth)[0] }}년 {{ parseYearMonth(info.closeYearMonth)[1] }}월까지)</span>
|
||||||
@@ -112,33 +31,13 @@
|
|||||||
import MyToast from "@/components/MyToast.vue";
|
import MyToast from "@/components/MyToast.vue";
|
||||||
import TopBackBar from "@/components/TopBackBar.vue";
|
import TopBackBar from "@/components/TopBackBar.vue";
|
||||||
import BottomBar from "@/components/BottomBar.vue";
|
import BottomBar from "@/components/BottomBar.vue";
|
||||||
import { ToastType } from "@/defs";
|
import { BettingInfo, ToastType } from "@/defs";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { SammoAPI, ValidResponse } from "./SammoAPI";
|
import { SammoAPI, ValidResponse } from "./SammoAPI";
|
||||||
import { isString, sum } from "lodash";
|
import { isString } from "lodash";
|
||||||
import { parseYearMonth } from "@/util/parseYearMonth";
|
import { parseYearMonth } from "@/util/parseYearMonth";
|
||||||
import { joinYearMonth } from "./util/joinYearMonth";
|
import { joinYearMonth } from "./util/joinYearMonth";
|
||||||
|
import BettingDetail from "@/components/BettingDetail.vue";
|
||||||
type SelectItem = {
|
|
||||||
title: string;
|
|
||||||
info?: string;
|
|
||||||
isHtml?: boolean;
|
|
||||||
aux?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type BettingInfo = {
|
|
||||||
id: number;
|
|
||||||
type: 'nationBetting',
|
|
||||||
name: string;
|
|
||||||
finished: boolean;
|
|
||||||
selectCnt: number;
|
|
||||||
isExclusive?: boolean;
|
|
||||||
reqInheritancePoint: boolean;
|
|
||||||
openYearMonth: number;
|
|
||||||
closeYearMonth: number;
|
|
||||||
candidates: SelectItem[];
|
|
||||||
winner?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type BettingListResponse = ValidResponse & {
|
type BettingListResponse = ValidResponse & {
|
||||||
bettingList: Record<number, Omit<BettingInfo & { totalAmount: number }, 'candidates'>>,
|
bettingList: Record<number, Omit<BettingInfo & { totalAmount: number }, 'candidates'>>,
|
||||||
@@ -146,187 +45,22 @@ type BettingListResponse = ValidResponse & {
|
|||||||
month: number,
|
month: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
type BettingDetailResponse = ValidResponse & {
|
|
||||||
bettingInfo: BettingInfo;
|
|
||||||
bettingDetail: [string, number][];
|
|
||||||
myBetting: [string, number][];
|
|
||||||
remainPoint: number;
|
|
||||||
year: number;
|
|
||||||
month: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toasts = ref<ToastType[]>([]);
|
const toasts = ref<ToastType[]>([]);
|
||||||
const year = ref<number>();
|
const year = ref<number>();
|
||||||
const month = ref<number>();
|
const month = ref<number>();
|
||||||
const yearMonth = ref<number>();
|
const yearMonth = ref<number>();
|
||||||
const bettingList = ref<BettingListResponse['bettingList']>();
|
const bettingList = ref<BettingListResponse['bettingList']>();
|
||||||
const bettingInfo = ref<BettingDetailResponse>();
|
|
||||||
|
|
||||||
const bettingAmount = ref<number>(0);
|
|
||||||
const pureBettingAmount = ref<number>(0);
|
|
||||||
|
|
||||||
const partialBet = ref(new Map<number, number>());
|
const targetBettingID = ref<number>();
|
||||||
const detailBet = ref<[string, number][]>([]);
|
|
||||||
|
|
||||||
const typeMap = ref(new Map<string, string>());
|
|
||||||
function getTypeStr(type: string): string {
|
|
||||||
const typeResult = typeMap.value.get(type);
|
|
||||||
if (typeResult !== undefined) {
|
|
||||||
return typeResult;
|
|
||||||
}
|
|
||||||
const bettingSubTypes = JSON.parse(type) as number[];
|
|
||||||
if (bettingSubTypes[0] < -1) {
|
|
||||||
return 'Invalid';
|
|
||||||
}
|
|
||||||
|
|
||||||
const textBettingType = bettingSubTypes.map((idx) => {
|
function addToast(msg: ToastType){
|
||||||
return bettingInfo.value?.bettingInfo.candidates[idx].title;
|
toasts.value.push(msg);
|
||||||
}).join(', ');
|
|
||||||
typeMap.value.set(type, textBettingType);
|
|
||||||
return textBettingType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickedBetType = ref(new Set<number>());
|
|
||||||
const pickedBetTypeKey = ref('[]');
|
|
||||||
|
|
||||||
const betPoint = ref(0);
|
|
||||||
const myBettings = ref(new Map<string, number>());
|
|
||||||
|
|
||||||
function toggleCandidate(idx: number) {
|
|
||||||
if (bettingInfo.value === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const selectCnt = bettingInfo.value.bettingInfo.selectCnt;
|
|
||||||
|
|
||||||
if (selectCnt == 1) {
|
|
||||||
pickedBetType.value.clear();
|
|
||||||
pickedBetType.value.add(idx);
|
|
||||||
pickedBetTypeKey.value = JSON.stringify([idx]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pickedBetType.value.has(idx)) {
|
|
||||||
pickedBetType.value.delete(idx);
|
|
||||||
}
|
|
||||||
else if (pickedBetType.value.size < selectCnt) {
|
|
||||||
pickedBetType.value.add(idx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
toasts.value.push({
|
|
||||||
title: '오류',
|
|
||||||
type: 'warning',
|
|
||||||
content: `이미 ${selectCnt}개를 선택했습니다.`,
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeArr = Array.from(pickedBetType.value.values());
|
|
||||||
pickedBetTypeKey.value = JSON.stringify(typeArr.sort((lhs, rhs) => lhs - rhs));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadBetting(bettingID: number) {
|
|
||||||
try {
|
|
||||||
const result = await SammoAPI.Betting.GetBettingDetail<BettingDetailResponse>({
|
|
||||||
betting_id: bettingID
|
|
||||||
});
|
|
||||||
year.value = result.year;
|
|
||||||
month.value = result.month;
|
|
||||||
yearMonth.value = joinYearMonth(result.year, result.month);
|
|
||||||
bettingInfo.value = result;
|
|
||||||
|
|
||||||
partialBet.value.clear();
|
|
||||||
|
|
||||||
const betSort = new Map<string, number>();
|
|
||||||
|
|
||||||
|
|
||||||
let _bettingAmount = 0;
|
|
||||||
let adminBettingAmount = 0;
|
|
||||||
for (const [bettingType, amount] of result.bettingDetail) {
|
|
||||||
console.log(amount, typeof (amount));
|
|
||||||
let userBet = true;
|
|
||||||
const bettingSubTypes = JSON.parse(bettingType) as number[];
|
|
||||||
for (const bettingSubType of bettingSubTypes) {
|
|
||||||
if (bettingSubType < 0) {
|
|
||||||
userBet = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const oldValue = partialBet.value.get(bettingSubType) ?? 0;
|
|
||||||
partialBet.value.set(bettingSubType, oldValue + amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userBet) {
|
|
||||||
const oldValue = betSort.get(bettingType) ?? 0;
|
|
||||||
betSort.set(bettingType, oldValue + amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
_bettingAmount += amount;
|
|
||||||
if (!userBet) {
|
|
||||||
adminBettingAmount += amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(_bettingAmount);
|
|
||||||
bettingAmount.value = _bettingAmount;
|
|
||||||
pureBettingAmount.value = _bettingAmount - adminBettingAmount;
|
|
||||||
|
|
||||||
detailBet.value = Array.from(betSort.entries());
|
|
||||||
detailBet.value.sort(([, lhsVal], [, rhsVal]) => {
|
|
||||||
return rhsVal - lhsVal;
|
|
||||||
})
|
|
||||||
|
|
||||||
pickedBetType.value.clear();
|
|
||||||
pickedBetTypeKey.value = '[]';
|
|
||||||
myBettings.value.clear();
|
|
||||||
|
|
||||||
for (const [betType, amount] of result.myBetting) {
|
|
||||||
myBettings.value.set(betType, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
if (isString(e)) {
|
|
||||||
toasts.value.push({
|
|
||||||
title: "에러",
|
|
||||||
content: e,
|
|
||||||
type: "danger",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitBet(): Promise<void> {
|
|
||||||
const info = bettingInfo.value;
|
|
||||||
if (info === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bettingID = info.bettingInfo.id;
|
|
||||||
const bettingType = JSON.parse(pickedBetTypeKey.value);
|
|
||||||
const amount = betPoint.value;
|
|
||||||
try {
|
|
||||||
await SammoAPI.Betting.Bet({
|
|
||||||
bettingID,
|
|
||||||
bettingType,
|
|
||||||
amount,
|
|
||||||
});
|
|
||||||
toasts.value.push({
|
|
||||||
title: '완료',
|
|
||||||
content: '베팅했습니다',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
await loadBetting(info.bettingInfo.id);
|
|
||||||
} catch (e) {
|
|
||||||
if (isString(e)) {
|
|
||||||
toasts.value.push({
|
|
||||||
title: "에러",
|
|
||||||
content: e,
|
|
||||||
type: "danger",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('시작!');
|
console.log('시작!');
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -0,0 +1,436 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="bettingDetailInfo !== undefined && info !== undefined">
|
||||||
|
<div class="bg2">
|
||||||
|
{{ info.name }}
|
||||||
|
<span v-if="info.finished">(종료)</span>
|
||||||
|
<span
|
||||||
|
v-else-if="(yearMonth ?? 0) <= info.closeYearMonth"
|
||||||
|
>({{ parseYearMonth(info.closeYearMonth)[0] }}년 {{ parseYearMonth(info.closeYearMonth)[1] }}월까지)</span>
|
||||||
|
<span v-else>(베팅 마감)</span>
|
||||||
|
(총액: {{ bettingAmount.toLocaleString() }})
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row bettingCandidates gx-1 gy-1">
|
||||||
|
<div
|
||||||
|
class="col-4 col-md-2"
|
||||||
|
v-for="(candidate, idx) in info.candidates"
|
||||||
|
:key="idx"
|
||||||
|
@click="toggleCandidate(idx)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'bettingCandidate',
|
||||||
|
pickedBetType.has(idx) ? 'picked' : undefined,
|
||||||
|
(info.finished && winner.has(idx)) ? 'picked' : undefined,
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="title bg1">{{ candidate.title }}</div>
|
||||||
|
<div class="info" v-if="candidate.isHtml" v-html="candidate.info"></div>
|
||||||
|
<div
|
||||||
|
class="pickRate"
|
||||||
|
>선택율: {{ ((partialBet.get(idx) ?? 0) / pureBettingAmount * 100).toFixed(1) }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!info.finished && (yearMonth ?? 0) <= info.closeYearMonth" class="row gx-0">
|
||||||
|
<div
|
||||||
|
class="col-6 col-md-3 align-self-center"
|
||||||
|
>잔여 {{ info.reqInheritancePoint ? '포인트' : '금' }} : {{ bettingDetailInfo.remainPoint.toLocaleString() }}</div>
|
||||||
|
<div
|
||||||
|
class="col-6 col-md-3 align-self-center"
|
||||||
|
>사용 포인트: {{ sum(Array.from(myBettings.values())).toLocaleString() }}</div>
|
||||||
|
<div class="col-6 col-md-3 align-self-center">대상: {{ getTypeStr(pickedBetTypeKey) }}</div>
|
||||||
|
<div class="col-4 col-md-2 d-grid">
|
||||||
|
<b-form-input
|
||||||
|
class="d-grid"
|
||||||
|
type="number"
|
||||||
|
v-model="betPoint"
|
||||||
|
:min="10"
|
||||||
|
:max="1000"
|
||||||
|
:step="10"
|
||||||
|
></b-form-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 col-md-1 d-grid">
|
||||||
|
<b-button class="d-grid" @click="submitBet">베팅</b-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="bg2">배당 순위</div>
|
||||||
|
<div
|
||||||
|
class="row"
|
||||||
|
:style="{
|
||||||
|
borderBottom: 'gray solid 1px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="col-5 text-center">대상</div>
|
||||||
|
<div class="col-2 text-center">베팅액</div>
|
||||||
|
<div class="col-3 text-center">내 베팅</div>
|
||||||
|
<div class="col-2 text-center">{{ info.finished? '배율':'기대 배율' }}</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="info.finished">
|
||||||
|
<div class="row" v-for="[betType, amount] of detailBet" :key="betType">
|
||||||
|
<template
|
||||||
|
v-for="[matchPoint, color] of [calcMatchPointWithColor(betType)]"
|
||||||
|
:key="matchPoint"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col-5"
|
||||||
|
:style="{
|
||||||
|
fontWeight: myBettings.has(betType) ? 'bold' : undefined,
|
||||||
|
color: color
|
||||||
|
}"
|
||||||
|
>{{ getTypeStr(betType) }}</div>
|
||||||
|
<div class="col-2 text-end">{{ amount.toLocaleString() }}</div>
|
||||||
|
<div class="col-3 text-center" v-if="myBettings.has(betType)">
|
||||||
|
<template
|
||||||
|
v-for="subPoint of [myBettings.get(betType) ?? 0]"
|
||||||
|
>({{ subPoint.toLocaleString() }} -> {{ calculatedReward[matchPoint] == 0? 0 : (subPoint * calculatedReward[matchPoint] / (calculatedSubAmount.get(matchPoint)??1)).toFixed(1).toLocaleString() }})</template>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center" v-else></div>
|
||||||
|
<div class="col-2 text-end">{{ (calculatedReward[matchPoint] == 0? 0 : (calculatedReward[matchPoint] / (calculatedSubAmount.get(matchPoint)??1))).toFixed(1).toLocaleString() }}배</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="row" v-for="[betType, amount] of detailBet" :key="betType">
|
||||||
|
<div
|
||||||
|
class="col-5"
|
||||||
|
:style="{
|
||||||
|
fontWeight: myBettings.has(betType) ? 'bold' : undefined,
|
||||||
|
}"
|
||||||
|
>{{ getTypeStr(betType) }}</div>
|
||||||
|
<div class="col-2 text-end">{{ amount.toLocaleString() }}</div>
|
||||||
|
<div class="col-3 text-center" v-if="myBettings.has(betType)">
|
||||||
|
<template
|
||||||
|
v-for="subPoint of [myBettings.get(betType) ?? 0]"
|
||||||
|
>({{ subPoint.toLocaleString() }} -> {{ (subPoint * maxBettingReward / amount).toFixed(1).toLocaleString() }})</template>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center" v-else></div>
|
||||||
|
<div class="col-2 text-end">{{ (maxBettingReward / amount).toFixed(1).toLocaleString() }}배</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">import { BettingInfo, ToastType } from '@/defs';
|
||||||
|
import { SammoAPI, ValidResponse } from "@/SammoAPI";
|
||||||
|
import { joinYearMonth } from '@/util/joinYearMonth';
|
||||||
|
import { parseYearMonth } from '@/util/parseYearMonth';
|
||||||
|
import { isString, range, sum } from 'lodash';
|
||||||
|
import { ref, defineProps, defineEmits, PropType, watch } from "vue";
|
||||||
|
|
||||||
|
type BettingDetailResponse = ValidResponse & {
|
||||||
|
bettingInfo: BettingInfo;
|
||||||
|
bettingDetail: [string, number][];
|
||||||
|
myBetting: [string, number][];
|
||||||
|
remainPoint: number;
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
bettingID: {
|
||||||
|
type: Number as PropType<number>,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'reqToast', content: ToastType): void,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const year = ref<number>(0);
|
||||||
|
const month = ref<number>(0);
|
||||||
|
const yearMonth = ref<number>(0);
|
||||||
|
|
||||||
|
const bettingDetailInfo = ref<BettingDetailResponse>();
|
||||||
|
const info = ref<BettingInfo>();
|
||||||
|
|
||||||
|
const bettingAmount = ref<number>(0);
|
||||||
|
const maxBettingReward = ref<number>(1);
|
||||||
|
const pureBettingAmount = ref<number>(0);
|
||||||
|
|
||||||
|
const partialBet = ref(new Map<number, number>());
|
||||||
|
const detailBet = ref<[string, number][]>([]);
|
||||||
|
|
||||||
|
const typeMap = ref(new Map<string, string>());
|
||||||
|
function getTypeStr(type: string): string {
|
||||||
|
const typeResult = typeMap.value.get(type);
|
||||||
|
if (typeResult !== undefined) {
|
||||||
|
return typeResult;
|
||||||
|
}
|
||||||
|
const bettingSubTypes = JSON.parse(type) as number[];
|
||||||
|
if (bettingSubTypes[0] < -1) {
|
||||||
|
return 'Invalid';
|
||||||
|
}
|
||||||
|
|
||||||
|
const textBettingType = bettingSubTypes.map((idx) => {
|
||||||
|
return bettingDetailInfo.value?.bettingInfo.candidates[idx].title;
|
||||||
|
}).join(', ');
|
||||||
|
typeMap.value.set(type, textBettingType);
|
||||||
|
return textBettingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickedBetType = ref(new Set<number>());
|
||||||
|
const pickedBetTypeKey = ref('[]');
|
||||||
|
|
||||||
|
const betPoint = ref(0);
|
||||||
|
const myBettings = ref(new Map<string, number>());
|
||||||
|
|
||||||
|
const winner = ref(new Set<number>());
|
||||||
|
const calculatedReward = ref<number[]>([]);
|
||||||
|
const calculatedSubAmount = ref(new Map<number, number>());
|
||||||
|
|
||||||
|
|
||||||
|
function calcMatchPointWithColor(type: string): [number, 'green' | 'yellow' | 'red' | undefined] {
|
||||||
|
if (!info.value?.finished) {
|
||||||
|
return [0, undefined];
|
||||||
|
}
|
||||||
|
const bettingSubTypes = JSON.parse(type) as number[];
|
||||||
|
if (bettingSubTypes[0] < -1) {
|
||||||
|
return [0, undefined];
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchPoint = 0;
|
||||||
|
for (const subType of bettingSubTypes) {
|
||||||
|
if (winner.value.has(subType)) {
|
||||||
|
matchPoint += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.value.isExclusive) {
|
||||||
|
if (matchPoint == info.value.selectCnt) {
|
||||||
|
return [matchPoint, 'green'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [matchPoint, 'red'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let color: 'green' | 'red' | 'yellow' = 'green';
|
||||||
|
if (matchPoint == 0) {
|
||||||
|
color = 'red';
|
||||||
|
}
|
||||||
|
else if (matchPoint < info.value.selectCnt) {
|
||||||
|
color = 'yellow';
|
||||||
|
}
|
||||||
|
return [matchPoint, color];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function calcReward() {
|
||||||
|
if (info.value === undefined || bettingDetailInfo.value === undefined) {
|
||||||
|
throw 'no info';
|
||||||
|
}
|
||||||
|
const selectCnt = info.value.selectCnt;
|
||||||
|
const rewardAmount = new Array<number>(selectCnt).fill(0);
|
||||||
|
|
||||||
|
const subAmount = new Map<number, number>();
|
||||||
|
for (const [bettingTypeStr, amount] of bettingDetailInfo.value.bettingDetail) {
|
||||||
|
if (amount == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [matchPoint,] = calcMatchPointWithColor(bettingTypeStr);
|
||||||
|
subAmount.set(matchPoint, (subAmount.get(matchPoint) ?? 0) + amount);
|
||||||
|
}
|
||||||
|
calculatedSubAmount.value = subAmount;
|
||||||
|
|
||||||
|
|
||||||
|
if (info.value.isExclusive) {
|
||||||
|
rewardAmount[selectCnt - 1] = bettingAmount.value;
|
||||||
|
calculatedReward.value = rewardAmount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let remainRewardAmount = bettingAmount.value;
|
||||||
|
|
||||||
|
for (const matchPoint of range(selectCnt, 0, -1)) {
|
||||||
|
if (!subAmount.has(matchPoint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const givenRewardAmount = remainRewardAmount / 2;
|
||||||
|
rewardAmount[matchPoint] = givenRewardAmount;
|
||||||
|
remainRewardAmount -= givenRewardAmount; // /2가 아니라 다른 값이 될 경우를 대비..
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const matchPoint of range(1, selectCnt + 1)) {
|
||||||
|
if (!subAmount.has(matchPoint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rewardAmount[matchPoint] += remainRewardAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedReward.value = rewardAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBetting(bettingID: number) {
|
||||||
|
try {
|
||||||
|
const result = await SammoAPI.Betting.GetBettingDetail<BettingDetailResponse>({
|
||||||
|
betting_id: bettingID
|
||||||
|
});
|
||||||
|
year.value = result.year;
|
||||||
|
month.value = result.month;
|
||||||
|
yearMonth.value = joinYearMonth(result.year, result.month);
|
||||||
|
bettingDetailInfo.value = result;
|
||||||
|
info.value = result.bettingInfo;
|
||||||
|
|
||||||
|
partialBet.value.clear();
|
||||||
|
|
||||||
|
const betSort = new Map<string, number>();
|
||||||
|
|
||||||
|
|
||||||
|
let _bettingAmount = 0;
|
||||||
|
let adminBettingAmount = 0;
|
||||||
|
for (const [bettingType, amount] of result.bettingDetail) {
|
||||||
|
console.log(amount, typeof (amount));
|
||||||
|
let userBet = true;
|
||||||
|
const bettingSubTypes = JSON.parse(bettingType) as number[];
|
||||||
|
for (const bettingSubType of bettingSubTypes) {
|
||||||
|
if (bettingSubType < 0) {
|
||||||
|
userBet = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const oldValue = partialBet.value.get(bettingSubType) ?? 0;
|
||||||
|
partialBet.value.set(bettingSubType, oldValue + amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userBet) {
|
||||||
|
const oldValue = betSort.get(bettingType) ?? 0;
|
||||||
|
betSort.set(bettingType, oldValue + amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
_bettingAmount += amount;
|
||||||
|
if (!userBet) {
|
||||||
|
adminBettingAmount += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(_bettingAmount);
|
||||||
|
bettingAmount.value = _bettingAmount;
|
||||||
|
pureBettingAmount.value = _bettingAmount - adminBettingAmount;
|
||||||
|
if (info.value.isExclusive && info.value.selectCnt > 1) {
|
||||||
|
maxBettingReward.value = _bettingAmount;
|
||||||
|
} else {
|
||||||
|
maxBettingReward.value = _bettingAmount / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailBet.value = Array.from(betSort.entries());
|
||||||
|
detailBet.value.sort(([, lhsVal], [, rhsVal]) => {
|
||||||
|
return rhsVal - lhsVal;
|
||||||
|
})
|
||||||
|
|
||||||
|
pickedBetType.value.clear();
|
||||||
|
pickedBetTypeKey.value = '[]';
|
||||||
|
myBettings.value.clear();
|
||||||
|
|
||||||
|
|
||||||
|
if (result.bettingInfo.winner) {
|
||||||
|
winner.value = new Set(result.bettingInfo.winner);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
winner.value.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [betType, amount] of result.myBetting) {
|
||||||
|
myBettings.value.set(betType, amount);
|
||||||
|
}
|
||||||
|
calcReward();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if (isString(e)) {
|
||||||
|
emit('reqToast', {
|
||||||
|
title: "에러",
|
||||||
|
content: e,
|
||||||
|
type: "danger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadBetting(props.bettingID);
|
||||||
|
watch(() => props.bettingID, (newBettingID) => {
|
||||||
|
void loadBetting(newBettingID);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function toggleCandidate(idx: number) {
|
||||||
|
if (info.value === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bettingDetailInfo.value === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.value.closeYearMonth < yearMonth.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.value.finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectCnt = bettingDetailInfo.value.bettingInfo.selectCnt;
|
||||||
|
|
||||||
|
if (selectCnt == 1) {
|
||||||
|
pickedBetType.value.clear();
|
||||||
|
pickedBetType.value.add(idx);
|
||||||
|
pickedBetTypeKey.value = JSON.stringify([idx]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pickedBetType.value.has(idx)) {
|
||||||
|
pickedBetType.value.delete(idx);
|
||||||
|
}
|
||||||
|
else if (pickedBetType.value.size < selectCnt) {
|
||||||
|
pickedBetType.value.add(idx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit('reqToast', {
|
||||||
|
title: '오류',
|
||||||
|
type: 'warning',
|
||||||
|
content: `이미 ${selectCnt}개를 선택했습니다.`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeArr = Array.from(pickedBetType.value.values());
|
||||||
|
pickedBetTypeKey.value = JSON.stringify(typeArr.sort((lhs, rhs) => lhs - rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBet(): Promise<void> {
|
||||||
|
|
||||||
|
const bettingInfo = info.value;
|
||||||
|
if (bettingInfo === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bettingID = bettingInfo.id;
|
||||||
|
const bettingType = JSON.parse(pickedBetTypeKey.value);
|
||||||
|
const amount = betPoint.value;
|
||||||
|
try {
|
||||||
|
await SammoAPI.Betting.Bet({
|
||||||
|
bettingID,
|
||||||
|
bettingType,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
emit('reqToast', {
|
||||||
|
title: '완료',
|
||||||
|
content: '베팅했습니다',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
await loadBetting(bettingInfo.id);
|
||||||
|
} catch (e) {
|
||||||
|
if (isString(e)) {
|
||||||
|
emit('reqToast', {
|
||||||
|
title: "에러",
|
||||||
|
content: e,
|
||||||
|
type: "danger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -223,3 +223,25 @@ export const diplomacyStateInfo: Record<diplomacyState, diplomacyInfo> = {
|
|||||||
2: { name: '통상' },
|
2: { name: '통상' },
|
||||||
7: { name: '불가침', color: 'green' },
|
7: { name: '불가침', color: 'green' },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type SelectItem = {
|
||||||
|
title: string;
|
||||||
|
info?: string;
|
||||||
|
isHtml?: boolean;
|
||||||
|
aux?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BettingInfo = {
|
||||||
|
id: number;
|
||||||
|
type: 'nationBetting',
|
||||||
|
name: string;
|
||||||
|
finished: boolean;
|
||||||
|
selectCnt: number;
|
||||||
|
isExclusive?: boolean;
|
||||||
|
reqInheritancePoint: boolean;
|
||||||
|
openYearMonth: number;
|
||||||
|
closeYearMonth: number;
|
||||||
|
candidates: SelectItem[];
|
||||||
|
winner?: number[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user