Parcourir la source

Merge branch 'dev-ly' of https://s-20coaj910c.zht2.com/cmy/data-marketing-platform into dev-ly

luoy il y a 6 jours
Parent
commit
ed94238cfa

+ 19 - 1
src/components/Title/index.vue

@@ -1,5 +1,6 @@
 <template>
-	<div class=" font-medium " style="font-size: 16px;">
+	<div class="font-medium" style="font-size: 16px;">
+		<div v-if="props.leftLine" class="left-line mr-2"></div>
 		{{ props.title }}
 		<slot></slot>
 	</div>
@@ -11,5 +12,22 @@ const props = defineProps({
 		type: String,
         default: '标题',
 	},
+	leftLine:{
+		type: Boolean,
+        default: false,
+
+	}
 });
 </script>
+<style lang="scss" scoped>
+.font-medium {
+	font-weight: 500;
+	display: flex;
+	align-items: center;
+}
+.left-line{
+	width: 4px;
+	height: 14px;
+	background: #167AF0;
+}
+</style>

+ 1 - 0
src/layout/footer/index.vue

@@ -22,6 +22,7 @@ const footerAuthor = computed(() => {
 .layout-footer {
 	width: 100%;
 	display: flex;
+	display: none;
 
 	&-warp {
 		margin: auto;

+ 11 - 0
src/views/count/user/adduser/i18n/en.ts

@@ -0,0 +1,11 @@
+export default {
+	addUser: {
+		analytics:'Added user analysis',
+		growth:'Go to U-Growth to text/push',
+		ai:'Generative AI intelligence',
+		addtrend:'New trends',
+		channel:'Select a channel',
+		userQuality:'User quality',
+		average:'Industry average'
+	},
+};

+ 11 - 0
src/views/count/user/adduser/i18n/zh-cn.ts

@@ -0,0 +1,11 @@
+export default {
+	addUser: {
+		analytics:'新增用户分析',
+		growth:'前往U-Growth发短信/Push',
+		ai:'生成ai智能',
+		addtrend:'新增趋势',
+		channel:'选择渠道',
+		userQuality:'用户质量',
+		average:'行业平均值'
+	},
+};

+ 426 - 3
src/views/count/user/adduser/index.vue

@@ -1,7 +1,430 @@
 <template>
-   <div>adduser</div>
+	<div class="layout-padding">
+		<div class="!overflow-auto px-1">
+			<div class="el-card p-2">
+				<div class="flex justify-between">
+					<Title :title="t('addUser.analytics')" />
+					<div class="">
+						<el-button type="primary">{{ t('addUser.growth') }}</el-button>
+						<el-button type="primary">{{ t('addUser.ai') }}</el-button>
+					</div>
+				</div>
+				<div>
+					<el-row shadow="hover" class="ml10">
+						<el-form :inline="true" :model="formData" @keyup.enter="query" ref="queryRef">
+							<el-form-item>
+								<el-date-picker v-model="formData.time" type="daterange" start-placeholder="开始时间" end-placeholder="结束时间" />
+							</el-form-item>
+							<el-form-item prop="appId">
+								<el-input :placeholder="'请输入应用ID'" clearable v-model="formData.appId" />
+							</el-form-item>
+						</el-form>
+					</el-row>
+				</div>
+			</div>
+			<div class="mt-3 el-card p-2">
+				<!-- 新增趋势 -->
+				<div class="">
+					<Title left-line :title="t('addUser.addtrend')">
+						<template #default>
+							<el-popover class="box-item" placement="right" trigger="hover" width="250">
+									<template #reference>
+										<el-icon class="ml-1" style="color: #a4b8cf"><InfoFilled /></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>
+												<p>
+													<span>
+														按天、周或月查看数据可进行版本、渠道的交叉筛选,小时数据最多展示7天并且不支持筛选。周区间定义为周日至次周周六。按周(按月)显示时,界面上用每周的周日(每个月的第一日)来代表该周(该月)
+													</span>
+												</p>
+											</div>
+										</div>
+									</template>
+								</el-popover>
+						</template>
+					</Title>
+					<div class="flex items-center justify-between mb-2 mt-3">
+						<el-form>
+							<el-form-item>
+								<el-select v-model="selectedChannelCompare" style="width: 140px" placeholder="渠道对比">
+									<el-option v-for="item in channelCompareOptions" :key="item.value" :label="item.label" :value="item.value" />
+								</el-select>
+								<el-button class="ml-2">{{ t('addUser.channel') }}</el-button>
+							</el-form-item>
+						</el-form>
+						<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>
+						</div>
+					</div>
+
+					<div class="relative">
+						<div ref="lineChartRef" style="width: 100%; height: 320px"></div>
+					</div>
+				</div>
+
+				<!-- 明细表格 -->
+				<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>
+							<el-button>导出</el-button>
+						</div>
+					</div>
+					<el-table v-if="showDetail1" :data="pagedTableRows" border>
+						<el-table-column prop="date" label="日期" min-width="140" />
+						<el-table-column label="新增用户(占比)" min-width="220">
+							<template #default="scope">
+								<div class="flex items-center justify-between w-full">
+									<span>{{ scope.row.newUsers }}</span>
+									<span class="text-gray-500 text-xs">{{ scope.row.ratio }}</span>
+								</div>
+							</template>
+						</el-table-column>
+					</el-table>
+					<div v-if="showDetail1" class="flex justify-end mt-3">
+						<el-pagination
+							v-model:current-page="currentPage"
+							v-model:page-size="pageSize"
+							background
+							layout="total, prev, pager, next, sizes"
+							:total="tableRows.length"
+							:page-sizes="[5, 10, 20]"
+						/>
+					</div>
+				</div>
+			</div>
+			<div class="mt-3 el-card p-2">
+				<!-- 新增用户质量 -->
+				<div class="">
+					<div class="flex items-center justify-between mb-2">
+						<Title left-line :title="t('addUser.userQuality')">
+							<template #default>
+								<el-popover class="box-item" placement="right" trigger="hover" width="250">
+									<template #reference>
+										<el-icon class="ml-1" style="color: #a4b8cf"><InfoFilled /></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>
+											</div>
+										</div>
+									</template>
+								</el-popover>
+							</template>
+						</Title>
+						
+					</div>
+					<div class="flex items-center ">
+							<el-select v-model="industryCompare"style="width: 120px">
+								<el-option label="行业对比" value="industry" />
+							</el-select>
+							<el-button class="ml-2">{{ t('addUser.average') }}</el-button>
+							<el-button link class="ml-2">清除</el-button>
+						</div>
+					<div class="relative">
+						<div ref="qualityChartRef" style="width: 100%; height: 320px"></div>
+					</div>
+				</div>
+				<!-- 明细表格 -->
+				<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>
+							<el-button>导出</el-button>
+						</div>
+					</div>
+					<el-table v-if="showDetail1" :data="pagedTableRows" border>
+						<el-table-column prop="date" label="日期" min-width="140" />
+						<el-table-column label="新增用户(占比)" min-width="220">
+							<template #default="scope">
+								<div class="flex items-center justify-between w-full">
+									<span>{{ scope.row.newUsers }}</span>
+									<span class="text-gray-500 text-xs">{{ scope.row.ratio }}</span>
+								</div>
+							</template>
+						</el-table-column>
+					</el-table>
+					<div v-if="showDetail1" class="flex justify-end mt-3">
+						<el-pagination
+							v-model:current-page="currentPage"
+							v-model:page-size="pageSize"
+							background
+							layout="total, prev, pager, next, sizes"
+							:total="tableRows.length"
+							:page-sizes="[5, 10, 20]"
+						/>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
-<script lang="ts" setup>
+<script setup lang="ts">
+import { ref, onMounted, watch, computed, defineAsyncComponent } from 'vue';
+import * as echarts from 'echarts';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+interface TableRow {
+	date: string;
+	newUsers: number;
+	ratio: string;
+}
+
+const formData = ref<Record<string, any>>({});
+const query = () => {
+	console.log(formData.value);
+};
+
+const selectedChannelCompare = ref('');
+const channelCompareOptions = [
+	{ label: '渠道对比', value: 'compare' },
+	{ label: '渠道A', value: 'a' },
+	{ label: '渠道B', value: 'b' },
+];
+
+// 图表相关
+const timeGranularity = ref<'hour' | 'day' | 'week' | 'month'>('week');
+const lineChartRef = ref<HTMLDivElement | null>(null);
+let chartInstance: echarts.ECharts | null = null;
+
+const lineChartData = ref<Array<{ x: string; value: number }>>([
+	{ x: '2025-07-01', value: 900 },
+	{ x: '2025-07-08', value: 1000 },
+	{ x: '2025-07-15', value: 1100 },
+	{ x: '2025-07-22', value: 1000 },
+	{ x: '2025-07-29', value: 600 },
+	{ x: '2025-08-05', value: 300 },
+	{ x: '2025-08-12', value: 250 },
+	{ x: '2025-08-19', value: 200 },
+	{ x: '2025-08-26', value: 650 },
+	{ x: '2025-09-02', value: 950 },
+	{ x: '2025-09-09', value: 900 },
+	{ x: '2025-09-16', value: 120 },
+]);
+
+function initLineChart(): void {
+	if (!lineChartRef.value) return;
+	if (chartInstance) chartInstance.dispose();
+	chartInstance = echarts.init(lineChartRef.value);
+	const option: echarts.EChartsOption = {
+		tooltip: { trigger: 'axis' },
+		grid: { left: 40, right: 20, top: 20, bottom: 30 },
+		xAxis: {
+			type: 'category',
+			data: lineChartData.value.map((d) => d.x),
+			axisLine: { lineStyle: { color: '#e5e7eb' } },
+			axisLabel: { color: '#6b7280' },
+			axisTick: { alignWithLabel: true },
+		},
+		yAxis: {
+			type: 'value',
+			axisLine: { show: false },
+			splitLine: { lineStyle: { color: '#f3f4f6' } },
+			axisLabel: { color: '#6b7280' },
+		},
+		series: [
+			{
+				name: '新增人数',
+				type: 'line',
+				smooth: true,
+				showSymbol: true,
+				symbolSize: 6,
+				itemStyle: { color: '#409EFF' },
+				lineStyle: { color: '#409EFF' },
+				data: lineChartData.value.map((d) => d.value),
+			},
+		],
+	};
+	chartInstance.setOption(option);
+}
+
+onMounted(() => {
+	initLineChart();
+	initQualityChart();
+	initBehaviorChart();
+});
+
+watch(timeGranularity, () => {
+	// 静态页面:仅重新渲染
+	initLineChart();
+});
+
+// 表格相关(静态数据)
+const tableRows = ref<TableRow[]>(
+	Array.from({ length: 42 }).map((_, idx) => ({
+		date: `2025-08-${String(11).padStart(2, '0')}`,
+		newUsers: 727,
+		ratio: '97.45%',
+	}))
+);
+
+const currentPage = ref(1);
+const pageSize = ref(5);
+const pagedTableRows = computed(() => {
+	const startIndex = (currentPage.value - 1) * pageSize.value;
+	return tableRows.value.slice(startIndex, startIndex + pageSize.value);
+});
+
+const Title = defineAsyncComponent(() => import('/@/components/Title/index.vue'));
+
+// 展开/收起明细
+const showDetail1 = ref(true);
+const showDetail2 = ref(true);
+const showDetail3 = ref(true);
+
+// 用户质量(留存率)
+const industryCompare = ref('industry');
+const qualityChartRef = ref<HTMLDivElement | null>(null);
+let qualityChart: echarts.ECharts | null = null;
+
+const qualityXAxis = ref<string[]>([
+	'2025-07-01',
+	'2025-07-08',
+	'2025-07-15',
+	'2025-07-22',
+	'2025-07-29',
+	'2025-08-05',
+	'2025-08-12',
+	'2025-08-19',
+	'2025-08-26',
+	'2025-09-02',
+	'2025-09-09',
+	'2025-09-16',
+]);
+
+const retentionSeries = ref<number[]>([20, 23, 27, 24, 22, 15, 5, 4, 16, 26, 25, 2]);
+const industryAvgSeries = ref<number[]>([16, 18, 20, 24, 25, 24, 16, 10, 15, 22, 21, 12]);
+const peerSameScaleSeries = ref<number[]>([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+function initQualityChart(): void {
+	if (!qualityChartRef.value) return;
+	if (qualityChart) qualityChart.dispose();
+	qualityChart = echarts.init(qualityChartRef.value);
+	const option: echarts.EChartsOption = {
+		tooltip: { trigger: 'axis', valueFormatter: (v) => `${v}%` },
+		legend: { data: ['留存率', '同行业App', '同行业同规模App'] },
+		grid: { left: 40, right: 20, top: 30, bottom: 30 },
+		xAxis: { type: 'category', data: qualityXAxis.value },
+		yAxis: {
+			type: 'value',
+			min: 0,
+			max: 30,
+			axisLabel: { formatter: '{value}%' },
+			splitLine: { lineStyle: { color: '#f3f4f6' } },
+		},
+		series: [
+			{ name: '留存率', type: 'line', smooth: true, data: retentionSeries.value },
+			{ name: '同行业App', type: 'line', smooth: true, data: industryAvgSeries.value, color: '#f59e0b' },
+			{ name: '同行业同规模App', type: 'line', smooth: true, data: peerSameScaleSeries.value, color: '#60a5fa' },
+		],
+	};
+	qualityChart.setOption(option);
+}
+
+// 质量明细表格(静态)
+interface QualityRow {
+	date: string;
+	retention: string;
+}
+const qualityRows = ref<QualityRow[]>(
+	Array.from({ length: 42 }).map(() => ({
+		date: `2025-08-11`,
+		retention: '15%',
+	}))
+);
+const qualityPage = ref(1);
+const qualityPageSize = ref(5);
+const pagedQualityRows = computed(() => {
+	const start = (qualityPage.value - 1) * qualityPageSize.value;
+	return qualityRows.value.slice(start, start + qualityPageSize.value);
+});
+
+// 用户行为分析(静态)
+const behaviorCompare = ref('behavior');
+const behaviorChartRef = ref<HTMLDivElement | null>(null);
+let behaviorChart: echarts.ECharts | null = null;
+
+const behaviorXAxis = ref<string[]>([
+	'2025-07-01',
+	'2025-07-08',
+	'2025-07-15',
+	'2025-07-22',
+	'2025-07-29',
+	'2025-08-05',
+	'2025-08-12',
+	'2025-08-19',
+	'2025-08-26',
+	'2025-09-02',
+	'2025-09-09',
+	'2025-09-16',
+]);
+
+const activeUsersSeries = ref<number[]>([100, 120, 130, 110, 105, 90, 80, 70, 95, 110, 100, 85]);
+const avgSessionSeries = ref<number[]>([120, 130, 140, 125, 120, 110, 100, 95, 115, 125, 120, 110]);
+const behaviorAvgSeries = ref<number[]>([110, 115, 120, 115, 110, 105, 100, 95, 110, 115, 110, 105]);
+
+function initBehaviorChart(): void {
+	if (!behaviorChartRef.value) return;
+	if (behaviorChart) behaviorChart.dispose();
+	behaviorChart = echarts.init(behaviorChartRef.value);
+	const option: echarts.EChartsOption = {
+		tooltip: { trigger: 'axis', valueFormatter: (v) => `${v}s` },
+		legend: { data: ['活跃用户数', '平均会话时长', '行为均值'] },
+		grid: { left: 40, right: 20, top: 30, bottom: 30 },
+		xAxis: { type: 'category', data: behaviorXAxis.value },
+		yAxis: {
+			type: 'value',
+			min: 0,
+			max: 150,
+			axisLabel: { formatter: '{value}s' },
+			splitLine: { lineStyle: { color: '#f3f4f6' } },
+		},
+		series: [
+			{ name: '活跃用户数', type: 'line', smooth: true, data: activeUsersSeries.value },
+			{ name: '平均会话时长', type: 'line', smooth: true, data: avgSessionSeries.value, color: '#f59e0b' },
+			{ name: '行为均值', type: 'line', smooth: true, data: behaviorAvgSeries.value, color: '#60a5fa' },
+		],
+	};
+	behaviorChart.setOption(option);
+}
+
+// 行为明细表格(静态)
+interface BehaviorRow {
+	date: string;
+	activeUsers: string;
+	avgSession: string;
+}
+const behaviorRows = ref<BehaviorRow[]>(
+	Array.from({ length: 42 }).map(() => ({
+		date: `2025-08-11`,
+		activeUsers: '100',
+		avgSession: '120',
+	}))
+);
+const behaviorPage = ref(1);
+const behaviorPageSize = ref(5);
+</script>
 
-</script>
+<style lang="scss" scoped>
+.highlight{
+	color: #2196f3;
+}
+</style>