feat(dashboard): 增加CFO财务资金浮存与资产负债中枢、COO运营转化复购与合伙人断档雷达全貌分析

这个提交包含在:
chen
2026-09-17 08:38:59 +08:00
父节点 c93150a5fd
当前提交 9ee5e06e89
共有 4 个文件被更改,包括 762 次插入 和 0 次删除

查看文件

@@ -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,
]);
}

查看文件

@@ -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;
}
</style>
<div id="index" class="dashboard-index panel panel-default panel-intro" v-cloak>
@@ -343,6 +424,98 @@
</el-col>
</el-row>
<!-- ================= 1.1 CFO 财务全貌 · 资金流向、平台浮存与资产负债中枢 ================= -->
<el-row :gutter="16">
<!-- 资金浮存池 (Cash Float) -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<div class="finance-hub-card card">
<div class="sa-flex sa-row-between mb-2">
<div class="sa-flex">
<span class="network-badge bg-primary mr-2" style="background: #1890ff;">浮</span>
<span class="font-bold text-dark" style="font-size: 14px;">资金浮存池 (Cash Float)</span>
</div>
<el-tag size="small" type="success" effect="light">平台流动免息资金</el-tag>
</div>
<div class="kpi-main-num text-primary" style="font-size: 24px; margin: 8px 0;">
<span class="currency">¥</span>{{ state.finance.total_float_funds }}
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>未提现佣金沉淀 (用户收益留存):</span>
<strong class="text-warning">¥{{ state.finance.commission_float }}</strong>
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>用户账户余额沉淀 (已充未销):</span>
<strong class="text-dark">¥{{ state.finance.user_balance_float }}</strong>
</div>
<div class="sa-flex sa-row-between pt-2" style="font-size: 12px; color: var(--sa-subfont); border-top: 1px dashed var(--sa-space);">
<span>真实净现金流 (GMV-出款-退款):</span>
<strong class="text-success">¥{{ state.finance.real_net_cash_flow }}</strong>
</div>
</div>
</el-col>
<!-- 提现出款与资金风控敞口 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<div class="finance-hub-card card">
<div class="sa-flex sa-row-between mb-2">
<div class="sa-flex">
<span class="network-badge bg-warning mr-2" style="background: #fa8c16;">提</span>
<span class="font-bold text-dark" style="font-size: 14px;">提现出款与风控敞口</span>
</div>
<span class="cursor-pointer text-primary" style="font-size: 12px;" @click="onOpenWithdraw('all')">提现流水 →</span>
</div>
<div class="kpi-main-num text-warning" style="font-size: 24px; margin: 8px 0;">
<span class="currency">¥</span>{{ state.finance.total_withdraw_paid }}
<span class="unit" style="font-size: 12px; font-weight: normal; color: var(--sa-subfont);">已打款出款</span>
</div>
<div class="finance-sub-metric sa-flex sa-row-between" :style="state.finance.pending_withdraw_count > 0 ? 'background: #fff2e8; border-color: #ffbb96;' : ''">
<span>待审核出款风控资金:</span>
<div class="sa-flex">
<strong :class="state.finance.pending_withdraw_count > 0 ? 'text-danger' : 'text-dark'">¥{{ state.finance.pending_withdraw_amount }}</strong>
<el-badge v-if="state.finance.pending_withdraw_count > 0" :value="state.finance.pending_withdraw_count + '笔'" class="ml-1" type="danger" />
<el-button v-if="state.finance.pending_withdraw_count > 0" type="danger" link size="mini" class="ml-1" @click="onOpenWithdraw(0)">审核</el-button>
</div>
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>累计申请总额:¥{{ state.finance.total_withdraw_applied }}</span>
<span>出款兑现率:<strong class="text-purple">{{ state.finance.withdraw_cash_rate }}%</strong></span>
</div>
<div class="sa-flex sa-row-between pt-2" style="font-size: 12px; color: var(--sa-subfont); border-top: 1px dashed var(--sa-space);">
<span>累计退款:¥{{ state.finance.total_refund_amount }} ({{ state.finance.total_refund_count }}笔)</span>
<span>退款率:<strong>{{ state.finance.refund_rate }}%</strong></span>
</div>
</div>
</el-col>
<!-- 资产负债表与未来承兑责任 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<div class="finance-hub-card card">
<div class="sa-flex sa-row-between mb-2">
<div class="sa-flex">
<span class="network-badge bg-danger mr-2" style="background: #f5222d;">债</span>
<span class="font-bold text-dark" style="font-size: 14px;">未来承兑责任准备金 (负债中枢)</span>
</div>
<el-tag size="small" type="danger" effect="light">履约与承兑储备</el-tag>
</div>
<div class="kpi-main-num text-danger" style="font-size: 24px; margin: 8px 0;">
<span class="currency">¥</span>{{ state.finance.total_future_liability }}
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>① 消费兑换券补贴待释放负债 (1.5倍补贴):</span>
<strong class="text-danger">¥{{ state.finance.pending_voucher_liability }}</strong>
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>② 马豆流通折算市值负债 (生态回购承兑):</span>
<strong class="text-purple">¥{{ state.finance.madow_market_liability }}</strong>
</div>
<div class="sa-flex sa-row-between pt-2" style="font-size: 12px; color: var(--sa-subfont); border-top: 1px dashed var(--sa-space);">
<span>③ 用户未提佣金流动负债:</span>
<strong class="text-warning">¥{{ state.finance.commission_liability }}</strong>
</div>
</div>
</el-col>
</el-row>
<!-- ================= 2. 全角色身份会员资产与收益统计全景透视 ================= -->
<!-- 2.1 三大核心板块快速指标卡 -->
<el-row :gutter="16">
@@ -515,6 +688,203 @@
</div>
</el-col>
</el-row>
<!-- ================= 2.3 COO 运营全貌 · 用户转化漏斗、合伙人掉队雷达与库存安全 ================= -->
<el-row :gutter="16">
<!-- 左侧:电商用户转化漏斗与复购动销 -->
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<div class="coo-panel-card card">
<div class="sa-flex sa-row-between mb-3">
<div>
<span class="font-bold text-dark" style="font-size: 14px;">用户转化漏斗与复购粘性</span>
</div>
<span class="cursor-pointer text-primary" style="font-size: 12px;" @click="onOpenUser">会员中心 →</span>
</div>
<!-- 漏斗横向展示 -->
<div class="sa-flex sa-row-between mb-3" style="background: #f8faff; padding: 12px; border-radius: 8px; border: 1px solid #e6f7ff;">
<div class="funnel-stage">
<div style="color: var(--sa-subfont); font-size: 11px;">全网注册</div>
<div class="font-bold text-dark" style="font-size: 18px; margin: 4px 0;">{{ state.operation.total_registered }} <span style="font-size: 11px;">人</span></div>
<el-tag size="mini" type="info">基数 100%</el-tag>
</div>
<div style="color: #1890ff; font-weight: bold; font-size: 16px;">→</div>
<div class="funnel-stage">
<div style="color: var(--sa-subfont); font-size: 11px;">付费买家</div>
<div class="font-bold text-primary" style="font-size: 18px; margin: 4px 0;">{{ state.operation.paid_users_count }} <span style="font-size: 11px;">人</span></div>
<el-tag size="mini" type="primary">转化 {{ state.operation.pay_conversion_rate }}%</el-tag>
</div>
<div style="color: #52c41a; font-weight: bold; font-size: 16px;">→</div>
<div class="funnel-stage">
<div style="color: var(--sa-subfont); font-size: 11px;">复购会员</div>
<div class="font-bold text-success" style="font-size: 18px; margin: 4px 0;">{{ state.operation.repeat_buy_users_count }} <span style="font-size: 11px;">人</span></div>
<el-tag size="mini" type="success">复购 {{ state.operation.repeat_purchase_rate }}%</el-tag>
</div>
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>单次购买买家:<strong class="text-dark">{{ state.operation.single_buy_users_count }} 人</strong></span>
<span>复购买家:<strong class="text-success">{{ state.operation.repeat_buy_users_count }} 人</strong></span>
</div>
<div class="finance-sub-metric sa-flex sa-row-between">
<span>付费用户人均客单 (ARPU):</span>
<strong class="text-warning">¥{{ state.operation.arpu }}</strong>
</div>
<div class="sa-flex sa-row-between pt-2" style="font-size: 12px; color: var(--sa-subfont); border-top: 1px dashed var(--sa-space);">
<span>在架商品:<strong class="text-dark">{{ state.operation.on_sale_goods_count }} 款</strong></span>
<span>全仓总库存:<strong class="text-dark">{{ state.operation.total_stock }} 件</strong></span>
</div>
</div>
</el-col>
<!-- 中间:合伙人 30 天自购断档掉队雷达 -->
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<div class="coo-panel-card card">
<div class="sa-flex sa-row-between mb-3">
<div>
<span class="font-bold text-dark" style="font-size: 14px;">合伙人 30天自购断档掉队雷达</span>
</div>
<span class="cursor-pointer text-primary" style="font-size: 12px;" @click="onOpenAgent('')">合伙团队 →</span>
</div>
<!-- 3状态指标标签 -->
<el-row :gutter="8" class="mb-3">
<el-col :span="8">
<div class="radar-stat-pill" style="background: #f6ffed; border: 1px solid #b7eb8f; text-align: center;">
<div style="font-size: 11px; color: #389e0d;">活跃达标</div>
<div style="font-size: 18px; font-weight: bold; color: #52c41a; margin-top: 2px;">{{ state.operation.active_partners }}</div>
<div style="font-size: 10px; color: #8c8c8c;">>7天余量</div>
</div>
</el-col>
<el-col :span="8">
<div class="radar-stat-pill" style="background: #fffbe6; border: 1px solid #ffe58f; text-align: center;">
<div style="font-size: 11px; color: #d46b08;">即将断档</div>
<div style="font-size: 18px; font-weight: bold; color: #fa8c16; margin-top: 2px;">{{ state.operation.warning_partners }}</div>
<div style="font-size: 10px; color: #fa8c16;">≤7天临界</div>
</div>
</el-col>
<el-col :span="8">
<div class="radar-stat-pill" style="background: #fff1f0; border: 1px solid #ffa39e; text-align: center;">
<div style="font-size: 11px; color: #cf1322;">已断档冻结</div>
<div style="font-size: 18px; font-weight: bold; color: #f5222d; margin-top: 2px;">{{ state.operation.frozen_partners }}</div>
<div style="font-size: 10px; color: #8c8c8c;">无分佣资格</div>
</div>
</el-col>
</el-row>
<!-- 7天临界断档名单列表(若有则紧急告警,若无则展示健康与已断档) -->
<div v-if="state.operation.warning_partners > 0">
<div class="warning-alert-row mb-2 sa-flex sa-row-between">
<span>⚠️ <strong>紧急唤醒:</strong>以下合伙人自购即将到期</span>
<span style="font-weight: bold;">{{ state.operation.warning_partners }} 人临界</span>
</div>
<div style="max-height: 120px; overflow-y: auto;">
<div class="sa-flex sa-row-between mb-2 pb-1" v-for="wUser in state.operation.warning_list" :key="wUser.user_id" style="border-bottom: 1px dashed #f0f2f5; font-size: 12px;">
<div class="sa-flex">
<sa-image :url="wUser.avatar" size="20" style="border-radius: 50%;"></sa-image>
<div class="ml-1">
<span class="font-bold">{{ wUser.nickname }}</span>
<el-tag size="mini" type="info" class="ml-1">{{ wUser.level_text }}</el-tag>
</div>
</div>
<div class="sa-flex">
<el-tag size="mini" type="danger">剩余 {{ wUser.days_left }} 天</el-tag>
<el-button type="primary" link size="mini" class="ml-1" @click="onOpenAgent('')">催单</el-button>
</div>
</div>
</div>
</div>
<div v-else>
<div class="sa-flex sa-row-between" style="background: #f6ffed; border: 1px solid #b7eb8f; padding: 8px 12px; border-radius: 6px; font-size: 12px; color: #389e0d; margin-bottom: 8px;">
<span>🛡️ 合伙股东自购健康度良好,暂无临近断档人员</span>
<span>全部在保</span>
</div>
<div style="font-size: 11px; color: var(--sa-subfont); margin-bottom: 4px;">已断档冻结人员采样 (失去分佣资格):</div>
<div style="max-height: 100px; overflow-y: auto;">
<div class="sa-flex sa-row-between mb-1 pb-1" v-for="fUser in state.operation.frozen_list.slice(0, 3)" :key="fUser.user_id" style="border-bottom: 1px dashed #f0f2f5; font-size: 12px;">
<div class="sa-flex">
<span class="text-muted mr-1">#{{ fUser.user_id }}</span>
<span class="text-dark">{{ fUser.nickname }}</span>
<span class="text-muted ml-1" style="font-size: 11px;">({{ fUser.mobile }})</span>
</div>
<el-tag size="mini" type="danger" effect="plain">{{ fUser.overdue_days }}</el-tag>
</div>
</div>
</div>
</div>
</el-col>
<!-- 右侧:供应链库存告急与平台经营健康度评分 -->
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<div class="coo-panel-card card">
<div class="sa-flex sa-row-between mb-2">
<div>
<span class="font-bold text-dark" style="font-size: 14px;">经营健康评分与供应链安全</span>
</div>
<el-tag size="small" :style="{ backgroundColor: state.operation.health.grade_color, color: '#fff', borderColor: state.operation.health.grade_color, fontWeight: 'bold' }">
{{ state.operation.health.grade }}
</el-tag>
</div>
<!-- 综合评分双栏展示 -->
<el-row :gutter="12" class="mb-3">
<el-col :span="10">
<div class="health-score-dial">
<div style="font-size: 11px; color: var(--sa-subfont); margin-bottom: 4px;">综合健康度</div>
<div class="health-score-val" :style="{ color: state.operation.health.grade_color }">{{ state.operation.health.score }}</div>
<div style="font-size: 10px; color: var(--sa-subfont); margin-top: 4px;">满分 100分</div>
</div>
</el-col>
<el-col :span="14">
<div style="font-size: 11px; color: var(--sa-subfont); line-height: 1.8;">
<div class="sa-flex sa-row-between">
<span>拨比健康 (30分):</span>
<strong class="text-dark">{{ state.operation.health.payout_score }}分</strong>
</div>
<div class="sa-flex sa-row-between">
<span>合伙人活力 (25分):</span>
<strong class="text-dark">{{ state.operation.health.active_score }}分</strong>
</div>
<div class="sa-flex sa-row-between">
<span>复购粘性 (25分):</span>
<strong class="text-dark">{{ state.operation.health.repeat_score }}分</strong>
</div>
<div class="sa-flex sa-row-between">
<span>履约安全 (20分):</span>
<strong class="text-dark">{{ state.operation.health.safety_score }}分</strong>
</div>
</div>
</el-col>
</el-row>
<!-- 库存告急监测 -->
<div class="sa-flex sa-row-between mb-1" style="border-top: 1px dashed var(--sa-space); padding-top: 8px;">
<div class="sa-flex">
<span class="font-bold text-dark" style="font-size: 12px;">库存告急预警 (≤10件)</span>
<el-badge :value="state.operation.low_stock_count" class="ml-2" :type="state.operation.low_stock_count > 0 ? 'danger' : 'info'" />
</div>
<span class="cursor-pointer text-primary" style="font-size: 11px;" @click="onOpenStockWarning">预警中心 →</span>
</div>
<div v-if="state.operation.low_stock_count > 0" style="max-height: 90px; overflow-y: auto;">
<div class="sa-flex sa-row-between mb-1 pb-1" v-for="sku in state.operation.low_stock_list" :key="sku.sku_id" style="border-bottom: 1px solid #f8f8f8; font-size: 12px;">
<div class="sa-flex" style="max-width: 70%;">
<sa-image :url="sku.goods_image" size="18" style="border-radius: 2px;"></sa-image>
<span class="ml-1 sa-table-line-1" :title="sku.goods_title">{{ sku.goods_title }}</span>
</div>
<div class="sa-flex">
<span class="low-stock-tag mr-1">{{ sku.stock }}件</span>
<el-button type="primary" link size="mini" @click="onOpenGoods(sku.goods_id)">补货</el-button>
</div>
</div>
</div>
<div v-else class="text-muted sa-flex sa-row-center" style="font-size: 12px; padding: 10px 0;">
✅ 所有在售商品库存充足
</div>
</div>
</el-col>
</el-row>
{/if}
{if $auth->check('shopro/dashboard/chart')}

查看文件

@@ -47,6 +47,52 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
role_list: [],
role_stats: {}
},
finance: {
commission_float: '0.00',
user_balance_float: '0.00',
total_float_funds: '0.00',
total_withdraw_applied: '0.00',
total_withdraw_paid: '0.00',
pending_withdraw_amount: '0.00',
pending_withdraw_count: 0,
withdraw_cash_rate: 0,
total_refund_amount: '0.00',
total_refund_count: 0,
refund_rate: 0,
real_net_cash_flow: '0.00',
pending_voucher_liability: '0.00',
madow_market_liability: '0.00',
commission_liability: '0.00',
total_future_liability: '0.00',
},
operation: {
total_registered: 0,
paid_users_count: 0,
pay_conversion_rate: 0,
single_buy_users_count: 0,
repeat_buy_users_count: 0,
repeat_purchase_rate: 0,
arpu: '0.00',
active_partners: 0,
warning_partners: 0,
frozen_partners: 0,
warning_list: [],
frozen_list: [],
on_sale_goods_count: 0,
total_stock: 0,
low_stock_count: 0,
low_stock_list: [],
health: {
score: 100,
grade: 'S·卓越',
grade_color: '#52c41a',
payout_score: 30,
active_score: 25,
repeat_score: 25,
safety_score: 20,
suggestions: []
}
},
total: {
user: {
data: {},
@@ -99,6 +145,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
if (res.data.partner_network) {
state.partner = res.data.partner_network;
}
if (res.data.finance_overview) {
state.finance = res.data.finance_overview;
}
if (res.data.operation_overview) {
state.operation = res.data.operation_overview;
}
for (var key in state.total) {
if (state.total[key].data && document.getElementById(`${key}Total`)) {
@@ -762,6 +814,28 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
onOpenTab(url, '收益明细');
}
function onOpenWithdraw(status) {
let url = 'shopro/withdraw/index' + (status !== undefined && status !== 'all' ? `?status=${status}` : '');
onOpenTab(url, '提现管理');
}
function onOpenGoods(goodsId) {
let url = goodsId ? `shopro/goods/goods/edit?ids=${goodsId}` : 'shopro/goods/goods/index';
onOpenTab(url, goodsId ? '编辑商品' : '商品管理');
}
function onOpenStockWarning() {
onOpenTab('shopro/goods/stock_warning/index', '库存预警');
}
function onOpenUser() {
onOpenTab('shopro/user/user/index', '会员管理');
}
function onOpenAftersale() {
onOpenTab('shopro/order/aftersale/index', '售后维权');
}
onMounted(() => {
Config.cardList.forEach(item => {
if (item.name == 'total' && item.status) {
@@ -796,6 +870,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'moment'], function (
onOpenDaily,
onOpenVoucher,
onOpenTab,
onOpenWithdraw,
onOpenGoods,
onOpenStockWarning,
onOpenUser,
onOpenAftersale,
}
}
}

查看文件

@@ -66,6 +66,7 @@ class DashboardWealthTest
$this->testChartEmptyDateFallback();
$this->testRankingPartnerAndDailyData();
$this->testWealthFinancialFormulaIntegrity();
$this->testCfoAndCooOverview();
echo "\n--------------------------------------------------------\n";
echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n";
@@ -237,6 +238,58 @@ class DashboardWealthTest
$this->assert($payoutRate == 0, "GMV 为 0 时分成拨比率恒为 0%");
}
}
/**
* 测试 7:CFO 财务中枢与 COO 运营全貌数据结构与勾稽
*/
public function testCfoAndCooOverview()
{
$controller = new TestableDashboard();
$controller->total();
$data = $controller->lastOutput['data'] ?? [];
$finance = $data['finance_overview'] ?? null;
$operation = $data['operation_overview'] ?? null;
$this->assert($finance !== null, "total() 返回完整的 finance_overview 财务全貌中枢数据");
$this->assert($operation !== null, "total() 返回完整的 operation_overview 运营全貌中枢数据");
if ($finance) {
$this->assert(isset($finance['commission_float']), "财务包含 commission_float 未提现佣金沉淀");
$this->assert(isset($finance['total_float_funds']), "财务包含 total_float_funds 免息流动沉淀池");
$this->assert(isset($finance['total_withdraw_paid']), "财务包含 total_withdraw_paid 累计已打款出款");
$this->assert(isset($finance['pending_withdraw_amount']), "财务包含 pending_withdraw_amount 待审核出款风控资金");
$this->assert(isset($finance['real_net_cash_flow']), "财务包含 real_net_cash_flow 真实净现金流");
$this->assert(isset($finance['total_future_liability']), "财务包含 total_future_liability 未来承兑准备金");
$floatDiff = abs((float)$finance['total_float_funds'] - ((float)$finance['commission_float'] + (float)$finance['user_balance_float']));
$this->assert($floatDiff < 0.01, "资金浮存勾稽: total_float_funds == commission_float + user_balance_float");
$liabilityDiff = abs((float)$finance['total_future_liability'] - ((float)$finance['pending_voucher_liability'] + (float)$finance['madow_market_liability'] + (float)$finance['commission_liability']));
$this->assert($liabilityDiff < 0.01, "承兑负债勾稽: total_future_liability == pending_voucher + madow_market + commission");
}
if ($operation) {
$this->assert(isset($operation['total_registered']), "运营包含 total_registered 注册用户数");
$this->assert(isset($operation['paid_users_count']), "运营包含 paid_users_count 付费买家数");
$this->assert(isset($operation['pay_conversion_rate']), "运营包含 pay_conversion_rate 付费转化率");
$this->assert(isset($operation['repeat_purchase_rate']), "运营包含 repeat_purchase_rate 复购率");
$this->assert(isset($operation['arpu']), "运营包含 arpu 付费买家客单贡献");
$this->assert(isset($operation['active_partners']), "运营包含 active_partners 活跃合伙人");
$this->assert(isset($operation['warning_partners']), "运营包含 warning_partners 7天内即将断档合伙人");
$this->assert(isset($operation['frozen_partners']), "运营包含 frozen_partners 已断档冻结合伙人");
$this->assert(is_array($operation['warning_list']), "运营包含 warning_list 即将断档人员名单");
$this->assert(isset($operation['low_stock_count']), "运营包含 low_stock_count 低库存告急SKU数量");
$this->assert(is_array($operation['low_stock_list']), "运营包含 low_stock_list 低库存SKU商品列表");
$this->assert(isset($operation['health']['score']), "运营包含经营健康度评分 score");
$this->assert(isset($operation['health']['grade']), "运营包含经营健康度评级 grade");
$this->assert(
$operation['paid_users_count'] == ($operation['single_buy_users_count'] + $operation['repeat_buy_users_count']),
"用户转化勾稽: paid_users_count({$operation['paid_users_count']}) == single({$operation['single_buy_users_count']}) + repeat({$operation['repeat_buy_users_count']})"
);
}
}
}
if (php_sapi_name() === 'cli' && basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {