Files
trade/web/frontend/src/components/KLineChart.vue
fish 8d4bcb4292 Web 前端新增暗夜模式切换
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 15:35:26 +08:00

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>