Просмотр исходного кода

feat:站点信息功率流图增加SOC信息显示

Jerry Liang 1 месяц назад
Родитель
Сommit
45aab662cf

+ 103 - 42
src/views/site-info-manage/component/powerFlowChart.vue

@@ -3,7 +3,7 @@
 </template>
 
 <script setup lang="ts">
-import { Chart, ChartEvent } from '@antv/g2'
+import { Chart } from '@antv/g2'
 import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
 import { usePersonalization } from '@/composable/usePersonalization'
 
@@ -22,55 +22,101 @@ export interface PowerChartData {
   Power?: number
 }
 
+export interface SocChartData {
+  Time: string
+  Soc?: number
+}
+
 let isVoidData = true
-const voidData: PowerChartData[] = (() => {
+// 缓存最近一次数据,供主题切换时复用
+let lastPowerData: PowerChartData[] | undefined = undefined
+let lastSocData: SocChartData[] | undefined = undefined
+
+// 生成一天的时间刻度(每 5 分钟)
+const buildTimes = (): string[] => {
   const times: string[] = []
   const totalMinutes = 24 * 60
-
   for (let m = 0; m < totalMinutes; m += 5) {
     const hh = String(Math.floor(m / 60)).padStart(2, '0')
     const mm = String(m % 60).padStart(2, '0')
     times.push(`${hh}:${mm}`)
   }
+  return times
+}
 
-  return times.map((t) => {
-    return [
-      { powerType: 'aPower', Time: t },
-      { powerType: 'Generator', Time: t },
-      { powerType: 'HomeLoad', Time: t },
-      { powerType: 'PV', Time: t },
-      { powerType: 'Grid', Time: t },
-    ]
-  }).flat()
-})()
+// 空功率数据
+const voidPowerData: PowerChartData[] = buildTimes()
+  .map((t) => [
+    { powerType: 'aPower', Time: t },
+    { powerType: 'Generator', Time: t },
+    { powerType: 'HomeLoad', Time: t },
+    { powerType: 'PV', Time: t },
+    { powerType: 'Grid', Time: t },
+  ])
+  .flat()
+
+// 空 SOC 数据
+const voidSocData: SocChartData[] = buildTimes().map((t) => ({ Time: t }))
+
+// 颜色映射(功率类型)
+const powerColorRelations: [string, string][] = [
+  ['aPower', '#41D187'],
+  ['Generator', '#5398FF'],
+  ['HomeLoad', '#0BF5FF'],
+  ['PV', '#F1A502'],
+  ['Grid', '#BB56FD'],
+]
+
+// 构建图表 options(上:功率堆叠面积;下:SOC 折线;共享 X 轴 Time)
+const buildOptions = (powerData: PowerChartData[], socData: SocChartData[]) => {
+  return {
+    type: 'spaceFlex' as const,
+    direction: 'col' as const,
+    ratio: [2, 1],
+    theme: { type: isDark.value ? 'classicDark' : 'classic' },
+    children: [
+      {
+        // 上图:功率堆叠面积
+        type: 'area' as const,
+        data: powerData,
+        // 与下图左内边距对齐,保证 X 轴严格对齐
+        paddingLeft: 60,
+        paddingBottom: 0,
+        // 压缩与下图的间隔,仅留一点缝隙
+        marginBottom: 6,
+        transform: [{ type: 'stackY' as const }],
+        encode: { x: 'Time', y: 'Power', color: 'powerType' },
+        scale: {
+          color: { palette: 'accent', relations: powerColorRelations },
+        },
+        axis: { x: false },
+        interaction: { elementHighlight: { background: true } },
+      },
+      {
+        // 下图:SOC 折线
+        type: 'line' as const,
+        data: socData,
+        paddingLeft: 60,
+        paddingTop: 0,
+        // 压缩与上图的间隔,仅留一点缝隙
+        marginTop: 6,
+        encode: { x: 'Time', y: 'Soc' },
+        scale: { y: { domain: [0, 100], nice: false } },
+        style: { stroke: '#F5A623', lineWidth: 2 },
+        axis: { y: { title: 'SOC (%)' } },
+      },
+    ],
+  }
+}
 
 onMounted(() => {
   chart = new Chart({
     container: chartContainer.value!,
     autoFit: true,
-    height: props.height || 300,
+    height: props.height || 380,
   })
 
-  chart
-    .area()
-    .theme({ type: isDark ? 'dark' : 'light' })
-    .data(voidData)
-    .encode('x', 'Time')
-    .encode('y', 'Power')
-    .encode('color', 'powerType')
-    .transform({ type: 'stackY' })
-    .interaction('elementHighlight', { background: true })
-    .scale('color', {
-      palette: 'accent',
-      relations: [
-        ['aPower', '#41D187'],
-        ['Generator', '#5398FF'],
-        ['HomeLoad', '#0BF5FF'],
-        ['PV', '#F1A502'],
-        ['Grid', '#BB56FD'],
-      ],
-    })
-
+  chart.options(buildOptions(voidPowerData, voidSocData))
   chart.render()
 })
 
@@ -79,27 +125,42 @@ onBeforeUnmount(() => {
 })
 
 // 重绘数据
-const redraw = (data: PowerChartData[] | undefined) => {
+const redraw = (
+  powerData: PowerChartData[] | undefined,
+  socData: SocChartData[] | undefined,
+) => {
   if (chart === null) return
 
-  if (data === undefined) {
+  if (powerData === undefined) {
     if (!isVoidData) {
-      chart.changeData(voidData)
+      isVoidData = true
+      chart.options(buildOptions(voidPowerData, voidSocData))
+      chart.render()
     }
   } else {
     isVoidData = false
-    chart.changeData(data)
+    chart.options(buildOptions(powerData, socData ?? voidSocData))
+    chart.render()
   }
 }
 
-watch(isDark, (newVal) => {
+watch(isDark, () => {
   if (chart) {
-    chart.theme({ type: newVal ? 'dark' : 'light' })
-    chart.render()
+    // 主题变化时重建 options 以应用新主题
+    redraw(lastPowerData, lastSocData)
   }
 })
 
+const redrawWithCache = (
+  powerData: PowerChartData[] | undefined,
+  socData: SocChartData[] | undefined,
+) => {
+  lastPowerData = powerData
+  lastSocData = socData
+  redraw(powerData, socData)
+}
+
 defineExpose({
-  redraw,
+  redraw: redrawWithCache,
 })
 </script>

+ 25 - 4
src/views/site-info-manage/component/siteDataModal.vue

@@ -65,9 +65,9 @@
       </NSpace>
     </div>
 
-    <!-- 功率流图表 -->
+    <!-- 功率流图表(含 SOC 联动子图,共享 X 轴) -->
     <NSpin :show="chartLoading" :delay="500">
-      <PowerFlowChart ref="powerFlowChartRef" class="h-[300px]" :height="300" />
+      <PowerFlowChart ref="powerFlowChartRef" class="h-[380px]" :height="380" />
     </NSpin>
   </NModal>
 </template>
@@ -86,7 +86,7 @@ import {
   NTimelineItem,
 } from 'naive-ui'
 import { ref, computed, watch, nextTick } from 'vue'
-import PowerFlowChart from './powerFlowChart.vue'
+import PowerFlowChart, { type SocChartData } from './powerFlowChart.vue'
 import { useSitePowerFlowDataStore } from '@/stores/modules/data/sitePowerFlowData'
 import { useSiteInfoDataStore } from '@/stores/modules/data/siteInfoData'
 import { useSiteModeDataStore } from '@/stores/modules/data/siteModeData'
@@ -161,12 +161,33 @@ async function loadPowerFlow() {
       await sitePowerFlowDataStore.queryPowerFlow(sn, date)
       powerData = sitePowerFlowDataStore.powerFlowChartGet(sn, date)
     }
-    powerFlowChartRef.value?.redraw(powerData)
+
+    // 构造 SOC 折线数据(复用原始功率流数据的 deviceTimeArray 与 socArray)
+    const rawPowerFlow = sitePowerFlowDataStore.powerFlowGet(sn, date)
+    const socData = buildSocData(rawPowerFlow)
+
+    // 功率与 SOC 同图渲染(上图功率堆叠面积,下图 SOC 折线,共享 X 轴)
+    powerFlowChartRef.value?.redraw(powerData, socData)
   } finally {
     chartLoading.value = false
   }
 }
 
+// 由原始功率流数据构造 SOC 折线数据
+function buildSocData(
+  rawPowerFlow: ReturnType<typeof sitePowerFlowDataStore.powerFlowGet>,
+): SocChartData[] | undefined {
+  if (!rawPowerFlow) return undefined
+  const { deviceTimeArray, socArray } = rawPowerFlow
+  if (!deviceTimeArray || !socArray || socArray.length === 0) return undefined
+
+  return deviceTimeArray.map((element, index) => {
+    const time = element.split(' ')[1].slice(0, 5) // 截取时间部分"00:00"
+    const soc = socArray[index] ?? undefined
+    return { Time: time, Soc: soc }
+  })
+}
+
 // 加载站点信息(aPower SN、版本)
 async function loadSiteInfo(sn: string) {
   let siteInfo = siteInfoDataStore.siteInfoGet(sn)