优化:交易组件优化
This commit is contained in:
parent
c0c423b46a
commit
25455e1a81
@ -11,15 +11,43 @@ description: Interprets the XTrader API from the Swagger 2.0 spec at https://api
|
||||
|
||||
## ⚠️ 强制执行:接口接入三步流程
|
||||
|
||||
**接入任意 XTrader 接口时,必须严格按以下顺序执行,不得跳过、不得调换、不得合并步骤。**
|
||||
**接入任意 XTrader 接口时,必须严格按以下顺序执行。收到「对接 XXX 接口」请求后,立即按此流程执行,不得跳过、不得调换、不得合并。**
|
||||
|
||||
| 步骤 | 动作 | 强制要求 |
|
||||
|------|------|----------|
|
||||
| **第一步** | 从 doc.json 整理并**在对话中输出**请求参数表、响应参数表、definitions 完整结构 | 在输出第一步结果**之前**,不得写任何业务代码;必须用 `mcp_web_fetch` 或 curl 获取 doc.json,解析 `paths` 与 `definitions` |
|
||||
| **第二步** | 根据第一步整理出的结构,在 `src/api/` 中定义 TypeScript 类型 | 必须等第一步输出完成后再执行;Model 必须与 definitions 对应 |
|
||||
| **第三步** | 实现请求函数并集成到页面 | 必须等第二步完成后再执行 |
|
||||
### 强制动作序列(必须依次执行)
|
||||
|
||||
**违反后果**:若跳过第一步直接写代码,会导致类型与接口文档不一致、遗漏字段或误用参数。
|
||||
1. **收到对接请求** → 立即用 `mcp_web_fetch` 或 `curl` 获取 `https://api.xtrader.vip/swagger/doc.json`
|
||||
2. **第一步** → 解析 `paths["<path>"]["<method>"]` 与 `definitions`,**在对话中输出**:
|
||||
- 请求参数表(Query/Body/鉴权)
|
||||
- 响应参数表(200 schema)
|
||||
- data 的 `$ref` 对应 definitions 的**完整字段表**
|
||||
- 输出后**明确标注「第一步完成」**
|
||||
3. **第二步** → 在 `src/api/` 中根据第一步表格定义 TypeScript 类型,**不得在第一步完成前执行**
|
||||
4. **第三步** → 实现请求函数并集成到页面,**不得在第二步完成前执行**
|
||||
|
||||
### 禁止行为
|
||||
|
||||
- ❌ 在对话中输出第一步结果**之前**写任何 `src/api/` 或 `src/views/` 业务代码
|
||||
- ❌ 跳过第一步直接定义类型或实现请求
|
||||
- ❌ 合并步骤(如边输出边写代码)
|
||||
|
||||
### 第一步输出模板(必须包含)
|
||||
|
||||
```
|
||||
## 第一步:GET/POST <path> 请求与响应参数
|
||||
|
||||
### 1. 请求参数
|
||||
| 类型 | 名称 | 类型 | 必填 | 说明 |
|
||||
| ... |
|
||||
|
||||
### 2. 响应参数(200)
|
||||
根结构:{ code, data, msg }
|
||||
|
||||
### 3. definitions["xxx"] 完整字段
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ... |
|
||||
|
||||
第一步完成。
|
||||
```
|
||||
|
||||
## 规范地址与格式
|
||||
|
||||
|
||||
2
.env
2
.env
@ -1,6 +1,6 @@
|
||||
# API 基础地址,不设置时默认 https://api.xtrader.vip
|
||||
# 连接测试服务器 192.168.3.21:8888 时复制本文件为 .env 或 .env.local 并取消下一行注释:
|
||||
VITE_API_BASE_URL=http://192.168.3.21:8888
|
||||
# VITE_API_BASE_URL=http://192.168.3.21:8888
|
||||
|
||||
# SSH 部署(npm run deploy),可选覆盖
|
||||
# DEPLOY_HOST=38.246.250.238
|
||||
|
||||
@ -12,6 +12,7 @@ const currentRoute = computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
if (userStore.isLoggedIn) {
|
||||
userStore.fetchUserInfo()
|
||||
userStore.fetchUsdcBalance()
|
||||
}
|
||||
})
|
||||
|
||||
@ -2,6 +2,51 @@ import { get } from './request'
|
||||
|
||||
const USDC_DECIMALS = 1_000_000
|
||||
|
||||
/**
|
||||
* getUserInfo 返回的 data(definitions system.SysUser)
|
||||
* doc.json definitions["system.SysUser"] 完整字段
|
||||
*/
|
||||
export interface UserInfoData {
|
||||
ID?: number
|
||||
userName?: string
|
||||
nickName?: string
|
||||
headerImg?: string
|
||||
uuid?: string
|
||||
authorityId?: number
|
||||
authority?: unknown
|
||||
authorities?: unknown[]
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
enable?: number
|
||||
walletAddress?: string
|
||||
externalWalletAddress?: string
|
||||
walletPrivEnc?: string
|
||||
promotionCode?: string
|
||||
puserId?: number
|
||||
remark?: string
|
||||
originSetting?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GetUserInfoResponse {
|
||||
code: number
|
||||
data?: UserInfoData
|
||||
msg?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/getUserInfo
|
||||
* 获取当前用户信息,需鉴权(x-token)
|
||||
*/
|
||||
export async function getUserInfo(
|
||||
authHeaders: Record<string, string>,
|
||||
): Promise<GetUserInfoResponse> {
|
||||
return get<GetUserInfoResponse>('/user/getUserInfo', undefined, {
|
||||
headers: authHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
/** getUsdcBalance 返回的 data 结构 */
|
||||
export interface UsdcBalanceData {
|
||||
amount: string
|
||||
|
||||
@ -177,6 +177,8 @@ const emit = defineEmits<{
|
||||
marketId?: string
|
||||
outcomeTitle?: string
|
||||
clobTokenIds?: string[]
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
},
|
||||
]
|
||||
}>()
|
||||
@ -300,6 +302,8 @@ function openTradeSingle(side: 'yes' | 'no') {
|
||||
title: props.marketTitle,
|
||||
marketId: props.marketId,
|
||||
clobTokenIds: props.clobTokenIds,
|
||||
yesLabel: props.yesLabel,
|
||||
noLabel: props.noLabel,
|
||||
})
|
||||
}
|
||||
|
||||
@ -310,6 +314,8 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
|
||||
marketId: outcome.marketId,
|
||||
outcomeTitle: outcome.title,
|
||||
clobTokenIds: outcome.clobTokenIds,
|
||||
yesLabel: outcome.yesLabel,
|
||||
noLabel: outcome.noLabel,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -56,6 +56,33 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Sell Market: Shares input + 25%/50%/Max -->
|
||||
<template v-if="activeTab === 'sell'">
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Total and To Win (Buy模式) 或 You'll receive (Sell模式) -->
|
||||
<div class="total-section">
|
||||
<!-- Buy模式 -->
|
||||
@ -68,7 +95,7 @@
|
||||
<span class="label">To win</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ toWinValue }}
|
||||
{{ toWinValue }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -78,15 +105,29 @@
|
||||
<span class="label">You'll receive</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ totalPrice }}
|
||||
{{ totalPrice }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||
<!-- Action Button -->
|
||||
<!-- Action Button: Buy 余额足够显示 Buy Yes/No,不足显示 Deposit;Sell 只显示 Sell Yes/No -->
|
||||
<v-btn
|
||||
v-if="activeTab === 'buy' && showDepositForBuy"
|
||||
class="deposit-btn"
|
||||
@click="deposit"
|
||||
>
|
||||
Deposit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@ -96,7 +137,7 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- Balance <= 0: Show Deposit Interface -->
|
||||
<!-- Balance <= 0: Show Deposit Interface (Buy) 或 Sell UI (Sell) -->
|
||||
<template v-else>
|
||||
<!-- Price Options -->
|
||||
<div class="price-options">
|
||||
@ -118,7 +159,8 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Amount Section -->
|
||||
<!-- Buy: Amount Section -->
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="input-group">
|
||||
<div class="amount-header">
|
||||
<div>
|
||||
@ -141,13 +183,64 @@
|
||||
<span class="label">To win</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ toWinValue }}
|
||||
{{ toWinValue }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Buy 余额不足时显示 Deposit -->
|
||||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- Deposit Button -->
|
||||
<v-btn class="deposit-btn" @click="deposit"> Deposit </v-btn>
|
||||
<!-- Sell: Shares + To receive + Avg. Price,只显示 Sell Yes/No(无 Deposit) -->
|
||||
<template v-else>
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-section">
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sell 只显示 Sell Yes/No -->
|
||||
<v-btn
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@click="submitOrder"
|
||||
>
|
||||
{{ actionButtonText }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@ -269,7 +362,7 @@
|
||||
<span class="label">To win</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ toWinValue }}
|
||||
{{ toWinValue }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -279,7 +372,7 @@
|
||||
<span class="label">You'll receive</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ totalPrice }}
|
||||
{{ totalPrice }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -348,6 +441,32 @@
|
||||
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
|
||||
>
|
||||
</div>
|
||||
<!-- Sell Market: Shares input + 25%/50%/Max -->
|
||||
<template v-if="activeTab === 'sell'">
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="total-section">
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="total-row">
|
||||
@ -356,7 +475,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">To win</span
|
||||
><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>
|
||||
</template>
|
||||
@ -364,15 +483,29 @@
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span
|
||||
><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
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||
<v-btn
|
||||
v-if="activeTab === 'buy' && showDepositForBuy"
|
||||
class="deposit-btn"
|
||||
@click="deposit"
|
||||
>
|
||||
Deposit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@ -397,6 +530,8 @@
|
||||
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
|
||||
>
|
||||
</div>
|
||||
<!-- Buy: Amount Section -->
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="input-group">
|
||||
<div class="amount-header">
|
||||
<div>
|
||||
@ -415,12 +550,62 @@
|
||||
<span class="label">To win</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ toWinValue }}
|
||||
{{ toWinValue }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||||
</template>
|
||||
<!-- Sell: Shares + To receive + Avg. Price -->
|
||||
<template v-else>
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-section">
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@click="submitOrder"
|
||||
>
|
||||
{{ actionButtonText }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="price-options hide-in-mobile-sheet">
|
||||
@ -522,7 +707,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">To win</span
|
||||
><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>
|
||||
</template>
|
||||
@ -530,7 +715,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span
|
||||
><span class="to-win-value"
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span
|
||||
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ totalPrice }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@ -624,6 +809,32 @@
|
||||
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
|
||||
>
|
||||
</div>
|
||||
<!-- Sell Market: Shares input + 25%/50%/Max -->
|
||||
<template v-if="activeTab === 'sell'">
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="total-section">
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="total-row">
|
||||
@ -633,7 +844,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">To win</span
|
||||
><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>
|
||||
</template>
|
||||
@ -641,15 +852,29 @@
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span
|
||||
><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
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="orderError" class="order-error">{{ orderError }}</p>
|
||||
<v-btn
|
||||
v-if="activeTab === 'buy' && showDepositForBuy"
|
||||
class="deposit-btn"
|
||||
@click="deposit"
|
||||
>
|
||||
Deposit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@ -674,6 +899,8 @@
|
||||
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
|
||||
>
|
||||
</div>
|
||||
<!-- Buy: Amount Section -->
|
||||
<template v-if="activeTab === 'buy'">
|
||||
<div class="input-group">
|
||||
<div class="amount-header">
|
||||
<div>
|
||||
@ -692,12 +919,62 @@
|
||||
<span class="label">To win</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
${{ toWinValue }}
|
||||
{{ toWinValue }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||||
</template>
|
||||
<!-- Sell: Shares + To receive + Avg. Price -->
|
||||
<template v-else>
|
||||
<div class="input-group shares-group">
|
||||
<div class="shares-header">
|
||||
<span class="label">Shares</span>
|
||||
<div class="shares-input">
|
||||
<v-text-field
|
||||
:model-value="shares"
|
||||
type="number"
|
||||
min="1"
|
||||
class="shares-input-field"
|
||||
hide-details
|
||||
density="compact"
|
||||
@update:model-value="onSharesInput"
|
||||
@keydown="onSharesKeydown"
|
||||
@paste="onSharesPaste"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares-buttons">
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-section">
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span>
|
||||
<span class="to-win-value">
|
||||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||||
{{ totalPrice }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="total-row avg-price-row">
|
||||
<span class="label">
|
||||
Avg. Price {{ avgPriceCents }}¢
|
||||
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
class="action-btn"
|
||||
:loading="orderLoading"
|
||||
:disabled="orderLoading"
|
||||
@click="submitOrder"
|
||||
>
|
||||
{{ actionButtonText }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="price-options hide-in-mobile-sheet">
|
||||
@ -798,7 +1075,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">To win</span
|
||||
><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>
|
||||
</template>
|
||||
@ -806,7 +1083,7 @@
|
||||
<div class="total-row">
|
||||
<span class="label">You'll receive</span
|
||||
><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
|
||||
}}</span
|
||||
>
|
||||
@ -850,8 +1127,8 @@
|
||||
</div>
|
||||
<v-card-text class="merge-dialog-body">
|
||||
<p class="merge-dialog-desc">
|
||||
Merge a share of Yes and No to get 1 USDC. You can do this to save cost when trying to get
|
||||
rid of a position.
|
||||
Merge a share of {{ yesLabel }} and {{ noLabel }} to get 1 USDC. You can do this to save
|
||||
cost when trying to get rid of a position.
|
||||
</p>
|
||||
<div class="merge-amount-row">
|
||||
<label class="merge-amount-label">Amount</label>
|
||||
@ -870,7 +1147,7 @@
|
||||
<button type="button" class="merge-max-link" @click="setMergeMax">Max</button>
|
||||
</p>
|
||||
<p v-if="!props.market?.marketId" class="merge-no-market">
|
||||
Please select a market first (e.g. click Buy Yes/No on a market).
|
||||
Please select a market first (e.g. click Buy {{ yesLabel }}/{{ noLabel }} on a market).
|
||||
</p>
|
||||
<p v-if="mergeError" class="merge-error">{{ mergeError }}</p>
|
||||
</v-card-text>
|
||||
@ -913,8 +1190,8 @@
|
||||
</div>
|
||||
<v-card-text class="split-dialog-body">
|
||||
<p class="split-dialog-desc">
|
||||
Use USDC to get one share of Yes and one share of No for this market. 1 USDC ≈ 1 complete
|
||||
set.
|
||||
Use USDC to get one share of {{ yesLabel }} and one share of {{ noLabel }} for this
|
||||
market. 1 USDC ≈ 1 complete set.
|
||||
</p>
|
||||
<div class="split-amount-row">
|
||||
<label class="split-amount-label">Amount (USDC)</label>
|
||||
@ -930,7 +1207,7 @@
|
||||
/>
|
||||
</div>
|
||||
<p v-if="!props.market?.marketId" class="split-no-market">
|
||||
Please select a market first (e.g. click Buy Yes/No on a market).
|
||||
Please select a market first (e.g. click Buy {{ yesLabel }}/{{ noLabel }} on a market).
|
||||
</p>
|
||||
<p v-if="splitError" class="split-error">{{ splitError }}</p>
|
||||
</v-card-text>
|
||||
@ -1117,7 +1394,12 @@ const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h'
|
||||
// Market mode state
|
||||
const isMarketMode = computed(() => limitType.value === 'Market')
|
||||
const amount = ref(0) // Market mode amount
|
||||
const balance = ref(0) // Market mode balance
|
||||
/** 余额(从 userStore 同步) */
|
||||
const balance = computed(() => {
|
||||
const s = userStore.balance
|
||||
const n = parseFloat(String(s ?? '0'))
|
||||
return Number.isFinite(n) ? n : 0
|
||||
})
|
||||
|
||||
const orderLoading = ref(false)
|
||||
const orderError = ref('')
|
||||
@ -1147,6 +1429,12 @@ const totalPrice = computed(() => {
|
||||
return (limitPrice.value * shares.value).toFixed(2)
|
||||
})
|
||||
|
||||
/** Sell 模式下的平均单价(¢) */
|
||||
const avgPriceCents = computed(() => {
|
||||
const p = limitPrice.value
|
||||
return (p * 100).toFixed(1)
|
||||
})
|
||||
|
||||
/** To win = 份数 × 1U = shares × 1 USDC */
|
||||
const toWinValue = computed(() => {
|
||||
if (isMarketMode.value) {
|
||||
@ -1162,7 +1450,9 @@ const toWinValue = computed(() => {
|
||||
|
||||
const actionButtonText = computed(() => {
|
||||
const label = selectedOption.value === 'yes' ? yesLabel.value : noLabel.value
|
||||
return `${activeTab.value} ${label}`
|
||||
const tab = activeTab.value
|
||||
const tabCapitalized = tab.charAt(0).toUpperCase() + tab.slice(1)
|
||||
return `${tabCapitalized} ${label}`
|
||||
})
|
||||
|
||||
function applyInitialOption(option: 'yes' | 'no') {
|
||||
@ -1316,6 +1606,17 @@ const setMaxAmount = () => {
|
||||
amount.value = balance.value
|
||||
}
|
||||
|
||||
/** Buy 模式:余额是否足够(>= 所需金额且不为 0)。cost:balance>0 时用 totalPrice,否则用 amount */
|
||||
const canAffordBuy = computed(() => {
|
||||
const bal = balance.value
|
||||
if (bal <= 0) return false
|
||||
const cost = bal > 0 ? parseFloat(totalPrice.value) || 0 : amount.value
|
||||
return bal >= cost
|
||||
})
|
||||
|
||||
/** Buy 模式且余额不足时显示 Deposit,否则显示 Buy Yes/No */
|
||||
const showDepositForBuy = computed(() => !canAffordBuy.value)
|
||||
|
||||
const deposit = () => {
|
||||
console.log('Depositing amount:', amount.value)
|
||||
// 实际应用中,这里应该调用存款API
|
||||
@ -1663,6 +1964,17 @@ async function submitOrder() {
|
||||
color: #3d8b40;
|
||||
}
|
||||
|
||||
.avg-price-row {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.avg-price-row .info-icon {
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.expiration-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -1783,6 +2095,13 @@ async function submitOrder() {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.deposit-btn--secondary {
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
color: #0066cc;
|
||||
border: 1px solid #0066cc;
|
||||
}
|
||||
|
||||
/* 移动端底部交易栏(红框样式) */
|
||||
.mobile-trade-bar-spacer {
|
||||
height: 72px;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getUsdcBalance, formatUsdcBalance } from '@/api/user'
|
||||
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
|
||||
|
||||
export interface UserInfo {
|
||||
/** 用户 ID(API 可能返回 id 或 ID) */
|
||||
@ -89,6 +89,30 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求用户信息(需已登录),更新 store 中的 user */
|
||||
async function fetchUserInfo() {
|
||||
const headers = getAuthHeaders()
|
||||
if (!headers) return
|
||||
try {
|
||||
const res = await getUserInfo(headers)
|
||||
console.log('[fetchUserInfo] 接口响应:', JSON.stringify(res, null, 2))
|
||||
if ((res.code === 0 || res.code === 200) && res.data) {
|
||||
const u = res.data
|
||||
user.value = {
|
||||
id: u.ID,
|
||||
ID: u.ID,
|
||||
userName: u.userName,
|
||||
nickName: u.nickName,
|
||||
headerImg: u.headerImg,
|
||||
...u,
|
||||
}
|
||||
if (token.value && user.value) saveToStorage(token.value, user.value)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[fetchUserInfo] 请求失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
@ -99,5 +123,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
logout,
|
||||
getAuthHeaders,
|
||||
fetchUsdcBalance,
|
||||
fetchUserInfo,
|
||||
}
|
||||
})
|
||||
|
||||
@ -506,12 +506,14 @@ const tradeDialogMarket = ref<{
|
||||
title: string
|
||||
marketId?: string
|
||||
clobTokenIds?: string[]
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
} | null>(null)
|
||||
const scrollRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function onCardOpenTrade(
|
||||
side: 'yes' | 'no',
|
||||
market?: { id: string; title: string; marketId?: string },
|
||||
market?: { id: string; title: string; marketId?: string; yesLabel?: string; noLabel?: string },
|
||||
) {
|
||||
tradeDialogSide.value = side
|
||||
tradeDialogMarket.value = market ?? null
|
||||
@ -526,7 +528,11 @@ const homeTradeMarketPayload = computed(() => {
|
||||
const chance = 50
|
||||
const yesPrice = Math.min(1, Math.max(0, chance / 100))
|
||||
const noPrice = 1 - yesPrice
|
||||
return { marketId, yesPrice, noPrice, title: m.title, clobTokenIds: m.clobTokenIds }
|
||||
const outcomes =
|
||||
m.yesLabel != null || m.noLabel != null
|
||||
? [m.yesLabel ?? 'Yes', m.noLabel ?? 'No']
|
||||
: undefined
|
||||
return { marketId, yesPrice, noPrice, title: m.title, clobTokenIds: m.clobTokenIds, outcomes }
|
||||
})
|
||||
|
||||
const sentinelRef = ref<HTMLElement | null>(null)
|
||||
|
||||
@ -177,13 +177,13 @@ const connectWithWallet = async () => {
|
||||
walletAddress,
|
||||
},
|
||||
)
|
||||
console.log('Login API response:', loginData)
|
||||
// 调试:打印登录信息数据结构
|
||||
console.log('[walletLogin] 登录响应:', JSON.stringify(loginData, null, 2))
|
||||
|
||||
if (loginData.code === 0 && loginData.data) {
|
||||
userStore.setUser({
|
||||
token: loginData.data.token,
|
||||
user: loginData.data.user,
|
||||
})
|
||||
const { token, user } = loginData.data
|
||||
console.log('[walletLogin] 存入 store 的 user:', JSON.stringify(user, null, 2))
|
||||
userStore.setUser({ token, user })
|
||||
userStore.fetchUsdcBalance()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user