|
@@ -3,41 +3,41 @@
|
|
|
<div class="ascribe">
|
|
|
<el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
|
|
|
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
- <el-card shadow="none" style="padding: 10px 14px;">
|
|
|
- <div class="top-info">
|
|
|
- <div class="title">卸载归因<el-tooltip class="box-item" effect="light" content="" placement="right-start">
|
|
|
- <svg style="margin: 0 0 0 8px; vertical-align: baseline;" width="14" height="14" viewBox="0 0 14 14"
|
|
|
- fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
- <path
|
|
|
- d="M7 14C8.93298 14 10.683 13.2165 11.9497 11.9497C13.2165 10.683 14 8.93298 14 7C14 5.06702 13.2165 3.31702 11.9497 2.05025C10.683 0.783503 8.93298 0 7 0C5.06702 0 3.31702 0.783503 2.05025 2.05025C0.783503 3.31702 0 5.06702 0 7C0 8.93298 0.783503 10.683 2.05025 11.9497C3.31702 13.2165 5.06702 14 7 14Z"
|
|
|
- fill="#1B4D88" fill-opacity="0.4" />
|
|
|
- <path
|
|
|
- d="M4 4.702C4 4.40333 4.02333 4.09533 4.07 3.778C4.126 3.46067 4.21933 3.17133 4.35 2.91C4.49 2.64867 4.67667 2.434 4.91 2.266C5.14333 2.08867 5.45133 2 5.834 2H7.794C8.102 2 8.37267 2.06533 8.606 2.196C8.84867 2.31733 9.04467 2.476 9.194 2.672C9.35267 2.868 9.474 3.092 9.558 3.344C9.65133 3.596 9.70733 3.848 9.726 4.1C9.754 4.352 9.74467 4.59467 9.698 4.828C9.66067 5.06133 9.59533 5.26667 9.502 5.444L7.934 8.314V9.574H6.324V8.146L7.808 5.556C7.892 5.416 7.948 5.234 7.976 5.01C8.01333 4.786 8.01333 4.57133 7.976 4.366C7.948 4.15133 7.878 3.96933 7.766 3.82C7.66333 3.67067 7.514 3.596 7.318 3.596H6.408C6.24933 3.596 6.11867 3.624 6.016 3.68C5.91333 3.72667 5.82933 3.80133 5.764 3.904C5.708 3.99733 5.67067 4.114 5.652 4.254C5.63333 4.38467 5.624 4.534 5.624 4.702H4ZM7.976 12.15H6.324V10.512H7.976V12.15Z"
|
|
|
- fill="white" />
|
|
|
- </svg>
|
|
|
- <template #content>
|
|
|
- <div style="width: 300px;">
|
|
|
- 卸载归因解读卸载设备在您应用中的最后活跃行为。
|
|
|
- 本模块功能展示周期内的卸载设备,在卸载前的最后7天(含当天)在您的应用中浏览次数TOP10的页面;展示应用在全网设备中的卸载量,及是否在当前周期内新安装了您关注行业的头部竞品。同时基于行业提供各周期的应用安装卸载比,辅助您判断行业的规模趋势。
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-tooltip>
|
|
|
- </div>
|
|
|
-
|
|
|
+ <LayoutHeader title="卸载归因" :style="{ marginBottom: '0' }">
|
|
|
+ <template #aside>
|
|
|
<div class="data-source-status">数据源状态:Demo数据</div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
+ </template>
|
|
|
+ <template #tooltip-content>
|
|
|
+ 卸载归因解读卸载设备在您应用中的最后活跃行为。<br/>
|
|
|
+ 本模块功能展示周期内的卸载设备,在卸载前的最后7天(含当天)在您的应用中浏览次数TOP10的页面;<br/>
|
|
|
+ 展示应用在全网设备中的卸载量,及是否在当前周期内新安装了您关注行业的头部竞品。<br/>
|
|
|
+ 同时基于行业提供各周期的应用安装卸载比,辅助您判断行业的规模趋势。
|
|
|
+ </template>
|
|
|
+ <template #content>
|
|
|
+ </template>
|
|
|
+ </LayoutHeader>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
-
|
|
|
<el-row :gutter="12" style="padding: 0 12px 12px; row-gap: 12px;">
|
|
|
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
<el-card shadow="none">
|
|
|
<div class="trend-container">
|
|
|
- <div class="title">卸载设备全量预测</div>
|
|
|
+ <div class="title">卸载设备全量预测<svg
|
|
|
+ width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <path d="M0 13C0 5.8203 5.8203 0 13 0H22C23.1046 0 24 0.895431 24 2V9C24 16.1797 18.1797 22 11 22H2C0.89543 22 0 21.1046 0 20V13Z" fill="url(#paint0_linear_834_812)"/>
|
|
|
+ <path d="M6 16.5L9.63695 5.5H11.6343L15.2712 16.5H13.4379L12.5137 13.3806H8.6979L7.77376 16.5H6ZM9.11526 12.0075H12.0964L11.6641 10.5299C11.3064 9.33582 10.9784 8.11194 10.6356 6.87313H10.576C10.2481 8.12687 9.90525 9.33582 9.54752 10.5299L9.11526 12.0075Z" fill="white"/>
|
|
|
+ <path d="M16.271 16.5V5.5H18V16.5H16.271Z" fill="white"/>
|
|
|
+ <defs>
|
|
|
+ <linearGradient id="paint0_linear_834_812" x1="12" y1="0" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
|
+ <stop stop-color="#4CFFEA"/>
|
|
|
+ <stop offset="1" stop-color="#0081EB"/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
<div class="card-tabs">
|
|
|
- <div class="card-tab" :class="{ active: activeTab === 'churnTrend' }"
|
|
|
- @click="handleTabClick('churnTrend')">
|
|
|
+ <div class="card-tab" :class="{ active: activeTab === Type.CHURN }"
|
|
|
+ @click="handleTabClick(Type.CHURN)">
|
|
|
卸载流失设备(预测)
|
|
|
<el-tooltip effect="light" content="" placement="right-start">
|
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
|
|
@@ -67,8 +67,8 @@
|
|
|
</el-tooltip>
|
|
|
|
|
|
</div>
|
|
|
- <div class="card-tab" :class="{ active: activeTab === 'recallTrend' }"
|
|
|
- @click="handleTabClick('recallTrend')">卸载召回设备(预测)
|
|
|
+ <div class="card-tab" :class="{ active: activeTab === Type.RECALL }"
|
|
|
+ @click="handleTabClick(Type.RECALL)">卸载召回设备(预测)
|
|
|
<el-tooltip effect="light" content="" placement="right-start">
|
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
|
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
@@ -100,7 +100,7 @@
|
|
|
<!-- 折线图 -->
|
|
|
<div class="chart-container">
|
|
|
<LineChart :data="currentChartData" :color="'#167af0'" height="270px"
|
|
|
- :smooth="false" :area-style="true" :title="activeTab === 'churnTrend' ? '卸载流失设备' : '卸载召回设备'" :showLegend="true" />
|
|
|
+ :smooth="false" :area-style="true" :title="activeTab === Type.CHURN ? '卸载流失设备' : '卸载召回设备'" :showLegend="true" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
@@ -115,13 +115,13 @@
|
|
|
<!-- 折线图 -->
|
|
|
<div class="chart-container">
|
|
|
<LineChart :data="data" :color="'#167af0'" height="270px" :showLegend="true"
|
|
|
- :smooth="false" :area-style="true" :isMultiSeries="true" :seriesNames="['当前应用', '移动视频行业TOP5', '医疗服务行业TOP5']" />
|
|
|
+ :smooth="false" :area-style="true" :isMultiSeries="true" :seriesNames="seriesNames" />
|
|
|
</div>
|
|
|
|
|
|
<el-divider style="margin: 30px 0; background: rgba(230, 230, 230, 1);"/>
|
|
|
|
|
|
<!-- 表格 -->
|
|
|
- <ExportToCSV :hidePage="true" :data="formatData" :columns="columns" :fileName="''" :hide-table="false" :tableStyle="{minWidth: '1476px'}" />
|
|
|
+ <ExportToCSV :data="formatData" :columns="columns" :fileName="''" :hide-table="false" :tableStyle="{maxWidth: '1476px'}" />
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
@@ -134,75 +134,117 @@
|
|
|
import LineChart from '/@/views/count/components/LineChart.vue'
|
|
|
import ExportToCSV from '/@/views/count/components/ExportToCSV.vue';
|
|
|
import { ref, computed } from 'vue'
|
|
|
+import LayoutHeader from '/@/views/count/components/LayoutHeader.vue';
|
|
|
+import { uninstallPredict, predictDetail } from '/@/api/count/churn';
|
|
|
+import { formatDate } from '/@/utils/formatTime';
|
|
|
+
|
|
|
+// 类型枚举
|
|
|
+enum Type {
|
|
|
+ CHURN = 0,
|
|
|
+ RECALL = 1
|
|
|
+}
|
|
|
|
|
|
-// 流失趋势数据
|
|
|
-const churnTrendData = ref([
|
|
|
- { date: '2025-01-01', value: 25 },
|
|
|
- { date: '2025-01-02', value: 32 },
|
|
|
- { date: '2025-01-03', value: 28 },
|
|
|
- { date: '2025-01-04', value: 45 },
|
|
|
- { date: '2025-01-05', value: 38 },
|
|
|
- { date: '2025-01-06', value: 42 },
|
|
|
- { date: '2025-01-07', value: 35 },
|
|
|
- { date: '2025-01-08', value: 48 },
|
|
|
- { date: '2025-01-09', value: 52 },
|
|
|
- { date: '2025-01-10', value: 39 },
|
|
|
- { date: '2025-01-11', value: 44 },
|
|
|
- { date: '2025-01-12', value: 37 },
|
|
|
- { date: '2025-01-13', value: 41 },
|
|
|
- { date: '2025-01-14', value: 46 }
|
|
|
-])
|
|
|
-
|
|
|
-// 召回趋势数据
|
|
|
-const recallTrendData = ref([
|
|
|
- { date: '2025-01-01', value: 15 },
|
|
|
- { date: '2025-01-02', value: 22 },
|
|
|
- { date: '2025-01-03', value: 18 },
|
|
|
- { date: '2025-01-04', value: 35 },
|
|
|
- { date: '2025-01-05', value: 28 },
|
|
|
- { date: '2025-01-06', value: 32 },
|
|
|
- { date: '2025-01-07', value: 25 },
|
|
|
- { date: '2025-01-08', value: 38 },
|
|
|
- { date: '2025-01-09', value: 42 },
|
|
|
- { date: '2025-01-10', value: 29 },
|
|
|
- { date: '2025-01-11', value: 34 },
|
|
|
- { date: '2025-01-12', value: 27 },
|
|
|
- { date: '2025-01-13', value: 31 },
|
|
|
- { date: '2025-01-14', value: 36 }
|
|
|
-])
|
|
|
-
|
|
|
-const activeTab = ref('churnTrend')
|
|
|
-
|
|
|
-// 计算当前图表数据
|
|
|
-const currentChartData = computed(() => {
|
|
|
- return activeTab.value === 'churnTrend' ? churnTrendData.value : recallTrendData.value
|
|
|
-})
|
|
|
+const activeTab = ref(Type.CHURN);
|
|
|
+const currentChartData = ref([] as any[])
|
|
|
|
|
|
-const handleTabClick = (tab: string) => {
|
|
|
+const handleTabClick = (tab: number) => {
|
|
|
activeTab.value = tab
|
|
|
+ getUninstallPredict();
|
|
|
}
|
|
|
|
|
|
const data = ref([
|
|
|
- { date: '2025-01-01', values: [{ value: 1, seriesName: '当前应用' }, { value: 2, seriesName: '移动视频行业TOP5' }, { value: 5, seriesName: '医疗服务行业TOP5' }] },
|
|
|
- { date: '2025-01-02', values: [{ value: 11, seriesName: '当前应用' }, { value: 4, seriesName: '移动视频行业TOP5' }, { value: 3, seriesName: '医疗服务行业TOP5' }] },
|
|
|
- { date: '2025-01-03', values: [{ value: 2, seriesName: '当前应用' }, { value: 2, seriesName: '移动视频行业TOP5' }, { value: 3, seriesName: '医疗服务行业TOP5' }] },
|
|
|
- { date: '2025-01-04', values: [{ value: 11, seriesName: '当前应用' }, { value: 5, seriesName: '移动视频行业TOP5' }, { value: 9, seriesName: '医疗服务行业TOP5' }] }
|
|
|
+ // { date: '2025-01-01', values: [{ value: 1, seriesName: '当前应用' }, { value: 2, seriesName: '移动视频行业TOP5' }, { value: 5, seriesName: '医疗服务行业TOP5' }] },
|
|
|
])
|
|
|
|
|
|
const columns = ref([
|
|
|
{prop: 'date', label: '日期'},
|
|
|
- {prop: 'app1', label: '当前应用'},
|
|
|
- {prop: 'app2', label: '移动视频行业TOP5'},
|
|
|
- {prop: 'app3', label: '医疗服务行业TOP5'},
|
|
|
+ // {prop: 'app1', label: '当前应用'},
|
|
|
])
|
|
|
|
|
|
+const seriesNames = computed(() => {
|
|
|
+ return columns.value.filter((item: any) => item.prop !== 'date').map((item: any) => item.label)
|
|
|
+})
|
|
|
+
|
|
|
const formatData = computed(() => {
|
|
|
- return data.value.map((item: any) => ({
|
|
|
- date: item.date,
|
|
|
- app1: item.values[0].value,
|
|
|
- app2: item.values[1].value,
|
|
|
- app3: item.values[2].value
|
|
|
- }))
|
|
|
+ return data.value.map((item: any) => {
|
|
|
+ const formattedItem: any = {
|
|
|
+ date: item.date
|
|
|
+ };
|
|
|
+
|
|
|
+ // 动态添加每个系列的数据
|
|
|
+ item.values.forEach((valueItem: any, index: number) => {
|
|
|
+ const propName = `app${index + 1}`;
|
|
|
+ formattedItem[propName] = valueItem.value;
|
|
|
+ });
|
|
|
+
|
|
|
+ return formattedItem;
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+const getUninstallPredict = (data?: any) => {
|
|
|
+ uninstallPredict({
|
|
|
+ type: activeTab.value,
|
|
|
+ cycle: 'week',
|
|
|
+ ...data
|
|
|
+ }).then((res) => {
|
|
|
+ if(res.code === 0 && res.data?.length > 0) {
|
|
|
+ currentChartData.value = res.data.map((item: any) => ({
|
|
|
+ date: formatDate(new Date(item.predictTime), 'YYYY-mm-dd'),
|
|
|
+ value: activeTab.value === Type.CHURN ? item.uninstallCount : item.recallCount
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const getPredictDetail = (params?: any) => {
|
|
|
+ predictDetail({
|
|
|
+ cycle: 'week',
|
|
|
+ ...params
|
|
|
+ }).then((res) => {
|
|
|
+ console.log(res);
|
|
|
+ const newColumns: any[] = []
|
|
|
+ if(res.code === 0 && res.data?.length > 0) {
|
|
|
+ columns.value = [];
|
|
|
+ data.value = res.data.map((item: any, index: number) => {
|
|
|
+ let rowData = {
|
|
|
+ date: formatDate(new Date(item.date), 'YYYY-mm-dd'),
|
|
|
+ values: [] as {value: number, seriesName: string}[]
|
|
|
+ }
|
|
|
+
|
|
|
+ item.type.forEach((type: any, _index: number) => {
|
|
|
+ const [key, _value] = Object.entries(type)[0];
|
|
|
+ console.log(key, _value);
|
|
|
+ rowData.values.push({
|
|
|
+ value: _value as number,
|
|
|
+ seriesName: key
|
|
|
+ })
|
|
|
+ newColumns.push({
|
|
|
+ prop: 'app' + (1+_index),
|
|
|
+ label: key
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ if(index === 0) {
|
|
|
+ columns.value = [
|
|
|
+ {prop: 'date', label: '日期'},
|
|
|
+ ...newColumns
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(columns.value);
|
|
|
+ return rowData;
|
|
|
+ })
|
|
|
+ data.value = data.value.sort((a: any, b: any) => {
|
|
|
+ return new Date(a.date).getTime() - new Date(b.date).getTime()
|
|
|
+ })
|
|
|
+ console.log(data.value);
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getUninstallPredict();
|
|
|
+ getPredictDetail();
|
|
|
})
|
|
|
</script>
|
|
|
<style scoped lang="scss">
|