一个简单的tradingview+web socket+vue的项目
最开始接触tradingview摸索了很久,之前的项目是前后端不分离的,后来公司需要重构项目用vue来写,网上查了一下,结合文档,踩了许多坑自己的摸索出来的先看一下项目结构先贴一下socket 代码js-apidatafees.js/*** JS API*/import DataUpdater from './dataUpdater'class datafeeds {/*** JS API* @p
·
最开始接触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条内容
所有评论(0)