feat,refac: 경매장 재설계, 유니크 경매장 구현 (#221)
- 경매장을 모든 입찰 기록이 남는 새로운 경매장 로직으로 변경
- ng_auction, ng_auction_bid
- DTO 사용
- 상회입찰 시 개인메시지로 알림
- 기존의 '배경에서 조용히 이루어지는' 유니크 입찰을 공개된 유니크 경매장으로 변경
- 종료 기간 명시
- 종료 기간에 가까워질때 입찰하면 자동 연장
- 최대 연장기간 있음
- 유니크 제한인 경우 24턴 연장
- 중원정세에서 '보물수배'로 알림
Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/221
This commit was merged in pull request #221.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<BContainer id="container" :toast="{ root: true }" class="bg0">
|
||||
<TopBackBar type="close" :title="isResAuction ? '경매장' : '유니크 경매장'" :reloadable="true" @reload="tryReload">
|
||||
<BButton @click="isResAuction = true">금/쌀</BButton>
|
||||
<BButton @click="isResAuction = false">유니크</BButton>
|
||||
</TopBackBar>
|
||||
<AuctionResource v-if="isResAuction"></AuctionResource>
|
||||
<AuctionUniqueItem v-else></AuctionUniqueItem>
|
||||
<BottomBar type="close"></BottomBar>
|
||||
</BContainer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
/*declare const staticValues: {
|
||||
serverID: string,
|
||||
turnterm: number,
|
||||
serverNick: string,
|
||||
};*/
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { BButton, BContainer } from "bootstrap-vue-3";
|
||||
import TopBackBar from "@/components/TopBackBar.vue";
|
||||
import BottomBar from "./components/BottomBar.vue";
|
||||
import AuctionResource from "@/components/AuctionResource.vue";
|
||||
import AuctionUniqueItem from "@/components/AuctionUniqueItem.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
const isResAuction = ref(true);
|
||||
|
||||
function tryReload() {
|
||||
console.log('갱신');
|
||||
}
|
||||
</script>
|
||||
+16
-14
@@ -62,12 +62,12 @@
|
||||
>
|
||||
</div>
|
||||
<div class="row px-4">
|
||||
<b-button class="col-6 offset-6" variant="primary" @click="setNextSpecialWar"> 구입 </b-button>
|
||||
<BButton class="col-6 offset-6" variant="primary" @click="setNextSpecialWar"> 구입 </BButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-4 col-sm-6 col-12 py-2">
|
||||
<div class="row px-4">
|
||||
<div class="a-right col-6 align-self-center">유니크 입찰</div>
|
||||
<div class="a-right col-6 align-self-center">유니크 경매</div>
|
||||
<div class="col-6">
|
||||
<select v-model="specificUnique" class="form-select col-6">
|
||||
<option disabled selected :value="null"> 유니크 선택 </option>
|
||||
@@ -89,14 +89,14 @@
|
||||
</div>
|
||||
<div class="a-right">
|
||||
<small class="form-text text-muted"
|
||||
>얻고자 하는 유니크 아이템을 포인트를 걸어 입찰합니다. 최고 포인트인 경우 다음 턴에 유니크를 얻습니다.<br />
|
||||
>얻고자 하는 유니크 아이템으로 경매를 시작합니다. 24턴 동안 진행됩니다.<br />
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span style="color: white" v-html="specificUnique == null?'':availableUnique[specificUnique].info" />
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row px-4">
|
||||
<b-button class="col-6 offset-6" variant="primary" @click="buySpecificUnique"> 구입 </b-button>
|
||||
<BButton class="col-6 offset-6" variant="primary" @click="openUniqueItemAuction"> 경매 시작 </BButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,7 +107,7 @@
|
||||
<div class="col col-md-4 col-sm-6 col-12 py-2">
|
||||
<div class="row px-4">
|
||||
<div class="a-right col-6 align-self-center">랜덤 턴 초기화</div>
|
||||
<b-button class="col-6" variant="primary" @click="buySimple('ResetTurnTime')"> 구입 </b-button>
|
||||
<BButton class="col-6" variant="primary" @click="buySimple('ResetTurnTime')"> 구입 </BButton>
|
||||
</div>
|
||||
<div class="a-right">
|
||||
<small class="form-text text-muted"
|
||||
@@ -120,7 +120,7 @@
|
||||
<div class="col col-md-4 col-sm-6 col-12 py-2">
|
||||
<div class="row px-4">
|
||||
<div class="a-right col-6 align-self-center">랜덤 유니크 획득</div>
|
||||
<b-button class="col-6" variant="primary" @click="buySimple('BuyRandomUnique')"> 구입 </b-button>
|
||||
<BButton class="col-6" variant="primary" @click="buySimple('BuyRandomUnique')"> 구입 </BButton>
|
||||
</div>
|
||||
<div class="a-right">
|
||||
<small class="form-text text-muted"
|
||||
@@ -133,7 +133,7 @@
|
||||
<div class="col col-md-4 col-sm-6 col-12 py-2">
|
||||
<div class="row px-4">
|
||||
<div class="a-right col-6 align-self-center">즉시 전투 특기 초기화</div>
|
||||
<b-button class="col-6" variant="primary" @click="buySimple('ResetSpecialWar')"> 구입 </b-button>
|
||||
<BButton class="col-6" variant="primary" @click="buySimple('ResetSpecialWar')"> 구입 </BButton>
|
||||
</div>
|
||||
<div class="a-right">
|
||||
<small class="form-text text-muted"
|
||||
@@ -173,13 +173,13 @@
|
||||
>
|
||||
</div>
|
||||
<div class="row px-4" style="margin-bottom: 1em">
|
||||
<b-button
|
||||
<BButton
|
||||
variant="secondary"
|
||||
class="col col-md-6 col-4 offset-md-0 offset-4"
|
||||
@click="inheritBuff[buffKey] = prevInheritBuff[buffKey] ?? 0"
|
||||
>
|
||||
리셋 </b-button
|
||||
><b-button variant="primary" class="col col-md-6 col-4" @click="buyInheritBuff(buffKey)"> 구입 </b-button>
|
||||
리셋 </BButton
|
||||
><BButton variant="primary" class="col col-md-6 col-4" @click="buyInheritBuff(buffKey)"> 구입 </BButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,6 +209,7 @@ import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
|
||||
import { SammoAPI } from "./SammoAPI";
|
||||
import type { inheritBuffType } from "./defs/API/InheritAction";
|
||||
import * as JosaUtil from '@/util/JosaUtil';
|
||||
import { BButton } from "bootstrap-vue-3";
|
||||
|
||||
type InheritanceType =
|
||||
| "previous"
|
||||
@@ -376,6 +377,7 @@ export default defineComponent({
|
||||
components: {
|
||||
TopBackBar,
|
||||
NumberInputWithInfo,
|
||||
BButton,
|
||||
},
|
||||
data() {
|
||||
const inheritBuff = {} as Record<inheritBuffType, number>;
|
||||
@@ -519,7 +521,7 @@ export default defineComponent({
|
||||
//TODO: 페이지 새로고침 필요없이 하도록
|
||||
location.reload();
|
||||
},
|
||||
async buySpecificUnique() {
|
||||
async openUniqueItemAuction() {
|
||||
if(this.specificUnique === null){
|
||||
alert("유니크를 선택해주세요.");
|
||||
return;
|
||||
@@ -544,8 +546,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
try {
|
||||
await SammoAPI.InheritAction.BuySpecificUnique({
|
||||
item: this.specificUnique,
|
||||
await SammoAPI.Auction.OpenUniqueAuction({
|
||||
itemID: this.specificUnique,
|
||||
amount,
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -554,7 +556,7 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
alert("성공했습니다.");
|
||||
alert("성공했습니다. 경매장을 확인해주세요.");
|
||||
//TODO: 페이지 새로고침 필요없이 하도록
|
||||
location.reload();
|
||||
},
|
||||
|
||||
+244
-160
@@ -1,7 +1,16 @@
|
||||
import type { Args } from "./processing/args";
|
||||
import {
|
||||
callSammoAPI, extractHttpMethod, GET, PATCH, POST, PUT,
|
||||
type APITail, type APICallT, type RawArgType, type ValidResponse, type InvalidResponse
|
||||
callSammoAPI,
|
||||
extractHttpMethod,
|
||||
GET,
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
type APITail,
|
||||
type APICallT,
|
||||
type RawArgType,
|
||||
type ValidResponse,
|
||||
type InvalidResponse,
|
||||
} from "./util/callSammoAPI";
|
||||
export type { ValidResponse, InvalidResponse };
|
||||
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
|
||||
@@ -12,168 +21,243 @@ import type { inheritBuffType } from "./defs/API/InheritAction";
|
||||
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse } from "./defs/API/Nation";
|
||||
import type { UploadImageResponse } from "./defs/API/Misc";
|
||||
import type { GeneralLogType, GetGeneralLogResponse, JoinArgs } from "./defs/API/General";
|
||||
import type { GetConstResponse, GetCurrentHistoryResponse, GetDiplomacyResponse, GetHistoryResponse } from "./defs/API/Global";
|
||||
import type {
|
||||
GetConstResponse,
|
||||
GetCurrentHistoryResponse,
|
||||
GetDiplomacyResponse,
|
||||
GetHistoryResponse,
|
||||
} from "./defs/API/Global";
|
||||
import type { CachedMapResult, GeneralListResponse, ItemTypeKey, MapResult } from "./defs";
|
||||
import type { VoteDetailResult, VoteListResult } from "./defs/API/Vote";
|
||||
import type { ActiveResourceAuctionList, OpenAuctionResponse, UniqueItemAuctionDetail, UniqueItemAuctionList } from "./defs/API/Auction";
|
||||
|
||||
const apiRealPath = {
|
||||
Betting: {
|
||||
Bet: PUT as APICallT<{
|
||||
bettingID: number,
|
||||
bettingType: number[],
|
||||
amount: number,
|
||||
}>,
|
||||
GetBettingDetail: NumVar('betting_id',
|
||||
GET as APICallT<undefined, BettingDetailResponse>
|
||||
),
|
||||
GetBettingList: GET as APICallT<{
|
||||
req?: 'bettingNation' | 'tournament'
|
||||
}, BettingListResponse>,
|
||||
},
|
||||
Command: {
|
||||
GetReservedCommand: GET as APICallT<undefined, ReservedCommandResponse>,
|
||||
PushCommand: PUT as APICallT<{
|
||||
amount: number
|
||||
}>,
|
||||
RepeatCommand: PUT as APICallT<{
|
||||
amount: number
|
||||
}>,
|
||||
ReserveCommand: PUT as APICallT<{
|
||||
turnList: number[],
|
||||
action: string,
|
||||
arg?: Args
|
||||
}, ReserveCommandResponse>,
|
||||
ReserveBulkCommand: PUT as APICallT<{
|
||||
turnList: number[],
|
||||
action: string,
|
||||
arg?: Args
|
||||
}[], ReserveBulkCommandResponse>,
|
||||
},
|
||||
General: {
|
||||
Join: POST as APICallT<JoinArgs>,
|
||||
GetGeneralLog: GET as APICallT<{
|
||||
reqType: GeneralLogType,
|
||||
reqTo?: number
|
||||
}, GetGeneralLogResponse>,
|
||||
DropItem: PUT as APICallT<{
|
||||
itemType: ItemTypeKey
|
||||
}>
|
||||
},
|
||||
Global: {
|
||||
GeneralList: GET as APICallT<undefined, GeneralListResponse>,
|
||||
GeneralListWithToken: GET as APICallT<undefined, GeneralListResponse>,
|
||||
GetConst: GET as APICallT<undefined, GetConstResponse>,
|
||||
GetHistory: StrVar('serverID')(
|
||||
NumVar('year',
|
||||
NumVar('month', GET as APICallT<undefined, GetHistoryResponse>
|
||||
))),
|
||||
GetCurrentHistory: GET as APICallT<undefined, GetCurrentHistoryResponse>,
|
||||
GetMap: GET as APICallT<{
|
||||
neutralView?: 0 | 1,
|
||||
showMe?: 0 | 1,
|
||||
}, MapResult>,
|
||||
GetCachedMap: GET as APICallT<undefined, CachedMapResult>,
|
||||
GetDiplomacy: GET as APICallT<undefined, GetDiplomacyResponse>,
|
||||
ExecuteEngine: POST as APICallT<undefined, ValidResponse & { updated: boolean }>,
|
||||
},
|
||||
InheritAction: {
|
||||
BuyHiddenBuff: PUT as APICallT<{
|
||||
type: inheritBuffType,
|
||||
level: number
|
||||
}>,
|
||||
BuyRandomUnique: PUT as APICallT<undefined>,
|
||||
BuySpecificUnique: PUT as APICallT<{
|
||||
item: string,
|
||||
amount: number,
|
||||
}>,
|
||||
ResetSpecialWar: PUT as APICallT<undefined>,
|
||||
ResetTurnTime: PUT as APICallT<undefined>,
|
||||
SetNextSpecialWar: PUT as APICallT<{
|
||||
type: string,
|
||||
}>,
|
||||
},
|
||||
Misc: {
|
||||
UploadImage: POST as APICallT<{
|
||||
imageData: string,
|
||||
}, UploadImageResponse>
|
||||
},
|
||||
NationCommand: {
|
||||
GetReservedCommand: GET as APICallT<undefined, ChiefResponse>,
|
||||
PushCommand: PUT as APICallT<{
|
||||
amount: number
|
||||
}>,
|
||||
RepeatCommand: PUT as APICallT<{
|
||||
amount: number
|
||||
}>,
|
||||
ReserveCommand: PUT as APICallT<{
|
||||
turnList: number[],
|
||||
action: string,
|
||||
arg?: Args
|
||||
}, ReserveCommandResponse>,
|
||||
ReserveBulkCommand: PUT as APICallT<{
|
||||
turnList: number[],
|
||||
action: string,
|
||||
arg?: Args
|
||||
}[], ReserveBulkCommandResponse>,
|
||||
},
|
||||
Nation: {
|
||||
GeneralList: GET as APICallT<undefined, NationGeneralListResponse>,
|
||||
SetNotice: PUT as APICallT<{
|
||||
msg: string,
|
||||
}>,
|
||||
SetScoutMsg: PUT as APICallT<{
|
||||
msg: string,
|
||||
}>,
|
||||
SetBill: PATCH as APICallT<{
|
||||
amount: number,
|
||||
}>,
|
||||
SetRate: PATCH as APICallT<{
|
||||
amount: number,
|
||||
}>,
|
||||
SetSecretLimit: PATCH as APICallT<{
|
||||
amount: number,
|
||||
}>,
|
||||
SetBlockWar: PATCH as APICallT<{
|
||||
value: boolean,
|
||||
}, SetBlockWarResponse>,
|
||||
SetBlockScout: PATCH as APICallT<{
|
||||
value: boolean,
|
||||
}>,
|
||||
GetGeneralLog: GET as APICallT<{
|
||||
generalID: number,
|
||||
reqType: GeneralLogType,
|
||||
reqTo?: number
|
||||
}, GetGeneralLogResponse>
|
||||
},
|
||||
Vote: {
|
||||
AddComment: POST as APICallT<{
|
||||
voteID: number,
|
||||
text: string,
|
||||
}>,
|
||||
GetVoteList: GET as APICallT<undefined, VoteListResult>,
|
||||
GetVoteDetail: GET as APICallT<{
|
||||
voteID: number
|
||||
}, VoteDetailResult>,
|
||||
NewVote: POST as APICallT<{
|
||||
title: string,
|
||||
multipleOptions?: number,
|
||||
endDate?: string,
|
||||
options: string[],
|
||||
keepOldVote?: boolean,
|
||||
}>,
|
||||
Vote: POST as APICallT<{
|
||||
voteID: number,
|
||||
selection: number[],
|
||||
}, ValidResponse & {wonLottery: boolean}>,
|
||||
}
|
||||
Auction: {
|
||||
BidBuyRiceAuction: PUT as APICallT<{
|
||||
auctionID: number;
|
||||
amount: number;
|
||||
}>,
|
||||
BidSellRiceAuction: PUT as APICallT<{
|
||||
auctionID: number;
|
||||
amount: number;
|
||||
}>,
|
||||
GetActiveResourceAuctionList: GET as APICallT<undefined, ActiveResourceAuctionList>,
|
||||
OpenBuyRiceAuction: POST as APICallT<
|
||||
{
|
||||
amount: number;
|
||||
closeTurnCnt: number;
|
||||
startBidAmount: number;
|
||||
finishBidAmount: number;
|
||||
},
|
||||
OpenAuctionResponse
|
||||
>,
|
||||
OpenSellRiceAuction: POST as APICallT<
|
||||
{
|
||||
amount: number;
|
||||
closeTurnCnt: number;
|
||||
startBidAmount: number;
|
||||
finishBidAmount: number;
|
||||
},
|
||||
OpenAuctionResponse
|
||||
>,
|
||||
|
||||
BidUniqueAuction: PUT as APICallT<{
|
||||
auctionID: number;
|
||||
amount: number;
|
||||
}>,
|
||||
GetUniqueItemAuctionDetail: GET as APICallT<{
|
||||
auctionID: number;
|
||||
}, UniqueItemAuctionDetail>,
|
||||
GetUniqueItemAuctionList: GET as APICallT<undefined, UniqueItemAuctionList>,
|
||||
OpenUniqueAuction: POST as APICallT<{
|
||||
itemID: string,
|
||||
amount: number,
|
||||
}, OpenAuctionResponse>,
|
||||
},
|
||||
Betting: {
|
||||
Bet: PUT as APICallT<{
|
||||
bettingID: number;
|
||||
bettingType: number[];
|
||||
amount: number;
|
||||
}>,
|
||||
GetBettingDetail: NumVar("betting_id", GET as APICallT<undefined, BettingDetailResponse>),
|
||||
GetBettingList: GET as APICallT<
|
||||
{
|
||||
req?: "bettingNation" | "tournament";
|
||||
},
|
||||
BettingListResponse
|
||||
>,
|
||||
},
|
||||
Command: {
|
||||
GetReservedCommand: GET as APICallT<undefined, ReservedCommandResponse>,
|
||||
PushCommand: PUT as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
RepeatCommand: PUT as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
ReserveCommand: PUT as APICallT<
|
||||
{
|
||||
turnList: number[];
|
||||
action: string;
|
||||
arg?: Args;
|
||||
},
|
||||
ReserveCommandResponse
|
||||
>,
|
||||
ReserveBulkCommand: PUT as APICallT<
|
||||
{
|
||||
turnList: number[];
|
||||
action: string;
|
||||
arg?: Args;
|
||||
}[],
|
||||
ReserveBulkCommandResponse
|
||||
>,
|
||||
},
|
||||
General: {
|
||||
Join: POST as APICallT<JoinArgs>,
|
||||
GetGeneralLog: GET as APICallT<
|
||||
{
|
||||
reqType: GeneralLogType;
|
||||
reqTo?: number;
|
||||
},
|
||||
GetGeneralLogResponse
|
||||
>,
|
||||
DropItem: PUT as APICallT<{
|
||||
itemType: ItemTypeKey;
|
||||
}>,
|
||||
},
|
||||
Global: {
|
||||
GeneralList: GET as APICallT<undefined, GeneralListResponse>,
|
||||
GeneralListWithToken: GET as APICallT<undefined, GeneralListResponse>,
|
||||
GetConst: GET as APICallT<undefined, GetConstResponse>,
|
||||
GetHistory: StrVar("serverID")(NumVar("year", NumVar("month", GET as APICallT<undefined, GetHistoryResponse>))),
|
||||
GetCurrentHistory: GET as APICallT<undefined, GetCurrentHistoryResponse>,
|
||||
GetMap: GET as APICallT<
|
||||
{
|
||||
neutralView?: 0 | 1;
|
||||
showMe?: 0 | 1;
|
||||
},
|
||||
MapResult
|
||||
>,
|
||||
GetCachedMap: GET as APICallT<undefined, CachedMapResult>,
|
||||
GetDiplomacy: GET as APICallT<undefined, GetDiplomacyResponse>,
|
||||
ExecuteEngine: POST as APICallT<undefined, ValidResponse & { updated: boolean }>,
|
||||
},
|
||||
InheritAction: {
|
||||
BuyHiddenBuff: PUT as APICallT<{
|
||||
type: inheritBuffType;
|
||||
level: number;
|
||||
}>,
|
||||
BuyRandomUnique: PUT as APICallT<undefined>,
|
||||
ResetSpecialWar: PUT as APICallT<undefined>,
|
||||
ResetTurnTime: PUT as APICallT<undefined>,
|
||||
SetNextSpecialWar: PUT as APICallT<{
|
||||
type: string;
|
||||
}>,
|
||||
},
|
||||
Misc: {
|
||||
UploadImage: POST as APICallT<
|
||||
{
|
||||
imageData: string;
|
||||
},
|
||||
UploadImageResponse
|
||||
>,
|
||||
},
|
||||
NationCommand: {
|
||||
GetReservedCommand: GET as APICallT<undefined, ChiefResponse>,
|
||||
PushCommand: PUT as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
RepeatCommand: PUT as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
ReserveCommand: PUT as APICallT<
|
||||
{
|
||||
turnList: number[];
|
||||
action: string;
|
||||
arg?: Args;
|
||||
},
|
||||
ReserveCommandResponse
|
||||
>,
|
||||
ReserveBulkCommand: PUT as APICallT<
|
||||
{
|
||||
turnList: number[];
|
||||
action: string;
|
||||
arg?: Args;
|
||||
}[],
|
||||
ReserveBulkCommandResponse
|
||||
>,
|
||||
},
|
||||
Nation: {
|
||||
GeneralList: GET as APICallT<undefined, NationGeneralListResponse>,
|
||||
SetNotice: PUT as APICallT<{
|
||||
msg: string;
|
||||
}>,
|
||||
SetScoutMsg: PUT as APICallT<{
|
||||
msg: string;
|
||||
}>,
|
||||
SetBill: PATCH as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
SetRate: PATCH as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
SetSecretLimit: PATCH as APICallT<{
|
||||
amount: number;
|
||||
}>,
|
||||
SetBlockWar: PATCH as APICallT<
|
||||
{
|
||||
value: boolean;
|
||||
},
|
||||
SetBlockWarResponse
|
||||
>,
|
||||
SetBlockScout: PATCH as APICallT<{
|
||||
value: boolean;
|
||||
}>,
|
||||
GetGeneralLog: GET as APICallT<
|
||||
{
|
||||
generalID: number;
|
||||
reqType: GeneralLogType;
|
||||
reqTo?: number;
|
||||
},
|
||||
GetGeneralLogResponse
|
||||
>,
|
||||
},
|
||||
Vote: {
|
||||
AddComment: POST as APICallT<{
|
||||
voteID: number;
|
||||
text: string;
|
||||
}>,
|
||||
GetVoteList: GET as APICallT<undefined, VoteListResult>,
|
||||
GetVoteDetail: GET as APICallT<
|
||||
{
|
||||
voteID: number;
|
||||
},
|
||||
VoteDetailResult
|
||||
>,
|
||||
NewVote: POST as APICallT<{
|
||||
title: string;
|
||||
multipleOptions?: number;
|
||||
endDate?: string;
|
||||
options: string[];
|
||||
keepOldVote?: boolean;
|
||||
}>,
|
||||
Vote: POST as APICallT<
|
||||
{
|
||||
voteID: number;
|
||||
selection: number[];
|
||||
},
|
||||
ValidResponse & { wonLottery: boolean }
|
||||
>,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const SammoAPI = APIPathGen(apiRealPath, (path: string[], tail: APITail, pathParam) => {
|
||||
const method = extractHttpMethod(tail);
|
||||
return (args?: RawArgType, returnError?: boolean) => {
|
||||
if (returnError) {
|
||||
return callSammoAPI(method, path.join('/'), args, pathParam, true);
|
||||
}
|
||||
return callSammoAPI(method, path.join('/'), args, pathParam);
|
||||
};
|
||||
});
|
||||
const method = extractHttpMethod(tail);
|
||||
return (args?: RawArgType, returnError?: boolean) => {
|
||||
if (returnError) {
|
||||
return callSammoAPI(method, path.join("/"), args, pathParam, true);
|
||||
}
|
||||
return callSammoAPI(method, path.join("/"), args, pathParam);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"common": "common_deprecated.ts"
|
||||
},
|
||||
"ingame_vue": {
|
||||
"v_auction": "v_auction.ts",
|
||||
"v_inheritPoint": "v_inheritPoint.ts",
|
||||
"v_board": "v_board.ts",
|
||||
"v_cachedMap": "v_cachedMap",
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="bg0">
|
||||
<div class="bg2">거래장</div>
|
||||
<div style="background-color: orange">쌀 구매</div>
|
||||
<div class="row gx-0">
|
||||
<div class="col">번호</div>
|
||||
<div class="col">판매자</div>
|
||||
<div class="col">물품</div>
|
||||
<div class="col">수량</div>
|
||||
<div class="col">시작 구매가</div>
|
||||
<div class="col">현재 구매가</div>
|
||||
<div class="col">즉시 구매가</div>
|
||||
<div class="col">단가</div>
|
||||
<div class="col">구매 예정자</div>
|
||||
<div class="col">거래 종료</div>
|
||||
</div>
|
||||
<div v-for="auction of buyRice" :key="auction.id" class="row gx-0" @click="selectedBuyRiceAuction = auction">
|
||||
<div class="col">{{ auction.id }}</div>
|
||||
<div class="col">{{ auction.hostName }}</div>
|
||||
<div class="col">쌀</div>
|
||||
<div class="col">{{ auction.amount }}</div>
|
||||
<div class="col">{{ auction.startBidAmount }}</div>
|
||||
<div class="col">{{ auction.highestBid ? auction.highestBid.amount : "-" }}</div>
|
||||
<div class="col">{{ auction.finishBidAmount }}</div>
|
||||
<div class="col">{{ auction.highestBid ? (auction.highestBid.amount / auction.amount).toFixed(2) : "-" }}</div>
|
||||
<div class="col">{{ auction.highestBid ? auction.highestBid.generalName : "-" }}</div>
|
||||
<div class="col">{{ auction.closeDate }}</div>
|
||||
</div>
|
||||
<div v-if="selectedBuyRiceAuction !== undefined" class="row">
|
||||
<div class="col">{{ selectedBuyRiceAuction.id }}번 쌀 {{ selectedBuyRiceAuction.amount }} 경매에</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="bidAmountBuyRiceAuction"
|
||||
:int="true"
|
||||
:min="selectedBuyRiceAuction.startBidAmount"
|
||||
:max="selectedBuyRiceAuction.finishBidAmount"
|
||||
title="금"
|
||||
:step="10"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col"><BButton @click="bidBuyRiceAuction">입찰</BButton></div>
|
||||
</div>
|
||||
|
||||
<div style="background-color: skyblue">쌀 판매</div>
|
||||
<div class="row gx-0">
|
||||
<div class="col">번호</div>
|
||||
<div class="col">판매자</div>
|
||||
<div class="col">물품</div>
|
||||
<div class="col">수량</div>
|
||||
<div class="col">시작 구매가</div>
|
||||
<div class="col">현재 구매가</div>
|
||||
<div class="col">즉시 구매가</div>
|
||||
<div class="col">단가</div>
|
||||
<div class="col">구매 예정자</div>
|
||||
<div class="col">거래 종료</div>
|
||||
</div>
|
||||
<div v-for="auction of sellRice" :key="auction.id" class="row gx-0" @click="selectedSellRiceAuction = auction">
|
||||
<div class="col">{{ auction.id }}</div>
|
||||
<div class="col">{{ auction.hostName }}</div>
|
||||
<div class="col">금</div>
|
||||
<div class="col">{{ auction.amount }}</div>
|
||||
<div class="col">{{ auction.startBidAmount }}</div>
|
||||
<div class="col">{{ auction.highestBid ? auction.highestBid.amount : "-" }}</div>
|
||||
<div class="col">{{ auction.finishBidAmount }}</div>
|
||||
<div class="col">{{ auction.highestBid ? (auction.highestBid.amount / auction.amount).toFixed(2) : "-" }}</div>
|
||||
<div class="col">{{ auction.highestBid ? auction.highestBid.generalName : "-" }}</div>
|
||||
<div class="col">{{ auction.closeDate }}</div>
|
||||
</div>
|
||||
<div v-if="selectedSellRiceAuction !== undefined" class="row">
|
||||
<div class="col">{{ selectedSellRiceAuction.id }}번 금 {{ selectedSellRiceAuction.amount }} 경매에</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="bidAmountSellRiceAuction"
|
||||
:int="true"
|
||||
:min="selectedSellRiceAuction.startBidAmount"
|
||||
:max="selectedSellRiceAuction.finishBidAmount"
|
||||
title="쌀"
|
||||
:step="10"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col"><BButton @click="bidSellRiceAuction">입찰</BButton></div>
|
||||
</div>
|
||||
|
||||
<div>경매 등록</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
매물
|
||||
<BButtonGroup>
|
||||
<BButton :pressed="openAuctionInfo.type == 'buyRice'" @click="openAuctionInfo.type = 'buyRice'"> 쌀 </BButton>
|
||||
<BButton :pressed="openAuctionInfo.type == 'sellRice'" @click="openAuctionInfo.type = 'sellRice'">
|
||||
금
|
||||
</BButton>
|
||||
</BButtonGroup>
|
||||
</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="openAuctionInfo.closeTurnCnt"
|
||||
:int="true"
|
||||
:min="3"
|
||||
:max="24"
|
||||
title="기간(턴)"
|
||||
:step="1"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="openAuctionInfo.amount"
|
||||
:int="true"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
title="수량"
|
||||
:step="10"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="openAuctionInfo.startBidAmount"
|
||||
:int="true"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
title="시작구매가"
|
||||
:step="10"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="openAuctionInfo.finishBidAmount"
|
||||
:int="true"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
title="즉시구매가"
|
||||
:step="10"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col">
|
||||
<BButton @click="openAuction">등록</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { BasicResourceAuctionInfo } from "@/defs/API/Auction";
|
||||
import { SammoAPI } from "@/SammoAPI";
|
||||
import { unwrap } from "@/util/unwrap";
|
||||
import { useToast, BButtonGroup, BButton } from "bootstrap-vue-3";
|
||||
import { isString } from "lodash";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
|
||||
const toasts = unwrap(useToast());
|
||||
|
||||
const buyRice = ref<BasicResourceAuctionInfo[]>([]);
|
||||
const sellRice = ref<BasicResourceAuctionInfo[]>([]);
|
||||
|
||||
const selectedBuyRiceAuction = ref<BasicResourceAuctionInfo | undefined>(undefined);
|
||||
const bidAmountBuyRiceAuction = ref<number>(0);
|
||||
|
||||
watch(selectedBuyRiceAuction, (auction) => {
|
||||
if (!auction) {
|
||||
return;
|
||||
}
|
||||
bidAmountBuyRiceAuction.value = auction.highestBid ? auction.highestBid.amount : auction.startBidAmount;
|
||||
});
|
||||
|
||||
|
||||
async function bidBuyRiceAuction() {
|
||||
if (selectedBuyRiceAuction.value === undefined) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await SammoAPI.Auction.BidBuyRiceAuction({
|
||||
auctionID: selectedBuyRiceAuction.value.id,
|
||||
amount: bidAmountBuyRiceAuction.value,
|
||||
});
|
||||
toasts.success({
|
||||
title: "입찰 완료",
|
||||
body: `입찰했습니다.`,
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedSellRiceAuction = ref<BasicResourceAuctionInfo | undefined>(undefined);
|
||||
const bidAmountSellRiceAuction = ref<number>(0);
|
||||
|
||||
watch(selectedSellRiceAuction, (auction) => {
|
||||
if (!auction) {
|
||||
return;
|
||||
}
|
||||
bidAmountSellRiceAuction.value = auction.highestBid ? auction.highestBid.amount : auction.startBidAmount;
|
||||
});
|
||||
|
||||
async function bidSellRiceAuction() {
|
||||
if (selectedSellRiceAuction.value === undefined) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await SammoAPI.Auction.BidSellRiceAuction({
|
||||
auctionID: selectedSellRiceAuction.value.id,
|
||||
amount: bidAmountSellRiceAuction.value,
|
||||
});
|
||||
toasts.success({
|
||||
title: "입찰 완료",
|
||||
body: `입찰했습니다.`,
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type openAuctionT = {
|
||||
type: "buyRice" | "sellRice";
|
||||
amount: number;
|
||||
startBidAmount: number;
|
||||
finishBidAmount: number;
|
||||
closeTurnCnt: number;
|
||||
};
|
||||
|
||||
const openAuctionInfo = reactive<openAuctionT>({
|
||||
type: "buyRice",
|
||||
amount: 1000,
|
||||
startBidAmount: 500,
|
||||
finishBidAmount: 2000,
|
||||
closeTurnCnt: 24,
|
||||
});
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const result = await SammoAPI.Auction.GetActiveResourceAuctionList();
|
||||
buyRice.value = result.buyRice;
|
||||
sellRice.value = result.sellRice;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openAuction() {
|
||||
const { type, amount, startBidAmount, finishBidAmount, closeTurnCnt } = openAuctionInfo;
|
||||
|
||||
try {
|
||||
const apiCall = type === "buyRice" ? SammoAPI.Auction.OpenBuyRiceAuction : SammoAPI.Auction.OpenSellRiceAuction;
|
||||
const result = await apiCall({
|
||||
amount,
|
||||
startBidAmount,
|
||||
finishBidAmount,
|
||||
closeTurnCnt,
|
||||
});
|
||||
toasts.success({
|
||||
title: "성공",
|
||||
body: `${result.auctionID}번 경매로 등록되었습니다.`,
|
||||
});
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
void refresh();
|
||||
console.log("mounted");
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="bg0">
|
||||
<div class="bg2">유니크 경매장</div>
|
||||
<div>내 가명: {{ obfuscatedName }}</div>
|
||||
<template v-if="currentAuction !== undefined">
|
||||
<div class="bg1">상세</div>
|
||||
<div class="row gx-0">
|
||||
<div class="col-4 col-md-2 bg2">경매 번호</div>
|
||||
<div class="col-4 col-md-2">{{ currentAuction.auction.id }}</div>
|
||||
|
||||
<div class="col-4 col-md-2 bg2">경매명</div>
|
||||
<div class="col-4 col-md-2">{{ currentAuction.auction.title }}</div>
|
||||
|
||||
<div class="col-4 col-md-2 bg2">주최자(익명)</div>
|
||||
<div class="col-4 col-md-2">{{ currentAuction.auction.hostName }}</div>
|
||||
|
||||
<div class="col-4 col-md-2 bg2">종료시점</div>
|
||||
<div class="col-4 col-md-2">{{ currentAuction.auction.closeDate }}</div>
|
||||
|
||||
<div class="col-4 col-md-2 bg2">최대지연</div>
|
||||
<div class="col-4 col-md-2">{{ currentAuction.auction.availableLatestBidCloseDate }}</div>
|
||||
</div>
|
||||
<div class="bg1">입찰자 목록</div>
|
||||
<div class="row gx-0 bg2">
|
||||
<div class="col-4">입찰자</div>
|
||||
<div class="col-4">입찰포인트</div>
|
||||
<div class="col-4">시각</div>
|
||||
</div>
|
||||
<div v-for="bidder of currentAuction.bidList" :key="bidder.amount" class="row gx-0">
|
||||
<div class="col-4">{{ bidder.generalName }}</div>
|
||||
<div class="col-4">{{ bidder.amount }}</div>
|
||||
<div class="col-4">{{ bidder.date }}</div>
|
||||
</div>
|
||||
<div class="bg1">입찰하기</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<NumberInputWithInfo
|
||||
v-model="bidAmount"
|
||||
:int="true"
|
||||
:min="currentAuction.bidList[0].amount"
|
||||
title="유산포인트"
|
||||
:step="1"
|
||||
></NumberInputWithInfo>
|
||||
</div>
|
||||
<div class="col"><BButton @click="bidAuction">입찰</BButton></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="bg1">목록</div>
|
||||
<div v-for="[auctionID, auction] of auctionList" :key="auctionID" class="row" @click="currentAuctionID = auctionID">
|
||||
<div class="col">{{ auction.id }}</div>
|
||||
<div class="col">{{ auction.finished ? "종료됨" : "진행중" }}</div>
|
||||
<div class="col">{{ auction.title }}</div>
|
||||
<div class="col">{{ auction.hostName }}</div>
|
||||
<div class="col">{{ auction.closeDate }}</div>
|
||||
<div class="col">{{ auction.remainCloseDateExtensionCnt > 0 ? "남음" : "소진" }}</div>
|
||||
<div class="col">{{ auction.highestBid.generalName }}</div>
|
||||
<div class="col">{{ auction.highestBid.amount }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UniqueItemAuctionDetail, UniqueItemAuctionList } from "@/defs/API/Auction";
|
||||
import { SammoAPI } from "@/SammoAPI";
|
||||
import { unwrap } from "@/util/unwrap";
|
||||
import { useToast, BButton } from "bootstrap-vue-3";
|
||||
import { isString } from "lodash";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import NumberInputWithInfo from "@/components/NumberInputWithInfo.vue";
|
||||
|
||||
type AuctionItemInfo = UniqueItemAuctionList["list"][0];
|
||||
|
||||
const currentAuctionID = ref<number>();
|
||||
const currentAuction = ref<UniqueItemAuctionDetail | undefined>(undefined);
|
||||
const bidAmount = ref<number>(5000);
|
||||
|
||||
async function refreshDetail() {
|
||||
if (currentAuctionID.value === undefined) {
|
||||
return;
|
||||
}
|
||||
const auctionID = currentAuctionID.value;
|
||||
try {
|
||||
currentAuction.value = await SammoAPI.Auction.GetUniqueItemAuctionDetail({ auctionID });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
unwrap(useToast()).danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
watch(currentAuctionID, () => {
|
||||
void refreshDetail();
|
||||
});
|
||||
const auctionList = ref(new Map<number, AuctionItemInfo>());
|
||||
const obfuscatedName = ref("");
|
||||
|
||||
const toasts = unwrap(useToast());
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const result = await SammoAPI.Auction.GetUniqueItemAuctionList();
|
||||
obfuscatedName.value = result.obfuscatedName;
|
||||
auctionList.value = new Map(result.list.map((auction) => [auction.id, auction]));
|
||||
if (currentAuctionID.value === undefined && result.list.length > 0) {
|
||||
currentAuctionID.value = result.list[0].id;
|
||||
bidAmount.value = result.list[0].highestBid.amount;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bidAuction() {
|
||||
if (currentAuction.value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const amount = bidAmount.value;
|
||||
const auctionInfo = currentAuction.value.auction;
|
||||
|
||||
if (confirm(`${auctionInfo.title}에 ${amount}유산포인트를 입찰하시겠습니까?`)) {
|
||||
try {
|
||||
await SammoAPI.Auction.BidUniqueAuction({ auctionID: auctionInfo.id, amount });
|
||||
toasts.success({
|
||||
title: "성공",
|
||||
body: "입찰이 완료되었습니다.",
|
||||
});
|
||||
void refreshDetail();
|
||||
void refresh();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
title: "에러",
|
||||
body: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void refresh();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { ValidResponse } from "@/util/callSammoAPI";
|
||||
|
||||
export type BasicResourceAuctionBidder = {
|
||||
amount: number;
|
||||
date: string;
|
||||
generalID: number;
|
||||
generalName: string;
|
||||
};
|
||||
|
||||
export type BasicResourceAuctionInfo = {
|
||||
id: number;
|
||||
type: "buyRice" | "sellRice";
|
||||
hostGeneralID: number;
|
||||
hostName: string;
|
||||
openDate: string;
|
||||
closeDate: string;
|
||||
amount: number;
|
||||
startBidAmount: number;
|
||||
finishBidAmount: number;
|
||||
highestBid: BasicResourceAuctionBidder;
|
||||
};
|
||||
|
||||
export type UniqueItemAuctionBidder = {
|
||||
generalName: string;
|
||||
amount: number;
|
||||
isCallerHighestBidder: boolean;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type UniqueItemAuctionInfo = {
|
||||
id: number;
|
||||
finished: boolean;
|
||||
title: string;
|
||||
target: string;
|
||||
isCallerHost: boolean;
|
||||
hostName: string;
|
||||
closeDate: string;
|
||||
remainCloseDateExtensionCnt: number;
|
||||
availableLatestBidCloseDate: string;
|
||||
};
|
||||
|
||||
export type ActiveResourceAuctionList = ValidResponse & {
|
||||
buyRice: BasicResourceAuctionInfo[];
|
||||
sellRice: BasicResourceAuctionInfo[];
|
||||
recentLogs: string[];
|
||||
generalID: number;
|
||||
};
|
||||
|
||||
export type UniqueItemAuctionList = ValidResponse & {
|
||||
list: (UniqueItemAuctionInfo & {
|
||||
highestBid: UniqueItemAuctionBidder;
|
||||
})[];
|
||||
obfuscatedName: string;
|
||||
};
|
||||
|
||||
export type UniqueItemAuctionDetail = ValidResponse & {
|
||||
auction: UniqueItemAuctionInfo;
|
||||
bidList: UniqueItemAuctionBidder[];
|
||||
obfuscatedName: string;
|
||||
};
|
||||
|
||||
export type OpenAuctionResponse = ValidResponse & {
|
||||
auctionID: number;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import "@scss/auction.scss";
|
||||
import { createApp } from 'vue'
|
||||
import PageAuction from '@/PageAuction.vue';
|
||||
import BootstrapVue3, { BToastPlugin } from 'bootstrap-vue-3'
|
||||
import { auto500px } from "./util/auto500px";
|
||||
import { insertCustomCSS } from "./util/customCSS";
|
||||
import { htmlReady } from "./util/htmlReady";
|
||||
|
||||
auto500px();
|
||||
|
||||
htmlReady(() => {
|
||||
insertCustomCSS();
|
||||
});
|
||||
createApp(PageAuction).use(BootstrapVue3).use(BToastPlugin).mount('#app')
|
||||
Reference in New Issue
Block a user