新增 Web 浏览端(Go+Vue 报表系统)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
97
web/frontend/src/components/KLineChart.vue
Normal file
97
web/frontend/src/components/KLineChart.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import type { Candle } from '@/api/candles'
|
||||
|
||||
const props = defineProps<{ data: Candle[] }>()
|
||||
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
|
||||
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(
|
||||
{
|
||||
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(() => {
|
||||
if (containerRef.value) {
|
||||
chart = echarts.init(containerRef.value)
|
||||
render()
|
||||
window.addEventListener('resize', resize)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
chart?.dispose()
|
||||
chart = null
|
||||
})
|
||||
|
||||
watch(() => props.data, render, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef" class="chart"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 560px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user