新增:持仓数据接口对接
This commit is contained in:
parent
38d70e3e56
commit
3e329c307e
@ -16,19 +16,38 @@ description: Interprets the XTrader API from the Swagger 2.0 spec at https://api
|
||||
### 强制动作序列(必须依次执行)
|
||||
|
||||
1. **收到对接请求** → 立即用 `mcp_web_fetch` 或 `curl` 获取 `https://api.xtrader.vip/swagger/doc.json`
|
||||
2. **第一步** → 解析 `paths["<path>"]["<method>"]` 与 `definitions`,**在对话中输出**:
|
||||
2. **检查接口是否存在** → 若 `paths["<path>"]` 或 `paths["<path>"]["<method>"]` **不存在**,则:
|
||||
- ❌ **禁止**自行猜测或虚构数据结构并自动对接
|
||||
- ✅ **必须**在对话中明确告知用户「该接口在 doc.json 中不存在」
|
||||
- ✅ **必须**向用户询问:
|
||||
- 是否仍要对接?若对接,请提供**请求参数**与**响应数据结构**(或示例 JSON)
|
||||
- 用户可选择**不对接**,则流程终止
|
||||
- 仅在用户明确提供数据结构或确认对接后,才可进入第二步
|
||||
3. **第一步**(接口存在时)→ 解析 `paths["<path>"]["<method>"]` 与 `definitions`,**在对话中输出**:
|
||||
- 请求参数表(Query/Body/鉴权)
|
||||
- 响应参数表(200 schema)
|
||||
- data 的 `$ref` 对应 definitions 的**完整字段表**
|
||||
- 输出后**明确标注「第一步完成」**
|
||||
3. **第二步** → 在 `src/api/` 中根据第一步表格定义 TypeScript 类型,**不得在第一步完成前执行**
|
||||
4. **第三步** → 实现请求函数并集成到页面,**不得在第二步完成前执行**
|
||||
4. **第二步** → 在 `src/api/` 中根据第一步表格(或用户提供的数据结构)定义 TypeScript 类型,**不得在第一步完成前执行**
|
||||
5. **第三步** → 实现请求函数并集成到页面,**不得在第二步完成前执行**
|
||||
|
||||
### 禁止行为
|
||||
|
||||
- ❌ 在对话中输出第一步结果**之前**写任何 `src/api/` 或 `src/views/` 业务代码
|
||||
- ❌ 跳过第一步直接定义类型或实现请求
|
||||
- ❌ 合并步骤(如边输出边写代码)
|
||||
- ❌ **接口文档不存在时**:自行猜测数据结构并自动对接;必须向用户询问后再决定是否对接
|
||||
|
||||
### 接口文档不存在时的处理流程
|
||||
|
||||
当 `paths["<path>"]` 在 doc.json 中**不存在**时:
|
||||
|
||||
1. **立即停止**:不写任何对接代码,不猜测数据结构
|
||||
2. **明确告知**:在对话中说明「该接口在 Swagger doc.json 中不存在」
|
||||
3. **向用户询问**:
|
||||
- 是否仍要对接?若对接,请提供请求参数与响应数据结构(或示例 JSON)
|
||||
- 用户可选择**不对接**,则流程终止
|
||||
4. **仅在用户明确提供**后,才继续执行第二步、第三步
|
||||
|
||||
### 第一步输出模板(必须包含)
|
||||
|
||||
@ -163,6 +182,7 @@ Swagger UI 页面(如 [PmEvent findPmEvent](https://api.xtrader.vip/swagger/in
|
||||
|
||||
## 简要检查清单
|
||||
|
||||
- [ ] **接口存在性**:若 doc.json 中无该 path,已向用户询问数据结构,未擅自猜测对接
|
||||
- [ ] **按规范顺序**:已先列出请求参数与响应参数,再建 Model,最后集成到页面
|
||||
- [ ] 规范 URL 使用 `https://api.xtrader.vip/swagger/doc.json`,或本地缓存与之一致
|
||||
- [ ] 请求 path、method、query/body 与 `paths` 一致
|
||||
|
||||
@ -38,12 +38,21 @@ export interface OrderListResponse {
|
||||
msg: string
|
||||
}
|
||||
|
||||
/** 订单状态:1=未成交(Live),2=已成交,0=已取消等 */
|
||||
export const OrderStatus = {
|
||||
Live: 1,
|
||||
Matched: 2,
|
||||
Cancelled: 0,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* GET /clob/order/getOrderList 请求参数
|
||||
*/
|
||||
export interface GetOrderListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
/** 订单状态筛选:1=未成交 */
|
||||
status?: number
|
||||
startCreatedAt?: string
|
||||
endCreatedAt?: string
|
||||
marketID?: string
|
||||
@ -60,8 +69,10 @@ export async function getOrderList(
|
||||
params: GetOrderListParams = {},
|
||||
config?: { headers?: Record<string, string> },
|
||||
): Promise<OrderListResponse> {
|
||||
const { page = 1, pageSize = 10, startCreatedAt, endCreatedAt, marketID, tokenID, userID } = params
|
||||
const { page = 1, pageSize = 10, status, startCreatedAt, endCreatedAt, marketID, tokenID, userID } =
|
||||
params
|
||||
const query: Record<string, string | number | undefined> = { page, pageSize }
|
||||
if (status != null && Number.isFinite(status)) query.status = status
|
||||
if (startCreatedAt != null && startCreatedAt !== '') query.startCreatedAt = startCreatedAt
|
||||
if (endCreatedAt != null && endCreatedAt !== '') query.endCreatedAt = endCreatedAt
|
||||
if (marketID != null && marketID !== '') query.marketID = marketID
|
||||
@ -136,3 +147,59 @@ export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem
|
||||
shares: String(size),
|
||||
}
|
||||
}
|
||||
|
||||
/** 钱包 Open Orders 展示项(与 Wallet.vue OpenOrder 一致) */
|
||||
export interface OpenOrderDisplayItem {
|
||||
id: string
|
||||
market: string
|
||||
side: 'Yes' | 'No'
|
||||
outcome: string
|
||||
price: string
|
||||
filled: string
|
||||
total: string
|
||||
expiration: string
|
||||
actionLabel?: string
|
||||
filledDisplay?: string
|
||||
orderID?: number
|
||||
tokenID?: string
|
||||
}
|
||||
|
||||
/** OrderType GTC=0 表示 Until Cancelled */
|
||||
const OrderType = { GTC: 0, GTD: 1 } as const
|
||||
|
||||
/**
|
||||
* 将 ClobOrderItem 映射为钱包 Open Orders 展示项(未成交订单)
|
||||
* price 为整数(已乘 10000)
|
||||
*/
|
||||
export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem {
|
||||
const id = String(order.ID ?? '')
|
||||
const market = order.market ?? ''
|
||||
const sideNum = order.side ?? Side.Buy
|
||||
const side = sideNum === Side.Sell ? 'No' : 'Yes'
|
||||
const outcome = order.outcome || (side === 'Yes' ? 'Yes' : 'No')
|
||||
const priceBps = order.price ?? 0
|
||||
const priceCents = Math.round(priceBps / 100)
|
||||
const price = `${priceCents}¢`
|
||||
const originalSize = order.originalSize ?? 0
|
||||
const sizeMatched = order.sizeMatched ?? 0
|
||||
const filled = `${sizeMatched}/${originalSize}`
|
||||
const totalUsd = (priceBps / 10000) * originalSize
|
||||
const total = `$${totalUsd.toFixed(2)}`
|
||||
const expiration =
|
||||
order.orderType === OrderType.GTC ? 'Until Cancelled' : order.expiration?.toString() ?? ''
|
||||
const actionLabel = sideNum === Side.Buy ? `Buy ${outcome}` : `Sell ${outcome}`
|
||||
return {
|
||||
id,
|
||||
market,
|
||||
side,
|
||||
outcome,
|
||||
price,
|
||||
filled,
|
||||
total,
|
||||
expiration,
|
||||
actionLabel,
|
||||
filledDisplay: filled,
|
||||
orderID: order.ID,
|
||||
tokenID: order.assetID,
|
||||
}
|
||||
}
|
||||
|
||||
122
src/api/position.ts
Normal file
122
src/api/position.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { get } from './request'
|
||||
|
||||
/** 分页结果 */
|
||||
export interface PageResult<T> {
|
||||
list: T[]
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 持仓项(与 doc.json definitions["model.ClobPosition"] 对齐)
|
||||
* GET /clob/position/getPositionList 列表项
|
||||
*/
|
||||
export interface ClobPositionItem {
|
||||
ID?: number
|
||||
available?: number
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
marketID?: string
|
||||
side?: number
|
||||
size?: number
|
||||
tokenId?: string
|
||||
userID?: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
/** 持仓列表响应 */
|
||||
export interface PositionListResponse {
|
||||
code: number
|
||||
data: PageResult<ClobPositionItem>
|
||||
msg: string
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /clob/position/getPositionList 请求参数
|
||||
*/
|
||||
export interface GetPositionListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
startCreatedAt?: string
|
||||
endCreatedAt?: string
|
||||
marketID?: string
|
||||
tokenID?: string
|
||||
userID?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取持仓列表
|
||||
* GET /clob/position/getPositionList
|
||||
* 需鉴权:x-token、x-user-id
|
||||
*/
|
||||
export async function getPositionList(
|
||||
params: GetPositionListParams = {},
|
||||
config?: { headers?: Record<string, string> },
|
||||
): Promise<PositionListResponse> {
|
||||
const { page = 1, pageSize = 10, startCreatedAt, endCreatedAt, marketID, tokenID, userID } = params
|
||||
const query: Record<string, string | number | undefined> = { page, pageSize }
|
||||
if (startCreatedAt != null && startCreatedAt !== '') query.startCreatedAt = startCreatedAt
|
||||
if (endCreatedAt != null && endCreatedAt !== '') query.endCreatedAt = endCreatedAt
|
||||
if (marketID != null && marketID !== '') query.marketID = marketID
|
||||
if (tokenID != null && tokenID !== '') query.tokenID = tokenID
|
||||
if (userID != null && Number.isFinite(userID)) query.userID = userID
|
||||
return get<PositionListResponse>('/clob/position/getPositionList', query, config)
|
||||
}
|
||||
|
||||
/** 钱包 Positions 展示项(与 Wallet.vue Position 一致) */
|
||||
export interface PositionDisplayItem {
|
||||
id: string
|
||||
market: string
|
||||
shares: string
|
||||
avgNow: string
|
||||
bet: string
|
||||
toWin: string
|
||||
value: string
|
||||
valueChange?: string
|
||||
valueChangePct?: string
|
||||
valueChangeLoss?: boolean
|
||||
sellOutcome?: string
|
||||
outcomeWord?: string
|
||||
iconChar?: string
|
||||
iconClass?: string
|
||||
outcomeTag?: string
|
||||
outcomePillClass?: string
|
||||
}
|
||||
|
||||
/** Side: Buy=1, Sell=2 */
|
||||
const Side = { Buy: 1, Sell: 2 } as const
|
||||
|
||||
/**
|
||||
* 将 ClobPositionItem 映射为钱包 Position 展示项
|
||||
* model.ClobPosition 仅有 size、available、marketID、side,无价格字段,部分展示用占位
|
||||
*/
|
||||
export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplayItem {
|
||||
const id = String(pos.ID ?? '')
|
||||
const market = pos.marketID ?? ''
|
||||
const size = pos.size ?? pos.available ?? 0
|
||||
const shares = `${size} shares`
|
||||
const sideNum = pos.side ?? Side.Buy
|
||||
const outcomeWord = sideNum === Side.Sell ? 'No' : 'Yes'
|
||||
const pillClass = sideNum === Side.Sell ? 'pill-down' : 'pill-yes'
|
||||
const sellOutcome = outcomeWord
|
||||
const valueUsd = size
|
||||
const value = `$${valueUsd.toFixed(2)}`
|
||||
const bet = value
|
||||
const toWin = `$${valueUsd.toFixed(2)}`
|
||||
const avgNow = '—'
|
||||
const outcomeTag = `${outcomeWord} —`
|
||||
return {
|
||||
id,
|
||||
market,
|
||||
shares,
|
||||
avgNow,
|
||||
bet,
|
||||
toWin,
|
||||
value,
|
||||
sellOutcome,
|
||||
outcomeWord,
|
||||
outcomeTag,
|
||||
outcomePillClass: pillClass,
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,49 @@ import { get } from './request'
|
||||
const USDC_DECIMALS = 1_000_000
|
||||
|
||||
/**
|
||||
* getUserInfo 返回的 data(definitions system.SysUser)
|
||||
* doc.json definitions["system.SysUser"] 完整字段
|
||||
* getUserInfo 返回的 data 结构(2026 更新)
|
||||
* data 包含 balance、orders、positions、userInfo
|
||||
*/
|
||||
|
||||
/** 余额项(data.balance) */
|
||||
export interface UserInfoBalance {
|
||||
ID?: number
|
||||
CreatedAt?: string
|
||||
UpdatedAt?: string
|
||||
userID?: number
|
||||
marketID?: string
|
||||
tokenType?: string
|
||||
tokenID?: string
|
||||
amount?: string
|
||||
available?: string
|
||||
locked?: string
|
||||
version?: number
|
||||
}
|
||||
|
||||
/** 订单项(data.orders[]) */
|
||||
export interface UserInfoOrder {
|
||||
ID?: number
|
||||
CreatedAt?: string
|
||||
UpdatedAt?: string
|
||||
userID?: number
|
||||
market?: string
|
||||
status?: number
|
||||
assetID?: string
|
||||
side?: number
|
||||
price?: number
|
||||
originalSize?: number
|
||||
sizeMatched?: number
|
||||
outcome?: string
|
||||
expiration?: number
|
||||
orderType?: number
|
||||
feeRateBps?: number
|
||||
}
|
||||
|
||||
/** 用户信息(data.userInfo) */
|
||||
export interface UserInfoData {
|
||||
ID?: number
|
||||
CreatedAt?: string
|
||||
UpdatedAt?: string
|
||||
userName?: string
|
||||
nickName?: string
|
||||
headerImg?: string
|
||||
@ -15,8 +53,6 @@ export interface UserInfoData {
|
||||
authorityId?: number
|
||||
authority?: unknown
|
||||
authorities?: unknown[]
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
enable?: number
|
||||
@ -26,12 +62,20 @@ export interface UserInfoData {
|
||||
promotionCode?: string
|
||||
puserId?: number
|
||||
remark?: string
|
||||
originSetting?: Record<string, unknown>
|
||||
originSetting?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/** getUserInfo 完整 data */
|
||||
export interface GetUserInfoData {
|
||||
balance?: UserInfoBalance
|
||||
orders?: UserInfoOrder[]
|
||||
positions?: unknown[]
|
||||
userInfo?: UserInfoData
|
||||
}
|
||||
|
||||
export interface GetUserInfoResponse {
|
||||
code: number
|
||||
data?: UserInfoData
|
||||
data?: GetUserInfoData
|
||||
msg?: string
|
||||
}
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
reconnectInterval: 2000,
|
||||
})
|
||||
sdk.onBalanceUpdate((data: BalanceData) => {
|
||||
if ((data.tokenType ?? '').toUpperCase() !== 'USDC') return
|
||||
const avail = data.available ?? data.amount
|
||||
if (avail != null) {
|
||||
balance.value = formatUsdcBalance(String(avail))
|
||||
@ -151,19 +152,22 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求用户信息(需已登录),更新 store 中的 user */
|
||||
/** 请求用户信息(需已登录),更新 store 中的 user 与 balance */
|
||||
async function fetchUserInfo() {
|
||||
const headers = getAuthHeaders()
|
||||
if (!headers) return
|
||||
try {
|
||||
const res = await getUserInfo(headers)
|
||||
const data = res.data as Record<string, unknown> | undefined
|
||||
// 接口返回 data.userInfo 或 data.user,取实际用户对象;若仍含 userInfo 则再取一层
|
||||
let u = (data?.userInfo ?? data?.user ?? data) as Record<string, unknown>
|
||||
if (u?.userInfo && (u.ID == null && u.id == null)) {
|
||||
u = u.userInfo as Record<string, unknown>
|
||||
if (res.code !== 0 && res.code !== 200) return
|
||||
// 更新余额:data.balance.available
|
||||
const bal = data?.balance as { available?: string } | undefined
|
||||
if (bal?.available != null) {
|
||||
balance.value = formatUsdcBalance(String(bal.available))
|
||||
}
|
||||
if ((res.code === 0 || res.code === 200) && u) {
|
||||
// 更新用户信息:data.userInfo
|
||||
const u = (data?.userInfo ?? data?.user ?? data) as Record<string, unknown> | undefined
|
||||
if (!u) return
|
||||
const rawId = u.ID ?? u.id
|
||||
const numId =
|
||||
typeof rawId === 'number'
|
||||
@ -180,7 +184,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
ID: Number.isFinite(numId) ? numId : undefined,
|
||||
} as UserInfo
|
||||
if (token.value && user.value) saveToStorage(token.value, user.value)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[fetchUserInfo] 请求失败:', e)
|
||||
}
|
||||
|
||||
@ -154,7 +154,10 @@
|
||||
<template v-if="activeTab === 'positions'">
|
||||
<!-- 移动端:可折叠列表 -->
|
||||
<div v-if="mobile" class="positions-mobile-list">
|
||||
<template v-if="filteredPositions.length === 0">
|
||||
<template v-if="positionLoading">
|
||||
<div class="empty-cell">{{ t('common.loading') }}</div>
|
||||
</template>
|
||||
<template v-else-if="filteredPositions.length === 0">
|
||||
<div class="empty-cell">{{ t('wallet.noPositionsFound') }}</div>
|
||||
</template>
|
||||
<div
|
||||
@ -256,7 +259,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="filteredPositions.length === 0">
|
||||
<tr v-if="positionLoading">
|
||||
<td colspan="6" class="empty-cell">{{ t('common.loading') }}</td>
|
||||
</tr>
|
||||
<tr v-else-if="filteredPositions.length === 0">
|
||||
<td colspan="6" class="empty-cell">{{ t('wallet.noPositionsFound') }}</td>
|
||||
</tr>
|
||||
<tr v-for="pos in paginatedPositions" :key="pos.id" class="position-row">
|
||||
@ -325,7 +331,10 @@
|
||||
<template v-else-if="activeTab === 'orders'">
|
||||
<!-- 移动端:挂单卡片列表 -->
|
||||
<div v-if="mobile" class="orders-mobile-list">
|
||||
<template v-if="filteredOpenOrders.length === 0">
|
||||
<template v-if="openOrderLoading">
|
||||
<div class="empty-cell">{{ t('common.loading') }}</div>
|
||||
</template>
|
||||
<template v-else-if="filteredOpenOrders.length === 0">
|
||||
<div class="empty-cell">{{ t('wallet.noOpenOrdersFound') }}</div>
|
||||
</template>
|
||||
<div v-for="ord in paginatedOpenOrders" :key="ord.id" class="order-mobile-card">
|
||||
@ -374,7 +383,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="filteredOpenOrders.length === 0">
|
||||
<tr v-if="openOrderLoading">
|
||||
<td colspan="8" class="empty-cell">{{ t('common.loading') }}</td>
|
||||
</tr>
|
||||
<tr v-else-if="filteredOpenOrders.length === 0">
|
||||
<td colspan="8" class="empty-cell">{{ t('wallet.noOpenOrdersFound') }}</td>
|
||||
</tr>
|
||||
<tr v-for="ord in paginatedOpenOrders" :key="ord.id">
|
||||
@ -615,7 +627,8 @@ import DepositDialog from '../components/DepositDialog.vue'
|
||||
import WithdrawDialog from '../components/WithdrawDialog.vue'
|
||||
import { useUserStore } from '../stores/user'
|
||||
import { pmCancelOrder } from '../api/market'
|
||||
import { getOrderList, mapOrderToHistoryItem } from '../api/order'
|
||||
import { getOrderList, mapOrderToHistoryItem, mapOrderToOpenOrderItem, OrderStatus } from '../api/order'
|
||||
import { getPositionList, mapPositionToDisplayItem } from '../api/position'
|
||||
import {
|
||||
MOCK_TOKEN_ID,
|
||||
MOCK_WALLET_POSITIONS,
|
||||
@ -724,9 +737,99 @@ interface HistoryItem {
|
||||
const positions = ref<Position[]>(
|
||||
USE_MOCK_WALLET ? [...MOCK_WALLET_POSITIONS] : [],
|
||||
)
|
||||
/** 持仓列表(API 数据,非 mock 时使用) */
|
||||
const positionList = ref<Position[]>([])
|
||||
const positionTotal = ref(0)
|
||||
const positionLoading = ref(false)
|
||||
|
||||
async function loadPositionList() {
|
||||
if (USE_MOCK_WALLET) return
|
||||
const headers = userStore.getAuthHeaders()
|
||||
if (!headers) {
|
||||
positionList.value = []
|
||||
positionTotal.value = 0
|
||||
return
|
||||
}
|
||||
const uid = userStore.user?.id ?? userStore.user?.ID
|
||||
const userID = uid != null ? Number(uid) : undefined
|
||||
if (!userID || !Number.isFinite(userID)) {
|
||||
positionList.value = []
|
||||
positionTotal.value = 0
|
||||
return
|
||||
}
|
||||
positionLoading.value = true
|
||||
try {
|
||||
const res = await getPositionList(
|
||||
{ page: page.value, pageSize: itemsPerPage.value, userID },
|
||||
{ headers },
|
||||
)
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
const list = res.data?.list ?? []
|
||||
positionList.value = list.map(mapPositionToDisplayItem)
|
||||
positionTotal.value = res.data?.total ?? 0
|
||||
} else {
|
||||
positionList.value = []
|
||||
positionTotal.value = 0
|
||||
}
|
||||
} catch {
|
||||
positionList.value = []
|
||||
positionTotal.value = 0
|
||||
} finally {
|
||||
positionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openOrders = ref<OpenOrder[]>(
|
||||
USE_MOCK_WALLET ? [...MOCK_WALLET_ORDERS] : [],
|
||||
)
|
||||
/** 未成交订单(API 数据,非 mock 时使用) */
|
||||
const openOrderList = ref<OpenOrder[]>([])
|
||||
const openOrderTotal = ref(0)
|
||||
const openOrderLoading = ref(false)
|
||||
|
||||
async function loadOpenOrders() {
|
||||
if (USE_MOCK_WALLET) return
|
||||
const headers = userStore.getAuthHeaders()
|
||||
if (!headers) {
|
||||
openOrderList.value = []
|
||||
openOrderTotal.value = 0
|
||||
return
|
||||
}
|
||||
const uid = userStore.user?.id ?? userStore.user?.ID
|
||||
const userID = uid != null ? Number(uid) : undefined
|
||||
if (!userID || !Number.isFinite(userID)) {
|
||||
openOrderList.value = []
|
||||
openOrderTotal.value = 0
|
||||
return
|
||||
}
|
||||
openOrderLoading.value = true
|
||||
try {
|
||||
const res = await getOrderList(
|
||||
{
|
||||
page: page.value,
|
||||
pageSize: itemsPerPage.value,
|
||||
userID,
|
||||
status: OrderStatus.Live,
|
||||
},
|
||||
{ headers },
|
||||
)
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
const list = res.data?.list ?? []
|
||||
const openOnly = list.filter((o) => (o.status ?? 1) === OrderStatus.Live)
|
||||
openOrderList.value = openOnly.map(mapOrderToOpenOrderItem)
|
||||
openOrderTotal.value = openOnly.length
|
||||
} else {
|
||||
openOrderList.value = []
|
||||
openOrderTotal.value = 0
|
||||
}
|
||||
} catch {
|
||||
openOrderList.value = []
|
||||
openOrderTotal.value = 0
|
||||
} finally {
|
||||
openOrderLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const history = ref<HistoryItem[]>(
|
||||
USE_MOCK_WALLET ? [...MOCK_WALLET_HISTORY] : [],
|
||||
)
|
||||
@ -780,8 +883,14 @@ function matchSearch(text: string): boolean {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
return !q || text.toLowerCase().includes(q)
|
||||
}
|
||||
const filteredPositions = computed(() => positions.value.filter((p) => matchSearch(p.market)))
|
||||
const filteredOpenOrders = computed(() => openOrders.value.filter((o) => matchSearch(o.market)))
|
||||
const filteredPositions = computed(() => {
|
||||
const list = USE_MOCK_WALLET ? positions.value : positionList.value
|
||||
return list.filter((p) => matchSearch(p.market))
|
||||
})
|
||||
const filteredOpenOrders = computed(() => {
|
||||
const list = USE_MOCK_WALLET ? openOrders.value : openOrderList.value
|
||||
return list.filter((o) => matchSearch(o.market))
|
||||
})
|
||||
const filteredHistory = computed(() => {
|
||||
const list = USE_MOCK_WALLET ? history.value : historyList.value
|
||||
return list.filter((h) => matchSearch(h.market))
|
||||
@ -795,27 +904,37 @@ function paginate<T>(list: T[]) {
|
||||
const start = (page.value - 1) * itemsPerPage.value
|
||||
return list.slice(start, start + itemsPerPage.value)
|
||||
}
|
||||
const paginatedPositions = computed(() => paginate(filteredPositions.value))
|
||||
const paginatedOpenOrders = computed(() => paginate(filteredOpenOrders.value))
|
||||
const paginatedPositions = computed(() => {
|
||||
if (USE_MOCK_WALLET) return paginate(filteredPositions.value)
|
||||
return filteredPositions.value
|
||||
})
|
||||
const paginatedOpenOrders = computed(() => {
|
||||
if (USE_MOCK_WALLET) return paginate(filteredOpenOrders.value)
|
||||
return filteredOpenOrders.value
|
||||
})
|
||||
const paginatedHistory = computed(() => {
|
||||
if (USE_MOCK_WALLET) return paginate(filteredHistory.value)
|
||||
return filteredHistory.value
|
||||
})
|
||||
|
||||
const totalPagesPositions = computed(() =>
|
||||
Math.max(1, Math.ceil(filteredPositions.value.length / itemsPerPage.value)),
|
||||
)
|
||||
const totalPagesOrders = computed(() =>
|
||||
Math.max(1, Math.ceil(filteredOpenOrders.value.length / itemsPerPage.value)),
|
||||
)
|
||||
const totalPagesPositions = computed(() => {
|
||||
const total = USE_MOCK_WALLET ? filteredPositions.value.length : positionTotal.value
|
||||
return Math.max(1, Math.ceil(total / itemsPerPage.value))
|
||||
})
|
||||
const totalPagesOrders = computed(() => {
|
||||
const total = USE_MOCK_WALLET ? filteredOpenOrders.value.length : openOrderTotal.value
|
||||
return Math.max(1, Math.ceil(total / itemsPerPage.value))
|
||||
})
|
||||
const totalPagesHistory = computed(() => {
|
||||
const total = USE_MOCK_WALLET ? filteredHistory.value.length : historyTotal.value
|
||||
return Math.max(1, Math.ceil(total / itemsPerPage.value))
|
||||
})
|
||||
|
||||
const currentListTotal = computed(() => {
|
||||
if (activeTab.value === 'positions') return filteredPositions.value.length
|
||||
if (activeTab.value === 'orders') return filteredOpenOrders.value.length
|
||||
if (activeTab.value === 'positions')
|
||||
return USE_MOCK_WALLET ? filteredPositions.value.length : positionTotal.value
|
||||
if (activeTab.value === 'orders')
|
||||
return USE_MOCK_WALLET ? filteredOpenOrders.value.length : openOrderTotal.value
|
||||
return USE_MOCK_WALLET ? filteredHistory.value.length : historyTotal.value
|
||||
})
|
||||
const currentTotalPages = computed(() => {
|
||||
@ -832,9 +951,13 @@ const currentPageEnd = computed(() =>
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
page.value = 1
|
||||
if (tab === 'positions' && !USE_MOCK_WALLET) loadPositionList()
|
||||
if (tab === 'orders' && !USE_MOCK_WALLET) loadOpenOrders()
|
||||
if (tab === 'history' && !USE_MOCK_WALLET) loadHistoryOrders()
|
||||
})
|
||||
watch([page, itemsPerPage], () => {
|
||||
if (activeTab.value === 'positions' && !USE_MOCK_WALLET) loadPositionList()
|
||||
if (activeTab.value === 'orders' && !USE_MOCK_WALLET) loadOpenOrders()
|
||||
if (activeTab.value === 'history' && !USE_MOCK_WALLET) loadHistoryOrders()
|
||||
})
|
||||
watch([currentListTotal, itemsPerPage], () => {
|
||||
@ -870,7 +993,12 @@ async function cancelOrder(ord: OpenOrder) {
|
||||
try {
|
||||
const res = await pmCancelOrder({ orderID, tokenID, userID }, { headers })
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
if (USE_MOCK_WALLET) {
|
||||
openOrders.value = openOrders.value.filter((o) => o.id !== ord.id)
|
||||
} else {
|
||||
openOrderList.value = openOrderList.value.filter((o) => o.id !== ord.id)
|
||||
openOrderTotal.value = openOrderList.value.length
|
||||
}
|
||||
userStore.fetchUsdcBalance()
|
||||
} else {
|
||||
cancelOrderError.value = res.msg || '取消失败'
|
||||
@ -885,7 +1013,12 @@ async function cancelOrder(ord: OpenOrder) {
|
||||
}
|
||||
|
||||
function cancelAllOrders() {
|
||||
if (USE_MOCK_WALLET) {
|
||||
openOrders.value = []
|
||||
} else {
|
||||
openOrderList.value = []
|
||||
openOrderTotal.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
const sellReceiveAmount = computed(() => {
|
||||
@ -1052,6 +1185,7 @@ const handleResize = () => plChartInstance?.resize()
|
||||
watch(plRange, () => updatePlChart())
|
||||
|
||||
onMounted(() => {
|
||||
if (!USE_MOCK_WALLET && activeTab.value === 'positions') loadPositionList()
|
||||
nextTick(() => {
|
||||
initPlChart()
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user