where('createtime', 'between', [$todayStart, $todayEnd])->count(); $yesterdayPost = (int)Db::name('fc_post')->where('createtime', 'between', [$yesterdayStart, $yesterdayEnd])->count(); $totalPost = (int)Db::name('fc_post')->count(); // 订单额与笔数(仅统计已支付订单) $todayOrderAmount = round((float)Db::name('fc_order')->where('status', 'paid') ->whereTime('paytime|createtime', 'between', [$todayStart, $todayEnd])->sum('amount'), 2); $yesterdayOrderAmount = round((float)Db::name('fc_order')->where('status', 'paid') ->whereTime('paytime|createtime', 'between', [$yesterdayStart, $yesterdayEnd])->sum('amount'), 2); $totalOrderAmount = round((float)Db::name('fc_order')->where('status', 'paid')->sum('amount'), 2); $todayOrderCount = (int)Db::name('fc_order')->where('status', 'paid') ->whereTime('paytime|createtime', 'between', [$todayStart, $todayEnd])->count(); $totalOrderCount = (int)Db::name('fc_order')->where('status', 'paid')->count(); // 用户与会员 $todayUser = (int)Db::name('user')->where('createtime', 'between', [$todayStart, $todayEnd])->count(); $yesterdayUser = (int)Db::name('user')->where('createtime', 'between', [$yesterdayStart, $yesterdayEnd])->count(); $totalUser = (int)Db::name('user')->count(); $memberCount = (int)Db::name('fc_user_ext')->where('is_member', 1)->count(); $merchantCount = (int)Db::name('fc_merchant')->count(); $recommendMerchantCount = (int)Db::name('fc_merchant')->where('recommend', 1)->count(); // 浏览量 PV $todayViews = (int)Db::name('fc_view_log')->where('createtime', 'between', [$todayStart, $todayEnd])->count(); $yesterdayViews = (int)Db::name('fc_view_log')->where('createtime', 'between', [$yesterdayStart, $yesterdayEnd])->count(); $totalViews = (int)Db::name('fc_post')->sum('views'); $metrics = [ 'todayPost' => $todayPost, 'yesterdayPost' => $yesterdayPost, 'postDelta' => $todayPost - $yesterdayPost, 'totalPost' => $totalPost, 'todayOrderAmount' => $todayOrderAmount, 'yesterdayOrderAmount' => $yesterdayOrderAmount, 'orderDelta' => round($todayOrderAmount - $yesterdayOrderAmount, 2), 'totalOrderAmount' => $totalOrderAmount, 'todayOrderCount' => $todayOrderCount, 'totalOrderCount' => $totalOrderCount, 'todayUser' => $todayUser, 'yesterdayUser' => $yesterdayUser, 'userDelta' => $todayUser - $yesterdayUser, 'totalUser' => $totalUser, 'memberCount' => $memberCount, 'merchantCount' => $merchantCount, 'recommendMerchant' => $recommendMerchantCount, 'todayViews' => $todayViews, 'yesterdayViews' => $yesterdayViews, 'viewsDelta' => $todayViews - $yesterdayViews, 'totalViews' => $totalViews, ]; /* ========================================================= * 2. 待办督办中心(待审核事项与预警) * ========================================================= */ $pendingPost = (int)Db::name('fc_post')->where('audit_status', 0)->count(); $pendingMerchant = (int)Db::name('fc_merchant')->where('audit_status', 0)->count(); $pendingWithdraw = (int)Db::name('fc_withdraw')->where('status', 'auditing')->count(); $pendingWithdrawAmount = round((float)Db::name('fc_withdraw')->where('status', 'auditing')->sum('amount'), 2); $pendingReport = (int)Db::name('fc_report')->where('status', 'pending')->count(); $pendingTotal = $pendingPost + $pendingMerchant + $pendingWithdraw + $pendingReport; $pending = [ 'total' => $pendingTotal, 'post' => $pendingPost, 'merchant' => $pendingMerchant, 'withdraw' => $pendingWithdraw, 'withdrawAmount' => $pendingWithdrawAmount, 'report' => $pendingReport, ]; /* ========================================================= * 3. 平台生态与分销资产 * ========================================================= */ $commissionPending = round((float)Db::name('fc_commission')->where('status', 0)->sum('amount'), 2); $commissionSettled = round((float)Db::name('fc_commission')->where('status', 1)->sum('amount'), 2); $distributorCount = (int)Db::name('fc_dist_relation')->distinct(true)->field('parent_id')->count(); $ecosystem = [ 'commissionPending' => $commissionPending, 'commissionSettled' => $commissionSettled, 'distributorCount' => $distributorCount, 'memberRate' => $totalUser > 0 ? round(($memberCount / $totalUser) * 100, 1) : 0, ]; /* ========================================================= * 4. 营收构成分析(按订单类型统计已支付实收) * ========================================================= */ $orderTypeLabels = [ 'member' => 'VIP会员购买', 'post' => '发布信息付费', 'payview' => '付费查看电话', 'recharge' => '钱包充值', 'top' => '置顶推广套餐', 'refresh' => '信息刷新', ]; $rawRevenue = Db::name('fc_order') ->where('status', 'paid') ->fieldRaw("type, count(*) as count, sum(amount) as amount") ->group('type') ->order('amount', 'desc') ->select(); $revenueTotalSum = 0; $revenueTypes = []; foreach ($rawRevenue as $r) { $amt = round((float)$r['amount'], 2); $cnt = (int)$r['count']; $label = isset($orderTypeLabels[$r['type']]) ? $orderTypeLabels[$r['type']] : ($r['type'] ? $r['type'] : '其他服务'); $revenueTotalSum += $amt; $revenueTypes[] = [ 'type' => $r['type'], 'label' => $label, 'count' => $cnt, 'amount' => $amt, ]; } foreach ($revenueTypes as &$item) { $item['percent'] = $revenueTotalSum > 0 ? round(($item['amount'] / $revenueTotalSum) * 100, 1) : 0; } unset($item); /* ========================================================= * 5. 热门分类发帖分布与同城区域排行 * ========================================================= */ // 一级分类发帖分布 $rawCategories = Db::name('fc_category') ->alias('c') ->join('__FC_POST__ p', '(p.category_id = c.id OR p.category_id IN (SELECT sub.id FROM ' . config('database.prefix') . 'fc_category sub WHERE sub.pid = c.id))', 'LEFT') ->where('c.pid', 0) ->field('c.id, c.name, count(p.id) as post_count') ->group('c.id') ->order('post_count', 'desc') ->select(); $categoryTotal = 0; $categoryRanks = []; foreach ($rawCategories as $c) { $cnt = (int)$c['post_count']; $categoryTotal += $cnt; $categoryRanks[] = [ 'id' => $c['id'], 'name' => $c['name'], 'count' => $cnt, ]; } foreach ($categoryRanks as &$cItem) { $cItem['percent'] = $categoryTotal > 0 ? round(($cItem['count'] / $categoryTotal) * 100, 1) : 0; } unset($cItem); // 同城热门地区 Top 5 $rawAreas = Db::name('fc_post') ->fieldRaw("IF(area != '', area, city) as region_name, count(*) as count") ->where("area != '' OR city != ''") ->group('region_name') ->order('count', 'desc') ->limit(5) ->select(); $areaRanks = []; $areaMaxCount = 1; foreach ($rawAreas as $ar) { $cnt = (int)$ar['count']; if ($cnt > $areaMaxCount) { $areaMaxCount = $cnt; } $areaRanks[] = [ 'region_name' => $ar['region_name'] ? $ar['region_name'] : '未知区域', 'count' => $cnt, ]; } foreach ($areaRanks as &$arItem) { $arItem['percent'] = round(($arItem['count'] / $areaMaxCount) * 100, 1); } unset($arItem); /* ========================================================= * 6. 多时段趋势数据计算(近 7 天 / 14 天 / 30 天) * ========================================================= */ $maxTrendDays = 30; $trendStart = $todayStart - ($maxTrendDays - 1) * 86400; // 发帖趋势 $postsMap = []; $postsQuery = Db::name('fc_post') ->where('createtime', 'between', [$trendStart, $todayEnd]) ->fieldRaw("FROM_UNIXTIME(`createtime`,'%Y-%m-%d') AS day, COUNT(*) AS num") ->group('day') ->select(); foreach ($postsQuery as $row) { $postsMap[$row['day']] = (int)$row['num']; } // 订单额趋势 $ordersMap = []; $ordersQuery = Db::name('fc_order') ->where('status', 'paid') ->whereTime('paytime|createtime', 'between', [$trendStart, $todayEnd]) ->fieldRaw("FROM_UNIXTIME(IFNULL(`paytime`,`createtime`),'%Y-%m-%d') AS day, SUM(`amount`) AS num") ->group('day') ->select(); foreach ($ordersQuery as $row) { $ordersMap[$row['day']] = round((float)$row['num'], 2); } // 新增用户趋势 $usersMap = []; $usersQuery = Db::name('user') ->where('createtime', 'between', [$trendStart, $todayEnd]) ->fieldRaw("FROM_UNIXTIME(`createtime`,'%Y-%m-%d') AS day, COUNT(*) AS num") ->group('day') ->select(); foreach ($usersQuery as $row) { $usersMap[$row['day']] = (int)$row['num']; } // 浏览量趋势 $viewsMap = []; $viewsQuery = Db::name('fc_view_log') ->where('createtime', 'between', [$trendStart, $todayEnd]) ->fieldRaw("FROM_UNIXTIME(`createtime`,'%Y-%m-%d') AS day, COUNT(*) AS num") ->group('day') ->select(); foreach ($viewsQuery as $row) { $viewsMap[$row['day']] = (int)$row['num']; } // 计算 7/14/30 天数据包 $ranges = [7, 14, 30]; $allTrends = []; foreach ($ranges as $span) { $startTs = $todayStart - ($span - 1) * 86400; $dates = []; $posts = []; $orders = []; $users = []; $views = []; $rows = []; for ($i = 0; $i < $span; $i++) { $day = date('Y-m-d', $startTs + $i * 86400); $p = isset($postsMap[$day]) ? $postsMap[$day] : 0; $o = isset($ordersMap[$day]) ? $ordersMap[$day] : 0; $u = isset($usersMap[$day]) ? $usersMap[$day] : 0; $v = isset($viewsMap[$day]) ? $viewsMap[$day] : 0; $dates[] = $day; $posts[] = $p; $orders[] = $o; $users[] = $u; $views[] = $v; $rows[] = [ 'date' => $day, 'posts' => $p, 'orders' => $o, 'users' => $u, 'views' => $v, ]; } $allTrends[$span] = [ 'dates' => $dates, 'posts' => $posts, 'orders' => $orders, 'users' => $users, 'views' => $views, 'rows' => array_reverse($rows), ]; } // 如果是 AJAX 请求局部刷新趋势,可直接返回对应时段数据 if ($this->request->isAjax() && $this->request->get('action') === 'trend') { $days = (int)$this->request->get('days', 14); $trendData = isset($allTrends[$days]) ? $allTrends[$days] : $allTrends[14]; return json(['code' => 1, 'msg' => 'ok', 'data' => $trendData]); } // 默认 14 天用于模板首屏初始化 $defaultTrend = $allTrends[14]; /* ========================================================= * 7. 视图变量绑定与前端配置传递 * ========================================================= */ $this->view->assign('metrics', $metrics); $this->view->assign('pending', $pending); $this->view->assign('ecosystem', $ecosystem); $this->view->assign('revenueTypes', $revenueTypes); $this->view->assign('categoryRanks', $categoryRanks); $this->view->assign('areaRanks', $areaRanks); $this->view->assign('trendRows', $defaultTrend['rows']); $this->view->assign('updatedAt', date('Y-m-d H:i')); // 向前端 RequireJS 传入完整时段数据包及分类分布,供平滑渲染交互 $this->assignconfig('allTrends', $allTrends); $this->assignconfig('revenueTypes', $revenueTypes); $this->assignconfig('categoryRanks', $categoryRanks); return $this->view->fetch(); } }