|
@@ -0,0 +1,191 @@
|
|
|
+<script setup lang="ts">
|
|
|
+/**
|
|
|
+ * @Name: IndicatorXBar.vue
|
|
|
+ * @Description: 搜索词-分析页-柱状图
|
|
|
+ * @Author: Cheney
|
|
|
+ */
|
|
|
+import { inject, onBeforeUnmount, reactive, Ref, ref, watch } from 'vue';
|
|
|
+import * as api from '/@/views/searchTerm/analysisPage/api';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+import emitter from '/@/utils/emitter';
|
|
|
+
|
|
|
+const filter = inject<Ref>('filter');
|
|
|
+const barLoading = ref(false);
|
|
|
+
|
|
|
+const barFilter = reactive({
|
|
|
+ layerSelect: 'Impressions_A_B_Count',
|
|
|
+ searchTermInp: '',
|
|
|
+});
|
|
|
+
|
|
|
+let responseData = null;
|
|
|
+const chartRef = ref<HTMLElement | null>(null);
|
|
|
+let chart: echarts.ECharts | null = null;
|
|
|
+let resizeObserver: ResizeObserver | null = null;
|
|
|
+const hasData = ref(true);
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ emitter.all.clear();
|
|
|
+ // 清理 ResizeObserver
|
|
|
+ if (resizeObserver) {
|
|
|
+ resizeObserver.disconnect();
|
|
|
+ }
|
|
|
+ if (chart) {
|
|
|
+ chart.dispose();
|
|
|
+ chart = null;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+emitter.on('QueryCondition-sendRequest', () => {
|
|
|
+ fetchBarData();
|
|
|
+});
|
|
|
+
|
|
|
+// watch(filter.value,() => {
|
|
|
+// barFilter.layerSelect = filter.value.layerType;
|
|
|
+// })
|
|
|
+
|
|
|
+async function fetchBarData() {
|
|
|
+ barLoading.value = true;
|
|
|
+ const query = {
|
|
|
+ date_start: filter.value.reportDate[0],
|
|
|
+ date_end: filter.value.reportDate[1],
|
|
|
+ report_range: filter.value.reportType,
|
|
|
+ layer_type: filter.value.layerType,
|
|
|
+ search_term: barFilter.searchTermInp,
|
|
|
+ [filter.value.layerType.split('_')[0]]: filter.value.variable,
|
|
|
+ metrics: barFilter.layerSelect
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ responseData = await api.getBarData(query);
|
|
|
+ if (!responseData.data || responseData.data.length === 0) {
|
|
|
+ // 处理空数据的情况
|
|
|
+ hasData.value = false;
|
|
|
+ if (chart) {
|
|
|
+ chart.clear();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ hasData.value = true;
|
|
|
+
|
|
|
+ const asinList = Object.keys(responseData.data[0]).filter(key => key !== 'Search_Query');
|
|
|
+
|
|
|
+// 准备图表数据
|
|
|
+ const searchQueries = responseData.data.map(item => item.Search_Query);
|
|
|
+ const seriesData = asinList.map(asin => ({
|
|
|
+ name: asin,
|
|
|
+ type: 'bar',
|
|
|
+ stack: 'total',
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'inside',
|
|
|
+ formatter: '{c}'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series'
|
|
|
+ },
|
|
|
+ data: responseData.data.map(item => item[asin] || 0)
|
|
|
+ }));
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'horizontal',
|
|
|
+ top: 'bottom'
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '15%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'value'
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: searchQueries,
|
|
|
+ axisLabel: {
|
|
|
+ interval: 0,
|
|
|
+ formatter: function (value) {
|
|
|
+ return value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: seriesData
|
|
|
+ };
|
|
|
+
|
|
|
+ initChart(option);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('==Error==', error);
|
|
|
+ } finally {
|
|
|
+ barLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function initChart(opt: echarts.EChartsCoreOption) {
|
|
|
+ if (!chart) {
|
|
|
+ chart = echarts.init(chartRef.value);
|
|
|
+ }
|
|
|
+ chart.setOption(opt);
|
|
|
+
|
|
|
+ // 添加 ResizeObserver 以处理图表大小变化
|
|
|
+ if (!resizeObserver) {
|
|
|
+ resizeObserver = new ResizeObserver(() => {
|
|
|
+ chart?.resize();
|
|
|
+ });
|
|
|
+ resizeObserver.observe(chartRef.value);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function changeChart() {
|
|
|
+ const option = {
|
|
|
+ title: {
|
|
|
+ text: 'Asin/Brand 关键词情况',
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ legend: {},
|
|
|
+ // grid: {
|
|
|
+ // left: '3%',
|
|
|
+ // right: '4%',
|
|
|
+ // bottom: '3%',
|
|
|
+ // containLabel: true,
|
|
|
+ // },
|
|
|
+ dataset: {
|
|
|
+ dimensions: ['Search_Query', 'Impressions_A_B_Count', 'Clicks_Total_Count', 'Cart_Adds_Total_Count', 'Purchases_Total_Count'],
|
|
|
+ source: responseData.data
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <el-card shadow="never" v-loading="barLoading" class="mt-5">
|
|
|
+ <div class="flex gap-5 mb-4 justify-center">
|
|
|
+ <div>
|
|
|
+ <span class="font-medium mr-0.5">层级 </span>
|
|
|
+ <el-select v-model="barFilter.layerSelect" @change="changeChart" style="width: 130px">
|
|
|
+ <el-option label="曝光次数" value="Impressions_A_B_Count" />
|
|
|
+ <el-option label="点击次数" value="Clicks_Total_Count" />
|
|
|
+ <el-option label="购买次数" value="Purchases_Total_Count" />
|
|
|
+ <el-option label="加购次数" value="Cart_Total_Count" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-show="!barLoading && !hasData" class="no-data-message" style="min-height: 500px">
|
|
|
+ <el-empty />
|
|
|
+ </div>
|
|
|
+ <div v-show="hasData" ref="chartRef" style="width: 100%; height: 500px"></div>
|
|
|
+ </el-card>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped></style>
|