123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- <script setup lang="ts">
- /**
- * @Name: IndicatorFunnel.vue
- * @Description: 搜索词-分析页-漏斗图
- * @Author: Cheney
- */
- import { Search } from '@element-plus/icons-vue';
- import { inject, onBeforeUnmount, reactive, ref, Ref, watch } from 'vue';
- import * as api from './api';
- import * as echarts from 'echarts';
- import emitter from '/@/utils/emitter';
- const filter = inject<Ref>('filter');
- const funnelFilter = reactive({
- layerSelect: 'all_asin',
- searchTermInp: '',
- });
- const funnelLoading = ref(false);
- 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', () => {
- fetchFunnelData();
- });
- watch(filter.value, () => {
- funnelFilter.layerSelect = filter.value.layerType;
- });
- async function fetchFunnelData() {
- funnelLoading.value = true;
- const query = {
- date_start: filter.value.reportDate[0],
- date_end: filter.value.reportDate[1],
- report_range: filter.value.reportType,
- layer_type: funnelFilter.layerSelect,
- };
- try {
- const response = await api.getFunnelData(query);
- if (!response.data || Object.keys(response.data).length === 0) {
- hasData.value = false;
- if (chart) {
- chart.clear();
- }
- return;
- }
- hasData.value = true;
- // 转换数据
- const rawData = response.data;
- const funnelData = Object.entries(rawData)
- .filter(([key]) => key.endsWith('log2'))
- .map(([key, value]) => {
- const name =
- key.replace('_Total_Countlog2', '').charAt(0).toUpperCase() +
- key.replace('_Total_Countlog2', '').slice(1).toLowerCase();
- const originalKey = key.replace('log2', '');
- return {
- value: value,
- name: name,
- originalValue: rawData[originalKey],
- };
- });
- const option = {
- title: {
- text: '购买旅程漏斗图',
- },
- tooltip: {
- trigger: 'item',
- formatter: function (params) {
- return `${params.name}: ${params.data.originalValue.toLocaleString()}`;
- },
- },
- // toolbox: {
- // feature: {
- // dataView: { readOnly: false },
- // },
- // },
- series: [
- {
- type: 'funnel',
- left: '10%',
- top: 50,
- bottom: 0,
- width: '80%',
- min: 0,
- max: 100,
- minSize: '0%',
- maxSize: '100%',
- sort: 'descending',
- gap: 2,
- labelLine: {
- length: 10,
- lineStyle: {
- width: 1,
- type: 'solid',
- },
- },
- itemStyle: {
- borderColor: '#fff',
- borderWidth: 1,
- },
- emphasis: {
- label: {
- fontSize: 20,
- },
- },
- data: funnelData,
- },
- ],
- };
- initChart(option);
- } catch (error) {
- console.error('==Error==', error);
- } finally {
- funnelLoading.value = false;
- }
- }
- function initChart(opt) {
- if (!chart) {
- chart = echarts.init(chartRef.value);
- }
- chart.setOption(opt);
- // 添加 ResizeObserver 以处理图表大小变化
- if (!resizeObserver) {
- resizeObserver = new ResizeObserver(() => {
- chart?.resize();
- });
- resizeObserver.observe(chartRef.value);
- }
- }
- </script>
- <template>
- <el-card shadow="never" v-loading="funnelLoading" 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="funnelFilter.layerSelect" @change="fetchFunnelData" style="width: 130px">
- <el-option label="Asin View" value="asin_view" />
- <el-option label="Brand View" value="brand_view" />
- <el-option label="All Asin" value="all_asin" />
- <el-option label="All Brand" value="all_brand" />
- </el-select>
- </div>
- <div>
- <span class="font-medium mr-0.5">搜索词 </span>
- <el-input v-model="funnelFilter.searchTermInp" :prefix-icon="Search" @change="fetchFunnelData" style="width: 200px" />
- </div>
- </div>
- <div v-show="!funnelLoading && !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>
|