diff --git a/src/api/baidu/mapWsQuote.ts b/src/api/baidu/mapWsQuote.ts index 9e8ba46..215bd4d 100644 --- a/src/api/baidu/mapWsQuote.ts +++ b/src/api/baidu/mapWsQuote.ts @@ -58,7 +58,7 @@ function upsertIntraday( const price = toNum(priceRaw) const avg = toNum(avgRaw) const totalVolume = toNum(totalVolumeRaw) - const list = [...points] + const list = points.slice() const idx = list.findIndex((p) => p.time === time) const othersSum = list.reduce((s, p, i) => (i === idx ? s : s + p.volume), 0) @@ -73,8 +73,11 @@ function upsertIntraday( return list } -/** Merge tick detailinfos into quote.trades (newest first, dedupe, cap 200). */ -export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): QuoteData { +/** + * Merge tick detailinfos into quote.trades (newest first, dedupe, cap 200). + * Mutates quote in place so Vue only invalidates trade-dependent effects. + */ +export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): void { const incoming: TradeTick[] = [...(data.detailinfos ?? [])] .reverse() .map((t) => ({ @@ -93,26 +96,27 @@ export function applyWsTick(quote: QuoteData, data: BaiduWsTickData): QuoteData merged.push(t) if (merged.length >= MAX_TRADES) break } - return { ...quote, trades: merged } + quote.trades = merged } -/** Apply snapshot fields onto quote (price, book, pankou, intraday point). */ -export function applyWsSnapshot(quote: QuoteData, data: BaiduWsSnapshotData): QuoteData { - const next: QuoteData = { ...quote } - +/** + * Apply snapshot fields onto quote (price, book, pankou, intraday point). + * Mutates quote in place — do not replace quote.value, or every tick rebuilds charts. + */ +export function applyWsSnapshot(quote: QuoteData, data: BaiduWsSnapshotData): void { if (data.cur) { const c = data.cur - if (c.price != null) next.last = toNum(c.price, next.last) - if (c.increase != null) next.change = toNum(c.increase, next.change) - if (c.ratio != null) next.changePercent = toNum(c.ratio, next.changePercent) - if (c.avgPrice != null) next.avg = toNum(c.avgPrice, next.avg) - if (c.status) next.status = c.status + if (c.price != null) quote.last = toNum(c.price, quote.last) + if (c.increase != null) quote.change = toNum(c.increase, quote.change) + if (c.ratio != null) quote.changePercent = toNum(c.ratio, quote.changePercent) + if (c.avgPrice != null) quote.avg = toNum(c.avgPrice, quote.avg) + if (c.status) quote.status = c.status } if (data.update) { - if (data.update.text) next.updatedAt = data.update.text - if (data.update.stockStatus) next.status = data.update.stockStatus - else if (data.update.tradeStatusCN) next.status = data.update.tradeStatusCN + if (data.update.text) quote.updatedAt = data.update.text + if (data.update.stockStatus) quote.status = data.update.stockStatus + else if (data.update.tradeStatusCN) quote.status = data.update.tradeStatusCN } if (data.pankouinfos?.length) { @@ -120,20 +124,20 @@ export function applyWsSnapshot(quote: QuoteData, data: BaiduWsSnapshotData): Qu const num = (ename: string, fallback: number) => by[ename] ? toNum(by[ename]!.originValue ?? by[ename]!.value, fallback) : fallback - next.open = num('open', next.open) - next.high = num('high', next.high) - next.low = num('low', next.low) - next.prevClose = num('preClose', next.prevClose) - next.volume = num('volume', next.volume) - next.openInterest = num('holdingAmount', next.openInterest) - next.amplitude = num('amplitudeRatio', next.amplitude) - next.settlement = num('settlement', next.settlement) - next.prevSettlement = num('prevSettlement', next.prevSettlement) - next.outerVol = num('outside', next.outerVol) - next.innerVol = num('inside', next.innerVol) - if (by.avgPrice) next.avg = num('avgPrice', next.avg) + quote.open = num('open', quote.open) + quote.high = num('high', quote.high) + quote.low = num('low', quote.low) + quote.prevClose = num('preClose', quote.prevClose) + quote.volume = num('volume', quote.volume) + quote.openInterest = num('holdingAmount', quote.openInterest) + quote.amplitude = num('amplitudeRatio', quote.amplitude) + quote.settlement = num('settlement', quote.settlement) + quote.prevSettlement = num('prevSettlement', quote.prevSettlement) + quote.outerVol = num('outside', quote.outerVol) + quote.innerVol = num('inside', quote.innerVol) + if (by.avgPrice) quote.avg = num('avgPrice', quote.avg) if (by.amount) { - next.amount = parseAmountYi(by.amount.value, by.amount.originValue) + quote.amount = parseAmountYi(by.amount.value, by.amount.originValue) } if (by.amountDelta) { const raw = by.amountDelta.originValue @@ -141,7 +145,7 @@ export function applyWsSnapshot(quote: QuoteData, data: BaiduWsSnapshotData): Qu const unavailable = (raw == null || !Number.isFinite(raw)) && (!display || display === '--') if (!unavailable) { - next.amountDelta = parseAmountDelta(display, raw) + quote.amountDelta = parseAmountDelta(display, raw) } } } @@ -166,20 +170,18 @@ export function applyWsSnapshot(quote: QuoteData, data: BaiduWsSnapshotData): Qu const bidVol = bids.reduce((s, b) => s + b.volume, 0) const askVol = asks.reduce((s, a) => s + a.volume, 0) const total = bidVol + askVol - next.orderBook = { asks, bids } - next.buyRatio = total > 0 ? Math.round((bidVol / total) * 100) : 50 - next.sellRatio = 100 - next.buyRatio + quote.orderBook = { asks, bids } + quote.buyRatio = total > 0 ? Math.round((bidVol / total) * 100) : 50 + quote.sellRatio = 100 - quote.buyRatio } if (data.point) { - next.intraday = upsertIntraday( - next.intraday, + quote.intraday = upsertIntraday( + quote.intraday, data.point.price, data.point.avgPrice, data.point.time, data.point.totalVolume, ) } - - return next } diff --git a/src/components/quote/ChartPanel.vue b/src/components/quote/ChartPanel.vue index 439ecbc..463f33c 100644 --- a/src/components/quote/ChartPanel.vue +++ b/src/components/quote/ChartPanel.vue @@ -6,12 +6,12 @@ - + diff --git a/src/composables/useAiAdvice.ts b/src/composables/useAiAdvice.ts index fe50436..aa6f343 100644 --- a/src/composables/useAiAdvice.ts +++ b/src/composables/useAiAdvice.ts @@ -108,8 +108,10 @@ export function useAiAdvice() { snapshot: quota.getAnalysisSnapshot(), } - console.log('[AI] 提交给 DeepSeek 的关键词', payload.keywords) - console.log('[AI] 提交给 DeepSeek 的数据快照', payload.snapshot) + // 勿 console.log 完整 snapshot:DevTools 会长期持有大对象导致内存暴涨 + if (import.meta.env.DEV) { + console.log('[AI] 分析', payload.snapshot.contract.code, payload.keywords.slice(0, 80)) + } const advice = await fetchDeepSeekAdvice(apiKey, payload) if (epoch !== analysisEpoch) return diff --git a/src/stores/quota.ts b/src/stores/quota.ts index 93413bd..39319af 100644 --- a/src/stores/quota.ts +++ b/src/stores/quota.ts @@ -137,10 +137,11 @@ export const useQuotaStore = defineStore('quota', () => { const code = activeContract().code if (msg.data.code && msg.data.code !== code) return const product = msg.data.product + // Mutate in place — replacing quote.value forces every chart/UI to rebuild. if (product === 'tick') { - quote.value = applyWsTick(quote.value, msg.data as BaiduWsTickData) + applyWsTick(quote.value, msg.data as BaiduWsTickData) } else if (product === 'snapshot') { - quote.value = applyWsSnapshot(quote.value, msg.data as BaiduWsSnapshotData) + applyWsSnapshot(quote.value, msg.data as BaiduWsSnapshotData) } } @@ -181,10 +182,11 @@ export const useQuotaStore = defineStore('quota', () => { startSessionWatch() } - /** (Re)connect only while page wants WS and within trading session. */ + /** Connect only while page wants WS and within trading session; skip if already running. */ function connectWs() { if (!wsDesired || !isWsTradingSession()) return wsInSession.value = true + if (quoteWs?.isRunning()) return quoteWs?.disconnect() quoteWs = new BaiduQuoteWs({ onMessage: handleWsMessage,