refactor(property-marketing): 重构投放账号趋势展示功能

-优化 EchartsItem 组件,支持动态数据更新
- 新增多个投放指标的图表展示,包括展示量、点击量、转化率等
- 实现账号类型切换时重新加载对应数据
- 优化数据加载逻辑,增加 loading 状态管理
- 重构 API 接口调用,支持传递参数获取数据
This commit is contained in:
linzhijun@xiaotiai.com
2025-07-06 23:25:33 +08:00
parent 534636c69e
commit 1dae78f770
3 changed files with 116 additions and 170 deletions

View File

@ -282,13 +282,13 @@ export const getMediaAccountsAuthorizedStatus = (id: string) => {
// 投放账号-趋势 // 投放账号-趋势
export const getPlacementAccountsTrend = (params = {}) => { export const getPlacementAccountsTrend = (params = {}) => {
return Http.get(`/v1/placement-accounts/trend`); return Http.get(`/v1/placement-accounts/trend`, params);
}; };
// 投放账号计划数据-趋势 // 投放账号计划数据-趋势
export const getPlacementAccountProjectsTrend = (id: string) => { export const getPlacementAccountProjectsTrend = (params = {}) => {
return Http.get(`/v1/placement-account-projects/trend`); return Http.get(`/v1/placement-account-projects/trend`, params);
}; };
// 投放指南查询 // 投放指南查询

View File

@ -10,7 +10,7 @@
</template> </template>
</a-popover> </a-popover>
</template> </template>
<div id="echarts-line" ref="chart" style="width: 100%; height: 450px"></div> <div ref="chart" style="width: 100%; height: 450px"></div>
</a-card> </a-card>
</div> </div>
</template> </template>
@ -19,7 +19,7 @@
import { defineProps, onMounted } from 'vue'; import { defineProps, onMounted } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { IconQuestionCircle } from '@arco-design/web-vue/es/icon'; import { IconQuestionCircle } from '@arco-design/web-vue/es/icon';
//子组件参数传递
const props = defineProps({ const props = defineProps({
title: { title: {
type: Object, type: Object,
@ -43,11 +43,18 @@ let chartInstance: echarts.ECharts | null = null;
const xAxisData = props.xAxisData; const xAxisData = props.xAxisData;
const seriesData = props.seriesData; const seriesData = props.seriesData;
console.log(seriesData, 'seriesData');
const initChart = () => { const initChart = () => {
if (!chart.value) return; if (!chart.value) return;
// 如果已有实例,就不重复初始化
if (chartInstance) {
chartInstance.dispose(chart.value);
}
chartInstance = echarts.init(chart.value); chartInstance = echarts.init(chart.value);
console.log('init');
const option = { const option = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -56,20 +63,6 @@ const initChart = () => {
borderColor: '#ccc', borderColor: '#ccc',
borderWidth: 1, borderWidth: 1,
textStyle: { color: '#333' }, textStyle: { color: '#333' },
formatter: function (params) {
let date = params[0].axisValue;
let tooltipHtml = `<div style="font-weight:bold;">消耗量 ${date}</div>`;
params.forEach((item) => {
tooltipHtml += `
<div style="display:flex;align-items:center;">
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;margin-right:5px;background-color:${item.color}"></span>
<span style="margin-right:10px;">${item.seriesName}</span>
<span>${item.value}</span>
</div>
`;
});
return tooltipHtml;
},
}, },
legend: { legend: {
type: 'scroll', type: 'scroll',
@ -80,7 +73,7 @@ const initChart = () => {
pageButtonItemGap: 5, pageButtonItemGap: 5,
pageButtonStyle: { color: '#666' }, pageButtonStyle: { color: '#666' },
textStyle: { color: '#666' }, textStyle: { color: '#666' },
data: seriesData.map((item) => item.name), data: seriesData,
}, },
grid: { grid: {
top: 80, // 调整图表内容位置,避免与图例重叠 top: 80, // 调整图表内容位置,避免与图例重叠
@ -104,9 +97,23 @@ const initChart = () => {
chartInstance.setOption(option); chartInstance.setOption(option);
}; };
watch(
() => props.seriesData,
() => {
initChart(); //重新渲染的方法
},
{ deep: true },
);
onMounted(() => { onMounted(() => {
initChart(); initChart();
}); });
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -36,11 +36,9 @@
<div class="filter-row-item flex items-center"> <div class="filter-row-item flex items-center">
<span class="label">运营人员</span> <span class="label">运营人员</span>
<a-select v-model="query.operator_id" size="medium" placeholder="全部" allow-clear @change="handleSearch"> <a-space class="w-160px">
<a-option v-for="(item, index) in operators" :key="index" :value="item.id" :label="item.name" <OperatorSelect v-model="query.operator_id" :options="operators" />
>{{ item.name }} </a-space>
</a-option>
</a-select>
</div> </div>
</div> </div>
<div class="filter-row flex"> <div class="filter-row flex">
@ -65,24 +63,71 @@
</div> </div>
</div> </div>
</div> </div>
<div class="table-wrap rounded-8px py-5px flex-1 flex flex-col"> <div class="table-wrap rounded-8px py-5px flex-1 flex flex-col" v-if="onLoading == false">
<a-row class="grid-demo" :gutter="24"> <a-row class="grid-demo" :gutter="24">
<a-col :span="12"> <a-col :span="12">
<EchartsItem <EchartsItem
:xAxisData="xhlEcharts.consumption_volume.xAxisData" :xAxisData="xhlEcharts?.total_use_amount?.date"
:seriesData="xhlEcharts.consumption_volume.seriesData" :seriesData="xhlEcharts?.total_use_amount?.series_data"
:title="{ name: '消耗量', popover: '消耗量' }" :title="{ name: '消耗量', popover: '消耗量' }"
></EchartsItem> ></EchartsItem>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<EchartsItem <EchartsItem
:xAxisData="xhlEcharts.click_count.xAxisData" :xAxisData="xhlEcharts?.avg_conversion_cost?.date"
:seriesData="xhlEcharts.click_count.seriesData" :seriesData="xhlEcharts?.avg_conversion_cost?.series_data"
:title="{ name: '消耗量', popover: '消耗量' }" :title="{ name: '展示量', popover: '展示量' }"
></EchartsItem>
</a-col>
</a-row>
<a-row class="grid-demo" :gutter="24">
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.click_number?.date"
:seriesData="xhlEcharts?.click_number?.series_data"
:title="{ name: '点击量', popover: '点击量' }"
></EchartsItem>
</a-col>
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.click_rate?.date"
:seriesData="xhlEcharts?.click_rate?.series_data"
:title="{ name: '点击率', popover: '点击率' }"
></EchartsItem>
</a-col>
</a-row>
<a-row class="grid-demo" :gutter="24">
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.avg_click_cost?.date"
:seriesData="xhlEcharts?.avg_click_cost?.series_data"
:title="{ name: '平均点击成本', popover: '平均点击成本' }"
></EchartsItem>
</a-col>
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.thousand_show_cost?.date"
:seriesData="xhlEcharts?.thousand_show_cost?.series_data"
:title="{ name: '千次展示成本', popover: '千次展示成本' }"
></EchartsItem>
</a-col>
</a-row>
<a-row class="grid-demo" :gutter="24">
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.conversion_number?.date"
:seriesData="xhlEcharts?.conversion_number?.series_data"
:title="{ name: '转化数', popover: '转化数' }"
></EchartsItem>
</a-col>
<a-col :span="12">
<EchartsItem
:xAxisData="xhlEcharts?.conversion_rate?.date"
:seriesData="xhlEcharts?.conversion_rate?.series_data"
:title="{ name: '转化率', popover: '转化率' }"
></EchartsItem> ></EchartsItem>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</div> </div>
</template> </template>
@ -93,46 +138,55 @@ import {
getPlacementAccountsList, getPlacementAccountsList,
getPlacementAccountsTrend, getPlacementAccountsTrend,
getPlacementAccountProjectsTrend, getPlacementAccountProjectsTrend,
fetchAccountOperators,
} from '@/api/all/propertyMarketing'; } from '@/api/all/propertyMarketing';
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select/index.vue';
const accountType = ref('1'); const accountType = ref(1);
const onLoading = ref(true);
const getOperators = async () => {
const { code, data } = await fetchAccountOperators();
if (code === 200) {
operators.value = data;
}
};
const query = reactive({ const query = reactive({
names: '', names: '',
platform: '', platform: '',
operator_id: '', operator_id: '',
}); });
const getPlacementAccounts = async () => { const xhlEcharts = reactive({});
const getAccountsTrends = async () => {
const { code, data } = await getPlacementAccountsTrend(query); const { code, data } = await getPlacementAccountsTrend(query);
if (code === 200) { if (code === 200) {
accountList.value = data; Object.assign(xhlEcharts, data);
} }
onLoading.value = false;
};
const getAccountProjectsTrend = async () => {
const { code, data } = await getPlacementAccountProjectsTrend(query);
if (code === 200) {
Object.assign(xhlEcharts, data);
}
onLoading.value = false;
}; };
const handleSearch = async () => { const handleSearch = async () => {
getPlacementAccounts(); if (accountType.value == 1) {
}; getAccountsTrends();
const handleReset = async () => {}; } else if (accountType.value == 2) {
// 投放账号计划数据-趋势 getAccountProjectsTrend();
const getPlacementAccountProjects = async () => {
const { code, data } = await getPlacementAccountProjectsTrend(query);
if (code === 200) {
accountList.value = data;
} }
}; };
const handleReset = async () => {};
const operators = ref([]); const operators = ref([]);
const accountList = ref([]); const accountList = ref([]);
const handleTabClick = (key) => { const handleTabClick = (key) => {
console.log(key, 'key'); handleSearch();
if (key == 1) {
getAccountList();
} else if (key == 2) {
getPlacementAccountProjects();
}
};
const getOperators = async () => {
}; };
// 获取账户名称 // 获取账户名称
@ -144,125 +198,10 @@ const getAccountList = async () => {
}; };
onMounted(() => { onMounted(() => {
getPlacementAccounts(); handleSearch();
getAccountList(); getAccountList();
getOperators(); getOperators();
}); });
const xhlEcharts = reactive({
consumption_volume: {
label: '消耗量',
key: 'consumption_volume',
xAxisData: ['06-05', '06-06', '06-07', '06-08', '06-09', '06-10', '06-11'],
seriesData: [
{
name: '本地推-小庭院推广计划',
type: 'line',
data: [30000, 40000, 25000, 20000, 29019, 29019, 29019],
color: '#7b68ee',
emphasis: { focus: 'series' },
},
{
name: '全球旅行小助手推广',
type: 'line',
data: [25000, 27000, 23000, 20000, 29019, 29019, 29019],
color: '#32c5ff',
emphasis: { focus: 'series' },
},
{
name: '定向旅居推广顶级套餐',
type: 'line',
data: [10000, 5000, 12000, 15000, 29019, 29019, 29019],
color: '#feb62f',
emphasis: { focus: 'series' },
},
{
name: '城市探索者特惠活动',
type: 'line',
data: [15000, 18000, 12000, 16000, 20000, 22000, 24000],
color: '#ff7d00',
emphasis: { focus: 'series' },
},
{
name: '高端民宿精选推荐',
type: 'line',
data: [22000, 20000, 25000, 23000, 21000, 19000, 22000],
color: '#f5222d',
emphasis: { focus: 'series' },
},
{
name: '美食探店达人计划',
type: 'line',
data: [18000, 19000, 17000, 16000, 18000, 20000, 21000],
color: '#722ed1',
emphasis: { focus: 'series' },
},
{
name: '户外探险装备推荐',
type: 'line',
data: [12000, 14000, 13000, 15000, 17000, 16000, 18000],
color: '#13c2c2',
emphasis: { focus: 'series' },
},
],
},
click_count: {
label: '点击量',
key: 'click_count',
xAxisData: ['06-05', '06-06', '06-07', '06-08', '06-09', '06-10', '06-11'],
seriesData: [
{
name: '本地推-小庭院推广计划',
type: 'line',
data: [30000, 40000, 25000, 20000, 29019, 29019, 29019],
color: '#7b68ee',
emphasis: { focus: 'series' },
},
{
name: '全球旅行小助手推广',
type: 'line',
data: [25000, 27000, 23000, 20000, 29019, 29019, 29019],
color: '#32c5ff',
emphasis: { focus: 'series' },
},
{
name: '定向旅居推广顶级套餐',
type: 'line',
data: [10000, 5000, 12000, 15000, 29019, 29019, 29019],
color: '#feb62f',
emphasis: { focus: 'series' },
},
{
name: '城市探索者特惠活动',
type: 'line',
data: [15000, 18000, 12000, 16000, 20000, 22000, 24000],
color: '#ff7d00',
emphasis: { focus: 'series' },
},
{
name: '高端民宿精选推荐',
type: 'line',
data: [22000, 20000, 25000, 23000, 21000, 19000, 22000],
color: '#f5222d',
emphasis: { focus: 'series' },
},
{
name: '美食探店达人计划',
type: 'line',
data: [18000, 19000, 17000, 16000, 18000, 20000, 21000],
color: '#722ed1',
emphasis: { focus: 'series' },
},
{
name: '户外探险装备推荐',
type: 'line',
data: [12000, 14000, 13000, 15000, 17000, 16000, 18000],
color: '#13c2c2',
emphasis: { focus: 'series' },
},
],
},
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">