Compare commits

..

No commits in common. "25455e1a813a7c342f1f29f798c37052677d92e3" and "2174abc9d37da5a92319b1d5ddec8a60c163abf7" have entirely different histories.

12 changed files with 139 additions and 676 deletions

View File

@ -11,43 +11,15 @@ description: Interprets the XTrader API from the Swagger 2.0 spec at https://api
## ⚠️ 强制执行:接口接入三步流程 ## ⚠️ 强制执行:接口接入三步流程
**接入任意 XTrader 接口时,必须严格按以下顺序执行。收到「对接 XXX 接口」请求后,立即按此流程执行,不得跳过、不得调换、不得合并。** **接入任意 XTrader 接口时,必须严格按以下顺序执行,不得跳过、不得调换、不得合并步骤。**
### 强制动作序列(必须依次执行) | 步骤 | 动作 | 强制要求 |
|------|------|----------|
| **第一步** | 从 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
View File

@ -1,6 +1,6 @@
# API 基础地址,不设置时默认 https://api.xtrader.vip # API 基础地址,不设置时默认 https://api.xtrader.vip
# 连接测试服务器 192.168.3.21:8888 时复制本文件为 .env 或 .env.local 并取消下一行注释: # 连接测试服务器 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可选覆盖 # SSH 部署npm run deploy可选覆盖
# DEPLOY_HOST=38.246.250.238 # DEPLOY_HOST=38.246.250.238

View File

@ -12,7 +12,6 @@ const currentRoute = computed(() => {
onMounted(() => { onMounted(() => {
if (userStore.isLoggedIn) { if (userStore.isLoggedIn) {
userStore.fetchUserInfo()
userStore.fetchUsdcBalance() userStore.fetchUsdcBalance()
} }
}) })

View File

@ -2,51 +2,6 @@ import { get } from './request'
const USDC_DECIMALS = 1_000_000 const USDC_DECIMALS = 1_000_000
/**
* getUserInfo datadefinitions 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 结构 */ /** getUsdcBalance 返回的 data 结构 */
export interface UsdcBalanceData { export interface UsdcBalanceData {
amount: string amount: string

View File

@ -177,8 +177,6 @@ const emit = defineEmits<{
marketId?: string marketId?: string
outcomeTitle?: string outcomeTitle?: string
clobTokenIds?: string[] clobTokenIds?: string[]
yesLabel?: string
noLabel?: string
}, },
] ]
}>() }>()
@ -302,8 +300,6 @@ function openTradeSingle(side: 'yes' | 'no') {
title: props.marketTitle, title: props.marketTitle,
marketId: props.marketId, marketId: props.marketId,
clobTokenIds: props.clobTokenIds, clobTokenIds: props.clobTokenIds,
yesLabel: props.yesLabel,
noLabel: props.noLabel,
}) })
} }
@ -314,8 +310,6 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
marketId: outcome.marketId, marketId: outcome.marketId,
outcomeTitle: outcome.title, outcomeTitle: outcome.title,
clobTokenIds: outcome.clobTokenIds, clobTokenIds: outcome.clobTokenIds,
yesLabel: outcome.yesLabel,
noLabel: outcome.noLabel,
}) })
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<v-card class="order-book" elevation="0"> <v-card class="order-book">
<!-- Header --> <!-- Header -->
<div class="order-book-header"> <div class="order-book-header">
<h3 class="order-book-title">Order Book</h3> <h3 class="order-book-title">Order Book</h3>
@ -198,8 +198,7 @@ const maxBidsTotal = computed(() => {
width: 100%; width: 100%;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #e7e7e7;
} }
.horizontal-progress-bar { .horizontal-progress-bar {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user' import { getUsdcBalance, formatUsdcBalance } from '@/api/user'
export interface UserInfo { export interface UserInfo {
/** 用户 IDAPI 可能返回 id 或 ID */ /** 用户 IDAPI 可能返回 id 或 ID */
@ -89,30 +89,6 @@ 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 { return {
token, token,
user, user,
@ -123,6 +99,5 @@ export const useUserStore = defineStore('user', () => {
logout, logout,
getAuthHeaders, getAuthHeaders,
fetchUsdcBalance, fetchUsdcBalance,
fetchUserInfo,
} }
}) })

View File

@ -86,7 +86,7 @@
rounded="sm" rounded="sm"
@click="openTrade(market, index, 'yes')" @click="openTrade(market, index, 'yes')"
> >
{{ yesLabel(market) }} {{ yesPrice(market) }} Yes {{ yesPrice(market) }}
</v-btn> </v-btn>
<v-btn <v-btn
class="buy-no-btn" class="buy-no-btn"
@ -95,7 +95,7 @@
rounded="sm" rounded="sm"
@click="openTrade(market, index, 'no')" @click="openTrade(market, index, 'no')"
> >
{{ noLabel(market) }} {{ noPrice(market) }} No {{ noPrice(market) }}
</v-btn> </v-btn>
</div> </div>
</div> </div>
@ -125,7 +125,7 @@
rounded="sm" rounded="sm"
@click="openSheetWithOption('yes')" @click="openSheetWithOption('yes')"
> >
{{ barMarket ? yesLabel(barMarket) : 'Yes' }} {{ barMarket ? yesPrice(barMarket) : '0¢' }} Yes {{ barMarket ? yesPrice(barMarket) : '0¢' }}
</v-btn> </v-btn>
<v-btn <v-btn
class="mobile-bar-btn mobile-bar-no" class="mobile-bar-btn mobile-bar-no"
@ -133,7 +133,7 @@
rounded="sm" rounded="sm"
@click="openSheetWithOption('no')" @click="openSheetWithOption('no')"
> >
{{ barMarket ? noLabel(barMarket) : 'No' }} {{ barMarket ? noPrice(barMarket) : '0¢' }} No {{ barMarket ? noPrice(barMarket) : '0¢' }}
</v-btn> </v-btn>
<v-menu <v-menu
v-model="mobileMenuOpen" v-model="mobileMenuOpen"
@ -239,7 +239,6 @@ const tradeMarketPayload = computed(() => {
noPrice, noPrice,
title: m.question, title: m.question,
clobTokenIds: m.clobTokenIds, clobTokenIds: m.clobTokenIds,
outcomes: m.outcomes,
} }
}) })
@ -567,14 +566,6 @@ function marketChance(market: PmEventMarketItem): number {
return Math.min(100, Math.max(0, Math.round(yesPrice * 100))) return Math.min(100, Math.max(0, Math.round(yesPrice * 100)))
} }
function yesLabel(market: PmEventMarketItem): string {
return market?.outcomes?.[0] ?? 'Yes'
}
function noLabel(market: PmEventMarketItem): string {
return market?.outcomes?.[1] ?? 'No'
}
function yesPrice(market: PmEventMarketItem): string { function yesPrice(market: PmEventMarketItem): string {
const raw = market?.outcomePrices?.[0] const raw = market?.outcomePrices?.[0]
if (raw == null) return '0¢' if (raw == null) return '0¢'
@ -743,14 +734,13 @@ watch(
} }
} }
/* 分时图卡片(扁平化) */ /* 分时图卡片 */
.chart-card.polymarket-chart { .chart-card.polymarket-chart {
margin-bottom: 24px; margin-bottom: 24px;
padding: 20px 24px 16px; padding: 20px 24px 16px;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
border-radius: 12px; border-radius: 12px;
box-shadow: none;
} }
.chart-header { .chart-header {
@ -894,7 +884,6 @@ watch(
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: none;
} }
.markets-list { .markets-list {

View File

@ -506,14 +506,12 @@ const tradeDialogMarket = ref<{
title: string title: string
marketId?: string marketId?: string
clobTokenIds?: string[] clobTokenIds?: string[]
yesLabel?: string
noLabel?: string
} | null>(null) } | null>(null)
const scrollRef = ref<HTMLElement | null>(null) const scrollRef = ref<HTMLElement | null>(null)
function onCardOpenTrade( function onCardOpenTrade(
side: 'yes' | 'no', side: 'yes' | 'no',
market?: { id: string; title: string; marketId?: string; yesLabel?: string; noLabel?: string }, market?: { id: string; title: string; marketId?: string },
) { ) {
tradeDialogSide.value = side tradeDialogSide.value = side
tradeDialogMarket.value = market ?? null tradeDialogMarket.value = market ?? null
@ -528,11 +526,7 @@ const homeTradeMarketPayload = computed(() => {
const chance = 50 const chance = 50
const yesPrice = Math.min(1, Math.max(0, chance / 100)) const yesPrice = Math.min(1, Math.max(0, chance / 100))
const noPrice = 1 - yesPrice const noPrice = 1 - yesPrice
const outcomes = return { marketId, yesPrice, noPrice, title: m.title, clobTokenIds: m.clobTokenIds }
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) const sentinelRef = ref<HTMLElement | null>(null)

View File

@ -177,13 +177,13 @@ const connectWithWallet = async () => {
walletAddress, walletAddress,
}, },
) )
// console.log('Login API response:', loginData)
console.log('[walletLogin] 登录响应:', JSON.stringify(loginData, null, 2))
if (loginData.code === 0 && loginData.data) { if (loginData.code === 0 && loginData.data) {
const { token, user } = loginData.data userStore.setUser({
console.log('[walletLogin] 存入 store 的 user:', JSON.stringify(user, null, 2)) token: loginData.data.token,
userStore.setUser({ token, user }) user: loginData.data.user,
})
userStore.fetchUsdcBalance() userStore.fetchUsdcBalance()
} }

View File

@ -130,7 +130,7 @@
rounded="sm" rounded="sm"
@click="openSheetWithOption('yes')" @click="openSheetWithOption('yes')"
> >
{{ yesLabel }} {{ yesPriceCents }}¢ Yes {{ yesPriceCents }}¢
</v-btn> </v-btn>
<v-btn <v-btn
class="mobile-bar-btn mobile-bar-no" class="mobile-bar-btn mobile-bar-no"
@ -138,7 +138,7 @@
rounded="sm" rounded="sm"
@click="openSheetWithOption('no')" @click="openSheetWithOption('no')"
> >
{{ noLabel }} {{ noPriceCents }}¢ No {{ noPriceCents }}¢
</v-btn> </v-btn>
<v-menu <v-menu
v-model="mobileMenuOpen" v-model="mobileMenuOpen"
@ -332,7 +332,6 @@ const tradeMarketPayload = computed(() => {
noPrice, noPrice,
title: m.question, title: m.question,
clobTokenIds: m.clobTokenIds, clobTokenIds: m.clobTokenIds,
outcomes: m.outcomes,
} }
} }
const qId = route.query.marketId const qId = route.query.marketId
@ -373,8 +372,6 @@ const yesPriceCents = computed(() =>
const noPriceCents = computed(() => const noPriceCents = computed(() =>
tradeMarketPayload.value ? Math.round(tradeMarketPayload.value.noPrice * 100) : 0 tradeMarketPayload.value ? Math.round(tradeMarketPayload.value.noPrice * 100) : 0
) )
const yesLabel = computed(() => currentMarket.value?.outcomes?.[0] ?? 'Yes')
const noLabel = computed(() => currentMarket.value?.outcomes?.[1] ?? 'No')
function openSheetWithOption(side: 'yes' | 'no') { function openSheetWithOption(side: 'yes' | 'no') {
tradeInitialOptionFromBar.value = side tradeInitialOptionFromBar.value = side
@ -924,14 +921,13 @@ onUnmounted(() => {
font-weight: 600; font-weight: 600;
} }
/* Polymarket 样式分时图卡片(扁平化) */ /* Polymarket 样式分时图卡片 */
.chart-card.polymarket-chart { .chart-card.polymarket-chart {
margin-top: 32px; margin-top: 32px;
padding: 20px 24px 16px; padding: 20px 24px 16px;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
border-radius: 12px; border-radius: 12px;
box-shadow: none;
} }
.chart-header { .chart-header {
@ -1031,12 +1027,11 @@ onUnmounted(() => {
color: #111827; color: #111827;
} }
/* Order Book Card Styles(扁平化) */ /* Order Book Card Styles */
.order-book-card { .order-book-card {
padding: 0; padding: 0;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
box-shadow: none;
} }
/* 左右布局:左侧弹性,右侧固定 */ /* 左右布局:左侧弹性,右侧固定 */
@ -1146,12 +1141,11 @@ onUnmounted(() => {
} }
} }
/* Comments / Top Holders / Activity(扁平化) */ /* Comments / Top Holders / Activity */
.activity-card { .activity-card {
margin-top: 32px; margin-top: 32px;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
overflow: hidden; overflow: hidden;
box-shadow: none;
} }
.detail-tabs { .detail-tabs {