yeg
2026-08-26 9f48020dfd3e29d1c0049a3da9df3203c40bc264
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { use } from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import VChart from 'vue-echarts'
 
interface ConsoleCell {
  no: string
  voltage: string
  temperature: string
  positive: string
  negative: string
  capacity: string
  current: string
}
 
interface ProjectMetric {
  key: 'finalVoltage1' | 'finalVoltage2' | 'dischargeCapacityF'
  title: string
  unit: string
}
 
const props = defineProps<{
  cells: ConsoleCell[]
}>()
 
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent])
 
const metrics: ProjectMetric[] = [
  { key: 'finalVoltage1', title: '最终电压1', unit: 'V' },
  { key: 'finalVoltage2', title: '最终电压2', unit: 'V' },
  { key: 'dischargeCapacityF', title: '每循环放电末容量 / F', unit: 'F' },
]
const palette = ['#3978f6', '#7d42d8', '#f08a3e', '#23a983', '#e15268', '#1b9ac8', '#b5892f', '#6957c8', '#4f8d44', '#d264a3', '#46748e', '#a95d37']
const seriesColors = ref<string[]>(props.cells.map((_, index) => palette[index % palette.length]))
const cycleLabels = Array.from({ length: 61 }, (_, index) => String(index + 1))
const chartUpdateOptions = { notMerge: true, lazyUpdate: false }
 
watch(() => props.cells, cells => {
  seriesColors.value = cells.map((_, index) => seriesColors.value[index] || palette[index % palette.length])
}, { deep: true })
 
const chartStateKey = computed(() => props.cells.map(cell => cell.no).join('|'))
 
function metricBase(cell: ConsoleCell, metric: ProjectMetric) {
  if (metric.key === 'finalVoltage1') return Number(cell.voltage)
  if (metric.key === 'finalVoltage2') return Number(cell.positive)
  return Number(cell.capacity) / 1000
}
 
function seriesValues(cell: ConsoleCell, metric: ProjectMetric, cellIndex: number) {
  const base = metricBase(cell, metric)
  return cycleLabels.map((_, pointIndex) => {
    const progress = pointIndex / (cycleLabels.length - 1)
    const cycleWave = Math.sin(progress * Math.PI * 4 + cellIndex * 0.38)
    const localWave = Math.cos(progress * Math.PI * 9 + cellIndex * 0.2)
    if (metric.key === 'dischargeCapacityF') {
      return Number((base * (1 - progress * 0.08) + cycleWave * 0.018 + localWave * 0.006).toFixed(4))
    }
    const direction = metric.key === 'finalVoltage1' ? 1 : -1
    return Number((base + cycleWave * 0.075 + localWave * 0.018 + direction * progress * 0.04).toFixed(4))
  })
}
 
function chartOption(metric: ProjectMetric) {
  const seriesData = props.cells.map((cell, index) => seriesValues(cell, metric, index))
  const values = seriesData.flat()
  const dataMin = values.length ? Math.min(...values) : 0
  const dataMax = values.length ? Math.max(...values) : 1
  const margin = Math.max((dataMax - dataMin) * 0.15, metric.key === 'dischargeCapacityF' ? 0.03 : 0.08)
  const createYAxis = (position: 'left' | 'right') => ({
    type: 'value',
    name: metric.unit,
    position,
    nameLocation: 'middle',
    nameGap: 42,
    nameRotate: position === 'left' ? 90 : -90,
    min: Number((dataMin - margin).toFixed(3)),
    max: Number((dataMax + margin).toFixed(3)),
    nameTextStyle: { color: '#8795a3', fontSize: 10 },
    axisLine: { show: true, lineStyle: { color: '#cbd8e4' } },
    axisTick: { show: true, lineStyle: { color: '#cbd8e4' } },
    axisLabel: { color: '#8795a3', fontSize: 10, margin: 8 },
    splitLine: { show: position === 'left', lineStyle: { color: '#e8eef4' } },
  })
 
  return {
    animation: false,
    color: seriesColors.value,
    grid: { top: 62, right: 62, bottom: 48, left: 62 },
    tooltip: { trigger: 'axis' },
    legend: {
      top: 8,
      left: 12,
      right: 12,
      type: 'scroll',
      icon: 'roundRect',
      itemWidth: 14,
      itemHeight: 8,
      itemGap: 16,
      textStyle: { fontSize: 10, color: '#52606d' },
    },
    xAxis: {
      type: 'category',
      boundaryGap: false,
      name: '循环次数',
      nameLocation: 'middle',
      nameGap: 30,
      data: cycleLabels,
      axisLine: { lineStyle: { color: '#cbd8e4' } },
      axisLabel: { color: '#8795a3', fontSize: 10, interval: 9 },
      nameTextStyle: { color: '#52606d', fontSize: 10 },
    },
    yAxis: [createYAxis('left'), createYAxis('right')],
    series: props.cells.map((cell, index) => ({ name: cell.no, type: 'line', smooth: true, showSymbol: false, lineStyle: { width: 2 }, data: seriesData[index] })),
  }
}
</script>
 
<template>
  <section class="project-chart-panel">
    <article v-for="metric in metrics" :key="metric.key" class="project-chart-card">
      <header><strong>{{ metric.title }}</strong></header>
      <VChart :key="`${metric.key}-${chartStateKey}`" class="project-chart" :option="chartOption(metric)" :update-options="chartUpdateOptions" autoresize />
    </article>
  </section>
</template>
 
<style scoped>
.project-chart-panel{display:grid;gap:16px;min-width:0}.project-chart-card{min-width:0;border:1px solid #d8e1e9;background:#fff}.project-chart-card>header{height:44px;display:flex;align-items:center;padding:0 18px;border-bottom:1px solid #edf1f5}.project-chart-card>header strong{color:#26333f;font-size:13px}.project-chart{width:100%;height:270px}@media(max-width:680px){.project-chart{height:250px}.project-chart-card>header{padding:0 12px}}
</style>