Browse Source

修复chart dom元素改变再渲染

chenlei1231 2 months ago
parent
commit
7a3b5a3b0e
2 changed files with 268 additions and 266 deletions
  1. 3 3
      src/hooks/web/useECharts.ts
  2. 265 263
      src/views/adweb/data/components/OriginCountryAnalysis.vue

+ 3 - 3
src/hooks/web/useECharts.ts

@@ -10,7 +10,7 @@ import echarts from '/@/utils/lib/echarts';
 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
 
 export function useECharts(elRef: Ref<HTMLDivElement>, theme: 'light' | 'dark' | 'default' = 'default') {
-  console.log("---useECharts---初始化加载---")
+  console.log('---useECharts---初始化加载---');
 
   const { getDarkMode: getSysDarkMode } = useRootSetting();
 
@@ -55,7 +55,7 @@ export function useECharts(elRef: Ref<HTMLDivElement>, theme: 'light' | 'dark' |
     }
   }
 
-  function setOptions(options: EChartsOption, clear = true) {
+  function setOptions(options: EChartsOption, clear = true, force = false) {
     cacheOptions.value = options;
     if (unref(elRef)?.offsetHeight === 0) {
       useTimeoutFn(() => {
@@ -65,7 +65,7 @@ export function useECharts(elRef: Ref<HTMLDivElement>, theme: 'light' | 'dark' |
     }
     nextTick(() => {
       useTimeoutFn(() => {
-        if (!chartInstance) {
+        if (!chartInstance || force) {
           initCharts(getDarkMode.value as 'default');
 
           if (!chartInstance) return;

+ 265 - 263
src/views/adweb/data/components/OriginCountryAnalysis.vue

@@ -1,344 +1,346 @@
 <template>
-    <a-card title="原产国" :loading="loading">
-        <template #extra>
-            <span>排序:</span>
-            <a-radio-group v-model:value="sortMode" button-style="solid" style="margin-left: 16px;">
-                <a-radio-button value="count">交易次数</a-radio-button>
-                <a-radio-button value="weight">交易重量</a-radio-button>
-                <a-radio-button value="amount">交易金额</a-radio-button>
-            </a-radio-group>
+  <a-card title="原产国" :loading="loading">
+    <template #extra>
+      <span>排序:</span>
+      <a-radio-group v-model:value="sortMode" button-style="solid" style="margin-left: 16px">
+        <a-radio-button value="count">交易次数</a-radio-button>
+        <a-radio-button value="weight">交易重量</a-radio-button>
+        <a-radio-button value="amount">交易金额</a-radio-button>
+      </a-radio-group>
+    </template>
+    <!-- 添加切换按钮组 -->
+    <div class="switch-button">
+      <a-radio-group v-model:value="viewMode" button-style="solid">
+        <a-radio-button value="chart">图表</a-radio-button>
+        <a-radio-button value="table">列表</a-radio-button>
+      </a-radio-group>
+    </div>
+
+    <!-- 图表视图 -->
+    <div v-show="viewMode === 'chart'" ref="chartRef" style="height: 400px"></div>
+
+    <!-- 表格视图 -->
+    <a-table
+      v-show="viewMode === 'table'"
+      :columns="columns"
+      :data-source="tableData"
+      :pagination="{
+        current: pagination.current,
+        pageSize: pagination.pageSize,
+        total: pagination.total,
+        showSizeChanger: true,
+        showQuickJumper: true,
+        showTotal: (total) => `共 ${total} 条`,
+        onShowSizeChange: handleTableChange,
+      }"
+      @change="handleTableChange"
+      size="middle"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.dataIndex === 'count' || column.dataIndex === 'num_supplier'">
+          {{ formatNumber(record[column.dataIndex]) }}
         </template>
-        <!-- 添加切换按钮组 -->
-        <div class="switch-button">
-            <a-radio-group v-model:value="viewMode" button-style="solid">
-                <a-radio-button value="chart">图表</a-radio-button>
-                <a-radio-button value="table">列表</a-radio-button>
-            </a-radio-group>
-        </div>
-
-        <!-- 图表视图 -->
-        <div v-show="viewMode === 'chart'" ref="chartRef" style="height: 400px"></div>
-
-        <!-- 表格视图 -->
-        <a-table v-show="viewMode === 'table'" :columns="columns" :data-source="tableData" :pagination="{
-            current: pagination.current,
-            pageSize: pagination.pageSize,
-            total: pagination.total,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条`,
-            onShowSizeChange: handleTableChange,
-        }" @change="handleTableChange" size="middle">
-            <template #bodyCell="{ column, record }">
-                <template v-if="column.dataIndex === 'count' || column.dataIndex === 'num_supplier'">
-                    {{ formatNumber(record[column.dataIndex]) }}
-                </template>
-                <template v-if="column.dataIndex === 'sum_weight' || column.dataIndex === 'sum_amount'">
-                    {{ formatDecimal(record[column.dataIndex]) }}
-                </template>
-            </template>
-        </a-table>
-    </a-card>
+        <template v-if="column.dataIndex === 'sum_weight' || column.dataIndex === 'sum_amount'">
+          {{ formatDecimal(record[column.dataIndex]) }}
+        </template>
+      </template>
+    </a-table>
+  </a-card>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, watch, nextTick, onUnmounted, Ref } from 'vue';
-import { useECharts } from '/@/hooks/web/useECharts';
-import { getOrigCountryReport } from '../customsData.api';
+  import { ref, onMounted, watch, nextTick, onUnmounted, Ref } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { getOrigCountryReport } from '../customsData.api';
 
-const chartRef = ref<HTMLDivElement | null>(null);
-const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
 
-const props = defineProps({
+  const props = defineProps({
     queryParam: {
-        type: Object,
-        required: true
-    }
-});
+      type: Object,
+      required: true,
+    },
+  });
 
-const loading = ref(false);
-const viewMode = ref('chart');
-const sortMode = ref('count');
-const allBuckets = ref({});
+  const loading = ref(false);
+  const viewMode = ref('chart');
+  const sortMode = ref('count');
+  const allBuckets = ref({});
 
-let chart = null;
+  let chart = null;
 
-// 定义表格列
-const columns = [
+  // 定义表格列
+  const columns = [
     {
-        title: '原产国',
-        dataIndex: 'val_cn',
-        key: 'val_cn',
+      title: '原产国',
+      dataIndex: 'val_cn',
+      key: 'val_cn',
     },
     {
-        title: '交易次数',
-        dataIndex: 'count',
-        key: 'count',
+      title: '交易次数',
+      dataIndex: 'count',
+      key: 'count',
     },
     {
-        title: '供应商数量',
-        dataIndex: 'num_supplier',
-        key: 'num_supplier',
+      title: '供应商数量',
+      dataIndex: 'num_supplier',
+      key: 'num_supplier',
     },
     {
-        title: '重量(KG)',
-        dataIndex: 'sum_weight',
-        key: 'sum_weight',
+      title: '重量(KG)',
+      dataIndex: 'sum_weight',
+      key: 'sum_weight',
     },
     {
-        title: '金额($)',
-        dataIndex: 'sum_amount',
-        key: 'sum_amount',
+      title: '金额($)',
+      dataIndex: 'sum_amount',
+      key: 'sum_amount',
     },
     {
-        title: '百分比',  // 新增百分比列
-        dataIndex: 'percentage',
-        key: 'percentage',
-        customRender: ({ record }) => {
-            const percent = (() => {
-                const valueKeyMap = {
-                    count: 'count',
-                    weight: 'sum_weight',
-                    amount: 'sum_amount'
-                };
-                const totalKeyMap = {
-                    count: 'count',
-                    weight: 'weight_sum',
-                    amount: 'amount_sum'
-                };
-
-                const selectedValue = record[valueKeyMap[sortMode.value]];
-                const total = allBuckets.value[totalKeyMap[sortMode.value]] || 0;
-
-                return total > 0 && selectedValue > 0
-                    ? ((selectedValue / total) * 100).toFixed(2)
-                    : 0.00;
-            })();
-
-            return `${percent}%`;
-        }
-    }
-];
+      title: '百分比', // 新增百分比列
+      dataIndex: 'percentage',
+      key: 'percentage',
+      customRender: ({ record }) => {
+        const percent = (() => {
+          const valueKeyMap = {
+            count: 'count',
+            weight: 'sum_weight',
+            amount: 'sum_amount',
+          };
+          const totalKeyMap = {
+            count: 'count',
+            weight: 'weight_sum',
+            amount: 'amount_sum',
+          };
+
+          const selectedValue = record[valueKeyMap[sortMode.value]];
+          const total = allBuckets.value[totalKeyMap[sortMode.value]] || 0;
+
+          return total > 0 && selectedValue > 0 ? ((selectedValue / total) * 100).toFixed(2) : 0.0;
+        })();
 
-// 格式化整数
-const formatNumber = (num: string | number) => {
+        return `${percent}%`;
+      },
+    },
+  ];
+
+  // 格式化整数
+  const formatNumber = (num: string | number) => {
     return Number(num).toLocaleString();
-};
+  };
 
-// 格式化小数(保留两位)
-const formatDecimal = (num: string | number) => {
+  // 格式化小数(保留两位)
+  const formatDecimal = (num: string | number) => {
     return Number(num).toLocaleString(undefined, {
-        minimumFractionDigits: 2,
-        maximumFractionDigits: 2
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2,
     });
-};
+  };
 
-// 计算表格数据
-const tableData = ref([]);
-const pagination = ref({
+  // 计算表格数据
+  const tableData = ref([]);
+  const pagination = ref({
     current: 1,
     pageSize: 10,
-    total: 0
-});
-const chartData = ref([]);
+    total: 0,
+  });
+  const chartData = ref([]);
 
-const initChart = () => {
+  const initChart = () => {
     updateChart();
-};
+  };
 
-const updateChart = async () => {
+  const updateChart = async () => {
     console.log('updateChart');
     const params = {
-        sort: sortMode.value,
-        page: 1,
-        page_size: 10,
-        ...props.queryParam,
+      sort: sortMode.value,
+      page: 1,
+      page_size: 10,
+      ...props.queryParam,
     };
 
     try {
-        const res = await getOrigCountryReport(params);
-
-        if (res && res.result && res.result.data && res.result.data.result && res.result.data.result.buckets) {
-            chartData.value = res.result.data.result.buckets;
-            allBuckets.value = res.result.data.result.allBuckets;
-        } else {
-            console.error('Unexpected API response structure:', res);
-            return; // Exit if the response structure is not as expected
-        }
-
-        const valueKeyMap = {
-            count: 'count',
-            weight: 'sum_weight',
-            amount: 'sum_amount'
-        };
-
-        // 获取前10个数据并计算总数
-        const data = chartData.value
-            .sort((a, b) => Number(b[valueKeyMap[sortMode.value]]) - Number(a[valueKeyMap[sortMode.value]]))
-            .slice(0, 10);
-
-        // 根据 sortMode 映射到 allBuckets 的对应关系
-        const totalKeyMap = {
-            count: 'count',
-            weight: 'weight_sum',
-            amount: 'amount_sum'
-        };
-
-        const total = allBuckets.value[totalKeyMap[sortMode.value]] || 0; // 使用映射获取总数
-        const otherCount = total - data.reduce((sum, item) => sum + Number(item[valueKeyMap[sortMode.value]]), 0);
-
-        const option = {
-            tooltip: {
-                trigger: 'item',
-                formatter: (params) => {
-                    const percent = ((params.value / total) * 100).toFixed(2);
-                    return `${params.name}<br/>${sortMode.value === 'count' ? '交易次数' : sortMode.value === 'weight' ? '交易重量' : '交易金额'}: ${formatNumber(params.value)}<br/>占比: ${percent}%`;
-                }
+      const res = await getOrigCountryReport(params);
+
+      if (res && res.result && res.result.data && res.result.data.result && res.result.data.result.buckets) {
+        chartData.value = res.result.data.result.buckets;
+        allBuckets.value = res.result.data.result.allBuckets;
+      } else {
+        console.error('Unexpected API response structure:', res);
+        return; // Exit if the response structure is not as expected
+      }
+
+      const valueKeyMap = {
+        count: 'count',
+        weight: 'sum_weight',
+        amount: 'sum_amount',
+      };
+
+      // 获取前10个数据并计算总数
+      const data = chartData.value.sort((a, b) => Number(b[valueKeyMap[sortMode.value]]) - Number(a[valueKeyMap[sortMode.value]])).slice(0, 10);
+
+      // 根据 sortMode 映射到 allBuckets 的对应关系
+      const totalKeyMap = {
+        count: 'count',
+        weight: 'weight_sum',
+        amount: 'amount_sum',
+      };
+
+      const total = allBuckets.value[totalKeyMap[sortMode.value]] || 0; // 使用映射获取总数
+      const otherCount = total - data.reduce((sum, item) => sum + Number(item[valueKeyMap[sortMode.value]]), 0);
+
+      const option = {
+        tooltip: {
+          trigger: 'item',
+          formatter: (params) => {
+            const percent = ((params.value / total) * 100).toFixed(2);
+            return `${params.name}<br/>${sortMode.value === 'count' ? '交易次数' : sortMode.value === 'weight' ? '交易重量' : '交易金额'}: ${formatNumber(params.value)}<br/>占比: ${percent}%`;
+          },
+        },
+        legend: {
+          orient: 'vertical',
+          right: 10,
+          top: 'center',
+          type: 'scroll',
+        },
+        series: [
+          {
+            name: '交易次数',
+            type: 'pie',
+            radius: ['40%', '70%'],
+            avoidLabelOverlap: true,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: '#fff',
+              borderWidth: 2,
             },
-            legend: {
-                orient: 'vertical',
-                right: 10,
-                top: 'center',
-                type: 'scroll'
+            label: {
+              show: true,
+              formatter: (params) => {
+                const percent = total > 0 ? ((params.value / total) * 100).toFixed(2) : 0.0;
+                return `${params.name}\n${percent}%`;
+              },
             },
-            series: [
-                {
-                    name: '交易次数',
-                    type: 'pie',
-                    radius: ['40%', '70%'],
-                    avoidLabelOverlap: true,
-                    itemStyle: {
-                        borderRadius: 10,
-                        borderColor: '#fff',
-                        borderWidth: 2
-                    },
-                    label: {
-                        show: true,
-                        formatter: (params) => {
-                            const percent = total > 0 ? ((params.value / total) * 100).toFixed(2) : 0.00;
-                            return `${params.name}\n${percent}%`;
-                        }
-                    },
-                    emphasis: {
-                        label: {
-                            show: true,
-                            fontSize: 14,
-                            fontWeight: 'bold'
-                        }
-                    },
-                    data: [
-                        ...data.map(item => ({
-                            name: item.val_cn || item.val,
-                            value: Number(item[valueKeyMap[sortMode.value]])
-                        })),
-                        {
-                            name: '其他',
-                            value: otherCount
-                        }
-                    ]
-                }
-            ]
-        };
-
-        nextTick(() => {
-            console.log('setOptions');
-            setOptions(option);
-        });
+            emphasis: {
+              label: {
+                show: true,
+                fontSize: 14,
+                fontWeight: 'bold',
+              },
+            },
+            data: [
+              ...data.map((item) => ({
+                name: item.val_cn || item.val,
+                value: Number(item[valueKeyMap[sortMode.value]]),
+              })),
+              {
+                name: '其他',
+                value: otherCount,
+              },
+            ],
+          },
+        ],
+      };
+
+      nextTick(() => {
+        console.log('setOptions');
+        setOptions(option, true, true);
+      });
     } catch (error) {
-        console.error('Failed to fetch data:', error);
+      console.error('Failed to fetch data:', error);
     }
-};
+  };
 
-// Replace registerTransactionTable with this new method
-const handleTableChange = async (pag, filters, sorter) => {
+  // Replace registerTransactionTable with this new method
+  async function handleTableChange(pag, filters, sorter) {
     loading.value = true;
     try {
-        const params = {
-            sort: sortMode.value,
-            page: pag.current,
-            page_size: pag.pageSize,
-            ...props.queryParam,
+      const params = {
+        sort: sortMode.value,
+        page: pag.current,
+        page_size: pag.pageSize,
+        ...props.queryParam,
+      };
+
+      const res = await getOrigCountryReport(params);
+      if (res.result.data.result && res.result.data.result.buckets) {
+        pagination.value = {
+          current: pag.current,
+          pageSize: pag.pageSize,
+          total: res.result.data.result.numBuckets || 0,
         };
-
-        const res = await getOrigCountryReport(params);
-        if (res.result.data.result && res.result.data.result.buckets) {
-            pagination.value = {
-                current: pag.current,
-                pageSize: pag.pageSize,
-                total: res.result.data.result.numBuckets || 0,
-            };
-            tableData.value = res.result.data.result.buckets;
-
-        }
+        tableData.value = res.result.data.result.buckets;
+      }
     } catch (error) {
-        console.error('Failed to fetch data:', error);
+      console.error('Failed to fetch data:', error);
     } finally {
-        loading.value = false;
+      loading.value = false;
     }
-};
+  }
 
-// 监听数据变化
-watch(
+  // 监听数据变化
+  watch(
     () => props.queryParam,
     () => {
-        if (viewMode.value === 'chart') {
-            updateChart();
-        } else {
-            handleTableChange(pagination.value, {}, {});
-        }
+      if (viewMode.value === 'chart') {
+        updateChart();
+      } else {
+        handleTableChange(pagination.value, {}, {});
+      }
     },
     { deep: true }
-);
+  );
 
-// 监听视图模式变化
-watch(viewMode, (newValue) => {
+  // 监听视图模式变化
+  watch(viewMode, (newValue) => {
     if (newValue === 'chart') {
-        updateChart();
+      updateChart();
     } else {
-        handleTableChange(pagination.value, {}, {});
+      handleTableChange(pagination.value, {}, {});
     }
-});
+  });
 
-watch(sortMode, () => {
+  watch(sortMode, () => {
     if (viewMode.value === 'chart') {
-        updateChart();
+      updateChart();
     } else {
-        handleTableChange(pagination.value, {}, {});
+      handleTableChange(pagination.value, {}, {});
     }
-});
+  });
 
-onMounted(() => {
+  onMounted(() => {
     if (viewMode.value === 'chart') {
-        initChart();
+      initChart();
     }
-});
+  });
 
-// 监听窗口大小变化
-window.addEventListener('resize', () => {
+  // 监听窗口大小变化
+  window.addEventListener('resize', () => {
     if (viewMode.value === 'chart') {
-        chart?.resize();
+      chart?.resize();
     }
-});
+  });
 
-// 组件卸载时清理
-onUnmounted(() => {
+  // 组件卸载时清理
+  onUnmounted(() => {
     chart?.dispose();
     window.removeEventListener('resize', () => {
-        chart?.resize();
+      chart?.resize();
     });
-});
+  });
 </script>
 
 <style scoped>
-.ant-card {
+  .ant-card {
     margin-bottom: 24px;
-}
+  }
 
-:deep(.ant-table-pagination) {
+  :deep(.ant-table-pagination) {
     margin: 16px 0;
-}
+  }
 
-.switch-button {
+  .switch-button {
     margin-bottom: 16px;
-}
+  }
 </style>