diff --git a/src/api/historyRecord.ts b/src/api/historyRecord.ts new file mode 100644 index 0000000..09d1c48 --- /dev/null +++ b/src/api/historyRecord.ts @@ -0,0 +1,185 @@ +/** + * 历史记录接口:getHistoryRecordListClient(需鉴权)、getHistoryRecordPublic(无需鉴权) + * 用于 Wallet.vue 历史 Tab 数据 + */ + +import { buildQuery, get } from './request' +import type { PageResult } from './types' + +/** 单条历史记录(与 doc.json definitions["polymarket.HistoryRecord"] 对齐) */ +export interface HistoryRecordItem { + ID?: number + asset?: string + bio?: string + conditionId?: string + createdAt?: string + eventSlug?: string + icon?: string + name?: string + outcome?: string + outcomeIndex?: number + price?: number + profileImage?: string + profileImageOptimized?: string + proxyWallet?: string + pseudonym?: string + side?: string + size?: number + slug?: string + timestamp?: number + title?: string + transactionHash?: string + type?: string + updatedAt?: string +} + +/** GET /hr/getHistoryRecordPublic 请求参数 */ +export interface GetHistoryRecordPublicParams { + page?: number + pageSize?: number + keyword?: string + slug?: string + title?: string + name?: string + bio?: string + createdAtRange?: string[] +} + +/** GET /hr/getHistoryRecordListClient 请求参数(客户端分页,需鉴权) */ +export interface GetHistoryRecordListClientParams { + page?: number + pageSize?: number + userId?: number + keyword?: string + slug?: string + title?: string + name?: string + bio?: string + createdAtRange?: string[] +} + +/** 响应 data 可能为分页或数组 */ +export interface HistoryRecordPublicResponse { + code: number + data?: PageResult | HistoryRecordItem[] + msg: string +} + +/** getHistoryRecordListClient 响应 data 为 PageResult */ +export interface HistoryRecordListClientResponse { + code: number + data?: PageResult + msg: string +} + +/** + * 客户端分页获取历史记录(需鉴权:x-token、x-user-id) + * GET /hr/getHistoryRecordListClient + */ +export async function getHistoryRecordListClient( + params: GetHistoryRecordListClientParams = {}, + config?: { headers?: Record }, +): Promise { + const query = buildQuery({ + page: params.page, + pageSize: params.pageSize, + userId: params.userId, + keyword: params.keyword, + slug: params.slug, + title: params.title, + name: params.name, + bio: params.bio, + createdAtRange: params.createdAtRange, + }) + return get('/hr/getHistoryRecordListClient', query, config) +} + +/** + * 获取历史记录(公开接口,无需鉴权) + * GET /hr/getHistoryRecordPublic + */ +export async function getHistoryRecordPublic( + params: GetHistoryRecordPublicParams = {}, +): Promise { + const query = buildQuery({ + page: params.page, + pageSize: params.pageSize, + keyword: params.keyword, + slug: params.slug, + title: params.title, + name: params.name, + bio: params.bio, + createdAtRange: params.createdAtRange, + }) + return get('/hr/getHistoryRecordPublic', query) +} + +/** 钱包 History 展示项(与 Wallet.vue HistoryItem / order.HistoryDisplayItem 一致) */ +export interface HistoryDisplayItem { + id: string + market: string + side: 'Yes' | 'No' + activity: string + value: string + activityDetail?: string + profitLoss?: string + profitLossNegative?: boolean + timeAgo?: string + avgPrice?: string + shares?: string + iconChar?: string + iconClass?: string +} + +function formatTimeAgo(createdAt: string | undefined, timestamp?: number): string { + const ms = createdAt ? new Date(createdAt).getTime() : (timestamp != null ? timestamp * 1000 : 0) + if (!ms) return '' + const diff = Date.now() - ms + if (diff < 60000) return 'Just now' + if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago` + if (diff < 86400000) return `${Math.floor(diff / 3600000)} hours ago` + if (diff < 604800000) return `${Math.floor(diff / 86400000)} days ago` + return new Date(ms).toLocaleDateString() +} + +/** + * 将 HistoryRecordItem 映射为钱包 History 展示项 + */ +export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): HistoryDisplayItem { + const id = String(record.ID ?? '') + const market = record.title ?? record.name ?? record.eventSlug ?? '' + const outcome = (record.outcome ?? record.side ?? 'Yes').toString() + const side = outcome === 'No' || outcome === 'Down' ? 'No' : 'Yes' + const typeLabel = record.type ?? 'Trade' + const activity = `${typeLabel} ${outcome}`.trim() + const price = record.price ?? 0 + const size = record.size ?? 0 + const valueUsd = price * size + const value = `$${Math.abs(valueUsd).toFixed(2)}` + const priceCents = Math.round(price * 100) + const activityDetail = size > 0 ? `Sold ${Math.floor(size)} ${outcome} at ${priceCents}¢` : value + const timeAgo = formatTimeAgo(record.createdAt, record.timestamp) + return { + id, + market, + side, + activity, + value, + activityDetail, + profitLoss: value, + profitLossNegative: valueUsd < 0, + timeAgo, + avgPrice: priceCents ? `${priceCents}¢` : undefined, + shares: size > 0 ? String(Math.floor(size)) : undefined, + } +} + +/** 从响应中取出 list 并映射为展示项,同时返回 total(数组时为 length) */ +export function getHistoryRecordList( + data: HistoryRecordPublicResponse['data'], +): { list: HistoryDisplayItem[]; total: number } { + if (!data) return { list: [], total: 0 } + const list = Array.isArray(data) ? data : (data as PageResult).list ?? [] + const total = Array.isArray(data) ? data.length : (data as PageResult).total ?? list.length + return { list: list.map(mapHistoryRecordToDisplayItem), total } +} diff --git a/src/api/position.ts b/src/api/position.ts index a9315ce..2bb5288 100644 --- a/src/api/position.ts +++ b/src/api/position.ts @@ -16,6 +16,8 @@ export interface ClobPositionMarket { outcomes?: string[] outcomePrices?: string[] | number[] clobTokenIds?: string[] + /** 市场是否已关闭,closed=true 表示可结算/可领取 */ + closed?: boolean [key: string]: unknown } @@ -162,8 +164,8 @@ export interface PositionDisplayItem { marketID?: string /** Token ID(用于 claimPosition,API 返回 tokenId) */ tokenID?: string - /** 是否待领取/未结算(needClaim 为 true 时显示领取按钮) */ - needClaim?: boolean + /** 所属市场是否已关闭,market.closed=true 表示可结算/可领取 */ + marketClosed?: boolean } /** @@ -230,6 +232,7 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay const marketID = String(pos.marketID ?? pos.market?.ID ?? '') const tokenID = pos.tokenId ?? (pos as { tokenID?: string }).tokenID ?? '' const imageUrl = (pos.market?.image ?? pos.market?.icon) as string | undefined + const marketClosed = pos.market?.closed === true return { id, @@ -252,6 +255,6 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay availableSharesNum: availableNum >= 0 ? availableNum : undefined, marketID: marketID || undefined, tokenID: tokenID || undefined, - needClaim: pos.needClaim, + marketClosed, } } diff --git a/src/api/priceHistory.ts b/src/api/priceHistory.ts new file mode 100644 index 0000000..92d8aa8 --- /dev/null +++ b/src/api/priceHistory.ts @@ -0,0 +1,97 @@ +/** + * 价格历史接口(GET /pmPriceHistory/getPmPriceHistoryPublic) + * 用于 TradeDetail.vue 折线图数据 + */ + +import { buildQuery, get } from './request' +import type { ApiResponse } from './types' +import type { PageResult } from './types' + +/** 单条价格历史(与 doc.json definitions["polymarket.PmPriceHistory"] 对齐) */ +export interface PmPriceHistoryItem { + ID?: number + createdAt?: string + fidelity?: number + interval?: string + /** 市场标识 */ + market?: string + /** 价格(通常 0–1 小数,前端展示可乘 100 为百分比) */ + price?: number + /** 时间戳(Unix 秒) */ + time?: number + updatedAt?: string +} + +/** GET /pmPriceHistory/getPmPriceHistoryPublic 请求参数 */ +export interface GetPmPriceHistoryPublicParams { + /** 市场标识(必填,用于筛选) */ + market: string + page?: number + pageSize?: number + /** 数据间隔 */ + interval?: string + /** 时间筛选 */ + time?: number + /** 创建时间范围 */ + createdAtRange?: string[] + fidelity?: number + keyword?: string + order?: string + sort?: string + price?: number +} + +/** 响应 data 为 PageResult */ +export interface PmPriceHistoryPublicResponse { + code: number + data?: PageResult + msg: string +} + +/** + * 获取价格历史(公开接口,无需鉴权) + * GET /pmPriceHistory/getPmPriceHistoryPublic + */ +export async function getPmPriceHistoryPublic( + params: GetPmPriceHistoryPublicParams, + config?: { headers?: Record }, +): Promise { + const { market, page = 1, pageSize = 500, interval, time, createdAtRange, fidelity, keyword, order, sort, price } = params + const query = buildQuery({ + market, + page, + pageSize, + interval, + time, + createdAtRange, + fidelity, + keyword, + order, + sort, + price, + }) + return get('/pmPriceHistory/getPmPriceHistoryPublic', query, config) +} + +/** 图表单点格式 [timestamp_ms, value_0_100] */ +export type ChartDataPoint = [number, number] + +/** + * 将接口返回的 list 转为 ECharts 折线图数据 + * - time 转为毫秒时间戳 + * - price 若 ≤1 视为小数概率,乘 100;否则视为 0–100 + */ +export function priceHistoryToChartData(list: PmPriceHistoryItem[]): ChartDataPoint[] { + if (!list?.length) return [] + const out: ChartDataPoint[] = [] + for (const item of list) { + const t = item.time + const p = item.price + if (t == null || p == null || !Number.isFinite(Number(t))) continue + const tsMs = Number(t) < 1e12 ? Number(t) * 1000 : Number(t) + const value = Number(p) <= 1 ? Number(p) * 100 : Number(p) + out.push([tsMs, value]) + } + out.sort((a, b) => a[0] - b[0]) + return out +}