diff --git a/application/admin/controller/shopro/Dashboard.php b/application/admin/controller/shopro/Dashboard.php index 839e327..8158e09 100644 --- a/application/admin/controller/shopro/Dashboard.php +++ b/application/admin/controller/shopro/Dashboard.php @@ -16,6 +16,9 @@ use addons\shopro\service\SearchHistory; use addons\shopro\service\partner\RateService; use addons\shopro\service\partner\IdentityService; use addons\shopro\service\partner\PartnerConfig; +use app\admin\model\shopro\Withdraw; +use app\admin\model\shopro\Refund; +use app\admin\model\shopro\goods\SkuPrice as SkuPriceModel; class Dashboard extends Common { @@ -283,6 +286,261 @@ class Dashboard extends Common 'role_stats' => $roleStats, ]; + // 8. CFO 视角:资金浮存、提现出款风控与资产负债中枢 + $userCommissionFloat = (float) User::sum('commission'); + $userMoneyFloat = (float) User::sum('money'); + $totalFloatFunds = round($userCommissionFloat + $userMoneyFloat, 2); + + $totalWithdrawApplied = (float) Withdraw::sum('amount'); + $totalWithdrawPaid = (float) Withdraw::where('status', 2)->sum('paid_fee'); + $pendingWithdrawAmount = (float) Withdraw::where('status', 0)->sum('amount'); + $pendingWithdrawCount = (int) Withdraw::where('status', 0)->count(); + $withdrawCashRate = $totalPayout > 0 ? round(($totalWithdrawPaid / $totalPayout) * 100, 2) : 0.0; + + $totalRefundAmount = (float) Refund::where('status', 'completed')->sum('refund_fee'); + $totalRefundCount = (int) Refund::where('status', 'completed')->count(); + $refundRate = $totalGmv > 0 ? round(($totalRefundAmount / $totalGmv) * 100, 2) : 0.0; + $realNetCashFlow = round($totalGmv - $totalRefundAmount - $totalWithdrawPaid, 2); + + // 未来总承兑责任 (待释放消费券 + 马豆折算市值 + 用户未提现佣金) + $pendingVoucherLiability = $pendingVoucher; + $madowMarketLiability = $totalMadowCny; + $commissionLiability = $userCommissionFloat; + $totalFutureLiability = round($pendingVoucherLiability + $madowMarketLiability + $commissionLiability, 2); + + $financeOverview = [ + 'commission_float' => number_format($userCommissionFloat, 2, '.', ''), + 'user_balance_float' => number_format($userMoneyFloat, 2, '.', ''), + 'total_float_funds' => number_format($totalFloatFunds, 2, '.', ''), + 'total_withdraw_applied' => number_format($totalWithdrawApplied, 2, '.', ''), + 'total_withdraw_paid' => number_format($totalWithdrawPaid, 2, '.', ''), + 'pending_withdraw_amount' => number_format($pendingWithdrawAmount, 2, '.', ''), + 'pending_withdraw_count' => $pendingWithdrawCount, + 'withdraw_cash_rate' => $withdrawCashRate, + 'total_refund_amount' => number_format($totalRefundAmount, 2, '.', ''), + 'total_refund_count' => $totalRefundCount, + 'refund_rate' => $refundRate, + 'real_net_cash_flow' => number_format($realNetCashFlow, 2, '.', ''), + 'pending_voucher_liability' => number_format($pendingVoucherLiability, 2, '.', ''), + 'madow_market_liability' => number_format($madowMarketLiability, 2, '.', ''), + 'commission_liability' => number_format($commissionLiability, 2, '.', ''), + 'total_future_liability' => number_format($totalFutureLiability, 2, '.', ''), + ]; + + // 9. COO 视角:转化复购漏斗、合伙人30天掉队雷达与供应链库存安全 + // 9.1 用户购买频次聚合与转化漏斗 + $paidOrderUsers = Order::whereIn('status', [Order::STATUS_PAID, Order::STATUS_COMPLETED]) + ->group('user_id') + ->field('user_id, count(*) as buy_count') + ->select(); + + $paidUsersCount = count($paidOrderUsers); + $singleBuyCount = 0; + $repeatBuyCount = 0; + foreach ($paidOrderUsers as $buyer) { + if ((int)$buyer->buy_count > 1) { + $repeatBuyCount++; + } else { + $singleBuyCount++; + } + } + $payConversionRate = $totalRegisteredUsers > 0 ? round(($paidUsersCount / $totalRegisteredUsers) * 100, 1) : 0.0; + $repeatPurchaseRate = $paidUsersCount > 0 ? round(($repeatBuyCount / $paidUsersCount) * 100, 1) : 0.0; + $arpu = $paidUsersCount > 0 ? round($totalGmv / $paidUsersCount, 2) : 0.0; + + // 9.2 合伙人 30 天自购断档掉队雷达(安全活跃、7天即将断档预警、已断档失效) + $now = time(); + $activeDays = (int) PartnerConfig::get('partner_active_days', 30); + $warningThresholdDays = 7; + $activeWindow = $now - ($activeDays * 86400); + $warningWindow = $now - (($activeDays - $warningThresholdDays) * 86400); + + $activePartnersCount = 0; + $warningPartnersCount = 0; + $frozenPartnersCount = 0; + $warningList = []; + $frozenList = []; + + // 批量拉取合伙人用户信息,避免循环内查询 + $agentUserIds = array_column(collection($allAgents)->toArray(), 'user_id'); + $agentUserMap = []; + if (!empty($agentUserIds)) { + $agentUserRows = User::whereIn('id', $agentUserIds)->field('id, nickname, mobile, avatar')->select(); + foreach ($agentUserRows as $uRow) { + $agentUserMap[$uRow->id] = $uRow; + } + } + + foreach ($allAgents as $ag) { + $lastTime = (int) $ag->getData('last_self_order_time'); + $uid = $ag->user_id; + $u = $agentUserMap[$uid] ?? null; + $nickname = $u ? ($u->nickname ?: '用户#' . $uid) : ('用户#' . $uid); + $mobile = ($u && $u->mobile) ? substr_replace($u->mobile, '****', 3, 4) : '-'; + $avatar = $u ? ($u->avatar ?: '') : ''; + $identityData = IdentityService::build($ag); + $identityText = $identityData['identity_text'] ?: ('Lv.' . $ag->level); + + if ($lastTime > 0 && $lastTime >= $warningWindow) { + // 安全活跃期 (距今 < 23天) + $activePartnersCount++; + } elseif ($lastTime > 0 && $lastTime >= $activeWindow) { + // 7 天内即将断档预警 (距今 23~30天) + $warningPartnersCount++; + $daysLeft = max(1, (int) ceil(($lastTime + $activeDays * 86400 - $now) / 86400)); + $warningList[] = [ + 'user_id' => $uid, + 'nickname' => $nickname, + 'avatar' => $avatar, + 'mobile' => $mobile, + 'level_text' => $identityText, + 'last_order_date' => date('Y-m-d H:i', $lastTime), + 'days_left' => $daysLeft, + ]; + } else { + // 已断档冻结 (> 30天 或 从未自购) + $frozenPartnersCount++; + if (count($frozenList) < 10) { + $overdueDays = $lastTime > 0 ? max(1, (int) floor(($now - $lastTime) / 86400) - $activeDays) : 999; + $frozenList[] = [ + 'user_id' => $uid, + 'nickname' => $nickname, + 'avatar' => $avatar, + 'mobile' => $mobile, + 'level_text' => $identityText, + 'last_order_date' => $lastTime > 0 ? date('Y-m-d H:i', $lastTime) : '从未自购', + 'overdue_days' => $overdueDays == 999 ? '从未自购' : "逾期 {$overdueDays} 天", + ]; + } + } + } + + // 9.3 供应链库存安全与动销商品 + $onSaleGoodsCount = (int) Goods::where('status', 'up')->count(); + $totalStock = (int) SkuPriceModel::where('status', 'up')->sum('stock'); + $lowStockCount = (int) SkuPriceModel::alias('sp') + ->join('shopro_goods g', 'sp.goods_id = g.id') + ->where('g.status', 'up') + ->where('sp.status', 'up') + ->where('sp.stock', '<=', 10) + ->count(); + + $lowStockSkuList = SkuPriceModel::alias('sp') + ->join('shopro_goods g', 'sp.goods_id = g.id') + ->where('g.status', 'up') + ->where('sp.status', 'up') + ->where('sp.stock', '<=', 10) + ->field('sp.id as sku_id, sp.goods_id, g.title as goods_title, g.image as goods_image, sp.goods_sku_text, sp.stock, sp.price') + ->order('sp.stock', 'asc') + ->limit(10) + ->select(); + + $lowStockFormatted = []; + foreach ($lowStockSkuList as $lsku) { + $skuText = is_array($lsku->goods_sku_text) ? implode(' ', $lsku->goods_sku_text) : ($lsku->goods_sku_text ?: '默认单规格'); + $lowStockFormatted[] = [ + 'sku_id' => $lsku->sku_id, + 'goods_id' => $lsku->goods_id, + 'goods_title' => $lsku->goods_title, + 'goods_image' => $lsku->goods_image, + 'sku_text' => $skuText, + 'stock' => (int) $lsku->stock, + 'price' => $lsku->price, + ]; + } + + // 9.4 平台经营健康度评分模型 (满分 100) + // ① 拨比健康度 (30分) + $payoutScore = 30.0; + if ($totalGmv > 0) { + if ($payoutRate > 60) { + $payoutScore = 10.0; + } elseif ($payoutRate > 40) { + $payoutScore = 20.0; + } elseif ($payoutRate < 15) { + $payoutScore = 25.0; + } else { + $payoutScore = 30.0; + } + } + // ② 合伙人活跃健康度 (25分) + $partnerActiveRate = $totalAgents > 0 ? ($activePartnersCount / $totalAgents) : 0; + $activeScore = round($partnerActiveRate * 25, 1); + // ③ 用户复购粘性 (25分, 基准30%得满分) + $repeatScore = $paidUsersCount > 0 ? min(25.0, round(($repeatPurchaseRate / 30) * 25, 1)) : 15.0; + // ④ 资金履约安全度 (20分) + $safetyScore = 20.0; + if ($refundRate > 5.0) { + $safetyScore = 10.0; + } elseif ($refundRate > 2.0) { + $safetyScore = 15.0; + } else { + $safetyScore = 20.0; + } + + $healthTotalScore = round($payoutScore + $activeScore + $repeatScore + $safetyScore, 1); + $healthGrade = 'S·卓越'; + $healthGradeColor = '#52c41a'; + if ($healthTotalScore < 70) { + $healthGrade = 'C·预警'; + $healthGradeColor = '#f5222d'; + } elseif ($healthTotalScore < 80) { + $healthGrade = 'B·稳健'; + $healthGradeColor = '#fa8c16'; + } elseif ($healthTotalScore < 90) { + $healthGrade = 'A·良好'; + $healthGradeColor = '#1890ff'; + } + + $healthSuggestions = []; + if ($payoutRate <= 35) { + $healthSuggestions[] = "当前综合拨比为 {$payoutRate}%,处于精算设定的安全区间,净现金流沉淀稳固。"; + } else { + $healthSuggestions[] = "当前综合拨比为 {$payoutRate}%,略高于基准安全线,建议关注毛利率较高的商品排期。"; + } + if ($warningPartnersCount > 0) { + $healthSuggestions[] = "监测到有 {$warningPartnersCount} 位合伙人进入 7 天内即将断档预警期,建议及时定向唤醒催单。"; + } else { + $healthSuggestions[] = "合伙股东团队自购状态良好,暂无 7 天内即将断档的合伙人。"; + } + if ($lowStockCount > 0) { + $healthSuggestions[] = "仓储有 {$lowStockCount} 款商品 SKU 库存告急(≤10件),请及时补充库存避免断货缺货。"; + } else { + $healthSuggestions[] = "在售商品库存充沛,供应链运转健康。"; + } + if ($pendingWithdrawCount > 0) { + $healthSuggestions[] = "当前有 {$pendingWithdrawCount} 笔提现申请等待审核(¥{$financeOverview['pending_withdraw_amount']}),请及时审核保障出款体验。"; + } + + $operationOverview = [ + 'total_registered' => $totalRegisteredUsers, + 'paid_users_count' => $paidUsersCount, + 'pay_conversion_rate' => $payConversionRate, + 'single_buy_users_count' => $singleBuyCount, + 'repeat_buy_users_count' => $repeatBuyCount, + 'repeat_purchase_rate' => $repeatPurchaseRate, + 'arpu' => number_format($arpu, 2, '.', ''), + 'active_partners' => $activePartnersCount, + 'warning_partners' => $warningPartnersCount, + 'frozen_partners' => $frozenPartnersCount, + 'warning_list' => $warningList, + 'frozen_list' => $frozenList, + 'on_sale_goods_count' => $onSaleGoodsCount, + 'total_stock' => $totalStock, + 'low_stock_count' => $lowStockCount, + 'low_stock_list' => $lowStockFormatted, + 'health' => [ + 'score' => $healthTotalScore, + 'grade' => $healthGrade, + 'grade_color' => $healthGradeColor, + 'payout_score' => $payoutScore, + 'active_score' => $activeScore, + 'repeat_score' => $repeatScore, + 'safety_score' => $safetyScore, + 'suggestions' => $healthSuggestions, + ] + ]; + $this->success('获取成功', null, [ 'user_data' => $userData, 'agent_data' => $agentData ?? null, @@ -291,6 +549,8 @@ class Dashboard extends Common 'madow_data' => $madowData, 'voucher_data' => $voucherData, 'partner_network' => $partnerNetwork, + 'finance_overview' => $financeOverview, + 'operation_overview' => $operationOverview, ]); } diff --git a/application/admin/view/shopro/dashboard/index.html b/application/admin/view/shopro/dashboard/index.html index 3f1e84e..dd64973 100644 --- a/application/admin/view/shopro/dashboard/index.html +++ b/application/admin/view/shopro/dashboard/index.html @@ -245,6 +245,87 @@ width: 18px; height: 20px; } + + /* CFO 财务中枢与资产负债卡片 */ + .dashboard-index .finance-hub-card { + padding: 16px 18px; + min-height: 200px; + } + + .dashboard-index .finance-sub-metric { + padding: 8px 12px; + background: #fbfbfc; + border: 1px solid #f0f2f5; + border-radius: 6px; + margin-bottom: 8px; + font-size: 12px; + } + + /* COO 运营全貌与雷达卡片 */ + .dashboard-index .coo-panel-card { + padding: 16px 18px; + min-height: 280px; + } + + .dashboard-index .funnel-stage { + flex: 1; + padding: 10px 8px; + background: #fafafa; + border-radius: 6px; + text-align: center; + margin-right: 6px; + position: relative; + } + + .dashboard-index .funnel-stage:last-child { + margin-right: 0; + } + + .dashboard-index .radar-stat-pill { + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + transition: all .2s; + } + + .dashboard-index .radar-stat-pill:hover { + opacity: 0.9; + transform: translateY(-1px); + } + + .dashboard-index .health-score-dial { + text-align: center; + padding: 14px 10px; + background: linear-gradient(135deg, #f8faff 0%, #f0f5ff 100%); + border: 1px solid #d6e4ff; + border-radius: 8px; + } + + .dashboard-index .health-score-val { + font-size: 34px; + font-weight: 800; + line-height: 1.1; + color: #1890ff; + } + + .dashboard-index .warning-alert-row { + background: #fffbe6; + border: 1px solid #ffe58f; + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + color: #d46b08; + } + + .dashboard-index .low-stock-tag { + display: inline-block; + padding: 2px 6px; + border-radius: 3px; + background: #fff1f0; + color: #cf1322; + border: 1px solid #ffa39e; + font-weight: bold; + }