优化:模拟数据做得更真实,钱包页面数据bug加跳转
This commit is contained in:
parent
4927953dc9
commit
062e370bea
@ -215,6 +215,60 @@ export async function findPmEvent(
|
|||||||
return get<PmEventDetailResponse>('/PmEvent/findPmEvent', query, config)
|
return get<PmEventDetailResponse>('/PmEvent/findPmEvent', query, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 从嵌套对象读取事件 ID(兼容多种后端字段名) */
|
||||||
|
export function readTradeRouteEventId(obj: unknown): string | undefined {
|
||||||
|
if (!obj || typeof obj !== 'object') return undefined
|
||||||
|
const r = obj as Record<string, unknown>
|
||||||
|
const keys = ['eventID', 'EventID', 'eventId', 'pmEventID', 'PmEventID', 'PMEventID', 'pmEventId']
|
||||||
|
for (const k of keys) {
|
||||||
|
const v = r[k]
|
||||||
|
if (v != null && String(v).trim() !== '') return String(v).trim()
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从嵌套对象读取事件 slug(不含通用 slug,避免与市场 slug 混淆) */
|
||||||
|
export function readTradeRouteEventSlug(obj: unknown): string | undefined {
|
||||||
|
if (!obj || typeof obj !== 'object') return undefined
|
||||||
|
const r = obj as Record<string, unknown>
|
||||||
|
const v = r.eventSlug ?? r.event_slug
|
||||||
|
if (v != null && String(v).trim() !== '') return String(v).trim()
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TradeDetailRouteInput {
|
||||||
|
/** 事件数字 ID(与首页 MarketCard :id 一致),优先用于路径参数 */
|
||||||
|
eventId?: string | number | null
|
||||||
|
/** 事件 slug;无 eventId 时用作路径;路径为数字 ID 时可作为 query.slug 传给 findPmEvent */
|
||||||
|
eventSlug?: string | null
|
||||||
|
/** 市场行 ID,对应详情页 query.marketId */
|
||||||
|
marketId?: string | null
|
||||||
|
/** 详情页标题回显 query.title */
|
||||||
|
title?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造与 MarketCard、EventMarkets → trade-detail 一致的 router.push 参数。
|
||||||
|
* 路径:`/trade-detail/:id`,id 为事件数字 ID 或事件 slug。
|
||||||
|
*/
|
||||||
|
export function buildTradeDetailPushOptions(
|
||||||
|
input: TradeDetailRouteInput,
|
||||||
|
): { name: 'trade-detail'; params: { id: string }; query: Record<string, string> } | null {
|
||||||
|
const eid =
|
||||||
|
input.eventId != null && String(input.eventId).trim() !== '' ? String(input.eventId).trim() : ''
|
||||||
|
const slugOnly = input.eventSlug?.trim() || ''
|
||||||
|
if (!eid && !slugOnly) return null
|
||||||
|
const pathId = eid || slugOnly
|
||||||
|
const numId = parseInt(eid, 10)
|
||||||
|
const isNumericEventPath =
|
||||||
|
eid !== '' && Number.isFinite(numId) && String(numId) === eid && numId >= 1
|
||||||
|
const query: Record<string, string> = {}
|
||||||
|
if (input.title?.trim()) query.title = input.title.trim()
|
||||||
|
if (input.marketId?.trim()) query.marketId = input.marketId.trim()
|
||||||
|
if (isNumericEventPath && slugOnly) query.slug = slugOnly
|
||||||
|
return { name: 'trade-detail', params: { id: pathId }, query }
|
||||||
|
}
|
||||||
|
|
||||||
/** 多选项卡片中单个选项(用于左右滑动切换) */
|
/** 多选项卡片中单个选项(用于左右滑动切换) */
|
||||||
export interface EventCardOutcome {
|
export interface EventCardOutcome {
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@ -3,10 +3,22 @@
|
|||||||
* 用于 Wallet.vue 历史 Tab 数据
|
* 用于 Wallet.vue 历史 Tab 数据
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { readTradeRouteEventId, readTradeRouteEventSlug } from './event'
|
||||||
import { buildQuery, get } from './request'
|
import { buildQuery, get } from './request'
|
||||||
import { BASE_URL } from './request'
|
import { BASE_URL } from './request'
|
||||||
import type { PageResult } from './types'
|
import type { PageResult } from './types'
|
||||||
|
|
||||||
|
/** 历史记录 type(与后端常量一致) */
|
||||||
|
export const HISTORY_RECORD_TYPE = {
|
||||||
|
TRADE: 'TRADE',
|
||||||
|
SPLIT: 'SPLIT',
|
||||||
|
MERGE: 'MERGE',
|
||||||
|
REDEEM: 'REDEEM',
|
||||||
|
REWARD: 'REWARD',
|
||||||
|
CONVERSION: 'CONVERSION',
|
||||||
|
MAKER_REBATE: 'MAKER_REBATE',
|
||||||
|
} as const
|
||||||
|
|
||||||
/** 单条历史记录(与 doc.json definitions["polymarket.HistoryRecord"] 对齐) */
|
/** 单条历史记录(与 doc.json definitions["polymarket.HistoryRecord"] 对齐) */
|
||||||
export interface HistoryRecordItem {
|
export interface HistoryRecordItem {
|
||||||
ID?: number
|
ID?: number
|
||||||
@ -36,6 +48,10 @@ export interface HistoryRecordItem {
|
|||||||
UpdatedAt?: string
|
UpdatedAt?: string
|
||||||
/** 金额(USDC),用于充值等类型 */
|
/** 金额(USDC),用于充值等类型 */
|
||||||
usdcSize?: number
|
usdcSize?: number
|
||||||
|
eventID?: number
|
||||||
|
eventId?: number
|
||||||
|
marketID?: string | number
|
||||||
|
marketId?: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET /hr/getHistoryRecordPublic 请求参数 */
|
/** GET /hr/getHistoryRecordPublic 请求参数 */
|
||||||
@ -136,6 +152,16 @@ export interface HistoryDisplayItem {
|
|||||||
iconClass?: string
|
iconClass?: string
|
||||||
/** 图标 URL(来自 record.icon,用于展示) */
|
/** 图标 URL(来自 record.icon,用于展示) */
|
||||||
imageUrl?: string
|
imageUrl?: string
|
||||||
|
/** 接口 type:TRADE、SPLIT、MERGE 等 */
|
||||||
|
recordType?: string
|
||||||
|
/** TRADE 时 API 的 side:buy / sell(小写,用于标签) */
|
||||||
|
tradeSideRaw?: string
|
||||||
|
/** 跳转详情:事件 ID */
|
||||||
|
tradeEventId?: string
|
||||||
|
/** 跳转详情:事件 slug */
|
||||||
|
tradeEventSlug?: string
|
||||||
|
/** 跳转详情 query.marketId */
|
||||||
|
detailMarketId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTimeAgo(dateStr: string | undefined, timestamp?: number): string {
|
function formatTimeAgo(dateStr: string | undefined, timestamp?: number): string {
|
||||||
@ -166,23 +192,38 @@ function toFullIconUrl(icon: string | undefined): string | undefined {
|
|||||||
*/
|
*/
|
||||||
export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): HistoryDisplayItem {
|
export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): HistoryDisplayItem {
|
||||||
const id = String(record.ID ?? '')
|
const id = String(record.ID ?? '')
|
||||||
const market = record.title ?? record.name ?? record.eventSlug ?? ''
|
const market = record.name ?? record.title ?? record.eventSlug ?? ''
|
||||||
const outcome = (record.outcome ?? record.side ?? 'Yes').toString()
|
const outcome = (record.outcome ?? 'Yes').toString()
|
||||||
const side = outcome === 'No' || outcome === 'Down' ? 'No' : 'Yes'
|
const side = outcome === 'No' || outcome === 'Down' ? 'No' : 'Yes'
|
||||||
const typeLabel = record.type ?? 'Trade'
|
const recordType = (record.type ?? HISTORY_RECORD_TYPE.TRADE).toString().toUpperCase()
|
||||||
const activity = `${typeLabel} ${outcome}`.trim()
|
const tradeSideRaw = (record.side ?? '').toLowerCase()
|
||||||
|
const activity = (record.title ?? `${recordType} · ${outcome}`).trim()
|
||||||
const usdcSize = record.usdcSize ?? 0
|
const usdcSize = record.usdcSize ?? 0
|
||||||
const price = record.price ?? 0
|
const price = record.price ?? 0
|
||||||
const size = record.size ?? 0
|
const size = record.size ?? 0
|
||||||
const valueUsd = usdcSize !== 0 ? usdcSize : price * size
|
const valueUsd = usdcSize !== 0 ? usdcSize : price * size
|
||||||
const value = `$${Math.abs(valueUsd).toFixed(2)}`
|
const value = `$${Math.abs(valueUsd).toFixed(2)}`
|
||||||
const priceCents = Math.round(price * 100)
|
const priceCents = Math.round(price * 100)
|
||||||
const activityDetail = size > 0 ? `Sold ${Math.floor(size)} ${outcome} at ${priceCents}¢` : value
|
const activityDetail =
|
||||||
|
size > 0
|
||||||
|
? recordType === HISTORY_RECORD_TYPE.TRADE
|
||||||
|
? `${tradeSideRaw === 'sell' ? 'Sold' : 'Bought'} ${Math.floor(size)} ${outcome} at ${priceCents}¢`
|
||||||
|
: `Sold ${Math.floor(size)} ${outcome} at ${priceCents}¢`
|
||||||
|
: value
|
||||||
const timeAgo = formatTimeAgo(
|
const timeAgo = formatTimeAgo(
|
||||||
record.UpdatedAt ?? record.updatedAt ?? record.CreatedAt ?? record.createdAt,
|
record.UpdatedAt ?? record.updatedAt ?? record.CreatedAt ?? record.createdAt,
|
||||||
record.timestamp,
|
record.timestamp,
|
||||||
)
|
)
|
||||||
const imageUrl = toFullIconUrl(record.icon)
|
const imageUrl = toFullIconUrl(record.icon)
|
||||||
|
const tradeEventId = readTradeRouteEventId(record)
|
||||||
|
const tradeEventSlug =
|
||||||
|
readTradeRouteEventSlug(record) ||
|
||||||
|
record.eventSlug?.trim() ||
|
||||||
|
record.slug?.trim() ||
|
||||||
|
undefined
|
||||||
|
const rawMk = record.marketID ?? record.marketId
|
||||||
|
const detailMarketId =
|
||||||
|
rawMk != null && String(rawMk).trim() !== '' ? String(rawMk).trim() : undefined
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
market,
|
market,
|
||||||
@ -196,6 +237,11 @@ export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): Histor
|
|||||||
avgPrice: priceCents ? `${priceCents}¢` : undefined,
|
avgPrice: priceCents ? `${priceCents}¢` : undefined,
|
||||||
shares: size > 0 ? String(Math.floor(size)) : undefined,
|
shares: size > 0 ? String(Math.floor(size)) : undefined,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
recordType,
|
||||||
|
tradeSideRaw: recordType === HISTORY_RECORD_TYPE.TRADE ? tradeSideRaw : undefined,
|
||||||
|
tradeEventId: tradeEventId || undefined,
|
||||||
|
tradeEventSlug: tradeEventSlug || undefined,
|
||||||
|
detailMarketId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,68 @@ export interface MockOrderBookRow {
|
|||||||
shares: number
|
shares: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生成随机订单簿数据,每次进入页面不同;保证卖单最低价 > 买单最高价 */
|
/** random integer in [min, max] inclusive */
|
||||||
export function generateRandomOrderBook(): {
|
function randomInt(min: number, max: number): number {
|
||||||
|
return min + Math.floor(Math.random() * (max - min + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateRandomOrderBookOptions {
|
||||||
|
/** 最优买价(美分);与卖盘对齐后恒满足 lowestAsk = highestBid + 1 */
|
||||||
|
highestBid?: number
|
||||||
|
/** 最优卖价(美分);与买盘对齐后恒满足 highestBid = lowestAsk - 1 */
|
||||||
|
lowestAsk?: number
|
||||||
|
/**
|
||||||
|
* 来自 outcomePrices[0] 的美分锚(如 probability×100),确定最优卖单价;
|
||||||
|
* 最优买单价 = 卖一 − 1(1¢ 价差)。
|
||||||
|
*/
|
||||||
|
outcomePriceAnchorCents?: number
|
||||||
|
/**
|
||||||
|
* 生成哪一侧:`both`、仅卖单 `asks`、仅买单 `bids`。
|
||||||
|
* 不传则每次随机(约各 25% 单边卖/单边买,50% 双边)。
|
||||||
|
*/
|
||||||
|
sides?: 'both' | 'asks' | 'bids'
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMockAsks(lowest: number, count: number, r: () => number): MockOrderBookRow[] {
|
||||||
|
return Array.from({ length: count }, (_, i) => ({
|
||||||
|
price: lowest + i,
|
||||||
|
shares: Math.round((500 + r() * 3000) * 10) / 10,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMockBids(highest: number, count: number, r: () => number): MockOrderBookRow[] {
|
||||||
|
const rows: MockOrderBookRow[] = []
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const p = highest - i
|
||||||
|
if (p < 1) break
|
||||||
|
rows.push({
|
||||||
|
price: p,
|
||||||
|
shares: Math.round((200 + r() * 3000) * 10) / 10,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 触摸价:最优卖一 la,最优买一 = la − 1(1¢ 价差) */
|
||||||
|
function touchFromLowestAsk(lowestAsk: number): { highestBid: number; lowestAsk: number } {
|
||||||
|
let la = Math.floor(lowestAsk)
|
||||||
|
la = Math.min(99, Math.max(2, la))
|
||||||
|
return { highestBid: la - 1, lowestAsk: la }
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchFromHighestBid(highestBid: number): { highestBid: number; lowestAsk: number } {
|
||||||
|
let hb = Math.floor(highestBid)
|
||||||
|
hb = Math.min(98, Math.max(1, hb))
|
||||||
|
return { highestBid: hb, lowestAsk: hb + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机订单簿:每次调用结果不同。
|
||||||
|
* - **买单最高价 = 卖单最低价 − 1**(触摸价差恒 1¢),档位向两侧延伸不重叠。
|
||||||
|
* - `outcomePriceAnchorCents`:outcomePrices[0] 换算的美分,锚定最优卖一。
|
||||||
|
* - 可仅卖单或仅买单;条数各侧 2~10 随机。
|
||||||
|
*/
|
||||||
|
export function generateRandomOrderBook(options?: GenerateRandomOrderBookOptions): {
|
||||||
asks: MockOrderBookRow[]
|
asks: MockOrderBookRow[]
|
||||||
bids: MockOrderBookRow[]
|
bids: MockOrderBookRow[]
|
||||||
lastPrice: number
|
lastPrice: number
|
||||||
@ -28,24 +88,63 @@ export function generateRandomOrderBook(): {
|
|||||||
} {
|
} {
|
||||||
const r = () => Math.random()
|
const r = () => Math.random()
|
||||||
|
|
||||||
// 买单最高价 25–35,卖单最低价 = 买单最高价 + 1 + spread,保证有价差
|
const sideRoll = r()
|
||||||
const highestBid = Math.floor(25 + r() * 11)
|
const sides: 'both' | 'asks' | 'bids' =
|
||||||
const spread = Math.max(1, Math.floor(1 + r() * 3))
|
options?.sides ??
|
||||||
const lowestAsk = highestBid + spread
|
(sideRoll < 0.25 ? 'asks' : sideRoll < 0.5 ? 'bids' : 'both')
|
||||||
|
|
||||||
const askPrices = Array.from({ length: 9 }, (_, i) => lowestAsk + i)
|
const nAsks = randomInt(2, 10)
|
||||||
const asks: MockOrderBookRow[] = askPrices.map((p) => ({
|
const nBids = randomInt(2, 10)
|
||||||
price: p,
|
|
||||||
shares: Math.round((500 + r() * 3000) * 10) / 10,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const bidPrices = Array.from({ length: 12 }, (_, i) => highestBid - i)
|
let highestBid = 0
|
||||||
const bids: MockOrderBookRow[] = bidPrices.map((p) => ({
|
let lowestAsk = 0
|
||||||
price: Math.max(1, p),
|
let asks: MockOrderBookRow[] = []
|
||||||
shares: Math.round((200 + r() * 3000) * 10) / 10,
|
let bids: MockOrderBookRow[] = []
|
||||||
}))
|
let lastPrice = 0
|
||||||
|
const spread = 1
|
||||||
|
|
||||||
const lastPrice = Math.floor((highestBid + lowestAsk) / 2)
|
if (sides === 'both') {
|
||||||
|
const anchor = options?.outcomePriceAnchorCents
|
||||||
|
const hbOpt = options?.highestBid
|
||||||
|
const laOpt = options?.lowestAsk
|
||||||
|
|
||||||
|
if (anchor != null && Number.isFinite(anchor) && anchor > 0) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromLowestAsk(Math.round(anchor)))
|
||||||
|
} else if (laOpt != null && Number.isFinite(laOpt)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromLowestAsk(laOpt))
|
||||||
|
} else if (hbOpt != null && Number.isFinite(hbOpt)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromHighestBid(hbOpt))
|
||||||
|
} else {
|
||||||
|
lowestAsk = randomInt(2, 99)
|
||||||
|
highestBid = lowestAsk - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
asks = buildMockAsks(lowestAsk, nAsks, r)
|
||||||
|
bids = buildMockBids(highestBid, nBids, r)
|
||||||
|
lastPrice = Math.round((highestBid + lowestAsk) / 2)
|
||||||
|
} else if (sides === 'asks') {
|
||||||
|
if (options?.highestBid != null && Number.isFinite(options.highestBid)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromHighestBid(options.highestBid))
|
||||||
|
} else if (options?.lowestAsk != null && Number.isFinite(options.lowestAsk)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromLowestAsk(options.lowestAsk))
|
||||||
|
} else {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromLowestAsk(randomInt(2, 99)))
|
||||||
|
}
|
||||||
|
asks = buildMockAsks(lowestAsk, nAsks, r)
|
||||||
|
bids = []
|
||||||
|
lastPrice = Math.round((highestBid + lowestAsk) / 2)
|
||||||
|
} else {
|
||||||
|
if (options?.lowestAsk != null && Number.isFinite(options.lowestAsk)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromLowestAsk(options.lowestAsk))
|
||||||
|
} else if (options?.highestBid != null && Number.isFinite(options.highestBid)) {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromHighestBid(options.highestBid))
|
||||||
|
} else {
|
||||||
|
;({ highestBid, lowestAsk } = touchFromHighestBid(randomInt(1, 98)))
|
||||||
|
}
|
||||||
|
bids = buildMockBids(highestBid, nBids, r)
|
||||||
|
asks = []
|
||||||
|
lastPrice = Math.round((highestBid + lowestAsk) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
return { asks, bids, lastPrice, spread }
|
return { asks, bids, lastPrice, spread }
|
||||||
}
|
}
|
||||||
@ -107,6 +206,8 @@ export interface MockPosition {
|
|||||||
marketID?: string
|
marketID?: string
|
||||||
tokenID?: string
|
tokenID?: string
|
||||||
needClaim?: boolean
|
needClaim?: boolean
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MockOpenOrder {
|
export interface MockOpenOrder {
|
||||||
@ -124,6 +225,9 @@ export interface MockOpenOrder {
|
|||||||
iconClass?: string
|
iconClass?: string
|
||||||
orderID?: number
|
orderID?: number
|
||||||
tokenID?: string
|
tokenID?: string
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
detailMarketId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MockHistoryItem {
|
export interface MockHistoryItem {
|
||||||
@ -140,6 +244,9 @@ export interface MockHistoryItem {
|
|||||||
shares?: string
|
shares?: string
|
||||||
iconChar?: string
|
iconChar?: string
|
||||||
iconClass?: string
|
iconClass?: string
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
detailMarketId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MOCK_WALLET_POSITIONS: MockPosition[] = [
|
export const MOCK_WALLET_POSITIONS: MockPosition[] = [
|
||||||
@ -163,6 +270,8 @@ export const MOCK_WALLET_POSITIONS: MockPosition[] = [
|
|||||||
marketID: 'mock-market-1',
|
marketID: 'mock-market-1',
|
||||||
tokenID: MOCK_TOKEN_ID,
|
tokenID: MOCK_TOKEN_ID,
|
||||||
needClaim: true,
|
needClaim: true,
|
||||||
|
tradeEventId: '1',
|
||||||
|
tradeEventSlug: 'mock-wallet-event',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'p2',
|
id: 'p2',
|
||||||
@ -218,6 +327,9 @@ export const MOCK_WALLET_ORDERS: MockOpenOrder[] = [
|
|||||||
iconClass: 'position-icon-btc',
|
iconClass: 'position-icon-btc',
|
||||||
orderID: 5,
|
orderID: 5,
|
||||||
tokenID: MOCK_TOKEN_ID,
|
tokenID: MOCK_TOKEN_ID,
|
||||||
|
tradeEventId: '1',
|
||||||
|
tradeEventSlug: 'mock-wallet-event',
|
||||||
|
detailMarketId: 'mock-market-1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'o2',
|
id: 'o2',
|
||||||
@ -249,6 +361,8 @@ export const MOCK_WALLET_HISTORY: MockHistoryItem[] = [
|
|||||||
{
|
{
|
||||||
id: 'h1',
|
id: 'h1',
|
||||||
market: 'Bitcoin Up or Down - February 9, 3:00AM-3:15AM ET',
|
market: 'Bitcoin Up or Down - February 9, 3:00AM-3:15AM ET',
|
||||||
|
tradeEventId: '1',
|
||||||
|
tradeEventSlug: 'mock-wallet-event',
|
||||||
side: 'No',
|
side: 'No',
|
||||||
activity: 'Sell No',
|
activity: 'Sell No',
|
||||||
activityDetail: 'Sold 1 Down at 50¢',
|
activityDetail: 'Sold 1 Down at 50¢',
|
||||||
|
|||||||
@ -1,8 +1,20 @@
|
|||||||
import { buildQuery, get, post } from './request'
|
import { readTradeRouteEventId, readTradeRouteEventSlug } from './event'
|
||||||
|
import { BASE_URL, buildQuery, get, post } from './request'
|
||||||
import type { ApiResponse, PageResult } from './types'
|
import type { ApiResponse, PageResult } from './types'
|
||||||
|
|
||||||
export type { PageResult }
|
export type { PageResult }
|
||||||
|
|
||||||
|
/** 订单列表嵌套的市场信息(getOrderList 响应内 pmMarket) */
|
||||||
|
export interface PmMarketEmbed {
|
||||||
|
ID?: number
|
||||||
|
question?: string
|
||||||
|
slug?: string
|
||||||
|
image?: string
|
||||||
|
icon?: string
|
||||||
|
conditionId?: string
|
||||||
|
outcomes?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单项(与 doc.json definitions["model.ClobOrder"] 对齐)
|
* 订单项(与 doc.json definitions["model.ClobOrder"] 对齐)
|
||||||
* GET /clob/order/getOrderList 列表项
|
* GET /clob/order/getOrderList 列表项
|
||||||
@ -23,9 +35,34 @@ export interface ClobOrderItem {
|
|||||||
sizeMatched?: number
|
sizeMatched?: number
|
||||||
status?: number
|
status?: number
|
||||||
userID?: number
|
userID?: number
|
||||||
|
pmMarket?: PmMarketEmbed
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 展示用标题:优先 pmMarket.question,否则回退 market(多为 ID) */
|
||||||
|
export function resolveClobOrderMarketTitle(order: ClobOrderItem): string {
|
||||||
|
const q = order.pmMarket?.question?.trim()
|
||||||
|
if (q) return q
|
||||||
|
const m = order.market?.toString().trim()
|
||||||
|
return m ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFullIconUrl(icon: string | undefined): string | undefined {
|
||||||
|
if (!icon?.trim()) return undefined
|
||||||
|
const s = icon.trim()
|
||||||
|
if (s.startsWith('http://') || s.startsWith('https://')) return s
|
||||||
|
const base = BASE_URL?.replace(/\/$/, '') ?? ''
|
||||||
|
return `${base}/${s.replace(/^\//, '')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 市场图:优先 pmMarket.image,其次 pmMarket.icon */
|
||||||
|
export function resolveClobOrderMarketImageUrl(order: ClobOrderItem): string | undefined {
|
||||||
|
const pm = order.pmMarket
|
||||||
|
if (!pm) return undefined
|
||||||
|
const raw = (pm.image ?? pm.icon ?? '').trim()
|
||||||
|
return raw ? toFullIconUrl(raw) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
/** 订单列表响应 */
|
/** 订单列表响应 */
|
||||||
export interface OrderListResponse {
|
export interface OrderListResponse {
|
||||||
code: number
|
code: number
|
||||||
@ -114,6 +151,8 @@ export interface HistoryDisplayItem {
|
|||||||
shares?: string
|
shares?: string
|
||||||
iconChar?: string
|
iconChar?: string
|
||||||
iconClass?: string
|
iconClass?: string
|
||||||
|
/** 来自订单 pmMarket.image / icon */
|
||||||
|
imageUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Side: Buy=1, Sell=2 */
|
/** Side: Buy=1, Sell=2 */
|
||||||
@ -140,7 +179,7 @@ function formatTimeAgo(createdAt: string | undefined): string {
|
|||||||
*/
|
*/
|
||||||
export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem {
|
export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem {
|
||||||
const id = String(order.ID ?? '')
|
const id = String(order.ID ?? '')
|
||||||
const market = order.market ?? ''
|
const market = resolveClobOrderMarketTitle(order) || (order.market ?? '')
|
||||||
const outcome = order.outcome ?? 'Yes'
|
const outcome = order.outcome ?? 'Yes'
|
||||||
const sideNum = order.side ?? Side.Buy
|
const sideNum = order.side ?? Side.Buy
|
||||||
const sideLabel = sideNum === Side.Sell ? 'Sell' : 'Buy'
|
const sideLabel = sideNum === Side.Sell ? 'Sell' : 'Buy'
|
||||||
@ -155,6 +194,7 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem
|
|||||||
const activityDetail = `${verb} ${Math.floor(size)} ${outcome} at ${priceCents}¢`
|
const activityDetail = `${verb} ${Math.floor(size)} ${outcome} at ${priceCents}¢`
|
||||||
const avgPrice = `${priceCents}¢`
|
const avgPrice = `${priceCents}¢`
|
||||||
const timeAgo = formatTimeAgo(order.createdAt)
|
const timeAgo = formatTimeAgo(order.createdAt)
|
||||||
|
const imageUrl = resolveClobOrderMarketImageUrl(order)
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
market,
|
market,
|
||||||
@ -167,6 +207,7 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem
|
|||||||
timeAgo,
|
timeAgo,
|
||||||
avgPrice,
|
avgPrice,
|
||||||
shares: String(Math.floor(size)),
|
shares: String(Math.floor(size)),
|
||||||
|
imageUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +227,14 @@ export interface OpenOrderDisplayItem {
|
|||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 已成交数量达到原始总数量,不可撤单 */
|
/** 已成交数量达到原始总数量,不可撤单 */
|
||||||
fullyFilled?: boolean
|
fullyFilled?: boolean
|
||||||
|
/** 来自 pmMarket.image / icon */
|
||||||
|
imageUrl?: string
|
||||||
|
/** 跳转详情:事件 ID */
|
||||||
|
tradeEventId?: string
|
||||||
|
/** 跳转详情:事件 slug */
|
||||||
|
tradeEventSlug?: string
|
||||||
|
/** 跳转详情 query.marketId(pmMarket.ID) */
|
||||||
|
detailMarketId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** OrderType GTC=0 表示 Until Cancelled */
|
/** OrderType GTC=0 表示 Until Cancelled */
|
||||||
@ -197,7 +246,7 @@ const OrderType = { GTC: 0, GTD: 1 } as const
|
|||||||
*/
|
*/
|
||||||
export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem {
|
export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem {
|
||||||
const id = String(order.ID ?? '')
|
const id = String(order.ID ?? '')
|
||||||
const market = order.market ?? ''
|
const market = resolveClobOrderMarketTitle(order) || (order.market ?? '')
|
||||||
const sideNum = order.side ?? Side.Buy
|
const sideNum = order.side ?? Side.Buy
|
||||||
const side = sideNum === Side.Sell ? 'No' : 'Yes'
|
const side = sideNum === Side.Sell ? 'No' : 'Yes'
|
||||||
const outcome = order.outcome || (side === 'Yes' ? 'Yes' : 'No')
|
const outcome = order.outcome || (side === 'Yes' ? 'Yes' : 'No')
|
||||||
@ -215,6 +264,13 @@ export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayI
|
|||||||
order.orderType === OrderType.GTC ? 'Until Cancelled' : order.expiration?.toString() ?? ''
|
order.orderType === OrderType.GTC ? 'Until Cancelled' : order.expiration?.toString() ?? ''
|
||||||
const actionLabel = sideNum === Side.Buy ? `Buy ${outcome}` : `Sell ${outcome}`
|
const actionLabel = sideNum === Side.Buy ? `Buy ${outcome}` : `Sell ${outcome}`
|
||||||
const fullyFilled = originalSize <= 0 || sizeMatched >= originalSize
|
const fullyFilled = originalSize <= 0 || sizeMatched >= originalSize
|
||||||
|
const imageUrl = resolveClobOrderMarketImageUrl(order)
|
||||||
|
const pm = order.pmMarket
|
||||||
|
const tradeEventId = readTradeRouteEventId(pm) ?? readTradeRouteEventId(order)
|
||||||
|
const slugExplicit = readTradeRouteEventSlug(pm) ?? readTradeRouteEventSlug(order)
|
||||||
|
const slugFromPm = pm?.slug?.trim()
|
||||||
|
const tradeEventSlug = slugExplicit || (!tradeEventId && slugFromPm ? slugFromPm : undefined)
|
||||||
|
const detailMarketId = pm?.ID != null ? String(pm.ID) : undefined
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
market,
|
market,
|
||||||
@ -229,5 +285,9 @@ export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayI
|
|||||||
orderID: order.ID,
|
orderID: order.ID,
|
||||||
tokenID: order.assetID,
|
tokenID: order.assetID,
|
||||||
fullyFilled,
|
fullyFilled,
|
||||||
|
imageUrl,
|
||||||
|
tradeEventId: tradeEventId || undefined,
|
||||||
|
tradeEventSlug: tradeEventSlug || undefined,
|
||||||
|
detailMarketId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { readTradeRouteEventId, readTradeRouteEventSlug } from './event'
|
||||||
import { buildQuery, get, post } from './request'
|
import { buildQuery, get, post } from './request'
|
||||||
import type { ApiResponse } from './types'
|
import type { ApiResponse } from './types'
|
||||||
import type { PageResult } from './types'
|
import type { PageResult } from './types'
|
||||||
@ -166,6 +167,10 @@ export interface PositionDisplayItem {
|
|||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 所属市场是否已关闭,market.closed=true 表示可结算/可领取 */
|
/** 所属市场是否已关闭,market.closed=true 表示可结算/可领取 */
|
||||||
marketClosed?: boolean
|
marketClosed?: boolean
|
||||||
|
/** 跳转市场详情:事件 ID(路径优先) */
|
||||||
|
tradeEventId?: string
|
||||||
|
/** 跳转市场详情:事件 slug */
|
||||||
|
tradeEventSlug?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,6 +238,11 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
|
|||||||
const tokenID = pos.tokenId ?? (pos as { tokenID?: string }).tokenID ?? ''
|
const tokenID = pos.tokenId ?? (pos as { tokenID?: string }).tokenID ?? ''
|
||||||
const imageUrl = (pos.market?.image ?? pos.market?.icon) as string | undefined
|
const imageUrl = (pos.market?.image ?? pos.market?.icon) as string | undefined
|
||||||
const marketClosed = pos.market?.closed === true
|
const marketClosed = pos.market?.closed === true
|
||||||
|
const tradeEventId = readTradeRouteEventId(pos.market) ?? readTradeRouteEventId(pos)
|
||||||
|
const slugExplicit = readTradeRouteEventSlug(pos.market) ?? readTradeRouteEventSlug(pos)
|
||||||
|
const slugFromMarket = pos.market?.slug?.trim()
|
||||||
|
const tradeEventSlug =
|
||||||
|
slugExplicit || (!tradeEventId && slugFromMarket ? slugFromMarket : undefined)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -256,5 +266,7 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
|
|||||||
marketID: marketID || undefined,
|
marketID: marketID || undefined,
|
||||||
tokenID: tokenID || undefined,
|
tokenID: tokenID || undefined,
|
||||||
marketClosed,
|
marketClosed,
|
||||||
|
tradeEventId: tradeEventId || undefined,
|
||||||
|
tradeEventSlug: tradeEventSlug || undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/api/vipLevel.ts
Normal file
18
src/api/vipLevel.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { get } from './request'
|
||||||
|
import type { ApiResponse } from './types'
|
||||||
|
|
||||||
|
/** GET /VipLevel/getPmVipLevelPublic — 手续费等级(无需鉴权) */
|
||||||
|
export interface PmVipLevelItem {
|
||||||
|
levelName: string
|
||||||
|
takerFeeRate: number
|
||||||
|
makerFeeRate: number
|
||||||
|
needDeposit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PmVipLevelPublicData {
|
||||||
|
list: PmVipLevelItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPmVipLevelPublic(): Promise<ApiResponse<PmVipLevelPublicData>> {
|
||||||
|
return get<ApiResponse<PmVipLevelPublicData>>('/VipLevel/getPmVipLevelPublic')
|
||||||
|
}
|
||||||
103
src/components/MarqueeTitle.vue
Normal file
103
src/components/MarqueeTitle.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="rootRef"
|
||||||
|
:class="['marquee-root', titleClass, { 'marquee-root--animate': overflow, 'marquee-root--fast': fast }]"
|
||||||
|
>
|
||||||
|
<div class="marquee-track">
|
||||||
|
<span>{{ text }}</span>
|
||||||
|
<span v-if="overflow" aria-hidden="true">{{ text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 展示的标题文案 */
|
||||||
|
text: string
|
||||||
|
/** 附加到根节点的类名(如 position-mobile-title、order-mobile-title) */
|
||||||
|
titleClass?: string
|
||||||
|
/** true 时使用较短动画时长(与订单/历史原样式一致) */
|
||||||
|
fast?: boolean
|
||||||
|
}>(),
|
||||||
|
{ titleClass: '', fast: false },
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootRef = ref<HTMLElement | null>(null)
|
||||||
|
const overflow = ref(false)
|
||||||
|
let resizeObserver: ResizeObserver | null = null
|
||||||
|
|
||||||
|
async function measure() {
|
||||||
|
await nextTick()
|
||||||
|
const root = rootRef.value
|
||||||
|
if (!root) {
|
||||||
|
overflow.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const track = root.querySelector('.marquee-track')
|
||||||
|
if (!(track instanceof HTMLElement)) {
|
||||||
|
overflow.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overflow.value = track.scrollWidth > root.clientWidth + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
measure()
|
||||||
|
resizeObserver = new ResizeObserver(() => measure())
|
||||||
|
if (rootRef.value) resizeObserver.observe(rootRef.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
resizeObserver?.disconnect()
|
||||||
|
resizeObserver = null
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.text,
|
||||||
|
() => measure(),
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(overflow, () => {
|
||||||
|
void nextTick(() => measure())
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.marquee-root {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-track {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: max-content;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-root--animate .marquee-track {
|
||||||
|
gap: 40px;
|
||||||
|
animation: marquee-title-scroll 10s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-root--animate.marquee-root--fast .marquee-track {
|
||||||
|
animation-duration: 9s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-track > span {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes marquee-title-scroll {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(calc(-50% - 20px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -91,7 +91,7 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import HorizontalProgressBar from './HorizontalProgressBar.vue'
|
import HorizontalProgressBar from './HorizontalProgressBar.vue'
|
||||||
import { generateRandomOrderBook } from '../api/mockData'
|
import { generateRandomOrderBook, type MockOrderBookRow } from '../api/mockData'
|
||||||
import { USE_MOCK_ORDER_BOOK } from '../config/mock'
|
import { USE_MOCK_ORDER_BOOK } from '../config/mock'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -126,6 +126,15 @@ const props = withDefaults(
|
|||||||
connected?: boolean
|
connected?: boolean
|
||||||
yesLabel?: string
|
yesLabel?: string
|
||||||
noLabel?: string
|
noLabel?: string
|
||||||
|
/** Yes 簿真实买单最高价(美分),单侧真实数据时用于生成另一侧模拟盘口 */
|
||||||
|
anchorBestBidYesCents?: number
|
||||||
|
anchorLowestAskYesCents?: number
|
||||||
|
anchorBestBidNoCents?: number
|
||||||
|
anchorLowestAskNoCents?: number
|
||||||
|
/** 无真实盘口时:Yes 侧用 outcomePrices[0] 换算的美分锚定最优卖一(买一=卖一−1) */
|
||||||
|
outcomePriceAnchorYesCents?: number
|
||||||
|
/** 无真实盘口时:No 侧锚定(常为 outcomePrices[1]),由父组件传入 */
|
||||||
|
outcomePriceAnchorNoCents?: number
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
asksYes: () => [],
|
asksYes: () => [],
|
||||||
@ -144,17 +153,179 @@ const props = withDefaults(
|
|||||||
connected: false,
|
connected: false,
|
||||||
yesLabel: 'Yes',
|
yesLabel: 'Yes',
|
||||||
noLabel: 'No',
|
noLabel: 'No',
|
||||||
|
anchorBestBidYesCents: 0,
|
||||||
|
anchorLowestAskYesCents: 0,
|
||||||
|
anchorBestBidNoCents: 0,
|
||||||
|
anchorLowestAskNoCents: 0,
|
||||||
|
outcomePriceAnchorYesCents: 0,
|
||||||
|
outcomePriceAnchorNoCents: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// State:up = Yes 交易,down = No 交易
|
// State:up = Yes 交易,down = No 交易
|
||||||
const activeTrade = ref('up')
|
const activeTrade = ref('up')
|
||||||
|
|
||||||
const initMock = generateRandomOrderBook()
|
/** 一侧真实、另一侧由 mock 填充时的模拟档位与展示的 last/spread */
|
||||||
const internalAsks = ref<OrderBookRow[]>([...initMock.asks])
|
type HybridSupplement = {
|
||||||
const internalBids = ref<OrderBookRow[]>([...initMock.bids])
|
mockAsks: OrderBookRow[]
|
||||||
const internalLastPrice = ref(initMock.lastPrice)
|
mockBids: OrderBookRow[]
|
||||||
const internalSpread = ref(initMock.spread)
|
lastPrice: number
|
||||||
|
spread: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const hybridSupplementYes = ref<HybridSupplement | null>(null)
|
||||||
|
const hybridSupplementNo = ref<HybridSupplement | null>(null)
|
||||||
|
|
||||||
|
/** 双侧皆无真实数据时的完整模拟簿(Yes / No 各自一份) */
|
||||||
|
const fullMockYes = ref<HybridSupplement | null>(null)
|
||||||
|
const fullMockNo = ref<HybridSupplement | null>(null)
|
||||||
|
|
||||||
|
function mockRowsToOrderBookRows(rows: MockOrderBookRow[]): OrderBookRow[] {
|
||||||
|
return rows.map((r) => ({ price: r.price, shares: r.shares }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function bestBidCentsFromRows(rows: OrderBookRow[] | undefined): number {
|
||||||
|
if (!rows?.length) return 0
|
||||||
|
return Math.max(...rows.map((b) => b.price))
|
||||||
|
}
|
||||||
|
|
||||||
|
function bestAskCentsFromRows(rows: OrderBookRow[] | undefined): number {
|
||||||
|
if (!rows?.length) return 0
|
||||||
|
return Math.min(...rows.map((a) => a.price))
|
||||||
|
}
|
||||||
|
|
||||||
|
function supplementFromGen(gen: ReturnType<typeof generateRandomOrderBook>): HybridSupplement {
|
||||||
|
return {
|
||||||
|
mockAsks: mockRowsToOrderBookRows(gen.asks),
|
||||||
|
mockBids: mockRowsToOrderBookRows(gen.bids),
|
||||||
|
lastPrice: gen.lastPrice,
|
||||||
|
spread: gen.spread,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncOrderBookMocks() {
|
||||||
|
if (!USE_MOCK_ORDER_BOOK || props.loading) {
|
||||||
|
hybridSupplementYes.value = null
|
||||||
|
hybridSupplementNo.value = null
|
||||||
|
fullMockYes.value = null
|
||||||
|
fullMockNo.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const runSide = (
|
||||||
|
nAsks: number,
|
||||||
|
nBids: number,
|
||||||
|
anchorBidProp: number,
|
||||||
|
anchorAskProp: number,
|
||||||
|
bidsRows: OrderBookRow[] | undefined,
|
||||||
|
asksRows: OrderBookRow[] | undefined,
|
||||||
|
setRef: (v: HybridSupplement | null) => void,
|
||||||
|
) => {
|
||||||
|
if (nAsks > 0 && nBids > 0) {
|
||||||
|
setRef(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (nAsks === 0 && nBids === 0) {
|
||||||
|
setRef(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hb =
|
||||||
|
anchorBidProp > 0 ? Math.floor(anchorBidProp) : bestBidCentsFromRows(bidsRows)
|
||||||
|
const la =
|
||||||
|
anchorAskProp > 0 ? Math.floor(anchorAskProp) : bestAskCentsFromRows(asksRows)
|
||||||
|
if (nAsks === 0 && nBids > 0 && hb > 0) {
|
||||||
|
const gen = generateRandomOrderBook({ highestBid: hb, sides: 'asks' })
|
||||||
|
setRef({
|
||||||
|
mockAsks: mockRowsToOrderBookRows(gen.asks),
|
||||||
|
mockBids: [],
|
||||||
|
lastPrice: gen.lastPrice,
|
||||||
|
spread: gen.spread,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (nBids === 0 && nAsks > 0 && la > 0) {
|
||||||
|
const gen = generateRandomOrderBook({ lowestAsk: la, sides: 'bids' })
|
||||||
|
setRef({
|
||||||
|
mockAsks: [],
|
||||||
|
mockBids: mockRowsToOrderBookRows(gen.bids),
|
||||||
|
lastPrice: gen.lastPrice,
|
||||||
|
spread: gen.spread,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setRef(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
runSide(
|
||||||
|
props.asksYes?.length ?? 0,
|
||||||
|
props.bidsYes?.length ?? 0,
|
||||||
|
props.anchorBestBidYesCents ?? 0,
|
||||||
|
props.anchorLowestAskYesCents ?? 0,
|
||||||
|
props.bidsYes,
|
||||||
|
props.asksYes,
|
||||||
|
(v) => {
|
||||||
|
hybridSupplementYes.value = v
|
||||||
|
},
|
||||||
|
)
|
||||||
|
runSide(
|
||||||
|
props.asksNo?.length ?? 0,
|
||||||
|
props.bidsNo?.length ?? 0,
|
||||||
|
props.anchorBestBidNoCents ?? 0,
|
||||||
|
props.anchorLowestAskNoCents ?? 0,
|
||||||
|
props.bidsNo,
|
||||||
|
props.asksNo,
|
||||||
|
(v) => {
|
||||||
|
hybridSupplementNo.value = v
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const ay = props.asksYes?.length ?? 0
|
||||||
|
const by = props.bidsYes?.length ?? 0
|
||||||
|
if (ay === 0 && by === 0) {
|
||||||
|
const anchorY = props.outcomePriceAnchorYesCents ?? 0
|
||||||
|
fullMockYes.value = supplementFromGen(
|
||||||
|
generateRandomOrderBook({
|
||||||
|
sides: 'both',
|
||||||
|
outcomePriceAnchorCents: anchorY > 0 ? anchorY : undefined,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fullMockYes.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const an = props.asksNo?.length ?? 0
|
||||||
|
const bn = props.bidsNo?.length ?? 0
|
||||||
|
if (an === 0 && bn === 0) {
|
||||||
|
const anchorN = props.outcomePriceAnchorNoCents ?? 0
|
||||||
|
fullMockNo.value = supplementFromGen(
|
||||||
|
generateRandomOrderBook({
|
||||||
|
sides: 'both',
|
||||||
|
outcomePriceAnchorCents: anchorN > 0 ? anchorN : undefined,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fullMockNo.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
props.loading,
|
||||||
|
props.asksYes?.length ?? 0,
|
||||||
|
props.bidsYes?.length ?? 0,
|
||||||
|
props.asksNo?.length ?? 0,
|
||||||
|
props.bidsNo?.length ?? 0,
|
||||||
|
props.anchorBestBidYesCents ?? 0,
|
||||||
|
props.anchorLowestAskYesCents ?? 0,
|
||||||
|
props.anchorBestBidNoCents ?? 0,
|
||||||
|
props.anchorLowestAskNoCents ?? 0,
|
||||||
|
props.outcomePriceAnchorYesCents ?? 0,
|
||||||
|
props.outcomePriceAnchorNoCents ?? 0,
|
||||||
|
] as const,
|
||||||
|
() => syncOrderBookMocks(),
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
// 根据 activeTrade 选择当前 tab 对应的数据;loading 时不显示模拟数据
|
// 根据 activeTrade 选择当前 tab 对应的数据;loading 时不显示模拟数据
|
||||||
const asks = computed(() => {
|
const asks = computed(() => {
|
||||||
@ -162,22 +333,34 @@ const asks = computed(() => {
|
|||||||
const isYes = activeTrade.value === 'up'
|
const isYes = activeTrade.value === 'up'
|
||||||
const fromYes = isYes ? props.asksYes : props.asksNo
|
const fromYes = isYes ? props.asksYes : props.asksNo
|
||||||
const fromLegacy = props.asks
|
const fromLegacy = props.asks
|
||||||
|
const hybrid = isYes ? hybridSupplementYes.value : hybridSupplementNo.value
|
||||||
const hasYesNo = (fromYes?.length ?? 0) > 0
|
const hasYesNo = (fromYes?.length ?? 0) > 0
|
||||||
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
||||||
if (hasYesNo) return fromYes ?? []
|
if (hasYesNo) return fromYes ?? []
|
||||||
if (hasLegacy) return fromLegacy ?? []
|
if (hasLegacy) return fromLegacy ?? []
|
||||||
return USE_MOCK_ORDER_BOOK ? internalAsks.value : []
|
if (USE_MOCK_ORDER_BOOK && hybrid && hybrid.mockAsks.length > 0) return hybrid.mockAsks
|
||||||
|
if (USE_MOCK_ORDER_BOOK) {
|
||||||
|
const full = isYes ? fullMockYes.value : fullMockNo.value
|
||||||
|
if (full?.mockAsks.length) return full.mockAsks
|
||||||
|
}
|
||||||
|
return []
|
||||||
})
|
})
|
||||||
const bids = computed(() => {
|
const bids = computed(() => {
|
||||||
if (props.loading) return []
|
if (props.loading) return []
|
||||||
const isYes = activeTrade.value === 'up'
|
const isYes = activeTrade.value === 'up'
|
||||||
const fromYes = isYes ? props.bidsYes : props.bidsNo
|
const fromYes = isYes ? props.bidsYes : props.bidsNo
|
||||||
const fromLegacy = props.bids
|
const fromLegacy = props.bids
|
||||||
|
const hybrid = isYes ? hybridSupplementYes.value : hybridSupplementNo.value
|
||||||
const hasYesNo = (fromYes?.length ?? 0) > 0
|
const hasYesNo = (fromYes?.length ?? 0) > 0
|
||||||
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
||||||
if (hasYesNo) return fromYes ?? []
|
if (hasYesNo) return fromYes ?? []
|
||||||
if (hasLegacy) return fromLegacy ?? []
|
if (hasLegacy) return fromLegacy ?? []
|
||||||
return USE_MOCK_ORDER_BOOK ? internalBids.value : []
|
if (USE_MOCK_ORDER_BOOK && hybrid && hybrid.mockBids.length > 0) return hybrid.mockBids
|
||||||
|
if (USE_MOCK_ORDER_BOOK) {
|
||||||
|
const full = isYes ? fullMockYes.value : fullMockNo.value
|
||||||
|
if (full?.mockBids.length) return full.mockBids
|
||||||
|
}
|
||||||
|
return []
|
||||||
})
|
})
|
||||||
const displayLastPrice = computed(() => {
|
const displayLastPrice = computed(() => {
|
||||||
if (props.loading) return '—'
|
if (props.loading) return '—'
|
||||||
@ -185,7 +368,13 @@ const displayLastPrice = computed(() => {
|
|||||||
const fromYesNo = isYes ? props.lastPriceYes : props.lastPriceNo
|
const fromYesNo = isYes ? props.lastPriceYes : props.lastPriceNo
|
||||||
if (fromYesNo != null) return fromYesNo
|
if (fromYesNo != null) return fromYesNo
|
||||||
if (props.lastPrice != null) return props.lastPrice
|
if (props.lastPrice != null) return props.lastPrice
|
||||||
return USE_MOCK_ORDER_BOOK ? internalLastPrice.value : 0
|
if (USE_MOCK_ORDER_BOOK) {
|
||||||
|
const h = isYes ? hybridSupplementYes.value : hybridSupplementNo.value
|
||||||
|
if (h) return h.lastPrice
|
||||||
|
const full = isYes ? fullMockYes.value : fullMockNo.value
|
||||||
|
if (full) return full.lastPrice
|
||||||
|
}
|
||||||
|
return 0
|
||||||
})
|
})
|
||||||
const displaySpread = computed(() => {
|
const displaySpread = computed(() => {
|
||||||
if (props.loading) return '—'
|
if (props.loading) return '—'
|
||||||
@ -193,7 +382,13 @@ const displaySpread = computed(() => {
|
|||||||
const fromYesNo = isYes ? props.spreadYes : props.spreadNo
|
const fromYesNo = isYes ? props.spreadYes : props.spreadNo
|
||||||
if (fromYesNo != null) return fromYesNo
|
if (fromYesNo != null) return fromYesNo
|
||||||
if (props.spread != null) return props.spread
|
if (props.spread != null) return props.spread
|
||||||
return USE_MOCK_ORDER_BOOK ? internalSpread.value : 0
|
if (USE_MOCK_ORDER_BOOK) {
|
||||||
|
const h = isYes ? hybridSupplementYes.value : hybridSupplementNo.value
|
||||||
|
if (h) return h.spread
|
||||||
|
const full = isYes ? fullMockYes.value : fullMockNo.value
|
||||||
|
if (full) return full.spread
|
||||||
|
}
|
||||||
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 仅在没有外部数据且开启 mock 时运行 mock 更新
|
// 仅在没有外部数据且开启 mock 时运行 mock 更新
|
||||||
@ -212,24 +407,22 @@ watch(
|
|||||||
mockInterval = undefined
|
mockInterval = undefined
|
||||||
} else if (!hasRealData && USE_MOCK_ORDER_BOOK && !mockInterval) {
|
} else if (!hasRealData && USE_MOCK_ORDER_BOOK && !mockInterval) {
|
||||||
mockInterval = setInterval(() => {
|
mockInterval = setInterval(() => {
|
||||||
const randomAskIndex = Math.floor(Math.random() * internalAsks.value.length)
|
const jitterSide = (book: HybridSupplement | null) => {
|
||||||
const askItem = internalAsks.value[randomAskIndex]
|
if (!book) return book
|
||||||
if (askItem) {
|
if (!book.mockAsks.length && !book.mockBids.length) return book
|
||||||
internalAsks.value[randomAskIndex] = {
|
const jitterRow = (row: OrderBookRow): OrderBookRow => ({
|
||||||
price: askItem.price,
|
...row,
|
||||||
shares: Math.max(0, askItem.shares + Math.floor(Math.random() * 100) - 50),
|
shares: Math.max(0, row.shares + Math.floor(Math.random() * 100) - 50),
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
mockAsks: book.mockAsks.map(jitterRow),
|
||||||
|
mockBids: book.mockBids.map(jitterRow),
|
||||||
|
lastPrice: Math.max(1, Math.min(99, book.lastPrice + Math.floor(Math.random() * 3) - 1)),
|
||||||
|
spread: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const randomBidIndex = Math.floor(Math.random() * internalBids.value.length)
|
fullMockYes.value = jitterSide(fullMockYes.value)
|
||||||
const bidItem = internalBids.value[randomBidIndex]
|
fullMockNo.value = jitterSide(fullMockNo.value)
|
||||||
if (bidItem) {
|
|
||||||
internalBids.value[randomBidIndex] = {
|
|
||||||
price: bidItem.price,
|
|
||||||
shares: Math.max(0, bidItem.shares + Math.floor(Math.random() * 100) - 50),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internalLastPrice.value = Math.max(1, Math.min(99, internalLastPrice.value + Math.floor(Math.random() * 3) - 1))
|
|
||||||
internalSpread.value = Math.max(1, internalSpread.value + Math.floor(Math.random() * 2) - 1)
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1494,6 +1494,7 @@ function indexOfAllowedPrice(v: number): number {
|
|||||||
|
|
||||||
export interface TradeMarketPayload {
|
export interface TradeMarketPayload {
|
||||||
marketId?: string
|
marketId?: string
|
||||||
|
/** 优先来自订单簿卖一(0–1);为 0 时可配合 outcomePrices 解析 */
|
||||||
yesPrice: number
|
yesPrice: number
|
||||||
noPrice: number
|
noPrice: number
|
||||||
title?: string
|
title?: string
|
||||||
@ -1501,6 +1502,8 @@ export interface TradeMarketPayload {
|
|||||||
clobTokenIds?: string[]
|
clobTokenIds?: string[]
|
||||||
/** 选项展示文案,如 ["Yes","No"] 或 ["Up","Down"],用于 Buy Yes/No 按钮文字 */
|
/** 选项展示文案,如 ["Yes","No"] 或 ["Up","Down"],用于 Buy Yes/No 按钮文字 */
|
||||||
outcomes?: string[]
|
outcomes?: string[]
|
||||||
|
/** yesPrice/noPrice 均为 0 时用于回退展示与限价(与 API outcomePrices 同源) */
|
||||||
|
outcomePrices?: Array<string | number>
|
||||||
/** 订单簿 Yes 买单最高价(美分),市价卖出时用于计算将收到金额 */
|
/** 订单簿 Yes 买单最高价(美分),市价卖出时用于计算将收到金额 */
|
||||||
bestBidYesCents?: number
|
bestBidYesCents?: number
|
||||||
/** 订单簿 No 买单最高价(美分),市价卖出时用于计算将收到金额 */
|
/** 订单簿 No 买单最高价(美分),市价卖出时用于计算将收到金额 */
|
||||||
@ -1633,8 +1636,41 @@ async function submitSplit() {
|
|||||||
|
|
||||||
defineExpose({ openMergeDialog, openSplitDialog })
|
defineExpose({ openMergeDialog, openSplitDialog })
|
||||||
|
|
||||||
const yesPriceCents = computed(() => (props.market ? Math.round(props.market.yesPrice * 100) : 19))
|
/** outcomePrices 单项 → 美元单价 0–1(兼容概率小数或 1–100 美分写法) */
|
||||||
const noPriceCents = computed(() => (props.market ? Math.round(props.market.noPrice * 100) : 82))
|
function outcomeRawToDollars(raw: string | number | undefined): number {
|
||||||
|
if (raw == null || (typeof raw === 'string' && raw.trim() === '')) return 0
|
||||||
|
const p = parseFloat(String(raw))
|
||||||
|
if (!Number.isFinite(p)) return 0
|
||||||
|
if (p > 1 && p <= 100) return Math.min(1, p / 100)
|
||||||
|
return Math.min(1, Math.max(0, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 展示/限价:优先 market.yesPrice(来自订单簿卖一),否则 outcomePrices[0] */
|
||||||
|
const yesPriceDollarsEffective = computed(() => {
|
||||||
|
const m = props.market
|
||||||
|
if (!m) return 0.19
|
||||||
|
if (Math.round(m.yesPrice * 100) > 0) return m.yesPrice
|
||||||
|
const d = outcomeRawToDollars(m.outcomePrices?.[0])
|
||||||
|
return d > 0 ? d : (m.yesPrice > 0 ? m.yesPrice : 0.19)
|
||||||
|
})
|
||||||
|
|
||||||
|
const noPriceDollarsEffective = computed(() => {
|
||||||
|
const m = props.market
|
||||||
|
if (!m) return 0.82
|
||||||
|
if (Math.round(m.noPrice * 100) > 0) return m.noPrice
|
||||||
|
const raw1 = m.outcomePrices?.[1]
|
||||||
|
if (raw1 != null && String(raw1).trim() !== '') {
|
||||||
|
const d = outcomeRawToDollars(raw1)
|
||||||
|
if (d > 0) return d
|
||||||
|
}
|
||||||
|
const y = yesPriceDollarsEffective.value
|
||||||
|
if (y > 0 && y <= 1) return Math.min(1, Math.max(0, 1 - y))
|
||||||
|
const d0 = outcomeRawToDollars(m.outcomePrices?.[0])
|
||||||
|
return d0 > 0 ? d0 : (m.noPrice > 0 ? m.noPrice : 0.82)
|
||||||
|
})
|
||||||
|
|
||||||
|
const yesPriceCents = computed(() => Math.round(yesPriceDollarsEffective.value * 100))
|
||||||
|
const noPriceCents = computed(() => Math.round(noPriceDollarsEffective.value * 100))
|
||||||
const yesLabel = computed(() => props.market?.outcomes?.[0] ?? 'Yes')
|
const yesLabel = computed(() => props.market?.outcomes?.[0] ?? 'Yes')
|
||||||
const noLabel = computed(() => props.market?.outcomes?.[1] ?? 'No')
|
const noLabel = computed(() => props.market?.outcomes?.[1] ?? 'No')
|
||||||
|
|
||||||
@ -1766,9 +1802,7 @@ const avgPriceCents = computed(() => {
|
|||||||
const toWinValue = computed(() => {
|
const toWinValue = computed(() => {
|
||||||
if (isMarketMode.value) {
|
if (isMarketMode.value) {
|
||||||
const price =
|
const price =
|
||||||
selectedOption.value === 'yes'
|
selectedOption.value === 'yes' ? yesPriceDollarsEffective.value : noPriceDollarsEffective.value
|
||||||
? (props.market?.yesPrice ?? 0.5)
|
|
||||||
: (props.market?.noPrice ?? 0.5)
|
|
||||||
const sharesFromAmount = price > 0 ? amount.value / price : 0
|
const sharesFromAmount = price > 0 ? amount.value / price : 0
|
||||||
return trimTrailingZeros(sharesFromAmount.toFixed(2))
|
return trimTrailingZeros(sharesFromAmount.toFixed(2))
|
||||||
}
|
}
|
||||||
@ -1831,9 +1865,9 @@ function clampLimitPrice(v: number): number {
|
|||||||
|
|
||||||
/** 根据当前 props.market 与 selectedOption 同步 limitPrice(组件显示或 market 更新时调用) */
|
/** 根据当前 props.market 与 selectedOption 同步 limitPrice(组件显示或 market 更新时调用) */
|
||||||
function syncLimitPriceFromMarket() {
|
function syncLimitPriceFromMarket() {
|
||||||
const yesP = props.market?.yesPrice ?? 0.19
|
limitPrice.value = clampLimitPrice(
|
||||||
const noP = props.market?.noPrice ?? 0.82
|
selectedOption.value === 'yes' ? yesPriceDollarsEffective.value : noPriceDollarsEffective.value,
|
||||||
limitPrice.value = clampLimitPrice(selectedOption.value === 'yes' ? yesP : noP)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -1903,9 +1937,9 @@ watch(limitType, () => saveTradePrefs(), { flush: 'post' })
|
|||||||
// Methods
|
// Methods
|
||||||
const handleOptionChange = (option: 'yes' | 'no') => {
|
const handleOptionChange = (option: 'yes' | 'no') => {
|
||||||
selectedOption.value = option
|
selectedOption.value = option
|
||||||
const yesP = props.market?.yesPrice ?? 0.19
|
limitPrice.value = clampLimitPrice(
|
||||||
const noP = props.market?.noPrice ?? 0.82
|
option === 'yes' ? yesPriceDollarsEffective.value : noPriceDollarsEffective.value,
|
||||||
limitPrice.value = clampLimitPrice(option === 'yes' ? yesP : noP)
|
)
|
||||||
emit('optionChange', option)
|
emit('optionChange', option)
|
||||||
|
|
||||||
// Set max shares when option changes in sell mode
|
// Set max shares when option changes in sell mode
|
||||||
|
|||||||
@ -221,6 +221,14 @@
|
|||||||
"noPositionsFound": "No positions found.",
|
"noPositionsFound": "No positions found.",
|
||||||
"noOpenOrdersFound": "No open orders found.",
|
"noOpenOrdersFound": "No open orders found.",
|
||||||
"noHistoryFound": "You haven't traded any polymarkets yet",
|
"noHistoryFound": "You haven't traded any polymarkets yet",
|
||||||
|
"historySideBuy": "BUY",
|
||||||
|
"historySideSell": "SELL",
|
||||||
|
"historyTypeSplit": "SPLIT",
|
||||||
|
"historyTypeMerge": "MERGE",
|
||||||
|
"historyTypeRedeem": "REDEEM",
|
||||||
|
"historyTypeReward": "REWARD",
|
||||||
|
"historyTypeConversion": "CONVERSION",
|
||||||
|
"historyTypeMakerRebate": "MAKER REBATE",
|
||||||
"market": "Market",
|
"market": "Market",
|
||||||
"avgNow": "AVG → NOW",
|
"avgNow": "AVG → NOW",
|
||||||
"bet": "BET",
|
"bet": "BET",
|
||||||
@ -308,7 +316,27 @@
|
|||||||
"nameSaved": "Username updated",
|
"nameSaved": "Username updated",
|
||||||
"changeAvatar": "Change avatar",
|
"changeAvatar": "Change avatar",
|
||||||
"avatarUploadSuccess": "Avatar updated",
|
"avatarUploadSuccess": "Avatar updated",
|
||||||
"avatarUploadFailed": "Failed to upload avatar"
|
"avatarUploadFailed": "Failed to upload avatar",
|
||||||
|
"memberCenter": "Member Center",
|
||||||
|
"depositCoin": "Deposit",
|
||||||
|
"withdrawCoin": "Withdraw"
|
||||||
|
},
|
||||||
|
"memberCenter": {
|
||||||
|
"title": "Member Center",
|
||||||
|
"currentLevel": "Current level",
|
||||||
|
"vipLabel": "VIP {n}",
|
||||||
|
"goRecharge": "Top up",
|
||||||
|
"explainTitle": "Levels",
|
||||||
|
"hintNeedMoreDeposit": "{amount} more USDC needed to reach the next level.",
|
||||||
|
"footnote": "* Thresholds and statistics follow backend rules.",
|
||||||
|
"tier0": "Complete registration.",
|
||||||
|
"tierNeedDeposit": "Cumulative deposits ≥ {amount} USDC.",
|
||||||
|
"feesLine": "Taker {taker} · Maker {maker}",
|
||||||
|
"loadError": "Failed to load VIP tiers. Please try again.",
|
||||||
|
"tier1": "Cumulative deposits ≥ $10,000 USDC.",
|
||||||
|
"tier2": "Cumulative deposits ≥ $50,000 USDC.",
|
||||||
|
"tier3": "Cumulative deposits ≥ $200,000 USDC.",
|
||||||
|
"tier4": "Cumulative deposits ≥ $500,000 USDC;"
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"title": "Deposit",
|
"title": "Deposit",
|
||||||
|
|||||||
@ -221,6 +221,14 @@
|
|||||||
"noPositionsFound": "ポジションがありません",
|
"noPositionsFound": "ポジションがありません",
|
||||||
"noOpenOrdersFound": "未約定注文がありません",
|
"noOpenOrdersFound": "未約定注文がありません",
|
||||||
"noHistoryFound": "まだ取引履歴がありません",
|
"noHistoryFound": "まだ取引履歴がありません",
|
||||||
|
"historySideBuy": "買い",
|
||||||
|
"historySideSell": "売り",
|
||||||
|
"historyTypeSplit": "スプリット",
|
||||||
|
"historyTypeMerge": "マージ",
|
||||||
|
"historyTypeRedeem": "償還",
|
||||||
|
"historyTypeReward": "リワード",
|
||||||
|
"historyTypeConversion": "コンバージョン",
|
||||||
|
"historyTypeMakerRebate": "メイカー還元",
|
||||||
"market": "市場",
|
"market": "市場",
|
||||||
"avgNow": "平均→現在",
|
"avgNow": "平均→現在",
|
||||||
"bet": "ベット",
|
"bet": "ベット",
|
||||||
@ -308,7 +316,27 @@
|
|||||||
"nameSaved": "ユーザー名を更新しました",
|
"nameSaved": "ユーザー名を更新しました",
|
||||||
"changeAvatar": "アバターを変更",
|
"changeAvatar": "アバターを変更",
|
||||||
"avatarUploadSuccess": "アバターを更新しました",
|
"avatarUploadSuccess": "アバターを更新しました",
|
||||||
"avatarUploadFailed": "アバターのアップロードに失敗しました"
|
"avatarUploadFailed": "アバターのアップロードに失敗しました",
|
||||||
|
"memberCenter": "メンバー",
|
||||||
|
"depositCoin": "入金",
|
||||||
|
"withdrawCoin": "出金"
|
||||||
|
},
|
||||||
|
"memberCenter": {
|
||||||
|
"title": "メンバーセンター",
|
||||||
|
"currentLevel": "現在のレベル",
|
||||||
|
"vipLabel": "VIP {n}",
|
||||||
|
"goRecharge": "チャージへ",
|
||||||
|
"explainTitle": "レベル説明",
|
||||||
|
"hintNeedMoreDeposit": "次のレベルまであと {amount} USDC の入金が必要です。",
|
||||||
|
"footnote": "* 数値と条件はバックエンドの規則に従います。",
|
||||||
|
"tier0": "登録で達成。",
|
||||||
|
"tierNeedDeposit": "累計入金 ≥ {amount} USDC。",
|
||||||
|
"feesLine": "テイカー {taker} · メイカー {maker}",
|
||||||
|
"loadError": "レベル設定の読み込みに失敗しました",
|
||||||
|
"tier1": "累計入金 ≥ $10,000 USDC。",
|
||||||
|
"tier2": "累計入金 ≥ $50,000 USDC。",
|
||||||
|
"tier3": "累計入金 ≥ $200,000 USDC。",
|
||||||
|
"tier4": "累計入金 ≥ $500,000 USDC。"
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"title": "入金",
|
"title": "入金",
|
||||||
|
|||||||
@ -221,6 +221,14 @@
|
|||||||
"noPositionsFound": "포지션이 없습니다",
|
"noPositionsFound": "포지션이 없습니다",
|
||||||
"noOpenOrdersFound": "미체결 주문이 없습니다",
|
"noOpenOrdersFound": "미체결 주문이 없습니다",
|
||||||
"noHistoryFound": "아직 거래 내역이 없습니다",
|
"noHistoryFound": "아직 거래 내역이 없습니다",
|
||||||
|
"historySideBuy": "매수",
|
||||||
|
"historySideSell": "매도",
|
||||||
|
"historyTypeSplit": "스플릿",
|
||||||
|
"historyTypeMerge": "머지",
|
||||||
|
"historyTypeRedeem": "상환",
|
||||||
|
"historyTypeReward": "리워드",
|
||||||
|
"historyTypeConversion": "전환",
|
||||||
|
"historyTypeMakerRebate": "메이커 리베이트",
|
||||||
"market": "시장",
|
"market": "시장",
|
||||||
"avgNow": "평균→현재",
|
"avgNow": "평균→현재",
|
||||||
"bet": "베팅",
|
"bet": "베팅",
|
||||||
@ -308,7 +316,27 @@
|
|||||||
"nameSaved": "사용자 이름이 업데이트되었습니다",
|
"nameSaved": "사용자 이름이 업데이트되었습니다",
|
||||||
"changeAvatar": "아바타 변경",
|
"changeAvatar": "아바타 변경",
|
||||||
"avatarUploadSuccess": "아바타가 업데이트되었습니다",
|
"avatarUploadSuccess": "아바타가 업데이트되었습니다",
|
||||||
"avatarUploadFailed": "아바타 업로드에 실패했습니다"
|
"avatarUploadFailed": "아바타 업로드에 실패했습니다",
|
||||||
|
"memberCenter": "멤버십",
|
||||||
|
"depositCoin": "입금",
|
||||||
|
"withdrawCoin": "출금"
|
||||||
|
},
|
||||||
|
"memberCenter": {
|
||||||
|
"title": "멤버 센터",
|
||||||
|
"currentLevel": "현재 등급",
|
||||||
|
"vipLabel": "VIP {n}",
|
||||||
|
"goRecharge": "충전하기",
|
||||||
|
"explainTitle": "등급 안내",
|
||||||
|
"hintNeedMoreDeposit": "다음 등급까지 {amount} USDC를 더 입금해야 합니다.",
|
||||||
|
"footnote": "* 통계와 기준은 백엔드 규칙을 따릅니다.",
|
||||||
|
"tier0": "가입 완료 시.",
|
||||||
|
"tierNeedDeposit": "누적 입금 ≥ {amount} USDC.",
|
||||||
|
"feesLine": "테이커 {taker} · 메이커 {maker}",
|
||||||
|
"loadError": "등급 정보를 불러오지 못했습니다",
|
||||||
|
"tier1": "누적 입금 ≥ $10,000 USDC.",
|
||||||
|
"tier2": "누적 입금 ≥ $50,000 USDC.",
|
||||||
|
"tier3": "누적 입금 ≥ $200,000 USDC.",
|
||||||
|
"tier4": "누적 입금 ≥ $500,000 USDC ."
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"title": "입금",
|
"title": "입금",
|
||||||
|
|||||||
@ -221,6 +221,14 @@
|
|||||||
"noPositionsFound": "暂无持仓",
|
"noPositionsFound": "暂无持仓",
|
||||||
"noOpenOrdersFound": "暂无未成交订单",
|
"noOpenOrdersFound": "暂无未成交订单",
|
||||||
"noHistoryFound": "您还未进行过任何交易",
|
"noHistoryFound": "您还未进行过任何交易",
|
||||||
|
"historySideBuy": "买入",
|
||||||
|
"historySideSell": "卖出",
|
||||||
|
"historyTypeSplit": "拆分",
|
||||||
|
"historyTypeMerge": "合并",
|
||||||
|
"historyTypeRedeem": "赎回",
|
||||||
|
"historyTypeReward": "奖励",
|
||||||
|
"historyTypeConversion": "转换",
|
||||||
|
"historyTypeMakerRebate": "做市返佣",
|
||||||
"market": "市场",
|
"market": "市场",
|
||||||
"avgNow": "均价→现价",
|
"avgNow": "均价→现价",
|
||||||
"bet": "投注",
|
"bet": "投注",
|
||||||
@ -308,7 +316,27 @@
|
|||||||
"nameSaved": "用户名已更新",
|
"nameSaved": "用户名已更新",
|
||||||
"changeAvatar": "更换头像",
|
"changeAvatar": "更换头像",
|
||||||
"avatarUploadSuccess": "头像已更新",
|
"avatarUploadSuccess": "头像已更新",
|
||||||
"avatarUploadFailed": "头像上传失败"
|
"avatarUploadFailed": "头像上传失败",
|
||||||
|
"memberCenter": "会员中心",
|
||||||
|
"depositCoin": "充币",
|
||||||
|
"withdrawCoin": "提币"
|
||||||
|
},
|
||||||
|
"memberCenter": {
|
||||||
|
"title": "会员中心",
|
||||||
|
"currentLevel": "当前等级",
|
||||||
|
"vipLabel": "VIP {n}",
|
||||||
|
"goRecharge": "去充值",
|
||||||
|
"explainTitle": "等级说明",
|
||||||
|
"hintNeedMoreDeposit": "距离下一等级还需要充值 {amount} USDC。",
|
||||||
|
"footnote": "* 统计数据与门槛以后台规则为准。",
|
||||||
|
"tier0": "完成注册即可。",
|
||||||
|
"tierNeedDeposit": "累计充值 ≥ {amount} USDC。",
|
||||||
|
"feesLine": "吃单 {taker} · 挂单 {maker}",
|
||||||
|
"loadError": "等级配置加载失败,请稍后重试",
|
||||||
|
"tier1": "累计充值 ≥ $10,000 USDC。",
|
||||||
|
"tier2": "累计充值 ≥ $50,000 USDC。",
|
||||||
|
"tier3": "累计充值 ≥ $200,000 USDC。",
|
||||||
|
"tier4": "累计充值 ≥ $500,000 USDC;"
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"title": "入金",
|
"title": "入金",
|
||||||
|
|||||||
@ -221,6 +221,14 @@
|
|||||||
"noPositionsFound": "暫無持倉",
|
"noPositionsFound": "暫無持倉",
|
||||||
"noOpenOrdersFound": "暫無未成交訂單",
|
"noOpenOrdersFound": "暫無未成交訂單",
|
||||||
"noHistoryFound": "您還未進行過任何交易",
|
"noHistoryFound": "您還未進行過任何交易",
|
||||||
|
"historySideBuy": "買入",
|
||||||
|
"historySideSell": "賣出",
|
||||||
|
"historyTypeSplit": "拆分",
|
||||||
|
"historyTypeMerge": "合併",
|
||||||
|
"historyTypeRedeem": "贖回",
|
||||||
|
"historyTypeReward": "獎勵",
|
||||||
|
"historyTypeConversion": "轉換",
|
||||||
|
"historyTypeMakerRebate": "做市返佣",
|
||||||
"market": "市場",
|
"market": "市場",
|
||||||
"avgNow": "均價→現價",
|
"avgNow": "均價→現價",
|
||||||
"bet": "投注",
|
"bet": "投注",
|
||||||
@ -308,7 +316,27 @@
|
|||||||
"nameSaved": "使用者名稱已更新",
|
"nameSaved": "使用者名稱已更新",
|
||||||
"changeAvatar": "更換頭像",
|
"changeAvatar": "更換頭像",
|
||||||
"avatarUploadSuccess": "頭像已更新",
|
"avatarUploadSuccess": "頭像已更新",
|
||||||
"avatarUploadFailed": "頭像上傳失敗"
|
"avatarUploadFailed": "頭像上傳失敗",
|
||||||
|
"memberCenter": "會員中心",
|
||||||
|
"depositCoin": "充幣",
|
||||||
|
"withdrawCoin": "提幣"
|
||||||
|
},
|
||||||
|
"memberCenter": {
|
||||||
|
"title": "會員中心",
|
||||||
|
"currentLevel": "目前等級",
|
||||||
|
"vipLabel": "VIP {n}",
|
||||||
|
"goRecharge": "去充值",
|
||||||
|
"explainTitle": "等級說明",
|
||||||
|
"hintNeedMoreDeposit": "距離下一等級還需要充值 {amount} USDC。",
|
||||||
|
"footnote": "* 統計數據與門檻以後台規則為準。",
|
||||||
|
"tier0": "完成註冊即可。",
|
||||||
|
"tierNeedDeposit": "累計充值 ≥ {amount} USDC。",
|
||||||
|
"feesLine": "吃單 {taker} · 掛單 {maker}",
|
||||||
|
"loadError": "等級配置載入失敗,請稍後再試",
|
||||||
|
"tier1": "累計充值 ≥ $10,000 USDC。",
|
||||||
|
"tier2": "累計充值 ≥ $50,000 USDC。",
|
||||||
|
"tier3": "累計充值 ≥ $200,000 USDC。",
|
||||||
|
"tier4": "累計充值 ≥ $500,000 USDC;"
|
||||||
},
|
},
|
||||||
"deposit": {
|
"deposit": {
|
||||||
"title": "入金",
|
"title": "入金",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import EventMarkets from '../views/EventMarkets.vue'
|
|||||||
import Wallet from '../views/Wallet.vue'
|
import Wallet from '../views/Wallet.vue'
|
||||||
import Search from '../views/Search.vue'
|
import Search from '../views/Search.vue'
|
||||||
import Profile from '../views/Profile.vue'
|
import Profile from '../views/Profile.vue'
|
||||||
|
import MemberCenter from '../views/MemberCenter.vue'
|
||||||
import ApiKey from '../views/ApiKey.vue'
|
import ApiKey from '../views/ApiKey.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@ -52,6 +53,11 @@ const router = createRouter({
|
|||||||
name: 'profile',
|
name: 'profile',
|
||||||
component: Profile,
|
component: Profile,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/member-center',
|
||||||
|
name: 'member-center',
|
||||||
|
component: MemberCenter,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/api-key',
|
path: '/api-key',
|
||||||
name: 'api-key',
|
name: 'api-key',
|
||||||
|
|||||||
@ -281,6 +281,7 @@ const tradeMarketPayload = computed(() => {
|
|||||||
title: m.question,
|
title: m.question,
|
||||||
clobTokenIds: m.clobTokenIds,
|
clobTokenIds: m.clobTokenIds,
|
||||||
outcomes: m.outcomes,
|
outcomes: m.outcomes,
|
||||||
|
outcomePrices: m.outcomePrices,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -415,7 +416,7 @@ function setChartSeries(seriesArr: ChartSeriesItem[]) {
|
|||||||
const series = chartInstance!.addSeries(LineSeries, {
|
const series = chartInstance!.addSeries(LineSeries, {
|
||||||
color,
|
color,
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
lineType: LineType.Curved,
|
lineType: LineType.Simple,
|
||||||
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
||||||
crosshairMarkerVisible: true,
|
crosshairMarkerVisible: true,
|
||||||
lastValueVisible: true,
|
lastValueVisible: true,
|
||||||
|
|||||||
@ -187,6 +187,12 @@
|
|||||||
:bids-yes="orderBookBidsYes"
|
:bids-yes="orderBookBidsYes"
|
||||||
:asks-no="orderBookAsksNo"
|
:asks-no="orderBookAsksNo"
|
||||||
:bids-no="orderBookBidsNo"
|
:bids-no="orderBookBidsNo"
|
||||||
|
:anchor-best-bid-yes="orderBookBestBidYesCents"
|
||||||
|
:anchor-lowest-ask-yes="orderBookLowestAskYesCents"
|
||||||
|
:anchor-best-bid-no="orderBookBestBidNoCents"
|
||||||
|
:anchor-lowest-ask-no="orderBookLowestAskNoCents"
|
||||||
|
:outcome-price-anchor-yes-cents="orderBookOutcomeAnchorYesCents"
|
||||||
|
:outcome-price-anchor-no-cents="orderBookOutcomeAnchorNoCents"
|
||||||
:last-price-yes="clobLastPriceYes"
|
:last-price-yes="clobLastPriceYes"
|
||||||
:last-price-no="clobLastPriceNo"
|
:last-price-no="clobLastPriceNo"
|
||||||
:spread-yes="clobSpreadYes"
|
:spread-yes="clobSpreadYes"
|
||||||
@ -615,6 +621,32 @@ const orderBookBestBidNoCents = computed(() => {
|
|||||||
if (!bids.length) return 0
|
if (!bids.length) return 0
|
||||||
return Math.max(...bids.map((b) => b.price))
|
return Math.max(...bids.map((b) => b.price))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** outcomePrices 单项 → 订单簿锚价(美分):概率 0–1 或已是美分 1–100 */
|
||||||
|
function outcomePriceToAnchorCents(raw: string | number | undefined): number {
|
||||||
|
if (raw == null || (typeof raw === 'string' && raw.trim() === '')) return 0
|
||||||
|
const p = parseFloat(String(raw))
|
||||||
|
if (!Number.isFinite(p)) return 0
|
||||||
|
if (p > 1 && p <= 100) return Math.min(99, Math.max(1, Math.round(p)))
|
||||||
|
return Math.min(99, Math.max(0, Math.round(p * 100)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 无真实盘口时模拟 Yes 簿:锚定 outcomePrices[0] 为最优卖一 */
|
||||||
|
const orderBookOutcomeAnchorYesCents = computed(() =>
|
||||||
|
outcomePriceToAnchorCents(currentMarket.value?.outcomePrices?.[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 无真实盘口时模拟 No 簿:优先 outcomePrices[1],否则与 Yes 互补(100−Yes¢) */
|
||||||
|
const orderBookOutcomeAnchorNoCents = computed(() => {
|
||||||
|
const m = currentMarket.value
|
||||||
|
const raw1 = m?.outcomePrices?.[1]
|
||||||
|
if (raw1 != null && String(raw1).trim() !== '') {
|
||||||
|
return outcomePriceToAnchorCents(raw1)
|
||||||
|
}
|
||||||
|
const y = orderBookOutcomeAnchorYesCents.value
|
||||||
|
if (y > 0) return Math.min(99, Math.max(2, 100 - y))
|
||||||
|
return outcomePriceToAnchorCents(m?.outcomePrices?.[0])
|
||||||
|
})
|
||||||
const clobLastPriceYes = computed(() => clobLastPriceByToken.value[0])
|
const clobLastPriceYes = computed(() => clobLastPriceByToken.value[0])
|
||||||
const clobLastPriceNo = computed(() => clobLastPriceByToken.value[1])
|
const clobLastPriceNo = computed(() => clobLastPriceByToken.value[1])
|
||||||
const clobSpreadYes = computed(() => clobSpreadByToken.value[0])
|
const clobSpreadYes = computed(() => clobSpreadByToken.value[0])
|
||||||
@ -758,14 +790,36 @@ function disconnectClob() {
|
|||||||
clobLoading.value = false
|
clobLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 传给 TradeComponent 的 market,供 Split 调用 /PmMarket/split;yesPrice/noPrice 取订单簿卖单最低价,无数据时为 0 */
|
/**
|
||||||
|
* 传给 TradeComponent:yesPrice/noPrice 优先取对应 token 订单簿 **卖单最低价**(美元 0–1),
|
||||||
|
* 取不到再用当前市场的 outcomePrices。
|
||||||
|
*/
|
||||||
const tradeMarketPayload = computed(() => {
|
const tradeMarketPayload = computed(() => {
|
||||||
const m = currentMarket.value
|
const m = currentMarket.value
|
||||||
const yesPrice = orderBookLowestAskYesCents.value / 100
|
const yC = orderBookLowestAskYesCents.value
|
||||||
const noPrice = orderBookLowestAskNoCents.value / 100
|
const nC = orderBookLowestAskNoCents.value
|
||||||
|
let yesPrice = yC > 0 ? yC / 100 : 0
|
||||||
|
let noPrice = nC > 0 ? nC / 100 : 0
|
||||||
const bestBidYesCents = orderBookBestBidYesCents.value
|
const bestBidYesCents = orderBookBestBidYesCents.value
|
||||||
const bestBidNoCents = orderBookBestBidNoCents.value
|
const bestBidNoCents = orderBookBestBidNoCents.value
|
||||||
|
|
||||||
if (m) {
|
if (m) {
|
||||||
|
if (yesPrice <= 0) {
|
||||||
|
const c = outcomePriceToAnchorCents(m.outcomePrices?.[0])
|
||||||
|
if (c > 0) yesPrice = c / 100
|
||||||
|
}
|
||||||
|
if (noPrice <= 0) {
|
||||||
|
const raw1 = m.outcomePrices?.[1]
|
||||||
|
if (raw1 != null && String(raw1).trim() !== '') {
|
||||||
|
const c = outcomePriceToAnchorCents(raw1)
|
||||||
|
if (c > 0) noPrice = c / 100
|
||||||
|
} else if (yesPrice > 0) {
|
||||||
|
noPrice = Math.min(1, Math.max(0, 1 - yesPrice))
|
||||||
|
} else {
|
||||||
|
const c = outcomePriceToAnchorCents(m.outcomePrices?.[0])
|
||||||
|
if (c > 0) noPrice = c / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
marketId: getMarketId(m),
|
marketId: getMarketId(m),
|
||||||
yesPrice,
|
yesPrice,
|
||||||
@ -773,6 +827,7 @@ const tradeMarketPayload = computed(() => {
|
|||||||
title: m.question,
|
title: m.question,
|
||||||
clobTokenIds: m.clobTokenIds,
|
clobTokenIds: m.clobTokenIds,
|
||||||
outcomes: m.outcomes,
|
outcomes: m.outcomes,
|
||||||
|
outcomePrices: m.outcomePrices,
|
||||||
bestBidYesCents,
|
bestBidYesCents,
|
||||||
bestBidNoCents,
|
bestBidNoCents,
|
||||||
}
|
}
|
||||||
@ -1152,7 +1207,7 @@ function ensureChartSeries() {
|
|||||||
chartSeries = chartInstance.addSeries(LineSeries, {
|
chartSeries = chartInstance.addSeries(LineSeries, {
|
||||||
color: lineColor,
|
color: lineColor,
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
lineType: LineType.Curved,
|
lineType: LineType.Simple,
|
||||||
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
||||||
crosshairMarkerVisible: true,
|
crosshairMarkerVisible: true,
|
||||||
lastValueVisible: true,
|
lastValueVisible: true,
|
||||||
|
|||||||
@ -75,6 +75,12 @@
|
|||||||
v-for="pos in paginatedPositions"
|
v-for="pos in paginatedPositions"
|
||||||
:key="pos.id"
|
:key="pos.id"
|
||||||
class="position-mobile-card design-pos-card"
|
class="position-mobile-card design-pos-card"
|
||||||
|
:class="{ 'wallet-row--nav': canOpenTradeDetail(pos) }"
|
||||||
|
:role="canOpenTradeDetail(pos) ? 'button' : undefined"
|
||||||
|
:tabindex="canOpenTradeDetail(pos) ? 0 : -1"
|
||||||
|
@click="onPositionRowClick(pos)"
|
||||||
|
@keydown.enter.prevent="canOpenTradeDetail(pos) && onPositionRowClick(pos)"
|
||||||
|
@keydown.space.prevent="canOpenTradeDetail(pos) && onPositionRowClick(pos)"
|
||||||
>
|
>
|
||||||
<div class="design-pos-top">
|
<div class="design-pos-top">
|
||||||
<div class="design-pos-left">
|
<div class="design-pos-left">
|
||||||
@ -91,12 +97,7 @@
|
|||||||
<span v-else class="position-icon-char">{{ pos.iconChar || '•' }}</span>
|
<span v-else class="position-icon-char">{{ pos.iconChar || '•' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="design-pos-title-col">
|
<div class="design-pos-title-col">
|
||||||
<div class="position-mobile-title marquee-container">
|
<MarqueeTitle :text="pos.market" title-class="position-mobile-title" />
|
||||||
<div class="marquee-track">
|
|
||||||
<span>{{ pos.market }}</span>
|
|
||||||
<span aria-hidden="true">{{ pos.market }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="design-pos-tags">
|
<div class="design-pos-tags">
|
||||||
<span
|
<span
|
||||||
v-if="pos.outcomeTag"
|
v-if="pos.outcomeTag"
|
||||||
@ -150,18 +151,33 @@
|
|||||||
<div v-else-if="filteredOpenOrders.length === 0" class="empty-cell">
|
<div v-else-if="filteredOpenOrders.length === 0" class="empty-cell">
|
||||||
{{ t('wallet.noOpenOrdersFound') }}
|
{{ t('wallet.noOpenOrdersFound') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-for="ord in paginatedOpenOrders" :key="ord.id" class="order-mobile-card design-order-card">
|
<div
|
||||||
|
v-for="ord in paginatedOpenOrders"
|
||||||
|
:key="ord.id"
|
||||||
|
class="order-mobile-card design-order-card"
|
||||||
|
:class="{ 'wallet-row--nav': canOpenTradeDetail(ord) }"
|
||||||
|
:role="canOpenTradeDetail(ord) ? 'button' : undefined"
|
||||||
|
:tabindex="canOpenTradeDetail(ord) ? 0 : -1"
|
||||||
|
@click="onOpenOrderRowClick(ord)"
|
||||||
|
@keydown.enter.prevent="canOpenTradeDetail(ord) && onOpenOrderRowClick(ord)"
|
||||||
|
@keydown.space.prevent="canOpenTradeDetail(ord) && onOpenOrderRowClick(ord)"
|
||||||
|
>
|
||||||
<div class="design-order-top">
|
<div class="design-order-top">
|
||||||
<div class="order-mobile-icon" :class="ord.iconClass">
|
<div class="order-mobile-icon" :class="ord.iconClass">
|
||||||
<span class="position-icon-char">{{ ord.iconChar || '•' }}</span>
|
<img
|
||||||
|
v-if="ord.imageUrl"
|
||||||
|
:src="ord.imageUrl"
|
||||||
|
alt=""
|
||||||
|
class="position-icon-img"
|
||||||
|
/>
|
||||||
|
<span v-else class="position-icon-char">{{ ord.iconChar || '•' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-mobile-main design-order-title-col">
|
<div class="order-mobile-main design-order-title-col">
|
||||||
<div class="order-mobile-title marquee-container order-title-marquee">
|
<MarqueeTitle
|
||||||
<div class="marquee-track">
|
:text="getOrderDisplayTitle(ord)"
|
||||||
<span>{{ getOrderDisplayTitle(ord) }}</span>
|
title-class="order-mobile-title"
|
||||||
<span aria-hidden="true">{{ getOrderDisplayTitle(ord) }}</span>
|
fast
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
<div class="design-order-tags">
|
<div class="design-order-tags">
|
||||||
<span class="order-side-pill" :class="getOrderSideClass(ord)">{{
|
<span class="order-side-pill" :class="getOrderSideClass(ord)">{{
|
||||||
getOrderActionLabel(ord)
|
getOrderActionLabel(ord)
|
||||||
@ -216,7 +232,15 @@
|
|||||||
v-for="h in paginatedHistory"
|
v-for="h in paginatedHistory"
|
||||||
:key="h.id"
|
:key="h.id"
|
||||||
class="history-mobile-card"
|
class="history-mobile-card"
|
||||||
:class="isFundingHistory(h) ? 'design-funding-card' : 'design-trade-card'"
|
:class="[
|
||||||
|
isFundingHistory(h) ? 'design-funding-card' : 'design-trade-card',
|
||||||
|
{ 'wallet-row--nav': historyRowCanOpenDetail(h) },
|
||||||
|
]"
|
||||||
|
:role="historyRowCanOpenDetail(h) ? 'button' : undefined"
|
||||||
|
:tabindex="historyRowCanOpenDetail(h) ? 0 : undefined"
|
||||||
|
@click="onHistoryRowClick(h)"
|
||||||
|
@keydown.enter.prevent="onHistoryRowClick(h)"
|
||||||
|
@keydown.space.prevent="onHistoryRowClick(h)"
|
||||||
>
|
>
|
||||||
<template v-if="!isFundingHistory(h)">
|
<template v-if="!isFundingHistory(h)">
|
||||||
<div class="design-history-top">
|
<div class="design-history-top">
|
||||||
@ -226,12 +250,7 @@
|
|||||||
<span v-else class="position-icon-char">{{ h.iconChar || '•' }}</span>
|
<span v-else class="position-icon-char">{{ h.iconChar || '•' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="history-mobile-main">
|
<div class="history-mobile-main">
|
||||||
<div class="history-mobile-title marquee-container history-title-marquee">
|
<MarqueeTitle :text="h.market" title-class="history-mobile-title" fast />
|
||||||
<div class="marquee-track">
|
|
||||||
<span>{{ h.market }}</span>
|
|
||||||
<span aria-hidden="true">{{ h.market }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="history-mobile-activity">
|
<div class="history-mobile-activity">
|
||||||
{{ h.timeAgo || h.activityDetail || h.activity }}
|
{{ h.timeAgo || h.activityDetail || h.activity }}
|
||||||
</div>
|
</div>
|
||||||
@ -261,12 +280,7 @@
|
|||||||
<span v-else class="position-icon-char">{{ getFundingIconText(h) }}</span>
|
<span v-else class="position-icon-char">{{ getFundingIconText(h) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="history-mobile-main">
|
<div class="history-mobile-main">
|
||||||
<div class="history-mobile-title marquee-container history-title-marquee">
|
<MarqueeTitle :text="getFundingTitle(h)" title-class="history-mobile-title" fast />
|
||||||
<div class="marquee-track">
|
|
||||||
<span>{{ getFundingTitle(h) }}</span>
|
|
||||||
<span aria-hidden="true">{{ getFundingTitle(h) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="history-mobile-activity">{{ h.timeAgo || '—' }}</div>
|
<div class="history-mobile-activity">{{ h.timeAgo || '—' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -329,7 +343,7 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="empty-cell">{{ t('common.noData') }}</div>
|
<div class="empty-cell">{{ t('common.noData') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="false && currentListTotal > 0" class="pagination-bar">
|
<div v-if="currentListTotal > 0" class="pagination-bar">
|
||||||
<span class="pagination-info">
|
<span class="pagination-info">
|
||||||
{{ currentPageStart }}–{{ currentPageEnd }} of {{ currentListTotal }}
|
{{ currentPageStart }}–{{ currentPageEnd }} of {{ currentListTotal }}
|
||||||
</span>
|
</span>
|
||||||
@ -456,6 +470,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -463,13 +478,19 @@ import { createChart, AreaSeries, LineType, LastPriceAnimationMode } from 'light
|
|||||||
import { toLwcData } from '../composables/useLightweightChart'
|
import { toLwcData } from '../composables/useLightweightChart'
|
||||||
import DepositDialog from '../components/DepositDialog.vue'
|
import DepositDialog from '../components/DepositDialog.vue'
|
||||||
import WithdrawDialog from '../components/WithdrawDialog.vue'
|
import WithdrawDialog from '../components/WithdrawDialog.vue'
|
||||||
|
import MarqueeTitle from '../components/MarqueeTitle.vue'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
import { useLocaleStore } from '../stores/locale'
|
import { useLocaleStore } from '../stores/locale'
|
||||||
import { useAuthError } from '../composables/useAuthError'
|
import { useAuthError } from '../composables/useAuthError'
|
||||||
import { cancelOrder as apiCancelOrder } from '../api/order'
|
import { cancelOrder as apiCancelOrder } from '../api/order'
|
||||||
import { getOrderList, mapOrderToOpenOrderItem, OrderStatus } from '../api/order'
|
import { getOrderList, mapOrderToOpenOrderItem, OrderStatus } from '../api/order'
|
||||||
import { getHistoryRecordListClient, getHistoryRecordList } from '../api/historyRecord'
|
import {
|
||||||
|
getHistoryRecordListClient,
|
||||||
|
getHistoryRecordList,
|
||||||
|
HISTORY_RECORD_TYPE,
|
||||||
|
} from '../api/historyRecord'
|
||||||
import { getPositionList, mapPositionToDisplayItem, claimPosition } from '../api/position'
|
import { getPositionList, mapPositionToDisplayItem, claimPosition } from '../api/position'
|
||||||
|
import { buildTradeDetailPushOptions } from '../api/event'
|
||||||
import {
|
import {
|
||||||
getSettlementRequestsListClient,
|
getSettlementRequestsListClient,
|
||||||
amountToUsdcDisplay,
|
amountToUsdcDisplay,
|
||||||
@ -486,6 +507,7 @@ import { USE_MOCK_WALLET } from '../config/mock'
|
|||||||
import { CrossChainUSDTAuth } from '../../sdk/approve'
|
import { CrossChainUSDTAuth } from '../../sdk/approve'
|
||||||
import { useToastStore } from '../stores/toast'
|
import { useToastStore } from '../stores/toast'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const { mobile } = useDisplay()
|
const { mobile } = useDisplay()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { formatAuthError } = useAuthError()
|
const { formatAuthError } = useAuthError()
|
||||||
@ -600,6 +622,10 @@ interface Position {
|
|||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 所属市场是否已关闭,marketClosed=true 表示可结算/可领取 */
|
/** 所属市场是否已关闭,marketClosed=true 表示可结算/可领取 */
|
||||||
marketClosed?: boolean
|
marketClosed?: boolean
|
||||||
|
/** 跳转市场详情:事件 ID(路径) */
|
||||||
|
tradeEventId?: string
|
||||||
|
/** 跳转市场详情:事件 slug */
|
||||||
|
tradeEventSlug?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从 avgNow "72¢ → 0.5¢" 解析出 [avg, now] */
|
/** 从 avgNow "72¢ → 0.5¢" 解析出 [avg, now] */
|
||||||
@ -627,6 +653,11 @@ interface OpenOrder {
|
|||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 已成交数量达到原始总数量,不可撤单 */
|
/** 已成交数量达到原始总数量,不可撤单 */
|
||||||
fullyFilled?: boolean
|
fullyFilled?: boolean
|
||||||
|
/** 来自订单 pmMarket.image / icon */
|
||||||
|
imageUrl?: string
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
detailMarketId?: string
|
||||||
}
|
}
|
||||||
interface HistoryItem {
|
interface HistoryItem {
|
||||||
id: string
|
id: string
|
||||||
@ -647,6 +678,71 @@ interface HistoryItem {
|
|||||||
iconClass?: string
|
iconClass?: string
|
||||||
/** 图标 URL(来自 record.icon) */
|
/** 图标 URL(来自 record.icon) */
|
||||||
imageUrl?: string
|
imageUrl?: string
|
||||||
|
/** API type:TRADE、SPLIT… */
|
||||||
|
recordType?: string
|
||||||
|
/** TRADE 时 buy / sell */
|
||||||
|
tradeSideRaw?: string
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
detailMarketId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function canOpenTradeDetail(opts: {
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
}): boolean {
|
||||||
|
return !!(opts.tradeEventId?.trim() || opts.tradeEventSlug?.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
function openTradeDetailFromWallet(input: {
|
||||||
|
tradeEventId?: string
|
||||||
|
tradeEventSlug?: string
|
||||||
|
marketId?: string
|
||||||
|
title?: string
|
||||||
|
}) {
|
||||||
|
const loc = buildTradeDetailPushOptions({
|
||||||
|
eventId: input.tradeEventId,
|
||||||
|
eventSlug: input.tradeEventSlug,
|
||||||
|
marketId: input.marketId,
|
||||||
|
title: input.title,
|
||||||
|
})
|
||||||
|
if (loc) router.push(loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPositionRowClick(pos: Position) {
|
||||||
|
if (!canOpenTradeDetail(pos)) return
|
||||||
|
openTradeDetailFromWallet({
|
||||||
|
tradeEventId: pos.tradeEventId,
|
||||||
|
tradeEventSlug: pos.tradeEventSlug,
|
||||||
|
marketId: pos.marketID,
|
||||||
|
title: pos.market,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenOrderRowClick(ord: OpenOrder) {
|
||||||
|
if (!canOpenTradeDetail(ord)) return
|
||||||
|
openTradeDetailFromWallet({
|
||||||
|
tradeEventId: ord.tradeEventId,
|
||||||
|
tradeEventSlug: ord.tradeEventSlug,
|
||||||
|
marketId: ord.detailMarketId,
|
||||||
|
title: ord.market,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function historyRowCanOpenDetail(h: HistoryItem): boolean {
|
||||||
|
if (isFundingHistory(h)) return false
|
||||||
|
return canOpenTradeDetail(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHistoryRowClick(h: HistoryItem) {
|
||||||
|
if (isFundingHistory(h)) return
|
||||||
|
if (!canOpenTradeDetail(h)) return
|
||||||
|
openTradeDetailFromWallet({
|
||||||
|
tradeEventId: h.tradeEventId,
|
||||||
|
tradeEventSlug: h.tradeEventSlug,
|
||||||
|
marketId: h.detailMarketId,
|
||||||
|
title: h.market,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOutcomeClass(outcomeTag?: string, fallback?: string): string {
|
function getOutcomeClass(outcomeTag?: string, fallback?: string): string {
|
||||||
@ -707,13 +803,37 @@ function getFundingIconClass(h: HistoryItem): string {
|
|||||||
return isWithdrawalHistory(h) ? 'funding-icon-withdraw' : 'funding-icon-deposit'
|
return isWithdrawalHistory(h) ? 'funding-icon-withdraw' : 'funding-icon-deposit'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHistoryTagLabel(h: HistoryItem): 'BUY' | 'SELL' {
|
function getHistoryTagLabel(h: HistoryItem): string {
|
||||||
return h.profitLossNegative ? 'BUY' : 'SELL'
|
if (!h.recordType) {
|
||||||
|
return h.profitLossNegative ? t('wallet.historySideBuy') : t('wallet.historySideSell')
|
||||||
|
}
|
||||||
|
const type = h.recordType.toUpperCase()
|
||||||
|
if (type === HISTORY_RECORD_TYPE.TRADE) {
|
||||||
|
const s = (h.tradeSideRaw ?? '').toLowerCase()
|
||||||
|
return s === 'sell' ? t('wallet.historySideSell') : t('wallet.historySideBuy')
|
||||||
|
}
|
||||||
|
const typeLabels: Record<string, string> = {
|
||||||
|
[HISTORY_RECORD_TYPE.SPLIT]: t('wallet.historyTypeSplit'),
|
||||||
|
[HISTORY_RECORD_TYPE.MERGE]: t('wallet.historyTypeMerge'),
|
||||||
|
[HISTORY_RECORD_TYPE.REDEEM]: t('wallet.historyTypeRedeem'),
|
||||||
|
[HISTORY_RECORD_TYPE.REWARD]: t('wallet.historyTypeReward'),
|
||||||
|
[HISTORY_RECORD_TYPE.CONVERSION]: t('wallet.historyTypeConversion'),
|
||||||
|
[HISTORY_RECORD_TYPE.MAKER_REBATE]: t('wallet.historyTypeMakerRebate'),
|
||||||
|
}
|
||||||
|
return typeLabels[type] ?? type
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHistoryTagClass(h: HistoryItem): string {
|
function getHistoryTagClass(h: HistoryItem): string {
|
||||||
|
if (!h.recordType) {
|
||||||
return h.profitLossNegative ? 'history-tag-buy' : 'history-tag-sell'
|
return h.profitLossNegative ? 'history-tag-buy' : 'history-tag-sell'
|
||||||
}
|
}
|
||||||
|
const type = h.recordType.toUpperCase()
|
||||||
|
if (type === HISTORY_RECORD_TYPE.TRADE) {
|
||||||
|
const s = (h.tradeSideRaw ?? '').toLowerCase()
|
||||||
|
return s === 'sell' ? 'history-tag-sell' : 'history-tag-buy'
|
||||||
|
}
|
||||||
|
return 'history-tag-type'
|
||||||
|
}
|
||||||
|
|
||||||
const positions = ref<Position[]>(USE_MOCK_WALLET ? [...MOCK_WALLET_POSITIONS] : [])
|
const positions = ref<Position[]>(USE_MOCK_WALLET ? [...MOCK_WALLET_POSITIONS] : [])
|
||||||
/** 提现记录列表 */
|
/** 提现记录列表 */
|
||||||
@ -798,7 +918,9 @@ async function loadOpenOrders() {
|
|||||||
const list = res.data?.list ?? []
|
const list = res.data?.list ?? []
|
||||||
const openOnly = list.filter((o) => (o.status ?? 1) === OrderStatus.Live)
|
const openOnly = list.filter((o) => (o.status ?? 1) === OrderStatus.Live)
|
||||||
openOrderList.value = openOnly.map(mapOrderToOpenOrderItem)
|
openOrderList.value = openOnly.map(mapOrderToOpenOrderItem)
|
||||||
openOrderTotal.value = openOnly.length
|
const apiTotal = res.data?.total
|
||||||
|
openOrderTotal.value =
|
||||||
|
typeof apiTotal === 'number' && Number.isFinite(apiTotal) ? apiTotal : openOnly.length
|
||||||
} else {
|
} else {
|
||||||
openOrderList.value = []
|
openOrderList.value = []
|
||||||
openOrderTotal.value = 0
|
openOrderTotal.value = 0
|
||||||
@ -1253,7 +1375,7 @@ function initPlChart() {
|
|||||||
bottomColor: color + '08',
|
bottomColor: color + '08',
|
||||||
lineColor: color,
|
lineColor: color,
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
lineType: LineType.Curved,
|
lineType: LineType.Simple,
|
||||||
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
|
||||||
priceFormat: { type: 'price', precision: 2 },
|
priceFormat: { type: 'price', precision: 2 },
|
||||||
})
|
})
|
||||||
@ -1522,6 +1644,15 @@ async function submitAuthorize() {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wallet-row--nav {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-row--nav:focus-visible {
|
||||||
|
outline: 2px solid rgb(var(--v-theme-primary));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.wallet-tabs {
|
.wallet-tabs {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
@ -1689,7 +1820,7 @@ async function submitAuthorize() {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.design-pos-card .position-mobile-title {
|
.design-pos-card :deep(.position-mobile-title) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -1701,33 +1832,6 @@ async function submitAuthorize() {
|
|||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marquee-container {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.marquee-track {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
min-width: max-content;
|
|
||||||
gap: 40px;
|
|
||||||
animation: wallet-title-marquee 10s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.marquee-track > span {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wallet-title-marquee {
|
|
||||||
0% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(calc(-50% - 20px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.design-pos-card .position-value {
|
.design-pos-card .position-value {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -1738,22 +1842,6 @@ async function submitAuthorize() {
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-title-marquee {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-title-marquee .marquee-track {
|
|
||||||
animation-duration: 9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-title-marquee {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-title-marquee .marquee-track {
|
|
||||||
animation-duration: 9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.design-order-card .design-order-tags {
|
.design-order-card .design-order-tags {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@ -1878,8 +1966,8 @@ async function submitAuthorize() {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-mobile-card.design-trade-card .history-mobile-title,
|
.history-mobile-card.design-trade-card :deep(.history-mobile-title),
|
||||||
.history-mobile-card.design-funding-card .history-mobile-title {
|
.history-mobile-card.design-funding-card :deep(.history-mobile-title) {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@ -1921,6 +2009,12 @@ async function submitAuthorize() {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-tag-type {
|
||||||
|
color: #6d28d9;
|
||||||
|
border: 1.5px solid #7c3aed;
|
||||||
|
background: rgba(124, 58, 237, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.funding-icon-deposit {
|
.funding-icon-deposit {
|
||||||
background: #dcfce7;
|
background: #dcfce7;
|
||||||
color: #16a34a;
|
color: #16a34a;
|
||||||
@ -2174,7 +2268,7 @@ async function submitAuthorize() {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.position-mobile-title {
|
.design-pos-title-col :deep(.position-mobile-title) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
@ -2293,7 +2387,7 @@ async function submitAuthorize() {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-mobile-title {
|
.order-mobile-main :deep(.order-mobile-title) {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
@ -2409,7 +2503,7 @@ async function submitAuthorize() {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-mobile-title {
|
.history-mobile-main :deep(.history-mobile-title) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user