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

最开始接触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 !== null && lastBar.time < subscriptionRecord.lastBarTime) return
    const isNewBar = subscriptionRecord.lastBarTime !== 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 = bars[bars.length - 2]
      subscriptionRecord.listener(previousBar)
    }
    subscriptionRecord.lastBarTime = lastBar.time
    subscriptionRecord.listener(lastBar)
  }
  periodLengthSeconds(resolution, requiredPeriodsCount) {
    let daysCount = 0
    if (resolution === 'D' || resolution === '1D') {
      daysCount = requiredPeriodsCount
    } else if (resolution === 'M' || resolution === '1M') {
      daysCount = 31 * requiredPeriodsCount
    } else if (resolution === 'W' || resolution === '1W') {
      daysCount = 7 * requiredPeriodsCount
    } else {
      daysCount = requiredPeriodsCount * parseInt(resolution) / (24 * 60)
    }
    // console.log(daysCount * 24 * 60 * 60, "'''''''''''''''")
    return daysCount * 24 * 60 * 60
  }
}
export default dataUpdater

socket 文件socket.js

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

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

  on(name, handler) {
    this.messageMap[name] = handler
  }
  doClose() {
    this.socket.close()
  }
  destroy() {
    if (this.heartBeatTimer) {
      clearInterval(this.heartBeatTimer)
      this.heartBeatTimer = null
    }
    // this.doClose()
    this.messageMap = {}
    this.connState = 0
    // this.socket = null
  }
}

export default socket

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

<template>
  <div id="trade-view" :class="isFullscreen?'full':''"/>
</template>

再引入如下代码

import screenfull from 'screenfull'    
//全屏插件 图表有默认的全屏按钮,我这里是自定义全屏按钮
var TvWidget = window.TradingView.widget  
//引入图表
import Socket from './datafeeds/socket.js'  
//引入socket
import 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', // "volumePaneSize" : "large"支持的值: 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', () => {
      this.socket.send({ cmd: 'req', args: ['candle.' + this.interval + '.' + 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 => {
      console.log('home.vue websocket 出错了', this.onClose)
    })
  },
 methods: {
    // 全屏切换
    fullScreen() {
      var what = this
      screenfull.toggle() // 切换全屏
      if (screenfull.isEnabled) {
        screenfull.on('change', () => {
          if (screenfull.isFullscreen) {
            // 全屏时,要执行的操作
            what.isFullscreen = true
          } else {
            // 取消全屏时,要执行的操作
            what.isFullscreen = false
          }
        })
      }
    },
    init() {
      if (!this.widget) {
        this.widget = 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', // 左右箭头
            // "header_settings",//设置按钮
            'header_indicators', // 技术指标线
            'header_chart_type', // 图表类型
            'pane_context_menu', // 图表右键菜单
            'header_resolutions', // 系统默认时间按钮
            // "hide_left_toolbar", //左边工具栏 hide_left_toolbar_by_default
            'header_saveload',
            // "display_market_status",
            '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: "mobile",
          customFormatters: {
            dateFormatter: {
              format: function(date) {
                return date.getUTCFullYear() + '/' + (date.getUTCMonth() + 1) + '/' + date.getUTCDate()
              }
            } // 时间格式

          },
          overrides: this.overridesBlack,
          studies_overrides: {
            'bollinger bands.median.color': '#33FF88',
            'bollinger bands.upper.linewidth': 7,
            // "volume.precision" : 1
            'volume.volume.color.0': '#ee6560',
            'volume.volume.color.1': '#4db872',
            'volume.volume.transparency': 75
          }
          // custom_css_url: './chart.css'
        })
        // var this = this.widget
        this.widget.onChartReady(() => {
          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 !== this.socket.socket.OPEN) {
        this.socket.doOpen()
      }
      if (this.socket.checkOpen()) {
        this.socket.send(data)
      } else {
        this.socket.on('open', () => {
          this.socket.send(data)
        })
      }
    },
    unSubscribe(interval) {
      // 停止订阅,删除过期缓存、缓存时间、缓存状态
      this.interval = interval
      var ticker = this.symbol + '-' + interval
      var tickertime = ticker + 'load'
      var tickerstate = ticker + 'state'
      var tickerCallback = ticker + 'Callback'
      delete this.cacheData[ticker]
      delete this.cacheData[tickertime]
      delete this.cacheData[tickerstate]
      delete this.cacheData[tickerCallback]
      this.sendMessage({
        cmd: 'unsub',
        args: ['candle.' + interval + '.' + this.symbol.toLowerCase()]
      })
    },
    onMessage(data) {
      var ticker = this.symbol + '-' + this.interval
      if (data.name === 'kline') {
        // websocket返回的值,数组代表时间段历史数据,不是增量
        var list = []

        var tickerstate = ticker + 'state'
        var tickerCallback = ticker + 'Callback'
        var onLoadedCallback = this.cacheData[tickerCallback]
        var kline = data.data.kline || ''
        kline.forEach((element) => {
          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] = list
          // this.subscribe()
        }
        // 新数据即当前时间段需要的数据,直接喂给图表插件
        if (onLoadedCallback) {
          onLoadedCallback(list)
          delete this.cacheData[tickerCallback]
        }
        // 请求完成,设置状态为false
        this.cacheData[tickerstate] = !1
        // 记录当前缓存时间,即数组最后一位的时间
        if (this.cacheData[ticker].length > 0) {
          this.lastTime = this.cacheData[ticker][this.cacheData[ticker].length - 1].time
        }
      }

      if (data.name === 'kline_real') {
        var result = data.data.kline
        // console.log(' >> sub:', data.type)
        // console.log(' >> interval:', this.interval)
        // data带有type,即返回的是订阅数据,
        // 缓存的key
        // 构造增量更新数据
        var barsData = {
          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 = barsData.time
        } else if (barsData.time === this.lastTime && this.cacheData[ticker] && this.cacheData[ticker].length) {
          // 如果增量更新的时间等于缓存时间,即在当前时间颗粒内产生了新数据,更新当前数据
          this.cacheData[ticker][this.cacheData[ticker].length - 1] = barsData
        }
        // 通知图表插件,可以开始增量更新的渲染了
        this.datafeeds.barsUpdater.updateData()
      }
    },
    initMessage(symbolInfo, resolution, rangeStartDate, rangeEndDate, onLoadedCallback) {
      // console.log('发起请求,从websocket获取当前时间段的数据');
      // 保留当前回调
      var tickerCallback = this.symbol + '-' + resolution + 'Callback'
      this.cacheData[tickerCallback] = onLoadedCallback
      // 获取需要请求的数据数目
      var limit = this.initLimit(resolution, rangeStartDate, rangeEndDate)
      // 商品名
      var symbol = this.symbol
      // 如果当前时间节点已经改变,停止上一个时间节点的订阅,修改时间节点值

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

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

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

<Tradingview ref="trade" :symbol="symbol" :interval="interval" />
import Tradingview from '@/components/tradingview'

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

 <a-tabs
        :active-key="resolution"
        class="resolutions charts-item"
        @change="checkResolution"
      >
        <a-tab-pane v-for="(item,index) in btnList" :key="index+''" :tab="item.label" />
      </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 = this.symbol + '-' + this.interval
      var tickerstate = ticker + 'state'
      if (this.$refs.trade.cacheData[tickerstate]) {
        return false
      }
      const what = this.$refs.trade.widget.chart()
      var item = 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 = key
          localStorage.resolutionIndex = key
          localStorage.interval = item.resolution
          what.setChartType(item.chartType)
          what.setResolution(item.resolution, function onReadyCallback() {})
          //切换分时
          break
      }
    }
  }

//初始化图表

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

//完整代码

<template>
  <div class="app">
    <header class="app__header">
      <a-tabs
        :active-key="resolution"
        class="resolutions charts-item"
        @change="checkResolution"
      >
        <a-tab-pane v-for="(item,index) in btnList" :key="index+''" :tab="item.label" />
      </a-tabs>
    </header>
    <Tradingview ref="trade" :symbol="symbol" :interval="interval" />
  </div>
</template>

<script>
import Tradingview from '@/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 = this.symbol + '-' + this.interval
      var tickerstate = ticker + 'state'
      if (this.$refs.trade.cacheData[tickerstate]) {
        return false
      }
      const what = this.$refs.trade.widget.chart()
      var item = 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 = key
          localStorage.resolutionIndex = key
          localStorage.interval = item.resolution
          what.setChartType(item.chartType)
          what.setResolution(item.resolution, function onReadyCallback() {})
          break
      }
    }
  }
}
</script>

<style lang="scss">
.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="text/javascript" src="<%= BASE_URL %>charting_library/charting_library.min.js" ></script>

然后运行项目就可以看到效果了
在这里插入图片描述
gihub项目地址 https://github.com/657797068/tradingview-vuejs-websocket.git

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值