一个简单的tradingview+web socket+vue的项目

特斯比特 2021-11-18 10:27:06

最开始接触tradingview摸索了很久之前的项目是前后端不分离的后来公司需要重构项目用vue来写网上查了一下结合文档踩了许多坑自己的摸索出来的
下面是项目结构
在这里插入图片描述

先贴一下socket 代码
js-api datafees.js

/**
 * JS API
 */import DataUpdater from './dataUpdater'class datafeeds {
  /**
     * JS API
     * @param {*Object} vue vue实例
     */
  constructor(vue) {
    this.self = vue    this.barsUpdater = new DataUpdater(this)
  }
  /**
     * @param {*Function} callback  回调函数
     * `onReady` should return result asynchronously.
     */
  onReady(callback) {
    return new Promise((resolve, reject) => {
      let configuration = this.defaultConfiguration()
      if (this.self.getConfig) {
        configuration = Object.assign(this.defaultConfiguration(), this.self.getConfig())
      }
      resolve(configuration)
    }).then(data => callback(data))
  }

  /**
   * @param {*String} symbolName  商品名称或ticker
   * @param {*Function} onSymbolResolvedCallback 成功回调
   * @param {*Function} onResolveErrorCallback   失败回调
   * `resolveSymbol` should return result asynchronously.
   */
  resolveSymbol(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
    return new Promise((resolve, reject) => {
      let symbolInfo = this.defaultSymbol()
      if (this.self.getSymbol) {
        symbolInfo = Object.assign(this.defaultSymbol(), this.self.getSymbol())
      }
      resolve(symbolInfo)
    }).then(data => onSymbolResolvedCallback(data)).catch(err => onResolveErrorCallback(err))
  }
  /**
     * @param {*Object} symbolInfo  商品信息对象
     * @param {*String} resolution  分辨率
     * @param {*Number} rangeStartDate  时间戳、最左边请求的K线时间
     * @param {*Number} rangeEndDate  时间戳、最右边请求的K线时间
     * @param {*Function} onDataCallback  回调函数
     * @param {*Function} onErrorCallback  回调函数
     */
  getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onDataCallback, onErrorCallback) {
    const onLoadedCallback = data => {
      data && data.length ? onDataCallback(data, { noData: true }) : onDataCallback([], { noData: true })
    }
    this.self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
  }
  /**
     * 订阅K线数据。图表库将调用onRealtimeCallback方法以更新实时数据
     * @param {*Object} symbolInfo 商品信息
     * @param {*String} resolution 分辨率
     * @param {*Function} onRealtimeCallback 回调函数
     * @param {*String} subscriberUID 监听的唯一标识符
     * @param {*Function} onResetCacheNeededCallback (从1.7开始): 将在bars数据发生变化时执行
     */
  subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) {
    this.barsUpdater.subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback)
  }
  /**
     * 取消订阅K线数据
     * @param {*String} subscriberUID 监听的唯一标识符
     */
  unsubscribeBars(subscriberUID) {
    this.barsUpdater.unsubscribeBars(subscriberUID)
  }
  /**
     * 默认配置
     */
  defaultConfiguration() {
    return {
      supports_search: true,
      supports_group_request: false,
      supported_resolutions: ['1', '5', '15', '30', '60', '1D', '1W', '1M'],
      supports_marks: true,
      supports_timescale_marks: true,
      supports_time: true
    }
  }
  /*
     * 默认商品信息
     */
  defaultSymbol() {
    return {
      'name': this.self.symbol.toLocaleUpperCase(),
      'timezone': 'Asia/Shanghai',
      'minmov': 1,
      'minmov2': 0,
      'pointvalue': 8,
      'fractional': false,
      'session': '24x7',
      'has_intraday': true,
      'has_no_volume': false,
      'has_weekly_and_monthly': true,
      'description': this.self.symbol.toLocaleUpperCase(),
      'pricescale': this.self.pricescale * 1,
      'ticker': this.self.symbol.toLocaleUpperCase(),
      'supported_resolutions': ['1', '5', '15', '30', '60', '1D', '1W', '1M']
    }
  }}export default datafeeds

数据更新器文件dataUpdaters.js

/**
 * 数据更新器
 * 通过更新器触发datafeeds的getBars实时更新图表数据
 */class dataUpdater {
  constructor(datafeeds) {
    this.subscribers = {}
    this.requestsPending = 0
    this.historyProvider = datafeeds  }
  subscribeBars(symbolInfo, resolution, newDataCallback, listenerGuid) {
    this.subscribers[listenerGuid] = {
      lastBarTime: null,
      listener: newDataCallback,
      resolution: resolution,
      symbolInfo: symbolInfo    }
  }
  unsubscribeBars(listenerGuid) {
    delete this.subscribers[listenerGuid]
  }

  // 更新图表
  updateData() {
    if (this.requestsPending) return
    this.requestsPending = 0
    for (const listenerGuid in this.subscribers) {
      this.requestsPending++
      this.updateDataForSubscriber(listenerGuid).then(() => this.requestsPending--).catch(() => this.requestsPending--)
    }
  }
  updateDataForSubscriber(listenerGuid) {
    return new Promise((resolve, reject) => {
      const subscriptionRecord = this.subscribers[listenerGuid]
      const rangeEndTime = parseInt((Date.now() / 1000).toString())
      const rangeStartTime = rangeEndTime - this.periodLengthSeconds(subscriptionRecord.resolution, 10)
      this.historyProvider.getBars(subscriptionRecord.symbolInfo, subscriptionRecord.resolution, rangeStartTime, rangeEndTime,
        bars => {
          this.onSubscriberDataReceived(listenerGuid, bars)
          resolve()
        },
        () => {
          reject()
        }
      )
    })
  }
  onSubscriberDataReceived(listenerGuid, bars) {
    if (!this.subscribers.hasOwnProperty(listenerGuid)) return
    if (!bars.length) return
    const lastBar = bars[bars.length - 1]
    const subscriptionRecord = this.subscribers[listenerGuid]
    if (subscriptionRecord.lastBarTime !&#61;&#61; null && lastBar.time < subscriptionRecord.lastBarTime) return
    const isNewBar &#61; subscriptionRecord.lastBarTime !&#61;&#61; null && lastBar.time > subscriptionRecord.lastBarTime    if (isNewBar) {
      if (bars.length < 2) {
        throw new Error('Not enough bars in history for proper pulse update. Need at least 2.')
      }
      const previousBar &#61; bars[bars.length - 2]
      subscriptionRecord.listener(previousBar)
    }
    subscriptionRecord.lastBarTime &#61; lastBar.time
    subscriptionRecord.listener(lastBar)
  }
  periodLengthSeconds(resolution, requiredPeriodsCount) {
    let daysCount &#61; 0
    if (resolution &#61;&#61;&#61; 'D' || resolution &#61;&#61;&#61; '1D') {
      daysCount &#61; requiredPeriodsCount    } else if (resolution &#61;&#61;&#61; 'M' || resolution &#61;&#61;&#61; '1M') {
      daysCount &#61; 31 * requiredPeriodsCount    } else if (resolution &#61;&#61;&#61; 'W' || resolution &#61;&#61;&#61; '1W') {
      daysCount &#61; 7 * requiredPeriodsCount    } else {
      daysCount &#61; requiredPeriodsCount * parseInt(resolution) / (24 * 60)
    }
    // console.log(daysCount * 24 * 60 * 60, &#34;'''''''''''''''&#34;)
    return daysCount * 24 * 60 * 60
  }}export default dataUpdater

socket 文件socket.js

import pako from 'pako'class socket {
  constructor(url &#61; 'ws://192.168.100.176:8000', options) {
    this.heartBeatTimer &#61; null
    this.options &#61; options    this.messageMap &#61; {}
    this.connState &#61; 0
    this.socket &#61; null
    this.url &#61; url  }
  doOpen() {
    if (this.connState) return
    this.connState &#61; 1
    this.afterOpenEmit &#61; []
    const BrowserWebSocket &#61; window.WebSocket || window.MozWebSocket    const socket &#61; new BrowserWebSocket(this.url)
    socket.binaryType &#61; 'arraybuffer'
    socket.onopen &#61; evt &#61;> this.onOpen(evt)
    socket.onclose &#61; evt &#61;> this.onClose(evt)
    socket.onmessage &#61; evt &#61;> this.onMessage(evt.data)
    socket.onerror &#61; err &#61;> this.onError(err)
    this.socket &#61; socket  }
  onOpen() {
    this.connState &#61; 2
    this.heartBeatTimer &#61; setInterval(this.checkHeartbeat.bind(this), 20000)
    this.onReceiver({
      Event: 'open'
    })
  }
  checkOpen() {
    return this.connState &#61;&#61;&#61; 2
  }
  onClose() {
    this.connState &#61; 0
    if (this.connState) {
      this.onReceiver({
        Event: 'close'
      })
    }
  }
  send(data) {
    this.socket.send(JSON.stringify(data))
  }
  emit(data) {
    return new Promise(resolve &#61;> {
      this.socket.send(JSON.stringify(data))
      this.on('message', data &#61;> {
        resolve(data)
      })
    })
  }
  onMessage(message) {
    try {
      let data &#61; []
      if (message instanceof ArrayBuffer) {
        data &#61; JSON.parse(pako.inflate(message, { to: 'string' }))
      } else {
        data &#61; JSON.parse(message)
      }
      this.onReceiver({
        Event: 'message',
        Data: data      })
    } catch (err) {
      // console.error(' >> Data parsing error:', err)
    }
  }
  checkHeartbeat() {
    const date &#61; Date.parse(new Date())
    const data &#61; {
      'cmd': 'ping',
      'args': [date]
    }

    this.pingDate &#61; date    // localStorage.pingDate &#61; date
    this.send(data)
  }
  onError() {
    // console.error(' >> Data parsing error:', err)
  }
  onReceiver(data) {
    const callback &#61; this.messageMap[data.Event]
    if (callback) callback(data.Data)
  }

  on(name, handler) {
    this.messageMap[name] &#61; handler  }
  doClose() {
    this.socket.close()
  }
  destroy() {
    if (this.heartBeatTimer) {
      clearInterval(this.heartBeatTimer)
      this.heartBeatTimer &#61; null
    }
    // this.doClose()
    this.messageMap &#61; {}
    this.connState &#61; 0
    // this.socket &#61; null
  }}export default socket

创建一个index.vue文件先写入模板文件

<template>
  <div id&#61;&#34;trade-view&#34; :class&#61;&#34;isFullscreen?'full':''&#34;/></template>

再引入如下代码

import screenfull from 'screenfull'    //全屏插件 图表有默认的全屏按钮我这里是自定义全屏按钮var TvWidget &#61; window.TradingView.widget  //引入图表import Socket from './datafeeds/socket.js'  //引入socketimport Datafeeds from './datafeeds/datafees.js' //引入图表JSAPI  

这是文档对jsapi的解释
在这里插入图片描述
图表的两个主要参数

 props: {
 	//交易对值
    symbolValue: {
      default: 'BTCUSDT',
      type: String    },
    // 时间周期值
    intervalValue: {
      default: '1',
      type: String    }
  },

data里面的一些值

data() {
    return {
      isFullscreen: false,
      widget: null,
      socket: new Socket('wss://mqs.coinka.cn/ws/v1/mqs/kline'),
      datafeeds: new Datafeeds(this),
      symbol: this.symbolValue,
      interval: this.intervalValue,
      cacheData: {},
      lastTime: null,
      getBarTimer: null,
      isLoading: true,
      debug: true,
      pricescale: 100,
       //精度
      pointvalue: 8,
     
      overridesBlack: {
        'volumePaneSize': 'small', // &#34;volumePaneSize&#34; : &#34;large&#34;支持的值: large默认, medium, small, tiny//  白色蜡烛样式
        'mainSeriesProperties.style': 1,
        'mainSeriesProperties.candleStyle.downColor': '#ee6560', // K线颜色
        'mainSeriesProperties.candleStyle.upColor': '#4db872',
        'mainSeriesProperties.candleStyle.borderDownColor': '#ee6560', // 边框颜色
        'mainSeriesProperties.candleStyle.borderUpColor': '#4db872',
        'mainSeriesProperties.candleStyle.wickDownColor': '#ee6560', // 烛芯颜色
        'mainSeriesProperties.candleStyle.wickUpColor': '#4db872',
        'paneProperties.vertGridProperties.color': '#293241', // 格子线条
        'paneProperties.horzGridProperties.color': '#293241',
        'paneProperties.vertGridProperties.style': 0,
        'paneProperties.horzGridProperties.style': 0,
        'paneProperties.legendProperties.showLegend': false,
        'paneProperties.topMargin': 10, // K线离顶部的距离/百分比
        'paneProperties.bottomMargin': 35, // K线离底部的距离/百分比
        'hide_left_toolbar_by_default': 'hidden',
        'symbolWatermarkProperties.color': 'rgba(0,0,0,0)',
        'paneProperties.background': '#192233', // 背景颜色
        'scalesProperties.backgroundColor': '#192233',
        'scalesProperties.fontSize': 12,
        'scalesProperties.lineColor': '#293241', // 边框线条颜色
        'scalesProperties.textColor': 'rgba(255,255,255,0.45)'
      }
    }
  },

初始化socket并请求数据

 created() {
    this.socket.doOpen()
    this.socket.on('open', () &#61;> {
      this.socket.send({ cmd: 'req', args: ['candle.' &#43; this.interval &#43; '.' &#43; this.symbol.toLowerCase(), 300, parseInt(Date.now() / 1000)] })
    })

    this.socket.on('message', this.onMessage)
    this.socket.on('close', this.onClose)
    this.socket.on('error', event &#61;> {
      console.log('home.vue websocket 出错了', this.onClose)
    })
  },

 methods: {
    // 全屏切换
    fullScreen() {
      var what &#61; this
      screenfull.toggle() // 切换全屏
      if (screenfull.isEnabled) {
        screenfull.on('change', () &#61;> {
          if (screenfull.isFullscreen) {
            // 全屏时要执行的操作
            what.isFullscreen &#61; true
          } else {
            // 取消全屏时要执行的操作
            what.isFullscreen &#61; false
          }
        })
      }
    },
    init() {
      if (!this.widget) {
        this.widget &#61; new TvWidget({
          symbol: this.symbol,
          interval: this.interval,
          autosize: true,
          fullscreen: false,
          container_id: 'trade-view',
          datafeed: this.datafeeds,
          library_path: '/charting_library/',
          timezone: 'Asia/Shanghai',
          locale: this.lang,
          debug: false,
          disabled_features: [
            'save_chart_properties_to_local_storage', // 本地存储
            'header_symbol_search', // 搜索
            'symbol_search_hot_key',
            'header_interval_dialog_button',
            'header_screenshot', // 照相机
            'header_compare',
            'timeframes_toolbar', // 底部时间栏目
            // 'volume_force_overlay', // k线与销量分开
            'header_undo_redo', // 左右箭头
            // &#34;header_settings&#34;,//设置按钮
            'header_indicators', // 技术指标线
            'header_chart_type', // 图表类型
            'pane_context_menu', // 图表右键菜单
            'header_resolutions', // 系统默认时间按钮
            // &#34;hide_left_toolbar&#34;, //左边工具栏 hide_left_toolbar_by_default
            'header_saveload',
            // &#34;display_market_status&#34;,
            'main_series_scale_menu', // 显示图表右下角的设置按钮
            'control_bar',
            'caption_buttons_text_if_possible',
            'header_widget'

          ],
          enabled_features: [
            'header_widget_dom_node',
            'use_localstorage_for_settings',
            'keep_left_toolbar_visible_on_small_screens', // 防止左侧工具栏在小屏幕上消失
            'adaptive_logo',
            'property_pages',
            'display_market_status',
            'remove_library_container_border',
            'move_logo_to_main_pane',
            'dont_show_boolean_study_arguments', // 是否隐藏指标参数
            'countdown',
            'caption_buttons_text_if_possible', // 在可能的情况下在标题中的“指标”和“比较”按钮上显示文字而不是图标
            'header_settings',
            'hide_last_na_study_output', // 隐藏最后一次指标输出
            'symbol_info', // 商品信息对话框
            'hide_left_toolbar_by_default'
          ],
          // preset: &#34;mobile&#34;,
          customFormatters: {
            dateFormatter: {
              format: function(date) {
                return date.getUTCFullYear() &#43; '/' &#43; (date.getUTCMonth() &#43; 1) &#43; '/' &#43; date.getUTCDate()
              }
            } // 时间格式

          },
          overrides: this.overridesBlack,
          studies_overrides: {
            'bollinger bands.median.color': '#33FF88',
            'bollinger bands.upper.linewidth': 7,
            // &#34;volume.precision&#34; : 1
            'volume.volume.color.0': '#ee6560',
            'volume.volume.color.1': '#4db872',
            'volume.volume.transparency': 75
          }
          // custom_css_url: './chart.css'
        })
        // var this &#61; this.widget
        this.widget.onChartReady(() &#61;> {
          this.widget            .chart()
            .createStudy('Moving Average', true, false, [5], null, {
              'plot.color': '#99aac7'
            })
          this.widget            .chart()
            .createStudy('Moving Average', false, false, [15], null, {
              'plot.color': '#e9e12f'
            })
          this.widget            .chart()
            .createStudy('Moving Average', false, false, [30], null, {
              'plot.color': '#2026dc'
            })
          this.widget            .chart()
            .createStudy('Moving Average', false, false, [60], null, {
              'plot.color': '#a109ef'
            })
        })
      }
    },
    sendMessage(data) {
      if (this.socket.socket.readyState !&#61;&#61; this.socket.socket.OPEN) {
        this.socket.doOpen()
      }
      if (this.socket.checkOpen()) {
        this.socket.send(data)
      } else {
        this.socket.on('open', () &#61;> {
          this.socket.send(data)
        })
      }
    },
    unSubscribe(interval) {
      // 停止订阅删除过期缓存、缓存时间、缓存状态
      this.interval &#61; interval      var ticker &#61; this.symbol &#43; '-' &#43; interval      var tickertime &#61; ticker &#43; 'load'
      var tickerstate &#61; ticker &#43; 'state'
      var tickerCallback &#61; ticker &#43; 'Callback'
      delete this.cacheData[ticker]
      delete this.cacheData[tickertime]
      delete this.cacheData[tickerstate]
      delete this.cacheData[tickerCallback]
      this.sendMessage({
        cmd: 'unsub',
        args: ['candle.' &#43; interval &#43; '.' &#43; this.symbol.toLowerCase()]
      })
    },
    onMessage(data) {
      var ticker &#61; this.symbol &#43; '-' &#43; this.interval      if (data.name &#61;&#61;&#61; 'kline') {
        // websocket返回的值数组代表时间段历史数据不是增量
        var list &#61; []

        var tickerstate &#61; ticker &#43; 'state'
        var tickerCallback &#61; ticker &#43; 'Callback'
        var onLoadedCallback &#61; this.cacheData[tickerCallback]
        var kline &#61; data.data.kline || ''
        kline.forEach((element) &#61;> {
          list.push({
            time: element.time,
            open: parseFloat(element.open),
            high: parseFloat(element.high),
            low: parseFloat(element.low),
            close: parseFloat(element.close),
            volume: parseFloat(element.volume)
          })
        })
        // 如果没有缓存数据则直接填充发起订阅
        if (!this.cacheData[ticker]) {
          this.cacheData[ticker] &#61; list          // this.subscribe()
        }
        // 新数据即当前时间段需要的数据直接喂给图表插件
        if (onLoadedCallback) {
          onLoadedCallback(list)
          delete this.cacheData[tickerCallback]
        }
        // 请求完成设置状态为false
        this.cacheData[tickerstate] &#61; !1
        // 记录当前缓存时间即数组最后一位的时间
        if (this.cacheData[ticker].length > 0) {
          this.lastTime &#61; this.cacheData[ticker][this.cacheData[ticker].length - 1].time        }
      }

      if (data.name &#61;&#61;&#61; 'kline_real') {
        var result &#61; data.data.kline        // console.log(' >> sub:', data.type)
        // console.log(' >> interval:', this.interval)
        // data带有type即返回的是订阅数据
        // 缓存的key
        // 构造增量更新数据
        var barsData &#61; {
          time: result.t,
          open: parseFloat(result.o),
          high: parseFloat(result.h),
          low: parseFloat(result.l),
          close: parseFloat(result.c),
          volume: parseFloat(result.v)
        }

        // 如果增量更新数据的时间大于缓存时间而且缓存有数据数据长度大于0
        if (barsData.time > this.lastTime && this.cacheData[ticker] && this.cacheData[ticker].length) {
          // 增量更新的数据直接加入缓存数组
          this.cacheData[ticker].push(barsData)
          // 修改缓存时间
          this.lastTime &#61; barsData.time        } else if (barsData.time &#61;&#61;&#61; this.lastTime && this.cacheData[ticker] && this.cacheData[ticker].length) {
          // 如果增量更新的时间等于缓存时间即在当前时间颗粒内产生了新数据更新当前数据
          this.cacheData[ticker][this.cacheData[ticker].length - 1] &#61; barsData        }
        // 通知图表插件可以开始增量更新的渲染了
        this.datafeeds.barsUpdater.updateData()
      }
    },
    initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
      // console.log('发起请求从websocket获取当前时间段的数据');
      // 保留当前回调
      var tickerCallback &#61; this.symbol &#43; '-' &#43; resolution &#43; 'Callback'
      this.cacheData[tickerCallback] &#61; onLoadedCallback      // 获取需要请求的数据数目
      var limit &#61; this.initLimit(resolution, rangeStartDate, rangeEndDate)
      // 商品名
      var symbol &#61; this.symbol      // 如果当前时间节点已经改变停止上一个时间节点的订阅修改时间节点值

      if (this.interval !&#61;&#61; resolution) {
        this.unSubscribe(this.interval)
        this.interval &#61; resolution      }
      // 获取当前时间段的数据在onMessage中执行回调onLoadedCallback
      this.socket.send({
        cmd: 'req',
        args: ['candle.' &#43; this.interval &#43; '.' &#43; symbol.toLowerCase(), limit, rangeEndDate]
        // id: 'trade.' &#43; that.interval &#43; '.' &#43; symbol.toLowerCase()
      })
    },
    initLimit(resolution, rangeStartDate, rangeEndDate) {
      var limit &#61; 0
      switch (resolution) {
        case '1D': limit &#61; Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24); break
        case '1W': limit &#61; Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24 / 7); break
        case '1M': limit &#61; Math.ceil((rangeEndDate - rangeStartDate) / 60 / 60 / 24 / 31); break
        default: limit &#61; Math.ceil((rangeEndDate - rangeStartDate) / 60 / resolution); break
      }
      return limit    },
    getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
      var ticker &#61; this.symbol &#43; '-' &#43; resolution      var tickerload &#61; ticker &#43; 'load'
      var tickerstate &#61; ticker &#43; 'state'
      if (!this.cacheData[ticker] && !this.cacheData[tickerstate]) {
        // 如果缓存没有数据而且未发出请求记录当前节点开始时间
        this.cacheData[tickerload] &#61; rangeStartDate        // 发起请求从websocket获取当前时间段的数据
        this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
        // 设置状态为true
        this.cacheData[tickerstate] &#61; !0
        return false
      }

      if (!this.cacheData[tickerload] || this.cacheData[tickerload] > rangeStartDate) {
        // 如果缓存有数据但是没有当前时间段的数据更新当前节点时间
        this.cacheData[tickerload] &#61; rangeStartDate        // 发起请求从websocket获取当前时间段的数据
        this.initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
        // 设置状态为true
        this.cacheData[tickerstate] &#61; !0
        return false
      }
      if (this.cacheData[tickerstate]) {
        // 正在从websocket获取数据禁止一切操作
        return false
      }
      ticker &#61; this.symbol &#43; '-' &#43; this.interval      if (this.cacheData[ticker] && this.cacheData[ticker].length) {
        this.isLoading &#61; false
        var newBars &#61; []
        this.cacheData[ticker].forEach(item &#61;> {
          if (item.time >&#61; rangeStartDate * 1000 && item.time <&#61; rangeEndDate * 1000) {
            newBars.push(item)
          }
        })
        onLoadedCallback(newBars)
      } else {
        var self &#61; this
        this.getBarTimer &#61; setTimeout(function() {
          self.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback)
        }, 100)
      }
    }
  }

然后我们需要的是将图表组件运行起来首先在app.vue中引入组件

<Tradingview ref&#61;&#34;trade&#34; :symbol&#61;&#34;symbol&#34; :interval&#61;&#34;interval&#34; />

import Tradingview from '&#64;/components/tradingview'

我这里是自定义分时按扭这里组件用的是Ant Design vue

 <a-tabs        :active-key&#61;&#34;resolution&#34;
        class&#61;&#34;resolutions charts-item&#34;
        &#64;change&#61;&#34;checkResolution&#34;
      >
        <a-tab-pane v-for&#61;&#34;(item,index) in btnList&#34; :key&#61;&#34;index&#43;''&#34; :tab&#61;&#34;item.label&#34; />
      </a-tabs>

data里面的一些值包含分时交易的对和分时按钮列表数组

  data() {
    return {
      resolution: 1,
      symbol: 'BTCUSDT',
      interval: '1D',
      btnList: [
        {
          label: '分时',
          resolution: '1',
          chartType: 3
        },
        {
          label: '1分',
          resolution: '1',
          chartType: 1
        },
        {
          label: '5分',
          resolution: '5',
          chartType: 1
        },
        {
          label: '15分',
          resolution: '15',
          chartType: 1
        },
        {
          label: '30分',
          resolution: '30',
          chartType: 1
        },
        {
          label: '1小时',
          resolution: '60',
          chartType: 1
        },
        {
          label: '1天',
          resolution: '1D',
          chartType: 1
        },
        {
          label: '1周',
          resolution: '1W',
          chartType: 1
        },
        {
          label: '1月',
          resolution: '1M',
          chartType: 1
        },
        {
          label: '指标',
          resolution: '',
          chartType: '8'
        },
        {
          label: '设置',
          resolution: '',
          chartType: '9'
        },
        {
          label: '全屏',
          resolution: '',
          chartType: '10'
        }
      ]
    }
  },

按钮方法

 methods: {
    // 切换分时
    checkResolution(key) {
      var ticker &#61; this.symbol &#43; '-' &#43; this.interval      var tickerstate &#61; ticker &#43; 'state'
      if (this.$refs.trade.cacheData[tickerstate]) {
        return false
      }
      const what &#61; this.$refs.trade.widget.chart()
      var item &#61; this.btnList[key]
      switch (key) {
        case '9':
          what.executeActionById('insertIndicator')
          //指标
          break
        case '10':
          what.executeActionById('chartProperties')
          // 设置
          break
        case '11':
          this.$refs.trade.fullScreen() 
          // 全屏
          break
        default:
          this.resolution &#61; key
          localStorage.resolutionIndex &#61; key
          localStorage.interval &#61; item.resolution
          what.setChartType(item.chartType)
          what.setResolution(item.resolution, function onReadyCallback() {})
          //切换分时
          break
      }
    }
  }

//初始化图表

mounted() {
    this.$refs.trade.init()
  },

//完整代码

<template>
  <div class&#61;&#34;app&#34;>
    <header class&#61;&#34;app__header&#34;>
      <a-tabs        :active-key&#61;&#34;resolution&#34;
        class&#61;&#34;resolutions charts-item&#34;
        &#64;change&#61;&#34;checkResolution&#34;
      >
        <a-tab-pane v-for&#61;&#34;(item,index) in btnList&#34; :key&#61;&#34;index&#43;''&#34; :tab&#61;&#34;item.label&#34; />
      </a-tabs>
    </header>
    <Tradingview ref&#61;&#34;trade&#34; :symbol&#61;&#34;symbol&#34; :interval&#61;&#34;interval&#34; />
  </div></template><script>import Tradingview from '&#64;/components/tradingview'export default {
  name: 'App',
  components: {
    Tradingview  },
  data() {
    return {
      resolution: 1,
      symbol: 'BTCUSDT',
      interval: '1D',
      btnList: [
        {
          label: '分时',
          resolution: '1',
          chartType: 3
        },
        {
          label: '1分',
          resolution: '1',
          chartType: 1
        },
        {
          label: '5分',
          resolution: '5',
          chartType: 1
        },
        {
          label: '15分',
          resolution: '15',
          chartType: 1
        },
        {
          label: '30分',
          resolution: '30',
          chartType: 1
        },
        {
          label: '1小时',
          resolution: '60',
          chartType: 1
        },
        {
          label: '1天',
          resolution: '1D',
          chartType: 1
        },
        {
          label: '1周',
          resolution: '1W',
          chartType: 1
        },
        {
          label: '1月',
          resolution: '1M',
          chartType: 1
        },
        {
          label: '指标',
          resolution: '',
          chartType: '8'
        },
        {
          label: '设置',
          resolution: '',
          chartType: '9'
        },
        {
          label: '全屏',
          resolution: '',
          chartType: '10'
        }
      ]
    }
  },
  mounted() {
    this.$refs.trade.init()
  },
  methods: {
    // 切换分时
    checkResolution(key) {
      var ticker &#61; this.symbol &#43; '-' &#43; this.interval      var tickerstate &#61; ticker &#43; 'state'
      if (this.$refs.trade.cacheData[tickerstate]) {
        return false
      }
      const what &#61; this.$refs.trade.widget.chart()
      var item &#61; this.btnList[key]
      switch (key) {
        case '9':
          what.executeActionById('insertIndicator')
          break
        case '10':
          what.executeActionById('chartProperties')
          break
        case '11':
          this.$refs.trade.fullScreen()
          break
        default:
          this.resolution &#61; key
          localStorage.resolutionIndex &#61; key
          localStorage.interval &#61; item.resolution
          what.setChartType(item.chartType)
          what.setResolution(item.resolution, function onReadyCallback() {})
          break
      }
    }
  }}</script><style lang&#61;&#34;scss&#34;>.app {
    text-align: center;

    &__header {
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #fff;
        height: 30px;
    }

    &__title {
        display: block;
        font-size: 1.5em;
    }}</style>

最后需要再index.html中引入静态文件

 <script type&#61;&#34;text/javascript&#34; src&#61;&#34;<%&#61; BASE_URL %>charting_library/charting_library.min.js&#34; ></script>

然后运行项目就可以看到效果了
在这里插入图片描述

声明:本文内容不代表斑马投诉网站观点,内容仅供参考,不构成投资建议。投资有风险,选择需谨慎! 如涉及内容、版权等问题,请联系我们,我们会在第一时间作出调整!

相关文章