소스 검색

修复了因缓存导致无法正确卸载echarts实例的bug

Shinohara Haruna 6 달 전
부모
커밋
9f42fe33d6
3개의 변경된 파일358개의 추가작업 그리고 147개의 파일을 삭제
  1. 49 0
      smsb-plus-ui/src/views/smsb/dashboard/device.vue
  2. 277 147
      smsb-plus-ui/src/views/smsb/dashboard/play.vue
  3. 32 0
      smsb-plus-ui/src/views/smsb/dashboard/play_info.vue

+ 49 - 0
smsb-plus-ui/src/views/smsb/dashboard/device.vue

@@ -89,6 +89,7 @@
 </template>
 
 <script setup lang="ts">
+// console.log('[device.vue] setup called');
 import { reactive } from 'vue';
 import { deviceStatistics } from '@/api/smsb/device/device';
 import * as echarts from 'echarts';
@@ -125,6 +126,7 @@ const stats = reactive([
 ]);
 
 const handleDateRangeChange = () => {
+  // console.log('[device.vue] handleDateRangeChange called');
   const rangeType = timeRadio.value;
   const today = new Date();
   const startDate = new Date();
@@ -155,12 +157,21 @@ const formatDate = (date: Date) => {
   return `${year}-${month}-${day}`;
 };
 const getOnlineTimeTop = async () => {
+  // console.log('[device.vue] getOnlineTimeTop called');
   const params = {
     startTime: dateRange.value[0],
     endTime: dateRange.value[1]
   };
   const res = await onlineTimeTop(params);
+  if (!onlineTime.value) {
+    // console.log('[onlineTime] ref is null when initializing echarts');
+  } else {
+    // console.log('[onlineTime] ref:', onlineTime.value);
+    echarts.dispose(onlineTime.value);
+    // console.log('[onlineTime] echarts instance disposed before init');
+  }
   const onlineTimeTopInstance = echarts.init(onlineTime.value, 'macaroons');
+  // console.log('[onlineTime] echarts instance created:', onlineTimeTopInstance);
   onlineTimeTopInstance.setOption({
     title: {
       text: ''
@@ -196,12 +207,21 @@ const getOnlineTimeTop = async () => {
   });
 };
 const getAlarmNumTop = async () => {
+  // console.log('[device.vue] getAlarmNumTop called');
   const params = {
     startTime: dateRange.value[0],
     endTime: dateRange.value[1]
   };
   const res = await alarmNumTop(params);
+  if (!alarmNum.value) {
+    // console.log('[alarmNum] ref is null when initializing echarts');
+  } else {
+    // console.log('[alarmNum] ref:', alarmNum.value);
+    echarts.dispose(alarmNum.value);
+    // console.log('[alarmNum] echarts instance disposed before init');
+  }
   const alarmNumTopInstance = echarts.init(alarmNum.value, 'macaroons');
+  // console.log('[alarmNum] echarts instance created:', alarmNumTopInstance);
   alarmNumTopInstance.setOption({
     title: {
       text: ''
@@ -237,12 +257,21 @@ const getAlarmNumTop = async () => {
   });
 };
 const alarmCountData = async () => {
+  // console.log('[device.vue] alarmCountData called');
   const params = {
     startTime: dateRange.value[0],
     endTime: dateRange.value[1]
   };
   const res = await alarmCountByType(params);
+  if (!alarmCount.value) {
+    // console.log('[alarmCount] ref is null when initializing echarts');
+  } else {
+    // console.log('[alarmCount] ref:', alarmCount.value);
+    echarts.dispose(alarmCount.value);
+    // console.log('[alarmCount] echarts instance disposed before init');
+  }
   const alarmTypeInstance = echarts.init(alarmCount.value, 'macaroons');
+  // console.log('[alarmCount] echarts instance created:', alarmTypeInstance);
   alarmTypeInstance.setOption({
     xAxis: {
       type: 'category',
@@ -260,13 +289,22 @@ const alarmCountData = async () => {
   });
 };
 const getAlarmLevel = async () => {
+  // console.log('[device.vue] getAlarmLevel called');
   const params = {
     startTime: dateRange.value[0],
     endTime: dateRange.value[1]
   };
   const res = await alarmCountByLevel(params);
 
+  if (!alarmLevel.value) {
+    // console.log('[alarmLevel] ref is null when initializing echarts');
+  } else {
+    // console.log('[alarmLevel] ref:', alarmLevel.value);
+    echarts.dispose(alarmLevel.value);
+    // console.log('[alarmLevel] echarts instance disposed before init');
+  }
   const alarmLevelInstance = echarts.init(alarmLevel.value, 'macaroons');
+  // console.log('[alarmLevel] echarts instance created:', alarmLevelInstance);
   alarmLevelInstance.setOption({
     title: {
       text: ''
@@ -313,6 +351,7 @@ const getAlarmLevel = async () => {
   });
 };
 const getDeviceStatistics = async () => {
+  // console.log('[device.vue] getDeviceStatistics called');
   const res = await deviceStatistics();
   stats.forEach((item) => {
     switch (item.label) {
@@ -330,7 +369,15 @@ const getDeviceStatistics = async () => {
         break;
     }
   });
+  if (!typePie.value) {
+    // console.log('[typePie] ref is null when initializing echarts');
+  } else {
+    // console.log('[typePie] ref:', typePie.value);
+    echarts.dispose(typePie.value);
+    // console.log('[typePie] echarts instance disposed before init');
+  }
   const typePieInstance = echarts.init(typePie.value, 'macaroons');
+  // console.log('[typePie] echarts instance created:', typePieInstance);
   typePieInstance.setOption({
     title: {
       text: '',
@@ -366,6 +413,7 @@ const getDeviceStatistics = async () => {
   });
 };
 const getAlarmList = async () => {
+  // console.log('[device.vue] getAlarmList called');
   loading.value = true;
   const res = await listDeviceErrorRecord(dialogQueryParams.value);
   alarmList.value = res.rows;
@@ -373,6 +421,7 @@ const getAlarmList = async () => {
   loading.value = false;
 };
 onMounted(() => {
+  // console.log('[device.vue] onMounted called');
   handleDateRangeChange();
   getDeviceStatistics();
   getAlarmList();

+ 277 - 147
smsb-plus-ui/src/views/smsb/dashboard/play.vue

@@ -1,152 +1,156 @@
 <template>
-  <div class="play-dashboard-page">
-    <el-container>
-      <el-main class="scrollable-main main-content">
-        <div class="play-dashboard-header">
-          <el-card shadow="hover" class="header-card">
-            <el-row justify="end" align="middle">
-              <el-col :span="19" class="text-right">
-                <el-radio-group v-model="timeRadio" size="small" @change="handleDateRangeChange">
-                  <el-radio-button label="近7天" value="week" />
-                  <el-radio-button label="近30天" value="month" />
-                </el-radio-group>
-              </el-col>
-              <el-col :span="5" class="text-right">
-                <el-date-picker v-model="dateRange" type="daterange" range-separator="-" start-placeholder="开始日期"
-                  end-placeholder="结束日期" class="date-picker" />
-              </el-col>
-            </el-row>
-          </el-card>
-        </div>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-card shadow="hover" class="stat-card">
-              <el-row :gutter="20">
-                <el-col :span="8">
-                  <h2 class="stat-number success">{{ totalNum }}</h2>
-                  <p class="success">总内容量</p>
+  <div class="play-dashboard-root">
+    <div class="play-dashboard-page">
+      <el-container>
+        <el-main class="scrollable-main main-content">
+          <div class="play-dashboard-header">
+            <el-card shadow="hover" class="header-card">
+              <el-row justify="end" align="middle">
+                <el-col :span="19" class="text-right">
+                  <el-radio-group v-model="timeRadio" size="small" @change="handleDateRangeChange">
+                    <el-radio-button label="近7天" value="week" />
+                    <el-radio-button label="近30天" value="month" />
+                  </el-radio-group>
                 </el-col>
-                <el-col :span="16">
-                  <div ref="createLine" class="chart-small"></div>
+                <el-col :span="5" class="text-right">
+                  <el-date-picker v-model="dateRange" type="daterange" range-separator="-" start-placeholder="开始日期"
+                    end-placeholder="结束日期" class="date-picker" />
                 </el-col>
               </el-row>
             </el-card>
-          </el-col>
-          <el-col :span="6">
-            <el-card shadow="hover" class="stat-card">
-              <h2 class="stat-number warning">{{ imageNum }}</h2>
-              <p class="warning">图片素材</p>
-            </el-card>
-          </el-col>
-          <el-col :span="6">
-            <el-card shadow="hover" class="stat-card">
-              <h2 class="stat-number success">{{ videoNum }}</h2>
-              <p class="success">视频素材</p>
-            </el-card>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20" class="margin-top-20">
-          <el-col :span="18">
-            <el-row :gutter="20">
-              <el-col :span="8">
-                <el-card shadow="hover">
-                  <h3>内容占比</h3>
-                  <div ref="typePie" class="chart-placeholder"></div>
-                </el-card>
-              </el-col>
-              <el-col :span="8">
-                <el-card shadow="hover">
-                  <h3>类型占比</h3>
-                  <div ref="tagPie" class="chart-placeholder"></div>
-                </el-card>
-              </el-col>
-              <el-col :span="8">
-                <el-card shadow="hover">
-                  <h3>素材统计</h3>
-                  <div ref="typeAndTag" class="chart-placeholder"></div>
-                </el-card>
-              </el-col>
-            </el-row>
-            <el-row :gutter="20" class="margin-top-20">
-              <el-col :span="12">
-                <el-card shadow="hover" class="disk-card">
-                  <h3>空间使用情况</h3>
-                  <div class="disk-info">
-                    <h2>已用空间:{{ diskUsed }}GB/{{ diskTotal }}GB</h2>
-                  </div>
-
-                  <div class="disk-progress">
-                    <el-row align="middle">
-                      <el-col :span="3">
-                        <h3>图片:</h3>
-                      </el-col>
-                      <el-col :span="21">
-                        <el-progress :text-inside="true" :stroke-width="26" :percentage="diskImage" />
-                      </el-col>
-                    </el-row>
-                  </div>
-                  <div class="disk-progress">
-                    <el-row align="middle">
-                      <el-col :span="3">
-                        <h3>视频:</h3>
-                      </el-col>
-                      <el-col :span="21">
-                        <el-progress :text-inside="true" :stroke-width="26" status="success" :percentage="diskVideo" />
-                      </el-col>
-                    </el-row>
-                  </div>
-                </el-card>
-              </el-col>
-              <el-col :span="12">
-                <el-card shadow="hover">
-                  <h3>播放时长统计</h3>
-                  <div ref="onlineTimeLine" class="chart-placeholder"></div>
-                </el-card>
-              </el-col>
-            </el-row>
-          </el-col>
-          <el-col :span="6">
-            <el-card shadow="hover">
-              <div>
-                <el-row :gutter="20" align="middle">
-                  <el-col :span="12">
-                    <h3>内容TOP5</h3>
-                  </el-col>
-                  <el-col :span="12">
-                    <el-select v-model="topType" @change="getPlayTop">
-                      <el-option label="播放次数" value="1" />
-                      <el-option label="播放时长" value="2" />
-                    </el-select>
-                  </el-col>
-                </el-row>
-              </div>
-              <div class="top-stats">
-                <el-row :gutter="20">
-                  <el-col :span="12">
-                    <h3>图片</h3>
-                  </el-col>
-                  <el-col :span="12">
-                    <h3>{{ imageTopNum }}</h3>
-                  </el-col>
-                </el-row>
-              </div>
-              <div ref="playImageTop" class="chart-placeholder"></div>
-              <div class="top-stats">
+          </div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-card shadow="hover" class="stat-card">
                 <el-row :gutter="20">
-                  <el-col :span="12">
-                    <h3>视频</h3>
+                  <el-col :span="8">
+                    <h2 class="stat-number success">{{ totalNum }}</h2>
+                    <p class="success">总内容量</p>
                   </el-col>
-                  <el-col :span="12">
-                    <h3>{{ videoTopNum }}</h3>
+                  <el-col :span="16">
+                    <div ref="createLine" class="chart-small"></div>
                   </el-col>
                 </el-row>
-              </div>
-              <div ref="playVideoTop" class="chart-placeholder"></div>
-            </el-card>
-          </el-col>
-        </el-row>
-      </el-main>
-    </el-container>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card shadow="hover" class="stat-card">
+                <h2 class="stat-number warning">{{ imageNum }}</h2>
+                <p class="warning">图片素材</p>
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card shadow="hover" class="stat-card">
+                <h2 class="stat-number success">{{ videoNum }}</h2>
+                <p class="success">视频素材</p>
+              </el-card>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20" class="margin-top-20">
+            <el-col :span="18">
+              <el-row :gutter="20">
+                <el-col :span="8">
+                  <el-card shadow="hover">
+                    <h3>内容占比</h3>
+                    <div ref="typePie" class="chart-placeholder"></div>
+                  </el-card>
+                </el-col>
+                <el-col :span="8">
+                  <el-card shadow="hover">
+                    <h3>类型占比</h3>
+                    <div ref="tagPie" class="chart-placeholder"></div>
+                  </el-card>
+                </el-col>
+                <el-col :span="8">
+                  <el-card shadow="hover">
+                    <h3>素材统计</h3>
+                    <div ref="typeAndTag" class="chart-placeholder"></div>
+                  </el-card>
+                </el-col>
+              </el-row>
+              <el-row :gutter="20" class="margin-top-20">
+                <el-col :span="12">
+                  <el-card shadow="hover" class="disk-card">
+                    <h3>空间使用情况</h3>
+                    <div class="disk-info">
+                      <h2>已用空间:{{ diskUsed }}GB/{{ diskTotal }}GB</h2>
+                    </div>
+
+                    <div class="disk-progress">
+                      <el-row align="middle">
+                        <el-col :span="3">
+                          <h3>图片:</h3>
+                        </el-col>
+                        <el-col :span="21">
+                          <el-progress :text-inside="true" :stroke-width="26" :percentage="diskImage" />
+                        </el-col>
+                      </el-row>
+                    </div>
+                    <div class="disk-progress">
+                      <el-row align="middle">
+                        <el-col :span="3">
+                          <h3>视频:</h3>
+                        </el-col>
+                        <el-col :span="21">
+                          <el-progress :text-inside="true" :stroke-width="26" status="success"
+                            :percentage="diskVideo" />
+                        </el-col>
+                      </el-row>
+                    </div>
+                  </el-card>
+                </el-col>
+                <el-col :span="12">
+                  <el-card shadow="hover">
+                    <h3>播放时长统计</h3>
+                    <div ref="onlineTimeLine" class="chart-placeholder"></div>
+                  </el-card>
+                </el-col>
+              </el-row>
+            </el-col>
+            <el-col :span="6">
+              <el-card shadow="hover">
+                <div>
+                  <el-row :gutter="20" align="middle">
+                    <el-col :span="12">
+                      <h3>内容TOP5</h3>
+                    </el-col>
+                    <el-col :span="12">
+                      <el-select v-model="topType" @change="getPlayTop">
+                        <el-option label="播放次数" value="1" />
+                        <el-option label="播放时长" value="2" />
+                      </el-select>
+                    </el-col>
+                  </el-row>
+                </div>
+                <div class="top-stats">
+                  <el-row :gutter="20">
+                    <el-col :span="12">
+                      <h3>图片</h3>
+                    </el-col>
+                    <el-col :span="12">
+                      <h3>{{ imageTopNum }}</h3>
+                    </el-col>
+                  </el-row>
+                </div>
+                <div ref="playImageTop" class="chart-placeholder"></div>
+                <div class="top-stats">
+                  <el-row :gutter="20">
+                    <el-col :span="12">
+                      <h3>视频</h3>
+                    </el-col>
+                    <el-col :span="12">
+                      <h3>{{ videoTopNum }}</h3>
+                    </el-col>
+                  </el-row>
+                </div>
+                <div ref="playVideoTop" class="chart-placeholder"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+        </el-main>
+      </el-container>
+      <!-- 保证只有一个根节点,所有内容都包裹在 play-dashboard-root 下 -->
+    </div>
   </div>
 </template>
 
@@ -296,6 +300,55 @@ body,
 
 <script setup lang="ts">
 import * as echarts from 'echarts';
+import { onUnmounted } from 'vue';
+
+// echarts 实例变量
+let onlineTimeLineInstance = null;
+let playImageTopInstance = null;
+let playVideoTopInstance = null;
+let typeAndTagInstance = null;
+let tagPieInstance = null;
+let createLineInstance = null;
+let typePieInstance = null;
+
+onUnmounted(() => {
+  // 销毁所有 echarts 实例
+  if (onlineTimeLine?.value) {
+    echarts.dispose(onlineTimeLine.value);
+    onlineTimeLineInstance = null;
+    // console.log('[onlineTimeLine] echarts instance disposed');
+  }
+  if (playImageTop?.value) {
+    echarts.dispose(playImageTop.value);
+    playImageTopInstance = null;
+    // console.log('[playImageTop] echarts instance disposed');
+  }
+  if (playVideoTop?.value) {
+    echarts.dispose(playVideoTop.value);
+    playVideoTopInstance = null;
+    // console.log('[playVideoTop] echarts instance disposed');
+  }
+  if (typeAndTag?.value) {
+    echarts.dispose(typeAndTag.value);
+    typeAndTagInstance = null;
+    // console.log('[typeAndTag] echarts instance disposed');
+  }
+  if (tagPie?.value) {
+    echarts.dispose(tagPie.value);
+    tagPieInstance = null;
+    // console.log('[tagPie] echarts instance disposed');
+  }
+  if (createLine?.value) {
+    echarts.dispose(createLine.value);
+    createLineInstance = null;
+    // console.log('[createLine] echarts instance disposed');
+  }
+  if (typePie?.value) {
+    echarts.dispose(typePie.value);
+    typePieInstance = null;
+    // console.log('[typePie] echarts instance disposed');
+  }
+});
 import { diskUse, fileStatistics, fileStatisticsByTag, numLine, statisticsByTypeAndTag } from '@/api/smsb/source/minioData';
 import { playTopStatistics, sumOnlineTimeLine } from '@/api/smsb/source/play_record';
 import { ref, onMounted } from 'vue'; // Import ref and onMounted
@@ -356,7 +409,18 @@ const getOnlineTimeLine = async () => {
     };
     const res = await sumOnlineTimeLine(params);
 
-    const alarmLevelInstance = echarts.init(onlineTimeLine.value, 'macaroons');
+    if (!onlineTimeLine.value) {
+      // console.log('[onlineTimeLine] ref is null when initializing echarts');
+    } else {
+      // console.log('[onlineTimeLine] ref:', onlineTimeLine.value);
+    }
+    if (onlineTimeLine.value) {
+      echarts.dispose(onlineTimeLine.value);
+      // console.log('[onlineTimeLine] echarts instance disposed before init');
+    }
+    onlineTimeLineInstance = echarts.init(onlineTimeLine.value, 'macaroons');
+    // console.log('[onlineTimeLine] echarts instance created:', onlineTimeLineInstance);
+    // console.log('[onlineTimeLine] echarts instance created:', alarmLevelInstance);
     alarmLevelInstance.setOption({
       title: { text: '' },
       tooltip: { trigger: 'axis' },
@@ -405,7 +469,18 @@ const getPlayTop = async () => {
     imageTopNum.value = res.data.imageNum;
     videoTopNum.value = res.data.videoNum;
 
-    const playImageTopInstance = echarts.init(playImageTop.value, 'macaroons');
+    if (!playImageTop.value) {
+      // console.log('[playImageTop] ref is null when initializing echarts');
+    } else {
+      // console.log('[playImageTop] ref:', playImageTop.value);
+    }
+    if (playImageTop.value) {
+      echarts.dispose(playImageTop.value);
+      // console.log('[playImageTop] echarts instance disposed before init');
+    }
+    playImageTopInstance = echarts.init(playImageTop.value, 'macaroons');
+    // console.log('[playImageTop] echarts instance created:', playImageTopInstance);
+    // console.log('[playImageTop] echarts instance created:', playImageTopInstance);
     playImageTopInstance.setOption({
       title: { text: '' },
       tooltip: {
@@ -424,7 +499,18 @@ const getPlayTop = async () => {
       series: [{ name: '', type: 'bar', data: res.data.imageNumberList }]
     });
 
-    const playVideoTopInstance = echarts.init(playVideoTop.value, 'macaroons');
+    if (!playVideoTop.value) {
+      // console.log('[playVideoTop] ref is null when initializing echarts');
+    } else {
+      // console.log('[playVideoTop] ref:', playVideoTop.value);
+    }
+    if (playVideoTop.value) {
+      echarts.dispose(playVideoTop.value);
+      // console.log('[playVideoTop] echarts instance disposed before init');
+    }
+    playVideoTopInstance = echarts.init(playVideoTop.value, 'macaroons');
+    // console.log('[playVideoTop] echarts instance created:', playVideoTopInstance);
+    // console.log('[playVideoTop] echarts instance created:', playVideoTopInstance);
     playVideoTopInstance.setOption({
       title: { text: '' },
       tooltip: {
@@ -452,7 +538,18 @@ const getNumByTypeAndTag = async () => {
     const res = await statisticsByTypeAndTag();
     const imageTagList = res.data.imageTagList;
     const videoTagList = res.data.videoTagList;
-    const typeAndTagInstance = echarts.init(typeAndTag.value, 'macaroons');
+    if (!typeAndTag.value) {
+      // console.log('[typeAndTag] ref is null when initializing echarts');
+    } else {
+      // console.log('[typeAndTag] ref:', typeAndTag.value);
+    }
+    if (typeAndTag.value) {
+      echarts.dispose(typeAndTag.value);
+      // console.log('[typeAndTag] echarts instance disposed before init');
+    }
+    typeAndTagInstance = echarts.init(typeAndTag.value, 'macaroons');
+    // console.log('[typeAndTag] echarts instance created:', typeAndTagInstance);
+    // console.log('[typeAndTag] echarts instance created:', typeAndTagInstance);
     typeAndTagInstance.setOption({
       legend: {},
       tooltip: {},
@@ -471,7 +568,18 @@ const getNumByTypeAndTag = async () => {
 const getNumByTag = async () => {
   try {
     const res = await fileStatisticsByTag();
-    const tagPieInstance = echarts.init(tagPie.value, 'macaroons');
+    if (!tagPie.value) {
+      // console.log('[tagPie] ref is null when initializing echarts');
+    } else {
+      // console.log('[tagPie] ref:', tagPie.value);
+    }
+    if (tagPie.value) {
+      echarts.dispose(tagPie.value);
+      // console.log('[tagPie] echarts instance disposed before init');
+    }
+    tagPieInstance = echarts.init(tagPie.value, 'macaroons');
+    // console.log('[tagPie] echarts instance created:', tagPieInstance);
+    // console.log('[tagPie] echarts instance created:', tagPieInstance);
     tagPieInstance.setOption({
       title: { text: '', subtext: '', left: 'center' },
       tooltip: { trigger: 'item' },
@@ -513,7 +621,18 @@ const getNumAndLine = async () => {
     videoNum.value = res.data.videoNum;
 
     const lineRes = await numLine(params);
-    const createLineInstance = echarts.init(createLine.value, 'macaroons');
+    if (!createLine.value) {
+      // console.log('[createLine] ref is null when initializing echarts');
+    } else {
+      // console.log('[createLine] ref:', createLine.value);
+    }
+    if (createLine.value) {
+      echarts.dispose(createLine.value);
+      // console.log('[createLine] echarts instance disposed before init');
+    }
+    createLineInstance = echarts.init(createLine.value, 'macaroons');
+    // console.log('[createLine] echarts instance created:', createLineInstance);
+    // console.log('[createLine] echarts instance created:', createLineInstance);
     createLineInstance.setOption({
       title: { text: '' },
       tooltip: { trigger: 'axis' },
@@ -541,7 +660,18 @@ const getNumAndLine = async () => {
       ]
     });
 
-    const typePieInstance = echarts.init(typePie.value, 'macaroons');
+    if (!typePie.value) {
+      // console.log('[typePie] ref is null when initializing echarts');
+    } else {
+      // console.log('[typePie] ref:', typePie.value);
+    }
+    if (typePie.value) {
+      echarts.dispose(typePie.value);
+      // console.log('[typePie] echarts instance disposed before init');
+    }
+    typePieInstance = echarts.init(typePie.value, 'macaroons');
+    // console.log('[typePie] echarts instance created:', typePieInstance);
+    // console.log('[typePie] echarts instance created:', typePieInstance);
     typePieInstance.setOption({
       title: { text: '', subtext: '', left: 'center' },
       tooltip: { trigger: 'item' },

+ 32 - 0
smsb-plus-ui/src/views/smsb/dashboard/play_info.vue

@@ -188,7 +188,17 @@ const getTimesAndDurationLine = async () => {
   };
   const res = await timesAndDurationLine(params);
   const playDurationList = res.data.playDurationList.map((seconds) => Math.round((seconds / 60) * 100) / 100);
+  if (!tadLine.value) {
+    // console.log('[tadLine] ref is null when initializing echarts');
+  } else {
+    // console.log('[tadLine] ref:', tadLine.value);
+  }
+  if (tadLine.value) {
+    echarts.dispose(tadLine.value);
+    // console.log('[tadLine] echarts instance disposed before init');
+  }
   const tadLineInstance = echarts.init(tadLine.value, 'macaroons');
+  // console.log('[tadLine] echarts instance created:', tadLineInstance);
   tadLineInstance.setOption({
     title: {
       text: ''
@@ -242,7 +252,17 @@ const getPushNumber = async () => {
   videoNum.value = res.data.videoNum;
 
   const lineRes = await getPushLine();
+  if (!pushLine.value) {
+    // console.log('[pushLine] ref is null when initializing echarts');
+  } else {
+    // console.log('[pushLine] ref:', pushLine.value);
+  }
+  if (pushLine.value) {
+    echarts.dispose(pushLine.value);
+    // console.log('[pushLine] echarts instance disposed before init');
+  }
   const pushLineInstance = echarts.init(pushLine.value, 'macaroons');
+  // console.log('[pushLine] echarts instance created:', pushLineInstance);
   pushLineInstance.setOption({
     title: {
       text: ''
@@ -386,6 +406,18 @@ onMounted(() => {
   // getPushNumber();
   // getRecordList();
 });
+
+import { onUnmounted, nextTick } from 'vue';
+onUnmounted(() => {
+  if (pushLine?.value) {
+    echarts.dispose(pushLine.value);
+    // console.log('[play_info] pushLine disposed on unmount');
+  }
+  if (tadLine?.value) {
+    echarts.dispose(tadLine.value);
+    // console.log('[play_info] tadLine disposed on unmount');
+  }
+});
 </script>
 <style scoped>
 .stat-card {