Browse Source

数据明细页面对接全量报告接口完成;

zhaowenkang/feature-20260206140254-后台AI复盘二期
songjie 1 month ago
parent
commit
c983595f89
  1. 22
      src/api/platformData.js
  2. 287
      src/views/PlatformData/UserOverview.vue

22
src/api/platformData.js

@ -17,3 +17,25 @@ export function getUserOverviewList() {
data: formData data: formData
}) })
} }
// 获取用户数据明细列表
export function getUserFullReportList(params) {
const formData = new FormData();
formData.append('token', localStorage.getItem('token'));
if (params) {
if (params.start_time) formData.append('start_time', params.start_time);
if (params.end_time) formData.append('end_time', params.end_time);
}
return request({
url: 'http://280e5e98.r7.cpolar.top/admin/user/fullReport/list',
method: 'post',
headers: {
'token': localStorage.getItem('token'),
'client': 'ios',
'version': '1',
'Content-Type': 'multipart/form-data'
},
data: formData
})
}

287
src/views/PlatformData/UserOverview.vue

@ -92,7 +92,7 @@
</div> </div>
<!-- 数据明细 --> <!-- 数据明细 -->
<div v-show="activeTab === 'detail'" class="tab-content detail-content">
<div v-show="activeTab === 'detail'" class="tab-content detail-content" v-loading="loading" element-loading-text="数据加载中...">
<!-- 搜索栏 --> <!-- 搜索栏 -->
<div class="search-bar"> <div class="search-bar">
<div class="search-label">时间段查询</div> <div class="search-label">时间段查询</div>
@ -104,8 +104,8 @@
end-placeholder="结束时间" end-placeholder="结束时间"
size="default" size="default"
/> />
<el-button type="primary" class="search-btn">搜索</el-button>
<el-button type="primary" class="reset-btn">重置</el-button>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-button type="primary" class="reset-btn" @click="handleReset">重置</el-button>
<el-button type="danger" class="export-btn">数据导出</el-button> <el-button type="danger" class="export-btn">数据导出</el-button>
</div> </div>
@ -137,7 +137,11 @@
<span class="text-green">{{ scope.row.monthlyNew }}</span> <span class="text-green">{{ scope.row.monthlyNew }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="periodNew" label="时间段新增" />
<el-table-column prop="periodNew" label="时间段新增">
<template #default="scope">
<span class="text-green">{{ scope.row.periodNew }}</span>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
@ -178,20 +182,26 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick, watch } from 'vue';
import { ref, onMounted, nextTick, watch, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { getUserOverviewList } from '../../api/platformData';
import { getUserOverviewList, getUserFullReportList } from '../../api/platformData';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const activeTab = ref(route.query.tab || 'overview'); const activeTab = ref(route.query.tab || 'overview');
const dateRange = ref(''); const dateRange = ref('');
const loading = ref(false);
const hasDateRange = computed(() => {
return dateRange.value && dateRange.value.length === 2;
});
const chartMemberRef = ref(null); const chartMemberRef = ref(null);
const chartNewOldRef = ref(null); const chartNewOldRef = ref(null);
const chartBarRef = ref(null); const chartBarRef = ref(null);
let chartBarInstance = null;
const overviewData = ref({ const overviewData = ref({
total: 0, total: 0,
@ -204,6 +214,17 @@ const overviewData = ref({
old_normal: 0 old_normal: 0
}); });
// - 使 ref
const tableData1 = ref([]);
const tableData2 = ref([]);
const tableData3 = ref([]);
const headerCellStyle = {
background: '#fff0f0',
color: '#333',
fontWeight: 'bold'
};
// //
const getGrowthClass = (growthStr) => { const getGrowthClass = (growthStr) => {
if (!growthStr) return ''; if (!growthStr) return '';
@ -221,10 +242,18 @@ const getGrowthText = (growthStr) => {
return `${prefix}${arrow} ${value}`; return `${prefix}${arrow} ${value}`;
}; };
//
const formatDate = (date) => {
if (!date) return '';
const d = new Date(date);
const pad = (n) => n < 10 ? '0' + n : n;
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
};
const fetchData = async () => { const fetchData = async () => {
try { try {
const res = await getUserOverviewList(); const res = await getUserOverviewList();
console.log("获取用户概览数据响应完成:",res.list);
console.log("获取用户概览数据响应完成:",res);
// datacode // datacode
if (res && res.list) { if (res && res.list) {
overviewData.value = res.list; overviewData.value = res.list;
@ -235,35 +264,82 @@ const fetchData = async () => {
} }
}; };
//
const tableData1 = [
{ type: '用户总数', total: '154,832', dailyNew: '+3.44', weeklyNew: '+21,379', monthlyNew: '+21,379', periodNew: '' },
{ type: '会员总数', total: '42,567', dailyNew: '+5.56', weeklyNew: '+2,379', monthlyNew: '+2,379', periodNew: '' },
{ type: '非会员总数', total: '112,265', dailyNew: '+9.32', weeklyNew: '+92,123', monthlyNew: '+92,123', periodNew: '' },
{ type: '新非网总数', total: '68,420', dailyNew: '+35.34', weeklyNew: '+12,689', monthlyNew: '+12,689', periodNew: '' },
{ type: '老非网总数', total: '68,420', dailyNew: '+23.45', weeklyNew: '+12,033', monthlyNew: '+12,033', periodNew: '' },
];
const tableData2 = [
{ channel: 'App Store', dailyNew: '154,832', weeklyNew: '+3.44', monthlyNew: '+21,379', periodNew: '', percent: '38%' },
{ channel: 'Play Store', dailyNew: '42,567', weeklyNew: '+5.56', monthlyNew: '+2,379', periodNew: '', percent: '30%' },
{ channel: 'H5', dailyNew: '112,265', weeklyNew: '+9.32', monthlyNew: '+92,123', periodNew: '', percent: '17%' },
{ channel: 'APK', dailyNew: '68,420', weeklyNew: '+35.34', monthlyNew: '+12,689', periodNew: '', percent: '10%' },
{ channel: '总计', dailyNew: '68,420', weeklyNew: '+23.45', monthlyNew: '+12,033', periodNew: '', percent: '100%' },
];
const tableData3 = [
{ channel: 'HC 注册过', dailyNew: '1,245', weeklyNew: '8,742', monthlyNew: '32,567', periodNew: '', percent: '38%' },
{ channel: 'Link 注册过', dailyNew: '987', weeklyNew: '6,912', monthlyNew: '25,432', periodNew: '', percent: '30%' },
{ channel: '海外 CRM', dailyNew: '543', weeklyNew: '3,801', monthlyNew: '14,567', periodNew: '', percent: '17%' },
{ channel: '其他', dailyNew: '321', weeklyNew: '2,247', monthlyNew: '8,654', periodNew: '', percent: '10%' },
{ channel: '总计', dailyNew: '3,096', weeklyNew: '21,702', monthlyNew: '81,220', periodNew: '', percent: '100%' },
];
const fetchDetailData = async () => {
loading.value = true;
let params = {};
if (dateRange.value && dateRange.value.length === 2) {
params.start_time = formatDate(dateRange.value[0]);
params.end_time = formatDate(dateRange.value[1]);
}
// Check if range search is active
const isRangeSearch = !!(params.start_time && params.end_time);
try {
const res = await getUserFullReportList(params);
console.log("获取数据明细响应:", res);
// data使res使res.data
const data = res.composition ? res : (res.data ? res.data : null);
if (data) {
// Map Table 1
if (data.composition) {
tableData1.value = data.composition.map(item => ({
type: item.label,
total: item.total.toLocaleString(),
dailyNew: isRangeSearch ? '' : (item.growth_day > 0 ? '+' + item.growth_day : item.growth_day),
weeklyNew: isRangeSearch ? '' : (item.growth_week > 0 ? '+' + item.growth_week : item.growth_week),
monthlyNew: isRangeSearch ? '' : (item.growth_month > 0 ? '+' + item.growth_month : item.growth_month),
periodNew: !isRangeSearch ? '' : item.growth_range
}));
}
// Map Table 2
if (data.new_source) {
tableData2.value = data.new_source.map(item => ({
channel: item.channel,
dailyNew: isRangeSearch ? '' : item.today_add,
weeklyNew: isRangeSearch ? '' : (item.week_add > 0 ? '+' + item.week_add : item.week_add),
monthlyNew: isRangeSearch ? '' : (item.month_add > 0 ? '+' + item.month_add : item.month_add),
periodNew: !isRangeSearch ? '' : item.range_add,
percent: item.rate
}));
}
// Map Table 3
if (data.old_source) {
tableData3.value = data.old_source.map(item => ({
channel: item.channel,
dailyNew: isRangeSearch ? '' : item.today_add,
weeklyNew: isRangeSearch ? '' : (item.week_add > 0 ? '+' + item.week_add : item.week_add),
monthlyNew: isRangeSearch ? '' : (item.month_add > 0 ? '+' + item.month_add : item.month_add),
periodNew: !isRangeSearch ? '' : item.range_add,
percent: item.rate
}));
}
// Chart Data
if (data.chart) {
nextTick(() => {
updateBarChart(data.chart, data.new_source || [], data.old_source || []);
});
}
}
} catch(e) {
console.error('获取数据明细失败:', e);
} finally {
loading.value = false;
}
};
const headerCellStyle = {
background: '#fff0f0',
color: '#333',
fontWeight: 'bold'
const handleSearch = () => {
fetchDetailData();
};
const handleReset = () => {
dateRange.value = '';
fetchDetailData();
}; };
const initCharts = () => { const initCharts = () => {
@ -319,67 +395,104 @@ const initCharts = () => {
} }
}); });
} else if (activeTab.value === 'detail') { } else if (activeTab.value === 'detail') {
nextTick(() => {
// Chart 3:
if (chartBarRef.value) {
const chart3 = echarts.init(chartBarRef.value);
chart3.setOption({
tooltip: {
trigger: 'item',
},
legend: {
data: ['新用户', '老用户'],
top: 'top',
left: 'center'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: ['App Store', 'Play Store', 'H5', 'APK', 'HC 注册过', 'Link 注册过', '海外 CRM', '其他'],
axisTick: { alignWithLabel: true }
}
],
yAxis: [
{ type: 'value' }
],
series: [
{
name: '新用户',
type: 'bar',
barWidth: '20%',
color: '#40a9ff',
data: [580, 1150, 650, 780, 0, 0, 0, 0],
stack: 'total'
},
{
name: '老用户',
type: 'bar',
barWidth: '20%',
color: '#9287e7',
data: [0, 0, 0, 0, 1245, 987, 543, 321],
stack: 'total'
}
]
});
}
});
// updateBarChart
//
// fetchDetailData
} }
}; };
const updateBarChart = (chartData, newSources, oldSources) => {
if (!chartBarRef.value) return;
if (!chartBarInstance) {
chartBarInstance = echarts.init(chartBarRef.value);
}
const xAxisData = chartData.x_axis || [];
const yAxisData = chartData.y_axis || [];
//
const newUserData = [];
const oldUserData = [];
//
const newSourceChannels = newSources.map(s => s.channel);
xAxisData.forEach((label, index) => {
const val = yAxisData[index] || 0;
if (newSourceChannels.includes(label)) {
newUserData.push(val);
oldUserData.push(0);
} else {
newUserData.push(0);
oldUserData.push(val);
}
});
const option = {
tooltip: {
trigger: 'item',
},
legend: {
data: ['新用户', '老用户'],
top: 'top',
left: 'center'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: xAxisData,
axisTick: { alignWithLabel: true }
}
],
yAxis: [
{ type: 'value' }
],
series: [
{
name: '新用户',
type: 'bar',
barWidth: '20%',
color: '#40a9ff',
data: newUserData,
stack: 'total'
},
{
name: '老用户',
type: 'bar',
barWidth: '20%',
color: '#9287e7',
data: oldUserData,
stack: 'total'
}
]
};
chartBarInstance.setOption(option);
};
watch(activeTab, (newVal) => { watch(activeTab, (newVal) => {
// tab URL // tab URL
router.replace({ query: { ...route.query, tab: newVal } }); router.replace({ query: { ...route.query, tab: newVal } });
initCharts();
if (newVal === 'overview') {
fetchData();
} else if (newVal === 'detail') {
fetchDetailData();
}
}); });
onMounted(() => { onMounted(() => {
fetchData();
if (activeTab.value === 'overview') {
fetchData();
} else {
fetchDetailData();
}
}); });
</script> </script>

Loading…
Cancel
Save