<template>
|
<div >
|
<div class="kline-charts">
|
<ul class="flex px-32 pb-20 box-border justify-between text-grey fontStyle font-26"
|
style="border-bottom:1px solid rgba(68,75,88,0.2);">
|
<template v-if="!isChange">
|
<li v-for="item in timeList" :key="item.id" class="mr-10" :class="{ 'textColor': item.id === timeValue.id }"
|
@click="choiceTime(item)">{{ item.text }}</li>
|
</template>
|
<template v-else>
|
<template v-if="isNight">
|
<li v-for="item in timeList" :key="item.id" class="mr-10"
|
:class="{ 'text-white': item.id === timeValue.id }" @click="choiceTime(item)">{{ item.text }}</li>
|
</template>
|
<template v-if="!isNight">
|
<li v-for="item in timeList" :key="item.id" class="mr-10"
|
:class="{ 'text-black': item.id === timeValue.id }" @click="choiceTime(item)">{{ item.text }}</li>
|
</template>
|
</template>
|
</ul>
|
<div class="chart-with-tools flex">
|
<div class="relative flex-1 min-w-0">
|
<div id="kline" class="h-828"></div>
|
<div
|
class="flex justify-center items-center text-center text-grey absolute left-0 top-0 w-full h-full z-10 mainBackground"
|
v-if="chartLoading">
|
<van-loading type="spinner"></van-loading>
|
</div>
|
</div>
|
<!-- 铅笔图标:控制绘图工具栏显示/隐藏 -->
|
<div class="draw-tools-wrap flex">
|
<div class="pencil-trigger draw-tool-item" :class="[isNight ? 'draw-toolbar-night' : 'draw-toolbar-light', { active: showDrawToolbar }]"
|
:title="showDrawToolbar ? $t('隐藏绘图工具') : $t('显示绘图工具')" @click="showDrawToolbar = !showDrawToolbar">
|
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
|
</div>
|
<!-- 绘图工具栏 -->
|
<div v-show="showDrawToolbar" class="draw-toolbar" :class="isNight ? 'draw-toolbar-night' : 'draw-toolbar-light'">
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'fibonacciLine' }" title="斐波那契" @click="onDrawToolClick('fibonacciLine')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M4 20L20 4l-2-2L2 18v4h2z"/><circle cx="6" cy="18" r="1.5"/><circle cx="18" cy="6" r="1.5"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'segment' }" title="线段" @click="onDrawToolClick('segment')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><line x1="4" y1="20" x2="20" y2="4" stroke="currentColor" stroke-width="2"/><circle cx="4" cy="20" r="2"/><circle cx="20" cy="4" r="2"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'horizontalStraightLine' }" title="水平线" @click="onDrawToolClick('horizontalStraightLine')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><line x1="2" y1="12" x2="22" y2="12" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="2"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'horizontalSegment' }" title="水平线段" @click="onDrawToolClick('horizontalSegment')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><line x1="6" y1="12" x2="18" y2="12" stroke="currentColor" stroke-width="2"/><circle cx="6" cy="12" r="2"/><circle cx="18" cy="12" r="2"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'verticalStraightLine' }" title="垂直线" @click="onDrawToolClick('verticalStraightLine')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><line x1="12" y1="2" x2="12" y2="22" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="2"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'verticalSegment' }" title="垂直线段" @click="onDrawToolClick('verticalSegment')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><line x1="12" y1="6" x2="12" y2="18" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="6" r="2"/><circle cx="12" cy="18" r="2"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'rect' }" title="矩形" @click="onDrawToolClick('rect')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><rect x="5" y="7" width="14" height="10" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="5" cy="7" r="1.5"/><circle cx="19" cy="17" r="1.5"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'triangle' }" title="三角形" @click="onDrawToolClick('triangle')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><path d="M12 5L4 19h16L12 5z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="5" r="1.5"/><circle cx="4" cy="19" r="1.5"/><circle cx="20" cy="19" r="1.5"/></svg>
|
</div>
|
<div class="draw-tool-item" :class="{ active: activeDrawTool === 'parallelogram' }" title="平行四边形" @click="onDrawToolClick('parallelogram')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><path d="M6 6h12l-4 12H2L6 6z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="6" cy="6" r="1.5"/><circle cx="18" cy="6" r="1.5"/><circle cx="14" cy="18" r="1.5"/></svg>
|
</div>
|
<div class="draw-tool-item" title="清除全部" @click="onDrawToolClick('remove')">
|
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
</div>
|
</div>
|
</div>
|
</div>
|
<ul class="flex px-32 py-20 box-border justify-between text-grey font-26" v-if="showBottom"
|
style="border-top:1px solid rgba(68,75,88,0.2);">
|
<template v-if="!isChange">
|
<li v-for="item in subTechnicalIndicatorTypes" :key="item" class="mr-20"
|
:class="{ 'textColor': typeValue === item }" @click="choiceType(item)">{{ item }}</li>
|
</template>
|
<template v-else>
|
<template v-if="isNight">
|
<li v-for="item in subTechnicalIndicatorTypes" :key="item" class="mr-20"
|
:class="{ 'text-white': typeValue === item }" @click="choiceType(item)">{{ item }}</li>
|
</template>
|
<template v-if="!isNight">
|
<li v-for="item in subTechnicalIndicatorTypes" :key="item" class="mr-20"
|
:class="{ 'text-black': typeValue === item }" @click="choiceType(item)">{{ item }}</li>
|
</template>
|
</template>
|
</ul>
|
</div>
|
</div>
|
</template>
|
<script>
|
import { init, dispose } from 'klinecharts'
|
let chart = null
|
import { _getKline } from "@/service/trade.api";
|
import config from './config'
|
import { clearAllTimers } from '@/utils/utis.js'
|
import { Loading } from 'vant';
|
import { customShapeTemplates } from './drawTools'
|
export default {
|
name: 'KlineCharts',
|
data() {
|
return {
|
// symbol: 'btc',
|
dealInfo: {},//交易对信息
|
|
timeValue: {}, // 当前k线周期
|
subTechnicalIndicatorTypes: ['MA', 'EMA', 'BOLL', 'VOL', 'MACD', 'KDJ', 'RSI'],
|
typeValue: 'VOL',//图形类型
|
klinecharts: null,//K线图实例
|
chart: null,
|
// resolution: '1',//分辨率
|
// lang: 'en', //语言
|
chartLoading: true, //加载动画
|
paneId: '',
|
chartData: [], // 图表数据
|
timer: null,
|
activeDrawTool: '', // 当前选中的绘图工具
|
showDrawToolbar: true // 绘图工具栏是否显示,由铅笔图标切换
|
}
|
},
|
computed: {
|
timeList() {
|
return [
|
{ id: '1min', time: '1min', text: this.$t('分时'), ts: 1 * 60 * 1000 },
|
// { id: '1mins', time: '1min', text: '1' + this.$t('分'), ts: 1 * 60 * 1000 },
|
{ id: '5min', time: '5min', text: '5' + this.$t('分'), ts: 5 * 60 * 1000 },
|
{ id: '15min', time: '15min', text: '15' + this.$t('分'), ts: 15 * 60 * 1000 },
|
{ id: '30min', time: '30min', text: '30' + this.$t('分'), ts: 30 * 60 * 1000 },
|
{ id: '60min', time: '60min', text: '1' + this.$t('小时'), ts: 60 * 60 * 1000 },
|
// { id: '4hour', time: '4hour', text: '4' + this.$t('小时'), ts: 4 * 60 * 60 * 1000 },
|
{ id: '1day', time: '1day', text: '1' + this.$t('天'), ts: 24 * 60 * 60 * 1000 },
|
{ id: '1week', time: '1week', text: '1' + this.$t('周'), ts: 7 * 24 * 60 * 60 * 1000 },
|
{ id: '1mon', time: '1mon', text: '1' + this.$t('月'), ts: 30 * 24 * 60 * 60 * 1000 }
|
]//时间列表
|
}
|
},
|
components: {
|
[Loading.name]: Loading
|
},
|
props: {
|
symbol: {
|
type: String,
|
default: ''
|
},
|
updateKey: {
|
type: Number,
|
default: 0
|
},
|
updateData: {
|
type: Object,
|
default() {
|
return {}
|
}
|
},
|
showBottom: {
|
type: Boolean,
|
default: true
|
},
|
isChangeLine: {
|
type: Boolean,
|
default: false
|
},
|
isNight: {
|
type: Boolean,
|
defalult: true
|
},
|
isChange: {
|
type: Boolean,
|
defalult: false
|
}
|
},
|
mounted() {
|
this.initData()
|
},
|
beforeUnmount() {
|
this.clearTimer()
|
dispose('kline')
|
},
|
watch: {
|
isChangeLine(val) {
|
this.clearTimer()
|
this.fetchData()
|
},
|
updateKey() { // 更新charts:只更新 this.updateData 的 close,其余沿用最后一根
|
const dataList = chart.getDataList()
|
if (dataList.length > 0) {
|
const lastData = dataList[dataList.length - 1]
|
const nowData = this.updateData
|
const newData = {
|
...lastData,
|
close: nowData.close != null && nowData.close !== '' ? (nowData.close / 1) : lastData.close
|
}
|
this.$nextTick(() => {
|
this.setChartType()
|
chart.updateData(newData)
|
})
|
}
|
}
|
},
|
methods: {
|
initData() {
|
this.timeValue = this.timeList.find(t => t.id === '15min') || this.timeList[0]
|
chart = init('kline', config);
|
chart.setOffsetRightSpace(25)
|
chart.setDataSpace(10)
|
chart.setPriceVolumePrecision(4, 2)
|
chart.createTechnicalIndicator('MA', false, { id: 'candle_pane' });
|
this.paneId = chart.createTechnicalIndicator('VOL');
|
// 注册自定义绘图图形:矩形、三角形、平行四边形
|
chart.addShapeTemplate(customShapeTemplates)
|
this.fetchData()
|
},
|
fetchData(time) { // 获取数据
|
// this.chartLoading = true
|
_getKline(this.symbol, this.timeValue.time || '1min').then(data => {
|
this.chartLoading = false
|
data.map(item => {
|
item.timestamp = item.ts
|
})
|
let str = data[0] ? data[0].close.toString() : ''
|
let length = 2
|
if (str.indexOf('.') != -1) {
|
length = str.split('.')[1].length
|
}
|
//let length = data[0] ? data[0].close.toString().split('.')[1].length : 4
|
chart.setPriceVolumePrecision(length, 2)
|
this.setChartType()
|
chart.applyNewData(data);
|
this.$emit('updataLine', false) // 关闭计时器
|
})
|
// TODO:轮询,删掉上面那段,添加到定时器中
|
this.timer = setInterval(() => {
|
_getKline(this.symbol, this.timeValue.time || time).then(data => {
|
this.chartLoading = false
|
data.map(item => {
|
item.timestamp = item.ts
|
})
|
let str = data[0] ? data[0].close.toString() : ''
|
let length = 2
|
if (str.indexOf('.') != -1) {
|
length = str.split('.')[1].length
|
}
|
//let length = data[0] ? data[0].close.toString().split('.')[1].length : 4
|
chart.setPriceVolumePrecision(length, 2)
|
this.setChartType()
|
chart.applyNewData(data);
|
this.$emit('updataLine', false)
|
})
|
}, 10000);
|
},
|
setChartType() {
|
let type = 'area'
|
if (this.timeValue.id === '1min') {
|
type = 'area'
|
} else {
|
type = 'candle_solid'
|
}
|
chart.setStyleOptions({
|
candle: {
|
type
|
}
|
})
|
},
|
choiceTime(value) { // 选择周期
|
this.timeValue = value
|
this.clearTimer()
|
this.fetchData()
|
},
|
choiceType(type) { // 选择副线
|
this.typeValue = type
|
chart.createTechnicalIndicator(type, false, { id: this.paneId })
|
},
|
clearTimer() {
|
clearInterval(this.timer)
|
this.timer = null
|
clearAllTimers()
|
},
|
onDrawToolClick(name) {
|
if (!chart) return
|
if (name === 'remove') {
|
chart.removeShape()
|
this.activeDrawTool = ''
|
return
|
}
|
this.activeDrawTool = name
|
chart.createShape(name, 'candle_pane')
|
},
|
},
|
deactivated() {
|
this.clearTimer()
|
}
|
}
|
</script>
|
<style lang="scss" scoped>
|
@import "@/assets/init.scss";
|
|
.chart-with-tools {
|
position: relative;
|
}
|
|
.draw-tools-wrap {
|
flex-shrink: 0;
|
display: flex;
|
border-left: 1px solid rgba(68, 75, 88, 0.2);
|
}
|
|
.draw-tools-wrap .pencil-trigger {
|
width: 44px;
|
min-width: 44px;
|
flex-shrink: 0;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 8px 0;
|
cursor: pointer;
|
transition: background 0.2s;
|
border-radius: 0;
|
}
|
.pencil-trigger.draw-toolbar-light {
|
background: rgba(255, 255, 255, 0.98);
|
color: #333;
|
}
|
.pencil-trigger.draw-toolbar-light:hover,
|
.pencil-trigger.draw-toolbar-light.active {
|
background: rgba(0, 0, 0, 0.06);
|
color: #2196f3;
|
}
|
.pencil-trigger.draw-toolbar-night {
|
background: rgba(17, 26, 46, 0.98);
|
color: #d9d9d9;
|
}
|
.pencil-trigger.draw-toolbar-night:hover,
|
.pencil-trigger.draw-toolbar-night.active {
|
background: rgba(255, 255, 255, 0.08);
|
color: #42a5f5;
|
}
|
|
.draw-toolbar {
|
width: 44px;
|
flex-shrink: 0;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 8px 0;
|
gap: 4px;
|
}
|
|
.draw-toolbar-light {
|
background: rgba(255, 255, 255, 0.98);
|
color: #333;
|
}
|
|
.draw-toolbar-night {
|
background: rgba(17, 26, 46, 0.98);
|
color: #d9d9d9;
|
border-left-color: rgba(68, 75, 88, 0.4);
|
}
|
|
.draw-tool-item {
|
width: 36px;
|
height: 36px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 6px;
|
cursor: pointer;
|
transition: background 0.2s;
|
}
|
|
.draw-toolbar-light .draw-tool-item:hover {
|
background: rgba(0, 0, 0, 0.06);
|
}
|
|
.draw-toolbar-light .draw-tool-item.active {
|
background: rgba(33, 150, 243, 0.15);
|
color: #2196f3;
|
}
|
|
.draw-toolbar-night .draw-tool-item:hover {
|
background: rgba(255, 255, 255, 0.08);
|
}
|
|
.draw-toolbar-night .draw-tool-item.active {
|
background: rgba(33, 150, 243, 0.25);
|
color: #42a5f5;
|
}
|
|
.draw-tool-item svg {
|
display: block;
|
}
|
</style>
|