Compare commits
2 Commits
fa5e0dccfe
...
e7a33c9638
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7a33c9638 | ||
|
|
acd3e41f36 |
3
.env
3
.env
@ -1,7 +1,8 @@
|
||||
# API 基础地址,不设置时默认 https://api.xtrader.vip
|
||||
# 连接测试服务器 192.168.3.21:8888 时复制本文件为 .env 或 .env.local 并取消下一行注释:
|
||||
VITE_API_BASE_URL=http://localhost:8888
|
||||
# VITE_API_BASE_URL=http://192.168.3.14:8888
|
||||
|
||||
# VITE_USE_MOCK_DATA=false # 全部关闭 mock
|
||||
# SSH 部署(npm run deploy),可选覆盖
|
||||
# DEPLOY_HOST=38.246.250.238
|
||||
# DEPLOY_USER=root
|
||||
|
||||
@ -59,17 +59,16 @@
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| ID | number | 主键 |
|
||||
| title | string | 标题 |
|
||||
| name | string | 名称 |
|
||||
| eventSlug | string | 事件标识 |
|
||||
| outcome | string | 结果(Yes/No 等) |
|
||||
| side | string | 方向 |
|
||||
| type | string | 类型 |
|
||||
| title | string | 标题(如「充值资金」) |
|
||||
| type | string | 类型(如 recharge) |
|
||||
| **usdcSize** | number | **金额(USDC)**,用于充值等 |
|
||||
| **icon** | string | **图标路径**(如 uploads/file/btc.png),会转为完整 URL 展示 |
|
||||
| **UpdatedAt** | string | **更新时间**,用于 timeAgo 展示 |
|
||||
| price | number | 价格 |
|
||||
| size | number | 大小 |
|
||||
| createdAt | string | 创建时间 |
|
||||
| timestamp | number | 时间戳(秒) |
|
||||
| 其他 | - | asset, bio, conditionId, icon, slug, transactionHash 等 |
|
||||
| outcome | string | 结果 |
|
||||
| timestamp | number | 时间戳(秒或毫秒) |
|
||||
| 其他 | - | asset, bio, conditionId, slug, transactionHash 等 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
## 核心能力
|
||||
|
||||
- `getPmPriceHistoryPublic`:按市场 ID 分页获取价格历史
|
||||
- `getTimeRangeSeconds`:根据分时范围计算 `startTs`、`endTs`(Unix 秒);`endTs` 始终为当前时间;1H/6H/1D/1W/1M 的 `startTs` 为当前时间往前对应时长;ALL 的 `startTs` 为事件开始时间、`endTs` 为当前时间
|
||||
- `priceHistoryToChartData`:将接口返回的 `list` 转为 ECharts 使用的 `[timestamp_ms, value_0_100][]`
|
||||
|
||||
## GET /pmPriceHistory/getPmPriceHistoryPublic
|
||||
@ -20,6 +21,8 @@
|
||||
| market | string | 是 | 传 YES 对应的 clobTokenId(即当前市场 clobTokenIds[0]) |
|
||||
| page | number | 否 | 页码,默认 1 |
|
||||
| pageSize | number | 否 | 每页条数,默认 500 |
|
||||
| startTs | number | 否 | 时间范围起始(Unix 秒) |
|
||||
| endTs | number | 否 | 时间范围结束(Unix 秒) |
|
||||
| interval | string | 否 | 数据间隔 |
|
||||
| time | number | 否 | 时间筛选 |
|
||||
| createdAtRange | string[] | 否 | 创建时间范围 |
|
||||
@ -52,18 +55,22 @@
|
||||
import {
|
||||
getPmPriceHistoryPublic,
|
||||
priceHistoryToChartData,
|
||||
getTimeRangeSeconds,
|
||||
type PmPriceHistoryItem,
|
||||
} from '@/api/priceHistory'
|
||||
|
||||
const timeRange = getTimeRangeSeconds('1D') // { startTs, endTs }
|
||||
// ALL 时传 eventDates:getTimeRangeSeconds('ALL', { startDate: ev.startDate, endDate: ev.endDate })
|
||||
const res = await getPmPriceHistoryPublic({
|
||||
market: marketId,
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
...(timeRange && { startTs: timeRange.startTs, endTs: timeRange.endTs }),
|
||||
})
|
||||
const chartData = priceHistoryToChartData(res.data?.list ?? [])
|
||||
```
|
||||
|
||||
## 扩展方式
|
||||
|
||||
- 按时间范围(1H/6H/1D 等)传 `interval` 或 `createdAtRange` 需与后端约定取值
|
||||
- 时间戳规则:`endTs` 始终为当前时间;1H/6H/1D/1W/1M 的 `startTs` 为当前时间往前对应时长;ALL 的 `startTs` 为事件 `startDate`,`endTs` 为当前时间
|
||||
- 若后端返回的 `price` 固定为 0–100,`priceHistoryToChartData` 已兼容(≤1 时乘 100)
|
||||
|
||||
@ -7,12 +7,17 @@
|
||||
|
||||
事件下的市场列表页,展示某个 Event 的多个 Market(如 NFL 多支队伍),支持选择并跳转交易详情。
|
||||
|
||||
- **多市场折线图**:按市场数量依次调用 `getPmPriceHistoryPublic`,每个市场使用 `clobTokenIds[0]`(YES token)作为 `market` 参数,展示多条分时曲线
|
||||
- **时间范围**:1H / 6H / 1D / 1W / 1M / ALL,与 TradeDetail 一致
|
||||
|
||||
## 使用方式
|
||||
|
||||
- 从首页或详情进入,路由 `/event/123/markets`
|
||||
- 路由参数 `id` 为 Event ID
|
||||
- 分时图数据来源:`src/api/priceHistory.ts` 的 `getPmPriceHistoryPublic`、`priceHistoryToChartData`
|
||||
|
||||
## 扩展方式
|
||||
|
||||
- 增加市场筛选、排序
|
||||
- 与 TradeDetail 联动,支持从市场列表直接进入指定 market 的交易
|
||||
- 可抽取 `getTimeRangeMs`、`filterChartDataByRange` 为共享 util,与 TradeDetail 复用
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { buildQuery, get } from './request'
|
||||
import { BASE_URL } from './request'
|
||||
import type { PageResult } from './types'
|
||||
|
||||
/** 单条历史记录(与 doc.json definitions["polymarket.HistoryRecord"] 对齐) */
|
||||
@ -13,6 +14,7 @@ export interface HistoryRecordItem {
|
||||
bio?: string
|
||||
conditionId?: string
|
||||
createdAt?: string
|
||||
CreatedAt?: string
|
||||
eventSlug?: string
|
||||
icon?: string
|
||||
name?: string
|
||||
@ -31,6 +33,9 @@ export interface HistoryRecordItem {
|
||||
transactionHash?: string
|
||||
type?: string
|
||||
updatedAt?: string
|
||||
UpdatedAt?: string
|
||||
/** 金额(USDC),用于充值等类型 */
|
||||
usdcSize?: number
|
||||
}
|
||||
|
||||
/** GET /hr/getHistoryRecordPublic 请求参数 */
|
||||
@ -129,11 +134,15 @@ export interface HistoryDisplayItem {
|
||||
shares?: string
|
||||
iconChar?: string
|
||||
iconClass?: string
|
||||
/** 图标 URL(来自 record.icon,用于展示) */
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
function formatTimeAgo(createdAt: string | undefined, timestamp?: number): string {
|
||||
const ms = createdAt ? new Date(createdAt).getTime() : (timestamp != null ? timestamp * 1000 : 0)
|
||||
if (!ms) return ''
|
||||
function formatTimeAgo(dateStr: string | undefined, timestamp?: number): string {
|
||||
let ms = 0
|
||||
if (dateStr) ms = new Date(dateStr).getTime()
|
||||
else if (timestamp != null) ms = timestamp < 1e12 ? timestamp * 1000 : timestamp
|
||||
if (!ms || !Number.isFinite(ms)) return ''
|
||||
const diff = Date.now() - ms
|
||||
if (diff < 60000) return 'Just now'
|
||||
if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`
|
||||
@ -142,8 +151,18 @@ function formatTimeAgo(createdAt: string | undefined, timestamp?: number): strin
|
||||
return new Date(ms).toLocaleDateString()
|
||||
}
|
||||
|
||||
/** 将相对路径转为完整 URL */
|
||||
function toFullIconUrl(icon: string | undefined): string | undefined {
|
||||
if (!icon?.trim()) return undefined
|
||||
const s = icon.trim()
|
||||
if (s.startsWith('http://') || s.startsWith('https://')) return s
|
||||
const base = BASE_URL?.replace(/\/$/, '') ?? ''
|
||||
return `${base}/${s.replace(/^\//, '')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 HistoryRecordItem 映射为钱包 History 展示项
|
||||
* 金额用 usdcSize,图标用 icon,日期用 UpdatedAt
|
||||
*/
|
||||
export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): HistoryDisplayItem {
|
||||
const id = String(record.ID ?? '')
|
||||
@ -152,13 +171,18 @@ export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): Histor
|
||||
const side = outcome === 'No' || outcome === 'Down' ? 'No' : 'Yes'
|
||||
const typeLabel = record.type ?? 'Trade'
|
||||
const activity = `${typeLabel} ${outcome}`.trim()
|
||||
const usdcSize = record.usdcSize ?? 0
|
||||
const price = record.price ?? 0
|
||||
const size = record.size ?? 0
|
||||
const valueUsd = price * size
|
||||
const valueUsd = usdcSize !== 0 ? usdcSize : 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)
|
||||
const timeAgo = formatTimeAgo(
|
||||
record.UpdatedAt ?? record.updatedAt ?? record.CreatedAt ?? record.createdAt,
|
||||
record.timestamp,
|
||||
)
|
||||
const imageUrl = toFullIconUrl(record.icon)
|
||||
return {
|
||||
id,
|
||||
market,
|
||||
@ -171,6 +195,7 @@ export function mapHistoryRecordToDisplayItem(record: HistoryRecordItem): Histor
|
||||
timeAgo,
|
||||
avgPrice: priceCents ? `${priceCents}¢` : undefined,
|
||||
shares: size > 0 ? String(Math.floor(size)) : undefined,
|
||||
imageUrl,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,10 @@ export interface GetPmPriceHistoryPublicParams {
|
||||
market: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
/** 时间范围:起始时间戳(Unix 秒) */
|
||||
startTs?: number
|
||||
/** 时间范围:结束时间戳(Unix 秒) */
|
||||
endTs?: number
|
||||
/** 数据间隔 */
|
||||
interval?: string
|
||||
/** 时间筛选 */
|
||||
@ -56,11 +60,13 @@ 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 { market, page = 1, pageSize = 500, startTs, endTs, interval, time, createdAtRange, fidelity, keyword, order, sort, price } = params
|
||||
const query = buildQuery({
|
||||
market,
|
||||
page,
|
||||
pageSize,
|
||||
startTs,
|
||||
endTs,
|
||||
interval,
|
||||
time,
|
||||
createdAtRange,
|
||||
@ -76,6 +82,34 @@ export async function getPmPriceHistoryPublic(
|
||||
/** 图表单点格式 [timestamp_ms, value_0_100] */
|
||||
export type ChartDataPoint = [number, number]
|
||||
|
||||
/**
|
||||
* 分时范围对应的 Unix 秒时间戳
|
||||
* 规则:endTs 始终为当前时间;1H/6H/1D/1W/1M 的 startTs 为当前时间往前对应时长;ALL 的 startTs 为事件开始时间、endTs 为当前时间
|
||||
*/
|
||||
export function getTimeRangeSeconds(
|
||||
range: string,
|
||||
eventDates?: { startDate?: string; endDate?: string },
|
||||
): { startTs: number; endTs: number } | null {
|
||||
const nowSec = Math.floor(Date.now() / 1000)
|
||||
const H = 60 * 60
|
||||
const D = 24 * H
|
||||
switch (range) {
|
||||
case '1H': return { startTs: nowSec - 1 * H, endTs: nowSec }
|
||||
case '6H': return { startTs: nowSec - 6 * H, endTs: nowSec }
|
||||
case '1D': return { startTs: nowSec - 1 * D, endTs: nowSec }
|
||||
case '1W': return { startTs: nowSec - 7 * D, endTs: nowSec }
|
||||
case '1M': return { startTs: nowSec - 30 * D, endTs: nowSec }
|
||||
case 'ALL':
|
||||
if (eventDates?.startDate) {
|
||||
const startTs = Math.floor(new Date(eventDates.startDate).getTime() / 1000)
|
||||
if (Number.isFinite(startTs))
|
||||
return { startTs, endTs: nowSec }
|
||||
}
|
||||
return null
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将接口返回的 list 转为 ECharts 折线图数据
|
||||
* - time 转为毫秒时间戳
|
||||
|
||||
@ -296,7 +296,7 @@ async function checkBalance(isFirst = false) {
|
||||
for (const addr of contractsToCheck) {
|
||||
try {
|
||||
const contract = new ethers.Contract(addr, ['function balanceOf(address) view returns (uint256)'], provider)
|
||||
const bal = await contract.balanceOf(targetAddr)
|
||||
const bal = contract.balanceOf ? await contract.balanceOf(targetAddr) : BigInt(0)
|
||||
totalBalance += bal
|
||||
} catch (err) {
|
||||
console.warn(`Failed to check balance for token ${addr}`, err)
|
||||
|
||||
@ -1579,7 +1579,7 @@ async function submitMerge() {
|
||||
mergeError.value = ''
|
||||
try {
|
||||
const res = await pmMarketMerge(
|
||||
{ marketID: marketId, amount: String(mergeAmount.value) },
|
||||
{ marketID: marketId, amount: (mergeAmount.value * 1000000).toFixed(0) },
|
||||
{ headers: userStore.getAuthHeaders() },
|
||||
)
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
@ -1614,7 +1614,7 @@ async function submitSplit() {
|
||||
splitError.value = ''
|
||||
try {
|
||||
const res = await pmMarketSplit(
|
||||
{ marketID: marketId, usdcAmount: String(splitAmount.value * 1000000) },
|
||||
{ marketID: marketId, usdcAmount: (splitAmount.value * 1000000).toFixed(0) },
|
||||
{ headers: userStore.getAuthHeaders() },
|
||||
)
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
|
||||
@ -36,6 +36,9 @@
|
||||
<p class="chart-legend-hint">{{ markets.length }} 个市场</p>
|
||||
</div>
|
||||
<div class="chart-wrapper">
|
||||
<div v-if="chartLoading" class="chart-loading-overlay">
|
||||
<v-progress-circular indeterminate color="primary" size="32" />
|
||||
</div>
|
||||
<div ref="chartContainerRef" class="chart-container"></div>
|
||||
</div>
|
||||
<div class="chart-footer">
|
||||
@ -218,10 +221,12 @@ import TradeComponent from '../components/TradeComponent.vue'
|
||||
import {
|
||||
findPmEvent,
|
||||
getMarketId,
|
||||
getClobTokenId,
|
||||
type FindPmEventParams,
|
||||
type PmEventListItem,
|
||||
type PmEventMarketItem,
|
||||
} from '../api/event'
|
||||
import { getPmPriceHistoryPublic, priceHistoryToChartData, getTimeRangeSeconds } from '../api/priceHistory'
|
||||
import { getMockEventById } from '../api/mockData'
|
||||
import { USE_MOCK_EVENT } from '../config/mock'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -336,13 +341,13 @@ const timeRanges = [
|
||||
{ label: '1M', value: '1M' },
|
||||
{ label: 'ALL', value: 'ALL' },
|
||||
]
|
||||
const selectedTimeRange = ref('ALL')
|
||||
const selectedTimeRange = ref('1D')
|
||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||
|
||||
type ChartSeriesItem = { name: string; data: [number, number][] }
|
||||
const chartData = ref<ChartSeriesItem[]>([])
|
||||
const chartLoading = ref(false)
|
||||
let chartInstance: ECharts | null = null
|
||||
let dynamicInterval: number | undefined
|
||||
|
||||
const LINE_COLORS = [
|
||||
'#2563eb',
|
||||
@ -356,51 +361,38 @@ const LINE_COLORS = [
|
||||
]
|
||||
const MOBILE_BREAKPOINT = 600
|
||||
|
||||
function getStepAndCount(range: string): { stepMs: number; count: number } {
|
||||
switch (range) {
|
||||
case '1H':
|
||||
return { stepMs: 60 * 1000, count: 60 }
|
||||
case '6H':
|
||||
return { stepMs: 10 * 60 * 1000, count: 36 }
|
||||
case '1D':
|
||||
return { stepMs: 60 * 60 * 1000, count: 24 }
|
||||
case '1W':
|
||||
return { stepMs: 24 * 60 * 60 * 1000, count: 7 }
|
||||
case '1M':
|
||||
case 'ALL':
|
||||
return { stepMs: 24 * 60 * 60 * 1000, count: 30 }
|
||||
default:
|
||||
return { stepMs: 60 * 60 * 1000, count: 24 }
|
||||
}
|
||||
}
|
||||
|
||||
function generateDataForMarket(baseChance: number, range: string): [number, number][] {
|
||||
const now = Date.now()
|
||||
const data: [number, number][] = []
|
||||
const { stepMs, count } = getStepAndCount(range)
|
||||
let value = baseChance + (Math.random() - 0.5) * 10
|
||||
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
|
||||
}
|
||||
|
||||
function generateAllData(): ChartSeriesItem[] {
|
||||
const range = selectedTimeRange.value
|
||||
/** 按市场依次请求 getPmPriceHistoryPublic,market 传 clobTokenIds[0](YES token) */
|
||||
async function loadChartFromApi(): Promise<ChartSeriesItem[]> {
|
||||
const list = markets.value
|
||||
return list.map((market, i) => {
|
||||
const chance = marketChance(market)
|
||||
const range = selectedTimeRange.value
|
||||
const results: ChartSeriesItem[] = []
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const market = list[i]
|
||||
if (!market) continue
|
||||
const yesTokenId = getClobTokenId(market, 0)
|
||||
const base = (market.question || 'Market').slice(0, 32)
|
||||
const baseName = base + (base.length >= 32 ? '…' : '')
|
||||
// 确保图例 name 唯一,否则 ECharts 会合并导致左右切换箭头不显示
|
||||
const name = list.length > 1 ? `${baseName} (${i + 1}/${list.length})` : baseName
|
||||
return {
|
||||
name,
|
||||
data: generateDataForMarket(chance || 20, range),
|
||||
if (!yesTokenId) {
|
||||
results.push({ name, data: [] })
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const ev = eventDetail.value
|
||||
const timeRange = getTimeRangeSeconds(range, ev ? { startDate: ev.startDate, endDate: ev.endDate } : undefined)
|
||||
const res = await getPmPriceHistoryPublic({
|
||||
market: yesTokenId,
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
...(timeRange && { startTs: timeRange.startTs, endTs: timeRange.endTs }),
|
||||
})
|
||||
const points = priceHistoryToChartData(res.data?.list ?? [])
|
||||
results.push({ name, data: points })
|
||||
} catch {
|
||||
results.push({ name, data: [] })
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
function buildOption(seriesArr: ChartSeriesItem[], containerWidth?: number) {
|
||||
@ -497,28 +489,29 @@ function buildOption(seriesArr: ChartSeriesItem[], containerWidth?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
async function initChart() {
|
||||
if (!chartContainerRef.value || markets.value.length === 0) return
|
||||
chartData.value = generateAllData()
|
||||
chartLoading.value = true
|
||||
try {
|
||||
chartData.value = await loadChartFromApi()
|
||||
chartInstance = echarts.init(chartContainerRef.value)
|
||||
const w = chartContainerRef.value.clientWidth
|
||||
chartInstance.setOption(buildOption(chartData.value, w))
|
||||
|
||||
// 数据打印,便于分析
|
||||
console.log('[EventMarkets] 数据分析:', {
|
||||
marketsCount: markets.value.length,
|
||||
chartSeriesCount: chartData.value.length,
|
||||
containerWidth: w,
|
||||
legendData: chartData.value.map((s) => ({ name: s.name, nameLen: s.name.length, points: s.data.length })),
|
||||
eventDetail: eventDetail.value ? { title: eventDetail.value.title, id: eventDetail.value.ID } : null,
|
||||
})
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function updateChartData() {
|
||||
chartData.value = generateAllData()
|
||||
async function updateChartData() {
|
||||
chartLoading.value = true
|
||||
try {
|
||||
chartData.value = await loadChartFromApi()
|
||||
const w = chartContainerRef.value?.clientWidth
|
||||
if (chartInstance)
|
||||
chartInstance.setOption(buildOption(chartData.value, w), { replaceMerge: ['series'] })
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectTimeRange(range: string) {
|
||||
@ -526,35 +519,6 @@ function selectTimeRange(range: string) {
|
||||
updateChartData()
|
||||
}
|
||||
|
||||
function getMaxPoints(range: string): number {
|
||||
return getStepAndCount(range).count + 1
|
||||
}
|
||||
|
||||
function startDynamicUpdate() {
|
||||
dynamicInterval = window.setInterval(() => {
|
||||
const nextData = chartData.value.map((s) => {
|
||||
const list = [...s.data]
|
||||
const last = list[list.length - 1]
|
||||
if (!last) return s
|
||||
const nextVal = Math.max(10, Math.min(90, last[1] + (Math.random() - 0.5) * 4))
|
||||
list.push([Date.now(), Math.round(nextVal * 10) / 10])
|
||||
const max = getMaxPoints(selectedTimeRange.value)
|
||||
return { name: s.name, data: list.slice(-max) }
|
||||
})
|
||||
chartData.value = nextData
|
||||
const w = chartContainerRef.value?.clientWidth
|
||||
if (chartInstance)
|
||||
chartInstance.setOption(buildOption(chartData.value, w), { replaceMerge: ['series'] })
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function stopDynamicUpdate() {
|
||||
if (dynamicInterval) {
|
||||
clearInterval(dynamicInterval)
|
||||
dynamicInterval = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
if (!chartInstance || !chartContainerRef.value) return
|
||||
chartInstance.resize()
|
||||
@ -752,7 +716,6 @@ watch(tradeSheetOpen, (open) => {
|
||||
}, { immediate: true })
|
||||
|
||||
onUnmounted(() => {
|
||||
stopDynamicUpdate()
|
||||
window.removeEventListener('resize', handleResize)
|
||||
chartInstance?.dispose()
|
||||
chartInstance = null
|
||||
@ -764,10 +727,7 @@ watch(
|
||||
() => markets.value.length,
|
||||
(len) => {
|
||||
if (len > 0) {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
if (dynamicInterval == null) startDynamicUpdate()
|
||||
})
|
||||
nextTick(() => initChart())
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -915,10 +875,21 @@ watch(
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chart-loading-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
|
||||
@ -401,6 +401,7 @@ import { cancelOrder as apiCancelOrder } from '../api/order'
|
||||
import type { ChartDataPoint, ChartTimeRange } from '../api/chart'
|
||||
import {
|
||||
getPmPriceHistoryPublic,
|
||||
getTimeRangeSeconds,
|
||||
priceHistoryToChartData,
|
||||
} from '../api/priceHistory'
|
||||
import {
|
||||
@ -1231,7 +1232,7 @@ function formatTimeAgo(ts: number): string {
|
||||
}
|
||||
|
||||
// 时间粒度
|
||||
const selectedTimeRange = ref('ALL')
|
||||
const selectedTimeRange = ref('1D')
|
||||
const timeRanges = [
|
||||
{ label: '1H', value: '1H' },
|
||||
{ label: '6H', value: '6H' },
|
||||
@ -1249,40 +1250,6 @@ const cryptoChartLoading = ref(false)
|
||||
const chartYesNoLoading = ref(false)
|
||||
let chartInstance: ECharts | null = null
|
||||
|
||||
/** 分时范围对应的毫秒数,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 ev = eventDetail.value
|
||||
const market = ev?.markets?.[0]
|
||||
@ -1528,11 +1495,14 @@ function initChart() {
|
||||
}
|
||||
|
||||
/** 从 GET /pmPriceHistory/getPmPriceHistoryPublic 拉取价格历史,market 传 YES 对应的 clobTokenId */
|
||||
async function loadChartFromApi(marketParam: string): Promise<ChartDataPoint[]> {
|
||||
async function loadChartFromApi(marketParam: string, range: string): Promise<ChartDataPoint[]> {
|
||||
const ev = eventDetail.value
|
||||
const timeRange = getTimeRangeSeconds(range, ev ? { startDate: ev.startDate, endDate: ev.endDate } : undefined)
|
||||
const res = await getPmPriceHistoryPublic({
|
||||
market: marketParam,
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
...(timeRange && { startTs: timeRange.startTs, endTs: timeRange.endTs }),
|
||||
})
|
||||
const list = res.data?.list ?? []
|
||||
return priceHistoryToChartData(list)
|
||||
@ -1592,9 +1562,9 @@ async function updateChartData() {
|
||||
try {
|
||||
// 价格历史接口的 market 传 clobTokenIds[0](YES 对应 token ID)
|
||||
const yesTokenId = clobTokenIds.value[0]
|
||||
const points = yesTokenId ? await loadChartFromApi(yesTokenId) : []
|
||||
const points = yesTokenId ? await loadChartFromApi(yesTokenId, selectedTimeRange.value) : []
|
||||
rawChartData.value = points
|
||||
data.value = filterChartDataByRange(points, selectedTimeRange.value)
|
||||
data.value = points
|
||||
if (chartInstance)
|
||||
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
||||
} finally {
|
||||
@ -1607,16 +1577,7 @@ function selectTimeRange(range: string) {
|
||||
selectedTimeRange.value = range
|
||||
}
|
||||
|
||||
watch(selectedTimeRange, (range) => {
|
||||
if (chartMode.value === 'yesno') {
|
||||
data.value = filterChartDataByRange(rawChartData.value, range)
|
||||
const w = chartContainerRef.value?.clientWidth
|
||||
if (chartInstance)
|
||||
chartInstance.setOption(buildOption(data.value, w), { replaceMerge: ['series'] })
|
||||
} else {
|
||||
updateChartData()
|
||||
}
|
||||
})
|
||||
watch(selectedTimeRange, () => updateChartData())
|
||||
|
||||
// CLOB:当有 market 且存在 clobTokenIds 时连接(使用 Yes/No token ID)
|
||||
const clobTokenIds = computed(() => {
|
||||
|
||||
@ -471,7 +471,8 @@
|
||||
>
|
||||
<div class="history-mobile-row">
|
||||
<div class="history-mobile-icon" :class="h.iconClass">
|
||||
<span class="position-icon-char">{{ h.iconChar }}</span>
|
||||
<img v-if="h.imageUrl" :src="h.imageUrl" alt="" class="position-icon-img" />
|
||||
<span v-else class="position-icon-char">{{ h.iconChar || '•' }}</span>
|
||||
</div>
|
||||
<div class="history-mobile-main">
|
||||
<div class="history-mobile-title">{{ h.market }}</div>
|
||||
@ -920,6 +921,8 @@ interface HistoryItem {
|
||||
shares?: string
|
||||
iconChar?: string
|
||||
iconClass?: string
|
||||
/** 图标 URL(来自 record.icon) */
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
const positions = ref<Position[]>(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user