115 lines
2.7 KiB
Vue
115 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
import type { Candle } from '@/api/candles'
|
|
import { useThemeStore } from '@/stores/theme'
|
|
|
|
const props = defineProps<{ data: Candle[] }>()
|
|
const theme = useThemeStore()
|
|
|
|
const containerRef = ref<HTMLDivElement | null>(null)
|
|
let chart: echarts.ECharts | null = null
|
|
|
|
function ensureChart() {
|
|
if (!containerRef.value) return
|
|
if (chart) {
|
|
chart.dispose()
|
|
chart = null
|
|
}
|
|
chart = echarts.init(containerRef.value, theme.isDark ? 'dark' : undefined)
|
|
}
|
|
|
|
function render() {
|
|
if (!chart) return
|
|
const dates = props.data.map((c) => c.trade_date)
|
|
// ECharts K 线顺序: [open, close, low, high]
|
|
const ohlc = props.data.map((c) => [c.open, c.close, c.low, c.high])
|
|
const oi = props.data.map((c) => c.oi)
|
|
|
|
chart.setOption(
|
|
{
|
|
backgroundColor: 'transparent',
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
|
|
legend: { data: ['K 线', '持仓量'], top: 0 },
|
|
grid: [
|
|
{ left: 60, right: 40, top: 40, height: '60%' },
|
|
{ left: 60, right: 40, top: '78%', height: '18%' },
|
|
],
|
|
xAxis: [
|
|
{ type: 'category', data: dates, scale: true, boundaryGap: false },
|
|
{ type: 'category', gridIndex: 1, data: dates, scale: true, boundaryGap: false, axisLabel: { show: false } },
|
|
],
|
|
yAxis: [
|
|
{ scale: true, splitArea: { show: true } },
|
|
{ gridIndex: 1, scale: true, splitNumber: 3 },
|
|
],
|
|
dataZoom: [
|
|
{ type: 'inside', xAxisIndex: [0, 1] },
|
|
{ type: 'slider', xAxisIndex: [0, 1], height: 18, bottom: 6 },
|
|
],
|
|
series: [
|
|
{
|
|
name: 'K 线',
|
|
type: 'candlestick',
|
|
data: ohlc,
|
|
itemStyle: {
|
|
color: '#ec3a3a',
|
|
color0: '#26a69a',
|
|
borderColor: '#ec3a3a',
|
|
borderColor0: '#26a69a',
|
|
},
|
|
},
|
|
{
|
|
name: '持仓量',
|
|
type: 'line',
|
|
xAxisIndex: 1,
|
|
yAxisIndex: 1,
|
|
data: oi,
|
|
smooth: true,
|
|
showSymbol: false,
|
|
lineStyle: { color: '#5470c6' },
|
|
areaStyle: { opacity: 0.15, color: '#5470c6' },
|
|
},
|
|
],
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
function resize() {
|
|
chart?.resize()
|
|
}
|
|
|
|
onMounted(() => {
|
|
ensureChart()
|
|
render()
|
|
window.addEventListener('resize', resize)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', resize)
|
|
chart?.dispose()
|
|
chart = null
|
|
})
|
|
|
|
watch(() => props.data, render, { deep: true })
|
|
watch(
|
|
() => theme.isDark,
|
|
() => {
|
|
ensureChart()
|
|
render()
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="containerRef" class="chart"></div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chart {
|
|
width: 100%;
|
|
height: 560px;
|
|
}
|
|
</style>
|