Browse Source

fix: ···

jcq 1 day ago
parent
commit
bd3b603528

+ 2 - 1
.env

@@ -5,9 +5,10 @@ VITE_IS_MICRO= true
 VITE_PUBLIC_PATH = /
 
 # 后端请求前缀
-VITE_API_URL = http://192.168.10.101:9999
+# VITE_API_URL = http://192.168.10.101:9999 
 # VITE_API_URL = http://192.168.3.118:9999
 #  VITE_API_URL = http://192.168.3.17:9999
+ VITE_API_URL = http://16.162.25.197:9999
 
 # OAUTH2 密码模式客户端信息
 VITE_OAUTH2_PASSWORD_CLIENT='pig:pig'

+ 1 - 0
src/views/count/user/adduser/components/AddTrend.vue

@@ -393,6 +393,7 @@ function handleCompareItemsChange(items: string[]) {
     .map(({ index }) => index);
 
   // 更新 previousCompareItems 为当前值,用于下次对比
+  
   previousCompareItems.value = items;
 
   // 先处理主数据项的name

+ 253 - 99
src/views/count/user/versionDistribution/components/version-trend-card.vue

@@ -1,7 +1,9 @@
 <template>
-	<div class="mt-2 el-card  p-4">
-		<Title left-line
-			:title="selectedVersionLocal === '' ? t('versionDistribution.allVersion') : selectedVersionLocal + t('versionDistribution.version')">
+	<div class="mt-2 el-card p-4">
+		<Title
+			left-line
+			:title="selectedVersionLocal === '' ? t('versionDistribution.allVersion') : selectedVersionLocal + t('versionDistribution.version')"
+		>
 			<template #default>
 				<el-popover class="box-item" placement="right" trigger="hover" width="300">
 					<template #reference>
@@ -15,20 +17,23 @@
 								<p><span>趋势图展示累计用户排名Top10版本的变化趋势</span></p>
 								<p><span class="highlight">新增用户:</span><span>第一次启动应用的用户(以设备为判断标准)</span></p>
 								<p>
-									<span
-										class="highlight">活跃用户:</span><span>启动过应用的用户(去重),启动过一次的用户即视为活跃用户,包括新用户与老用户</span>
+									<span class="highlight">活跃用户:</span><span>启动过应用的用户(去重),启动过一次的用户即视为活跃用户,包括新用户与老用户</span>
 								</p>
 								<p>
-									<span
-										class="highlight">启动次数:</span><span>打开应用视为启动。完全退出或后台运行超过30s后再次进入应用,视为一次新启动。开发过程中可以通过setSessionContinueMills来自定义两次启动的间隔,默认30s</span>
+									<span class="highlight">启动次数:</span
+									><span
+										>打开应用视为启动。完全退出或后台运行超过30s后再次进入应用,视为一次新启动。开发过程中可以通过setSessionContinueMills来自定义两次启动的间隔,默认30s</span
+									>
 								</p>
 								<p>
-									<span
-										class="highlight">版本累计用户(%):</span><span>截止到现在,该版本的累计用户(占累计用户全体的比例);若该版本的用户升级到其他版本,则累计用户会减少</span>
+									<span class="highlight">版本累计用户(%):</span
+									><span>截止到现在,该版本的累计用户(占累计用户全体的比例);若该版本的用户升级到其他版本,则累计用户会减少</span>
 								</p>
 								<p><span class="highlight">升级用户:</span><span>从其他版本升级到该版本的用户(以设备为判断标准)</span></p>
 								<p>
-									<span>如果当日用户先启动老版本然后升级到新版本,分版本查看数据时,此用户在新老版本都会被算为活跃用户(按总体查看数据时不受影响)</span>
+									<span
+										>如果当日用户先启动老版本然后升级到新版本,分版本查看数据时,此用户在新老版本都会被算为活跃用户(按总体查看数据时不受影响)</span
+									>
 								</p>
 							</div>
 						</div>
@@ -36,24 +41,26 @@
 				</el-popover>
 			</template>
 		</Title>
-		<div class=" p-10">
+		<div class="p-10">
 			<div class="flex items-center justify-between mb-2 mt-3">
 				<div class="flex items-center">
-					<el-select v-if="selectedVersionLocal !== ''" v-model="selectedVersionLocal" class="w-[140px] ml-2"
-						style="width: 140px" placeholder="全部频道">
-						<el-option v-for="item in channelOptions" :key="item.value" :label="item.label"
-							:value="item.value" />
+					<el-select
+						v-if="selectedVersionLocal !== ''"
+						v-model="selectedVersionLocal"
+						class="w-[140px] ml-2"
+						style="width: 140px"
+						placeholder="全部频道"
+					>
+						<el-option v-for="item in channelOptions" :key="item.value" :label="item.label" :value="item.value" />
 					</el-select>
-					<el-select v-model="industryCompare" class="!w-[120px] ml-2" clearable
-						@change="handleCompareChange">
+					<el-select v-model="industryCompare" class="!w-[120px] ml-2" clearable @change="handleCompareChange">
 						<el-option label="版本对比" value="version" />
 						<el-option v-if="selectedVersionLocal !== ''" label="渠道对比" value="channel" />
 						<el-option v-if="selectedVersionLocal !== ''" label="时段对比" value="time" />
 					</el-select>
 
 					<!-- 版本对比和渠道对比使用popover -->
-					<el-popover v-if="industryCompare !== 'time' && industryCompare" placement="bottom" trigger="click"
-						width="400">
+					<el-popover v-if="industryCompare !== 'time' && industryCompare" placement="bottom" trigger="click" width="400">
 						<template #reference>
 							<el-button class="ml-2">{{ t('versionDistribution.version') }}</el-button>
 						</template>
@@ -61,12 +68,16 @@
 							<div class="p-3">
 								<div class="mb-3">
 									<label class="text-sm font-medium mb-2 block">{{ getCompareTitle() }}</label>
-									<el-input v-model="searchKeyword" :placeholder="`请搜索${getCompareTypeText()}`"
-										clearable @input="filterCompareOptions" size="small" />
+									<el-input
+										v-model="searchKeyword"
+										:placeholder="`请搜索${getCompareTypeText()}`"
+										clearable
+										@input="filterCompareOptions"
+										size="small"
+									/>
 								</div>
 								<div class="max-h-60 overflow-y-auto">
-									<el-checkbox-group v-model="selectedCompareItems"
-										@change="handleCompareItemsChange">
+									<el-checkbox-group v-model="selectedCompareItems" @change="handleCompareItemsChange">
 										<div v-for="item in filteredCompareOptions" :key="item" class="mb-2">
 											<el-checkbox :label="item" size="small">{{ item }}</el-checkbox>
 										</div>
@@ -77,19 +88,32 @@
 					</el-popover>
 
 					<!-- 时段对比使用日期选择 -->
-					<el-popover v-if="industryCompare === 'time'" placement="bottom" trigger="click" width="300"
-						:visible="timeCompareVisible" :hide-after="0" :persistent="true">
+					<el-popover
+						v-if="industryCompare === 'time'"
+						placement="bottom"
+						trigger="click"
+						width="300"
+						:visible="timeCompareVisible"
+						:hide-after="0"
+						:persistent="true"
+					>
 						<template #reference>
-							<el-button class="ml-2"
-								@click="timeCompareVisible = !timeCompareVisible">{{ t('versionDistribution.version') }}</el-button>
+							<el-button class="ml-2" @click="timeCompareVisible = !timeCompareVisible">{{ t('versionDistribution.version') }}</el-button>
 						</template>
 						<template #default>
 							<div class="p-3">
 								<div class="mb-3">
 									<label class="text-sm font-medium mb-2 block">选择对比时段</label>
-									<el-date-picker v-model="timeCompareRange" type="date" format="YYYY-MM-DD"
-										value-format="YYYY-MM-DD" :disabled-date="disableAfterToday"
-										@change="handleTimeCompareChange" style="width: 100%" :clearable="false" />
+									<el-date-picker
+										v-model="timeCompareRange"
+										type="date"
+										format="YYYY-MM-DD"
+										value-format="YYYY-MM-DD"
+										:disabled-date="disableAfterToday"
+										@change="handleTimeCompareChange"
+										style="width: 100%"
+										:clearable="false"
+									/>
 								</div>
 							</div>
 						</template>
@@ -108,7 +132,7 @@
 				</div>
 			</div>
 
-			<div class="relative">
+			<div class="relative ml-[-50px]">
 				<div ref="lineChartRef" style="width: 100%; height: 320px"></div>
 			</div>
 		</div>
@@ -118,13 +142,12 @@
 			<div class="flex items-center justify-between mb-2">
 				<div class="flex">
 					<div v-if="selectedVersionLocal == ''" 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 v-model="queryDate">
+							<el-radio-button :label="0">今日</el-radio-button>
+							<el-radio-button :label="1">作日 </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="showDetail = !showDetail">
+					<div class="text-base font-medium cursor-pointer select-none ml-3 items-center flex text-[#167AF0]" @click="showDetail = !showDetail">
 						{{ showDetail ? '收起明细数据' : '展开明细数据' }}
 						<el-icon class="ml-2">
 							<ArrowDown v-if="showDetail" />
@@ -137,14 +160,32 @@
 					<el-button>导出</el-button>
 				</div>
 			</div>
-			<el-table v-if="showDetail" :data="pagedTableRows" border>
+			<el-table v-if="showDetail && queryDate === 1" :data="tableData" border>
 				<el-table-column prop="date" label="日期" align="center" min-width="140" />
-				<el-table-column prop="hyyh" label="启动次数" align="center" min-width="140" />
-				<el-table-column prop="ratio" label="启动次数(占比)" align="center" min-width="220"> </el-table-column>
+				<el-table-column prop="newUser" label="新增用户" align="center" min-width="140" />
+				<el-table-column prop="activeUser" label="活跃用户" align="center" min-width="140" />
+				<el-table-column prop="launch" label="启动次数" align="center" min-width="140" />
+				<el-table-column prop="upgradeUser" label="升级用户" align="center" min-width="140" />
+			</el-table>
+			<el-table v-else-if="showDetail && queryDate === 0" :data="tableData" border>
+				<el-table-column prop="version" label="版本" align="center" min-width="140" />
+				<el-table-column prop="newUser" label="截至至今版本累计用户(%)" align="center" min-width="140" />
+				<el-table-column prop="totalUser" label="累计用户" align="center" min-width="140" />
+				<el-table-column prop="newUser" label="新增用户" align="center" min-width="140" />
+				<el-table-column prop="activeUser" label="活跃用户" align="center" min-width="140" />
+				<el-table-column prop="launch" label="启动次数" align="center" min-width="140" />
+				<el-table-column prop="upgradeUser" label="升级用户" align="center" min-width="140" />
 			</el-table>
 			<div v-if="showDetail" class="flex justify-end mt-2">
-				<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]" />
+				<el-pagination
+					v-model:current-page="pagination.current"
+					v-model:page-size="pagination.size"
+					@change="getDataDetail"
+					background
+					layout="total, prev, pager, next, sizes"
+					:total="pagination.total"
+					:page-sizes="[5, 10, 20]"
+				/>
 			</div>
 		</div>
 	</div>
@@ -166,9 +207,9 @@ import { getTrend as getLaunchTrend } from '/@/api/count/activations';
 
 import { getTrendUpgrade, getTrendDetailAll, getTrendDetailOne, getTrendDetailSource, getTrendSource } from '/@/api/count/version';
 
-
 const { t } = useI18n();
 
+
 interface ChannelOption {
 	label: string;
 	value: string;
@@ -186,12 +227,14 @@ const props = defineProps<{
 	selectedVersion?: string;
 	channelOptions: ChannelOption[];
 	showAllVersions?: boolean;
-	formData?: {
+	formData: {
 		time?: string[];
 		version?: string;
 	};
 }>();
 
+const queryDate = ref(0);
+
 const emit = defineEmits<{
 	(e: 'update:selectedVersion', v: string): void;
 }>();
@@ -204,11 +247,14 @@ watch(
 	}
 );
 watch(selectedVersionLocal, (v) => emit('update:selectedVersion', v));
-watch(() => props.formData, (v) => {
-	formData.value.fromDate = v?.time && v.time[0] ? v.time[0] : '';
-	formData.value.toDate = v?.time && v.time[1] ? v.time[1] : '';
-
-})
+watch(
+	() => props.formData,
+	(v) => {
+		formData.value.fromDate = v?.time && v.time[0] ? v.time[0] : '';
+		formData.value.toDate = v?.time && v.time[1] ? v.time[1] : '';
+		formData.value.version = v?.version ? [v?.version] : [];
+	}
+);
 
 // 版本、渠道和时段对比相关状态
 const industryCompare = ref('');
@@ -251,8 +297,14 @@ const formData = ref({
 	channel: [] as string[],
 	fromDate: '',
 	toDate: '',
-	timeUnit: "day",
+	timeUnit: 'day',
+});
+const pagination = ref({
+	current: 1, //当前页数
+	total: 0, // 数据总数
+	size: 5, // 每页显示条数
 });
+const tableData = ref([]);
 
 const getData = async (type: string) => {
 	//上方图表
@@ -270,14 +322,12 @@ const getData = async (type: string) => {
 		toDate: props.formData?.time?.[1] || '',
 	};
 	console.log(formData.value);
-	
 
 	// 根据选择的类型调用不同的API
 	if (timeGranularity.value === 'addUser') {
 		res = await getAddUserTrend({ ...formData.value });
 	}
 	console.log(formData.value);
-	
 
 	// 根据选择的类型调用不同的API
 	if (timeGranularity.value === 'addUser') {
@@ -297,8 +347,58 @@ const getData = async (type: string) => {
 	initChartData(data);
 };
 
+// 为对比项添加数据到图表
+const addCompareData = async (item: string) => {
+	const compareFormData = {
+		...formData.value,
+		version: industryCompare.value === 'version' ? [item] : formData.value.version,
+		channel: industryCompare.value === 'channel' ? [item] : formData.value.channel,
+	};
+
+	let res;
+	let data;
+
+	// 根据选择的类型调用不同的API
+	if (timeGranularity.value === 'addUser') {
+		res = await getAddUserTrend(compareFormData);
+		data = res?.data || [];
+	} else if (timeGranularity.value === 'activeUser') {
+		res = await getActiveUserTrend(compareFormData);
+		data = res?.data || [];
+	} else if (timeGranularity.value === 'launchUser') {
+		res = await getLaunchTrend(compareFormData);
+		data = res?.data || [];
+	} else if (timeGranularity.value === 'upgradeUser') {
+		res = await getTrendUpgrade(compareFormData);
+		data = res?.data || [];
+	}
+
+	// 将新数据添加到图表中
+	if (data && data.items && data.items.length > 0) {
+		const newItem = data.items[0];
+		const randomColorIndex = Math.floor(Math.random() * colorSchemes.length);
+
+		const chartItem = {
+			name: getName(newItem, 0, data),
+			type: 'line',
+			smooth: true,
+			data: newItem.data,
+			itemStyle: {
+				color: colorSchemes[randomColorIndex].color,
+			},
+			lineStyle: {
+				color: colorSchemes[randomColorIndex].color,
+			},
+		};
+
+		lineChartData.value.items.push(chartItem);
+		initLineChart();
+	}
+};
+
 const initChartData = async (data: any) => {
 	if (!industryCompare.value) {
+		// 非对比模式:显示主数据
 		masterData.value = data;
 		lineChartData.value.items = data.items.map((item: any, index: number) => {
 			const randomColorIndex = Math.floor(Math.random() * colorSchemes.length);
@@ -317,31 +417,44 @@ const initChartData = async (data: any) => {
 		});
 		chartTimes.value = [];
 	} else {
-		lineChartData.value.items.push(
-			data.items.map((item: any, index: number) => {
-				const randomColorIndex = Math.floor(Math.random() * colorSchemes.length);
-				return {
-					name: getName(item, index, data),
-					type: 'line',
-					smooth: true,
-					data: item.data,
-					itemStyle: {
-						color: colorSchemes[randomColorIndex].color,
-					},
-					lineStyle: {
-						color: colorSchemes[randomColorIndex].color,
-					},
-				};
-			})[0]
-		);
+		// 对比模式:将新数据添加到现有图表中
+		if (data && data.items && data.items.length > 0) {
+			const newItem = data.items[0];
+			const randomColorIndex = Math.floor(Math.random() * colorSchemes.length);
+
+			const chartItem = {
+				name: getName(newItem, 0, data),
+				type: 'line',
+				smooth: true,
+				data: newItem.data,
+				itemStyle: {
+					color: colorSchemes[randomColorIndex].color,
+				},
+				lineStyle: {
+					color: colorSchemes[randomColorIndex].color,
+				},
+			};
+
+			lineChartData.value.items.push(chartItem);
+		}
+
 		if (industryCompare.value == 'time') {
 			chartTimes.value.push(data.dates);
-			lineChartData.value.items[0].name = formData.value.fromDate + ' ~ ' + formData.value.toDate;
+			if (lineChartData.value.items.length > 0) {
+				lineChartData.value.items[0].name = formData.value.fromDate + ' ~ ' + formData.value.toDate;
+			}
 		} else {
 			chartTimes.value = [];
 		}
 	}
-	lineChartData.value.dates = masterData.value.dates;
+
+	// 确保日期数据正确设置
+	if (masterData.value.dates && masterData.value.dates.length > 0) {
+		lineChartData.value.dates = masterData.value.dates;
+	} else if (data && data.dates) {
+		lineChartData.value.dates = data.dates;
+	}
+
 	initLineChart();
 };
 
@@ -364,6 +477,29 @@ function formatNumber(value: number | string): string {
 	const num = typeof value === 'number' ? value : Number(value || 0);
 	return num.toLocaleString('zh-CN');
 }
+const getDataDetail = async () => {
+	if (props.formData.version === '') {
+		const res = await getTrendDetailAll({
+			...formData.value,
+			...pagination.value,
+		});
+		const dataDetail = res?.data || [];
+		pagination.value.current = dataDetail.current; //当前页码
+		pagination.value.total = dataDetail.total; //总条数
+		pagination.value.size = dataDetail.size; //每页条数
+		tableData.value = dataDetail.records;
+	} else {
+		const res = await getTrendDetailOne({
+			...formData.value,
+			...pagination.value,
+		});
+		const dataDetail = res?.data || [];
+		pagination.value.current = dataDetail.current; //当前页码
+		pagination.value.total = dataDetail.total; //总条数
+		pagination.value.size = dataDetail.size; //每页条数
+		tableData.value = dataDetail.records;
+	}
+};
 
 const formatterTips = (params: any) => {
 	if (!params || !params.length) return '';
@@ -422,7 +558,7 @@ function initLineChart(): void {
 			type: 'scroll', // 支持图例滚动
 		},
 		grid: {
-			left: 40,
+			left: 100,
 			right: 20,
 			top: 20, // 为图例留出空间
 			bottom: 60,
@@ -451,37 +587,28 @@ onMounted(() => {
 });
 
 watch(timeGranularity, () => {
-	getData('');
+	getData('clearAll');
+});
+watch(queryDate, () => {
+	getDataDetail();
 });
-
 watch(selectedVersionLocal, () => {
 	industryCompare.value = '';
 	getData('');
 });
 
 // 监听showAllVersions变化,重新初始化对比选项
-watch(() => props.showAllVersions, () => {
-	if (industryCompare.value === 'version') {
-		initCompareOptions();
+watch(
+	() => props.showAllVersions,
+	() => {
+		if (industryCompare.value === 'version') {
+			initCompareOptions();
+		}
 	}
-});
-
-const tableRows = ref<TableRow[]>(
-	Array.from({ length: 42 }).map((_, idx) => ({
-		date: `2025-08-${String(11).padStart(2, '0')}`,
-		newUsers: 727,
-		hyyh: '115',
-		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);
-});
+
 
 // 版本和渠道对比相关函数
 function handleCompareChange(value: string) {
@@ -494,7 +621,29 @@ function clearCompare() {
 	selectedCompareItems.value = [];
 	timeCompareRange.value = '';
 	industryCompare.value = '';
-	getData('clearAll');
+
+	// 重置图表数据到主数据
+	if (masterData.value && masterData.value.items) {
+		lineChartData.value.items = masterData.value.items.map((item: any, index: number) => {
+			const randomColorIndex = Math.floor(Math.random() * colorSchemes.length);
+			return {
+				name: item.name,
+				type: 'line',
+				smooth: true,
+				data: item.data,
+				itemStyle: {
+					color: colorSchemes[randomColorIndex].color,
+				},
+				lineStyle: {
+					color: colorSchemes[randomColorIndex].color,
+				},
+			};
+		});
+		lineChartData.value.dates = masterData.value.dates;
+		initLineChart();
+	} else {
+		getData('clearAll');
+	}
 }
 
 function getCompareTitle(): string {
@@ -523,7 +672,7 @@ function filterCompareOptions() {
 	}
 }
 
-function handleCompareItemsChange(items: string[]) {
+async function handleCompareItemsChange(items: string[]) {
 	selectedCompareItems.value = items;
 
 	// 对比当前值和之前的值
@@ -563,16 +712,21 @@ function handleCompareItemsChange(items: string[]) {
 		// 从后往前删除,避免索引变化影响
 		removedItemIndices
 			.sort((a, b) => b - a)
-			.forEach(index => {
-				// 清理对应的图表数据
-				// 这里可以根据实际需要清理图表数据
+			.forEach((index) => {
+				// 删除对应的图表数据项(索引+1是因为第一个是主数据)
+				if (lineChartData.value.items.length > index + 1) {
+					lineChartData.value.items.splice(index + 1, 1);
+				}
 			});
+		initLineChart();
 	}
 
 	// 根据是否有新增项来决定是否重新获取数据
 	if (addedItems.length > 0) {
-		// 触发数据重新获取
-		getData('');
+		// 为每个新增项获取数据并添加到图表
+		for (const item of addedItems) {
+			await addCompareData(item);
+		}
 	} else if (removedItemIndices.length > 0) {
 		// 如果只是移除了项,则重新初始化图表
 		initLineChart();
@@ -603,7 +757,7 @@ function handleTimeCompareChange(value: string) {
 	timeCompareRange.value = value;
 	formData.value.toDate = timeCompareRange.value;
 	formData.value.fromDate = dayjs(timeCompareRange.value).subtract(7, 'day').format('YYYY-MM-DD');
-	formData.value.channel = props.channelOptions?.map(item => item.value) || [];
+	formData.value.channel = props.channelOptions?.map((item) => item.value) || [];
 	formData.value.version = selectedVersionLocal.value ? [selectedVersionLocal.value] : [];
 
 	getData('');

+ 1 - 0
src/views/count/user/versionDistribution/index.vue

@@ -46,6 +46,7 @@ const getDefaultDateRange = () => {
 };
 const formData = ref<Record<string, any>>({
 	time: getDefaultDateRange(), // 时间范围
+	version:''
 });
 
 

+ 4 - 0
src/views/marketing/config/components/push.vue

@@ -150,7 +150,11 @@ const formData = ref<any>({
 const appOptions = ref<any[]>([]);
 const getAppListData = async () => {
 	const res = await getAppList();
+	// console.log(res);
+	
 	const data = res.data;
+	console.log(data);
+	
 	appOptions.value = data.map((item: any, index: number) => {
 		return {
 			label: item.appName,