新增:限价单接口对接
This commit is contained in:
parent
297d2d1c56
commit
b73a910b43
@ -105,8 +105,13 @@ Swagger UI 页面(如 [PmEvent findPmEvent](https://api.xtrader.vip/swagger/in
|
|||||||
|
|
||||||
### 公开事件列表 `GET /PmEvent/getPmEventPublic`
|
### 公开事件列表 `GET /PmEvent/getPmEventPublic`
|
||||||
|
|
||||||
- **Query**:page、pageSize、keyword、createdAtRange(array)。
|
- **Query**:page、pageSize、keyword、createdAtRange(array)、tokenid(可选,来自 market.clobTokenIds 的值,可传单个或数组)。
|
||||||
- **响应 200**:`data` 为 `response.PageResult`(list、page、pageSize、total);list 项为 `polymarket.PmEvent`,内含 markets、series、tags 等,markets[].outcomePrices 为价格数组,首项对应 Yes 概率。
|
- **响应 200**:`data` 为 `response.PageResult`(list、page、pageSize、total);list 项为 `polymarket.PmEvent`,内含 markets、series、tags 等,markets[].outcomePrices 为价格数组,首项对应 Yes 概率;markets[].clobTokenIds 与 outcomes/outcomePrices 顺序一致。
|
||||||
|
|
||||||
|
### 订单类型与交易方向(用于交易接口,`src/api/constants.ts`)
|
||||||
|
|
||||||
|
- **OrderType**:GTC=0(一直有效直到取消)、GTD=1(指定时间内有效)、FOK=2(全部成交或取消)、FAK=3(立即成交剩余取消)、Market=4(市价单)。
|
||||||
|
- **Side**:Buy=1、Sell=2。
|
||||||
|
|
||||||
### 通用响应与鉴权
|
### 通用响应与鉴权
|
||||||
|
|
||||||
|
|||||||
30
src/api/constants.ts
Normal file
30
src/api/constants.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 订单类型(OrderType)
|
||||||
|
* 用于交易接口入参
|
||||||
|
*/
|
||||||
|
export const OrderType = {
|
||||||
|
/** Good Till Cancelled: 一直有效直到取消 */
|
||||||
|
GTC: 0,
|
||||||
|
/** Good Till Date: 指定时间内有效 */
|
||||||
|
GTD: 1,
|
||||||
|
/** Fill Or Kill: 全部成交或立即取消(不允许部分成交) */
|
||||||
|
FOK: 2,
|
||||||
|
/** Fill And Kill (IOC): 立即成交,剩余部分取消 */
|
||||||
|
FAK: 3,
|
||||||
|
/** Market Order: 市价单,立即按最优价成交,剩余取消 (同 FAK,但不限价) */
|
||||||
|
Market: 4,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type OrderTypeValue = (typeof OrderType)[keyof typeof OrderType]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交易方向(Side)
|
||||||
|
*/
|
||||||
|
export const Side = {
|
||||||
|
/** 买入 */
|
||||||
|
Buy: 1,
|
||||||
|
/** 卖出 */
|
||||||
|
Sell: 2,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SideValue = (typeof Side)[keyof typeof Side]
|
||||||
@ -65,8 +65,8 @@ export interface PmEventMarketItem {
|
|||||||
outcomes?: string[]
|
outcomes?: string[]
|
||||||
/** 各选项价格,outcomes[0] 对应 outcomePrices[0] */
|
/** 各选项价格,outcomes[0] 对应 outcomePrices[0] */
|
||||||
outcomePrices?: string[] | number[]
|
outcomePrices?: string[] | number[]
|
||||||
/** 市场对应的 clob token id 与 outcomePrices 和outcomes顺序一致 */
|
/** 市场对应的 clob token id 与 outcomePrices、outcomes 顺序一致,outcomes[0] 对应 clobTokenIds[0] */
|
||||||
clobTokenIds:string[]
|
clobTokenIds?: string[]
|
||||||
endDate?: string
|
endDate?: string
|
||||||
volume?: number
|
volume?: number
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
@ -79,6 +79,16 @@ export function getMarketId(m: PmEventMarketItem | null | undefined): string | u
|
|||||||
return raw != null ? String(raw) : undefined
|
return raw != null ? String(raw) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 从市场项取 clobTokenId,outcomeIndex 0=Yes/第一选项,1=No/第二选项 */
|
||||||
|
export function getClobTokenId(
|
||||||
|
m: PmEventMarketItem | null | undefined,
|
||||||
|
outcomeIndex: 0 | 1 = 0
|
||||||
|
): string | undefined {
|
||||||
|
if (!m?.clobTokenIds?.length) return undefined
|
||||||
|
const id = m.clobTokenIds[outcomeIndex]
|
||||||
|
return id != null ? String(id) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
/** 对应 definitions polymarket.PmSeries 常用字段 */
|
/** 对应 definitions polymarket.PmSeries 常用字段 */
|
||||||
export interface PmEventSeriesItem {
|
export interface PmEventSeriesItem {
|
||||||
ID?: number
|
ID?: number
|
||||||
@ -109,22 +119,30 @@ export interface GetPmEventListParams {
|
|||||||
keyword?: string
|
keyword?: string
|
||||||
/** 创建时间范围,如 ['2025-01-01', '2025-12-31'] */
|
/** 创建时间范围,如 ['2025-01-01', '2025-12-31'] */
|
||||||
createdAtRange?: string[]
|
createdAtRange?: string[]
|
||||||
|
/** clobTokenIds 对应的值,用于按市场 token 筛选;可从 market.clobTokenIds 获取 */
|
||||||
|
tokenid?: string | string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页获取 Event 列表(公开接口,不需要鉴权)
|
* 分页获取 Event 列表(公开接口,不需要鉴权)
|
||||||
* GET /PmEvent/getPmEventPublic
|
* GET /PmEvent/getPmEventPublic
|
||||||
|
*
|
||||||
|
* Query: page, pageSize, keyword, createdAtRange, tokenid
|
||||||
|
* tokenid 对应 market.clobTokenIds 中的值,可传单个或数组
|
||||||
*/
|
*/
|
||||||
export async function getPmEventPublic(
|
export async function getPmEventPublic(
|
||||||
params: GetPmEventListParams = {}
|
params: GetPmEventListParams = {}
|
||||||
): Promise<PmEventListResponse> {
|
): Promise<PmEventListResponse> {
|
||||||
const { page = 1, pageSize = 10, keyword, createdAtRange } = params
|
const { page = 1, pageSize = 10, keyword, createdAtRange, tokenid } = params
|
||||||
const query: Record<string, string | number | string[] | undefined> = {
|
const query: Record<string, string | number | string[] | undefined> = {
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
}
|
}
|
||||||
if (keyword != null && keyword !== '') query.keyword = keyword
|
if (keyword != null && keyword !== '') query.keyword = keyword
|
||||||
if (createdAtRange != null && createdAtRange.length) query.createdAtRange = createdAtRange
|
if (createdAtRange != null && createdAtRange.length) query.createdAtRange = createdAtRange
|
||||||
|
if (tokenid != null) {
|
||||||
|
query.tokenid = Array.isArray(tokenid) ? tokenid : [tokenid]
|
||||||
|
}
|
||||||
|
|
||||||
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
||||||
}
|
}
|
||||||
@ -186,6 +204,8 @@ export interface EventCardOutcome {
|
|||||||
noLabel?: string
|
noLabel?: string
|
||||||
/** 可选,用于交易时区分 market */
|
/** 可选,用于交易时区分 market */
|
||||||
marketId?: string
|
marketId?: string
|
||||||
|
/** 用于下单 tokenId,与 outcomes 顺序一致 */
|
||||||
|
clobTokenIds?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,6 +233,8 @@ export interface EventCardItem {
|
|||||||
isNew?: boolean
|
isNew?: boolean
|
||||||
/** 当前市场 ID(单 market 时为第一个 market 的 ID,供交易/Split 使用) */
|
/** 当前市场 ID(单 market 时为第一个 market 的 ID,供交易/Split 使用) */
|
||||||
marketId?: string
|
marketId?: string
|
||||||
|
/** 用于下单 tokenId,单 market 时取自 firstMarket.clobTokenIds */
|
||||||
|
clobTokenIds?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 内存缓存:列表数据,切换页面时复用,下拉刷新时清空 */
|
/** 内存缓存:列表数据,切换页面时复用,下拉刷新时清空 */
|
||||||
@ -298,6 +320,7 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem {
|
|||||||
yesLabel: m.outcomes?.[0] ?? 'Yes',
|
yesLabel: m.outcomes?.[0] ?? 'Yes',
|
||||||
noLabel: m.outcomes?.[1] ?? 'No',
|
noLabel: m.outcomes?.[1] ?? 'No',
|
||||||
marketId: getMarketId(m),
|
marketId: getMarketId(m),
|
||||||
|
clobTokenIds: m.clobTokenIds,
|
||||||
}))
|
}))
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
@ -316,5 +339,6 @@ export function mapEventItemToCard(item: PmEventListItem): EventCardItem {
|
|||||||
noLabel: firstMarket?.outcomes?.[1] ?? 'No',
|
noLabel: firstMarket?.outcomes?.[1] ?? 'No',
|
||||||
isNew: item.new === true,
|
isNew: item.new === true,
|
||||||
marketId: getMarketId(firstMarket),
|
marketId: getMarketId(firstMarket),
|
||||||
|
clobTokenIds: firstMarket?.clobTokenIds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,36 @@ export interface ApiResponse<T = unknown> {
|
|||||||
msg: string
|
msg: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单请求体(/clob/gateway/submitOrder)
|
||||||
|
* tokenID 来自 market.clobTokenIds,outcomeIndex 0=Yes 1=No
|
||||||
|
*/
|
||||||
|
export interface ClobSubmitOrderRequest {
|
||||||
|
expiration: number
|
||||||
|
feeRateBps: number
|
||||||
|
nonce: number
|
||||||
|
orderType: number
|
||||||
|
/** 价格(整数,已乘 10000) */
|
||||||
|
price: number
|
||||||
|
side: number
|
||||||
|
/** 数量(份额) */
|
||||||
|
size: number
|
||||||
|
taker: boolean
|
||||||
|
tokenID: string
|
||||||
|
userID: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /clob/gateway/submitOrder
|
||||||
|
* 下单(买入/卖出 Yes 或 No),需鉴权
|
||||||
|
*/
|
||||||
|
export async function pmOrderPlace(
|
||||||
|
data: ClobSubmitOrderRequest,
|
||||||
|
config?: { headers?: Record<string, string> }
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
return post<ApiResponse>('/clob/gateway/submitOrder', data, config)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split 请求体(/PmMarket/split)
|
* Split 请求体(/PmMarket/split)
|
||||||
* 用 USDC 兑换该市场的 Yes+No 份额(1 USDC ≈ 1 Yes + 1 No)
|
* 用 USDC 兑换该市场的 Yes+No 份额(1 USDC ≈ 1 Yes + 1 No)
|
||||||
|
|||||||
@ -171,7 +171,7 @@ const router = useRouter()
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
openTrade: [
|
openTrade: [
|
||||||
side: 'yes' | 'no',
|
side: 'yes' | 'no',
|
||||||
market?: { id: string; title: string; marketId?: string; outcomeTitle?: string }
|
market?: { id: string; title: string; marketId?: string; outcomeTitle?: string; clobTokenIds?: string[] }
|
||||||
]
|
]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@ -198,6 +198,8 @@ const props = withDefaults(
|
|||||||
isNew?: boolean
|
isNew?: boolean
|
||||||
/** 当前市场 ID(单 market 时供交易/Split 使用) */
|
/** 当前市场 ID(单 market 时供交易/Split 使用) */
|
||||||
marketId?: string
|
marketId?: string
|
||||||
|
/** 用于下单 tokenId,单 market 时 */
|
||||||
|
clobTokenIds?: string[]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
marketTitle: 'Mamdan opens city-owned grocery store b...',
|
marketTitle: 'Mamdan opens city-owned grocery store b...',
|
||||||
@ -289,7 +291,12 @@ const navigateToDetail = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openTradeSingle(side: 'yes' | 'no') {
|
function openTradeSingle(side: 'yes' | 'no') {
|
||||||
emit('openTrade', side, { id: props.id, title: props.marketTitle, marketId: props.marketId })
|
emit('openTrade', side, {
|
||||||
|
id: props.id,
|
||||||
|
title: props.marketTitle,
|
||||||
|
marketId: props.marketId,
|
||||||
|
clobTokenIds: props.clobTokenIds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
|
function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
|
||||||
@ -298,6 +305,7 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
|
|||||||
title: outcome.title,
|
title: outcome.title,
|
||||||
marketId: outcome.marketId,
|
marketId: outcome.marketId,
|
||||||
outcomeTitle: outcome.title,
|
outcomeTitle: outcome.title,
|
||||||
|
clobTokenIds: outcome.clobTokenIds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -84,8 +84,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
<!-- Action Button -->
|
<!-- Action Button -->
|
||||||
<v-btn class="action-btn" @click="submitOrder">
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">
|
||||||
{{ actionButtonText }}
|
{{ actionButtonText }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -172,13 +173,17 @@
|
|||||||
<v-icon>mdi-minus</v-icon>
|
<v-icon>mdi-minus</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model.number="limitPrice"
|
:model-value="limitPrice"
|
||||||
type="number"
|
type="number"
|
||||||
min="0.01"
|
min="0"
|
||||||
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
class="price-input-field"
|
class="price-input-field"
|
||||||
hide-details
|
hide-details
|
||||||
density="compact"
|
density="compact"
|
||||||
|
@update:model-value="onLimitPriceInput"
|
||||||
|
@keydown="onLimitPriceKeydown"
|
||||||
|
@paste="onLimitPricePaste"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-btn class="adjust-btn" icon @click="increasePrice">
|
<v-btn class="adjust-btn" icon @click="increasePrice">
|
||||||
<v-icon>mdi-plus</v-icon>
|
<v-icon>mdi-plus</v-icon>
|
||||||
@ -193,12 +198,15 @@
|
|||||||
<span class="label">Shares</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model.number="shares"
|
:model-value="shares"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="1"
|
||||||
class="shares-input-field"
|
class="shares-input-field"
|
||||||
hide-details
|
hide-details
|
||||||
density="compact"
|
density="compact"
|
||||||
|
@update:model-value="onSharesInput"
|
||||||
|
@keydown="onSharesKeydown"
|
||||||
|
@paste="onSharesPaste"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -267,8 +275,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Button -->
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
<v-btn class="action-btn" @click="submitOrder">
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">
|
||||||
{{ actionButtonText }}
|
{{ actionButtonText }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@ -314,7 +322,8 @@
|
|||||||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="price-options hide-in-mobile-sheet">
|
<div class="price-options hide-in-mobile-sheet">
|
||||||
@ -346,7 +355,7 @@
|
|||||||
<span class="label">Limit Price</span>
|
<span class="label">Limit Price</span>
|
||||||
<div class="price-input">
|
<div class="price-input">
|
||||||
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
||||||
<v-text-field v-model.number="limitPrice" type="number" min="0.01" step="0.01" class="price-input-field" hide-details density="compact"></v-text-field>
|
<v-text-field :model-value="limitPrice" type="number" min="0" max="1" step="0.01" class="price-input-field" hide-details density="compact" @update:model-value="onLimitPriceInput" @keydown="onLimitPriceKeydown" @paste="onLimitPricePaste"></v-text-field>
|
||||||
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -355,7 +364,7 @@
|
|||||||
<div class="shares-header">
|
<div class="shares-header">
|
||||||
<span class="label">Shares</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field v-model.number="shares" type="number" min="0" class="shares-input-field" hide-details density="compact"></v-text-field>
|
<v-text-field :model-value="shares" type="number" min="1" class="shares-input-field" hide-details density="compact" @update:model-value="onSharesInput" @keydown="onSharesKeydown" @paste="onSharesPaste"></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||||||
@ -393,7 +402,8 @@
|
|||||||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
@ -462,41 +472,42 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="price-options hide-in-mobile-sheet">
|
|
||||||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
|
||||||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="amount-header">
|
|
||||||
<div><span class="label amount-label">Amount</span><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span></div>
|
|
||||||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="amount-buttons">
|
|
||||||
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
|
||||||
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
|
||||||
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
|
||||||
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
|
||||||
</template>
|
</template>
|
||||||
|
</div>
|
||||||
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="price-options hide-in-mobile-sheet">
|
<div class="price-options hide-in-mobile-sheet">
|
||||||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||||||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group limit-price-group">
|
<div class="input-group">
|
||||||
|
<div class="amount-header">
|
||||||
|
<div><span class="label amount-label">Amount</span><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span></div>
|
||||||
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="amount-buttons">
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
||||||
|
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="price-options hide-in-mobile-sheet">
|
||||||
|
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||||||
|
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="input-group limit-price-group">
|
||||||
<div class="limit-price-header">
|
<div class="limit-price-header">
|
||||||
<span class="label">Limit Price</span>
|
<span class="label">Limit Price</span>
|
||||||
<div class="price-input">
|
<div class="price-input">
|
||||||
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
||||||
<v-text-field v-model.number="limitPrice" type="number" min="0.01" step="0.01" class="price-input-field" hide-details density="compact"></v-text-field>
|
<v-text-field :model-value="limitPrice" type="number" min="0" max="1" step="0.01" class="price-input-field" hide-details density="compact" @update:model-value="onLimitPriceInput" @keydown="onLimitPriceKeydown" @paste="onLimitPricePaste"></v-text-field>
|
||||||
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -505,7 +516,7 @@
|
|||||||
<div class="shares-header">
|
<div class="shares-header">
|
||||||
<span class="label">Shares</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field v-model.number="shares" type="number" min="0" class="shares-input-field" hide-details density="compact"></v-text-field>
|
<v-text-field :model-value="shares" type="number" min="1" class="shares-input-field" hide-details density="compact" @update:model-value="onSharesInput" @keydown="onSharesKeydown" @paste="onSharesPaste"></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||||||
@ -543,7 +554,8 @@
|
|||||||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
|
<v-btn class="action-btn" :loading="orderLoading" :disabled="orderLoading" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
@ -648,7 +660,8 @@
|
|||||||
import { ref, computed, watch, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
import { pmMarketMerge, pmMarketSplit } from '../api/market'
|
import { pmMarketMerge, pmMarketSplit, pmOrderPlace } from '../api/market'
|
||||||
|
import { OrderType, Side } from '../api/constants'
|
||||||
|
|
||||||
const { mobile } = useDisplay()
|
const { mobile } = useDisplay()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -658,6 +671,8 @@ export interface TradeMarketPayload {
|
|||||||
yesPrice: number
|
yesPrice: number
|
||||||
noPrice: number
|
noPrice: number
|
||||||
title?: string
|
title?: string
|
||||||
|
/** 与 outcomes/outcomePrices 顺序一致,用于下单 tokenId:0=Yes 1=No */
|
||||||
|
clobTokenIds?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@ -762,7 +777,7 @@ const limitType = ref('Limit')
|
|||||||
const expirationEnabled = ref(false)
|
const expirationEnabled = ref(false)
|
||||||
const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
||||||
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
||||||
const shares = ref(20) // 初始份额
|
const shares = ref(20) // 初始份额(正整数)
|
||||||
const expirationTime = ref('5m') // 初始过期时间
|
const expirationTime = ref('5m') // 初始过期时间
|
||||||
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // 过期时间选项
|
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // 过期时间选项
|
||||||
|
|
||||||
@ -771,6 +786,9 @@ const isMarketMode = computed(() => limitType.value === 'Market')
|
|||||||
const amount = ref(0) // Market mode amount
|
const amount = ref(0) // Market mode amount
|
||||||
const balance = ref(0) // Market mode balance
|
const balance = ref(0) // Market mode balance
|
||||||
|
|
||||||
|
const orderLoading = ref(false)
|
||||||
|
const orderError = ref('')
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
optionChange: [option: 'yes' | 'no']
|
optionChange: [option: 'yes' | 'no']
|
||||||
@ -803,11 +821,15 @@ function applyInitialOption(option: 'yes' | 'no') {
|
|||||||
syncLimitPriceFromMarket()
|
syncLimitPriceFromMarket()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampLimitPrice(v: number): number {
|
||||||
|
return Math.min(1, Math.max(0, Number.isFinite(v) ? v : 0))
|
||||||
|
}
|
||||||
|
|
||||||
/** 根据当前 props.market 与 selectedOption 同步 limitPrice(组件显示或 market 更新时调用) */
|
/** 根据当前 props.market 与 selectedOption 同步 limitPrice(组件显示或 market 更新时调用) */
|
||||||
function syncLimitPriceFromMarket() {
|
function syncLimitPriceFromMarket() {
|
||||||
const yesP = props.market?.yesPrice ?? 0.19
|
const yesP = props.market?.yesPrice ?? 0.19
|
||||||
const noP = props.market?.noPrice ?? 0.82
|
const noP = props.market?.noPrice ?? 0.82
|
||||||
limitPrice.value = selectedOption.value === 'yes' ? yesP : noP
|
limitPrice.value = clampLimitPrice(selectedOption.value === 'yes' ? yesP : noP)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -820,6 +842,7 @@ watch(() => props.initialOption, (option) => {
|
|||||||
|
|
||||||
watch(() => props.market, (m) => {
|
watch(() => props.market, (m) => {
|
||||||
if (m) {
|
if (m) {
|
||||||
|
orderError.value = ''
|
||||||
if (props.initialOption) applyInitialOption(props.initialOption)
|
if (props.initialOption) applyInitialOption(props.initialOption)
|
||||||
else syncLimitPriceFromMarket()
|
else syncLimitPriceFromMarket()
|
||||||
}
|
}
|
||||||
@ -830,29 +853,90 @@ const handleOptionChange = (option: 'yes' | 'no') => {
|
|||||||
selectedOption.value = option
|
selectedOption.value = option
|
||||||
const yesP = props.market?.yesPrice ?? 0.19
|
const yesP = props.market?.yesPrice ?? 0.19
|
||||||
const noP = props.market?.noPrice ?? 0.82
|
const noP = props.market?.noPrice ?? 0.82
|
||||||
limitPrice.value = option === 'yes' ? yesP : noP
|
limitPrice.value = clampLimitPrice(option === 'yes' ? yesP : noP)
|
||||||
emit('optionChange', option)
|
emit('optionChange', option)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限价调整方法
|
/** 仅在值在 [0,1] 且为有效数字时更新,否则保持原值不变 */
|
||||||
|
function onLimitPriceInput(v: unknown) {
|
||||||
|
const num = v == null ? NaN : Number(v)
|
||||||
|
if (!Number.isFinite(num) || num < 0 || num > 1) return
|
||||||
|
limitPrice.value = num
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 只允许数字和小数点输入 */
|
||||||
|
function onLimitPriceKeydown(e: KeyboardEvent) {
|
||||||
|
const key = e.key
|
||||||
|
const allowed = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End']
|
||||||
|
if (allowed.includes(key)) return
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
if (['a', 'c', 'v', 'x'].includes(key.toLowerCase())) return
|
||||||
|
}
|
||||||
|
if (key >= '0' && key <= '9') return
|
||||||
|
if (key === '.' && !String((e.target as HTMLInputElement)?.value ?? '').includes('.')) return
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 粘贴时只接受有效数字 */
|
||||||
|
function onLimitPricePaste(e: ClipboardEvent) {
|
||||||
|
const text = e.clipboardData?.getData('text') ?? ''
|
||||||
|
const num = parseFloat(text)
|
||||||
|
if (!Number.isFinite(num) || num < 0 || num > 1) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限价调整方法(0–1 区间)
|
||||||
const decreasePrice = () => {
|
const decreasePrice = () => {
|
||||||
limitPrice.value = Math.max(0.01, limitPrice.value - 0.01)
|
limitPrice.value = clampLimitPrice(limitPrice.value - 0.01)
|
||||||
}
|
}
|
||||||
|
|
||||||
const increasePrice = () => {
|
const increasePrice = () => {
|
||||||
limitPrice.value += 0.01
|
limitPrice.value = clampLimitPrice(limitPrice.value + 0.01)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 份额调整方法
|
/** 将 shares 限制为正整数(>= 1) */
|
||||||
|
function clampShares(v: number): number {
|
||||||
|
const n = Math.floor(Number.isFinite(v) ? v : 1)
|
||||||
|
return Math.max(1, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 仅在值为正整数时更新 shares */
|
||||||
|
function onSharesInput(v: unknown) {
|
||||||
|
const num = v == null ? NaN : Number(v)
|
||||||
|
const n = Math.floor(num)
|
||||||
|
if (!Number.isFinite(num) || n < 1 || num !== n) return
|
||||||
|
shares.value = n
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 只允许数字输入(Shares 为正整数) */
|
||||||
|
function onSharesKeydown(e: KeyboardEvent) {
|
||||||
|
const key = e.key
|
||||||
|
const allowed = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End']
|
||||||
|
if (allowed.includes(key)) return
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
if (['a', 'c', 'v', 'x'].includes(key.toLowerCase())) return
|
||||||
|
}
|
||||||
|
if (key >= '0' && key <= '9') return
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 粘贴时只接受正整数 */
|
||||||
|
function onSharesPaste(e: ClipboardEvent) {
|
||||||
|
const text = e.clipboardData?.getData('text') ?? ''
|
||||||
|
const num = parseInt(text, 10)
|
||||||
|
if (!Number.isFinite(num) || num < 1) e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 份额调整方法(保证正整数)
|
||||||
const adjustShares = (amount: number) => {
|
const adjustShares = (amount: number) => {
|
||||||
shares.value = Math.max(0, shares.value + amount)
|
shares.value = clampShares(shares.value + amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 份额百分比调整方法(仅在Sell模式下使用)
|
// 份额百分比调整方法(仅在Sell模式下使用)
|
||||||
const setSharesPercentage = (percentage: number) => {
|
const setSharesPercentage = (percentage: number) => {
|
||||||
// 假设最大份额为100,实际应用中可能需要根据用户的可用份额来计算
|
|
||||||
const maxShares = 100
|
const maxShares = 100
|
||||||
shares.value = Math.round((maxShares * percentage) / 100)
|
shares.value = clampShares(Math.round((maxShares * percentage) / 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Market mode methods
|
// Market mode methods
|
||||||
@ -870,17 +954,95 @@ const deposit = () => {
|
|||||||
// 实际应用中,这里应该调用存款API
|
// 实际应用中,这里应该调用存款API
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交订单(含 Set expiration)
|
/** 将 expirationTime 如 "5m" 转为 Unix 秒级时间戳(GTD 用),0 表示无过期 */
|
||||||
function submitOrder() {
|
function parseExpirationTimestamp(expTime: string): number {
|
||||||
emit('submit', {
|
const m = /^(\d+)(m|h|d)$/i.exec(expTime)
|
||||||
|
if (!m) return 0
|
||||||
|
const [, num, unit] = m
|
||||||
|
const n = parseInt(num ?? '0', 10)
|
||||||
|
if (!Number.isFinite(n) || n <= 0) return 0
|
||||||
|
const now = Date.now()
|
||||||
|
let ms = n
|
||||||
|
if (unit?.toLowerCase() === 'm') ms = n * 60 * 1000
|
||||||
|
else if (unit?.toLowerCase() === 'h') ms = n * 3600 * 1000
|
||||||
|
else if (unit?.toLowerCase() === 'd') ms = n * 86400 * 1000
|
||||||
|
return Math.floor((now + ms) / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交订单(含 Set expiration):对接 /clob/gateway/submitOrder
|
||||||
|
async function submitOrder() {
|
||||||
|
const marketId = props.market?.marketId
|
||||||
|
const clobIds = props.market?.clobTokenIds
|
||||||
|
const outcomeIndex = selectedOption.value === 'yes' ? 0 : 1
|
||||||
|
const tokenId = clobIds?.[outcomeIndex]
|
||||||
|
|
||||||
|
const payload = {
|
||||||
side: activeTab.value as 'buy' | 'sell',
|
side: activeTab.value as 'buy' | 'sell',
|
||||||
option: selectedOption.value,
|
option: selectedOption.value,
|
||||||
limitPrice: limitPrice.value,
|
limitPrice: limitPrice.value,
|
||||||
shares: shares.value,
|
shares: shares.value,
|
||||||
expirationEnabled: expirationEnabled.value,
|
expirationEnabled: expirationEnabled.value,
|
||||||
expirationTime: expirationTime.value,
|
expirationTime: expirationTime.value,
|
||||||
...(props.market?.marketId != null && { marketId: props.market.marketId }),
|
...(marketId != null && { marketId }),
|
||||||
})
|
}
|
||||||
|
emit('submit', payload)
|
||||||
|
|
||||||
|
if (!tokenId) {
|
||||||
|
orderError.value = '请先选择市场(需包含 clobTokenIds)'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const headers = userStore.getAuthHeaders()
|
||||||
|
if (!headers) {
|
||||||
|
orderError.value = '请先登录'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const uid = userStore.user?.id ?? userStore.user?.ID
|
||||||
|
const userIdNum = uid != null ? Number(uid) : 0
|
||||||
|
if (!Number.isFinite(userIdNum) || userIdNum <= 0) {
|
||||||
|
orderError.value = '用户信息异常'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMarket = limitType.value === 'Market'
|
||||||
|
const orderTypeNum = isMarket
|
||||||
|
? OrderType.Market
|
||||||
|
: expirationEnabled.value
|
||||||
|
? OrderType.GTD
|
||||||
|
: OrderType.GTC
|
||||||
|
const sideNum = activeTab.value === 'buy' ? Side.Buy : Side.Sell
|
||||||
|
const expiration =
|
||||||
|
orderTypeNum === OrderType.GTD && expirationEnabled.value
|
||||||
|
? parseExpirationTimestamp(expirationTime.value)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
orderLoading.value = true
|
||||||
|
orderError.value = ''
|
||||||
|
try {
|
||||||
|
const res = await pmOrderPlace(
|
||||||
|
{
|
||||||
|
expiration,
|
||||||
|
feeRateBps: 0,
|
||||||
|
nonce: 0,
|
||||||
|
orderType: orderTypeNum,
|
||||||
|
price: limitPrice.value,
|
||||||
|
side: sideNum,
|
||||||
|
size: clampShares(shares.value),
|
||||||
|
taker: true,
|
||||||
|
tokenID: tokenId,
|
||||||
|
userID: userIdNum,
|
||||||
|
},
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
|
if (res.code === 0 || res.code === 200) {
|
||||||
|
userStore.fetchUsdcBalance()
|
||||||
|
} else {
|
||||||
|
orderError.value = res.msg || '下单失败'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
orderError.value = e instanceof Error ? e.message : 'Request failed'
|
||||||
|
} finally {
|
||||||
|
orderLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -1423,6 +1585,12 @@ function submitOrder() {
|
|||||||
color: #dc2626;
|
color: #dc2626;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-error {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.merge-dialog-actions {
|
.merge-dialog-actions {
|
||||||
padding: 16px 20px 20px;
|
padding: 16px 20px 20px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
|||||||
@ -156,6 +156,7 @@ const tradeMarketPayload = computed(() => {
|
|||||||
yesPrice,
|
yesPrice,
|
||||||
noPrice,
|
noPrice,
|
||||||
title: m.question,
|
title: m.question,
|
||||||
|
clobTokenIds: m.clobTokenIds,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@
|
|||||||
:no-label="card.noLabel"
|
:no-label="card.noLabel"
|
||||||
:is-new="card.isNew"
|
:is-new="card.isNew"
|
||||||
:market-id="card.marketId"
|
:market-id="card.marketId"
|
||||||
|
:clob-token-ids="card.clobTokenIds"
|
||||||
@open-trade="onCardOpenTrade"
|
@open-trade="onCardOpenTrade"
|
||||||
/>
|
/>
|
||||||
<div v-if="eventList.length === 0 && !loadingMore" class="home-list-empty">
|
<div v-if="eventList.length === 0 && !loadingMore" class="home-list-empty">
|
||||||
@ -202,7 +203,7 @@ const noMoreEvents = computed(() => {
|
|||||||
const footerLang = ref('English')
|
const footerLang = ref('English')
|
||||||
const tradeDialogOpen = ref(false)
|
const tradeDialogOpen = ref(false)
|
||||||
const tradeDialogSide = ref<'yes' | 'no'>('yes')
|
const tradeDialogSide = ref<'yes' | 'no'>('yes')
|
||||||
const tradeDialogMarket = ref<{ id: string; title: string; marketId?: string } | null>(null)
|
const tradeDialogMarket = ref<{ id: string; title: string; marketId?: string; clobTokenIds?: string[] } | null>(null)
|
||||||
const scrollRef = ref<HTMLElement | null>(null)
|
const scrollRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
function onCardOpenTrade(side: 'yes' | 'no', market?: { id: string; title: string; marketId?: string }) {
|
function onCardOpenTrade(side: 'yes' | 'no', market?: { id: string; title: string; marketId?: string }) {
|
||||||
@ -211,7 +212,7 @@ function onCardOpenTrade(side: 'yes' | 'no', market?: { id: string; title: strin
|
|||||||
tradeDialogOpen.value = true
|
tradeDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 传给 TradeComponent 的 market(Home 弹窗/底部栏),供 Split 等使用;优先用 marketId,无则用事件 id */
|
/** 传给 TradeComponent 的 market(Home 弹窗/底部栏),供 Split、下单等使用 */
|
||||||
const homeTradeMarketPayload = computed(() => {
|
const homeTradeMarketPayload = computed(() => {
|
||||||
const m = tradeDialogMarket.value
|
const m = tradeDialogMarket.value
|
||||||
if (!m) return undefined
|
if (!m) return undefined
|
||||||
@ -219,7 +220,7 @@ const homeTradeMarketPayload = computed(() => {
|
|||||||
const chance = 50
|
const chance = 50
|
||||||
const yesPrice = Math.min(1, Math.max(0, chance / 100))
|
const yesPrice = Math.min(1, Math.max(0, chance / 100))
|
||||||
const noPrice = 1 - yesPrice
|
const noPrice = 1 - yesPrice
|
||||||
return { marketId, yesPrice, noPrice, title: m.title }
|
return { marketId, yesPrice, noPrice, title: m.title, clobTokenIds: m.clobTokenIds }
|
||||||
})
|
})
|
||||||
|
|
||||||
const sentinelRef = ref<HTMLElement | null>(null)
|
const sentinelRef = ref<HTMLElement | null>(null)
|
||||||
|
|||||||
@ -265,6 +265,7 @@ const tradeMarketPayload = computed(() => {
|
|||||||
yesPrice,
|
yesPrice,
|
||||||
noPrice,
|
noPrice,
|
||||||
title: m.question,
|
title: m.question,
|
||||||
|
clobTokenIds: m.clobTokenIds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const qId = route.query.marketId
|
const qId = route.query.marketId
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user