Compare commits
5 Commits
947ba83b2d
...
fa5e0dccfe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa5e0dccfe | ||
|
|
a29dac0146 | ||
|
|
3983105628 | ||
|
|
9dd61be92f | ||
|
|
d44e7157e8 |
@ -8,7 +8,7 @@ Event(预测市场事件)相关接口与类型定义,对接 XTrader API
|
|||||||
|
|
||||||
## 核心能力
|
## 核心能力
|
||||||
|
|
||||||
- `getPmEventPublic`:分页获取公开事件列表(无需鉴权)
|
- `getPmEventPublic`:分页获取公开事件列表(无需鉴权);请求时固定传入 **startDateMax**、**endDateMin** 为当前时间戳(Unix 秒),**startDateMin**、**endDateMax** 不传
|
||||||
- `findPmEvent`:按 id/slug 查询事件详情(需鉴权)
|
- `findPmEvent`:按 id/slug 查询事件详情(需鉴权)
|
||||||
- `mapEventItemToCard`:将 `PmEventListItem` 转为 `EventCardItem`(供 MarketCard 使用)
|
- `mapEventItemToCard`:将 `PmEventListItem` 转为 `EventCardItem`(供 MarketCard 使用)
|
||||||
- 内存缓存:`getEventListCache`、`setEventListCache`、`clearEventListCache`,用于列表切换页面时复用
|
- 内存缓存:`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)
|
- `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 优先)
|
- `mapPositionToDisplayItem`:将接口项转为展示结构;`market` 优先用 `market.question`,否则用 marketID;`avgNow` 有 `market.outcomePrices` 时展示「AVG → NOW」格式;`iconChar`/`iconClass`/`imageUrl` 用于展示图标(market.image 优先);**marketClosed** 取自 `market.closed`,用于钱包侧判断可结算/可领取
|
||||||
|
|
||||||
## GET /clob/position/getPositionList
|
## GET /clob/position/getPositionList
|
||||||
|
|
||||||
@ -43,8 +43,7 @@
|
|||||||
| cost | string | 成本 |
|
| cost | string | 成本 |
|
||||||
| outcome | string | 方向 |
|
| outcome | string | 方向 |
|
||||||
| version | number | 版本号 |
|
| version | number | 版本号 |
|
||||||
| needClaim | boolean | 是否待领取结算 |
|
| market | ClobPositionMarket | 内嵌市场详情(question、outcomes、outcomePrices、closed 等) |
|
||||||
| market | ClobPositionMarket | 内嵌市场详情(question、outcomes、outcomePrices 等) |
|
|
||||||
| createdAt | string | 创建时间 |
|
| createdAt | string | 创建时间 |
|
||||||
| updatedAt | string | 更新时间 |
|
| updatedAt | string | 更新时间 |
|
||||||
|
|
||||||
@ -60,6 +59,7 @@
|
|||||||
| outcomes | string[] | 选项(如 ["Up", "Down"]) |
|
| outcomes | string[] | 选项(如 ["Up", "Down"]) |
|
||||||
| outcomePrices | string[] \| number[] | 各选项当前价格 |
|
| outcomePrices | string[] \| number[] | 各选项当前价格 |
|
||||||
| clobTokenIds | string[] | CLOB Token ID 列表 |
|
| 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
|
- Trade Up / Trade Down Tab
|
||||||
- Asks、Bids 列表,带 `HorizontalProgressBar` 深度条
|
- Asks、Bids 列表,带 `HorizontalProgressBar` 深度条(买卖两边共用同一最大值 `maxOrderBookTotal`,取两边累计总量中的最大值,便于对比深度)
|
||||||
- Last price、Spread 展示
|
- Last price、Spread 展示
|
||||||
- Live / 连接中 状态展示(均通过 i18n 国际化)
|
- Live / 连接中 状态展示(均通过 i18n 国际化)
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
- `isLoggedIn`、`avatarUrl`:派生状态
|
- `isLoggedIn`、`avatarUrl`:派生状态
|
||||||
- `balance`:USDC 余额显示(如 "0.00"),支持 **UserSocket** 实时推送更新
|
- `balance`:USDC 余额显示(如 "0.00"),支持 **UserSocket** 实时推送更新
|
||||||
- `setUser`:设置登录数据并持久化,登录成功后自动连接 UserSocket
|
- `setUser`:设置登录数据并持久化,登录成功后自动连接 UserSocket
|
||||||
- `logout`:清空并断开 UserSocket
|
- `logout`:退出登录;先调用 **POST /jwt/jsonInBlacklist** 将当前 JWT 加入黑名单,再清空本地 token/user 并断开 UserSocket;接口失败时仍执行本地登出
|
||||||
- `getAuthHeaders`:返回 `{ 'x-token', 'x-user-id' }`,未登录时返回 `undefined`
|
- `getAuthHeaders`:返回 `{ 'x-token', 'x-user-id' }`,未登录时返回 `undefined`
|
||||||
- `fetchUserInfo`、`fetchUsdcBalance`:拉取并更新用户信息与余额;`fetchUserInfo` 兼容多种 API 字段名(id/ID、userName/username 等)
|
- `fetchUserInfo`、`fetchUsdcBalance`:拉取并更新用户信息与余额;`fetchUserInfo` 兼容多种 API 字段名(id/ID、userName/username 等)
|
||||||
- 内部 `parseUserId`:从 API 返回的 user 对象解析 id/ID,兼容 number 与 string,供 `setUser` 与 `fetchUserInfo` 复用
|
- 内部 `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` 转为展示值
|
- 订单簿:`OrderBook` 组件,通过 **ClobSdk** 对接 CLOB WebSocket 实时数据(全量快照、增量更新、成交推送);份额接口按 6 位小数传(1_000_000 = 1 share),`priceSizeToRows` 与 `mergeDelta` 会将 raw 值除以 `ORDER_BOOK_SIZE_SCALE` 转为展示值
|
||||||
- 交易:`TradeComponent`,传入 `market`、`initialOption`、`positions`(持仓数据)
|
- 交易:`TradeComponent`,传入 `market`、`initialOption`、`positions`(持仓数据)
|
||||||
- 持仓列表:通过 `getPositionList` 获取当前市场持仓,传递给 `TradeComponent` 用于计算可合并份额
|
- 持仓列表:通过 `getPositionList` 获取当前市场持仓,传递给 `TradeComponent` 用于计算可合并份额
|
||||||
@ -36,7 +36,7 @@
|
|||||||
## 扩展方式
|
## 扩展方式
|
||||||
|
|
||||||
1. **订单簿**:已通过 `sdk/clobSocket.ts` 的 ClobSdk 对接 CLOB WebSocket,使用 **Yes/No token ID** 订阅 `price_size_all`、`price_size_delta`、`trade` 消息
|
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
|
3. **Comments**:对接评论接口,替换 placeholder
|
||||||
4. **Top Holders**:对接持仓接口
|
4. **Top Holders**:对接持仓接口
|
||||||
5. **Activity**:已对接 CLOB `trade` 消息,实时追加成交记录
|
5. **Activity**:已对接 CLOB `trade` 消息,实时追加成交记录
|
||||||
|
|||||||
@ -10,8 +10,9 @@
|
|||||||
## 核心能力
|
## 核心能力
|
||||||
|
|
||||||
- Portfolio 卡片:余额、Deposit/Withdraw 按钮
|
- Portfolio 卡片:余额、Deposit/Withdraw 按钮
|
||||||
- Profit/Loss 卡片:时间范围切换、ECharts 图表
|
- Profit/Loss 卡片:时间范围切换(1D/1W/1M/ALL)、ECharts 资产变化折线图;数据格式为 `[timestamp_ms, pnl][]`,**暂无接口时全部显示为 0**(真实时间轴 + 数值 0),有接口后在此处对接
|
||||||
- Tab:Positions、Open orders、History、Withdrawals(提现记录)
|
- Tab:Positions、Open orders、**History**(历史记录来自 **GET /hr/getHistoryRecordListClient**,`src/api/historyRecord.ts`,需鉴权、按当前用户分页)、Withdrawals(提现记录)
|
||||||
|
- **可结算/领取**:未结算项(unsettledItems)由持仓中有 `marketID`、`tokenID` 且 **所属 market.closed=true** 的项组成,用于「领取结算」按钮;不再使用 needClaim 判断
|
||||||
- Withdrawals:分页列表,状态筛选(全部/审核中/提现成功/审核不通过/提现失败),对接 GET /pmset/getPmSettlementRequestsListClient
|
- Withdrawals:分页列表,状态筛选(全部/审核中/提现成功/审核不通过/提现失败),对接 GET /pmset/getPmSettlementRequestsListClient
|
||||||
- DepositDialog、WithdrawDialog 组件
|
- DepositDialog、WithdrawDialog 组件
|
||||||
- **401 权限错误**:取消订单等接口失败时,通过 `useAuthError().formatAuthError` 统一提示「请先登录」或「权限不足」
|
- **401 权限错误**:取消订单等接口失败时,通过 `useAuthError().formatAuthError` 统一提示「请先登录」或「权限不足」
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export interface PmEventListResponse {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /PmEvent/getPmEventPublic 请求参数(与 doc.json 对齐)
|
* GET /PmEvent/getPmEventPublic 请求参数(与 doc.json 对齐)
|
||||||
|
* startDateMin、endDateMax 不传;startDateMax、endDateMin 传当前时间戳(秒)
|
||||||
*/
|
*/
|
||||||
export interface GetPmEventListParams {
|
export interface GetPmEventListParams {
|
||||||
page?: number
|
page?: number
|
||||||
@ -147,6 +148,7 @@ export async function getPmEventPublic(
|
|||||||
archived = false,
|
archived = false,
|
||||||
closed = false,
|
closed = false,
|
||||||
} = params
|
} = params
|
||||||
|
const nowTs = Math.floor(Date.now() / 1000)
|
||||||
const query = buildQuery({
|
const query = buildQuery({
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
@ -156,6 +158,8 @@ export async function getPmEventPublic(
|
|||||||
active: active ? 'true' : 'false',
|
active: active ? 'true' : 'false',
|
||||||
archived: archived ? 'true' : 'false',
|
archived: archived ? 'true' : 'false',
|
||||||
closed: closed ? 'true' : 'false',
|
closed: closed ? 'true' : 'false',
|
||||||
|
startDateMax: nowTs,
|
||||||
|
endDateMin: nowTs,
|
||||||
})
|
})
|
||||||
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
return get<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
||||||
}
|
}
|
||||||
|
|||||||
185
src/api/historyRecord.ts
Normal file
185
src/api/historyRecord.ts
Normal file
@ -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> | HistoryRecordItem[]
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** getHistoryRecordListClient 响应 data 为 PageResult */
|
||||||
|
export interface HistoryRecordListClientResponse {
|
||||||
|
code: number
|
||||||
|
data?: PageResult<HistoryRecordItem>
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端分页获取历史记录(需鉴权:x-token、x-user-id)
|
||||||
|
* GET /hr/getHistoryRecordListClient
|
||||||
|
*/
|
||||||
|
export async function getHistoryRecordListClient(
|
||||||
|
params: GetHistoryRecordListClientParams = {},
|
||||||
|
config?: { headers?: Record<string, string> },
|
||||||
|
): Promise<HistoryRecordListClientResponse> {
|
||||||
|
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<HistoryRecordListClientResponse>('/hr/getHistoryRecordListClient', query, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取历史记录(公开接口,无需鉴权)
|
||||||
|
* GET /hr/getHistoryRecordPublic
|
||||||
|
*/
|
||||||
|
export async function getHistoryRecordPublic(
|
||||||
|
params: GetHistoryRecordPublicParams = {},
|
||||||
|
): Promise<HistoryRecordPublicResponse> {
|
||||||
|
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<HistoryRecordPublicResponse>('/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<HistoryRecordItem>).list ?? []
|
||||||
|
const total = Array.isArray(data) ? data.length : (data as PageResult<HistoryRecordItem>).total ?? list.length
|
||||||
|
return { list: list.map(mapHistoryRecordToDisplayItem), total }
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
@ -16,6 +16,8 @@ export interface ClobPositionMarket {
|
|||||||
outcomes?: string[]
|
outcomes?: string[]
|
||||||
outcomePrices?: string[] | number[]
|
outcomePrices?: string[] | number[]
|
||||||
clobTokenIds?: string[]
|
clobTokenIds?: string[]
|
||||||
|
/** 市场是否已关闭,closed=true 表示可结算/可领取 */
|
||||||
|
closed?: boolean
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,8 +164,8 @@ export interface PositionDisplayItem {
|
|||||||
marketID?: string
|
marketID?: string
|
||||||
/** Token ID(用于 claimPosition,API 返回 tokenId) */
|
/** Token ID(用于 claimPosition,API 返回 tokenId) */
|
||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 是否待领取/未结算(needClaim 为 true 时显示领取按钮) */
|
/** 所属市场是否已关闭,market.closed=true 表示可结算/可领取 */
|
||||||
needClaim?: boolean
|
marketClosed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,6 +232,7 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
|
|||||||
const marketID = String(pos.marketID ?? pos.market?.ID ?? '')
|
const marketID = String(pos.marketID ?? pos.market?.ID ?? '')
|
||||||
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
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -252,6 +255,6 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
|
|||||||
availableSharesNum: availableNum >= 0 ? availableNum : undefined,
|
availableSharesNum: availableNum >= 0 ? availableNum : undefined,
|
||||||
marketID: marketID || undefined,
|
marketID: marketID || undefined,
|
||||||
tokenID: tokenID || undefined,
|
tokenID: tokenID || undefined,
|
||||||
needClaim: pos.needClaim,
|
marketClosed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/api/priceHistory.ts
Normal file
97
src/api/priceHistory.ts
Normal file
@ -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<PmPriceHistoryItem> */
|
||||||
|
export interface PmPriceHistoryPublicResponse {
|
||||||
|
code: number
|
||||||
|
data?: PageResult<PmPriceHistoryItem>
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取价格历史(公开接口,无需鉴权)
|
||||||
|
* GET /pmPriceHistory/getPmPriceHistoryPublic
|
||||||
|
*/
|
||||||
|
export async function getPmPriceHistoryPublic(
|
||||||
|
params: GetPmPriceHistoryPublicParams,
|
||||||
|
config?: { headers?: Record<string, string> },
|
||||||
|
): Promise<PmPriceHistoryPublicResponse> {
|
||||||
|
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<PmPriceHistoryPublicResponse>('/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
|
||||||
|
}
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<div v-for="(ask, index) in asksWithCumulativeTotal" :key="index" class="order-item">
|
<div v-for="(ask, index) in asksWithCumulativeTotal" :key="index" class="order-item">
|
||||||
<div class="order-progress">
|
<div class="order-progress">
|
||||||
<HorizontalProgressBar
|
<HorizontalProgressBar
|
||||||
:max="maxAsksTotal"
|
:max="maxOrderBookTotal"
|
||||||
:value="ask.total"
|
:value="ask.total"
|
||||||
:fillStyle="{ backgroundColor: '#e89595' }"
|
:fillStyle="{ backgroundColor: '#e89595' }"
|
||||||
:trackStyle="{ backgroundColor: 'transparent' }"
|
:trackStyle="{ backgroundColor: 'transparent' }"
|
||||||
@ -57,7 +57,7 @@
|
|||||||
>
|
>
|
||||||
<div class="order-progress">
|
<div class="order-progress">
|
||||||
<HorizontalProgressBar
|
<HorizontalProgressBar
|
||||||
:max="maxBidsTotal"
|
:max="maxOrderBookTotal"
|
||||||
:value="bid.total"
|
:value="bid.total"
|
||||||
:fillStyle="{ backgroundColor: '#7acc7a' }"
|
:fillStyle="{ backgroundColor: '#7acc7a' }"
|
||||||
:trackStyle="{ backgroundColor: 'transparent' }"
|
:trackStyle="{ backgroundColor: 'transparent' }"
|
||||||
@ -284,17 +284,12 @@ const bidsWithCumulativeTotal = computed(() => {
|
|||||||
// Reverse to display in descending order
|
// Reverse to display in descending order
|
||||||
})
|
})
|
||||||
|
|
||||||
// Calculate max total from cumulative totals
|
// Shared max total from both asks and bids cumulative totals (for progress bar scale)
|
||||||
const maxAsksTotal = computed(() => {
|
const maxOrderBookTotal = computed(() => {
|
||||||
const askTotals = asksWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
const askTotals = asksWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
||||||
const allTotals = [...askTotals]
|
|
||||||
return Math.max(...allTotals)
|
|
||||||
})
|
|
||||||
|
|
||||||
const maxBidsTotal = computed(() => {
|
|
||||||
const bidTotals = bidsWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
const bidTotals = bidsWithCumulativeTotal.value.map((item) => item.cumulativeTotal)
|
||||||
const allTotals = [...bidTotals]
|
const allTotals = [...askTotals, ...bidTotals]
|
||||||
return Math.max(...allTotals)
|
return allTotals.length ? Math.max(...allTotals) : 0
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -94,7 +96,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -154,11 +158,7 @@
|
|||||||
|
|
||||||
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||||
<!-- Action Button: Buy 余额足够显示 Buy Yes/No,不足显示 {{ t('trade.deposit') }};Sell 只显示 Sell Yes/No -->
|
<!-- Action Button: Buy 余额足够显示 Buy Yes/No,不足显示 {{ t('trade.deposit') }};Sell 只显示 Sell Yes/No -->
|
||||||
<v-btn
|
<v-btn v-if="activeTab === 'buy' && showDepositForBuy" class="deposit-btn" @click="deposit">
|
||||||
v-if="activeTab === 'buy' && showDepositForBuy"
|
|
||||||
class="deposit-btn"
|
|
||||||
@click="deposit"
|
|
||||||
>
|
|
||||||
{{ t('trade.deposit') }}
|
{{ t('trade.deposit') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
@ -201,7 +201,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -243,7 +245,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -352,7 +356,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -382,7 +388,9 @@
|
|||||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||||
<v-btn class="share-btn" @click="setMaxShares">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setMaxShares">{{ t('trade.max') }}</v-btn>
|
||||||
</div>
|
</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">
|
<div v-if="activeTab === 'buy'" class="matching-info">
|
||||||
<v-icon size="14">mdi-information</v-icon>
|
<v-icon size="14">mdi-information</v-icon>
|
||||||
<span>20.00 matching</span>
|
<span>20.00 matching</span>
|
||||||
@ -506,7 +514,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -537,7 +547,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -563,12 +575,14 @@
|
|||||||
<div class="total-section">
|
<div class="total-section">
|
||||||
<template v-if="activeTab === 'buy'">
|
<template v-if="activeTab === 'buy'">
|
||||||
<div v-if="!isMarketMode" class="total-row">
|
<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>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">{{ t('trade.toWin') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
{{ toWinValue }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -576,9 +590,8 @@
|
|||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
totalPrice
|
{{ totalPrice }}</span
|
||||||
}}</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
@ -630,7 +643,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -669,7 +684,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -768,7 +785,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -820,7 +839,8 @@
|
|||||||
<div class="total-section">
|
<div class="total-section">
|
||||||
<template v-if="activeTab === 'buy'">
|
<template v-if="activeTab === 'buy'">
|
||||||
<div class="total-row">
|
<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>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">{{ t('trade.toWin') }}</span
|
||||||
@ -936,7 +956,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -967,7 +989,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -999,7 +1023,8 @@
|
|||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">{{ t('trade.toWin') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
{{ toWinValue }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -1007,9 +1032,8 @@
|
|||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
totalPrice
|
{{ totalPrice }}</span
|
||||||
}}</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
@ -1061,7 +1085,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.amount') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -1100,7 +1126,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -1199,7 +1227,9 @@
|
|||||||
<div class="input-group shares-group">
|
<div class="input-group shares-group">
|
||||||
<div class="shares-header sell-shares-header">
|
<div class="shares-header sell-shares-header">
|
||||||
<span class="label">{{ t('trade.shares') }}</span>
|
<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>
|
||||||
<div class="shares-input-wrapper">
|
<div class="shares-input-wrapper">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -1251,12 +1281,14 @@
|
|||||||
<div class="total-section">
|
<div class="total-section">
|
||||||
<template v-if="activeTab === 'buy'">
|
<template v-if="activeTab === 'buy'">
|
||||||
<div class="total-row">
|
<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>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">{{ t('trade.toWin') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
|
{{ toWinValue }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -1264,9 +1296,8 @@
|
|||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">{{ t('trade.youllReceive') }}</span
|
||||||
><span class="to-win-value"
|
><span class="to-win-value"
|
||||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
|
><v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||||
totalPrice
|
{{ totalPrice }}</span
|
||||||
}}</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -1324,7 +1355,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="merge-available">
|
<p class="merge-available">
|
||||||
{{ t('trade.mergeAvailableShares') }} {{ availableMergeShares }}
|
{{ 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>
|
||||||
<p v-if="!props.market?.marketId" class="merge-no-market">
|
<p v-if="!props.market?.marketId" class="merge-no-market">
|
||||||
{{ t('trade.mergeNoMarket', { yesLabel, noLabel }) }}
|
{{ t('trade.mergeNoMarket', { yesLabel, noLabel }) }}
|
||||||
@ -1498,7 +1531,13 @@ const props = withDefaults(
|
|||||||
/** 当前市场持仓列表,用于计算可合并份额 */
|
/** 当前市场持仓列表,用于计算可合并份额 */
|
||||||
positions?: TradePositionItem[]
|
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 = ''
|
splitError.value = ''
|
||||||
try {
|
try {
|
||||||
const res = await pmMarketSplit(
|
const res = await pmMarketSplit(
|
||||||
{ marketID: marketId, usdcAmount: String(splitAmount.value) },
|
{ marketID: marketId, usdcAmount: String(splitAmount.value * 1000000) },
|
||||||
{ headers: userStore.getAuthHeaders() },
|
{ headers: userStore.getAuthHeaders() },
|
||||||
)
|
)
|
||||||
if (res.code === 0 || res.code === 200) {
|
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 limitPrice = ref(0.82) // 内部存储 0–1,显示为美分与按钮一致
|
||||||
const shares = ref(20) // 初始份额(正整数)
|
const shares = ref(20) // 初始份额(正整数)
|
||||||
const expirationTime = ref('5m') // 初始过期时间
|
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(() =>
|
const expirationOptions = computed(() =>
|
||||||
EXPIRATION_VALUES.map((v) => ({ title: t(`trade.expiration.${v}`), value: v })),
|
EXPIRATION_VALUES.map((v) => ({ title: t(`trade.expiration.${v}`), value: v })),
|
||||||
)
|
)
|
||||||
@ -1944,7 +1995,10 @@ const adjustShares = (amount: number) => {
|
|||||||
|
|
||||||
/** 卖出时输入份额是否超过最大可卖 */
|
/** 卖出时输入份额是否超过最大可卖 */
|
||||||
const sellSharesExceedsMax = computed(
|
const sellSharesExceedsMax = computed(
|
||||||
() => activeTab.value === 'sell' && maxAvailableShares.value >= 0 && shares.value > maxAvailableShares.value,
|
() =>
|
||||||
|
activeTab.value === 'sell' &&
|
||||||
|
maxAvailableShares.value >= 0 &&
|
||||||
|
shares.value > maxAvailableShares.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 份额百分比调整方法(仅在Sell模式下使用)
|
// 份额百分比调整方法(仅在Sell模式下使用)
|
||||||
@ -2058,12 +2112,7 @@ async function submitOrder() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const uid = userStore?.user?.ID ?? 0
|
const uid = userStore?.user?.ID ?? 0
|
||||||
const userIdNum =
|
const userIdNum = typeof uid === 'number' ? uid : uid != null ? parseInt(String(uid), 10) : 0
|
||||||
typeof uid === 'number'
|
|
||||||
? uid
|
|
||||||
: uid != null
|
|
||||||
? parseInt(String(uid), 10)
|
|
||||||
: 0
|
|
||||||
if (!Number.isFinite(userIdNum) || userIdNum <= 0) {
|
if (!Number.isFinite(userIdNum) || userIdNum <= 0) {
|
||||||
orderError.value = t('trade.userError')
|
orderError.value = t('trade.userError')
|
||||||
isNoAvailableSharesError.value = false
|
isNoAvailableSharesError.value = false
|
||||||
@ -2096,9 +2145,7 @@ async function submitOrder() {
|
|||||||
? parseExpirationTimestamp(expirationTime.value)
|
? parseExpirationTimestamp(expirationTime.value)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
const rawSize = isMarket && activeTab.value === 'buy'
|
const rawSize = isMarket && activeTab.value === 'buy' ? amount.value : clampShares(shares.value)
|
||||||
? amount.value
|
|
||||||
: clampShares(shares.value)
|
|
||||||
const sizeValue = Math.round(rawSize * 1_000_000)
|
const sizeValue = Math.round(rawSize * 1_000_000)
|
||||||
|
|
||||||
orderLoading.value = true
|
orderLoading.value = true
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
|
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
|
||||||
import { getUserWsUrl, BASE_URL } from '@/api/request'
|
import { getUserWsUrl, BASE_URL } from '@/api/request'
|
||||||
|
import { jsonInBlacklist } from '@/api/jwt'
|
||||||
import { UserSdk, type BalanceData, type PositionData } from '../../sdk/userSocket'
|
import { UserSdk, type BalanceData, type PositionData } from '../../sdk/userSocket'
|
||||||
|
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
@ -49,11 +50,7 @@ function parseUserId(raw: { id?: number | string; ID?: number } | null | undefin
|
|||||||
} {
|
} {
|
||||||
const rawId = raw?.ID ?? raw?.id
|
const rawId = raw?.ID ?? raw?.id
|
||||||
const numId =
|
const numId =
|
||||||
typeof rawId === 'number'
|
typeof rawId === 'number' ? rawId : rawId != null ? parseInt(String(rawId), 10) : undefined
|
||||||
? rawId
|
|
||||||
: rawId != null
|
|
||||||
? parseInt(String(rawId), 10)
|
|
||||||
: undefined
|
|
||||||
return { id: rawId ?? numId, numId: Number.isFinite(numId) ? numId : undefined }
|
return { id: rawId ?? numId, numId: Number.isFinite(numId) ? numId : undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +117,9 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 订阅 position_update 推送,返回取消订阅函数 */
|
/** 订阅 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)
|
positionUpdateCallbacks.push(cb)
|
||||||
return () => {
|
return () => {
|
||||||
const i = positionUpdateCallbacks.indexOf(cb)
|
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()
|
disconnectUserSocket()
|
||||||
token.value = ''
|
token.value = ''
|
||||||
user.value = null
|
user.value = null
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<div v-if="cryptoChartLoading" class="chart-loading-overlay">
|
<div v-if="cryptoChartLoading || chartYesNoLoading" class="chart-loading-overlay">
|
||||||
<v-progress-circular indeterminate color="primary" size="32" />
|
<v-progress-circular indeterminate color="primary" size="32" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="chartContainerRef" class="chart-container"></div>
|
<div ref="chartContainerRef" class="chart-container"></div>
|
||||||
@ -398,12 +398,11 @@ import {
|
|||||||
type OpenOrderDisplayItem,
|
type OpenOrderDisplayItem,
|
||||||
} from '../api/order'
|
} from '../api/order'
|
||||||
import { cancelOrder as apiCancelOrder } from '../api/order'
|
import { cancelOrder as apiCancelOrder } from '../api/order'
|
||||||
|
import type { ChartDataPoint, ChartTimeRange } from '../api/chart'
|
||||||
import {
|
import {
|
||||||
normalizeChartData,
|
getPmPriceHistoryPublic,
|
||||||
fetchChartHistory,
|
priceHistoryToChartData,
|
||||||
type ChartDataPoint,
|
} from '../api/priceHistory'
|
||||||
type ChartTimeRange,
|
|
||||||
} from '../api/chart'
|
|
||||||
import {
|
import {
|
||||||
isCryptoEvent as checkIsCryptoEvent,
|
isCryptoEvent as checkIsCryptoEvent,
|
||||||
inferCryptoSymbol,
|
inferCryptoSymbol,
|
||||||
@ -1242,52 +1241,47 @@ const timeRanges = [
|
|||||||
{ label: 'ALL', value: 'ALL' },
|
{ label: 'ALL', value: 'ALL' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 图表数据格式:[时间戳(ms), 概率(0-100)][]。接口约定见 src/api/chart.ts(ChartHistoryParams / ChartHistoryItem / normalizeChartData)
|
|
||||||
function generateData(range: string): ChartDataPoint[] {
|
|
||||||
const now = Date.now()
|
|
||||||
const data: [number, number][] = []
|
|
||||||
let stepMs: number
|
|
||||||
let count: number
|
|
||||||
switch (range) {
|
|
||||||
case '1H':
|
|
||||||
stepMs = 60 * 1000
|
|
||||||
count = 60
|
|
||||||
break
|
|
||||||
case '6H':
|
|
||||||
stepMs = 10 * 60 * 1000
|
|
||||||
count = 36
|
|
||||||
break
|
|
||||||
case '1D':
|
|
||||||
stepMs = 60 * 60 * 1000
|
|
||||||
count = 24
|
|
||||||
break
|
|
||||||
case '1W':
|
|
||||||
stepMs = 24 * 60 * 60 * 1000
|
|
||||||
count = 7
|
|
||||||
break
|
|
||||||
case '1M':
|
|
||||||
case 'ALL':
|
|
||||||
stepMs = 24 * 60 * 60 * 1000
|
|
||||||
count = 30
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
stepMs = 60 * 60 * 1000
|
|
||||||
count = 24
|
|
||||||
}
|
|
||||||
let value = 15 + Math.random() * 25
|
|
||||||
for (let i = count; i >= 0; i--) {
|
|
||||||
const t = now - i * stepMs
|
|
||||||
value = Math.max(10, Math.min(90, value + (Math.random() - 0.5) * 6))
|
|
||||||
data.push([t, Math.round(value * 10) / 10])
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||||
const data = ref<[number, number][]>([])
|
const data = ref<[number, number][]>([])
|
||||||
|
/** Yes/No 折线图:接口返回的完整数据(time 已转 ms),用于分时筛选 */
|
||||||
|
const rawChartData = ref<ChartDataPoint[]>([])
|
||||||
const cryptoChartLoading = ref(false)
|
const cryptoChartLoading = ref(false)
|
||||||
|
const chartYesNoLoading = ref(false)
|
||||||
let chartInstance: ECharts | null = null
|
let chartInstance: ECharts | null = null
|
||||||
let dynamicInterval: number | undefined
|
|
||||||
|
/** 分时范围对应的毫秒数,ALL 返回 null 表示不截断 */
|
||||||
|
function getTimeRangeMs(range: string): number | null {
|
||||||
|
const H = 60 * 60 * 1000
|
||||||
|
const D = 24 * H
|
||||||
|
switch (range) {
|
||||||
|
case '1H':
|
||||||
|
return 1 * H
|
||||||
|
case '6H':
|
||||||
|
return 6 * H
|
||||||
|
case '1D':
|
||||||
|
return 1 * D
|
||||||
|
case '1W':
|
||||||
|
return 7 * D
|
||||||
|
case '1M':
|
||||||
|
return 30 * D
|
||||||
|
case 'ALL':
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按分时范围过滤 [timestamp_ms, value][],保留区间 [now - rangeMs, now] 内的点 */
|
||||||
|
function filterChartDataByRange(
|
||||||
|
points: ChartDataPoint[],
|
||||||
|
range: string,
|
||||||
|
): ChartDataPoint[] {
|
||||||
|
if (!points.length) return []
|
||||||
|
const rangeMs = getTimeRangeMs(range)
|
||||||
|
if (rangeMs == null) return points
|
||||||
|
const nowMs = Date.now()
|
||||||
|
const cutoffMs = nowMs - rangeMs
|
||||||
|
return points.filter(([ts]) => ts >= cutoffMs)
|
||||||
|
}
|
||||||
|
|
||||||
const currentChance = computed(() => {
|
const currentChance = computed(() => {
|
||||||
const ev = eventDetail.value
|
const ev = eventDetail.value
|
||||||
@ -1523,7 +1517,7 @@ function buildOptionForCrypto(
|
|||||||
|
|
||||||
function initChart() {
|
function initChart() {
|
||||||
if (!chartContainerRef.value) return
|
if (!chartContainerRef.value) return
|
||||||
data.value = generateData(selectedTimeRange.value)
|
data.value = []
|
||||||
chartInstance = echarts.init(chartContainerRef.value)
|
chartInstance = echarts.init(chartContainerRef.value)
|
||||||
const w = chartContainerRef.value.clientWidth
|
const w = chartContainerRef.value.clientWidth
|
||||||
if (chartMode.value === 'crypto') {
|
if (chartMode.value === 'crypto') {
|
||||||
@ -1533,12 +1527,15 @@ function initChart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从接口拉取图表数据(接入时在 updateChartData 中调用并赋给 data.value) */
|
/** 从 GET /pmPriceHistory/getPmPriceHistoryPublic 拉取价格历史,market 传 YES 对应的 clobTokenId */
|
||||||
async function loadChartFromApi(marketId: string): Promise<ChartDataPoint[]> {
|
async function loadChartFromApi(marketParam: string): Promise<ChartDataPoint[]> {
|
||||||
const res = await fetchChartHistory(
|
const res = await getPmPriceHistoryPublic({
|
||||||
{ marketID: marketId, range: selectedTimeRange.value as ChartTimeRange }
|
market: marketParam,
|
||||||
)
|
page: 1,
|
||||||
return normalizeChartData(res.data ?? [])
|
pageSize: 500,
|
||||||
|
})
|
||||||
|
const list = res.data?.list ?? []
|
||||||
|
return priceHistoryToChartData(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MINUTE_MS = 60 * 1000
|
const MINUTE_MS = 60 * 1000
|
||||||
@ -1566,7 +1563,6 @@ function applyCryptoRealtimePoint(point: [number, number]) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用接口时:在 updateChartData 内先 await loadChartFromApi(marketId),再 setOption;暂无接口时用 generateData
|
|
||||||
async function updateChartData() {
|
async function updateChartData() {
|
||||||
const w = chartContainerRef.value?.clientWidth
|
const w = chartContainerRef.value?.clientWidth
|
||||||
if (chartMode.value === 'crypto') {
|
if (chartMode.value === 'crypto') {
|
||||||
@ -1592,60 +1588,35 @@ async function updateChartData() {
|
|||||||
} else {
|
} else {
|
||||||
cryptoWsUnsubscribe?.()
|
cryptoWsUnsubscribe?.()
|
||||||
cryptoWsUnsubscribe = null
|
cryptoWsUnsubscribe = null
|
||||||
data.value = generateData(selectedTimeRange.value)
|
chartYesNoLoading.value = true
|
||||||
|
try {
|
||||||
|
// 价格历史接口的 market 传 clobTokenIds[0](YES 对应 token ID)
|
||||||
|
const yesTokenId = clobTokenIds.value[0]
|
||||||
|
const points = yesTokenId ? await loadChartFromApi(yesTokenId) : []
|
||||||
|
rawChartData.value = points
|
||||||
|
data.value = filterChartDataByRange(points, selectedTimeRange.value)
|
||||||
if (chartInstance)
|
if (chartInstance)
|
||||||
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
||||||
|
} finally {
|
||||||
|
chartYesNoLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectTimeRange(range: string) {
|
function selectTimeRange(range: string) {
|
||||||
selectedTimeRange.value = range
|
selectedTimeRange.value = range
|
||||||
updateChartData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMaxPoints(range: string): number {
|
watch(selectedTimeRange, (range) => {
|
||||||
switch (range) {
|
if (chartMode.value === 'yesno') {
|
||||||
case '1H':
|
data.value = filterChartDataByRange(rawChartData.value, range)
|
||||||
return 60
|
|
||||||
case '6H':
|
|
||||||
return 36
|
|
||||||
case '1D':
|
|
||||||
return 24
|
|
||||||
case '1W':
|
|
||||||
return 7
|
|
||||||
case '1M':
|
|
||||||
case 'ALL':
|
|
||||||
return 30
|
|
||||||
default:
|
|
||||||
return 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startDynamicUpdate() {
|
|
||||||
dynamicInterval = window.setInterval(() => {
|
|
||||||
if (chartMode.value === 'crypto') return
|
|
||||||
const list = [...data.value]
|
|
||||||
const last = list[list.length - 1]
|
|
||||||
if (!last) return
|
|
||||||
const nextVal = Math.max(10, Math.min(90, last[1] + (Math.random() - 0.5) * 4))
|
|
||||||
const nextT = Date.now()
|
|
||||||
list.push([nextT, Math.round(nextVal * 10) / 10])
|
|
||||||
const max = getMaxPoints(selectedTimeRange.value)
|
|
||||||
data.value = list.slice(-max)
|
|
||||||
const w = chartContainerRef.value?.clientWidth
|
const w = chartContainerRef.value?.clientWidth
|
||||||
if (chartInstance)
|
if (chartInstance)
|
||||||
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
||||||
}, 3000)
|
} else {
|
||||||
|
updateChartData()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
function stopDynamicUpdate() {
|
|
||||||
if (dynamicInterval) {
|
|
||||||
clearInterval(dynamicInterval)
|
|
||||||
dynamicInterval = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(selectedTimeRange, () => updateChartData())
|
|
||||||
|
|
||||||
// CLOB:当有 market 且存在 clobTokenIds 时连接(使用 Yes/No token ID)
|
// CLOB:当有 market 且存在 clobTokenIds 时连接(使用 Yes/No token ID)
|
||||||
const clobTokenIds = computed(() => {
|
const clobTokenIds = computed(() => {
|
||||||
@ -1711,15 +1682,13 @@ const unsubscribePositionUpdate = userStore.onPositionUpdate((data) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadEventDetail()
|
loadEventDetail().then(() => updateChartData())
|
||||||
initChart()
|
initChart()
|
||||||
startDynamicUpdate()
|
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
unsubscribePositionUpdate()
|
unsubscribePositionUpdate()
|
||||||
stopDynamicUpdate()
|
|
||||||
cryptoWsUnsubscribe?.()
|
cryptoWsUnsubscribe?.()
|
||||||
cryptoWsUnsubscribe = null
|
cryptoWsUnsubscribe = null
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
|
|||||||
@ -735,7 +735,8 @@ 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, mapOrderToHistoryItem, mapOrderToOpenOrderItem, OrderStatus } from '../api/order'
|
import { getOrderList, mapOrderToOpenOrderItem, OrderStatus } from '../api/order'
|
||||||
|
import { getHistoryRecordListClient, getHistoryRecordList } from '../api/historyRecord'
|
||||||
import { getPositionList, mapPositionToDisplayItem, claimPosition } from '../api/position'
|
import { getPositionList, mapPositionToDisplayItem, claimPosition } from '../api/position'
|
||||||
import {
|
import {
|
||||||
getSettlementRequestsListClient,
|
getSettlementRequestsListClient,
|
||||||
@ -780,7 +781,7 @@ const withdrawStatusOptions = computed(() => [
|
|||||||
const currentPositionList = computed(() =>
|
const currentPositionList = computed(() =>
|
||||||
USE_MOCK_WALLET ? positions.value : positionList.value,
|
USE_MOCK_WALLET ? positions.value : positionList.value,
|
||||||
)
|
)
|
||||||
/** 未结算项:从持仓列表中筛出可领取的(有 marketID+tokenID;若后端有 needClaim 则仅 needClaim 为 true) */
|
/** 未结算项:从持仓列表中筛出可领取的(有 marketID+tokenID,且所属市场已关闭 market.closed=true) */
|
||||||
const unsettledItems = computed(() => {
|
const unsettledItems = computed(() => {
|
||||||
const list = currentPositionList.value
|
const list = currentPositionList.value
|
||||||
return list
|
return list
|
||||||
@ -788,7 +789,7 @@ const unsettledItems = computed(() => {
|
|||||||
(p) =>
|
(p) =>
|
||||||
p.marketID &&
|
p.marketID &&
|
||||||
p.tokenID &&
|
p.tokenID &&
|
||||||
(p.needClaim === undefined || p.needClaim === true),
|
p.marketClosed === true,
|
||||||
)
|
)
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
const amount = parseFloat(String(p.value).replace(/[^0-9.-]/g, '')) || 0
|
const amount = parseFloat(String(p.value).replace(/[^0-9.-]/g, '')) || 0
|
||||||
@ -872,8 +873,8 @@ interface Position {
|
|||||||
marketID?: string
|
marketID?: string
|
||||||
/** Token ID(从持仓列表来,用于领取结算) */
|
/** Token ID(从持仓列表来,用于领取结算) */
|
||||||
tokenID?: string
|
tokenID?: string
|
||||||
/** 是否待领取/未结算(后端可选,无则按有 marketID+tokenID 视为可领取) */
|
/** 所属市场是否已关闭,marketClosed=true 表示可结算/可领取 */
|
||||||
needClaim?: boolean
|
marketClosed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从 avgNow "72¢ → 0.5¢" 解析出 [avg, now] */
|
/** 从 avgNow "72¢ → 0.5¢" 解析出 [avg, now] */
|
||||||
@ -1030,6 +1031,7 @@ const historyList = ref<HistoryItem[]>([])
|
|||||||
const historyTotal = ref(0)
|
const historyTotal = ref(0)
|
||||||
const historyLoading = ref(false)
|
const historyLoading = ref(false)
|
||||||
|
|
||||||
|
/** 历史记录来自 GET /hr/getHistoryRecordListClient(需鉴权,按当前用户分页) */
|
||||||
async function loadHistoryOrders() {
|
async function loadHistoryOrders() {
|
||||||
if (USE_MOCK_WALLET) return
|
if (USE_MOCK_WALLET) return
|
||||||
const headers = userStore.getAuthHeaders()
|
const headers = userStore.getAuthHeaders()
|
||||||
@ -1047,18 +1049,18 @@ async function loadHistoryOrders() {
|
|||||||
}
|
}
|
||||||
historyLoading.value = true
|
historyLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getOrderList(
|
const res = await getHistoryRecordListClient(
|
||||||
{
|
{
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: itemsPerPage.value,
|
pageSize: itemsPerPage.value,
|
||||||
userID,
|
userId: userID,
|
||||||
},
|
},
|
||||||
{ headers },
|
{ headers },
|
||||||
)
|
)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
const list = res.data?.list ?? []
|
const { list, total } = getHistoryRecordList(res.data)
|
||||||
historyList.value = list.map(mapOrderToHistoryItem)
|
historyList.value = list
|
||||||
historyTotal.value = res.data?.total ?? 0
|
historyTotal.value = total
|
||||||
} else {
|
} else {
|
||||||
historyList.value = []
|
historyList.value = []
|
||||||
historyTotal.value = 0
|
historyTotal.value = 0
|
||||||
@ -1346,8 +1348,11 @@ function shareHistory(id: string) {
|
|||||||
const plChartRef = ref<HTMLElement | null>(null)
|
const plChartRef = ref<HTMLElement | null>(null)
|
||||||
let plChartInstance: ECharts | null = null
|
let plChartInstance: ECharts | null = null
|
||||||
|
|
||||||
/** 根据时间范围生成盈亏折线数据 [timestamp, pnl] */
|
/**
|
||||||
function generatePlData(range: string): [number, number][] {
|
* 资产变化折线图数据 [timestamp_ms, pnl]
|
||||||
|
* 暂无接口时返回真实格式的时间序列,数值均为 0;有接口后改为从 API 拉取并在此处做分时过滤
|
||||||
|
*/
|
||||||
|
function getPlChartData(range: string): [number, number][] {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
let stepMs: number
|
let stepMs: number
|
||||||
let count: number
|
let count: number
|
||||||
@ -1371,11 +1376,9 @@ function generatePlData(range: string): [number, number][] {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
const data: [number, number][] = []
|
const data: [number, number][] = []
|
||||||
let pnl = 0
|
|
||||||
for (let i = count; i >= 0; i--) {
|
for (let i = count; i >= 0; i--) {
|
||||||
const t = now - i * stepMs
|
const t = now - i * stepMs
|
||||||
pnl += (Math.random() - 0.48) * 20
|
data.push([t, 0])
|
||||||
data.push([t, Math.round(pnl * 100) / 100])
|
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@ -1437,18 +1440,18 @@ function buildPlChartOption(chartData: [number, number][]) {
|
|||||||
const plChartData = ref<[number, number][]>([])
|
const plChartData = ref<[number, number][]>([])
|
||||||
|
|
||||||
function updatePlChart() {
|
function updatePlChart() {
|
||||||
plChartData.value = generatePlData(plRange.value)
|
plChartData.value = getPlChartData(plRange.value)
|
||||||
const last = plChartData.value[plChartData.value.length - 1]
|
const last = plChartData.value[plChartData.value.length - 1]
|
||||||
if (last) profitLoss.value = last[1].toFixed(2)
|
if (last != null) profitLoss.value = last[1].toFixed(2)
|
||||||
if (plChartInstance)
|
if (plChartInstance)
|
||||||
plChartInstance.setOption(buildPlChartOption(plChartData.value), { replaceMerge: ['series'] })
|
plChartInstance.setOption(buildPlChartOption(plChartData.value), { replaceMerge: ['series'] })
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPlChart() {
|
function initPlChart() {
|
||||||
if (!plChartRef.value) return
|
if (!plChartRef.value) return
|
||||||
plChartData.value = generatePlData(plRange.value)
|
plChartData.value = getPlChartData(plRange.value)
|
||||||
const last = plChartData.value[plChartData.value.length - 1]
|
const last = plChartData.value[plChartData.value.length - 1]
|
||||||
if (last) profitLoss.value = last[1].toFixed(2)
|
if (last != null) profitLoss.value = last[1].toFixed(2)
|
||||||
plChartInstance = echarts.init(plChartRef.value)
|
plChartInstance = echarts.init(plChartRef.value)
|
||||||
plChartInstance.setOption(buildPlChartOption(plChartData.value))
|
plChartInstance.setOption(buildPlChartOption(plChartData.value))
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user