Skip to content

最流畅的更新 ​

本节目标:理解“最流畅的更新”的核心思路,并能把它用于实际开发或面试表达。

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import WebSocketService from '@/services/websocketService'
import { getToken } from '@/utils/auth'
export interface SymbolSvg {
  svg: string
  svgb: string
}
export interface ForexSymbol {
  id: number
  symbol: string
  contact_size: string
  digits: string
  description: string
  type: string
  add: number
  symbol_svg: SymbolSvg
  is_favorite: boolean
  sort: string
}
export interface TradeForm {
  number: string //

数量

  type: 'lots' | 'shares' //

交易类型,假设可能的值为 'lots' 或

 'shares'
  pending: boolean //

是否挂单

  pending_number: string //

挂单数量

  take: boolean //

是否设置止盈

  take_number: string //

止盈数量

  take_type: 'price' | 'percent' //

止盈类型,假设可能的值为 'price' 或

 'percent'
  stop: boolean //

是否设置止损

  stop_number: string //

止损数量

  stop_type: 'price' | 'percent' //

止损类型,假设可能的值为 'price' 或

 'percent'
  ask: number //

买价格

  bid: number //

卖价

  status: 'buy' | 'sell' //

交易状态

}
export interface PricePayload {
  ask: number
  bid: number
  ts: number
  dayOpen: number
  dayHigh: number
  dayLow: number
  preClose: number
}
const useTradeViewStore = defineStore(
  'tradeView',
  () => {
    const tabs = ref<ForexSymbol[]>([])
    const currentTab = ref<ForexSymbol | null>(null)
    const wsSubList = ref<string[]>([])
    const cData = ref<unknown>(null)
    const tradeForm = ref<TradeForm>({
      number: '',
      type: 'lots',
      pending: false,
      pending_number: '',
      take: false, //

止盈

      take_number: '',
      take_type: 'price',
      stop: false, //

止损

      stop_number: '',
      stop_type: 'price',
      ask: 0, //

买价格

      bid: 0, //

卖价

      status: 'buy', // buy / sell
    })
    const rowMap = computed(() => {
      const _map = new Map<string, ForexSymbol>()
      for (const item of fullDataList.value) {
        _map.set(item.symbol, item)
      }
      return _map
    })
    const fullDataList = ref<ForexSymbol[]>([])
    const priceQueue = new Map<string, PricePayload[]>()
    let rafId: number | null = null
    function scheduleFlush() {
      if (rafId === null) rafId = requestAnimationFrame(flushQueue)
    }
    function flushQueue() {
      priceQueue.forEach((queue, symbol) => {
        const last = queue[queue.length - 1] //

只取最新一条

        queue.length = 0 //

清空队列

        const row = rowMap.value.get(symbol)
        if (!row) return
        //

对比旧值 ── 只改动真的发生变化的字段

        if (last.bid !== row.bid) {
          row.bidTrend = last.bid > row.bid ? 'up' : 'down'
          row.bid = last.bid
          clearTrendLater(row, 'bidTrend')
        }
        if (last.ask !== row.ask) {
          row.askTrend = last.ask > row.ask ? 'up' : 'down'
          row.ask = last.ask
          clearTrendLater(row, 'askTrend')
        }
        /* ===

如果还需写入 dayOpen/dayHigh 等,可同理赋值

 === */
      })
      rafId = null
    }
    /* ======== 500 ms

后自动移除闪烁类

 ======== */
    function clearTrendLater(row: TableRow, key: 'bidTrend' | 'askTrend') {
      setTimeout(() => {
        row[key] = null
      }, 500)
    }
    async function initNavTabs(list: ForexSymbol[], favoritesTop4: ForexSymbol[]) {
      if (!tabs.value.length) {
        if (favoritesTop4.length) {
          tabs.value = favoritesTop4
        } else {
          tabs.value = list.slice(0, 4)
        }
        currentTab.value =
          tabs.value.find((t) => t.symbol === currentTab.value?.symbol) ?? tabs.value[0] ?? null
      }
    }
    function initWebSocket(host: string, srv: string, loginid: string) {
      const token = getToken()
      // const url = `${host}?auth=${token}&servername=${srv}&loginid=${loginid}`
      const url = `ws://122.51.27.47:8099/ws?auth=${token}&servername=${srv}&loginid=${loginid}`
      const svc = WebSocketService.getInstance()
      svc.connect(url)
      svc.addEventListener('message', handleIncomingData)
    }
    function handleIncomingData(raw: unknown) {
      let txt = ''
      if (typeof raw === 'string') txt = raw.trim()
      else if (raw instanceof ArrayBuffer) txt = new TextDecoder().decode(raw)
      else return //

非文本忽略

      const parts = txt.split(/\s+/)
      if (parts.length !== 8) return //

非行情消息忽略

      const [symbol, askStr, bidStr, tsStr, open, high, low, pre] = parts
      const payload: PricePayload = {
        ask: +askStr,
        bid: +bidStr,
        ts: +tsStr,
        dayOpen: +open,
        dayHigh: +high,
        dayLow: +low,
        preClose: +pre,
      }
      if (!priceQueue.has(symbol)) priceQueue.set(symbol, [])
      priceQueue.get(symbol)!.push(payload)
      scheduleFlush()
    }
    //

订阅或更新订阅:mode='append' 追加,'update' 覆盖

    function subscribe(symbols: string[], mode: 'append' | 'update' = 'update') {
      const svc = WebSocketService.getInstance()
      const currentSubscribedSymbols = wsSubList.value //

当前已订阅的列表

      const currentSubscribedSet = new Set(currentSubscribedSymbols) //

当前已订阅的 Set 形式,用于高效查找

      const effectiveTargetSymbolsSet = new Set(symbols)
      const currentActiveSymbol = currentTab.value?.symbol
      if (currentActiveSymbol) {
        effectiveTargetSymbolsSet.add(currentActiveSymbol)
      }
      let symbolsToActuallySubscribe: string[] = []
      let symbolsToActuallyUnsubscribe: string[] = []
      if (mode === 'append') {
        symbolsToActuallySubscribe = Array.from(effectiveTargetSymbolsSet).filter(
          (s) => !currentSubscribedSet.has(s),
        )
        if (symbolsToActuallySubscribe.length > 0) {
          wsSubList.value.push(...symbolsToActuallySubscribe)
        }
      } else {
        symbolsToActuallyUnsubscribe = currentSubscribedSymbols.filter(
          (s) => !effectiveTargetSymbolsSet.has(s),
        )
        symbolsToActuallySubscribe = Array.from(effectiveTargetSymbolsSet).filter(
          (s) => !currentSubscribedSet.has(s),
        )
        wsSubList.value = Array.from(effectiveTargetSymbolsSet)
      }
      if (symbolsToActuallyUnsubscribe.length > 0) {
        svc.send(`unsub:${symbolsToActuallyUnsubscribe.join(';')};`)
      }
      if (symbolsToActuallySubscribe.length > 0) {
        svc.send(`sub:${symbolsToActuallySubscribe.join(';')};`)
      }
      if (symbolsToActuallyUnsubscribe.length === 0 && symbolsToActuallySubscribe.length === 0) {
      }
    }
    //

取消所有订阅

    function unsubscribeAll() {
      WebSocketService.getInstance().send('unsuball')
      wsSubList.value = []
    }
    //

更新当前激活的标签

    function updateCurrentTab(tab: ForexSymbol) {
      currentTab.value = tab
    }
    function delTab(tab: ForexSymbol) {
      tabs.value = tabs.value.filter((t) => t.symbol !== tab.symbol)
    }
    //

更新标签数组并持久化

    function updateTradingTabs(tabsOrTab: ForexSymbol | ForexSymbol[]) {
      const arr = Array.isArray(tabsOrTab) ? tabsOrTab : [tabsOrTab]
      tabs.value = arr
    }
    return {
      tabs,
      currentTab,
      wsSubList,
      cData,
      tradeForm,
      fullDataList,
      initNavTabs,
      initWebSocket,
      handleIncomingData,
      subscribe,
      unsubscribeAll,
      updateCurrentTab,
      updateTradingTabs,
      delTab,
    }
  },
  {
    persist: {
      key: 'trade-view', // localStorage

键

      pick: ['tabs', 'currentTab', 'tradeForm'], //

只持久化这两个字段

      debug: import.meta.env.DEV, //

调试环境下输出错误

    },
  },
)
export default useTradeViewStore

本站总访问 — 次