import { v4 as uuidv4 } from 'uuid'; import { executeQuery, executeQuerySingle } from './clickhouse'; // 时间粒度枚举 export enum TimeGranularity { HOUR = 'hour', DAY = 'day', WEEK = 'week', MONTH = 'month', } // 事件类型枚举 export enum EventType { CLICK = 'click', REDIRECT = 'redirect', CONVERSION = 'conversion', ERROR = 'error', } // 转化类型枚举 export enum ConversionType { VISIT = 'visit', STAY = 'stay', INTERACT = 'interact', SIGNUP = 'signup', SUBSCRIPTION = 'subscription', PURCHASE = 'purchase', } // 构建日期过滤条件 function buildDateFilter(startDate?: string, endDate?: string): string { let dateFilter = ''; if (startDate && endDate) { dateFilter = ` AND date >= '${startDate}' AND date <= '${endDate}'`; } else if (startDate) { dateFilter = ` AND date >= '${startDate}'`; } else if (endDate) { dateFilter = ` AND date <= '${endDate}'`; } return dateFilter; } /** * 获取链接概览数据 */ export async function getLinkOverview( linkId: string, startDate?: string, endDate?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); const query = ` SELECT count() as total_visits, uniq(visitor_id) as unique_visitors, avg(time_spent_sec) as average_time_spent, countIf(time_spent_sec < 10) as bounce_count, countIf(event_type = 'conversion') as conversion_count, uniq(referrer) as unique_referrers, countIf(device_type = 'mobile') as mobile_count, countIf(device_type = 'tablet') as tablet_count, countIf(device_type = 'desktop') as desktop_count, countIf(device_type = 'other') as other_count, countIf(is_qr_scan = true) as qr_scan_count, sum(conversion_value) as total_conversion_value FROM link_events WHERE link_id = '${linkId}' ${dateFilter} `; const result = await executeQuerySingle<{ total_visits: number; unique_visitors: number; average_time_spent: number; bounce_count: number; conversion_count: number; unique_referrers: number; mobile_count: number; tablet_count: number; desktop_count: number; other_count: number; qr_scan_count: number; total_conversion_value: number; }>(query); if (!result) { return { totalVisits: 0, uniqueVisitors: 0, averageTimeSpent: 0, bounceCount: 0, conversionCount: 0, uniqueReferrers: 0, deviceTypes: { mobile: 0, tablet: 0, desktop: 0, other: 0, }, qrScanCount: 0, totalConversionValue: 0, }; } // 将设备类型计数转换为字典 const deviceTypes = { mobile: Number(result.mobile_count), tablet: Number(result.tablet_count), desktop: Number(result.desktop_count), other: Number(result.other_count), }; return { totalVisits: Number(result.total_visits), uniqueVisitors: Number(result.unique_visitors), averageTimeSpent: Number(result.average_time_spent), bounceCount: Number(result.bounce_count), conversionCount: Number(result.conversion_count), uniqueReferrers: Number(result.unique_referrers), deviceTypes, qrScanCount: Number(result.qr_scan_count), totalConversionValue: Number(result.total_conversion_value), }; } catch (error) { console.error('获取链接概览数据失败', error); throw error; } } /** * 获取转化漏斗数据 */ export async function getConversionFunnel( linkId: string, startDate?: string, endDate?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); const query = ` SELECT countIf(conversion_type = 'visit') as visit_count, countIf(conversion_type = 'stay') as stay_count, countIf(conversion_type = 'interact') as interact_count, countIf(conversion_type = 'signup') as signup_count, countIf(conversion_type = 'subscription') as subscription_count, countIf(conversion_type = 'purchase') as purchase_count FROM link_events WHERE link_id = '${linkId}' AND event_type = 'conversion' ${dateFilter} `; const result = await executeQuerySingle<{ visit_count: number; stay_count: number; interact_count: number; signup_count: number; subscription_count: number; purchase_count: number; }>(query); if (!result) { return { steps: [ { name: 'Visit', value: 0, percent: 0 }, { name: 'Stay', value: 0, percent: 0 }, { name: 'Interact', value: 0, percent: 0 }, { name: 'Signup', value: 0, percent: 0 }, { name: 'Subscription', value: 0, percent: 0 }, { name: 'Purchase', value: 0, percent: 0 }, ], totalConversions: 0, conversionRate: 0, }; } // 计算总转化数 const totalConversions = Number(result.visit_count) + Number(result.stay_count) + Number(result.interact_count) + Number(result.signup_count) + Number(result.subscription_count) + Number(result.purchase_count); // 计算转化率 const conversionRate = totalConversions > 0 ? Number(result.purchase_count) / Number(result.visit_count) * 100 : 0; // 构建步骤数据 const steps = [ { name: 'Visit', value: Number(result.visit_count), percent: 100, }, { name: 'Stay', value: Number(result.stay_count), percent: result.visit_count > 0 ? (Number(result.stay_count) / Number(result.visit_count)) * 100 : 0, }, { name: 'Interact', value: Number(result.interact_count), percent: result.visit_count > 0 ? (Number(result.interact_count) / Number(result.visit_count)) * 100 : 0, }, { name: 'Signup', value: Number(result.signup_count), percent: result.visit_count > 0 ? (Number(result.signup_count) / Number(result.visit_count)) * 100 : 0, }, { name: 'Subscription', value: Number(result.subscription_count), percent: result.visit_count > 0 ? (Number(result.subscription_count) / Number(result.visit_count)) * 100 : 0, }, { name: 'Purchase', value: Number(result.purchase_count), percent: result.visit_count > 0 ? (Number(result.purchase_count) / Number(result.visit_count)) * 100 : 0, }, ]; return { steps, totalConversions, conversionRate, }; } catch (error) { console.error('获取转化漏斗数据失败', error); throw error; } } /** * 获取访问趋势数据 */ export async function getVisitTrends( linkId: string, startDate?: string, endDate?: string, granularity: TimeGranularity = TimeGranularity.DAY, ) { try { const dateFilter = buildDateFilter(startDate, endDate); const queryString = ` SELECT toStartOfInterval(event_time, INTERVAL 1 ${granularity}) as timestamp, count() as visits, uniq(visitor_id) as unique_visitors FROM link_events WHERE link_id = '${linkId}' ${dateFilter} GROUP BY timestamp ORDER BY timestamp `; const results = await executeQuery<{ timestamp: string; visits: number; unique_visitors: number; }>(queryString); // 计算总计 const totals = { visits: results.reduce((sum, item) => sum + Number(item.visits), 0), uniqueVisitors: results.reduce((sum, item) => sum + Number(item.unique_visitors), 0), }; // 格式化时间戳 const trends = results.map(item => ({ timestamp: formatTimestamp(item.timestamp, granularity), visits: Number(item.visits), uniqueVisitors: Number(item.unique_visitors), })); return { trends, totals, }; } catch (error) { console.error('获取访问趋势数据失败', error); throw error; } } /** * 追踪事件 */ export async function trackEvent(eventData: { linkId: string; eventType: EventType; visitorId?: string; sessionId?: string; referrer?: string; userAgent?: string; ipAddress?: string; timeSpent?: number; conversionType?: ConversionType; conversionValue?: number; customData?: Record; isQrScan?: boolean; qrCodeId?: string; utmSource?: string; utmMedium?: string; utmCampaign?: string; }) { try { // 检查必要字段 if (!eventData.linkId) { throw new Error('Missing required field: linkId'); } // 生成缺失的ID和时间戳 const eventId = uuidv4(); const timestamp = new Date().toISOString(); const visitorId = eventData.visitorId || uuidv4(); const sessionId = eventData.sessionId || uuidv4(); // 设置默认值 const isQrScan = !!eventData.isQrScan; const qrCodeId = eventData.qrCodeId || ''; const conversionValue = eventData.conversionValue || 0; const conversionType = eventData.conversionType || ConversionType.VISIT; const timeSpentSec = eventData.timeSpent || 0; // 准备插入数据 const insertQuery = ` INSERT INTO link_events ( event_id, event_time, link_id, visitor_id, session_id, event_type, ip_address, referrer, utm_source, utm_medium, utm_campaign, user_agent, time_spent_sec, is_qr_scan, qr_code_id, conversion_type, conversion_value, custom_data ) VALUES ( '${eventId}', '${timestamp}', '${eventData.linkId}', '${visitorId}', '${sessionId}', '${eventData.eventType}', '${eventData.ipAddress || ''}', '${eventData.referrer || ''}', '${eventData.utmSource || ''}', '${eventData.utmMedium || ''}', '${eventData.utmCampaign || ''}', '${eventData.userAgent || ''}', ${timeSpentSec}, ${isQrScan}, '${qrCodeId}', '${conversionType}', ${conversionValue}, '${JSON.stringify(eventData.customData || {})}' ) `; await executeQuery(insertQuery); return { success: true, eventId, timestamp, }; } catch (error) { console.error('事件追踪失败', error); throw error; } } /** * 格式化时间戳 */ function formatTimestamp(timestamp: string, granularity: TimeGranularity): string { const date = new Date(timestamp); switch (granularity) { case TimeGranularity.HOUR: return `${date.toISOString().substring(0, 13)}:00`; case TimeGranularity.DAY: return date.toISOString().substring(0, 10); case TimeGranularity.WEEK: { const firstDayOfYear = new Date(date.getFullYear(), 0, 1); const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000; const weekNum = Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); return `${date.getFullYear()}-W${weekNum.toString().padStart(2, '0')}`; } case TimeGranularity.MONTH: return date.toISOString().substring(0, 7); default: return date.toISOString().substring(0, 10); } } /** * 获取链接表现数据 */ export async function getLinkPerformance( linkId: string, startDate?: string, endDate?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); const query = ` SELECT count() as total_clicks, uniq(visitor_id) as unique_visitors, avg(time_spent_sec) as average_time_spent, countIf(time_spent_sec < 10) as bounce_count, uniq(referrer) as unique_referrers, countIf(event_type = 'conversion' AND conversion_type = 'purchase') as conversion_count, count(DISTINCT DATE(event_time)) as active_days, max(event_time) as last_click_time, countIf(device_type = 'mobile') as mobile_clicks, countIf(device_type = 'desktop') as desktop_clicks FROM link_events WHERE link_id = '${linkId}' ${dateFilter} `; const result = await executeQuerySingle<{ total_clicks: number; unique_visitors: number; average_time_spent: number; bounce_count: number; unique_referrers: number; conversion_count: number; active_days: number; last_click_time: string; mobile_clicks: number; desktop_clicks: number; }>(query); if (!result) { return { totalClicks: 0, uniqueVisitors: 0, averageTimeSpent: 0, bounceRate: 0, uniqueReferrers: 0, conversionRate: 0, activeDays: 0, lastClickTime: null, deviceDistribution: { mobile: 0, desktop: 0, }, }; } // 计算跳出率 const bounceRate = result.total_clicks > 0 ? (result.bounce_count / result.total_clicks) * 100 : 0; // 计算转化率 const conversionRate = result.unique_visitors > 0 ? (result.conversion_count / result.unique_visitors) * 100 : 0; return { totalClicks: Number(result.total_clicks), uniqueVisitors: Number(result.unique_visitors), averageTimeSpent: Number(result.average_time_spent), bounceRate: Number(bounceRate.toFixed(2)), uniqueReferrers: Number(result.unique_referrers), conversionRate: Number(conversionRate.toFixed(2)), activeDays: Number(result.active_days), lastClickTime: result.last_click_time, deviceDistribution: { mobile: Number(result.mobile_clicks), desktop: Number(result.desktop_clicks), }, }; } catch (error) { console.error('获取链接表现数据失败', error); throw error; } } /** * 获取平台分布数据 */ export async function getPlatformDistribution( startDate?: string, endDate?: string, linkId?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建链接过滤条件 let linkFilter = ''; if (linkId) { linkFilter = ` AND link_id = '${linkId}'`; } const query = ` SELECT os, browser, count() as visit_count FROM link_events WHERE 1=1 ${dateFilter} ${linkFilter} GROUP BY os, browser ORDER BY visit_count DESC `; const results = await executeQuery<{ os: string; browser: string; visit_count: number; }>(query); // 平台统计 const platforms: { [key: string]: number } = {}; // 浏览器统计 const browsers: { [key: string]: number } = {}; // 计算总访问量 const totalVisits = results.reduce((sum, item) => sum + Number(item.visit_count), 0); // 处理平台和浏览器数据 for (const item of results) { const platform = item.os || 'unknown'; const browser = item.browser || 'unknown'; const count = Number(item.visit_count); // 累加平台数据 platforms[platform] = (platforms[platform] || 0) + count; // 累加浏览器数据 browsers[browser] = (browsers[browser] || 0) + count; } // 计算百分比并格式化结果 const platformData = Object.entries(platforms).map(([name, count]) => ({ name, count: Number(count), percent: totalVisits > 0 ? Number(((count / totalVisits) * 100).toFixed(1)) : 0, })); const browserData = Object.entries(browsers).map(([name, count]) => ({ name, count: Number(count), percent: totalVisits > 0 ? Number(((count / totalVisits) * 100).toFixed(1)) : 0, })); // 按访问量排序 platformData.sort((a, b) => b.count - a.count); browserData.sort((a, b) => b.count - a.count); return { totalVisits: totalVisits, platforms: platformData, browsers: browserData, }; } catch (error) { console.error('获取平台分布数据失败', error); throw error; } } /** * 获取链接状态分布数据 */ export async function getLinkStatusDistribution( startDate?: string, endDate?: string, projectId?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建项目过滤条件 let projectFilter = ''; if (projectId) { projectFilter = ` AND project_id = '${projectId}'`; } const query = ` SELECT is_active, count() as link_count FROM links WHERE 1=1 ${dateFilter} ${projectFilter} GROUP BY is_active `; const results = await executeQuery<{ is_active: boolean; link_count: number; }>(query); // 初始化数据 let activeCount = 0; let inactiveCount = 0; // 处理查询结果 for (const item of results) { if (item.is_active) { activeCount = Number(item.link_count); } else { inactiveCount = Number(item.link_count); } } // 计算总数 const totalLinks = activeCount + inactiveCount; // 计算百分比 const activePercent = totalLinks > 0 ? (activeCount / totalLinks) * 100 : 0; const inactivePercent = totalLinks > 0 ? (inactiveCount / totalLinks) * 100 : 0; // 构建状态分布数据 const statusDistribution = [ { status: 'active', count: activeCount, percent: Number(activePercent.toFixed(1)), }, { status: 'inactive', count: inactiveCount, percent: Number(inactivePercent.toFixed(1)), }, ]; return { totalLinks, statusDistribution, }; } catch (error) { console.error('获取链接状态分布数据失败', error); throw error; } } /** * 获取设备分析详情 */ export async function getDeviceAnalysis( startDate?: string, endDate?: string, linkId?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建链接过滤条件 let linkFilter = ''; if (linkId) { linkFilter = ` AND link_id = '${linkId}'`; } const query = ` SELECT device_type, count() as visit_count FROM link_events WHERE 1=1 ${dateFilter} ${linkFilter} GROUP BY device_type ORDER BY visit_count DESC `; const results = await executeQuery<{ device_type: string; visit_count: number; }>(query); // 设备类型统计 const deviceTypes: { [key: string]: number } = {}; // 计算总访问量 const totalVisits = results.reduce((sum, item) => sum + Number(item.visit_count), 0); // 处理设备数据 for (const item of results) { const type = item.device_type || 'unknown'; const count = Number(item.visit_count); // 累加类型数据 deviceTypes[type] = (deviceTypes[type] || 0) + count; } // 计算百分比并格式化类型结果 const typeData = Object.entries(deviceTypes).map(([name, count]) => ({ name, count: Number(count), percent: totalVisits > 0 ? Number(((count / totalVisits) * 100).toFixed(1)) : 0, })); // 排序类型数据 typeData.sort((a, b) => b.count - a.count); return { totalVisits, deviceTypes: typeData, deviceBrands: [], // 返回空数组,因为数据库中没有设备品牌信息 deviceModels: [], // 返回空数组,因为数据库中没有设备型号信息 }; } catch (error) { console.error('获取设备分析详情失败', error); throw error; } } /** * 获取热门链接数据 */ export async function getPopularLinks( startDate?: string, endDate?: string, projectId?: string, sortBy: 'visits' | 'uniqueVisitors' | 'conversionRate' = 'visits', limit: number = 10, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建项目过滤条件 let projectFilter = ''; if (projectId) { projectFilter = ` AND l.project_id = '${projectId}'`; } // 根据排序字段构建ORDER BY子句 let orderBy = ''; switch (sortBy) { case 'visits': orderBy = 'ORDER BY total_visits DESC'; break; case 'uniqueVisitors': orderBy = 'ORDER BY unique_visitors DESC'; break; case 'conversionRate': orderBy = 'ORDER BY conversion_rate DESC'; break; default: orderBy = 'ORDER BY total_visits DESC'; } const query = ` SELECT l.link_id, l.original_url, l.title, l.is_active, count() as total_visits, uniq(e.visitor_id) as unique_visitors, countIf(e.event_type = 'conversion' AND e.conversion_type = 'purchase') as conversion_count, countIf(e.time_spent_sec < 10) as bounce_count FROM links l JOIN link_events e ON l.link_id = e.link_id WHERE 1=1 ${dateFilter} ${projectFilter} GROUP BY l.link_id, l.original_url, l.title, l.is_active ${orderBy} LIMIT ${limit} `; const results = await executeQuery<{ link_id: string; original_url: string; title: string; is_active: boolean; total_visits: number; unique_visitors: number; conversion_count: number; bounce_count: number; }>(query); // 处理查询结果 const links = results.map(link => { const totalVisits = Number(link.total_visits); const uniqueVisitors = Number(link.unique_visitors); const conversionCount = Number(link.conversion_count); const bounceCount = Number(link.bounce_count); // 计算转化率 const conversionRate = uniqueVisitors > 0 ? (conversionCount / uniqueVisitors) * 100 : 0; // 计算跳出率 const bounceRate = totalVisits > 0 ? (bounceCount / totalVisits) * 100 : 0; return { id: link.link_id, url: link.original_url, title: link.title || '无标题', isActive: link.is_active, totalVisits, uniqueVisitors, conversionCount, conversionRate: Number(conversionRate.toFixed(2)), bounceCount, bounceRate: Number(bounceRate.toFixed(2)), }; }); return { links, totalCount: links.length, }; } catch (error) { console.error('获取热门链接数据失败', error); throw error; } } /** * 获取热门引荐来源数据 */ export async function getPopularReferrers( startDate?: string, endDate?: string, linkId?: string, type: 'domain' | 'full' = 'domain', limit: number = 10, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建链接过滤条件 let linkFilter = ''; if (linkId) { linkFilter = ` AND link_id = '${linkId}'`; } // 决定是按域名还是完整URL分组 const groupByField = type === 'domain' ? 'domain(referrer)' : 'referrer'; const query = ` SELECT ${groupByField} as source, count() as visit_count, uniq(visitor_id) as unique_visitors, countIf(event_type = 'conversion' AND conversion_type = 'purchase') as conversion_count, avg(time_spent_sec) as average_time_spent FROM link_events WHERE referrer != '' ${dateFilter} ${linkFilter} GROUP BY ${groupByField} ORDER BY visit_count DESC LIMIT ${limit} `; const results = await executeQuery<{ source: string; visit_count: number; unique_visitors: number; conversion_count: number; average_time_spent: number; }>(query); // 计算总访问量 const totalVisits = results.reduce((sum, item) => sum + Number(item.visit_count), 0); // 处理查询结果 const referrers = results.map(referrer => { const visitCount = Number(referrer.visit_count); const uniqueVisitors = Number(referrer.unique_visitors); const conversionCount = Number(referrer.conversion_count); // 计算转化率 const conversionRate = uniqueVisitors > 0 ? (conversionCount / uniqueVisitors) * 100 : 0; // 计算百分比 const percent = totalVisits > 0 ? (visitCount / totalVisits) * 100 : 0; return { source: referrer.source || '(direct)', visitCount, uniqueVisitors, conversionCount, conversionRate: Number(conversionRate.toFixed(2)), averageTimeSpent: Number(referrer.average_time_spent), percent: Number(percent.toFixed(1)), }; }); return { referrers, totalVisits, }; } catch (error) { console.error('获取热门引荐来源数据失败', error); throw error; } } /** * 获取QR码分析数据 */ export async function getQrCodeAnalysis( startDate?: string, endDate?: string, linkId?: string, qrCodeId?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建过滤条件 let filters = ' AND is_qr_scan = true'; if (linkId) { filters += ` AND link_id = '${linkId}'`; } if (qrCodeId) { filters += ` AND qr_code_id = '${qrCodeId}'`; } // 查询QR码扫描基本指标 const basicQuery = ` SELECT count() as total_scans, uniq(visitor_id) as unique_scanners, countIf(event_type = 'conversion' AND conversion_type = 'purchase') as conversion_count, avg(time_spent_sec) as average_time_spent FROM link_events WHERE 1=1 ${dateFilter} ${filters} `; const basicResult = await executeQuerySingle<{ total_scans: number; unique_scanners: number; conversion_count: number; average_time_spent: number; }>(basicQuery); // 查询QR码扫描的位置分布 const locationQuery = ` SELECT city, country, count() as scan_count FROM link_events WHERE 1=1 ${dateFilter} ${filters} GROUP BY city, country ORDER BY scan_count DESC LIMIT 10 `; const locationResults = await executeQuery<{ city: string; country: string; scan_count: number; }>(locationQuery); // 查询QR码扫描设备分布 const deviceQuery = ` SELECT device_type, count() as scan_count FROM link_events WHERE 1=1 ${dateFilter} ${filters} GROUP BY device_type ORDER BY scan_count DESC `; const deviceResults = await executeQuery<{ device_type: string; scan_count: number; }>(deviceQuery); // 查询QR码扫描时间分布 const timeQuery = ` SELECT toHour(event_time) as hour, count() as scan_count FROM link_events WHERE 1=1 ${dateFilter} ${filters} GROUP BY hour ORDER BY hour `; const timeResults = await executeQuery<{ hour: number; scan_count: number; }>(timeQuery); // 计算基本指标 const totalScans = Number(basicResult?.total_scans || 0); const uniqueScanners = Number(basicResult?.unique_scanners || 0); const conversionCount = Number(basicResult?.conversion_count || 0); // 计算转化率 const conversionRate = uniqueScanners > 0 ? (conversionCount / uniqueScanners) * 100 : 0; // 处理位置数据 const locations = locationResults.map(loc => ({ city: loc.city || 'Unknown', country: loc.country || 'Unknown', scanCount: Number(loc.scan_count), percent: totalScans > 0 ? Number(((Number(loc.scan_count) / totalScans) * 100).toFixed(1)) : 0, })); // 处理设备类型数据 const deviceCounts: { [key: string]: number } = {}; for (const device of deviceResults) { const type = device.device_type || 'unknown'; deviceCounts[type] = Number(device.scan_count); } // 计算设备分布的百分比 const totalDeviceCount = Object.values(deviceCounts).reduce((sum, count) => sum + count, 0); const deviceDistribution = Object.entries(deviceCounts).map(([type, count]) => ({ type, count, percent: totalDeviceCount > 0 ? Number(((count / totalDeviceCount) * 100).toFixed(1)) : 0, })); // 排序设备分布 deviceDistribution.sort((a, b) => b.count - a.count); // 处理时间分布数据 const hourlyDistribution = Array.from({ length: 24 }, (_, i) => ({ hour: i, scanCount: 0, percent: 0 })); for (const time of timeResults) { const hour = Number(time.hour); const count = Number(time.scan_count); if (hour >= 0 && hour < 24) { hourlyDistribution[hour].scanCount = count; hourlyDistribution[hour].percent = totalScans > 0 ? (count / totalScans) * 100 : 0; } } return { overview: { totalScans, uniqueScanners, conversionCount, conversionRate: Number(conversionRate.toFixed(2)), averageTimeSpent: Number(basicResult?.average_time_spent || 0), }, locations, deviceDistribution, hourlyDistribution, }; } catch (error) { console.error('获取QR码分析数据失败', error); throw error; } } /** * 获取概览卡片数据 */ export async function getOverviewCards( startDate?: string, endDate?: string, projectId?: string, ) { try { const dateFilter = buildDateFilter(startDate, endDate); // 构建项目过滤条件 let projectFilter = ''; if (projectId) { projectFilter = ` AND l.project_id = '${projectId}'`; } // 获取当前周期的数据 const currentQuery = ` SELECT count(DISTINCT e.link_id) as total_links, count() as total_visits, uniq(e.visitor_id) as unique_visitors, countIf(e.event_type = 'conversion' AND e.conversion_type = 'purchase') as total_conversions, sum(e.conversion_value) as total_revenue, countIf(l.is_active = true) as active_links FROM link_events e JOIN links l ON e.link_id = l.link_id WHERE 1=1 ${dateFilter} ${projectFilter} `; const currentResult = await executeQuerySingle<{ total_links: number; total_visits: number; unique_visitors: number; total_conversions: number; total_revenue: number; active_links: number; }>(currentQuery); // 计算前一时期的日期范围 let previousStartDate = ''; let previousEndDate = ''; if (startDate && endDate) { const start = new Date(startDate); const end = new Date(endDate); const duration = end.getTime() - start.getTime(); const prevStart = new Date(start.getTime() - duration); const prevEnd = new Date(end.getTime() - duration); previousStartDate = prevStart.toISOString().split('T')[0]; previousEndDate = prevEnd.toISOString().split('T')[0]; } // 获取前一时期的数据 let previousResult = null; if (previousStartDate && previousEndDate) { const previousDateFilter = buildDateFilter(previousStartDate, previousEndDate); const previousQuery = ` SELECT count(DISTINCT e.link_id) as total_links, count() as total_visits, uniq(e.visitor_id) as unique_visitors, countIf(e.event_type = 'conversion' AND e.conversion_type = 'purchase') as total_conversions, sum(e.conversion_value) as total_revenue, countIf(l.is_active = true) as active_links FROM link_events e JOIN links l ON e.link_id = l.link_id WHERE 1=1 ${previousDateFilter} ${projectFilter} `; previousResult = await executeQuerySingle<{ total_links: number; total_visits: number; unique_visitors: number; total_conversions: number; total_revenue: number; active_links: number; }>(previousQuery); } // 计算同比变化 function calculateChange(current: number, previous: number): number { if (previous === 0) return 0; return Number(((current - previous) / previous * 100).toFixed(1)); } // 获取当前值,并设置默认值 const currentTotalLinks = Number(currentResult?.total_links || 0); const currentTotalVisits = Number(currentResult?.total_visits || 0); const currentUniqueVisitors = Number(currentResult?.unique_visitors || 0); const currentTotalConversions = Number(currentResult?.total_conversions || 0); const currentTotalRevenue = Number(currentResult?.total_revenue || 0); const currentActiveLinks = Number(currentResult?.active_links || 0); // 获取前一时期的值,并设置默认值 const previousTotalLinks = Number(previousResult?.total_links || 0); const previousTotalVisits = Number(previousResult?.total_visits || 0); const previousUniqueVisitors = Number(previousResult?.unique_visitors || 0); const previousTotalConversions = Number(previousResult?.total_conversions || 0); const previousTotalRevenue = Number(previousResult?.total_revenue || 0); const previousActiveLinks = Number(previousResult?.active_links || 0); // 计算转化率 const currentConversionRate = currentUniqueVisitors > 0 ? (currentTotalConversions / currentUniqueVisitors) * 100 : 0; const previousConversionRate = previousUniqueVisitors > 0 ? (previousTotalConversions / previousUniqueVisitors) * 100 : 0; // 计算活跃链接百分比 const currentActivePercentage = currentTotalLinks > 0 ? (currentActiveLinks / currentTotalLinks) * 100 : 0; const previousActivePercentage = previousTotalLinks > 0 ? (previousActiveLinks / previousTotalLinks) * 100 : 0; // 构建结果 return { cards: [ { title: '总访问量', currentValue: currentTotalVisits, previousValue: previousTotalVisits, change: calculateChange(currentTotalVisits, previousTotalVisits), format: 'number', }, { title: '独立访客', currentValue: currentUniqueVisitors, previousValue: previousUniqueVisitors, change: calculateChange(currentUniqueVisitors, previousUniqueVisitors), format: 'number', }, { title: '转化次数', currentValue: currentTotalConversions, previousValue: previousTotalConversions, change: calculateChange(currentTotalConversions, previousTotalConversions), format: 'number', }, { title: '总收入', currentValue: currentTotalRevenue, previousValue: previousTotalRevenue, change: calculateChange(currentTotalRevenue, previousTotalRevenue), format: 'currency', }, { title: '转化率', currentValue: Number(currentConversionRate.toFixed(1)), previousValue: Number(previousConversionRate.toFixed(1)), change: calculateChange(currentConversionRate, previousConversionRate), format: 'percent', }, { title: '活跃链接率', currentValue: Number(currentActivePercentage.toFixed(1)), previousValue: Number(previousActivePercentage.toFixed(1)), change: calculateChange(currentActivePercentage, previousActivePercentage), format: 'percent', }, ], timeRange: { current: { startDate: startDate || '', endDate: endDate || '', }, previous: { startDate: previousStartDate, endDate: previousEndDate, }, }, }; } catch (error) { console.error('获取概览卡片数据失败', error); throw error; } }