Ver Fonte

fix<tools.ts, SP, SB, SD数据趋势图>: 修复SP, SB, SD数据趋势图显示错误问题; echarts包版本升级

WanGxC há 11 meses atrás
pai
commit
7174424994

+ 15 - 15
package-lock.json

@@ -24,7 +24,7 @@
 				"cropperjs": "^2.0.0-beta.4",
 				"dayjs": "^1.11.11",
 				"e-icon-picker": "^2.1.1",
-				"echarts": "^5.4.1",
+				"echarts": "^5.5.1",
 				"echarts-gl": "^2.0.9",
 				"echarts-wordcloud": "^2.1.0",
 				"element-plus": "^2.7.6",
@@ -5900,12 +5900,12 @@
 			"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
 		},
 		"node_modules/echarts": {
-			"version": "5.4.3",
-			"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
-			"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
+			"version": "5.5.1",
+			"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
+			"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
 			"dependencies": {
 				"tslib": "2.3.0",
-				"zrender": "5.4.4"
+				"zrender": "5.6.0"
 			}
 		},
 		"node_modules/echarts-gl": {
@@ -10205,9 +10205,9 @@
 			}
 		},
 		"node_modules/zrender": {
-			"version": "5.4.4",
-			"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
-			"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
+			"version": "5.6.0",
+			"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
+			"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
 			"dependencies": {
 				"tslib": "2.3.0"
 			}
@@ -14918,12 +14918,12 @@
 			"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
 		},
 		"echarts": {
-			"version": "5.4.3",
-			"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
-			"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
+			"version": "5.5.1",
+			"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
+			"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
 			"requires": {
 				"tslib": "2.3.0",
-				"zrender": "5.4.4"
+				"zrender": "5.6.0"
 			},
 			"dependencies": {
 				"tslib": {
@@ -18295,9 +18295,9 @@
 			"dev": true
 		},
 		"zrender": {
-			"version": "5.4.4",
-			"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
-			"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
+			"version": "5.6.0",
+			"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
+			"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
 			"requires": {
 				"tslib": "2.3.0"
 			},

+ 1 - 1
package.json

@@ -24,7 +24,7 @@
 		"cropperjs": "^2.0.0-beta.4",
 		"dayjs": "^1.11.11",
 		"e-icon-picker": "^2.1.1",
-		"echarts": "^5.4.1",
+		"echarts": "^5.5.1",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.1.0",
 		"element-plus": "^2.7.6",

+ 4 - 4
src/components/MetricsCards/index.vue

@@ -60,7 +60,7 @@ const unsetColor = (color: string ) => {
 const changedMetric = (newVal: string, oldVal: string) => {
   for (const info of props.metricItems) {
     if (info.value === newVal) {
-      info.disabled = true 
+      info.disabled = true
     } else if (info.value === oldVal) {
       info.disabled = false
     }
@@ -83,7 +83,7 @@ const clickCard = (metric: string) => {
     emits('update:modelValue', selectedMetric.value)
     emits('change', selectedMetric.value)
   } else {  // 不存在则添加
-    if (selectedMetric.value.length === 3) { 
+    if (selectedMetric.value.length === 3) {
       selectedMetric.value[2].metric = metric
       selectedMetric.value[2].label = metricMap.value[metric]
     } else {
@@ -115,7 +115,7 @@ watch(
     const dup:{[key: string]: boolean} = {}
     for (const info of displayMetrics.value) { dup[info.metric] = true }
     let needNum = 6 - displayMetrics.value.length
-    if (needNum > 0) {  
+    if (needNum > 0) {
       // 从所有维度中选择剩余
       for (const info of props.metricItems) {
         if (!dup[info.value]) {
@@ -131,7 +131,7 @@ watch(
         info.disabled = true
       } else {
         info.disabled = false
-      }  
+      }
     }
   }
 )

+ 254 - 161
src/views/adManage/sb/chartComponents/dataTendency.vue

@@ -1,67 +1,63 @@
 <script lang="ts" setup>
-import { ref,onMounted, onBeforeUnmount, Ref, unref, watch, computed } from 'vue'
-import * as echarts from 'echarts'
-import {sbCampaignMetricsEnum, spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
-import MetricsCards from '/@/components/MetricsCards/index.vue'
-import XEUtils from 'xe-utils'
-import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
-
-// import { useShopInfo } from '/@/stores/shopInfo'
-// import { usePublicData } from '/@/stores/publicData'
-// import { storeToRefs } from 'pinia'
+import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
+import * as echarts from 'echarts';
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js';
+import MetricsCards from '/@/components/MetricsCards/index.vue';
+import XEUtils from 'xe-utils';
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js';
 
 defineOptions({
-  name: "DataTendencyChart"
-})
+  name: 'DataTendencyChart',
+});
 
 interface Props {
-  fetchCard: Function,
-  fetchLine: Function,
-  fetchLineMonth?: Function,
-  fetchLineWeek?: Function,
-  query: {[key: string]: any},
-  initMetric?: ShowMetric[],
-  metricEnum?: {[key: string]: string}[]
+  fetchCard: Function;
+  fetchLine: Function;
+  fetchLineMonth?: Function;
+  fetchLineWeek?: Function;
+  query: { [key: string]: any };
+  initMetric?: ShowMetric[];
+  metricEnum?: { [key: string]: string }[];
 }
 
 const props = withDefaults(defineProps<Props>(), {
   initMetric: () => [
-    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
-    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
-    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+    { metric: 'Impression', color: '#0085ff', label: '曝光量' },
+    { metric: 'Click', color: '#3fd4cf', label: '点击量' },
+    { metric: 'Spend', color: '#ff9500', label: '花费' },
   ],
-  metricEnum: () => sbCampaignMetricsEnum
-})
-
-const metrics = ref(props.initMetric)
-// const shopInfo = useShopInfo()
-// const publicData = usePublicData()
-// const { dateRange } = storeToRefs(publicData)
-const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj:any
-const chartRef = ref()
-const statDim = ref('day')
+  metricEnum: () => spCampaignMetricsEnum,
+});
+
+const metrics = ref(props.initMetric);
+const metricsItems: Ref<MetricData[]> = ref([]);
+let chartObj: any;
+const chartRef = ref();
+const statDim = ref('day');
 const option: any = {
   dataset: {
-    source: []
+    source: [],
   },
   tooltip: {
     trigger: 'axis',
     axisPointer: {
       label: {
-        backgroundColor: '#6a7985'
-      }
-    }
+        backgroundColor: '#6a7985',
+      },
+    },
   },
   legend: {
-    selected: {},  // 控制显隐
-    show: false
+    selected: {}, // 控制显隐
+    show: false,
   },
   grid: {
-    top: 50, right: 150, bottom: 30, left: 65,
+    top: 50,
+    right: 150,
+    bottom: 30,
+    left: 65,
   },
   xAxis: {
-    type: 'category'
+    type: 'category',
   },
   yAxis: [
     {
@@ -69,13 +65,13 @@ const option: any = {
       type: 'value',
       name: '',
       splitLine: {
-        show: true // 设置显示分割线
+        show: true, // 设置显示分割线
       },
       axisLine: {
         show: true,
-				lineStyle: { color: '' }
+        lineStyle: { color: '' },
       },
-      show: true
+      show: true,
     },
     {
       id: 1,
@@ -83,15 +79,15 @@ const option: any = {
       name: '',
       position: 'right',
       splitLine: {
-        show: false
+        show: false,
       },
       axisLine: {
         show: true,
-				lineStyle: {
-					color: ''
-				}
+        lineStyle: {
+          color: '',
+        },
       },
-      show: true
+      show: true,
     },
     {
       id: 2,
@@ -100,16 +96,16 @@ const option: any = {
       offset: 90,
       name: '',
       splitLine: {
-        show: false
+        show: false,
       },
       axisLine: {
         show: true,
-				lineStyle: {
-					color: ''
-				}
+        lineStyle: {
+          color: '',
+        },
       },
-      show: true
-    }
+      show: true,
+    },
   ],
   series: [
     {
@@ -118,14 +114,14 @@ const option: any = {
       type: 'bar',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       barWidth: '18px',
       yAxisIndex: 0,
       itemStyle: {
         color: '',
-        borderRadius: 4,
-      }
+        borderRadius: [4, 4, 4, 4],
+      },
     },
     {
       id: 1,
@@ -133,7 +129,7 @@ const option: any = {
       type: 'line',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       symbolSize: 6,
       symbol: 'circle',
@@ -150,8 +146,8 @@ const option: any = {
         // ]),
       },
       emphasis: {
-        focus:'series'
-      }
+        focus: 'series',
+      },
     },
     {
       id: 2,
@@ -159,7 +155,7 @@ const option: any = {
       type: 'line',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       symbolSize: 6,
       symbol: 'circle',
@@ -168,126 +164,223 @@ const option: any = {
       itemStyle: {},
       areaStyle: {},
       emphasis: {
-        focus:'series'
-      }
-    }
-  ]
-}
-const loading = ref(true)
-const queryParams = computed(() => parseQueryParams(props.query))
+        focus: 'series',
+      },
+    },
+  ],
+};
+const loading = ref(true);
+const queryParams = computed(() => parseQueryParams(props.query));
 
 onMounted(() => {
-  getMetricsItems()
-	addResize()
+  getMetricsItems();
+  addResize();
   // initLine()
-  setTimeout(() => { initLine() }, 0)
-})
+  setTimeout(() => {
+    initLine();
+  }, 0);
+});
+
 onBeforeUnmount(() => {
-	if(chartObj) {
-		chartObj.dispose()
-    chartObj = null
-	}
-  removeResize()
-})
-
-const initLine = async () => {
-	chartObj = echarts.init(chartRef.value)
-	const items = await getDataset()
-	option.dataset.source = items
-
-  XEUtils.arrayEach(option.series, (info:any, index) => {
-    const color = metrics.value[index].color
-    info.name = metrics.value[index].label
-    info.encode.y = metrics.value[index].metric
-    if (info.type === 'bar') {
-      info.itemStyle.color = color
-    } else {
-      info.itemStyle = { color: color, borderColor: color }
-      info.areaStyle = {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: color + '53' },
-          { offset: 1, color: color + '03' },
-        ])
-      }
-    }
-  })
-  XEUtils.arrayEach(option.yAxis, (info:any, index) => {
-    info.name = metrics.value[index].label
-    info.axisLine.lineStyle.color = metrics.value[index].color
-  })
-
-  XEUtils.arrayEach(props.metricEnum, info => {
-    option.legend.selected[info.label] = false
-  })
-  for(const info of metrics.value) {
-    option.legend.selected[info.label] = true
+  if (chartObj) {
+    chartObj.dispose();
+    chartObj = null;
   }
-  // console.log(option)
-	chartObj.setOption(option)
-  loading.value = false
+  removeResize();
+});
+
+const metricColors = {
+  Impression: '#0085ff',
+  Click: '#3fd4cf',
+  Spend: '#ff9500',
+  // ... 其他指标的颜色
+};
+
+function getColorForMetric(metric: string): string {
+  return metricColors[metric] || '#999999'; // 如果没有预定义的颜色,返回一个默认颜色
+}
+
+async function initLine() {
+  chartObj = echarts.init(chartRef.value);
+  option.dataset.source = await getDataset();
+
+  // 初始化系列和 y 轴
+  option.series = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: index === 0 ? 'bar' : 'line',
+    encode: { x: 'Name', y: metric.metric },
+    yAxisIndex: index,
+    itemStyle: { color: option.series.type == 'bar' ? '#0085ff' : metric.color, borderRadius: [6, 6, 6, 6] },
+    lineStyle: { color: metric.color },
+    barWidth: '10%',
+    areaStyle:
+      index !== 0
+        ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: metric.color + '53' },
+              { offset: 1, color: metric.color + '03' },
+            ]),
+          }
+        : undefined,
+  }));
+
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: { show: true, lineStyle: { color: metric.color } },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 初始化图例
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  chartObj.setOption(option);
+  loading.value = false;
 }
-const getDataset = async () => {
-	if (statDim.value === 'week') {
+
+async function getDataset() {
+  if (statDim.value === 'week') {
     if (props.fetchLineWeek) {
-      const resp = await props.fetchLineWeek(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineWeek(queryParams.value);
+      return resp.data;
     }
   } else if (statDim.value === 'month') {
     if (props.fetchLineMonth) {
-      const resp = await props.fetchLineMonth(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineMonth(queryParams.value);
+      return resp.data;
     }
   } else {
-    const resp = await props.fetchLine(queryParams.value)
-    return resp.data
+    const resp = await props.fetchLine(queryParams.value);
+    return resp.data;
   }
 }
-const getMetricsItems = async () => {
-	const resp = await props.fetchCard(queryParams.value)
-	const data = resp.data
-  metricsItems.value.length = 0
-	XEUtils.arrayEach(props.metricEnum, info => {
-		const tmp:MetricData = {
-			label: info.label,
-			value: info.value,
-			metricVal: data[info.value],
-			gapVal: data[`gap${info.value}`],
-			preVal: data[`prev${info.value}`],
-		}
-		metricsItems.value.push(tmp)
-	})
-}
 
-const changeMetric = () => {
-  const opt = buildChartOpt(option, metrics.value)
-  chartObj.setOption(opt)
+async function getMetricsItems() {
+  const resp = await props.fetchCard(queryParams.value);
+  const data = resp.data;
+  metricsItems.value.length = 0;
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    const tmp: MetricData = {
+      label: info.label,
+      value: info.value,
+      metricVal: data[info.value],
+      gapVal: data[`gap${info.value}`],
+      preVal: data[`prev${info.value}`],
+    };
+    metricsItems.value.push(tmp);
+  });
 }
 
-const changeStatDim = async () => {
-  loading.value = true
-  let source = await getDataset()
-  if (source.length > 0) {
-    chartObj.setOption({dataset: {source: source}})
+// const changeMetric = () => {
+//   const opt = buildChartOpt(option, metrics.value)
+//   chartObj.setOption(opt)
+// }
+
+function changeMetric() {
+  // 更新图例选中状态
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  // 重新创建 series 数组,只包含选中的指标
+  option.series = metrics.value.map((metric, index) => {
+    const baseConfig = {
+      id: index,
+      name: metric.label,
+      type: metric.color === '#0085ff' ? 'bar' : 'line',
+      encode: { x: 'Name', y: metric.metric },
+      yAxisIndex: index,
+      itemStyle: { color: metric.color, borderRadius: [6, 6, 6, 6] },
+      lineStyle: { color: metric.color },
+      barWidth: '10%',
+    };
+
+    if (baseConfig != 'bar') {
+      baseConfig.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: metric.color + '53' },
+          { offset: 1, color: metric.color + '03' },
+        ]),
+      };
+    }
+
+    return baseConfig;
+  });
+
+  // 更新 y 轴显示状态
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: {
+      show: true,
+      lineStyle: { color: metric.color },
+    },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 确保只有必要的 y 轴
+  while (option.yAxis.length > metrics.value.length) {
+    option.yAxis.pop();
   }
-  loading.value = false
+
+  chartObj.setOption(option, true); // 使用 true 作为第二个参数,强制完全刷新
 }
 
 watch(
-  props.query,
-  async () => {
-    // console.log("------watch-----queryParams", props.query)
-    loading.value = true
-    await getMetricsItems()
-    const items = await getDataset()
-    const opt = { dataset: { source: items } }
-    chartObj.setOption(opt)
-    loading.value = false
+  metrics,
+  () => {
+    changeMetric();
+  },
+  { deep: true }
+);
+
+async function changeStatDim() {
+  loading.value = true;
+  let source = await getDataset();
+  if (source.length > 0) {
+    chartObj.setOption({ dataset: { source: source } });
   }
-)
+  loading.value = false;
+}
 
-const resizeChart = () => { chartObj.resize() }
-const addResize = () => { window.addEventListener('resize', resizeChart) }
-const removeResize = () => { window.removeEventListener('resize', resizeChart) }
+watch(props.query, async () => {
+  // console.log("------watch-----queryParams", props.query)
+  loading.value = true;
+  await getMetricsItems();
+  const items = await getDataset();
+  const opt = { dataset: { source: items } };
+  chartObj.setOption(opt);
+  loading.value = false;
+});
+
+function resizeChart() {
+  chartObj.resize();
+}
+
+function addResize() {
+  window.addEventListener('resize', resizeChart);
+}
+
+function removeResize() {
+  window.removeEventListener('resize', resizeChart);
+}
 </script>
 
 <template>
@@ -298,8 +391,8 @@ const removeResize = () => { window.removeEventListener('resize', resizeChart) }
       <el-radio-button label="week" :disabled="!props.fetchLineWeek">周</el-radio-button>
       <el-radio-button label="month" :disabled="!props.fetchLineWeek">月</el-radio-button>
     </el-radio-group>
-    <div style="height: 350px;" ref="chartRef"></div>
-</div>
+    <div style="height: 350px" ref="chartRef"></div>
+  </div>
 </template>
 
 <style scoped>

+ 193 - 118
src/views/adManage/sd/chartComponents/dataTendency.vue

@@ -1,28 +1,23 @@
 <script lang="ts" setup>
-import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue'
-import * as echarts from 'echarts'
-import { sdCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js'
-import MetricsCards from '/@/components/MetricsCards/index.vue'
-import XEUtils from 'xe-utils'
-import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
-
-// import { useShopInfo } from '/@/stores/shopInfo'
-// import { usePublicData } from '/@/stores/publicData'
-// import { storeToRefs } from 'pinia'
+import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
+import * as echarts from 'echarts';
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js';
+import MetricsCards from '/@/components/MetricsCards/index.vue';
+import XEUtils from 'xe-utils';
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js';
 
 defineOptions({
   name: 'DataTendencyChart',
-})
+});
 
 interface Props {
-  fetchCard: Function
-  fetchLine: Function
-  fetchLineMonth?: Function
-  fetchLineWeek?: Function
-  fetchLineHour?: Function
-  query: { [key: string]: any }
-  initMetric?: ShowMetric[]
-  metricEnum?: { [key: string]: string }[]
+  fetchCard: Function;
+  fetchLine: Function;
+  fetchLineMonth?: Function;
+  fetchLineWeek?: Function;
+  query: { [key: string]: any };
+  initMetric?: ShowMetric[];
+  metricEnum?: { [key: string]: string }[];
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -31,17 +26,14 @@ const props = withDefaults(defineProps<Props>(), {
     { metric: 'Click', color: '#3fd4cf', label: '点击量' },
     { metric: 'Spend', color: '#ff9500', label: '花费' },
   ],
-  metricEnum: () => sdCampaignMetricsEnum,
-})
+  metricEnum: () => spCampaignMetricsEnum,
+});
 
-const metrics = ref(props.initMetric)
-// const shopInfo = useShopInfo()
-// const publicData = usePublicData()
-// const { dateRange } = storeToRefs(publicData)
-const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj: any
-const chartRef = ref()
-const statDim = ref('day')
+const metrics = ref(props.initMetric);
+const metricsItems: Ref<MetricData[]> = ref([]);
+let chartObj: any;
+const chartRef = ref();
+const statDim = ref('day');
 const option: any = {
   dataset: {
     source: [],
@@ -70,7 +62,6 @@ const option: any = {
   yAxis: [
     {
       id: 0,
-      min: 0,
       type: 'value',
       name: '',
       splitLine: {
@@ -84,7 +75,6 @@ const option: any = {
     },
     {
       id: 1,
-      min: 0,
       type: 'value',
       name: '',
       position: 'right',
@@ -101,7 +91,6 @@ const option: any = {
     },
     {
       id: 2,
-      min: 0,
       type: 'value',
       position: 'right',
       offset: 90,
@@ -131,7 +120,7 @@ const option: any = {
       yAxisIndex: 0,
       itemStyle: {
         color: '',
-        borderRadius: 4,
+        borderRadius: [4, 4, 4, 4],
       },
     },
     {
@@ -179,87 +168,107 @@ const option: any = {
       },
     },
   ],
-}
-const loading = ref(true)
-const queryParams = computed(() => parseQueryParams(props.query))
+};
+const loading = ref(true);
+const queryParams = computed(() => parseQueryParams(props.query));
 
 onMounted(() => {
-  getMetricsItems()
-  addResize()
+  getMetricsItems();
+  addResize();
   // initLine()
   setTimeout(() => {
-    initLine()
-  }, 0)
-})
+    initLine();
+  }, 0);
+});
+
 onBeforeUnmount(() => {
   if (chartObj) {
-    chartObj.dispose()
-    chartObj = null
+    chartObj.dispose();
+    chartObj = null;
   }
-  removeResize()
-})
+  removeResize();
+});
 
-const initLine = async () => {
-  chartObj = echarts.init(chartRef.value)
-  const items = await getDataset()
-  option.dataset.source = items
+const metricColors = {
+  Impression: '#0085ff',
+  Click: '#3fd4cf',
+  Spend: '#ff9500',
+  // ... 其他指标的颜色
+};
 
-  XEUtils.arrayEach(option.series, (info: any, index) => {
-    const color = metrics.value[index].color
-    info.name = metrics.value[index].label
-    info.encode.y = metrics.value[index].metric
-    if (info.type === 'bar') {
-      info.itemStyle.color = color
-    } else {
-      info.itemStyle = { color: color, borderColor: color }
-      info.areaStyle = {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: color + '53' },
-          { offset: 1, color: color + '03' },
-        ]),
-      }
-    }
-  })
-  XEUtils.arrayEach(option.yAxis, (info: any, index) => {
-    info.name = metrics.value[index].label
-    info.axisLine.lineStyle.color = metrics.value[index].color
-  })
+function getColorForMetric(metric: string): string {
+  return metricColors[metric] || '#999999'; // 如果没有预定义的颜色,返回一个默认颜色
+}
 
-  XEUtils.arrayEach(props.metricEnum, (info) => {
-    option.legend.selected[info.label] = false
-  })
-  for (const info of metrics.value) {
-    option.legend.selected[info.label] = true
-  }
-  // console.log(option)
-  chartObj.setOption(option)
-  loading.value = false
+async function initLine() {
+  chartObj = echarts.init(chartRef.value);
+  option.dataset.source = await getDataset();
+
+  // 初始化系列和 y 轴
+  option.series = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: index === 0 ? 'bar' : 'line',
+    encode: { x: 'Name', y: metric.metric },
+    yAxisIndex: index,
+    itemStyle: { color: option.series.type == 'bar' ? '#0085ff' : metric.color, borderRadius: [6, 6, 6, 6] },
+    lineStyle: { color: metric.color },
+    barWidth: '10%',
+    areaStyle:
+      index !== 0
+        ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: metric.color + '53' },
+              { offset: 1, color: metric.color + '03' },
+            ]),
+          }
+        : undefined,
+  }));
+
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: { show: true, lineStyle: { color: metric.color } },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 初始化图例
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  chartObj.setOption(option);
+  loading.value = false;
 }
-const getDataset = async () => {
+
+async function getDataset() {
   if (statDim.value === 'week') {
     if (props.fetchLineWeek) {
-      const resp = await props.fetchLineWeek(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineWeek(queryParams.value);
+      return resp.data;
     }
   } else if (statDim.value === 'month') {
     if (props.fetchLineMonth) {
-      const resp = await props.fetchLineMonth(queryParams.value)
-      return resp.data
-    }
-  } else if (statDim.value === 'hour') {
-    if (props.fetchLineHour) {
-      const resp = await props.fetchLineHour(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineMonth(queryParams.value);
+      return resp.data;
     }
   } else {
-    const resp = await props.fetchLine(queryParams.value)
-    return resp.data
+    const resp = await props.fetchLine(queryParams.value);
+    return resp.data;
   }
 }
-const getMetricsItems = async () => {
-  const resp = await props.fetchCard(queryParams.value)
-  const data = resp.data
-  metricsItems.value.length = 0
+
+async function getMetricsItems() {
+  const resp = await props.fetchCard(queryParams.value);
+  const data = resp.data;
+  metricsItems.value.length = 0;
   XEUtils.arrayEach(props.metricEnum, (info) => {
     const tmp: MetricData = {
       label: info.label,
@@ -267,43 +276,109 @@ const getMetricsItems = async () => {
       metricVal: data[info.value],
       gapVal: data[`gap${info.value}`],
       preVal: data[`prev${info.value}`],
-    }
-    metricsItems.value.push(tmp)
-  })
+    };
+    metricsItems.value.push(tmp);
+  });
 }
 
-const changeMetric = () => {
-  const opt = buildChartOpt(option, metrics.value)
-  chartObj.setOption(opt)
+// const changeMetric = () => {
+//   const opt = buildChartOpt(option, metrics.value)
+//   chartObj.setOption(opt)
+// }
+
+function changeMetric() {
+  // 更新图例选中状态
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  // 重新创建 series 数组,只包含选中的指标
+  option.series = metrics.value.map((metric, index) => {
+    const baseConfig = {
+      id: index,
+      name: metric.label,
+      type: metric.color === '#0085ff' ? 'bar' : 'line',
+      encode: { x: 'Name', y: metric.metric },
+      yAxisIndex: index,
+      itemStyle: { color: metric.color, borderRadius: [6, 6, 6, 6] },
+      lineStyle: { color: metric.color },
+      barWidth: '10%',
+    };
+
+    if (baseConfig != 'bar') {
+      baseConfig.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: metric.color + '53' },
+          { offset: 1, color: metric.color + '03' },
+        ]),
+      };
+    }
+
+    return baseConfig;
+  });
+
+  // 更新 y 轴显示状态
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: {
+      show: true,
+      lineStyle: { color: metric.color },
+    },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 确保只有必要的 y 轴
+  while (option.yAxis.length > metrics.value.length) {
+    option.yAxis.pop();
+  }
+
+  chartObj.setOption(option, true); // 使用 true 作为第二个参数,强制完全刷新
 }
 
+watch(
+  metrics,
+  () => {
+    changeMetric();
+  },
+  { deep: true }
+);
 const changeStatDim = async () => {
-  loading.value = true
-  let source = await getDataset()
+  loading.value = true;
+  let source = await getDataset();
   if (source.length > 0) {
-    chartObj.setOption({ dataset: { source: source } })
+    chartObj.setOption({ dataset: { source: source } });
   }
-  loading.value = false
-}
+  loading.value = false;
+};
 
 watch(props.query, async () => {
   // console.log("------watch-----queryParams", props.query)
-  loading.value = true
-  await getMetricsItems()
-  const items = await getDataset()
-  const opt = { dataset: { source: items } }
-  chartObj.setOption(opt)
-  loading.value = false
-})
+  loading.value = true;
+  await getMetricsItems();
+  const items = await getDataset();
+  const opt = { dataset: { source: items } };
+  chartObj.setOption(opt);
+  loading.value = false;
+});
 
-const resizeChart = () => {
-  chartObj.resize()
+function resizeChart() {
+  chartObj.resize();
 }
-const addResize = () => {
-  window.addEventListener('resize', resizeChart)
+
+function addResize() {
+  window.addEventListener('resize', resizeChart);
 }
-const removeResize = () => {
-  window.removeEventListener('resize', resizeChart)
+
+function removeResize() {
+  window.removeEventListener('resize', resizeChart);
 }
 </script>
 

+ 251 - 160
src/views/adManage/sp/chartComponents/dataTendency.vue

@@ -1,67 +1,63 @@
 <script lang="ts" setup>
-import {computed, onBeforeUnmount, onMounted, Ref, ref, watch} from 'vue'
-import * as echarts from 'echarts'
-import {spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
-import MetricsCards from '/@/components/MetricsCards/index.vue'
-import XEUtils from 'xe-utils'
-import {buildChartOpt, parseQueryParams} from '/@/views/adManage/utils/tools.js'
-
-// import { useShopInfo } from '/@/stores/shopInfo'
-// import { usePublicData } from '/@/stores/publicData'
-// import { storeToRefs } from 'pinia'
+import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue';
+import * as echarts from 'echarts';
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js';
+import MetricsCards from '/@/components/MetricsCards/index.vue';
+import XEUtils from 'xe-utils';
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js';
 
 defineOptions({
-  name: "DataTendencyChart"
-})
+  name: 'DataTendencyChart',
+});
 
 interface Props {
-  fetchCard: Function,
-  fetchLine: Function,
-  fetchLineMonth?: Function,
-  fetchLineWeek?: Function,
-  query: {[key: string]: any},
-  initMetric?: ShowMetric[],
-  metricEnum?: {[key: string]: string}[]
+  fetchCard: Function;
+  fetchLine: Function;
+  fetchLineMonth?: Function;
+  fetchLineWeek?: Function;
+  query: { [key: string]: any };
+  initMetric?: ShowMetric[];
+  metricEnum?: { [key: string]: string }[];
 }
 
 const props = withDefaults(defineProps<Props>(), {
   initMetric: () => [
-    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
-    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
-    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+    { metric: 'Impression', color: '#0085ff', label: '曝光量' },
+    { metric: 'Click', color: '#3fd4cf', label: '点击量' },
+    { metric: 'Spend', color: '#ff9500', label: '花费' },
   ],
-  metricEnum: () => spCampaignMetricsEnum
-})
-
-const metrics = ref(props.initMetric)
-// const shopInfo = useShopInfo()
-// const publicData = usePublicData()
-// const { dateRange } = storeToRefs(publicData)
-const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj:any
-const chartRef = ref()
-const statDim = ref('day')
+  metricEnum: () => spCampaignMetricsEnum,
+});
+
+const metrics = ref(props.initMetric);
+const metricsItems: Ref<MetricData[]> = ref([]);
+let chartObj: any;
+const chartRef = ref();
+const statDim = ref('day');
 const option: any = {
   dataset: {
-    source: []
+    source: [],
   },
   tooltip: {
     trigger: 'axis',
     axisPointer: {
       label: {
-        backgroundColor: '#6a7985'
-      }
-    }
+        backgroundColor: '#6a7985',
+      },
+    },
   },
   legend: {
-    selected: {},  // 控制显隐
-    show: false
+    selected: {}, // 控制显隐
+    show: false,
   },
   grid: {
-    top: 50, right: 150, bottom: 30, left: 65,
+    top: 50,
+    right: 150,
+    bottom: 30,
+    left: 65,
   },
   xAxis: {
-    type: 'category'
+    type: 'category',
   },
   yAxis: [
     {
@@ -69,13 +65,13 @@ const option: any = {
       type: 'value',
       name: '',
       splitLine: {
-        show: true // 设置显示分割线
+        show: true, // 设置显示分割线
       },
       axisLine: {
         show: true,
-				lineStyle: { color: '' }
+        lineStyle: { color: '' },
       },
-      show: true
+      show: true,
     },
     {
       id: 1,
@@ -83,15 +79,15 @@ const option: any = {
       name: '',
       position: 'right',
       splitLine: {
-        show: false
+        show: false,
       },
       axisLine: {
         show: true,
-				lineStyle: {
-					color: ''
-				}
+        lineStyle: {
+          color: '',
+        },
       },
-      show: true
+      show: true,
     },
     {
       id: 2,
@@ -100,16 +96,16 @@ const option: any = {
       offset: 90,
       name: '',
       splitLine: {
-        show: false
+        show: false,
       },
       axisLine: {
         show: true,
-				lineStyle: {
-					color: ''
-				}
+        lineStyle: {
+          color: '',
+        },
       },
-      show: true
-    }
+      show: true,
+    },
   ],
   series: [
     {
@@ -118,14 +114,14 @@ const option: any = {
       type: 'bar',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       barWidth: '18px',
       yAxisIndex: 0,
       itemStyle: {
         color: '',
         borderRadius: [4, 4, 4, 4],
-      }
+      },
     },
     {
       id: 1,
@@ -133,7 +129,7 @@ const option: any = {
       type: 'line',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       symbolSize: 6,
       symbol: 'circle',
@@ -150,8 +146,8 @@ const option: any = {
         // ]),
       },
       emphasis: {
-        focus:'series'
-      }
+        focus: 'series',
+      },
     },
     {
       id: 2,
@@ -159,7 +155,7 @@ const option: any = {
       type: 'line',
       encode: {
         x: 'Name',
-        y: ''
+        y: '',
       },
       symbolSize: 6,
       symbol: 'circle',
@@ -168,128 +164,223 @@ const option: any = {
       itemStyle: {},
       areaStyle: {},
       emphasis: {
-        focus:'series'
-      }
-    }
-  ]
-}
-const loading = ref(true)
-const queryParams = computed(() => parseQueryParams(props.query))
+        focus: 'series',
+      },
+    },
+  ],
+};
+const loading = ref(true);
+const queryParams = computed(() => parseQueryParams(props.query));
 
 onMounted(() => {
-  getMetricsItems()
-	addResize()
+  getMetricsItems();
+  addResize();
   // initLine()
-  setTimeout(() => { initLine() }, 0)
-})
+  setTimeout(() => {
+    initLine();
+  }, 0);
+});
+
 onBeforeUnmount(() => {
-	if(chartObj) {
-		chartObj.dispose()
-    chartObj = null
-	}
-  removeResize()
-})
-
-const initLine = async () => {
-	chartObj = echarts.init(chartRef.value)
-	const items = await getDataset()
-	option.dataset.source = items
-
-  XEUtils.arrayEach(option.series, (info:any, index) => {
-    const color = metrics.value[index].color
-    info.name = metrics.value[index].label
-    info.encode.y = metrics.value[index].metric
-    if (info.type === 'bar') {
-      info.itemStyle.color = color
-    } else {
-      info.itemStyle = { color: color, borderColor: color }
-      info.areaStyle = {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: color + '53' },
-          { offset: 1, color: color + '03' },
-        ])
-      }
-    }
-  })
-  XEUtils.arrayEach(option.yAxis, (info:any, index) => {
-    info.name = metrics.value[index].label
-    info.axisLine.lineStyle.color = metrics.value[index].color
-  })
-
-  XEUtils.arrayEach(props.metricEnum, info => {
-    option.legend.selected[info.label] = false
-  })
-  for(const info of metrics.value) {
-    option.legend.selected[info.label] = true
+  if (chartObj) {
+    chartObj.dispose();
+    chartObj = null;
   }
-  // console.log(option)
-	chartObj.setOption(option)
-  loading.value = false
+  removeResize();
+});
+
+const metricColors = {
+  Impression: '#0085ff',
+  Click: '#3fd4cf',
+  Spend: '#ff9500',
+  // ... 其他指标的颜色
+};
+
+function getColorForMetric(metric: string): string {
+  return metricColors[metric] || '#999999'; // 如果没有预定义的颜色,返回一个默认颜色
+}
+
+async function initLine() {
+  chartObj = echarts.init(chartRef.value);
+  option.dataset.source = await getDataset();
+
+  // 初始化系列和 y 轴
+  option.series = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: index === 0 ? 'bar' : 'line',
+    encode: { x: 'Name', y: metric.metric },
+    yAxisIndex: index,
+    itemStyle: { color: option.series.type == 'bar' ? '#0085ff' : metric.color, borderRadius: [6, 6, 6, 6] },
+    lineStyle: { color: metric.color },
+    barWidth: '10%',
+    areaStyle:
+      index !== 0
+        ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: metric.color + '53' },
+              { offset: 1, color: metric.color + '03' },
+            ]),
+          }
+        : undefined,
+  }));
+
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: { show: true, lineStyle: { color: metric.color } },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 初始化图例
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  chartObj.setOption(option);
+  loading.value = false;
 }
-const getDataset = async () => {
-	if (statDim.value === 'week') {
+
+async function getDataset() {
+  if (statDim.value === 'week') {
     if (props.fetchLineWeek) {
-      const resp = await props.fetchLineWeek(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineWeek(queryParams.value);
+      return resp.data;
     }
   } else if (statDim.value === 'month') {
     if (props.fetchLineMonth) {
-      const resp = await props.fetchLineMonth(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineMonth(queryParams.value);
+      return resp.data;
     }
   } else {
-    const resp = await props.fetchLine(queryParams.value)
-    return resp.data
+    const resp = await props.fetchLine(queryParams.value);
+    return resp.data;
   }
 }
-const getMetricsItems = async () => {
-	const resp = await props.fetchCard(queryParams.value)
-	const data = resp.data
-  metricsItems.value.length = 0
-	XEUtils.arrayEach(props.metricEnum, info => {
-		const tmp:MetricData = {
-			label: info.label,
-			value: info.value,
-			metricVal: data[info.value],
-			gapVal: data[`gap${info.value}`],
-			preVal: data[`prev${info.value}`],
-		}
-		metricsItems.value.push(tmp)
-	})
-}
 
-const changeMetric = () => {
-  const opt = buildChartOpt(option, metrics.value)
-  chartObj.setOption(opt)
+async function getMetricsItems() {
+  const resp = await props.fetchCard(queryParams.value);
+  const data = resp.data;
+  metricsItems.value.length = 0;
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    const tmp: MetricData = {
+      label: info.label,
+      value: info.value,
+      metricVal: data[info.value],
+      gapVal: data[`gap${info.value}`],
+      preVal: data[`prev${info.value}`],
+    };
+    metricsItems.value.push(tmp);
+  });
 }
 
-const changeStatDim = async () => {
-  loading.value = true
-  let source = await getDataset()
-  if (source.length > 0) {
-    chartObj.setOption({dataset: {source: source}})
+// const changeMetric = () => {
+//   const opt = buildChartOpt(option, metrics.value)
+//   chartObj.setOption(opt)
+// }
+
+function changeMetric() {
+  // 更新图例选中状态
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  // 重新创建 series 数组,只包含选中的指标
+  option.series = metrics.value.map((metric, index) => {
+    const baseConfig = {
+      id: index,
+      name: metric.label,
+      type: metric.color === '#0085ff' ? 'bar' : 'line',
+      encode: { x: 'Name', y: metric.metric },
+      yAxisIndex: index,
+      itemStyle: { color: metric.color, borderRadius: [6, 6, 6, 6] },
+      lineStyle: { color: metric.color },
+      barWidth: '10%',
+    };
+
+    if (baseConfig != 'bar') {
+      baseConfig.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: metric.color + '53' },
+          { offset: 1, color: metric.color + '03' },
+        ]),
+      };
+    }
+
+    return baseConfig;
+  });
+
+  // 更新 y 轴显示状态
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: {
+      show: true,
+      lineStyle: { color: metric.color },
+    },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 确保只有必要的 y 轴
+  while (option.yAxis.length > metrics.value.length) {
+    option.yAxis.pop();
   }
-  loading.value = false
+
+  chartObj.setOption(option, true); // 使用 true 作为第二个参数,强制完全刷新
 }
 
 watch(
-  props.query,
-  async () => {
-    // console.log("------watch-----queryParams", props.query)
-    loading.value = true
-    await getMetricsItems()
-    const items = await getDataset()
-    const opt = { dataset: { source: items } }
-    chartObj.setOption(opt)
-    loading.value = false
+  metrics,
+  () => {
+    changeMetric();
+  },
+  { deep: true }
+);
+
+async function changeStatDim() {
+  loading.value = true;
+  let source = await getDataset();
+  if (source.length > 0) {
+    chartObj.setOption({ dataset: { source: source } });
   }
-)
+  loading.value = false;
+}
+
+watch(props.query, async () => {
+  // console.log("------watch-----queryParams", props.query)
+  loading.value = true;
+  await getMetricsItems();
+  const items = await getDataset();
+  const opt = { dataset: { source: items } };
+  chartObj.setOption(opt);
+  loading.value = false;
+});
 
-const resizeChart = () => { chartObj.resize() }
-const addResize = () => { window.addEventListener('resize', resizeChart) }
-const removeResize = () => { window.removeEventListener('resize', resizeChart) }
+function resizeChart() {
+  chartObj.resize();
+}
 
+function addResize() {
+  window.addEventListener('resize', resizeChart);
+}
 
+function removeResize() {
+  window.removeEventListener('resize', resizeChart);
+}
 </script>
 
 <template>
@@ -300,8 +391,8 @@ const removeResize = () => { window.removeEventListener('resize', resizeChart) }
       <el-radio-button label="week" :disabled="!props.fetchLineWeek">周</el-radio-button>
       <el-radio-button label="month" :disabled="!props.fetchLineWeek">月</el-radio-button>
     </el-radio-group>
-    <div style="height: 350px;" ref="chartRef"></div>
-</div>
+    <div style="height: 350px" ref="chartRef"></div>
+  </div>
 </template>
 
 <style scoped>

+ 87 - 28
src/views/adManage/utils/tools.ts

@@ -1,6 +1,7 @@
 import dayjs, { Dayjs } from 'dayjs'
 import { unref } from 'vue'
 import XEUtils from 'xe-utils'
+import * as echarts from 'echarts'
 
 export function recentDaysRange(timezone: string, days: number): string[] {
   const now_tz = dayjs(new Date()).tz(timezone)
@@ -9,39 +10,97 @@ export function recentDaysRange(timezone: string, days: number): string[] {
   return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
 }
 
-export function buildChartOpt(option: any, metrics: any[]) {
-  const tmp: any = {}
+// export function buildChartOpt(option: any, metrics: any[]) {
+//   const tmp: any = {}
+//   const opt: any = {
+//     legend: { selected: {} },
+//     yAxis: [],
+//     series: [],
+//   }
+//   for (const info of metrics) {
+//     tmp[info.color] = info
+//   }
+//   for (const info of option.series) {
+//     const color = info.itemStyle.color
+//     const metricInfo = tmp[color]
+//     console.log('tmp[color]', tmp[color]);
+//     if (metricInfo) {
+//       opt.series.push({
+//         id: info.id,
+//         name: metricInfo.label,
+//         encode: { y: metricInfo.metric },
+//       })
+//       opt.yAxis.push({ id: info.id, name: metricInfo.label, show: true })
+//     } else {
+//       opt.yAxis.push({ id: info.id, show: false })
+//     }
+//   }
+//   for (const label of Object.keys(option.legend.selected)) {
+//     if (XEUtils.findIndexOf(metrics, (info) => info.label === label) === -1) {
+//       opt.legend.selected[label] = false
+//     } else {
+//       opt.legend.selected[label] = true
+//     }
+//   }
+//   return opt
+// }
+
+export function buildChartOpt(option: any, metrics: any[], allMetrics: any[]) {
   const opt: any = {
     legend: { selected: {} },
     yAxis: [],
     series: [],
   }
-  for (const info of metrics) {
-    tmp[info.color] = info
-  }
-  for (const info of option.series) {
-    const color = info.itemStyle.color
-    const metricInfo = tmp[color]
-    if (metricInfo) {
-      opt.series.push({
-        id: info.id,
-        name: metricInfo.label,
-        encode: { y: metricInfo.metric },
-      })
-      opt.yAxis.push({ id: info.id, name: metricInfo.label, show: true })
-    } else {
-      opt.yAxis.push({ id: info.id, show: false })
-    }
-  }
-  for (const label of Object.keys(option.legend.selected)) {
-    if (XEUtils.findIndexOf(metrics, (info) => info.label === label) === -1) {
-      opt.legend.selected[label] = false
-    } else {
-      opt.legend.selected[label] = true
-    }
-  }
-  console.log(opt)
-  return opt
+
+  // 创建一个指标到颜色的映射
+  const metricToColor = allMetrics.reduce((acc, metric, index) => {
+    acc[metric.metric] = {color: metric.color, index};
+    return acc;
+  }, {});
+
+  // 更新系列和 y 轴
+  allMetrics.forEach((metric, index) => {
+    const isSelected = metrics.some(m => m.metric === metric.metric);
+    const colorInfo = metricToColor[metric.metric];
+
+    opt.series.push({
+      ...option.series[index], // 保留原有的系列配置
+      id: index,
+      name: metric.label,
+      encode: { y: metric.metric },
+      itemStyle: {
+        color: colorInfo.color,
+        opacity: isSelected ? 1 : 0
+      },
+      lineStyle: {
+        color: colorInfo.color,
+        opacity: isSelected ? 1 : 0
+      },
+      areaStyle: option.series[index].type === 'line' ? {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: colorInfo.color + '53' },
+          { offset: 1, color: colorInfo.color + '03' },
+        ]),
+        opacity: isSelected ? 1 : 0
+      } : undefined,
+    });
+
+    opt.yAxis.push({
+      ...option.yAxis[index], // 保留原有的轴配置
+      id: index,
+      name: metric.label,
+      show: isSelected,
+      axisLine: {
+        show: isSelected,
+        lineStyle: { color: colorInfo.color }
+      }
+    });
+
+    // 更新图例
+    opt.legend.selected[metric.label] = isSelected;
+  });
+
+  return opt;
 }
 
 export function getCompareDate(dateRange: string[]) {

+ 0 - 0
src/views/keyword/topSearchTermTable/api.ts


+ 22 - 0
src/views/keyword/topSearchTermTable/index.vue

@@ -0,0 +1,22 @@
+<script setup lang="ts">
+/**
+ * @Name: index.vue
+ * @Description: 关键词-TopSearchTerm Table
+ * @Author: Cheney
+ */
+</script>
+
+<template>
+<div style="background-color: #f7f7f7;">
+  <div class="flex justify-between mt-1.5 mx-2">
+    <span class="font-bold text-lg">
+      Top Search Term - Table
+    </span>
+    <el-button type="info">关键词管理</el-button>
+  </div>
+</div>
+</template>
+
+<style scoped>
+
+</style>

+ 195 - 113
src/views/productCenter/productAnalysis/components/DateTendency/index.vue

@@ -11,27 +11,27 @@
 </template>
 
 <script lang="ts" setup>
-import * as echarts from 'echarts'
-import { Ref, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
-import { productListMetricsEnum } from '/@/views/productCenter/productList/utils/enum'
+import * as echarts from 'echarts';
+import { Ref, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import { productListMetricsEnum } from '/@/views/productCenter/productList/utils/enum';
 // import MetricsCards from '/@/components/MetricsCards/index.vue'
-import XEUtils from 'xe-utils'
-import MetricsCards from '../MetricsCards/index.vue'
-import emitter from '/@/utils/emitter'
-import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils';
+import MetricsCards from '../MetricsCards/index.vue';
+import emitter from '/@/utils/emitter';
+import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js';
 
 defineOptions({
   name: 'DataTendencyChart',
-})
+});
 
 interface Props {
-  fetchCard: Function
-  fetchLine: Function
-  fetchLineMonth?: Function
-  fetchLineWeek?: Function
-  query: { [key: string]: any }
-  initMetric?: ShowMetric[]
-  metricEnum?: { [key: string]: string }[]
+  fetchCard: Function;
+  fetchLine: Function;
+  fetchLineMonth?: Function;
+  fetchLineWeek?: Function;
+  query: { [key: string]: any };
+  initMetric?: ShowMetric[];
+  metricEnum?: { [key: string]: string }[];
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -41,14 +41,19 @@ const props = withDefaults(defineProps<Props>(), {
     { metric: 'Spend', color: '#ff9500', label: '花费' },
   ],
   metricEnum: () => productListMetricsEnum,
-})
+});
 
-const metrics = ref(props.initMetric)
-
-const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj: any
-const chartRef = ref()
-const statDim = ref('day')
+const metrics = ref(props.initMetric);
+const metricColors = {
+  Impression: '#0085ff',
+  Click: '#3fd4cf',
+  Spend: '#ff9500',
+  // ... 其他指标的颜色
+};
+const metricsItems: Ref<MetricData[]> = ref([]);
+let chartObj: any;
+const chartRef = ref();
+const statDim = ref('day');
 const option: any = {
   dataset: {
     source: [],
@@ -205,7 +210,7 @@ const option: any = {
       type: 'line',
       encode: {
         x: 'Name',
-        y: metrics.value[1].metric,  // TODO: 数据源后续需要修改
+        y: metrics.value[1].metric, // TODO: 数据源后续需要修改
       },
       symbolSize: 6,
       symbol: 'circle',
@@ -220,27 +225,26 @@ const option: any = {
         focus: 'series',
       },
     },
-
   ],
-}
-const loading = ref(true)
-const queryParams = computed(() => parseQueryParams(props.query))
+};
+const loading = ref(true);
+const queryParams = computed(() => parseQueryParams(props.query));
 
 onMounted(() => {
-  getMetricsItems()
-  addResize()
+  getMetricsItems();
+  addResize();
   // initLine()
   setTimeout(() => {
-    initLine()
-  }, 0)
-})
+    initLine();
+  }, 0);
+});
 onBeforeUnmount(() => {
   if (chartObj) {
-    chartObj.dispose()
-    chartObj = null
+    chartObj.dispose();
+    chartObj = null;
   }
-  removeResize()
-})
+  removeResize();
+});
 
 // const initLine = async () => {
 //   chartObj = echarts.init(chartRef.value)
@@ -279,68 +283,140 @@ onBeforeUnmount(() => {
 //   loading.value = false
 // }
 
-const initLine = async () => {
-  chartObj = echarts.init(chartRef.value)
-  const items = await getDataset()
-  option.dataset.source = items
-
-  XEUtils.arrayEach(option.series, (info: any, index) => {
-    if (info.id < 3) { // 确保只处理前3个series
-      const color = metrics.value[index].color
-      info.name = metrics.value[index].label
-      info.encode.y = metrics.value[index]?.metric
-      if (info.type === 'bar') {
-        info.itemStyle.color = color
-      } else {
-        info.itemStyle = { color: color, borderColor: color }
-        info.areaStyle = {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: color + '53' },
-            { offset: 1, color: color + '03' },
-          ]),
-        }
-      }
-    }
-  })
-  XEUtils.arrayEach(option.yAxis, (info: any, index) => {
-    if (index < metrics.value.length) {
-      info.name = metrics.value[index].label
-      info.axisLine.lineStyle.color = metrics.value[index].color
+async function initLine() {
+  chartObj = echarts.init(chartRef.value);
+  option.dataset.source = await getDataset();
+
+  // 获取前三个 metrics
+  const limitedMetrics = metrics.value.slice(0, 3);
+
+  // 初始化系列和 y 轴
+  const newSeries = limitedMetrics.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: index === 0 ? 'bar' : 'line',
+    encode: { x: 'Name', y: metric.metric },
+    yAxisIndex: index,
+    itemStyle: { color: index === 0 ? '#0085ff' : metric.color, borderRadius: [6, 6, 6, 6] },
+    lineStyle: { color: metric.color },
+    barWidth: '10%',
+    areaStyle:
+      index !== 0
+        ? {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: metric.color + '53' },
+              { offset: 1, color: metric.color + '03' },
+            ]),
+          }
+        : undefined,
+  }));
+
+  // 将新的 series 添加到现有的 option.series 中
+  option.series = [...newSeries, ...option.series.slice(3)];
+
+  option.yAxis = limitedMetrics.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: { show: true, lineStyle: { color: metric.color } },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 初始化图例
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = limitedMetrics.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  chartObj.setOption(option);
+  loading.value = false;
+}
+
+// TODO: 新增的线条暂未处理
+function changeMetric() {
+  const limitedMetrics = metrics.value.slice(0, 3);
+  // 更新图例选中状态
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  // 重新创建 series 数组,只包含选中的指标
+  option.series = limitedMetrics.map((metric, index) => {
+    const baseConfig = {
+      id: index,
+      name: metric.label,
+      type: metric.color === '#0085ff' ? 'bar' : 'line',
+      encode: { x: 'Name', y: metric.metric },
+      yAxisIndex: index,
+      itemStyle: { color: metric.color, borderRadius: [6, 6, 6, 6] },
+      lineStyle: { color: metric.color },
+      barWidth: '10%',
+    };
+
+    if (baseConfig != 'bar') {
+      baseConfig.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: metric.color + '53' },
+          { offset: 1, color: metric.color + '03' },
+        ]),
+      };
     }
-  })
 
-  XEUtils.arrayEach(props.metricEnum, (info) => {
-    option.legend.selected[info.label] = false
-  })
-  for (const info of metrics.value) {
-    option.legend.selected[info.label] = true
+    return baseConfig;
+  });
+
+  // 更新 y 轴显示状态
+  option.yAxis = limitedMetrics.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: {
+      show: true,
+      lineStyle: { color: metric.color },
+    },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 确保只有必要的 y 轴
+  while (option.yAxis.length > limitedMetrics.length) {
+    option.yAxis.pop();
   }
 
-  chartObj.setOption(option)
-  loading.value = false
+  chartObj.setOption(option, true); // 使用 true 作为第二个参数,强制完全刷新
 }
 
-const getDataset = async () => {
+async function getDataset() {
   if (statDim.value === 'week') {
     if (props.fetchLineWeek) {
-      const resp = await props.fetchLineWeek(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineWeek(queryParams.value);
+      return resp.data;
     }
   } else if (statDim.value === 'month') {
     if (props.fetchLineMonth) {
-      const resp = await props.fetchLineMonth(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineMonth(queryParams.value);
+      return resp.data;
     }
   } else {
-    const resp = await props.fetchLine(queryParams.value)
-    return resp.data
+    const resp = await props.fetchLine(queryParams.value);
+    return resp.data;
   }
 }
 
-const getMetricsItems = async () => {
-  const resp = await props.fetchCard(queryParams.value)
-  const data = resp.data
-  metricsItems.value.length = 0
+async function getMetricsItems() {
+  const resp = await props.fetchCard(queryParams.value);
+  const data = resp.data;
+  metricsItems.value.length = 0;
   XEUtils.arrayEach(props.metricEnum, (info) => {
     const tmp: MetricData = {
       label: info.label,
@@ -348,48 +424,54 @@ const getMetricsItems = async () => {
       metricVal: data[info.value],
       gapVal: data[`gap${info.value}`],
       preVal: data[`prev${info.value}`],
-    }
-    metricsItems.value.push(tmp)
-  })
+    };
+    metricsItems.value.push(tmp);
+  });
 }
 
-const changeMetric = () => {
-  const opt = buildChartOpt(option, metrics.value)
-  chartObj.setOption(opt)
-}
+watch(
+    metrics,
+    () => {
+      changeMetric();
+    },
+    { deep: true }
+);
+
+const emit = defineEmits(['changeStatDim']);
 
-const emit = defineEmits(['changeStatDim'])
-const changeStatDim = async () => {
-  emitter.emit('DateTendency-changeStatDim') // 触发DataTable的loading
-  loading.value = true
-  let source = await getDataset()
+async function changeStatDim() {
+  emitter.emit('DateTendency-changeStatDim'); // 触发DataTable的loading
+  loading.value = true;
+  let source = await getDataset();
   if (source.length > 0) {
-    chartObj.setOption({ dataset: { source: source } })
+    chartObj.setOption({ dataset: { source: source } });
   }
-  loading.value = false
-  emit('changeStatDim', source) // 向父组件传递数据后再传递给DataTable
+  loading.value = false;
+  emit('changeStatDim', source); // 向父组件传递数据后再传递给DataTable
 }
 
 watch(props.query, async () => {
   // console.log("------watch-----queryParams", props.query)
-  emitter.emit('DateTendency-changeStatDim') // 触发DataTable的loading
-  loading.value = true
-  await getMetricsItems()
-  const items = await getDataset()
-  emit('changeStatDim', items) // 向父组件传递数据后再传递给DataTable
-  const opt = { dataset: { source: items } }
-  chartObj.setOption(opt)
-  loading.value = false
-})
-
-const resizeChart = () => {
-  chartObj.resize()
+  emitter.emit('DateTendency-changeStatDim'); // 触发DataTable的loading
+  loading.value = true;
+  await getMetricsItems();
+  const items = await getDataset();
+  emit('changeStatDim', items); // 向父组件传递数据后再传递给DataTable
+  const opt = { dataset: { source: items } };
+  chartObj.setOption(opt);
+  loading.value = false;
+});
+
+function resizeChart() {
+  chartObj.resize();
 }
-const addResize = () => {
-  window.addEventListener('resize', resizeChart)
+
+function addResize() {
+  window.addEventListener('resize', resizeChart);
 }
-const removeResize = () => {
-  window.removeEventListener('resize', resizeChart)
+
+function removeResize() {
+  window.removeEventListener('resize', resizeChart);
 }
 </script>
 

+ 190 - 100
src/views/productCenter/productList/components/DateTendency/index.vue

@@ -18,20 +18,21 @@ import { productListMetricsEnum } from '/@/views/productCenter/productList/utils
 import MetricsCards from '../MetricsCards/index.vue'
 import XEUtils from 'xe-utils'
 import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum';
 
 
 defineOptions({
   name: 'DataTendencyChart',
-})
+});
 
 interface Props {
-  fetchCard: Function
-  fetchLine: Function
-  fetchLineMonth?: Function
-  fetchLineWeek?: Function
-  query: { [key: string]: any }
-  initMetric?: ShowMetric[]
-  metricEnum?: { [key: string]: string }[]
+  fetchCard: Function;
+  fetchLine: Function;
+  fetchLineMonth?: Function;
+  fetchLineWeek?: Function;
+  query: { [key: string]: any };
+  initMetric?: ShowMetric[];
+  metricEnum?: { [key: string]: string }[];
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -40,15 +41,14 @@ const props = withDefaults(defineProps<Props>(), {
     { metric: 'Click', color: '#3fd4cf', label: '点击量' },
     { metric: 'Spend', color: '#ff9500', label: '花费' },
   ],
-  metricEnum: () => productListMetricsEnum,
-})
+  metricEnum: () => spCampaignMetricsEnum,
+});
 
-const metrics = ref(props.initMetric)
-
-const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj: any
-const chartRef = ref()
-const statDim = ref('day')
+const metrics = ref(props.initMetric);
+const metricsItems: Ref<MetricData[]> = ref([]);
+let chartObj: any;
+const chartRef = ref();
+const statDim = ref('day');
 const option: any = {
   dataset: {
     source: [],
@@ -135,7 +135,7 @@ const option: any = {
       yAxisIndex: 0,
       itemStyle: {
         color: '',
-        borderRadius: 4,
+        borderRadius: [4, 4, 4, 4],
       },
     },
     {
@@ -183,84 +183,107 @@ const option: any = {
       },
     },
   ],
-}
-const loading = ref(true)
-const queryParams = computed(() => parseQueryParams(props.query))
+};
+const loading = ref(true);
+const queryParams = computed(() => parseQueryParams(props.query));
 
 onMounted(() => {
-  getMetricsItems()
-  addResize()
+  getMetricsItems();
+  addResize();
   // initLine()
   setTimeout(() => {
-    initLine()
-  }, 0)
-})
+    initLine();
+  }, 0);
+});
+
 onBeforeUnmount(() => {
   if (chartObj) {
-    chartObj.dispose()
-    chartObj = null
+    chartObj.dispose();
+    chartObj = null;
   }
-  removeResize()
-})
-
-const initLine = async () => {
-  chartObj = echarts.init(chartRef.value)
-  const items = await getDataset()
-  option.dataset.source = items
-
-  XEUtils.arrayEach(option.series, (info: any, index) => {
-    const color = metrics.value[index].color
-    info.name = metrics.value[index].label
-    info.encode.y = metrics.value[index].metric
-    if (info.type === 'bar') {
-      info.itemStyle.color = color
-    } else {
-      info.itemStyle = { color: color, borderColor: color }
-      info.areaStyle = {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: color + '53' },
-          { offset: 1, color: color + '03' },
-        ]),
-      }
-    }
-  })
-  XEUtils.arrayEach(option.yAxis, (info: any, index) => {
-    info.name = metrics.value[index].label
-    info.axisLine.lineStyle.color = metrics.value[index].color
-  })
+  removeResize();
+});
 
-  XEUtils.arrayEach(props.metricEnum, (info) => {
-    option.legend.selected[info.label] = false
-  })
-  for (const info of metrics.value) {
-    option.legend.selected[info.label] = true
-  }
-  // console.log(option)
-  chartObj.setOption(option)
-  loading.value = false
+const metricColors = {
+  Impression: '#0085ff',
+  Click: '#3fd4cf',
+  Spend: '#ff9500',
+  // ... 其他指标的颜色
+};
+
+function getColorForMetric(metric: string): string {
+  return metricColors[metric] || '#999999'; // 如果没有预定义的颜色,返回一个默认颜色
 }
 
-const getDataset = async () => {
+async function initLine() {
+  chartObj = echarts.init(chartRef.value);
+  option.dataset.source = await getDataset();
+
+  // 初始化系列和 y 轴
+  option.series = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: index === 0 ? 'bar' : 'line',
+    encode: { x: 'Name', y: metric.metric },
+    yAxisIndex: index,
+    itemStyle: { color: option.series.type == 'bar' ? '#0085ff' : metric.color, borderRadius: [6, 6, 6, 6] },
+    lineStyle: { color: metric.color },
+    barWidth: '10%',
+    areaStyle:
+        index !== 0
+            ? {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: metric.color + '53' },
+                { offset: 1, color: metric.color + '03' },
+              ]),
+            }
+            : undefined,
+  }));
+
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: { show: true, lineStyle: { color: metric.color } },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 初始化图例
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  chartObj.setOption(option);
+  loading.value = false;
+}
+
+async function getDataset() {
   if (statDim.value === 'week') {
     if (props.fetchLineWeek) {
-      const resp = await props.fetchLineWeek(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineWeek(queryParams.value);
+      return resp.data;
     }
   } else if (statDim.value === 'month') {
     if (props.fetchLineMonth) {
-      const resp = await props.fetchLineMonth(queryParams.value)
-      return resp.data
+      const resp = await props.fetchLineMonth(queryParams.value);
+      return resp.data;
     }
   } else {
-    const resp = await props.fetchLine(queryParams.value)
-    return resp.data
+    const resp = await props.fetchLine(queryParams.value);
+    return resp.data;
   }
 }
 
-const getMetricsItems = async () => {
-  const resp = await props.fetchCard(queryParams.value)
-  const data = resp.data
-  metricsItems.value.length = 0
+async function getMetricsItems() {
+  const resp = await props.fetchCard(queryParams.value);
+  const data = resp.data;
+  metricsItems.value.length = 0;
   XEUtils.arrayEach(props.metricEnum, (info) => {
     const tmp: MetricData = {
       label: info.label,
@@ -268,43 +291,110 @@ const getMetricsItems = async () => {
       metricVal: data[info.value],
       gapVal: data[`gap${info.value}`],
       preVal: data[`prev${info.value}`],
-    }
-    metricsItems.value.push(tmp)
-  })
+    };
+    metricsItems.value.push(tmp);
+  });
 }
 
-const changeMetric = () => {
-  const opt = buildChartOpt(option, metrics.value)
-  chartObj.setOption(opt)
+// const changeMetric = () => {
+//   const opt = buildChartOpt(option, metrics.value)
+//   chartObj.setOption(opt)
+// }
+
+function changeMetric() {
+  // 更新图例选中状态
+  option.legend.selected = props.metricEnum.reduce((acc, metric) => {
+    acc[metric.label] = metrics.value.some((m) => m.metric === metric.value);
+    return acc;
+  }, {});
+
+  // 重新创建 series 数组,只包含选中的指标
+  option.series = metrics.value.map((metric, index) => {
+    const baseConfig = {
+      id: index,
+      name: metric.label,
+      type: metric.color === '#0085ff' ? 'bar' : 'line',
+      encode: { x: 'Name', y: metric.metric },
+      yAxisIndex: index,
+      itemStyle: { color: metric.color, borderRadius: [6, 6, 6, 6] },
+      lineStyle: { color: metric.color },
+      barWidth: '10%',
+    };
+
+    if (baseConfig != 'bar') {
+      baseConfig.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: metric.color + '53' },
+          { offset: 1, color: metric.color + '03' },
+        ]),
+      };
+    }
+
+    return baseConfig;
+  });
+
+  // 更新 y 轴显示状态
+  option.yAxis = metrics.value.map((metric, index) => ({
+    id: index,
+    name: metric.label,
+    type: 'value',
+    position: index === 0 ? 'left' : 'right',
+    offset: index > 1 ? (index - 1) * 80 : 0,
+    axisLine: {
+      show: true,
+      lineStyle: { color: metric.color },
+    },
+    splitLine: {
+      show: index === 0,
+    },
+    show: true,
+  }));
+
+  // 确保只有必要的 y 轴
+  while (option.yAxis.length > metrics.value.length) {
+    option.yAxis.pop();
+  }
+
+  chartObj.setOption(option, true); // 使用 true 作为第二个参数,强制完全刷新
 }
 
-const changeStatDim = async () => {
-  loading.value = true
-  let source = await getDataset()
+watch(
+    metrics,
+    () => {
+      changeMetric();
+    },
+    { deep: true }
+);
+
+async function changeStatDim() {
+  loading.value = true;
+  let source = await getDataset();
   if (source.length > 0) {
-    chartObj.setOption({ dataset: { source: source } })
+    chartObj.setOption({ dataset: { source: source } });
   }
-  loading.value = false
+  loading.value = false;
 }
 
 watch(props.query, async () => {
   // console.log("------watch-----queryParams", props.query)
-  loading.value = true
-  await getMetricsItems()
-  const items = await getDataset()
-  const opt = { dataset: { source: items } }
-  chartObj.setOption(opt)
-  loading.value = false
-})
-
-const resizeChart = () => {
-  chartObj.resize()
+  loading.value = true;
+  await getMetricsItems();
+  const items = await getDataset();
+  const opt = { dataset: { source: items } };
+  chartObj.setOption(opt);
+  loading.value = false;
+});
+
+function resizeChart() {
+  chartObj.resize();
 }
-const addResize = () => {
-  window.addEventListener('resize', resizeChart)
+
+function addResize() {
+  window.addEventListener('resize', resizeChart);
 }
-const removeResize = () => {
-  window.removeEventListener('resize', resizeChart)
+
+function removeResize() {
+  window.removeEventListener('resize', resizeChart);
 }
 </script>