浏览代码

fix: 用户分析-版本分布

jcq 6 天之前
父节点
当前提交
924f646c98

+ 3 - 3
src/views/count/user/activations/index.vue

@@ -80,9 +80,9 @@
 				<!-- 明细表格 -->
 				<div class="mt-3">
 					<div class="flex items-center justify-between mb-2">
-						<div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
-							{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
-						</div>
+						<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail1 = !showDetail1">
+								{{ showDetail1 ? '收起明细数据' : '展开明细数据' }} <el-icon class="ml-2"><ArrowDown  v-if="showDetail1"/> <ArrowUp  v-else/> </el-icon>
+							</div>
 						<div>
 							<el-button>导出</el-button>
 						</div>

+ 3 - 3
src/views/count/user/activeUser/index.vue

@@ -92,9 +92,9 @@
 				<!-- 明细表格 -->
 				<div class="mt-3">
 					<div class="flex items-center justify-between mb-2">
-						<div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
-							{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
-						</div>
+						<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail1 = !showDetail1">
+								{{ showDetail1 ? '收起明细数据' : '展开明细数据' }} <el-icon class="ml-2"><ArrowDown  v-if="showDetail1"/> <ArrowUp  v-else/> </el-icon>
+							</div>
 						<div>
 							<el-button>导出</el-button>
 						</div>

+ 6 - 6
src/views/count/user/adduser/index.vue

@@ -75,9 +75,9 @@
 				<!-- 明细表格 -->
 				<div class="mt-3">
 					<div class="flex items-center justify-between mb-2">
-						<div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
-							{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
-						</div>
+						<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail1 = !showDetail1">
+								{{ showDetail1 ? '收起明细数据' : '展开明细数据' }} <el-icon class="ml-2"><ArrowDown  v-if="showDetail1"/> <ArrowUp  v-else/> </el-icon>
+							</div>
 						<div>
 							<el-button>导出</el-button>
 						</div>
@@ -141,9 +141,9 @@
 				<!-- 明细表格 -->
 				<div class="mt-3">
 					<div class="flex items-center justify-between mb-2">
-						<div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
-							{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
-						</div>
+						<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail1 = !showDetail1">
+								{{ showDetail1 ? '收起明细数据' : '展开明细数据' }} <el-icon class="ml-2"><ArrowDown  v-if="showDetail1"/> <ArrowUp  v-else/> </el-icon>
+							</div>
 						<div>
 							<el-button>导出</el-button>
 						</div>

+ 1 - 1
src/views/count/user/versionDistribution/i18n/zh-cn.ts

@@ -4,7 +4,7 @@ export default {
 		aijb:'查看完整ai简报',
 		addtrend:'新增趋势',
 		version:'版本',
-		allVersion:'全部版本',
+		allVersion:'Top10 版本',
 		versionComparison:'版本对比'
 	},
 };

+ 204 - 46
src/views/count/user/versionDistribution/index.vue

@@ -3,33 +3,10 @@
 		<div class="!overflow-auto px-1">
 			<div class="el-card p-2">
 				<div class="flex justify-between">
-					<Title :title="t('activations.analytics')">
-						<template #default>
-							<el-popover class="box-item" placement="right" trigger="hover" width="250">
-								<template #reference>
-									<el-icon class="ml-1" style="color: #a4b8cf"><QuestionFilled /></el-icon>
-								</template>
-								<template #default>
-									<div class="ant-popover-inner-content">
-										<div class="um-page-tips-content" style="line-height: 24px">
-											<p>
-												<span class="highlight">启动次数:</span
-												><span
-													>打开应用视为启动。完全退出或后台运行超过30s后再次进入应用,视为一次新启动。开发过程中可以通过setSessionContinueMills来自定义两次启动的间隔,默认30s</span
-												>
-											</p>
-											<p><span class="highlight">启动次数占比:</span><span>某日/周/月的启动次数占所选时间段总启动次数的比例</span></p>
-											<p>
-												<span
-													>按天、周或月查看数据可进行版本、渠道的交叉筛选,小时数据最多展示7天并且不支持筛选。周区间定义为周日至次周周六。按周(按月)显示时,界面上用每周的周日(每个月的第一日)来代表该周(该月)</span
-												>
-											</p>
-										</div>
-									</div>
-								</template>
-							</el-popover>
-						</template>
-					</Title>
+					<Title :title="t('versionDistribution.analytics')" />
+					<div class="">
+						<el-button type="primary">{{ t('versionDistribution.aijb') }}</el-button>
+					</div>
 				</div>
 				<div>
 					<el-row shadow="hover" class="ml10 mt-2">
@@ -37,11 +14,6 @@
 							<el-form-item>
 								<el-date-picker v-model="formData.time" type="daterange" start-placeholder="开始时间" end-placeholder="结束时间" />
 							</el-form-item>
-							<el-form-item>
-								<el-select v-model="selectedChannelCompare" class="w-[140px]" placeholder="全部渠道">
-									<el-option v-for="item in channelCompareOptions" :key="item.value" :label="item.label" :value="item.value" />
-								</el-select>
-							</el-form-item>
 							<el-form-item>
 								<el-select v-model="selectedChannelCompare" class="w-[140px]" placeholder="全部版本">
 									<el-option v-for="item in channelCompareOptions" :key="item.value" :label="item.label" :value="item.value" />
@@ -52,22 +24,57 @@
 				</div>
 			</div>
 			<div class="mt-2 el-card p-2">
+				<Title left-line :title="t('versionDistribution.allVersion')">
+					<template #default>
+						<el-popover class="box-item" placement="right" trigger="hover" width="300">
+							<template #reference>
+								<el-icon class="ml-1" style="color: #a4b8cf"><QuestionFilled /></el-icon>
+							</template>
+							<template #default>
+								<div class="ant-popover-inner-content">
+									<div class="um-page-tips-content" style="line-height: 24px">
+										<p><span>趋势图展示累计用户排名Top10版本的变化趋势</span></p>
+										<p><span class="highlight">新增用户:</span><span>第一次启动应用的用户(以设备为判断标准)</span></p>
+										<p>
+											<span class="highlight">活跃用户:</span
+											><span>启动过应用的用户(去重),启动过一次的用户即视为活跃用户,包括新用户与老用户</span>
+										</p>
+										<p>
+											<span class="highlight">启动次数:</span
+											><span
+												>打开应用视为启动。完全退出或后台运行超过30s后再次进入应用,视为一次新启动。开发过程中可以通过setSessionContinueMills来自定义两次启动的间隔,默认30s</span
+											>
+										</p>
+										<p>
+											<span class="highlight">版本累计用户(%):</span
+											><span>截止到现在,该版本的累计用户(占累计用户全体的比例);若该版本的用户升级到其他版本,则累计用户会减少</span>
+										</p>
+										<p><span class="highlight">升级用户:</span><span>从其他版本升级到该版本的用户(以设备为判断标准)</span></p>
+										<p>
+											<span
+												>如果当日用户先启动老版本然后升级到新版本,分版本查看数据时,此用户在新老版本都会被算为活跃用户(按总体查看数据时不受影响)</span
+											>
+										</p>
+									</div>
+								</div>
+							</template>
+						</el-popover>
+					</template>
+				</Title>
 				<div class="">
 					<div class="flex items-center justify-between mb-2 mt-3">
 						<div>
 							<el-select v-model="selectedChannelCompare" class="w-[140px] ml-2" style="width: 140px" placeholder="版本对比">
 								<el-option v-for="item in channelCompareOptions" :key="item.value" :label="item.label" :value="item.value" />
 							</el-select>
-							<el-button type="primary" class="ml-2">{{ t('activations.version') }}</el-button>
+							<el-button type="primary" class="ml-2">{{ t('versionDistribution.version') }}</el-button>
 						</div>
 
 						<div class="flex items-center">
-							<el-radio-group v-model="timeGranularity" size="small">
-								<el-radio-button label="hour">小时</el-radio-button>
-
-								<el-radio-button label="day">天</el-radio-button>
-								<el-radio-button label="week">周</el-radio-button>
-								<el-radio-button label="month">月</el-radio-button>
+							<el-radio-group v-model="timeGranularity">
+								<el-radio-button label="hour">新增用户</el-radio-button>
+								<el-radio-button label="day">活跃用户</el-radio-button>
+								<el-radio-button label="week">启动次数</el-radio-button>
 							</el-radio-group>
 						</div>
 					</div>
@@ -80,9 +87,19 @@
 				<!-- 明细表格 -->
 				<div class="mt-3">
 					<div class="flex items-center justify-between mb-2">
-						<div class="text-base font-medium cursor-pointer select-none" @click="showDetail1 = !showDetail1">
-							{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
+						<div class="flex">
+							<div class="flex items-center">
+								<el-radio-group v-model="timeGranularity">
+									<el-radio-button label="hour">今日</el-radio-button>
+									<el-radio-button label="day">作日 </el-radio-button>
+								</el-radio-group>
+							</div>
+							<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail1 = !showDetail1">
+								{{ showDetail1 ? '收起明细数据' : '展开明细数据' }}
+								<el-icon class="ml-2"><ArrowDown v-if="showDetail1" /> <ArrowUp v-else /> </el-icon>
+							</div>
 						</div>
+
 						<div>
 							<el-button>导出</el-button>
 						</div>
@@ -104,6 +121,75 @@
 					</div>
 				</div>
 			</div>
+			<div class="mt-2 el-card p-2">
+				<div class="flex justify-between">
+					<Title left-line :title="'版本用户来源'">
+						<template #default>
+							<el-popover class="box-item" placement="right" trigger="hover" width="300">
+								<template #reference>
+									<el-icon class="ml-1" style="color: #a4b8cf"><QuestionFilled /></el-icon>
+								</template>
+								<template #default>
+									<div class="ant-popover-inner-content">
+										<div class="um-page-tips-content" style="line-height: 24px">
+											<p><span class="highlight">版本用户来源:</span><span>展示各版本用户的主要获取渠道分布</span></p>
+											<p><span class="highlight">新增用户:</span><span>第一次启动应用的用户(以设备为判断标准)</span></p>
+											<p><span class="highlight">升级用户比例:</span><span>从其他版本升级到该版本的用户占比</span></p>
+										</div>
+									</div>
+								</template>
+							</el-popover>
+						</template>
+					</Title>
+				</div>
+				<div class="flex items-center justify-between mt-3">
+					<el-select v-model="channelDistribution" style="width: 180px" placeholder="新增用户渠道分布">
+						<el-option label="新增用户渠道分布" value="distribution" />
+					</el-select>
+					<div class="ml-4">
+						<el-radio-group v-model="timeRange">
+							<el-radio-button label="yesterday">昨天</el-radio-button>
+							<el-radio-button label="7days">过去7天</el-radio-button>
+							<el-radio-button label="30days">过去30天</el-radio-button>
+						</el-radio-group>
+					</div>
+				</div>
+
+				<!-- 水平柱状图 -->
+				<div class="mt-4">
+					<div class="relative">
+						<div ref="barChartRef" style="width: 100%; height: 200px"></div>
+					</div>
+				</div>
+
+				<!-- 明细表格 -->
+				<div class="mt-4">
+					<div class="flex items-center justify-between mb-2">
+						<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail2 = !showDetail2">
+							{{ showDetail2 ? '收起明细数据' : '展开明细数据' }} <el-icon class="ml-2"><ArrowDown v-if="showDetail2" /> <ArrowUp v-else /> </el-icon>
+						</div>
+
+						<div>
+							<el-button>导出</el-button>
+						</div>
+					</div>
+					<el-table v-if="showDetail2" :data="pagedSourceRows" border>
+						<el-table-column prop="channel" label="渠道" min-width="140" />
+						<el-table-column prop="newUsers" label="新增用户" min-width="140" />
+						<el-table-column prop="upgradeRatio" label="升级用户比例" min-width="140" />
+					</el-table>
+					<div v-if="showDetail2" class="flex justify-end mt-3">
+						<el-pagination
+							v-model:current-page="sourcePage"
+							v-model:page-size="sourcePageSize"
+							background
+							layout="total, prev, pager, next, sizes"
+							:total="sourceRows.length"
+							:page-sizes="[5, 10, 20]"
+						/>
+					</div>
+				</div>
+			</div>
 		</div>
 	</div>
 </template>
@@ -112,6 +198,7 @@
 import { ref, onMounted, watch, computed, defineAsyncComponent } from 'vue';
 import * as echarts from 'echarts';
 import { useI18n } from 'vue-i18n';
+import { QuestionFilled } from '@element-plus/icons-vue';
 
 const { t } = useI18n();
 
@@ -128,9 +215,9 @@ const query = () => {
 
 const selectedChannelCompare = ref('');
 const channelCompareOptions = [
-	{ label: '渠道对比', value: 'compare' },
-	{ label: '渠道A', value: 'a' },
-	{ label: '渠道B', value: 'b' },
+	{ label: '全部版本', value: '     ' },
+	{ label: '1.0', value: 'a' },
+	{ label: '2.0', value: 'b' },
 ];
 
 // 图表相关
@@ -191,6 +278,7 @@ function initLineChart(): void {
 
 onMounted(() => {
 	initLineChart();
+	initBarChart();
 });
 
 watch(timeGranularity, () => {
@@ -221,17 +309,87 @@ const Title = defineAsyncComponent(() => import('/@/components/Title/index.vue')
 // 展开/收起明细
 const showDetail1 = ref(true);
 
+// 版本用户来源相关
+const channelDistribution = ref('distribution');
+const timeRange = ref('yesterday');
+const barChartRef = ref<HTMLDivElement | null>(null);
+let barChartInstance: echarts.ECharts | null = null;
+
+const sourceRows = ref<Array<{ channel: string; newUsers: number; upgradeRatio: string }>>([
+	{ channel: '渠道A', newUsers: 100, upgradeRatio: '10%' },
+	{ channel: '渠道B', newUsers: 80, upgradeRatio: '15%' },
+	{ channel: '渠道C', newUsers: 70, upgradeRatio: '20%' },
+	{ channel: '渠道D', newUsers: 60, upgradeRatio: '25%' },
+	{ channel: '渠道E', newUsers: 50, upgradeRatio: '30%' },
+	{ channel: '渠道F', newUsers: 40, upgradeRatio: '35%' },
+	{ channel: '渠道G', newUsers: 30, upgradeRatio: '40%' },
+	{ channel: '渠道H', newUsers: 20, upgradeRatio: '45%' },
+	{ channel: '渠道I', newUsers: 10, upgradeRatio: '50%' },
+	{ channel: '渠道J', newUsers: 5, upgradeRatio: '55%' },
+]);
+
+function initBarChart(): void {
+	if (!barChartRef.value) return;
+	if (barChartInstance) barChartInstance.dispose();
+	barChartInstance = echarts.init(barChartRef.value);
+	const option: echarts.EChartsOption = {
+		tooltip: { trigger: 'axis' },
+		grid: { left: 40, right: 20, top: 20, bottom: 30 },
+		xAxis: {
+			type: 'value',
+			axisLine: { show: false },
+			splitLine: { lineStyle: { color: '#f3f4f6' } },
+			axisLabel: { color: '#6b7280' },
+		},
+		yAxis: {
+			type: 'category',
+			data: sourceRows.value.map((d) => d.channel),
+			axisLine: { lineStyle: { color: '#e5e7eb' } },
+			axisLabel: { color: '#6b7280' },
+			axisTick: { alignWithLabel: true },
+		},
+		series: [
+			{
+				name: '新增用户',
+				type: 'bar',
+				barWidth: '60%',
+				itemStyle: { color: '#409EFF' },
+				data: sourceRows.value.map((d) => d.newUsers),
+			},
+		],
+	};
+	barChartInstance.setOption(option);
+}
+
+onMounted(() => {
+	initBarChart();
+});
+
+watch(timeRange, () => {
+	// 静态页面:仅重新渲染
+	initBarChart();
+});
+
+const sourcePage = ref(1);
+const sourcePageSize = ref(5);
+const pagedSourceRows = computed(() => {
+	const startIndex = (sourcePage.value - 1) * sourcePageSize.value;
+
+	return sourceRows.value.slice(startIndex, startIndex + sourcePageSize.value);
+});
 
+const showDetail2 = ref(true);
 </script>
  
 <style lang="scss" scoped>
 .highlight {
 	color: #2196f3;
 }
-.el-form-item--default{
+.el-form-item--default {
 	margin-bottom: 0;
 }
-.el-form.el-form--inline .el-form-item--default.el-form-item:last-of-type, .el-form.el-form--inline .el-form-item--small.el-form-item:last-of-type{
+.el-form.el-form--inline .el-form-item--default.el-form-item:last-of-type,
+.el-form.el-form--inline .el-form-item--small.el-form-item:last-of-type {
 	margin-bottom: 0 !important;
 }
 </style>