|
@@ -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>
|