KLineChart.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <template>
  2. <div class="kline-wrapper" :style="{ height: height }">
  3. <div ref="chartContainer" class="kline-chart"></div>
  4. </div>
  5. </template>
  6. <script setup>
  7. import { onMounted, onBeforeUnmount, ref, watch, nextTick, toRaw } from 'vue'
  8. import * as klinecharts from 'klinecharts'
  9. const props = defineProps({
  10. data: { type: Array, default: () => [] },
  11. height: { type: String, default: '100%' },
  12. precision: { type: Object, default: () => ({ price: 2, volume: 2 }) },
  13. colors: {
  14. type: Object,
  15. default: () => ({
  16. up: '#2EBD85',
  17. down: '#F6465D',
  18. grid: '#F2F4F6',
  19. text: '#929AA5',
  20. targetLine: '#4A6EF5'
  21. })
  22. }
  23. })
  24. const chartContainer = ref(null)
  25. let chartInstance = null
  26. const initChart = () => {
  27. if (!chartContainer.value) return
  28. if (chartInstance) klinecharts.dispose(chartContainer.value)
  29. chartInstance = klinecharts.init(chartContainer.value)
  30. // 设置精度
  31. const { price, volume } = props.precision
  32. if (chartInstance.setPriceVolumePrecision) {
  33. chartInstance.setPriceVolumePrecision(price, volume)
  34. } else if (chartInstance.setPrecision) {
  35. chartInstance.setPrecision(price, volume)
  36. }
  37. // 样式配置
  38. const { up, down, grid, text, targetLine } = props.colors
  39. chartInstance.setStyleOptions({
  40. grid: { show: true, horizontal: { show: true, size: 1, color: grid, style: 'dash', dashValue: [5, 5] }, vertical: { show: false } },
  41. candle: {
  42. type: 'candle_solid',
  43. bar: { upColor: up, downColor: down, noChangeColor: up },
  44. priceMark: {
  45. show: true,
  46. last: { show: true, upColor: up, downColor: down, line: { show: true, style: 'dash' }, text: { show: true, color: '#FFF', paddingLeft: 4, paddingRight: 4, borderRadius: 2 } }
  47. },
  48. tooltip: { showRule: 'follow_cross', showType: 'rect', dataSource: 'none' }
  49. },
  50. xAxis: { axisLine: { show: false }, tickLine: { show: false }, tickText: { color: text, size: 10, paddingTop: 8 } },
  51. yAxis: { inside: true, axisLine: { show: false }, tickLine: { show: false }, tickText: { color: text, size: 10, paddingLeft: 8 } },
  52. })
  53. chartInstance.createTechnicalIndicator('VOL', false, { id: 'pane_1', heightRatio: 0.2 })
  54. // 初始加载
  55. if (props.data && props.data.length > 0) {
  56. chartInstance.applyNewData(toRaw(props.data))
  57. }
  58. }
  59. // --- 🔥 核心修复:智能判断是“更新”还是“重置” ---
  60. watch(() => props.data, (newData) => {
  61. if (!chartInstance) return
  62. const rawData = toRaw(newData)
  63. const currentList = chartInstance.getDataList()
  64. // 1. 如果新数据为空,清空图表
  65. if (rawData.length === 0) {
  66. chartInstance.clearData()
  67. return
  68. }
  69. // 2. 如果当前图表为空,直接加载
  70. if (currentList.length === 0) {
  71. chartInstance.applyNewData(rawData)
  72. return
  73. }
  74. // 3. 🔥 关键判断:
  75. // 如果第一根 K 线的时间戳变了,说明切换了周期或币种 -> 全量重置
  76. const firstOld = currentList[0]
  77. const firstNew = rawData[0]
  78. if (firstOld.timestamp !== firstNew.timestamp) {
  79. chartInstance.applyNewData(rawData)
  80. return
  81. }
  82. // 4. 如果第一根时间没变,说明是实时跳动或追加 -> 增量更新
  83. if (rawData.length > 0) {
  84. const lastData = rawData[rawData.length - 1]
  85. chartInstance.updateData(lastData)
  86. }
  87. }, { deep: true })
  88. // 监听精度
  89. watch(() => props.precision, (val) => {
  90. if (chartInstance) {
  91. if (chartInstance.setPriceVolumePrecision) chartInstance.setPriceVolumePrecision(val.price, val.volume)
  92. else if (chartInstance.setPrecision) chartInstance.setPrecision(val.price, val.volume)
  93. }
  94. }, { deep: true })
  95. onMounted(() => {
  96. nextTick(() => initChart())
  97. window.addEventListener('resize', handleResize)
  98. })
  99. onBeforeUnmount(() => {
  100. window.removeEventListener('resize', handleResize)
  101. if (chartInstance) {
  102. klinecharts.dispose(chartContainer.value)
  103. chartInstance = null
  104. }
  105. })
  106. const handleResize = () => {
  107. if (chartInstance) chartInstance.resize()
  108. }
  109. </script>
  110. <style scoped>
  111. .kline-wrapper { width: 100%; position: relative; }
  112. .kline-chart { width: 100%; height: 100%; }
  113. </style>