'UNSPECIFIED', // UNSPECIFIED 1 => 'UNKNOWN', // UNKNOWN 2 => 'ENABLED', // ENABLED 3 => 'PAUSED', // PAUSED 4 => 'REMOVED', // REMOVED 5 => 'FROZEN', // FROZEN 6 => 'ARCHIVED', // ARCHIVED ]; // 状态映射数组 private static $platformMapping = [ 1 => 'meta', 2 => 'google', 3 => 'tiktok', ]; // 状态映射数组 private static $creativeTypeMapping = [ 1 => 'image', 2 => 'video', ]; // Redis 键名前缀 const REDIS_KEY_PREFIX = 'ad_data_count:'; /** * 获取广告数据的各项总数(统一接口) * * @param array $customerIds 客户 ID 数组 * @return array 各项 count 统计数据 */ public static function getAdCountData($merchantId, $storeId, $customerIds, $startDate, $endDate) { if (empty($customerIds)) { return []; } // 生成唯一的哈希键 $hashKey = self::generateHashKey($customerIds); $redisKey = self::REDIS_KEY_PREFIX . $hashKey; // 尝试从 Redis 中获取缓存值 $cachedData = Redis::get($redisKey); if (!empty($cachedData)) { return json_decode($cachedData, true); } // 没有缓存时重新计算 $countData = self::calculateCountData($merchantId, $storeId, $customerIds, $startDate, $endDate); // 缓存到 Redis,有效期 10 分钟 Redis::setex($redisKey, 600, json_encode($countData)); return $countData; } /** * 计算广告数据的 count */ protected static function calculateCountData($merchantId, $storeId, $customerIds, $startDate, $endDate) { if (!$startDate || !$endDate) { [$startDate, $endDate] = self::getLastWeekDateRange(); } return [ 'account_list_count' => self::getAccountListCount($merchantId, $storeId, $customerIds, $startDate, $endDate), 'campaign_list_count' => self::getCampaignListCount($customerIds, $startDate, $endDate), 'adset_list_count' => self::getAdsetListCount($customerIds, $startDate, $endDate), 'ad_list_count' => self::getAdListCount($customerIds, $startDate, $endDate), 'creative_list_count' => self::getCreativeListCount($customerIds, $startDate, $endDate), ]; } /** * 生成 Redis 键的唯一哈希值 */ protected static function generateHashKey(array $customerIds) { sort($customerIds); return md5(json_encode($customerIds)); } /** * 获取广告列表的 count */ protected static function getAdListCount(array $customerIds, $startDate, $endDate) { return self::getAdList(0, $customerIds, 1, 1, '', $startDate, $endDate, 0, false, true); } /** * 获取广告系列列表的 count */ protected static function getCampaignListCount(array $customerIds, $startDate, $endDate) { return self::getCampaignList(0, $customerIds, 1, 1, '', $startDate, $endDate, 0, false, true); } /** * 获取广告组列表的 count */ protected static function getAdsetListCount(array $customerIds, $startDate, $endDate) { return self::getAdsetList(0, $customerIds, 1, 1, '', $startDate, $endDate, 0, false, true); } /** * 获取账户列表的 count */ protected static function getAccountListCount($merchantId, $storeId, $customerIds, $startDate, $endDate) { return self::getAccountList(0, $merchantId, $storeId, $customerIds, 1, 1, '', $startDate, $endDate, false, true); } /** * 获取账户列表的 count */ protected static function getCreativeListCount(array $customerIds, $startDate, $endDate) { return self::getCreativeInsightData(0, $customerIds, 1, 1, '', $startDate, $endDate, false, true); } /** * 获取最近 7 天的起始和结束日期 */ protected static function getLastWeekDateRange() { $endDate = (int)date('Ymd'); $startDate = (int)date('Ymd', strtotime('-6 days')); return [$startDate, $endDate]; } /** * 获取广告系列列表 */ public static function getCampaignList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false, $countOnly = false) { // 检查 customerIds 是否为空,直接返回空结构 if (empty($customerIds)) { return $countOnly ? 0 : [ 'pagination' => [ 'startIndex' => 0, 'maxResults' => 0, 'count' => 0, 'pageNo' => $page, 'pageSize' => $pageSize, 'pages' => 0, ], 'statistics' => [], 'data' => [], ]; } // 基础查询:广告活动和日数据表联接 $query = BpsAdCampaign::alias('c') ->cache(false) // 强制不使用缓存 ->where('c.account_id', 'in', $customerIds); // 如果只需要记录条数,执行计数查询 if ($countOnly) { return $query->count(); } // 动态构建日期条件 $dateCondition = ''; if ($startDate && $endDate) { $dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'"; } $query->leftJoin('bps.bps_ads_insights d', "c.campaign_id = d.ad_campaign_id AND c.platform_type = d.platform AND {$dateCondition}") ->field('c.campaign_id, c.status as status, c.name, c.account_id,c.platform_type, COALESCE(SUM(d.clicks), 0) as clicks, COALESCE(SUM(d.spend) / 1000000, 0) as spend, COALESCE(SUM(d.impressions), 0) as impressions, COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart, COALESCE(SUM(d.purchases), 0) as purchases, COALESCE(SUM(d.platform_purchase), 0) as platform_purchase, COALESCE(SUM(d.pixel_purchase), 0) as pixel_purchase, COALESCE(SUM(d.platform_purchase_value / 1000000), 0) as purchases_value, COALESCE(SUM(d.platform_purchase_value / 1000000), 0) as revenue, COALESCE(SUM(d.total_cost / 1000000), 0) as total_cost, -1 as conversion_rate, -1 as roas, -1 as ctr,-1 as net_profit,-1 as net_profit_margin,-1 as net_profit_on_ad_spend') ->group('c.campaign_id, c.status, c.account_id, c.name,c.platform_type'); // ->where('c.account_id', 'in', $customerIds); // 添加 customerIds 条件 // 根据 $requireSpend 参数决定是否添加 SUM(d.spend) > 0 的条件 if ($requireSpend) { $query->having('SUM(d.spend) > 0'); } if ($status !== 0) { $query->where('c.status', '=', $status); } // 添加关键字过滤条件 $query->where(function ($query) use ($keyword, $platformType) { if ($keyword) { $query->whereRaw('LOWER(c.name) LIKE ?', ['%' . strtolower($keyword) . '%']); } if ($platformType) { $platformType = (int)$platformType; $query->where('c.platform_type', '=', $platformType); } }); $query->order('c.platform_type,c.account_id,c.status', 'asc'); // 获取所有符合条件的数据(不分页) $allCampaigns = $query->select()->toArray(); $total_spend = array_sum(array_column($allCampaigns, 'spend')); $total_cost = array_sum(array_column($allCampaigns, 'total_cost')); $total_impressions = array_sum(array_column($allCampaigns, 'impressions')); $total_clicks = array_sum(array_column($allCampaigns, 'clicks')); $total_purchases_value = array_sum(array_column($allCampaigns, 'purchases_value')); $total_revenue = array_sum(array_column($allCampaigns, 'revenue')); $total_purchases = array_sum(array_column($allCampaigns, 'purchases')); // $total_platform_purchase = array_sum(array_column($allCampaigns, 'platform_purchase')); // $total_pixel_purchase = array_sum(array_column($allCampaigns, 'pixel_purchase')); $adds_to_cart = array_sum(array_column($allCampaigns, 'adds_to_cart')); $cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2); // 汇总统计数据 $statistics = [ 'platform_purchase' => number_format(array_sum(array_column($allCampaigns, 'platform_purchase'))), 'pixel_purchase' => number_format(array_sum(array_column($allCampaigns, 'pixel_purchase'))), 'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'x', 'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出 'clicks' => number_format($total_clicks), 'impressions' => number_format($total_impressions), 'adds_to_cart' => number_format($adds_to_cart), 'cost_per_atc' => $adds_to_cart == 0 ? '-' : '$' . number_format(($total_spend / $adds_to_cart), 2), 'purchases' => number_format($total_purchases), 'purchases_value' => array_sum(array_column($allCampaigns, 'purchases_value')), 'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00', 'revenue' => '$' . number_format(array_sum(array_column($allCampaigns, 'revenue')), 2) ?: '$0.00', // 格式化收入 'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本 'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率 'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format(abs($total_revenue - $total_cost), 2), // 净利润 'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率 'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2) * 100 . '%', // 广告支出净利润 // 计算总的 CTR 'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比 ]; // 获取分页数据 // $campaigns = $query->paginate($pageSize, false, ['page' => $page]); // 获取分页数据 $page = max(1, (int)$page); // 确保页码不小于 1 // $ads = $query->page($page, $pageSize)->select(); $campaigns = $query->limit(($page - 1) * $pageSize, $pageSize)->select(); $accountIds = array_unique(array_column($campaigns->toArray(), 'account_id')); $accountNames = BpsAdsMerchantRelation::whereIn('account_id', $accountIds) ->column('account_name', 'account_id'); // 确保数据格式统一 $result = array_map(function ($item) use ($accountNames){ // CTR 的计算:点击率 = 点击数 / 展示数 $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // Conversion Rate 的计算:转换率 = 购买数 / 点击数 $conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-'; // Net Profit 的计算:净利润 = 收入 - 总成本 $net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format(abs($item['revenue'] - $item['total_cost']), 2); // Net Profit Margin 的计算:净利润率 = 净利润 / 收入 $net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-'; // Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出 $net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) * 100 . '%' : '-'; return [ 'id' => $item['campaign_id'], 'platform_type' => $item['platform_type'], 'account_id' => $item['account_id'], // 映射为 customer_id 'account_name' => $accountNames[$item['account_id']] ?? '-', // 新增字段 'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-' 'status' => $item['status'], 'platform_purchase' => number_format($item['platform_purchase']), 'pixel_purchase' => number_format($item['pixel_purchase']), 'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'x', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => number_format($item['impressions']), 'clicks' => number_format($item['clicks']), 'ctr' => $ctr, // CTR 字段 'adds_to_cart' => number_format($item['adds_to_cart']), 'cost_per_atc' => $item['adds_to_cart'] > 0 ? '$' . number_format(($item['spend'] / $item['adds_to_cart']), 2) : '-', 'purchases' => number_format($item['purchases']), 'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额 'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '-', 'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入 'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本 'conversion_rate' => $conversion_rate, 'net_profit' => $net_profit, 'net_profit_margin' => $net_profit_margin, 'net_profit_on_ad_spend' => $net_profit_on_ad_spend, ]; }, $campaigns->toArray()); // Pagination 数据 $pagination = [ 'startIndex' => ($page - 1) * $pageSize + 1, // 确保索引从 1 开始 'maxResults' => count($campaigns), // 当前页实际返回数据数量 'count' => count($allCampaigns), // 符合条件的总记录数 'pageNo' => $page, // 当前页码 'pageSize' => $pageSize, // 每页条数 'pages' => (int)ceil(count($allCampaigns) / $pageSize), // 总页数 ]; return [ 'pagination' => $pagination, 'statistics' => $statistics, 'data' => $result, ]; } /** * 导出广告系列列表到 Excel * * @param int $platformType 平台类型 * @param array $customerIds 客户ID列表 * @param int $page 当前页码 * @param int $pageSize 每页条数 * @param string $keyword 关键字 * @param string|null $startDate 开始日期 * @param string|null $endDate 结束日期 * @param int $status 广告系列状态 */ public static function exportCampaignsToExcel($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false) { // 调用 getCampaignList 获取广告系列数据 $campaignList = self::getCampaignList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate, $endDate, $status, $requireSpend); if (empty($campaignList['data'])) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } // 创建 Spreadsheet 对象 $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头 $headers = [ 'A1' => 'Ad Campaigns', 'B1' => 'Status', 'C1' => 'Ad Platform', // 修正为 Ad Platform 'D1' => 'Ad Accounts', // 修正为 Ad Accounts 'E1' => 'Platform Purchases', 'F1' => 'Best Pixel Purchases', 'G1' => 'ROAS', 'H1' => 'Amount Spent', 'I1' => 'Impressions', 'J1' => 'Clicks', 'K1' => 'Click Through Rate', 'L1' => 'Adds to Cart', 'M1' => 'Cost per ATC', 'N1' => 'Purchases', 'O1' => 'Purchases Value', 'P1' => 'Cost per Purchase', 'Q1' => 'Conversion Rate', 'R1' => 'Revenue', 'S1' => 'Total Cost', 'T1' => 'Net Profit', 'U1' => 'Net Profit Margin', 'V1' => 'Net Profit on Ad Spend' // 新增 V 列 ]; foreach ($headers as $cell => $header) { $sheet->setCellValue($cell, $header); } // 填充数据 $row = 2; foreach ($campaignList['data'] as $campaign) { // $sheet->setCellValueExplicit('A' . $row, (string)$campaign['campaign_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('A' . $row, $campaign['name']); // Ad Campaigns $sheet->setCellValue('B' . $row, self::$statusMapping[$campaign['status']]); // Status $sheet->setCellValue('C' . $row, self::$platformMapping[$campaign['platform_type']]); // Platform Type // $sheet->setCellValueExplicit('D' . $row, (string)$campaign['account_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('D' . $row, $campaign['account_name']); $sheet->setCellValue('E' . $row, $campaign['platform_purchase']); $sheet->setCellValue('F' . $row, $campaign['pixel_purchase']); $sheet->setCellValue('G' . $row, $campaign['roas']); // ROAS $sheet->setCellValue('H' . $row, $campaign['spend']); // Amount Spent $sheet->setCellValue('I' . $row, $campaign['impressions']); // Impressions $sheet->setCellValue('J' . $row, $campaign['clicks']); // Clicks $sheet->setCellValue('K' . $row, $campaign['ctr']); // Click Through Rate $sheet->setCellValue('L' . $row, $campaign['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $row, $campaign['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $row, $campaign['purchases']); // Purchases $sheet->setCellValue('O' . $row, $campaign['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $row, $campaign['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $row, $campaign['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $row, $campaign['revenue']); // Revenue $sheet->setCellValue('S' . $row, $campaign['total_cost']); // Total Cost $sheet->setCellValue('T' . $row, $campaign['net_profit']); // Net Profit $sheet->setCellValue('U' . $row, $campaign['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $row, $campaign['net_profit_on_ad_spend']); // Net Profit on Ad Spend $row++; } // 填充统计信息到表格的最后一行 $statistics = $campaignList['statistics']; $statisticsRow = $row; // 统计信息从当前行开始 $sheet->setCellValue('D' . $statisticsRow, 'Totals'); // 统计信息标题 $sheet->setCellValue('E' . $statisticsRow, $statistics['platform_purchase']); // Platform Purchases $sheet->setCellValue('F' . $statisticsRow, $statistics['pixel_purchase']); // Pixel Purchases $sheet->setCellValue('G' . $statisticsRow, $statistics['roas']); // ROAS $sheet->setCellValue('H' . $statisticsRow, $statistics['amount_spend']); // Amount Spent $sheet->setCellValue('I' . $statisticsRow, $statistics['impressions']); // Impressions $sheet->setCellValue('J' . $statisticsRow, $statistics['clicks']); // Clicks $sheet->setCellValue('K' . $statisticsRow, $statistics['ctr']); // Click Through Rate $sheet->setCellValue('L' . $statisticsRow, $statistics['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $statisticsRow, $statistics['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $statisticsRow, $statistics['purchases']); // Purchases $sheet->setCellValue('O' . $statisticsRow, $statistics['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $statisticsRow, $statistics['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $statisticsRow, $statistics['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $statisticsRow, $statistics['revenue']); // Revenue $sheet->setCellValue('S' . $statisticsRow, $statistics['total_cost']); // Total Cost $sheet->setCellValue('T' . $statisticsRow, $statistics['net_profit']); // Net Profit $sheet->setCellValue('U' . $statisticsRow, $statistics['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $statisticsRow, $statistics['net_profit_on_ad_spend']); // Net Profit on Ad Spend unset($statistics); unset($campaignList); // 文件名与路径 $fileName = 'Exported_Campaigns_' . date('Y-m-d_H-i-s') . '.xlsx'; $filePath = public_path() . '/' . $fileName; // 保存 Excel 文件 $writer = new Xlsx($spreadsheet); try { $writer->save($filePath); return response()->download($filePath, $fileName); } catch (\Exception $e) { // return ['success' => false, 'message' => $e->getMessage()]; $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } } /** * 导出广告组列表到 Excel * * @param int $platformType 平台类型 * @param array $customerIds 客户ID列表 * @param int $page 当前页码 * @param int $pageSize 每页条数 * @param string $keyword 关键字 * @param string|null $startDate 开始日期 * @param string|null $endDate 结束日期 * @param int $status 广告系列状态 */ public static function exportAdsetsToExcel($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false) { // 调用 getCampaignList 获取广告系列数据 $adsetList = self::getAdsetList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate, $endDate, $status, $requireSpend); if (empty($adsetList['data'])) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } // 创建 Spreadsheet 对象 $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头 $headers = [ 'A1' => 'Ad Sets', 'B1' => 'Status', 'C1' => 'Ad Platform', // 修正为 Ad Platform 'D1' => 'Ad Campaigns', 'E1' => 'Platform Purchases', 'F1' => 'Best Pixel Purchases', 'G1' => 'ROAS', 'H1' => 'Amount Spent', 'I1' => 'Impressions', 'J1' => 'Clicks', 'K1' => 'Click Through Rate', 'L1' => 'Adds to Cart', 'M1' => 'Cost per ATC', 'N1' => 'Purchases', 'O1' => 'Purchases Value', 'P1' => 'Cost per Purchase', 'Q1' => 'Conversion Rate', 'R1' => 'Revenue', 'S1' => 'Total Cost', 'T1' => 'Net Profit', 'U1' => 'Net Profit Margin', 'V1' => 'Net Profit on Ad Spend' // 新增 V 列 ]; foreach ($headers as $cell => $header) { $sheet->setCellValue($cell, $header); } // 填充数据 $row = 2; foreach ($adsetList['data'] as $adset) { // $sheet->setCellValueExplicit('A' . $row, (string)$campaign['campaign_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('A' . $row, $adset['name']); // Ad Campaigns $sheet->setCellValue('B' . $row, self::$statusMapping[$adset['status']]); // Status $sheet->setCellValue('C' . $row, self::$platformMapping[$adset['platform_type']]); // Platform Type // $sheet->setCellValueExplicit('D' . $row, (string)$adset['account_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('D' . $row, $adset['campaign_name']); $sheet->setCellValue('E' . $row, $adset['platform_purchase']); $sheet->setCellValue('F' . $row, $adset['pixel_purchase']); $sheet->setCellValue('G' . $row, $adset['roas']); // ROAS $sheet->setCellValue('H' . $row, $adset['spend']); // Amount Spent $sheet->setCellValue('I' . $row, $adset['impressions']); // Impressions $sheet->setCellValue('J' . $row, $adset['clicks']); // Clicks $sheet->setCellValue('K' . $row, $adset['ctr']); // Click Through Rate $sheet->setCellValue('L' . $row, $adset['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $row, $adset['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $row, $adset['purchases']); // Purchases $sheet->setCellValue('O' . $row, $adset['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $row, $adset['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $row, $adset['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $row, $adset['revenue']); // Revenue $sheet->setCellValue('S' . $row, $adset['total_cost']); // Total Cost $sheet->setCellValue('T' . $row, $adset['net_profit']); // Net Profit $sheet->setCellValue('U' . $row, $adset['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $row, $adset['net_profit_on_ad_spend']); // Net Profit on Ad Spend $row++; } // 填充统计信息到表格的最后一行 $statistics = $adsetList['statistics']; $statisticsRow = $row; // 统计信息从当前行开始 $sheet->setCellValue('D' . $statisticsRow, 'Totals'); // 统计信息标题 $sheet->setCellValue('E' . $statisticsRow, $statistics['platform_purchase']); $sheet->setCellValue('F' . $statisticsRow, $statistics['pixel_purchase']); $sheet->setCellValue('G' . $statisticsRow, $statistics['roas']); // ROAS $sheet->setCellValue('H' . $statisticsRow, $statistics['amount_spend']); // Amount Spent $sheet->setCellValue('I' . $statisticsRow, $statistics['impressions']); // Impressions $sheet->setCellValue('J' . $statisticsRow, $statistics['clicks']); // Clicks $sheet->setCellValue('K' . $statisticsRow, $statistics['ctr']); // Click Through Rate $sheet->setCellValue('L' . $statisticsRow, $statistics['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $statisticsRow, $statistics['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $statisticsRow, $statistics['purchases']); // Purchases $sheet->setCellValue('O' . $statisticsRow, $statistics['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $statisticsRow, $statistics['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $statisticsRow, $statistics['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $statisticsRow, $statistics['revenue']); // Revenue $sheet->setCellValue('S' . $statisticsRow, $statistics['total_cost']); // Total Cost $sheet->setCellValue('T' . $statisticsRow, $statistics['net_profit']); // Net Profit $sheet->setCellValue('U' . $statisticsRow, $statistics['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $statisticsRow, $statistics['net_profit_on_ad_spend']); // Net Profit on Ad Spend unset($statistics); unset($adsetList); // 文件名与路径 $fileName = 'Exported_Adsets_' . date('Y-m-d_H-i-s') . '.xlsx'; $filePath = public_path() . '/' . $fileName; // 保存 Excel 文件 $writer = new Xlsx($spreadsheet); try { $writer->save($filePath); return response()->download($filePath, $fileName); } catch (\Exception $e) { // return ['success' => false, 'message' => $e->getMessage()]; $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } } /** * 导出广告列表到 Excel * * @param int $platformType 平台类型 * @param array $customerIds 客户ID列表 * @param int $page 当前页码 * @param int $pageSize 每页条数 * @param string $keyword 关键字 * @param string|null $startDate 开始日期 * @param string|null $endDate 结束日期 * @param int $status 广告系列状态 */ public static function exportAdsToExcel($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false) { // 调用 getCampaignList 获取广告系列数据 $adList = self::getAdList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate, $endDate, $status, $requireSpend, false); if (empty($adList['data'])) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } // 创建 Spreadsheet 对象 $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头 $headers = [ 'A1' => 'Ads', 'B1' => 'Status', 'C1' => 'Ad Platform', // 修正为 Ad Platform 'D1' => 'Ad Sets', 'E1' => 'Platform Purchases', 'F1' => 'Best Pixel Purchases', 'G1' => 'ROAS', 'H1' => 'Amount Spent', 'I1' => 'Impressions', 'J1' => 'Clicks', 'K1' => 'Click Through Rate', 'L1' => 'Adds to Cart', 'M1' => 'Cost per ATC', 'N1' => 'Purchases', 'O1' => 'Purchases Value', 'P1' => 'Cost per Purchase', 'Q1' => 'Conversion Rate', 'R1' => 'Revenue', 'S1' => 'Total Cost', 'T1' => 'Net Profit', 'U1' => 'Net Profit Margin', 'V1' => 'Net Profit on Ad Spend' // 新增 V 列 ]; foreach ($headers as $cell => $header) { $sheet->setCellValue($cell, $header); } // 填充数据 $row = 2; foreach ($adList['data'] as $ad) { // $sheet->setCellValueExplicit('A' . $row, (string)$campaign['campaign_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('A' . $row, $ad['name']); // Ad Campaigns $sheet->setCellValue('B' . $row, self::$statusMapping[$ad['status']]); // Status $sheet->setCellValue('C' . $row, self::$platformMapping[$ad['platform_type']]); // Platform Type // $sheet->setCellValueExplicit('D' . $row, (string)$ad['account_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); $sheet->setCellValue('D' . $row, $ad['ad_set_name']); $sheet->setCellValue('E' . $row, $ad['platform_purchase']); $sheet->setCellValue('F' . $row, $ad['pixel_purchase']); $sheet->setCellValue('G' . $row, $ad['roas']); // ROAS $sheet->setCellValue('H' . $row, $ad['spend']); // Amount Spent $sheet->setCellValue('I' . $row, $ad['impressions']); // Impressions $sheet->setCellValue('J' . $row, $ad['clicks']); // Clicks $sheet->setCellValue('K' . $row, $ad['ctr']); // Click Through Rate $sheet->setCellValue('L' . $row, $ad['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $row, $ad['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $row, $ad['purchases']); // Purchases $sheet->setCellValue('O' . $row, $ad['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $row, $ad['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $row, $ad['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $row, $ad['revenue']); // Revenue $sheet->setCellValue('S' . $row, $ad['total_cost']); // Total Cost $sheet->setCellValue('T' . $row, $ad['net_profit']); // Net Profit $sheet->setCellValue('U' . $row, $ad['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $row, $ad['net_profit_on_ad_spend']); // Net Profit on Ad Spend $row++; } // 填充统计信息到表格的最后一行 $statistics = $adList['statistics']; $statisticsRow = $row; // 统计信息从当前行开始 $sheet->setCellValue('D' . $statisticsRow, 'Totals'); // 统计信息标题 $sheet->setCellValue('E' . $statisticsRow, $statistics['platform_purchase']); $sheet->setCellValue('F' . $statisticsRow, $statistics['pixel_purchase']); $sheet->setCellValue('G' . $statisticsRow, $statistics['roas']); // ROAS $sheet->setCellValue('H' . $statisticsRow, $statistics['amount_spend']); // Amount Spent $sheet->setCellValue('I' . $statisticsRow, $statistics['impressions']); // Impressions $sheet->setCellValue('J' . $statisticsRow, $statistics['clicks']); // Clicks $sheet->setCellValue('K' . $statisticsRow, $statistics['ctr']); // Click Through Rate $sheet->setCellValue('L' . $statisticsRow, $statistics['adds_to_cart']); // Adds to Cart $sheet->setCellValue('M' . $statisticsRow, $statistics['cost_per_atc']); // Cost per ATC $sheet->setCellValue('N' . $statisticsRow, $statistics['purchases']); // Purchases $sheet->setCellValue('O' . $statisticsRow, $statistics['purchases_value']); // Purchases Value $sheet->setCellValue('P' . $statisticsRow, $statistics['cost_per_purchase']); // Cost per Purchase $sheet->setCellValue('Q' . $statisticsRow, $statistics['conversion_rate']); // Conversion Rate $sheet->setCellValue('R' . $statisticsRow, $statistics['revenue']); // Revenue $sheet->setCellValue('S' . $statisticsRow, $statistics['total_cost']); // Total Cost $sheet->setCellValue('T' . $statisticsRow, $statistics['net_profit']); // Net Profit $sheet->setCellValue('U' . $statisticsRow, $statistics['net_profit_margin']); // Net Profit Margin $sheet->setCellValue('V' . $statisticsRow, $statistics['net_profit_on_ad_spend']); // Net Profit on Ad Spend unset($statistics); unset($adList); // 文件名与路径 $fileName = 'Exported_Ads_' . date('Y-m-d_H-i-s') . '.xlsx'; $filePath = public_path() . '/' . $fileName; // 保存 Excel 文件 $writer = new Xlsx($spreadsheet); try { $writer->save($filePath); return response()->download($filePath, $fileName); } catch (\Exception $e) { // return ['success' => false, 'message' => $e->getMessage()]; $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } } public static function exportCreativesToExcel($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $requireSpend = false) { // 获取广告创意数据 $creativeList = self::getCreativeInsightData($platformType, $customerIds, $page, $pageSize, $keyword, $startDate, $endDate, $requireSpend, false); if (empty($creativeList['data'])) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } // 创建 Spreadsheet 对象 $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头 $headers = [ 'A1' => 'Creative', 'B1' => 'Ad Platform', 'C1' => 'Thumbnail/Creative URL', 'D1' => 'Creative Type', 'E1' => 'Ad Count', 'F1' => 'Spend', 'G1' => 'Purchase value', 'H1' => 'ROAS', 'I1' => 'CPA', 'J1' => 'CPC (link click)', 'K1' => 'CPM', 'L1' => 'CPC (all)', 'M1' => 'AOV', 'N1' => 'Click to ATC ratio', 'O1' => 'ATC to purchase ratio', 'P1' => 'Purchases', 'Q1' => '1st frame retention', 'R1' => 'Thumbstop', 'S1' => 'CTR (outbound)', 'T1' => 'Click to purchase', 'U1' => 'CTR (all)', 'V1' => '25% video plays (rate)', 'W1' => '50% video plays (rate)', 'X1' => '75% video plays (rate)', 'Y1' => '100% video plays (rate)', 'Z1' => 'Hold rate' ]; // 填充表头 foreach ($headers as $cell => $header) { $sheet->setCellValue($cell, $header); } // 填充数据 $row = 2; foreach ($creativeList['data'] as $creative) { $sheet->setCellValue('A' . $row, $creative['creative']); // Creative $sheet->setCellValue('B' . $row, self::$platformMapping[$creative['platform']]); // Ad Platform $sheet->setCellValue('C' . $row, $creative['thumbnail_url'] ?: $creative['creative_url']); // Thumbnail/Creative URL $sheet->setCellValue('D' . $row, self::$creativeTypeMapping[$creative['creative_type']]); // Creative Type $sheet->setCellValue('E' . $row, $creative['ad_count']); // Ad Count $sheet->setCellValue('F' . $row, $creative['spend']); // Spend $sheet->setCellValue('G' . $row, $creative['purchases_value']); // Purchase value $sheet->setCellValue('H' . $row, $creative['roas']); // ROAS $sheet->setCellValue('I' . $row, $creative['cpa']); // CPA $sheet->setCellValue('J' . $row, $creative['cpc_link_click']); // CPC (link click) $sheet->setCellValue('K' . $row, $creative['cpm']); // CPM $sheet->setCellValue('L' . $row, $creative['cpc_all']); // CPC (all) $sheet->setCellValue('M' . $row, $creative['aov']); // AOV $sheet->setCellValue('N' . $row, $creative['click_to_atc_ratio']); // Click to ATC ratio $sheet->setCellValue('O' . $row, $creative['atc_to_purchase_ratio']); // ATC to purchase ratio $sheet->setCellValue('P' . $row, $creative['purchases']); // Purchases $sheet->setCellValue('Q' . $row, $creative['first_frame_retention']); // 1st frame retention $sheet->setCellValue('R' . $row, $creative['thumbstop']); // Thumbstop $sheet->setCellValue('S' . $row, $creative['ctr_outbound']); // CTR (outbound) $sheet->setCellValue('T' . $row, $creative['click_to_purchase']); // Click to purchase $sheet->setCellValue('U' . $row, $creative['ctr_all']); // CTR (all) $sheet->setCellValue('V' . $row, $creative['video_plays_25_rate']); // 25% video plays (rate) $sheet->setCellValue('W' . $row, $creative['video_plays_50_rate']); // 50% video plays (rate) $sheet->setCellValue('X' . $row, $creative['video_plays_75_rate']); // 75% video plays (rate) $sheet->setCellValue('Y' . $row, $creative['video_plays_100_rate']); // 100% video plays (rate) $sheet->setCellValue('Z' . $row, $creative['hold_rate']); // Hold rate $row++; } // 填充统计信息到表格的最后一行 $statistics = $creativeList['statistics']; $statisticsRow = $row; // 统计信息从当前行开始 $sheet->setCellValue('E' . $statisticsRow, 'Totals'); // 统计信息标题 $sheet->setCellValue('F' . $statisticsRow, $statistics['spend']); // Spend $sheet->setCellValue('G' . $statisticsRow, $statistics['purchases_value']); // Purchase value $sheet->setCellValue('H' . $statisticsRow, $statistics['roas']); // ROAS $sheet->setCellValue('I' . $statisticsRow, $statistics['cpa']); // CPA $sheet->setCellValue('J' . $statisticsRow, $statistics['cpc_link_click']); // CPC (link click) $sheet->setCellValue('K' . $statisticsRow, $statistics['cpm']); // CPM $sheet->setCellValue('L' . $statisticsRow, $statistics['cpc_all']); // CPC (all) $sheet->setCellValue('M' . $statisticsRow, $statistics['aov']); // AOV $sheet->setCellValue('N' . $statisticsRow, $statistics['click_to_atc_ratio']); // Click to ATC ratio $sheet->setCellValue('O' . $statisticsRow, $statistics['atc_to_purchase_ratio']); // ATC to purchase ratio $sheet->setCellValue('P' . $statisticsRow, $statistics['purchases']); // Purchases $sheet->setCellValue('Q' . $statisticsRow, $statistics['first_frame_retention']); // 1st frame retention $sheet->setCellValue('R' . $statisticsRow, $statistics['thumbstop']); // Thumbstop $sheet->setCellValue('S' . $statisticsRow, $statistics['ctr_outbound']); // CTR (outbound) $sheet->setCellValue('T' . $statisticsRow, $statistics['click_to_purchase']); // Click to purchase $sheet->setCellValue('U' . $statisticsRow, $statistics['ctr_all']); // CTR (all) $sheet->setCellValue('V' . $statisticsRow, $statistics['video_plays_25_rate']); // 25% video plays (rate) $sheet->setCellValue('W' . $statisticsRow, $statistics['video_plays_50_rate']); // 50% video plays (rate) $sheet->setCellValue('X' . $statisticsRow, $statistics['video_plays_75_rate']); // 75% video plays (rate) $sheet->setCellValue('Y' . $statisticsRow, $statistics['video_plays_100_rate']); // 100% video plays (rate) $sheet->setCellValue('Z' . $statisticsRow, $statistics['hold_rate']); // Hold rate // 释放内存 unset($statistics); unset($creativeList); // 文件名与路径 $fileName = 'Exported_Creatives_' . date('Y-m-d_H-i-s') . '.xlsx'; $filePath = public_path() . '/' . $fileName; // 保存 Excel 文件 $writer = new Xlsx($spreadsheet); try { $writer->save($filePath); return response()->download($filePath, $fileName); } catch (\Exception $e) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } } /** * 获取广告组列表 */ public static function getAdsetList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false, $countOnly = false) { // 检查 customerIds 是否为空,直接返回空结构 if (empty($customerIds)) { return $countOnly ? 0 : [ 'pagination' => [ 'startIndex' => 0, 'maxResults' => 0, 'count' => 0, 'pageNo' => $page, 'pageSize' => $pageSize, 'pages' => 0, ], 'statistics' => [], 'data' => [], ]; } // 基础查询:广告活动和日数据表联接 $query = BpsAdSet::alias('s') ->cache(false) // 强制不使用缓存 ->where('s.account_id', 'in', $customerIds); // 如果只需要记录条数,执行计数查询 if ($countOnly) { return $query->count(); } // 动态构建日期条件 $dateCondition = ''; if ($startDate && $endDate) { $dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'"; } // 基础查询:广告组和日数据表联接 $query->leftJoin('bps.bps_ads_insights d', "s.ad_set_id = d.ad_set_id AND s.platform_type = d.platform AND {$dateCondition}") ->leftJoin('bps.bps_ads_campaign c', 's.campaign_id = c.campaign_id') // 联接广告系列表 ->field('s.ad_set_id, s.status as status, s.name, s.account_id,s.platform_type,c.name as campaign_name, COALESCE(SUM(d.clicks), 0) as clicks, COALESCE(SUM(d.spend) / 1000000, 0) as spend, COALESCE(SUM(d.impressions), 0) as impressions, COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart, COALESCE(SUM(d.purchases), 0) as purchases, COALESCE(SUM(d.platform_purchase), 0) as platform_purchase, COALESCE(SUM(d.pixel_purchase), 0) as pixel_purchase, COALESCE(SUM(d.platform_purchase_value / 1000000), 0) as purchases_value, COALESCE(SUM(d.platform_purchase_value / 1000000), 0) as revenue, COALESCE(SUM(d.total_cost / 1000000), 0) as total_cost, -1 as conversion_rate, -1 as roas, -1 as ctr,-1 as net_profit,-1 as net_profit_margin,-1 as net_profit_on_ad_spend') ->group('s.ad_set_id, s.status, s.account_id, s.name,s.platform_type,c.name'); // ->where('s.account_id', 'in', $customerIds); // 添加 customerIds 条件 // 根据 $requireSpend 参数决定是否添加 SUM(d.spend) > 0 的条件 if ($requireSpend) { $query->having('SUM(d.spend) > 0'); } if ($status !== 0) { $query->where('s.status', '=', $status); } // 添加关键字过滤条件 $query->where(function ($query) use ($keyword, $platformType) { if ($keyword) { // $query->where('s.name', 'like', '%' . $keyword . '%'); $query->whereRaw('LOWER(s.name) LIKE ?', ['%' . strtolower($keyword) . '%']); } if ($platformType) { $platformType = (int)$platformType; $query->where('s.platform_type', '=', $platformType); } }); $query->order('s.platform_type, s.account_id, s.status', 'asc'); // 获取所有符合条件的数据(不分页) $allAdsets = $query->select()->toArray(); $total_spend = array_sum(array_column($allAdsets, 'spend')); $total_cost = array_sum(array_column($allAdsets, 'total_cost')); $total_impressions = array_sum(array_column($allAdsets, 'impressions')); $total_clicks = array_sum(array_column($allAdsets, 'clicks')); $total_purchases_value = array_sum(array_column($allAdsets, 'purchases_value')); $total_revenue = array_sum(array_column($allAdsets, 'revenue')); $total_purchases = array_sum(array_column($allAdsets, 'purchases')); // $total_platform_purchase = array_sum(array_column($allAdsets, 'platform_purchase')); // $total_pixel_purchase = array_sum(array_column($allAdsets, 'pixel_purchase')); $adds_to_cart = array_sum(array_column($allAdsets, 'adds_to_cart')); $cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2); // 汇总统计数据 $statistics = [ 'platform_purchase' => number_format(array_sum(array_column($allAdsets, 'platform_purchase'))), 'pixel_purchase' => number_format(array_sum(array_column($allAdsets, 'pixel_purchase'))), 'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'x', 'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出 'clicks' => number_format($total_clicks), 'impressions' => number_format($total_impressions), 'adds_to_cart' => number_format($adds_to_cart), 'cost_per_atc' => $adds_to_cart == 0 ? '-' : '$' . number_format(($total_spend / $adds_to_cart), 2), 'purchases' => number_format($total_purchases), 'purchases_value' => array_sum(array_column($allAdsets, 'purchases_value')), 'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00', 'revenue' => '$' . number_format(array_sum(array_column($allAdsets, 'revenue')), 2) ?: '$0.00', // 格式化收入 'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本 'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率 'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format(abs($total_revenue - $total_cost), 2), // 净利润 'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率 'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2) * 100 . '%', // 广告支出净利润 // 计算总的 CTR 'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比 ]; // 获取分页数据 // $adsets = $query->paginate($pageSize, false, ['page' => $page]); $page = max(1, (int)$page); // 确保页码不小于 1 $adsets = $query->limit(($page - 1) * $pageSize, $pageSize)->select(); // 确保数据格式统一 $result = array_map(function ($item) { // CTR 的计算:点击率 = 点击数 / 展示数 $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // Conversion Rate 的计算:转换率 = 购买数 / 点击数 $conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-'; // Net Profit 的计算:净利润 = 收入 - 总成本 $net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format(abs($item['revenue'] - $item['total_cost']), 2); // Net Profit Margin 的计算:净利润率 = 净利润 / 收入 $net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-'; // Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出 $net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) * 100 . '%' : '-'; return [ 'id' => $item['ad_set_id'], 'platform_type' => $item['platform_type'], 'account_id' => $item['account_id'], // 映射为 customer_id 'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-' 'campaign_name' => $item['campaign_name'] ?: '-', // 若 name 为空则显示 '-' 'status' => $item['status'], 'platform_purchase' => number_format($item['platform_purchase']), 'pixel_purchase' => number_format($item['pixel_purchase']), 'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'x', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => number_format($item['impressions']), 'clicks' => number_format($item['clicks']), 'ctr' => $ctr, // CTR 字段 'adds_to_cart' => number_format($item['adds_to_cart']), 'cost_per_atc' => $item['adds_to_cart'] > 0 ? '$' . number_format(($item['spend'] / $item['adds_to_cart']), 2) : '-', 'purchases' => number_format($item['purchases']), 'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额 'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '-', 'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入 'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本 'conversion_rate' => $conversion_rate, // 没有提供有效的计算,保持为 '-' 'net_profit' => $net_profit, // 没有提供 net_profit 计算,保持为 '-' 'net_profit_margin' => $net_profit_margin, // 没有提供 net_profit_margin 计算,保持为 '-' 'net_profit_on_ad_spend' => $net_profit_on_ad_spend, // 没有提供 net_profit_on_ad_spend 计算,保持为 '-' ]; }, $adsets->toArray()); // Pagination 数据 $pagination = [ 'startIndex' => ($page - 1) * $pageSize + 1, // 确保索引从 1 开始 'maxResults' => count($adsets), // 当前页实际返回数据数量 'count' => count($allAdsets), // 符合条件的总记录数 'pageNo' => $page, // 当前页码 'pageSize' => $pageSize, // 每页条数 'pages' => (int)ceil(count($allAdsets) / $pageSize), // 总页数 ]; return [ 'pagination' => $pagination, 'statistics' => $statistics, 'data' => $result, ]; } /** * 导出广告账号列表到 Excel * * @param int $platformType 平台类型 * @param array $customerIds 客户ID列表 * @param int $page 当前页码 * @param int $pageSize 每页条数 * @param string $keyword 关键字 * @param string|null $startDate 开始日期 * @param string|null $endDate 结束日期 * @param int $status 广告系列状态 */ public static function exportAccountsToExcel($platformType, $merchantId, $storeId, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $requireSpend = false) { // 调用 getCampaignList 获取广告系列数据 $accountList = self::getAccountList($platformType, $merchantId, $storeId, $customerIds, $page, $pageSize, $keyword, $startDate, $endDate, $requireSpend); if (empty($accountList['data'])) { $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } // 创建 Spreadsheet 对象 $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // 设置表头 $headers = [ 'A1' => 'Ad Account Id', 'B1' => 'Advertiser Name', 'C1' => 'Ad Platform', 'D1' => 'Platform Purchases', 'E1' => 'Best Pixel Purchases', 'F1' => 'ROAS', 'G1' => 'Amount Spent', 'H1' => 'Impressions', 'I1' => 'Clicks', 'J1' => 'Click Through Rate', 'K1' => 'Adds to Cart', 'L1' => 'Cost per ATC', 'M1' => 'Purchases', 'N1' => 'Purchases Value', 'O1' => 'Cost per Purchase', 'P1' => 'Conversion Rate', 'Q1' => 'Revenue', 'R1' => 'Total Cost', 'S1' => 'Net Profit', 'T1' => 'Net Profit Margin', 'U1' => 'Net Profit on Ad Spend' ]; foreach ($headers as $cell => $header) { $sheet->setCellValue($cell, $header); } // 填充数据 // 填充数据 $row = 2; foreach ($accountList['data'] as $account) { $sheet->setCellValueExplicit('A' . $row, (string)$account['user_id'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING); // Ad Account Id $sheet->setCellValue('B' . $row, $account['advertiser_name']); // Advertiser Name $sheet->setCellValue('C' . $row, self::$platformMapping[$account['platform_type']]); // Ad Platform $sheet->setCellValue('D' . $row, $account['platform_purchase']); $sheet->setCellValue('E' . $row, $account['pixel_purchase']); $sheet->setCellValue('F' . $row, $account['roas']); // ROAS(原 G 列) $sheet->setCellValue('G' . $row, $account['spend']); // Amount Spent(原 H 列) $sheet->setCellValue('H' . $row, $account['impressions']); // Impressions(原 I 列) $sheet->setCellValue('I' . $row, $account['clicks']); // Clicks(原 J 列) $sheet->setCellValue('J' . $row, $account['ctr']); // Click Through Rate(原 K 列) $sheet->setCellValue('K' . $row, $account['adds_to_cart']); // Adds to Cart(原 L 列) $sheet->setCellValue('L' . $row, $account['cost_per_atc']); // Cost per ATC(原 M 列) $sheet->setCellValue('M' . $row, $account['purchases']); // Purchases(原 N 列) $sheet->setCellValue('N' . $row, $account['purchases_value']); // Purchases Value(原 O 列) $sheet->setCellValue('O' . $row, $account['cost_per_purchase']); // Cost per Purchase(原 P 列) $sheet->setCellValue('P' . $row, $account['conversion_rate']); // Conversion Rate(原 Q 列) $sheet->setCellValue('Q' . $row, $account['revenue']); // Revenue(原 R 列) $sheet->setCellValue('R' . $row, $account['total_cost']); // Total Cost(原 S 列) $sheet->setCellValue('S' . $row, $account['net_profit']); // Net Profit(原 T 列) $sheet->setCellValue('T' . $row, $account['net_profit_margin']); // Net Profit Margin(原 U 列) $sheet->setCellValue('U' . $row, $account['net_profit_on_ad_spend']); // Net Profit on Ad Spend(原 V 列) $row++; } // 填充统计信息到表格的最后一行 $statistics = $accountList['statistics']; $statisticsRow = $row; // 统计信息从当前行开始 $sheet->setCellValue('C' . $statisticsRow, 'Totals'); // 统计信息标题(调整到 A 列) $sheet->setCellValue('D' . $statisticsRow, $statistics['platform_purchase']); $sheet->setCellValue('E' . $statisticsRow, $statistics['pixel_purchase']); $sheet->setCellValue('F' . $statisticsRow, $statistics['roas']); // ROAS(原 G 列) $sheet->setCellValue('G' . $statisticsRow, $statistics['amount_spend']); // Amount Spent(原 H 列) $sheet->setCellValue('H' . $statisticsRow, $statistics['impressions']); // Impressions(原 I 列) $sheet->setCellValue('I' . $statisticsRow, $statistics['clicks']); // Clicks(原 J 列) $sheet->setCellValue('J' . $statisticsRow, $statistics['ctr']); // Click Through Rate(原 K 列) $sheet->setCellValue('K' . $statisticsRow, $statistics['adds_to_cart']); // Adds to Cart(原 L 列) $sheet->setCellValue('L' . $statisticsRow, $statistics['cost_per_atc']); // Cost per ATC(原 M 列) $sheet->setCellValue('M' . $statisticsRow, $statistics['purchases']); // Purchases(原 N 列) $sheet->setCellValue('N' . $statisticsRow, $statistics['purchases_value']); // Purchases Value(原 O 列) $sheet->setCellValue('O' . $statisticsRow, $statistics['cost_per_purchase']); // Cost per Purchase(原 P 列) $sheet->setCellValue('P' . $statisticsRow, $statistics['conversion_rate']); // Conversion Rate(原 Q 列) $sheet->setCellValue('Q' . $statisticsRow, $statistics['revenue']); // Revenue(原 R 列) $sheet->setCellValue('R' . $statisticsRow, $statistics['total_cost']); // Total Cost(原 S 列) $sheet->setCellValue('S' . $statisticsRow, $statistics['net_profit']); // Net Profit(原 T 列) $sheet->setCellValue('T' . $statisticsRow, $statistics['net_profit_margin']); // Net Profit Margin(原 U 列) $sheet->setCellValue('U' . $statisticsRow, $statistics['net_profit_on_ad_spend']); // Net Profit on Ad Spend(原 V 列) unset($statistics); unset($accountList); // 文件名与路径 $fileName = 'Exported_Accounts_' . date('Y-m-d_H-i-s') . '.xlsx'; $filePath = public_path() . '/' . $fileName; // 保存 Excel 文件 $writer = new Xlsx($spreadsheet); try { $writer->save($filePath); return response()->download($filePath, $fileName); } catch (\Exception $e) { // return ['success' => false, 'message' => $e->getMessage()]; $data = [ 'code' => 901, 'msg' => 'No data available for export.', 'data' => [] ]; return new Response(400, ['Content-Type' => 'application/json'], json_encode($data, JSON_UNESCAPED_UNICODE)); } } public static function getAccountList($platformType, $merchantId, $storeId, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $requireSpend = false, $countOnly = false) { // 检查 customerIds 是否为空,直接返回计数为 0 if (empty($customerIds)) { return $countOnly ? 0 : [ 'pagination' => [ 'startIndex' => 0, 'maxResults' => $pageSize, 'count' => 0, 'pageNo' => $page, 'pageSize' => $pageSize, 'pages' => 0, ], 'statistics' => [], 'data' => [], ]; } // 构建查询条件 // $query = ThirdUserAdvertiser::alias('a') // ->cache(false) // ->where('a.advertiser_id', 'in', $customerIds); $query = BpsAdsMerchantStoreRelation::alias('bamr') ->cache(false) ->where('bamr.merchant_id', '=', $merchantId) ->where('bamr.store_id', '=', $storeId) ->where('bamr.account_id', 'in', $customerIds); // 仅计数时优化查询 if ($countOnly) { return $query->count('distinct(bamr.account_id)'); // 只查询总记录数 } // 动态构建日期条件 $dateCondition = ''; if ($startDate && $endDate) { $dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'"; } // 基础查询:广告和日数据表联接 // 其他联表及字段计算 $query->leftJoin('bps.bps_ads_insights d', "bamr.account_id = d.account_id AND {$dateCondition}") ->field('bamr.account_id, bamr.account_name,bamr.platform, COALESCE(SUM(d.clicks), 0) as clicks, COALESCE(SUM(d.spend) / 1000000, 0) as spend, COALESCE(SUM(d.impressions), 0) as impressions, COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart, COALESCE(SUM(d.purchases), 0) as purchases, COALESCE(SUM(d.platform_purchase), 0) as platform_purchase, COALESCE(SUM(d.pixel_purchase), 0) as pixel_purchase, COALESCE(SUM(d.platform_purchase_value) / 1000000, 0) as purchases_value, COALESCE(SUM(d.platform_purchase_value) / 1000000, 0) as revenue, COALESCE(SUM(d.total_cost) / 1000000, 0) as total_cost, -1 as conversion_rate, -1 as roas, -1 as ctr, -1 as net_profit, -1 as net_profit_margin, -1 as net_profit_on_ad_spend') ->group('bamr.account_id, bamr.account_name,bamr.platform'); // ->where('bamr.account_id', 'in', $customerIds); // 添加 customerIds 条件 // 如果传入了 status 参数,按状态筛选 // if ($status !== 0) { // $query->where('a.status', '=', $status); // } // 添加关键字过滤条件 $query->where(function ($query) use ($keyword, $platformType) { if ($keyword) { $query->whereRaw('LOWER(bamr.account_name) LIKE ?', ['%' . strtolower($keyword) . '%']); } if ($platformType) { // $a = (int)$platformType; // $platformTypeNames = [1 => 'facebook', 2 => 'google', 3 => 'tiktok']; $query->where('bamr.platform', '=', $platformType); } }); $query->order('bamr.platform', 'asc'); // 根据 $requireSpend 参数决定是否添加 SUM(d.spend) > 0 的条件 if ($requireSpend) { $query->having('SUM(d.spend) > 0'); } // 获取所有符合条件的数据(不分页) $allAccounts = $query->select()->toArray(); // 打印调试 SQL 查询 // $sql = $query->getLastSql(); // dump($sql); // 汇总统计数据 $total_spend = array_sum(array_column($allAccounts, 'spend')); $total_cost = array_sum(array_column($allAccounts, 'total_cost')); $total_impressions = array_sum(array_column($allAccounts, 'impressions')); $total_clicks = array_sum(array_column($allAccounts, 'clicks')); $total_purchases_value = array_sum(array_column($allAccounts, 'purchases_value')); $total_revenue = array_sum(array_column($allAccounts, 'revenue')); $total_purchases = array_sum(array_column($allAccounts, 'purchases')); $adds_to_cart = array_sum(array_column($allAccounts, 'adds_to_cart')); $cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2); $statistics = [ 'platform_purchase' => number_format(array_sum(array_column($allAccounts, 'platform_purchase'))), 'pixel_purchase' => number_format(array_sum(array_column($allAccounts, 'pixel_purchase'))), 'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'x', 'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出 'clicks' => number_format($total_clicks), 'impressions' => number_format($total_impressions), 'adds_to_cart' => number_format($adds_to_cart), 'cost_per_atc' => $adds_to_cart == 0 ? '-' : '$' . number_format(($total_spend / $adds_to_cart), 2), 'purchases' => number_format($total_purchases), 'purchases_value' => '$' . number_format($total_purchases_value, 2) ?: '$0.00', // 格式化销售额$total_purchases_value,, 'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00', 'revenue' => '$' . number_format(array_sum(array_column($allAccounts, 'revenue')), 2) ?: '$0.00', // 格式化收入 'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本 'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率 'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format(abs($total_revenue - $total_cost), 2), // 净利润 'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率 'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2) * 100 . '%', // 平均广告花费的净利润 'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比 ]; // 获取分页数据 $page = max(1, (int)$page); // 确保页码不小于 1 // $ads = $query->page($page, $pageSize)->select(); $accounts = $query->limit(($page - 1) * $pageSize, $pageSize)->select(); // dump($ads); // 确保数据格式统一 $result = array_map(function ($item) { // CTR 的计算:点击率 = 点击数 / 展示数 $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // Conversion Rate 的计算:转换率 = 购买数 / 点击数 $conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-'; // Net Profit 的计算:净利润 = 收入 - 总成本 $net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format(abs($item['revenue'] - $item['total_cost']), 2); // Net Profit Margin 的计算:净利润率 = 净利润 / 收入 $net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-'; // Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出 $net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) * 100 . '%' : '-'; // $platformTypeIds = ['facebook' => 1, 'google' => 2, 'tiktok' => 3]; return [ 'user_id' => $item['account_id'], // 'platform_type' => $platformTypeIds[$item['third_type']], 'platform_type' => $item['platform'], 'advertiser_name' => $item['account_name'] ?: '-', // 若 name 为空则显示 '-' // 'ad_set_name' => $item['ad_set_name'] ?: '-', // 若 name 为空则显示 '-' // 'status' => $item['status'], 'platform_purchase' => number_format($item['platform_purchase']), 'pixel_purchase' => number_format($item['pixel_purchase']), 'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'x', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'amount_spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => number_format($item['impressions']), 'clicks' => number_format($item['clicks']), 'ctr' => $ctr, // CTR 字段 'adds_to_cart' => number_format($item['adds_to_cart']), 'cost_per_atc' => $item['adds_to_cart'] > 0 ? '$' . number_format(($item['spend'] / $item['adds_to_cart']), 2) : '-', 'purchases' => number_format($item['purchases']), 'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额 'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '-', 'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入 'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本 'conversion_rate' => $conversion_rate, 'net_profit' => $net_profit, 'net_profit_margin' => $net_profit_margin, 'net_profit_on_ad_spend' => $net_profit_on_ad_spend, ]; }, $accounts->toArray()); // Pagination 数据 $pagination = [ 'startIndex' => ($page - 1) * $pageSize + 1, // 确保索引从 1 开始 'maxResults' => count($accounts), // 当前页实际返回数据数量 'count' => count($allAccounts), // 符合条件的总记录数 'pageNo' => $page, // 当前页码 'pageSize' => $pageSize, // 每页条数 'pages' => (int)ceil(count($allAccounts) / $pageSize), // 总页数 ]; return [ 'pagination' => $pagination, 'statistics' => $statistics, 'data' => $result, ]; } public static function getAdList($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $status = 0, $requireSpend = false, $countOnly = false) { // 检查 customerIds 是否为空,直接返回空结构 if (empty($customerIds)) { return $countOnly ? 0 : [ 'pagination' => [ 'startIndex' => 0, 'maxResults' => 0, 'count' => 0, 'pageNo' => $page, 'pageSize' => $pageSize, 'pages' => 0, ], 'statistics' => [], 'data' => [], ]; } // 基础查询:广告活动和日数据表联接 $query = BpsAdAd::alias('a') ->cache(false) // 强制不使用缓存 ->where('a.account_id', 'in', $customerIds); // 如果只需要记录条数,执行计数查询 if ($countOnly) { return $query->count(); } // 动态构建日期条件 $dateCondition = ''; if ($startDate && $endDate) { $dateCondition = "d.date BETWEEN '{$startDate}' AND '{$endDate}'"; } // 基础查询:广告和日数据表联接 $query->leftJoin('bps.bps_ads_insights d', "a.ad_id = d.ad_id AND a.platform_type = d.platform AND {$dateCondition}") ->leftJoin('bps.bps_ads_set s', 'a.ad_set_id = s.ad_set_id') // 联接广告组表,获取 ad_set_name ->field('a.ad_id, a.status as status, a.name, a.account_id,a.platform_type,s.name as ad_set_name, COALESCE(SUM(d.clicks), 0) as clicks, COALESCE(SUM(d.spend) / 1000000, 0) as spend, COALESCE(SUM(d.impressions), 0) as impressions, COALESCE(SUM(d.adds_to_cart), 0) as adds_to_cart, COALESCE(SUM(d.purchases), 0) as purchases, COALESCE(SUM(d.platform_purchase), 0) as platform_purchase, COALESCE(SUM(d.pixel_purchase), 0) as pixel_purchase, COALESCE(SUM(d.platform_purchase_value) / 1000000, 0) as purchases_value, COALESCE(SUM(d.platform_purchase_value / 1000000), 0) as revenue, COALESCE(SUM(d.total_cost) / 1000000, 0) as total_cost, -1 as conversion_rate, -1 as roas, -1 as ctr, -1 as net_profit, -1 as net_profit_margin, -1 as net_profit_on_ad_spend') ->group('a.ad_id, a.status, a.account_id, a.name,a.platform_type, s.name'); // ->where('a.account_id', 'in', $customerIds); // 添加 customerIds 条件 // 如果传入了 status 参数,按状态筛选 if ($status !== 0) { $query->where('a.status', '=', $status); } // 根据 $requireSpend 参数决定是否添加 SUM(d.spend) > 0 的条件 if ($requireSpend) { $query->having('SUM(d.spend) > 0'); } // 添加关键字过滤条件 $query->where(function ($query) use ($keyword, $platformType) { if ($keyword) { // $query->where('a.name', 'like', '%' . $keyword . '%'); $query->whereRaw('LOWER(a.name) LIKE ?', ['%' . strtolower($keyword) . '%']); } if ($platformType) { $platformType = (int)$platformType; $query->where('a.platform_type', '=', $platformType); } }); $query->order('a.platform_type, a.account_id, a.status', 'asc'); // 获取所有符合条件的数据(不分页) $allAds = $query->select()->toArray(); // 汇总统计数据 $total_spend = array_sum(array_column($allAds, 'spend')); $total_cost = array_sum(array_column($allAds, 'total_cost')); $total_impressions = array_sum(array_column($allAds, 'impressions')); $total_clicks = array_sum(array_column($allAds, 'clicks')); $total_purchases_value = array_sum(array_column($allAds, 'purchases_value')); $total_revenue = array_sum(array_column($allAds, 'revenue')); $total_purchases = array_sum(array_column($allAds, 'purchases')); $adds_to_cart = array_sum(array_column($allAds, 'adds_to_cart')); $cost_per_purchase = $total_purchases == 0 ? 0 : round($total_spend / $total_purchases, 2); // 生成唯一缓存键 $cacheKey = 'adlist_stats:' . md5(serialize([ $platformType, $customerIds, $pageSize, $keyword, $startDate, $endDate, $status, $requireSpend ])); // 尝试从缓存读取统计数据 if (!$countOnly && $cached = Redis::get($cacheKey)) { $statistics = json_decode($cached, true); // 跳过后续统计计算直接使用缓存数据... } else { $statistics = [ 'platform_purchase' => number_format(array_sum(array_column($allAds, 'platform_purchase'))), 'pixel_purchase' => number_format(array_sum(array_column($allAds, 'pixel_purchase'))), 'roas' => $total_spend == 0 ? '-' : round($total_revenue / $total_spend, 2) . 'x', 'amount_spend' => '$' . number_format($total_spend, 2) ?: '$0.00', // 格式化支出 'clicks' => number_format($total_clicks), 'impressions' => number_format($total_impressions), 'adds_to_cart' => number_format($adds_to_cart), 'cost_per_atc' => $adds_to_cart == 0 ? '-' : '$' . number_format(($total_spend / $adds_to_cart), 2), 'purchases' => number_format($total_purchases), 'purchases_value' => array_sum(array_column($allAds, 'purchases_value')), 'cost_per_purchase' => '$' . number_format($cost_per_purchase, 2) ?: '$0.00', 'revenue' => '$' . number_format(array_sum(array_column($allAds, 'revenue')), 2) ?: '$0.00', // 格式化收入 'total_cost' => '$' . number_format($total_cost, 2) ?: '$0.00', // 格式化总成本 'conversion_rate' => $total_clicks == 0 ? '-' : round(($total_purchases / $total_clicks) * 100, 2) . '%', // 转换率 'net_profit' => ($total_revenue - $total_cost) >= 0 ? '+$' . number_format($total_revenue - $total_cost, 2) : '-$' . number_format(abs($total_revenue - $total_cost), 2), // 净利润 'net_profit_margin' => $total_revenue == 0 ? '-' : round(($total_revenue - $total_cost) / $total_revenue, 2) * 100 . '%', // 净利润率 'net_profit_on_ad_spend' => $total_spend == 0 ? '-' : round(($total_revenue - $total_cost) / $total_spend, 2) * 100 . '%', // 平均广告花费的净利润 'ctr' => ($total_impressions > 0) ? number_format(($total_clicks / $total_impressions) * 100, 2) . '%' : '-', // 格式化为百分比 ]; // 将新生成的统计数据存入缓存(10分钟有效期) Redis::setex($cacheKey, 600, json_encode($statistics)); } // 获取分页数据 $page = max(1, (int)$page); // 确保页码不小于 1 // $ads = $query->page($page, $pageSize)->select(); $ads = $query->limit(($page - 1) * $pageSize, $pageSize)->select(); // dump($ads); // 确保数据格式统一 $result = array_map(function ($item) { // CTR 的计算:点击率 = 点击数 / 展示数 $ctr = $item['impressions'] > 0 ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-'; // Conversion Rate 的计算:转换率 = 购买数 / 点击数 $conversion_rate = $item['clicks'] > 0 ? round(($item['purchases'] / $item['clicks']) * 100, 2) . '%' : '-'; // Net Profit 的计算:净利润 = 收入 - 总成本 $net_profit = ($item['revenue'] - $item['total_cost']) >= 0 ? '+$' . number_format($item['revenue'] - $item['total_cost'], 2) : '-$' . number_format(abs($item['revenue'] - $item['total_cost']), 2); // Net Profit Margin 的计算:净利润率 = 净利润 / 收入 $net_profit_margin = $item['revenue'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['revenue'], 2) * 100 . '%' : '-'; // Net Profit on Ad Spend 的计算:广告支出净利润 = (收入 - 总成本) / 广告支出 $net_profit_on_ad_spend = $item['spend'] > 0 ? round(($item['revenue'] - $item['total_cost']) / $item['spend'], 2) * 100 . '%' : '-'; return [ 'id' => $item['ad_id'], 'platform_type' => $item['platform_type'], 'account_id' => $item['account_id'], // 映射为 customer_id 'name' => $item['name'] ?: '-', // 若 name 为空则显示 '-' 'ad_set_name' => $item['ad_set_name'] ?: '-', // 若 name 为空则显示 '-' 'status' => $item['status'], 'platform_purchase' => number_format($item['platform_purchase']), 'pixel_purchase' => number_format($item['pixel_purchase']), 'roas' => $item['spend'] == 0 ? '-' : round($item['revenue'] / $item['spend'], 2) . 'x', 'spend' => '$' . number_format($item['spend'], 2), // 格式化支出 'impressions' => number_format($item['impressions']), 'clicks' => number_format($item['clicks']), 'ctr' => $ctr, // CTR 字段 'adds_to_cart' => number_format($item['adds_to_cart']), 'cost_per_atc' => $item['adds_to_cart'] > 0 ? '$' . number_format(($item['spend'] / $item['adds_to_cart']), 2) : '-', 'purchases' => number_format($item['purchases']), 'purchases_value' => '$' . number_format($item['purchases_value'], 2), // 格式化购买金额 'cost_per_purchase' => $item['purchases'] > 0 ? '$' . number_format(($item['spend'] / $item['purchases']), 2) : '-', 'revenue' => '$' . number_format($item['revenue'], 2), // 格式化收入 'total_cost' => '$' . number_format($item['total_cost'], 2), // 格式化总成本 'conversion_rate' => $conversion_rate, 'net_profit' => $net_profit, 'net_profit_margin' => $net_profit_margin, 'net_profit_on_ad_spend' => $net_profit_on_ad_spend, ]; }, $ads->toArray()); // Pagination 数据 $pagination = [ 'startIndex' => ($page - 1) * $pageSize + 1, // 确保索引从 1 开始 'maxResults' => count($ads), // 当前页实际返回数据数量 'count' => count($allAds), // 符合条件的总记录数 'pageNo' => $page, // 当前页码 'pageSize' => $pageSize, // 每页条数 'pages' => (int)ceil(count($allAds) / $pageSize), // 总页数 ]; return [ 'pagination' => $pagination, 'statistics' => $statistics, 'data' => $result, ]; } public static function getCreativeInsightData($platformType, $customerIds, $page, $pageSize, $keyword, $startDate = null, $endDate = null, $requireSpend = false, $countOnly = false) { // 1. 创建查询对象,初始化 BpsAdCreativeInsight 查询 $creativeDataQuery = BpsAdCreativeInsight::alias('i') ->join('bps.bps_ads_creative c', 'i.creative_id = c.creative_id', 'LEFT'); // 联接 bps_ads_creative 表 if ($platformType != 0) { $creativeDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选 } // 2. 日期范围筛选 if ($startDate && $endDate) { $creativeDataQuery->whereBetween('i.date', [$startDate, $endDate]); } // 3. 客户 ID 过滤(如果提供了) if (!empty($customerIds)) { $creativeDataQuery->whereIn('i.account_id', $customerIds); } else { return $countOnly ? 0 : [ 'data' => [], 'total' => 0, 'statistics' => [], 'pagination' => [ 'startIndex' => 0, 'maxResults' => $pageSize, 'count' => 0, 'pageNo' => 1, 'pageSize' => $pageSize ] ]; } // 仅返回记录数时的逻辑 if ($countOnly) { return $creativeDataQuery->count('distinct(i.creative_id)') ?: 0; } // 4. 关键词过滤 if ($keyword) { // $creativeDataQuery->where('c.name', 'like', '%' . $keyword . '%'); // 在 bps_ads_creative 表中查找关键词 $creativeDataQuery->whereRaw('LOWER(c.name) LIKE ?', ['%' . strtolower($keyword) . '%']); // 在 bps_ads_creative 表中查找关键词 } // 5. 数据聚合(按 creative_id 和其他字段) $creativeDataQuery->group('i.creative_id, i.platform, i.account_id, c.name, c.type,c.url, c.thumbnail_url') // 按需要的字段分组 ->field([ 'i.creative_id', 'i.platform', 'c.name AS creative_name', // 从 bps_ads_creative 表中选择 name 'c.url AS creative_url', // 从 bps_ads_creative 表中选择 url 'c.type AS creative_type', // 从 bps_ads_creative 表中选择 url 'c.thumbnail_url', // 从 bps_ads_creative 表中选择 thumbnail_url ThinkDb::raw('COALESCE(SUM(i.spend) / 1000000, 0) AS total_spend'), ThinkDb::raw('COALESCE(SUM(i.purchases_value) / 1000000, 0) AS total_purchases_value'), ThinkDb::raw('COALESCE(SUM(i.purchases), 0) AS total_purchases'), ThinkDb::raw('COALESCE(SUM(i.revenue) / 1000000, 0) AS total_revenue'), ThinkDb::raw('COALESCE(SUM(i.impressions), 0) AS total_impressions'), ThinkDb::raw('COALESCE(SUM(i.clicks), 0) AS total_clicks'), ThinkDb::raw('COALESCE(SUM(i.link_clicks), 0) AS total_link_clicks'), ThinkDb::raw('COALESCE(SUM(i.adds_to_cart), 0) AS total_adds_to_cart'), ThinkDb::raw('COALESCE(SUM(i.outbound_clicks), 0) AS total_outbound_clicks'), ThinkDb::raw('COALESCE(SUM(i.video_25),0) AS video_25'), ThinkDb::raw('COALESCE(SUM(i.video_50),0) AS video_50'), ThinkDb::raw('COALESCE(SUM(i.video_75),0) AS video_75'), ThinkDb::raw('COALESCE(SUM(i.video_100),0) AS video_100'), ThinkDb::raw('COALESCE(SUM(i.video_play),0) AS video_plays'), ThinkDb::raw('-1 AS hold_rate') ]); // 根据 $requireSpend 参数决定是否添加 SUM(i.spend) > 0 的条件 if ($requireSpend) { $creativeDataQuery->having('SUM(i.spend) > 0'); } // 6. 执行查询并获取聚合结果 // $aggregatedData = $creativeDataQuery->select(); // 6. 执行查询并获取聚合结果 $aggregatedData = $creativeDataQuery->select()->toArray(); // 转换为数组处理 $totalRecords = count($aggregatedData); // 总记录数 // 计算分页起始索引 $startIndex = ($page - 1) * $pageSize; // 打印调试 SQL 查询 //$sql = $creativeDataQuery->getLastSql(); // dump($sql); // 7. 初始化广告创意的汇总数据和统计数据 $creativeSummaryData = []; // $statisticsData = $this->initializeStatistics(); $statisticsData = []; // 8. 遍历查询结果并计算每个 creative 的相关统计数据 foreach ($aggregatedData as $creativeData) { // 初始化该 creative_id 的数据(如果不存在) $creativeId = $creativeData['creative_id']; if (!isset($creativeSummaryData[$creativeId])) { $creativeSummaryData[$creativeId] = [ 'creative_id' => $creativeData['creative_id'], 'platform' => $creativeData['platform'], 'creative' => $creativeData['creative_name'], 'creative_type' => $creativeData['creative_type'], 'creative_url' => $creativeData['creative_url'] ?? '', 'thumbnail_url' => $creativeData['thumbnail_url'] ?? '', 'title' => $creativeData['title'] ?? '', 'spend' => 0, 'purchases_value' => 0, 'roas' => 0, 'cpa' => '-', 'cpc_link_click' => '-', 'cpm' => '-', 'cpc_all' => '-', 'aov' => '-', 'click_to_atc_ratio' => '-', 'atc_to_purchase_ratio' => '-', 'purchases' => 0, 'first_frame_retention' => '-', 'thumbstop' => '-', 'ctr_outbound' => '-', 'click_to_purchase' => '-', 'ctr_all' => '-', 'video_25' => 0, 'video_50' => 0, 'video_75' => 0, 'video_100' => 0, 'video_plays' => 0, 'video_plays_25_rate' => '-', 'video_plays_50_rate' => '-', 'video_plays_75_rate' => '-', 'video_plays_100_rate' => '-', 'hold_rate' => '-', 'total_conversions_value' => 0, 'total_conversions' => 0, 'impressions' => 0, 'clicks' => 0, 'link_clicks' => 0, 'outbound_clicks' => 0, 'adds_to_cart' => 0, 'revenue' => 0, 'ad_count' => 0 ]; } // 更新该 creative_id 的统计数据 $creativeSummaryData[$creativeId]['spend'] += $creativeData['total_spend']; $creativeSummaryData[$creativeId]['purchases_value'] += $creativeData['total_purchases_value']; $creativeSummaryData[$creativeId]['purchases'] += $creativeData['total_purchases']; $creativeSummaryData[$creativeId]['impressions'] += $creativeData['total_impressions']; $creativeSummaryData[$creativeId]['clicks'] += $creativeData['total_clicks']; $creativeSummaryData[$creativeId]['link_clicks'] += $creativeData['total_link_clicks']; $creativeSummaryData[$creativeId]['outbound_clicks'] += $creativeData['total_outbound_clicks']; $creativeSummaryData[$creativeId]['adds_to_cart'] += $creativeData['total_adds_to_cart']; $creativeSummaryData[$creativeId]['revenue'] += $creativeData['total_revenue']; $creativeSummaryData[$creativeId]['video_25'] += $creativeData['video_25']; $creativeSummaryData[$creativeId]['video_50'] += $creativeData['video_50']; $creativeSummaryData[$creativeId]['video_75'] += $creativeData['video_75']; $creativeSummaryData[$creativeId]['video_100'] += $creativeData['video_100']; $creativeSummaryData[$creativeId]['video_plays'] += $creativeData['video_plays']; // 填充广告计数 // $creativeSummaryData[$creativeData->creative_id]['ad_count'] = rand(10, 200); // 每个 creative_id 对应一个广告 } // dump($creativeSummaryData); // 汇总总体统计数据 $statisticsData['spend'] = array_sum(array_column($creativeSummaryData, 'spend')); $statisticsData['purchases_value'] = array_sum(array_column($creativeSummaryData, 'purchases_value')); $statisticsData['purchases'] = array_sum(array_column($creativeSummaryData, 'purchases')); $statisticsData['impressions'] = array_sum(array_column($creativeSummaryData, 'impressions')); $statisticsData['clicks'] = array_sum(array_column($creativeSummaryData, 'clicks')); $statisticsData['link_clicks'] = array_sum(array_column($creativeSummaryData, 'link_clicks')); $statisticsData['outbound_clicks'] = array_sum(array_column($creativeSummaryData, 'outbound_clicks')); $statisticsData['adds_to_cart'] = array_sum(array_column($creativeSummaryData, 'adds_to_cart')); $statisticsData['revenue'] = array_sum(array_column($creativeSummaryData, 'revenue')); $statisticsData['video_25'] = array_sum(array_column($creativeSummaryData, 'video_25')); $statisticsData['video_50'] = array_sum(array_column($creativeSummaryData, 'video_50')); $statisticsData['video_75'] = array_sum(array_column($creativeSummaryData, 'video_75')); $statisticsData['video_100'] = array_sum(array_column($creativeSummaryData, 'video_100')); $statisticsData['video_plays'] = array_sum(array_column($creativeSummaryData, 'video_plays')); // 汇总统计数据 $statistics = [ 'spend' => '$' . number_format($statisticsData['spend'], 2), // 格式化金额 'purchases_value' => '$' . number_format($statisticsData['purchases_value'], 2), // 格式化金额 'roas' => $statisticsData['spend'] == 0 ? '-' : round($statisticsData['purchases_value'] / $statisticsData['spend'], 2) . 'x', 'cpa' => $statisticsData['purchases'] == 0 ? '-' : '$' . number_format($statisticsData['spend'] / $statisticsData['purchases'], 2), 'cpc_link_click' => $statisticsData['link_clicks'] == 0 ? '-' : '$' . number_format($statisticsData['spend'] / $statisticsData['link_clicks'], 2), 'cpm' => $statisticsData['impressions'] == 0 ? '-' : '$' . number_format($statisticsData['spend'] * 1000 / $statisticsData['impressions'], 2), // sum(spend) * 1000 / sum(Impression) 'cpc_all' => $statisticsData['clicks'] == 0 ? '-' : '$' . number_format($statisticsData['spend'] / $statisticsData['clicks'], 2), 'aov' => $statisticsData['purchases'] == 0 ? '-' : '$' . number_format($statisticsData['purchases_value'] / $statisticsData['purchases'], 2), // 格式化金额 'click_to_atc_ratio' => ($statisticsData['clicks'] > 0) ? number_format(($statisticsData['adds_to_cart'] / $statisticsData['clicks']) * 100, 2) . '%' : '-', 'atc_to_purchase_ratio' => ($statisticsData['adds_to_cart'] > 0) ? number_format(($statisticsData['purchases'] / $statisticsData['adds_to_cart']) * 100, 2) . '%' : '-', 'purchases' => $statisticsData['purchases'], 'first_frame_retention' => self::calculateFirstFrameRetention($statisticsData['impressions'], $statisticsData['video_plays'], $statisticsData['video_25']), 'thumbstop' => self::calculateThumbstop($statisticsData['impressions'], $statisticsData['video_plays'], $statisticsData['video_25']), 'ctr_outbound' => ($statisticsData['impressions'] > 0) ? number_format(($statisticsData['outbound_clicks'] / $statisticsData['impressions']) * 100, 2) . '%' : '-', // 格式化为百分比 'click_to_purchase' => ($statisticsData['purchases'] > 0) ? number_format(($statisticsData['outbound_clicks'] / $statisticsData['purchases']) * 100, 2) . '%' : '-', 'ctr_all' => ($statisticsData['impressions'] > 0) ? number_format(($statisticsData['clicks'] / $statisticsData['impressions']) * 100, 2) . '%' : '-', // 格式化为百分比 'video_plays_25_rate' => ($statisticsData['video_plays'] > 0) ? number_format(($statisticsData['video_25'] / $statisticsData['video_plays']) * 100, 2) . '%' : '-', 'video_plays_50_rate' => ($statisticsData['video_plays'] > 0) ? number_format(($statisticsData['video_50'] / $statisticsData['video_plays']) * 100, 2) . '%' : '-', 'video_plays_75_rate' => ($statisticsData['video_plays'] > 0) ? number_format(($statisticsData['video_75'] / $statisticsData['video_plays']) * 100, 2) . '%' : '-', 'video_plays_100_rate' => ($statisticsData['video_plays'] > 0) ? number_format(($statisticsData['video_100'] / $statisticsData['video_plays']) * 100, 2) . '%' : '-', 'hold_rate' => self::calculateHoldRate($statisticsData['video_25'], $statisticsData['video_50'], $statisticsData['video_75'], $statisticsData['video_100']),// 格式化百分比 ]; // 获取每个 creative_id 对应的广告数量 $creativeIds_from_google = array_keys(array_filter($creativeSummaryData, function ($item) { return $item['platform'] == 2; })); if (!empty($creativeIds_from_google)) { // 将整数日期转为 Y-m-d 格式 $formattedStartDate = DateTime::createFromFormat('Ymd', $startDate)->format('Y-m-d'); $formattedEndDate = DateTime::createFromFormat('Ymd', $endDate)->format('Y-m-d'); // 查询满足条件的 asset_id 和唯一 ad_id 计数 $ad_count_google = AssetRelation::whereIn('asset_id', $creativeIds_from_google) ->whereBetween('date', [$formattedStartDate, $formattedEndDate]) ->group('asset_id') ->column('COUNT(DISTINCT ad_id) AS ad_count', 'asset_id'); } $creativeIds_from_fb_tk = array_keys(array_filter($creativeSummaryData, function ($item) { return ($item['platform'] == 1 || $item['platform'] == 3); })); if (!empty($creativeIds_from_fb_tk)) { $ad_count_fb_tk = BpsAdAd::whereIn('creative_id', $creativeIds_from_fb_tk) ->group('creative_id') ->column('COUNT(DISTINCT ad_id) as ad_count', 'creative_id'); } // 确保变量存在,避免未定义错误 $ad_count_fb_tk = $ad_count_fb_tk ?? []; $ad_count_google = $ad_count_google ?? []; // 合并数组 $ad_count_combined = $ad_count_fb_tk + $ad_count_google; //dump($ad_count_combined); // dump($creativeIds_from_fb_tk); // 将广告数量填充到对应的 creativeSummaryData 中 foreach ($creativeSummaryData as $creativeId => &$creativeData) { $creativeData['ad_count'] = $ad_count_combined[$creativeId] ?? 0; // 如果没有匹配记录,默认填充 0 // dump($creativeData); } unset($creativeData); // 解除引用 // dump($creativeSummaryData); // 格式化返回的创意数据 $formattedData = array_map(function ($item) { return [ 'creative_id' => $item['creative_id'], 'creative' => $item['creative'] ?: '-', 'platform' => $item['platform'] ?: '-', 'creative_type' => $item['creative_type'], 'creative_url' => $item['creative_url'], 'thumbnail_url' => $item['thumbnail_url'], 'spend' => '$' . number_format($item['spend'], 2), 'purchases_value' => '$' . number_format($item['purchases_value'], 2), 'roas' => $item['spend'] == 0 ? '-' : round($item['purchases_value'] / $item['spend'], 2) . 'x', 'cpa' => $item['purchases'] == 0 ? '-' : '$' . number_format($item['spend'] / $item['purchases'], 2), 'cpc_link_click' => $item['link_clicks'] == 0 ? '-' : '$' . number_format($item['spend'] / $item['link_clicks'], 2), 'cpm' => $item['impressions'] == 0 ? '-' : '$' . number_format($item['spend'] * 1000 / $item['impressions'], 2), 'cpc_all' => $item['clicks'] == 0 ? '-' : '$' . number_format($item['spend'] / $item['clicks'], 2), 'aov' => $item['purchases'] == 0 ? '-' : '$' . number_format($item['purchases_value'] / $item['purchases'], 2), 'click_to_atc_ratio' => ($item['clicks'] > 0) ? number_format(($item['adds_to_cart'] / $item['clicks']) * 100, 2) . '%' : '-', 'atc_to_purchase_ratio' => ($item['adds_to_cart'] > 0) ? number_format(($item['purchases'] / $item['adds_to_cart']) * 100, 2) . '%' : '-', 'purchases' => $item['purchases'], 'first_frame_retention' => self::calculateFirstFrameRetention($item['impressions'], $item['video_plays'], $item['video_25']), 'thumbstop' => self::calculateThumbstop($item['impressions'], $item['video_plays'], $item['video_25']), 'ctr_outbound' => ($item['impressions'] > 0) ? number_format(($item['outbound_clicks'] / $item['impressions']) * 100, 2) . '%' : '-', // 格式化为百分比 'click_to_purchase' => ($item['purchases'] > 0) ? number_format(($item['outbound_clicks'] / $item['purchases']) * 100, 2) . '%' : '-', 'ctr_all' => ($item['impressions'] > 0) ? number_format(($item['clicks'] / $item['impressions']) * 100, 2) . '%' : '-', 'total_conversions_value' => '$' . number_format($item['purchases_value'], 2), //准备删除 'impressions' => $item['impressions'], 'video_plays_25_rate' => ($item['video_plays'] > 0) ? number_format($item['video_25'] / $item['video_plays'], 2) . '%' : '-', 'video_plays_50_rate' => ($item['video_plays'] > 0) ? number_format($item['video_50'] / $item['video_plays'], 2) . '%' : '-', 'video_plays_75_rate' => ($item['video_plays'] > 0) ? number_format($item['video_75'] / $item['video_plays'], 2) . '%' : '-', 'video_plays_100_rate' => ($item['video_plays'] > 0) ? number_format($item['video_100'] / $item['video_plays'], 2) . '%' : '-', 'hold_rate' => self::calculateHoldRate($item['video_25'], $item['video_50'], $item['video_75'], $item['video_100']), 'ad_count' => $item['ad_count'], // 添加更多的格式化字段 ]; }, $creativeSummaryData); // $formattedData = array_values($creativeSummaryData); // 未分页处理 $pagedData = array_slice(array_values($formattedData), $startIndex, $pageSize); // 分页处理 // 9. 返回分页数据 return [ 'data' => $pagedData, 'total' => count($creativeSummaryData), 'statistics' => $statistics, // 汇总的统计数据 'pagination' => [ 'startIndex' => ($page - 1) * $pageSize, 'maxResults' => $pageSize, 'count' => count($creativeSummaryData), 'pageNo' => $page, 'pageSize' => $pageSize, 'pages' => ceil(count($creativeSummaryData) / $pageSize) ] ]; } public static function calculateFirstFrameRetention($impressions, $video_play, $video_25) { // 如果 impressions 为 0 或 NULL,直接返回 0 if ($impressions == 0 || $video_play == 0) { return '-'; } // 处理 NULL 值,用 0 替换 $video_25 = $video_25 ?? 0; // 计算公式 return number_format(($video_play / $impressions) * ($video_25 / $video_play) * 100, 2) . '%'; } public static function calculateThumbstop($impressions, $video_play, $video_25) { // 如果 impressions 为 0 或 NULL,直接返回 0 if ($impressions == 0) { return '-'; } // 处理 NULL 值,用 0 替换 $video_play = $video_play ?? 0; $video_25 = $video_25 ?? 0; // 计算公式 return number_format((($video_play * 0.7) + ($video_25 * 0.3)) / $impressions * 100, 2) . '%'; } public static function calculateHoldRate($video_25, $video_50, $video_75, $video_100) { // 如果 video_25 为 0 或 NULL,直接返回 0 if ($video_25 == 0) { return '-'; } // 处理 NULL 值,用 0 替换 $video_25 = $video_25 ?? 0; $video_50 = $video_50 ?? 0; $video_75 = $video_75 ?? 0; $video_100 = $video_100 ?? 0; // 计算公式 $numerator = $video_25 + $video_50 + $video_75 + $video_100; $denominator = $video_25 * 4; return number_format(($numerator / $denominator) * 100, 2) . '%'; } /** * 初始化统计数据 */ private function initializeStatistics() { return [ 'impressions' => 0, 'clicks' => 0, 'revenue' => 0, 'spend' => 0, 'purchases_value' => 0, 'roas' => 0, 'cpa' => '-', 'cpc_link_click' => '-', 'cpm' => '-', 'cpc_all' => '-', 'aov' => '-', 'click_to_atc_ratio' => '-', 'atc_to_purchase_ratio' => '-', 'purchases' => 0, 'first_frame_retention' => '-', 'thumbstop' => '-', 'ctr_outbound' => '-', 'click_to_purchase' => '-', 'ctr_all' => '-', 'video_plays_25_rate' => 0, 'video_plays_50_rate' => 0, 'video_plays_75_rate' => 0, 'video_plays_100_rate' => 0, 'hold_rate' => '-', // Add other stats as necessary ]; } public function getAdcycleInsight($platformType, $customerIds, $cycle, $startDate = null, $endDate = null) { // 1. 查询全部数据集 $adcycleDataQuery = BpsAdInsight::alias('i'); if ($platformType != 0) { $adcycleDataQuery->where('i.platform', $platformType); // 只有 platformType 不为 0 时才添加筛选 } //dump($customerIds); // 2. 客户 ID 过滤(如果提供了) if (!empty($customerIds)) { $adcycleDataQuery->whereIn('i.account_id', $customerIds); } else { return [ 'data' => [], 'total' => 0 ]; } // 3. 时间范围筛选 if ($startDate && $endDate) { $adcycleDataQuery->whereBetween('i.date', [$startDate, $endDate]); } // 4. 获取数据并按日期聚合 $adcycleData = $adcycleDataQuery->field([ 'i.date', ThinkDb::raw('COALESCE(SUM(i.spend) / 1000000, 0) as spend'), ThinkDb::raw('COALESCE(SUM(i.revenue), 0) as revenue') ]) ->group('i.date') // 按日期进行分组 ->select(); // dump(ThinkDb::getLastSql()); // 5. 处理数据,按照周期进行分组 $processedData = $this->processDataByCycle($adcycleData->toArray(), $cycle); // 6. 返回处理后的数据 return $processedData; } /** * 按周期处理数据 * * @param array $adcycleData 原始数据 * @param string $cycle 周期类型(daily, weekly, monthly) * @return array 按照周期分组后的数据 */ private function processDataByCycle($adcycleData, $cycle) { // 排序 adcycleData 数组,确保按照日期升序排列 usort($adcycleData, function ($a, $b) { return $a['date'] <=> $b['date']; // 按照 'YYYYMMDD' 字符顺序排序 }); $groupedData = []; foreach ($adcycleData as $data) { // 根据周期类型,调整日期分组 switch ($cycle) { case 1: // 将 'YYYYMMDD' 格式的日期转换为 'YYYY-MM-DD' $date = DateTime::createFromFormat('Ymd', $data['date']); // 转换为 DateTime 对象 $key = $date ? $date->format('Y-m-d') : ''; // 格式化为 'YYYY-MM-DD' break; case 2: // 使用 ISO 周格式来分组 $key = $this->getWeekFromDate($data['date']); break; case 3: // 按年-月格式分组 $key = $this->getMonthFromDate($data['date']); break; default: throw new \InvalidArgumentException("Invalid cycle value. Use 'daily', 'weekly' or 'monthly'."); } // 汇总数据 if (!isset($groupedData[$key])) { $groupedData[$key] = [ 'spend' => 0, 'revenue' => 0, // 'conversions_value' => 0, // 'conversions' => 0, // 'impressions' => 0, // 'clicks' => 0 ]; } // 累加数据 $groupedData[$key]['spend'] += $data['spend']; $groupedData[$key]['revenue'] += $data['revenue']; // $groupedData[$key]['conversions_value'] += $data->purchases_value; // $groupedData[$key]['conversions'] += $data->purchases; // $groupedData[$key]['impressions'] += $data->impressions; // $groupedData[$key]['clicks'] += $data->clicks; } // 格式化返回数据 $formattedData = []; foreach ($groupedData as $key => $values) { $roas = $values['spend'] > 0 ? $values['revenue'] / $values['spend'] : 0; $formattedData[] = [ 'date' => $key, 'spend' => round($values['spend'], 2), 'roas' => $roas > 0 ? round($roas, 2) : 0 ]; } // 如果没有数据,返回空数组并提供默认的分页信息 if (empty($formattedData)) { return [ 'data' => [], 'total' => 0, ]; } // 返回格式化后的数据 return [ 'data' => $formattedData, 'total' => count($formattedData), ]; } /** * 获取周数(基于 ISO 周格式) * * @param int $date 日期(格式:YYYYMMDD) * @return string ISO 周格式(YYYY-Www) */ private function getWeekFromDate($date) { // 将日期转换为 PHP DateTime 对象 $dateStr = (string)$date; $dateObj = \DateTime::createFromFormat('Ymd', $dateStr); // 获取 ISO 周格式 return $dateObj->format('o-W') . 'W'; } /** * 获取月分(格式:YYYY-MM) * * @param int $date 日期(格式:YYYYMMDD) * @return string 月份格式(YYYY-MM) */ private function getMonthFromDate($date) { // 将日期转换为 PHP DateTime 对象 $dateStr = (string)$date; $dateObj = \DateTime::createFromFormat('Ymd', $dateStr); // 获取年-月格式 return $dateObj->format('Y-m'); } public static function getPlatformType($thirdType) { $platformMapping = [ 'facebook' => 1, 'google' => 2, 'tiktok' => 3, ]; return $platformMapping[$thirdType] ?? null; // 如果不存在的第三方类型,返回 null } private static function getLastQueryParams() { $query = ThinkDb::getLastQuery(); return $query ? $query->getOptions()['where'] : []; } // 修改enableSqlLog方法 public static function enableSqlLog() { if (!self::$sqlLogEnabled) { ThinkDb::listen(function($sql, $time, $explain) { $logData = [ 'sql' => $sql, 'time' => $time . 'ms', 'params' => self::getLastQueryParams(), 'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5) ]; // 仅在非空时记录 EXPLAIN if (!empty($explain)) { $logData['explain'] = $explain; } Log::channel('sql')->debug('SQL', $logData); }); self::$sqlLogEnabled = true; } } }