优化:文档更新
This commit is contained in:
parent
a29dac0146
commit
fa5e0dccfe
@ -8,7 +8,7 @@ Event(预测市场事件)相关接口与类型定义,对接 XTrader API
|
||||
|
||||
## 核心能力
|
||||
|
||||
- `getPmEventPublic`:分页获取公开事件列表(无需鉴权)
|
||||
- `getPmEventPublic`:分页获取公开事件列表(无需鉴权);请求时固定传入 **startDateMax**、**endDateMin** 为当前时间戳(Unix 秒),**startDateMin**、**endDateMax** 不传
|
||||
- `findPmEvent`:按 id/slug 查询事件详情(需鉴权)
|
||||
- `mapEventItemToCard`:将 `PmEventListItem` 转为 `EventCardItem`(供 MarketCard 使用)
|
||||
- 内存缓存:`getEventListCache`、`setEventListCache`、`clearEventListCache`,用于列表切换页面时复用
|
||||
|
||||
87
docs/api/historyRecord.md
Normal file
87
docs/api/historyRecord.md
Normal file
@ -0,0 +1,87 @@
|
||||
# historyRecord.ts
|
||||
|
||||
**路径**:`src/api/historyRecord.ts`
|
||||
|
||||
## 功能用途
|
||||
|
||||
历史记录接口,用于 Wallet 页「History」Tab。**Wallet 使用 GET /hr/getHistoryRecordListClient**(需鉴权、按当前用户分页);另提供 GET /hr/getHistoryRecordPublic(不需要鉴权)备用。
|
||||
|
||||
## 核心能力
|
||||
|
||||
- `getHistoryRecordListClient`:客户端分页获取历史记录(需鉴权,传 userId、page、pageSize)
|
||||
- `getHistoryRecordPublic`:分页获取历史记录(无需鉴权)
|
||||
- `getHistoryRecordList`:从响应 data 中解析 list 并映射为 `HistoryDisplayItem[]`,同时返回 total
|
||||
- `mapHistoryRecordToDisplayItem`:单条 `HistoryRecordItem` → 钱包展示项(与 Wallet.vue HistoryItem 一致)
|
||||
|
||||
## GET /hr/getHistoryRecordListClient(Wallet 使用)
|
||||
|
||||
需鉴权:x-token、x-user-id。
|
||||
|
||||
### 请求参数(Query)
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| page | number | 页码 |
|
||||
| pageSize | number | 每页条数 |
|
||||
| userId | number | 用户 ID |
|
||||
| keyword | string | 关键字 |
|
||||
| slug | string | 标识 |
|
||||
| title | string | 标题 |
|
||||
| name | string | 名称 |
|
||||
| bio | string | 简介 |
|
||||
| createdAtRange | string[] | 创建时间范围 |
|
||||
|
||||
### 响应
|
||||
|
||||
`{ code, data, msg }`,`data` 为 `PageResult<HistoryRecordItem>`(list、page、pageSize、total)。
|
||||
|
||||
## GET /hr/getHistoryRecordPublic
|
||||
|
||||
### 请求参数(Query,可选)
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| page | number | 页码 |
|
||||
| pageSize | number | 每页条数 |
|
||||
| keyword | string | 关键字 |
|
||||
| slug | string | 标识 |
|
||||
| title | string | 标题 |
|
||||
| name | string | 名称 |
|
||||
| bio | string | 简介 |
|
||||
| createdAtRange | string[] | 创建时间范围 |
|
||||
|
||||
### 响应
|
||||
|
||||
`{ code, data, msg }`。`data` 可能为 `PageResult<HistoryRecordItem>`(list、page、pageSize、total)或 `HistoryRecordItem[]`。
|
||||
|
||||
### HistoryRecordItem(与 doc.json polymarket.HistoryRecord 对齐)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ID | number | 主键 |
|
||||
| title | string | 标题 |
|
||||
| name | string | 名称 |
|
||||
| eventSlug | string | 事件标识 |
|
||||
| outcome | string | 结果(Yes/No 等) |
|
||||
| side | string | 方向 |
|
||||
| type | string | 类型 |
|
||||
| price | number | 价格 |
|
||||
| size | number | 大小 |
|
||||
| createdAt | string | 创建时间 |
|
||||
| timestamp | number | 时间戳(秒) |
|
||||
| 其他 | - | asset, bio, conditionId, icon, slug, transactionHash 等 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
```typescript
|
||||
import { getHistoryRecordPublic, getHistoryRecordList } from '@/api/historyRecord'
|
||||
|
||||
const res = await getHistoryRecordPublic({ page: 1, pageSize: 10 })
|
||||
const { list, total } = getHistoryRecordList(res.data)
|
||||
// list 为 HistoryDisplayItem[],可直接用于 Wallet 历史列表
|
||||
```
|
||||
|
||||
## 扩展方式
|
||||
|
||||
- 若后端固定返回分页结构,可收紧 `HistoryRecordPublicResponse['data']` 类型为 `PageResult<HistoryRecordItem>`
|
||||
- 展示字段(market、activity、value、timeAgo 等)可在 `mapHistoryRecordToDisplayItem` 中按实际接口字段再调整
|
||||
27
docs/api/jwt.md
Normal file
27
docs/api/jwt.md
Normal file
@ -0,0 +1,27 @@
|
||||
# jwt.ts
|
||||
|
||||
**路径**:`src/api/jwt.ts`
|
||||
|
||||
## 功能用途
|
||||
|
||||
JWT 相关接口,用于退出登录时将当前 token 加入黑名单,使 token 失效。
|
||||
|
||||
## 核心能力
|
||||
|
||||
- `jsonInBlacklist`:POST /jwt/jsonInBlacklist,需鉴权(x-token、x-user-id)
|
||||
|
||||
## POST /jwt/jsonInBlacklist
|
||||
|
||||
### 请求
|
||||
|
||||
- 方法:POST
|
||||
- 鉴权:需在 headers 中传 x-token、x-user-id
|
||||
- Body:无
|
||||
|
||||
### 响应
|
||||
|
||||
`{ code, msg }`,标准 ApiResponse。
|
||||
|
||||
### 使用场景
|
||||
|
||||
退出登录时由 `useUserStore().logout()` 调用,在清空本地 token 前先请求该接口,使服务端将当前 JWT 加入黑名单。若请求失败(如网络错误、401),仍会执行本地登出。
|
||||
@ -8,8 +8,8 @@
|
||||
|
||||
## 核心能力
|
||||
|
||||
- `getPositionList`:分页获取持仓列表(需鉴权 x-token、x-user-id);返回项含 `needClaim`、`market`(内嵌市场 question、outcomes、outcomePrices)
|
||||
- `mapPositionToDisplayItem`:将接口项转为展示结构;`market` 优先用 `market.question`,否则用 marketID;`avgNow` 有 `market.outcomePrices` 时展示「AVG → NOW」格式;`iconChar`/`iconClass`/`imageUrl` 用于展示图标(market.image 优先)
|
||||
- `getPositionList`:分页获取持仓列表(需鉴权 x-token、x-user-id);返回项含 `market`(内嵌市场 question、outcomes、outcomePrices、**closed**)
|
||||
- `mapPositionToDisplayItem`:将接口项转为展示结构;`market` 优先用 `market.question`,否则用 marketID;`avgNow` 有 `market.outcomePrices` 时展示「AVG → NOW」格式;`iconChar`/`iconClass`/`imageUrl` 用于展示图标(market.image 优先);**marketClosed** 取自 `market.closed`,用于钱包侧判断可结算/可领取
|
||||
|
||||
## GET /clob/position/getPositionList
|
||||
|
||||
@ -43,8 +43,7 @@
|
||||
| cost | string | 成本 |
|
||||
| outcome | string | 方向 |
|
||||
| version | number | 版本号 |
|
||||
| needClaim | boolean | 是否待领取结算 |
|
||||
| market | ClobPositionMarket | 内嵌市场详情(question、outcomes、outcomePrices 等) |
|
||||
| market | ClobPositionMarket | 内嵌市场详情(question、outcomes、outcomePrices、closed 等) |
|
||||
| createdAt | string | 创建时间 |
|
||||
| updatedAt | string | 更新时间 |
|
||||
|
||||
@ -60,6 +59,7 @@
|
||||
| outcomes | string[] | 选项(如 ["Up", "Down"]) |
|
||||
| outcomePrices | string[] \| number[] | 各选项当前价格 |
|
||||
| clobTokenIds | string[] | CLOB Token ID 列表 |
|
||||
| closed | boolean | 市场是否已关闭,true 表示可结算/可领取 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
|
||||
69
docs/api/priceHistory.md
Normal file
69
docs/api/priceHistory.md
Normal file
@ -0,0 +1,69 @@
|
||||
# priceHistory.ts
|
||||
|
||||
**路径**:`src/api/priceHistory.ts`
|
||||
|
||||
## 功能用途
|
||||
|
||||
价格历史公开接口,用于 TradeDetail 页面的 Yes/No 折线图。对接 **GET /pmPriceHistory/getPmPriceHistoryPublic**(无需鉴权)。
|
||||
|
||||
## 核心能力
|
||||
|
||||
- `getPmPriceHistoryPublic`:按市场 ID 分页获取价格历史
|
||||
- `priceHistoryToChartData`:将接口返回的 `list` 转为 ECharts 使用的 `[timestamp_ms, value_0_100][]`
|
||||
|
||||
## GET /pmPriceHistory/getPmPriceHistoryPublic
|
||||
|
||||
### 请求参数(Query)
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| market | string | 是 | 传 YES 对应的 clobTokenId(即当前市场 clobTokenIds[0]) |
|
||||
| page | number | 否 | 页码,默认 1 |
|
||||
| pageSize | number | 否 | 每页条数,默认 500 |
|
||||
| interval | string | 否 | 数据间隔 |
|
||||
| time | number | 否 | 时间筛选 |
|
||||
| createdAtRange | string[] | 否 | 创建时间范围 |
|
||||
| fidelity | number | 否 | 数据精度 |
|
||||
| keyword | string | 否 | 关键字 |
|
||||
| order | string | 否 | 排序 |
|
||||
| sort | string | 否 | 排序字段 |
|
||||
| price | number | 否 | 价格筛选 |
|
||||
|
||||
### 响应
|
||||
|
||||
`{ code, data, msg }`,`data` 为 `PageResult<PmPriceHistoryItem>`(`list`、`page`、`pageSize`、`total`)。
|
||||
|
||||
### PmPriceHistoryItem(list 单项)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ID | number | 主键 |
|
||||
| market | string | 市场标识 |
|
||||
| price | number | 价格(0–1 小数或 0–100,由 priceHistoryToChartData 统一为 0–100) |
|
||||
| time | number | Unix 秒时间戳 |
|
||||
| interval | string | 数据间隔 |
|
||||
| fidelity | number | 数据精度 |
|
||||
| createdAt | string | 创建时间 |
|
||||
| updatedAt | string | 更新时间 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
```typescript
|
||||
import {
|
||||
getPmPriceHistoryPublic,
|
||||
priceHistoryToChartData,
|
||||
type PmPriceHistoryItem,
|
||||
} from '@/api/priceHistory'
|
||||
|
||||
const res = await getPmPriceHistoryPublic({
|
||||
market: marketId,
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
})
|
||||
const chartData = priceHistoryToChartData(res.data?.list ?? [])
|
||||
```
|
||||
|
||||
## 扩展方式
|
||||
|
||||
- 按时间范围(1H/6H/1D 等)传 `interval` 或 `createdAtRange` 需与后端约定取值
|
||||
- 若后端返回的 `price` 固定为 0–100,`priceHistoryToChartData` 已兼容(≤1 时乘 100)
|
||||
@ -9,7 +9,7 @@
|
||||
## 核心能力
|
||||
|
||||
- Trade Up / Trade Down Tab
|
||||
- Asks、Bids 列表,带 `HorizontalProgressBar` 深度条
|
||||
- Asks、Bids 列表,带 `HorizontalProgressBar` 深度条(买卖两边共用同一最大值 `maxOrderBookTotal`,取两边累计总量中的最大值,便于对比深度)
|
||||
- Last price、Spread 展示
|
||||
- Live / 连接中 状态展示(均通过 i18n 国际化)
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
- `isLoggedIn`、`avatarUrl`:派生状态
|
||||
- `balance`:USDC 余额显示(如 "0.00"),支持 **UserSocket** 实时推送更新
|
||||
- `setUser`:设置登录数据并持久化,登录成功后自动连接 UserSocket
|
||||
- `logout`:清空并断开 UserSocket
|
||||
- `logout`:退出登录;先调用 **POST /jwt/jsonInBlacklist** 将当前 JWT 加入黑名单,再清空本地 token/user 并断开 UserSocket;接口失败时仍执行本地登出
|
||||
- `getAuthHeaders`:返回 `{ 'x-token', 'x-user-id' }`,未登录时返回 `undefined`
|
||||
- `fetchUserInfo`、`fetchUsdcBalance`:拉取并更新用户信息与余额;`fetchUserInfo` 兼容多种 API 字段名(id/ID、userName/username 等)
|
||||
- 内部 `parseUserId`:从 API 返回的 user 对象解析 id/ID,兼容 number 与 string,供 `setUser` 与 `fetchUserInfo` 复用
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
## 核心能力
|
||||
|
||||
- 分时图:ECharts 渲染,支持 Past、时间粒度切换;**加密货币事件**可切换 YES/NO 分时图与加密货币价格走势图(CoinGecko 实时数据)
|
||||
- 分时图:ECharts 渲染,支持 Past、时间粒度切换(1H/6H/1D/1W/1M/ALL);**Yes/No 模式**数据来自 **GET /pmPriceHistory/getPmPriceHistoryPublic**(market 传 clobTokenIds[0]),接口返回 `time`(Unix 秒)、`price`(0–1)转成 `[timestamp_ms, value_0_100][]` 后缓存在 `rawChartData`,**分时**为前端按当前选中范围过滤:1H=最近 1 小时、6H=6 小时、1D=1 天、1W=7 天、1M=30 天、ALL=全部,切换时间范围不重复请求;**加密货币事件**可切换 YES/NO 分时图与加密货币价格走势图(CoinGecko 实时数据)
|
||||
- 订单簿:`OrderBook` 组件,通过 **ClobSdk** 对接 CLOB WebSocket 实时数据(全量快照、增量更新、成交推送);份额接口按 6 位小数传(1_000_000 = 1 share),`priceSizeToRows` 与 `mergeDelta` 会将 raw 值除以 `ORDER_BOOK_SIZE_SCALE` 转为展示值
|
||||
- 交易:`TradeComponent`,传入 `market`、`initialOption`、`positions`(持仓数据)
|
||||
- 持仓列表:通过 `getPositionList` 获取当前市场持仓,传递给 `TradeComponent` 用于计算可合并份额
|
||||
@ -36,7 +36,7 @@
|
||||
## 扩展方式
|
||||
|
||||
1. **订单簿**:已通过 `sdk/clobSocket.ts` 的 ClobSdk 对接 CLOB WebSocket,使用 **Yes/No token ID** 订阅 `price_size_all`、`price_size_delta`、`trade` 消息
|
||||
2. **分时图**:可接入 WebSocket 推送的图表数据;加密货币事件已支持 YES/NO 分时与加密货币价格图切换(`src/api/cryptoChart.ts`)
|
||||
2. **分时图**:Yes/NO 折线图仅使用真实接口 `getPmPriceHistoryPublic`,无模拟数据与定时器;事件详情加载完成后自动请求并展示,无 marketId 或接口无数据时展示空图;加密货币事件已支持 YES/NO 分时与加密货币价格图切换(`src/api/cryptoChart.ts`)
|
||||
3. **Comments**:对接评论接口,替换 placeholder
|
||||
4. **Top Holders**:对接持仓接口
|
||||
5. **Activity**:已对接 CLOB `trade` 消息,实时追加成交记录
|
||||
|
||||
@ -10,8 +10,9 @@
|
||||
## 核心能力
|
||||
|
||||
- Portfolio 卡片:余额、Deposit/Withdraw 按钮
|
||||
- Profit/Loss 卡片:时间范围切换、ECharts 图表
|
||||
- Tab:Positions、Open orders、History、Withdrawals(提现记录)
|
||||
- Profit/Loss 卡片:时间范围切换(1D/1W/1M/ALL)、ECharts 资产变化折线图;数据格式为 `[timestamp_ms, pnl][]`,**暂无接口时全部显示为 0**(真实时间轴 + 数值 0),有接口后在此处对接
|
||||
- Tab:Positions、Open orders、**History**(历史记录来自 **GET /hr/getHistoryRecordListClient**,`src/api/historyRecord.ts`,需鉴权、按当前用户分页)、Withdrawals(提现记录)
|
||||
- **可结算/领取**:未结算项(unsettledItems)由持仓中有 `marketID`、`tokenID` 且 **所属 market.closed=true** 的项组成,用于「领取结算」按钮;不再使用 needClaim 判断
|
||||
- Withdrawals:分页列表,状态筛选(全部/审核中/提现成功/审核不通过/提现失败),对接 GET /pmset/getPmSettlementRequestsListClient
|
||||
- DepositDialog、WithdrawDialog 组件
|
||||
- **401 权限错误**:取消订单等接口失败时,通过 `useAuthError().formatAuthError` 统一提示「请先登录」或「权限不足」
|
||||
|
||||
@ -110,6 +110,7 @@ export interface PmEventListResponse {
|
||||
|
||||
/**
|
||||
* GET /PmEvent/getPmEventPublic 请求参数(与 doc.json 对齐)
|
||||
* startDateMin、endDateMax 不传;startDateMax、endDateMin 传当前时间戳(秒)
|
||||
*/
|
||||
export interface GetPmEventListParams {
|
||||
page?: number
|
||||
@ -147,6 +148,7 @@ export async function getPmEventPublic(
|
||||
archived = false,
|
||||
closed = false,
|
||||
} = params
|
||||
const nowTs = Math.floor(Date.now() / 1000)
|
||||
const query = buildQuery({
|
||||
page,
|
||||
pageSize,
|
||||
@ -156,6 +158,8 @@ export async function getPmEventPublic(
|
||||
active: active ? 'true' : 'false',
|
||||
archived: archived ? 'true' : 'false',
|
||||
closed: closed ? 'true' : 'false',
|
||||
startDateMax: nowTs,
|
||||
endDateMin: nowTs,
|
||||
})
|
||||
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
||||
}
|
||||
|
||||
18
src/api/jwt.ts
Normal file
18
src/api/jwt.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* JWT 相关接口
|
||||
* POST /jwt/jsonInBlacklist:将当前 JWT 加入黑名单(退出登录时调用)
|
||||
*/
|
||||
|
||||
import { post } from './request'
|
||||
import type { ApiResponse } from './types'
|
||||
|
||||
/**
|
||||
* 将 JWT 加入黑名单(需鉴权:x-token)
|
||||
* POST /jwt/jsonInBlacklist
|
||||
* 退出登录时调用,使当前 token 失效
|
||||
*/
|
||||
export async function jsonInBlacklist(
|
||||
config?: { headers?: Record<string, string> },
|
||||
): Promise<ApiResponse> {
|
||||
return post<ApiResponse>('/jwt/jsonInBlacklist', undefined, config)
|
||||
}
|
||||
@ -63,7 +63,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -94,7 +96,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -154,11 +158,7 @@
|
||||
|
||||
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||
<!-- Action Button: Buy 余额足够显示 Buy Yes/No,不足显示 {{ t('trade.deposit') }};Sell 只显示 Sell Yes/No -->
|
||||
<v-btn
|
||||
v-if="activeTab === 'buy' && showDepositForBuy"
|
||||
class="deposit-btn"
|
||||
@click="deposit"
|
||||
>
|
||||
<v-btn v-if="activeTab === 'buy' && showDepositForBuy" class="deposit-btn" @click="deposit">
|
||||
{{ t('trade.deposit') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
@ -201,7 +201,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -243,7 +245,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -352,7 +356,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -382,7 +388,9 @@
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setMaxShares">{{ t('trade.max') }}</v-btn>
|
||||
</div>
|
||||
<p v-if="sellSharesExceedsMax" class="shares-exceeds-max-hint">{{ t('trade.sharesExceedsMax', { max: maxAvailableShares }) }}</p>
|
||||
<p v-if="sellSharesExceedsMax" class="shares-exceeds-max-hint">
|
||||
{{ t('trade.sharesExceedsMax', { max: maxAvailableShares }) }}
|
||||
</p>
|
||||
<div v-if="activeTab === 'buy'" class="matching-info">
|
||||
<v-icon size="14">mdi-information</v-icon>
|
||||
<span>20.00 matching</span>
|
||||
@ -506,7 +514,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -537,7 +547,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -563,22 +575,23 @@
|
||||
<div class="total-section">
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div v-if="!isMarketMode" class="total-row">
|
||||
<span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
||||
<span class="label">{{ t('trade.total') }}</span
|
||||
><span class="total-value">${{ totalPrice }}</span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
||||
>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ toWinValue }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
||||
totalPrice
|
||||
}}</span
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
@ -630,7 +643,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -669,7 +684,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -768,7 +785,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -820,13 +839,14 @@
|
||||
<div class="total-section">
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
||||
<span class="label">{{ t('trade.total') }}</span
|
||||
><span class="total-value">${{ totalPrice }}</span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
||||
>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@ -936,7 +956,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -967,7 +989,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -996,20 +1020,20 @@
|
||||
<span class="label">Total</span
|
||||
><span class="total-value">${{ totalPrice }}</span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
||||
>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ toWinValue }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
||||
totalPrice
|
||||
}}</span
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
@ -1061,7 +1085,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.amount') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -1100,7 +1126,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -1199,7 +1227,9 @@
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header sell-shares-header">
|
||||
<span class="label">{{ t('trade.shares') }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline">{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span>
|
||||
<span v-if="activeTab === 'sell'" class="max-shares-inline"
|
||||
>{{ t('trade.maxShares') }}: {{ maxAvailableShares }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="shares-input-wrapper">
|
||||
<v-text-field
|
||||
@ -1251,22 +1281,23 @@
|
||||
<div class="total-section">
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
||||
<span class="label">{{ t('trade.total') }}</span
|
||||
><span class="total-value">${{ totalPrice }}</span>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
||||
>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.toWin') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ toWinValue }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="total-row">
|
||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
||||
totalPrice
|
||||
}}</span
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@ -1324,7 +1355,9 @@
|
||||
</div>
|
||||
<p class="merge-available">
|
||||
{{ t('trade.mergeAvailableShares') }} {{ availableMergeShares }}
|
||||
<button type="button" class="merge-max-link" @click="setMergeMax">{{ t('trade.max') }}</button>
|
||||
<button type="button" class="merge-max-link" @click="setMergeMax">
|
||||
{{ t('trade.max') }}
|
||||
</button>
|
||||
</p>
|
||||
<p v-if="!props.market?.marketId" class="merge-no-market">
|
||||
{{ t('trade.mergeNoMarket', { yesLabel, noLabel }) }}
|
||||
@ -1498,7 +1531,13 @@ const props = withDefaults(
|
||||
/** 当前市场持仓列表,用于计算可合并份额 */
|
||||
positions?: TradePositionItem[]
|
||||
}>(),
|
||||
{ initialOption: undefined, initialTab: undefined, embeddedInSheet: false, market: undefined, positions: () => [] },
|
||||
{
|
||||
initialOption: undefined,
|
||||
initialTab: undefined,
|
||||
embeddedInSheet: false,
|
||||
market: undefined,
|
||||
positions: () => [],
|
||||
},
|
||||
)
|
||||
|
||||
// 移动端:底部栏与弹出层
|
||||
@ -1575,7 +1614,7 @@ async function submitSplit() {
|
||||
splitError.value = ''
|
||||
try {
|
||||
const res = await pmMarketSplit(
|
||||
{ marketID: marketId, usdcAmount: String(splitAmount.value) },
|
||||
{ marketID: marketId, usdcAmount: String(splitAmount.value * 1000000) },
|
||||
{ headers: userStore.getAuthHeaders() },
|
||||
)
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
@ -1639,7 +1678,19 @@ const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
||||
const limitPrice = ref(0.82) // 内部存储 0–1,显示为美分与按钮一致
|
||||
const shares = ref(20) // 初始份额(正整数)
|
||||
const expirationTime = ref('5m') // 初始过期时间
|
||||
const EXPIRATION_VALUES = ['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d'] as const
|
||||
const EXPIRATION_VALUES = [
|
||||
'5m',
|
||||
'15m',
|
||||
'30m',
|
||||
'1h',
|
||||
'2h',
|
||||
'4h',
|
||||
'8h',
|
||||
'12h',
|
||||
'1d',
|
||||
'2d',
|
||||
'3d',
|
||||
] as const
|
||||
const expirationOptions = computed(() =>
|
||||
EXPIRATION_VALUES.map((v) => ({ title: t(`trade.expiration.${v}`), value: v })),
|
||||
)
|
||||
@ -1944,7 +1995,10 @@ const adjustShares = (amount: number) => {
|
||||
|
||||
/** 卖出时输入份额是否超过最大可卖 */
|
||||
const sellSharesExceedsMax = computed(
|
||||
() => activeTab.value === 'sell' && maxAvailableShares.value >= 0 && shares.value > maxAvailableShares.value,
|
||||
() =>
|
||||
activeTab.value === 'sell' &&
|
||||
maxAvailableShares.value >= 0 &&
|
||||
shares.value > maxAvailableShares.value,
|
||||
)
|
||||
|
||||
// 份额百分比调整方法(仅在Sell模式下使用)
|
||||
@ -2058,12 +2112,7 @@ async function submitOrder() {
|
||||
return
|
||||
}
|
||||
const uid = userStore?.user?.ID ?? 0
|
||||
const userIdNum =
|
||||
typeof uid === 'number'
|
||||
? uid
|
||||
: uid != null
|
||||
? parseInt(String(uid), 10)
|
||||
: 0
|
||||
const userIdNum = typeof uid === 'number' ? uid : uid != null ? parseInt(String(uid), 10) : 0
|
||||
if (!Number.isFinite(userIdNum) || userIdNum <= 0) {
|
||||
orderError.value = t('trade.userError')
|
||||
isNoAvailableSharesError.value = false
|
||||
@ -2096,9 +2145,7 @@ async function submitOrder() {
|
||||
? parseExpirationTimestamp(expirationTime.value)
|
||||
: 0
|
||||
|
||||
const rawSize = isMarket && activeTab.value === 'buy'
|
||||
? amount.value
|
||||
: clampShares(shares.value)
|
||||
const rawSize = isMarket && activeTab.value === 'buy' ? amount.value : clampShares(shares.value)
|
||||
const sizeValue = Math.round(rawSize * 1_000_000)
|
||||
|
||||
orderLoading.value = true
|
||||
|
||||
@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
|
||||
import { getUserWsUrl, BASE_URL } from '@/api/request'
|
||||
import { jsonInBlacklist } from '@/api/jwt'
|
||||
import { UserSdk, type BalanceData, type PositionData } from '../../sdk/userSocket'
|
||||
|
||||
export interface UserInfo {
|
||||
@ -49,11 +50,7 @@ function parseUserId(raw: { id?: number | string; ID?: number } | null | undefin
|
||||
} {
|
||||
const rawId = raw?.ID ?? raw?.id
|
||||
const numId =
|
||||
typeof rawId === 'number'
|
||||
? rawId
|
||||
: rawId != null
|
||||
? parseInt(String(rawId), 10)
|
||||
: undefined
|
||||
typeof rawId === 'number' ? rawId : rawId != null ? parseInt(String(rawId), 10) : undefined
|
||||
return { id: rawId ?? numId, numId: Number.isFinite(numId) ? numId : undefined }
|
||||
}
|
||||
|
||||
@ -120,7 +117,9 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
|
||||
/** 订阅 position_update 推送,返回取消订阅函数 */
|
||||
function onPositionUpdate(cb: (data: PositionData & Record<string, unknown>) => void): () => void {
|
||||
function onPositionUpdate(
|
||||
cb: (data: PositionData & Record<string, unknown>) => void,
|
||||
): () => void {
|
||||
positionUpdateCallbacks.push(cb)
|
||||
return () => {
|
||||
const i = positionUpdateCallbacks.indexOf(cb)
|
||||
@ -147,7 +146,15 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
async function logout() {
|
||||
const headers = getAuthHeaders()
|
||||
if (headers) {
|
||||
try {
|
||||
await jsonInBlacklist({ headers })
|
||||
} catch {
|
||||
// 忽略失败,仍执行本地登出
|
||||
}
|
||||
}
|
||||
disconnectUserSocket()
|
||||
token.value = ''
|
||||
user.value = null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user