feat: 重构运营看板,新增多维度数据展示与交互功能
新增同城便民生活服务平台标识,重构看板为业务总览与运营看板 新增待办督办中心、营收构成分析、热门分类与区域排行等模块 新增近7/14/30天多时段趋势切换,支持发布量/订单额/用户/曝光量指标切换 新增平台商业与生态资产统计模块,新增待办预警与空状态提示 新增趋势数据汇总与明细表联动,优化响应式布局与交互样式 新增商家管理、举报投诉等语言翻译项,新增登录推荐图
这个提交包含在:
@@ -6,124 +6,334 @@ use app\common\controller\Backend;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 同城-数据总览
|
||||
* 同城分类信息 - 数据总览与运营看板
|
||||
*
|
||||
* 后台核心看板:今日/累计发布量、待审核数、今日/累计订单额、会员数、商家数、
|
||||
* 分销佣金(待结算/已结算)、提现待审数,以及近 14 天趋势(发布量 / 订单额 / 新增注册用户)。
|
||||
* 数据一律由本控制器直接查库(fa_fc_* / fa_user),不在视图里写 SQL,也不走 /api/v1/admin/**。
|
||||
* 后台核心运营仪表盘:
|
||||
* 1. 今日核心指标与昨日对比(发布量 / 订单额 / 新增用户 / 浏览曝光量)
|
||||
* 2. 待办督办中心(帖子待审 / 商家待审 / 提现待审 / 举报待处理)
|
||||
* 3. 多时段多维趋势分析(近 7 天 / 14 天 / 30 天,支持发布量、订单额、新增用户、浏览量平滑折线图)
|
||||
* 4. 营收来源构成分析(会员购买 / 发帖付费 / 置顶推广 / 付费查看等占比环形图)
|
||||
* 5. 热门分类发帖分布与同城区域活跃排行
|
||||
* 6. 用户、会员、商户与分销生态资产全景
|
||||
*
|
||||
* @icon fa fa-dashboard
|
||||
*/
|
||||
class Dashboard extends Backend
|
||||
{
|
||||
/** 趋势统计天数 */
|
||||
const TREND_DAYS = 14;
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* 数据总览首页入口与数据下发
|
||||
*
|
||||
* @return string|\think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$todayStart = strtotime('today');
|
||||
$todayEnd = $todayStart + 86400 - 1;
|
||||
$yesterdayStart = $todayStart - 86400;
|
||||
$yesterdayEnd = $todayStart - 1;
|
||||
|
||||
/* ---------- 核心指标 ---------- */
|
||||
$metrics = [
|
||||
/* =========================================================
|
||||
* 1. 核心 KPI(今日、较昨日对比、累计总量)
|
||||
* ========================================================= */
|
||||
// 发布量
|
||||
'todayPost' => (int)Db::name('fc_post')->where('createtime', 'between', [$todayStart, $todayEnd])->count(),
|
||||
'totalPost' => (int)Db::name('fc_post')->count(),
|
||||
'pendingPost' => (int)Db::name('fc_post')->where('audit_status', 0)->count(),
|
||||
// 订单额(仅统计已支付订单;paytime 缺失时回落 createtime)
|
||||
'todayOrderAmount' => round((float)Db::name('fc_order')->where('status', 'paid')
|
||||
->whereTime('paytime|createtime', 'between', [$todayStart, $todayEnd])->sum('amount'), 2),
|
||||
'totalOrderAmount' => round((float)Db::name('fc_order')->where('status', 'paid')->sum('amount'), 2),
|
||||
// 用户 / 商家
|
||||
'memberCount' => (int)Db::name('fc_user_ext')->where('is_member', 1)->count(),
|
||||
'merchantCount' => (int)Db::name('fc_merchant')->count(),
|
||||
'userCount' => (int)Db::name('user')->count(),
|
||||
// 分销佣金
|
||||
'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),
|
||||
// 提现待审核
|
||||
'withdrawAuditing' => (int)Db::name('fc_withdraw')->where('status', 'auditing')->count(),
|
||||
$todayPost = (int)Db::name('fc_post')->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,
|
||||
];
|
||||
|
||||
/* ---------- 近 14 天趋势 ---------- */
|
||||
$dateStart = $todayStart - (self::TREND_DAYS - 1) * 86400;
|
||||
$days = [];
|
||||
for ($i = 0; $i < self::TREND_DAYS; $i++) {
|
||||
$days[] = date('Y-m-d', $dateStart + $i * 86400);
|
||||
/* =========================================================
|
||||
* 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 = [];
|
||||
$posts = Db::name('fc_post')
|
||||
->where('createtime', 'between time', [$dateStart, $todayEnd])
|
||||
$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 ($posts as $row) {
|
||||
foreach ($postsQuery as $row) {
|
||||
$postsMap[$row['day']] = (int)$row['num'];
|
||||
}
|
||||
|
||||
// 每天订单额(已支付;paytime 为空时按 createtime 归属)
|
||||
// 订单额趋势
|
||||
$ordersMap = [];
|
||||
$orders = Db::name('fc_order')
|
||||
$ordersQuery = Db::name('fc_order')
|
||||
->where('status', 'paid')
|
||||
->whereTime('paytime|createtime', 'between', [$dateStart, $todayEnd])
|
||||
->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 ($orders as $row) {
|
||||
foreach ($ordersQuery as $row) {
|
||||
$ordersMap[$row['day']] = round((float)$row['num'], 2);
|
||||
}
|
||||
|
||||
// 每天新增注册用户(fa_user.createtime)
|
||||
$membersMap = [];
|
||||
$members = Db::name('user')
|
||||
->where('createtime', 'between time', [$dateStart, $todayEnd])
|
||||
// 新增用户趋势
|
||||
$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 ($members as $row) {
|
||||
$membersMap[$row['day']] = (int)$row['num'];
|
||||
foreach ($usersQuery as $row) {
|
||||
$usersMap[$row['day']] = (int)$row['num'];
|
||||
}
|
||||
|
||||
/* ---------- 今日较昨日的变化(用于首页涨跌标识) ---------- */
|
||||
$yesterday = date('Y-m-d', $todayStart - 86400);
|
||||
$metrics['yesterdayPost'] = isset($postsMap[$yesterday]) ? $postsMap[$yesterday] : 0;
|
||||
$metrics['yesterdayOrder'] = isset($ordersMap[$yesterday]) ? $ordersMap[$yesterday] : 0;
|
||||
$metrics['postDelta'] = $metrics['todayPost'] - $metrics['yesterdayPost'];
|
||||
$metrics['orderDelta'] = round($metrics['todayOrderAmount'] - $metrics['yesterdayOrder'], 2);
|
||||
// 浏览量趋势
|
||||
$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'];
|
||||
}
|
||||
|
||||
$trend = [
|
||||
'dates' => $days,
|
||||
'posts' => [],
|
||||
'orders' => [],
|
||||
'members' => [],
|
||||
];
|
||||
$trendRows = [];
|
||||
foreach ($days as $day) {
|
||||
$postNum = isset($postsMap[$day]) ? $postsMap[$day] : 0;
|
||||
$orderNum = isset($ordersMap[$day]) ? $ordersMap[$day] : 0;
|
||||
$memberNum = isset($membersMap[$day]) ? $membersMap[$day] : 0;
|
||||
// 计算 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;
|
||||
|
||||
$trend['posts'][] = $postNum;
|
||||
$trend['orders'][] = $orderNum;
|
||||
$trend['members'][] = $memberNum;
|
||||
|
||||
$trendRows[] = [
|
||||
$dates[] = $day;
|
||||
$posts[] = $p;
|
||||
$orders[] = $o;
|
||||
$users[] = $u;
|
||||
$views[] = $v;
|
||||
$rows[] = [
|
||||
'date' => $day,
|
||||
'posts' => $postNum,
|
||||
'orders' => $orderNum,
|
||||
'members' => $memberNum,
|
||||
'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('trendRows', $trendRows);
|
||||
$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'));
|
||||
$this->assignconfig('trend', $trend);
|
||||
|
||||
// 向前端 RequireJS 传入完整时段数据包及分类分布,供平滑渲染交互
|
||||
$this->assignconfig('allTrends', $allTrends);
|
||||
$this->assignconfig('revenueTypes', $revenueTypes);
|
||||
$this->assignconfig('categoryRanks', $categoryRanks);
|
||||
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
@@ -2,49 +2,92 @@
|
||||
|
||||
return [
|
||||
'核心看板' => '核心看板',
|
||||
'全部指标由后台直接统计;订单额仅统计「已支付」订单,佣金不含已驳回部分,提现为「待审核」笔数。'
|
||||
=> '全部指标由后台直接统计;订单额仅统计「已支付」订单,佣金不含已驳回部分,提现为「待审核」笔数。',
|
||||
'业务总览' => '业务总览',
|
||||
'业务总览与运营看板' => '业务总览与运营看板',
|
||||
'同城便民生活服务平台' => '同城便民生活服务平台',
|
||||
'数据更新于' => '数据更新于',
|
||||
'订单额仅统计已支付订单' => '订单额仅统计已支付订单',
|
||||
'订单额仅统计已支付实收口径' => '订单额仅统计已支付实收口径',
|
||||
'内容管理' => '内容管理',
|
||||
'商家管理' => '商家管理',
|
||||
'提现审核' => '提现审核',
|
||||
'刷新数据' => '刷新数据',
|
||||
'举报投诉' => '举报投诉',
|
||||
'刷新看板' => '刷新看板',
|
||||
'刷新' => '刷新',
|
||||
'运营待办' => '运营待办',
|
||||
'当前有' => '当前有',
|
||||
'项待处理事项需要审核跟进:' => '项待处理事项需要审核跟进:',
|
||||
'待审核信息' => '待审核信息',
|
||||
'信息待审核' => '信息待审核',
|
||||
'商家入驻待审' => '商家入驻待审',
|
||||
'提现申请待审' => '提现申请待审',
|
||||
'提现待审核' => '提现待审核',
|
||||
'共' => '共',
|
||||
'违规举报待处理' => '违规举报待处理',
|
||||
'举报待处理' => '举报待处理',
|
||||
'当前所有待审核帖子、商家、提现与举报均已处理完毕,平台健康运行中'
|
||||
=> '当前所有待审核帖子、商家、提现与举报均已处理完毕,平台健康运行中',
|
||||
'今日核心指标' => '今日核心指标',
|
||||
'较昨日' => '较昨日',
|
||||
'与昨日持平' => '与昨日持平',
|
||||
'去审核' => '去审核',
|
||||
'去处理' => '去处理',
|
||||
'暂无待审核' => '暂无待审核',
|
||||
'今日发布量' => '今日发布量',
|
||||
'累计发布量' => '累计发布量',
|
||||
'待审核信息数' => '待审核信息数',
|
||||
'今日订单额' => '今日订单额',
|
||||
'累计订单额' => '累计订单额',
|
||||
'会员数' => '会员数',
|
||||
'商家数' => '商家数',
|
||||
'注册用户数' => '注册用户数',
|
||||
'平台累计' => '平台累计',
|
||||
'分销佣金' => '分销佣金',
|
||||
'待结算佣金' => '待结算佣金',
|
||||
'已结算佣金' => '已结算佣金',
|
||||
'待审核提现数' => '待审核提现数',
|
||||
'近 14 天趋势' => '近 14 天趋势',
|
||||
'篇' => '篇',
|
||||
'较昨日' => '较昨日',
|
||||
'持平' => '持平',
|
||||
'累计' => '累计',
|
||||
'今日订单实收' => '今日订单实收',
|
||||
'今日新增用户' => '今日新增用户',
|
||||
'人' => '人',
|
||||
'累计注册' => '累计注册',
|
||||
'今日信息曝光量' => '今日信息曝光量',
|
||||
'次' => '次',
|
||||
'累计曝光' => '累计曝光',
|
||||
'业务多维趋势透视' => '业务多维趋势透视',
|
||||
'发布量按发布时间归属,订单额按实际支付时间归属'
|
||||
=> '发布量按发布时间归属,订单额按实际支付时间归属',
|
||||
'选择时段' => '选择时段',
|
||||
'近 7 天' => '近 7 天',
|
||||
'近 14 天' => '近 14 天',
|
||||
'趋势图' => '趋势图',
|
||||
'近 14 天合计' => '近 14 天合计',
|
||||
'近 30 天' => '近 30 天',
|
||||
'切换趋势指标' => '切换趋势指标',
|
||||
'发布量按发布时间统计,订单额按支付时间统计'
|
||||
=> '发布量按发布时间统计,订单额按支付时间统计',
|
||||
'近 14 天明细' => '近 14 天明细',
|
||||
'展开查看每日数据' => '展开查看每日数据',
|
||||
'收起明细' => '收起明细',
|
||||
'近 14 天每日发布量、订单额与新增注册用户'
|
||||
=> '近 14 天每日发布量、订单额与新增注册用户',
|
||||
'日期' => '日期',
|
||||
'发布量' => '发布量',
|
||||
'订单额' => '订单额',
|
||||
'订单额(元)' => '订单额(元)',
|
||||
'新增用户' => '新增用户',
|
||||
'新增注册用户' => '新增注册用户',
|
||||
'信息曝光' => '信息曝光',
|
||||
'时段累计总量' => '时段累计总量',
|
||||
'日均水平' => '日均水平',
|
||||
'单日最高峰值' => '单日最高峰值',
|
||||
'业务趋势图' => '业务趋势图',
|
||||
'营收来源构成' => '营收来源构成',
|
||||
'实收合计' => '实收合计',
|
||||
'营收构成环形图' => '营收构成环形图',
|
||||
'笔' => '笔',
|
||||
'暂无已支付订单数据' => '暂无已支付订单数据',
|
||||
'热门分类发帖分布' => '热门分类发帖分布',
|
||||
'累计发帖' => '累计发帖',
|
||||
'暂无分类数据' => '暂无分类数据',
|
||||
'同城活跃区域 Top 5' => '同城活跃区域 Top 5',
|
||||
'基于信息发布所处行政区/县的热度分布' => '基于信息发布所处行政区/县的热度分布',
|
||||
'查看帖子列表' => '查看帖子列表',
|
||||
'全部帖子' => '全部帖子',
|
||||
'暂无地区发布数据' => '暂无地区发布数据',
|
||||
'平台商业与生态资产' => '平台商业与生态资产',
|
||||
'会员沉淀、商户入驻及分销裂变体系' => '会员沉淀、商户入驻及分销裂变体系',
|
||||
'VIP会员体系' => 'VIP会员体系',
|
||||
'有效VIP会员' => '有效VIP会员',
|
||||
'会员转化率' => '会员转化率',
|
||||
'商户入驻生态' => '商户入驻生态',
|
||||
'入驻商户数' => '入驻商户数',
|
||||
'推荐商家数' => '推荐商家数',
|
||||
'二级分销裂变' => '二级分销裂变',
|
||||
'推广分销员' => '推广分销员',
|
||||
'已结佣金' => '已结佣金',
|
||||
'待结佣金' => '待结佣金',
|
||||
'每日数据明细 (近 14 天)' => '每日数据明细 (近 14 天)',
|
||||
'每日数据明细 (近 %s 天)' => '每日数据明细 (近 %s 天)',
|
||||
'展开明细' => '展开明细',
|
||||
'收起明细' => '收起明细',
|
||||
'日期' => '日期',
|
||||
'发布量 (篇)' => '发布量 (篇)',
|
||||
'订单实收 (元)' => '订单实收 (元)',
|
||||
'新增注册用户 (人)' => '新增注册用户 (人)',
|
||||
'信息曝光量 (次)' => '信息曝光量 (次)',
|
||||
'暂无明细数据' => '暂无明细数据',
|
||||
'元' => '元',
|
||||
];
|
||||
|
||||
@@ -1,188 +1,424 @@
|
||||
<div class="fc-dash">
|
||||
<!-- 页头 -->
|
||||
<header class="fc-dash__head">
|
||||
<div>
|
||||
<h1 class="fc-dash__title">{:__('业务总览')}</h1>
|
||||
<div class="fc-dash__head-info">
|
||||
<h1 class="fc-dash__title">{:__('业务总览与运营看板')}</h1>
|
||||
<p class="fc-dash__desc">
|
||||
{:__('数据更新于')} {$updatedAt} · {:__('订单额仅统计已支付订单')}
|
||||
{:__('同城便民生活服务平台')} · {:__('数据更新于')} {$updatedAt} · {:__('订单额仅统计已支付实收口径')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fc-dash__actions">
|
||||
<a href="fc/post" title="内容管理" class="fc-btn addtabsit {:$auth->check('fc/post')?'':'hide'}">
|
||||
内容
|
||||
</a>
|
||||
<a href="fc/withdraw" title="提现审核" class="fc-btn addtabsit {:$auth->check('fc/withdraw')?'':'hide'}">
|
||||
提现
|
||||
{if $metrics.withdrawAuditing > 0}
|
||||
<span class="fc-btn__badge">{$metrics.withdrawAuditing}</span>
|
||||
<a href="fc/post" title="{:__('内容管理')}" class="fc-btn addtabsit {:$auth->check('fc/post')?'':'hide'}">
|
||||
<i class="fa fa-file-text-o" aria-hidden="true"></i> {:__('内容管理')}
|
||||
{if $pending.post > 0}
|
||||
<span class="fc-btn__badge">{$pending.post}</span>
|
||||
{/if}
|
||||
</a>
|
||||
<button type="button" class="fc-btn fc-btn--primary fc-dash__refresh" title="刷新数据">
|
||||
刷新
|
||||
<a href="fc/merchant" title="{:__('商家管理')}" class="fc-btn addtabsit {:$auth->check('fc/merchant')?'':'hide'}">
|
||||
<i class="fa fa-shopping-bag" aria-hidden="true"></i> {:__('商家管理')}
|
||||
{if $pending.merchant > 0}
|
||||
<span class="fc-btn__badge">{$pending.merchant}</span>
|
||||
{/if}
|
||||
</a>
|
||||
<a href="fc/withdraw" title="{:__('提现审核')}" class="fc-btn addtabsit {:$auth->check('fc/withdraw')?'':'hide'}">
|
||||
<i class="fa fa-credit-card" aria-hidden="true"></i> {:__('提现审核')}
|
||||
{if $pending.withdraw > 0}
|
||||
<span class="fc-btn__badge">{$pending.withdraw}</span>
|
||||
{/if}
|
||||
</a>
|
||||
<a href="fc/report" title="{:__('举报投诉')}" class="fc-btn addtabsit {:$auth->check('fc/report')?'':'hide'}">
|
||||
<i class="fa fa-shield" aria-hidden="true"></i> {:__('举报投诉')}
|
||||
{if $pending.report > 0}
|
||||
<span class="fc-btn__badge">{$pending.report}</span>
|
||||
{/if}
|
||||
</a>
|
||||
<button type="button" class="fc-btn fc-btn--primary fc-dash__refresh" title="{:__('刷新看板')}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i> {:__('刷新')}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 待办督办预警专区 -->
|
||||
{if $pending.total > 0}
|
||||
<div class="fc-alert-banner">
|
||||
<div class="fc-alert-banner__lead">
|
||||
<span class="fc-alert-banner__badge"><i class="fa fa-bell-o" aria-hidden="true"></i> {:__('运营待办')}</span>
|
||||
<span class="fc-alert-banner__text">{:__('当前有')} <strong>{$pending.total}</strong> {:__('项待处理事项需要审核跟进:')}</span>
|
||||
</div>
|
||||
<div class="fc-alert-banner__items">
|
||||
{if $pending.post > 0}
|
||||
<a href="fc/post" class="fc-alert-pill addtabsit" title="{:__('待审核信息')}">
|
||||
<i class="fa fa-clock-o text-warning" aria-hidden="true"></i>
|
||||
<span>{:__('信息待审核')} <b>{$pending.post}</b></span>
|
||||
</a>
|
||||
{/if}
|
||||
{if $pending.merchant > 0}
|
||||
<a href="fc/merchant" class="fc-alert-pill addtabsit" title="{:__('商家入驻待审')}">
|
||||
<i class="fa fa-user-circle-o text-info" aria-hidden="true"></i>
|
||||
<span>{:__('商家入驻待审')} <b>{$pending.merchant}</b></span>
|
||||
</a>
|
||||
{/if}
|
||||
{if $pending.withdraw > 0}
|
||||
<a href="fc/withdraw" class="fc-alert-pill addtabsit" title="{:__('提现申请待审')}">
|
||||
<i class="fa fa-money text-danger" aria-hidden="true"></i>
|
||||
<span>{:__('提现待审核')} <b>{$pending.withdraw}</b> ({:__('共')} ¥{$pending.withdrawAmount|number_format=2})</span>
|
||||
</a>
|
||||
{/if}
|
||||
{if $pending.report > 0}
|
||||
<a href="fc/report" class="fc-alert-pill addtabsit" title="{:__('违规举报待处理')}">
|
||||
<i class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></i>
|
||||
<span>{:__('举报待处理')} <b>{$pending.report}</b></span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{else}
|
||||
<div class="fc-alert-banner fc-alert-banner--ok">
|
||||
<i class="fa fa-check-circle fc-alert-banner__ok-icon" aria-hidden="true"></i>
|
||||
<span>{:__('当前所有待审核帖子、商家、提现与举报均已处理完毕,平台健康运行中')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- 4 大核心 KPI 指标卡 -->
|
||||
<section class="fc-kpi-grid" aria-label="{:__('今日核心指标')}">
|
||||
<!-- 1. 今日发布量 -->
|
||||
<article class="fc-kpi">
|
||||
<div class="fc-kpi__head">
|
||||
<span class="fc-kpi__label">{:__('今日发布量')}</span>
|
||||
<span class="fc-kpi__icon"><i class="fa fa-paper-plane-o" aria-hidden="true"></i></span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--green"><i class="fa fa-paper-plane-o" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<p class="fc-kpi__value">{$metrics.todayPost}</p>
|
||||
<p class="fc-kpi__foot">
|
||||
<p class="fc-kpi__value">{$metrics.todayPost} <small class="fc-kpi__unit">{:__('篇')}</small></p>
|
||||
<div class="fc-kpi__foot">
|
||||
<div class="fc-kpi__compare">
|
||||
{if $metrics.postDelta > 0}
|
||||
<span class="fc-trend fc-trend--up"><i class="fa fa-long-arrow-up" aria-hidden="true"></i>{$metrics.postDelta}</span>
|
||||
<span>{:__('较昨日')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{elseif $metrics.postDelta < 0}
|
||||
<span class="fc-trend fc-trend--down"><i class="fa fa-long-arrow-down" aria-hidden="true"></i>{:abs($metrics['postDelta'])}</span>
|
||||
<span>{:__('较昨日')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{else}
|
||||
<span class="fc-trend fc-trend--flat">{:__('与昨日持平')}</span>
|
||||
<span class="fc-trend fc-trend--flat">{:__('持平')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fc-kpi__total">
|
||||
{:__('累计')} <span>{$metrics.totalPost}</span> {:__('篇')}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 2. 今日订单额 -->
|
||||
<article class="fc-kpi">
|
||||
<div class="fc-kpi__head">
|
||||
<span class="fc-kpi__label">{:__('今日订单额')}</span>
|
||||
<span class="fc-kpi__label">{:__('今日订单实收')}</span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--gold"><i class="fa fa-jpy" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<p class="fc-kpi__value fc-kpi__value--money">¥{$metrics.todayOrderAmount|number_format=2}</p>
|
||||
<p class="fc-kpi__foot">
|
||||
<div class="fc-kpi__foot">
|
||||
<div class="fc-kpi__compare">
|
||||
{if $metrics.orderDelta > 0}
|
||||
<span class="fc-trend fc-trend--up"><i class="fa fa-long-arrow-up" aria-hidden="true"></i>{$metrics.orderDelta|number_format=2}</span>
|
||||
<span>{:__('较昨日')}</span>
|
||||
<span class="fc-trend fc-trend--up"><i class="fa fa-long-arrow-up" aria-hidden="true"></i>¥{$metrics.orderDelta|number_format=2}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{elseif $metrics.orderDelta < 0}
|
||||
<span class="fc-trend fc-trend--down"><i class="fa fa-long-arrow-down" aria-hidden="true"></i>{:number_format(abs($metrics['orderDelta']), 2)}</span>
|
||||
<span>{:__('较昨日')}</span>
|
||||
<span class="fc-trend fc-trend--down"><i class="fa fa-long-arrow-down" aria-hidden="true"></i>¥{:number_format(abs($metrics['orderDelta']), 2)}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{else}
|
||||
<span class="fc-trend fc-trend--flat">{:__('与昨日持平')}</span>
|
||||
<span class="fc-trend fc-trend--flat">{:__('持平')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fc-kpi__total">
|
||||
{:__('累计')} <span>¥{$metrics.totalOrderAmount|number_format=2}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 3. 今日新增用户 -->
|
||||
<article class="fc-kpi">
|
||||
<div class="fc-kpi__head">
|
||||
<span class="fc-kpi__label">{:__('待审核信息数')}</span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--red"><i class="fa fa-clock-o" aria-hidden="true"></i></span>
|
||||
<span class="fc-kpi__label">{:__('今日新增用户')}</span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--blue"><i class="fa fa-user-plus" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<p class="fc-kpi__value">{$metrics.pendingPost}</p>
|
||||
<p class="fc-kpi__foot">
|
||||
{if $metrics.pendingPost > 0 && $auth->check('fc/post')}
|
||||
<a href="fc/post" title="{:__('内容管理')}" class="fc-kpi__link addtabsit">{:__('去审核')}<i class="fa fa-angle-right" aria-hidden="true"></i></a>
|
||||
<p class="fc-kpi__value">{$metrics.todayUser} <small class="fc-kpi__unit">{:__('人')}</small></p>
|
||||
<div class="fc-kpi__foot">
|
||||
<div class="fc-kpi__compare">
|
||||
{if $metrics.userDelta > 0}
|
||||
<span class="fc-trend fc-trend--up"><i class="fa fa-long-arrow-up" aria-hidden="true"></i>{$metrics.userDelta}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{elseif $metrics.userDelta < 0}
|
||||
<span class="fc-trend fc-trend--down"><i class="fa fa-long-arrow-down" aria-hidden="true"></i>{:abs($metrics['userDelta'])}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{else}
|
||||
<span>{:__('暂无待审核')}</span>
|
||||
<span class="fc-trend fc-trend--flat">{:__('持平')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fc-kpi__total">
|
||||
{:__('累计注册')} <span>{$metrics.totalUser}</span> {:__('人')}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 4. 今日信息浏览曝光 -->
|
||||
<article class="fc-kpi">
|
||||
<div class="fc-kpi__head">
|
||||
<span class="fc-kpi__label">{:__('待审核提现数')}</span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--blue"><i class="fa fa-credit-card" aria-hidden="true"></i></span>
|
||||
<span class="fc-kpi__label">{:__('今日信息曝光量')}</span>
|
||||
<span class="fc-kpi__icon fc-kpi__icon--purple"><i class="fa fa-eye" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<p class="fc-kpi__value">{$metrics.withdrawAuditing}</p>
|
||||
<p class="fc-kpi__foot">
|
||||
{if $metrics.withdrawAuditing > 0 && $auth->check('fc/withdraw')}
|
||||
<a href="fc/withdraw" title="{:__('提现审核')}" class="fc-kpi__link addtabsit">{:__('去处理')}<i class="fa fa-angle-right" aria-hidden="true"></i></a>
|
||||
<p class="fc-kpi__value">{$metrics.todayViews} <small class="fc-kpi__unit">{:__('次')}</small></p>
|
||||
<div class="fc-kpi__foot">
|
||||
<div class="fc-kpi__compare">
|
||||
{if $metrics.viewsDelta > 0}
|
||||
<span class="fc-trend fc-trend--up"><i class="fa fa-long-arrow-up" aria-hidden="true"></i>{$metrics.viewsDelta}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{elseif $metrics.viewsDelta < 0}
|
||||
<span class="fc-trend fc-trend--down"><i class="fa fa-long-arrow-down" aria-hidden="true"></i>{:abs($metrics['viewsDelta'])}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{else}
|
||||
<span>{:__('暂无待审核')}</span>
|
||||
<span class="fc-trend fc-trend--flat">{:__('持平')}</span>
|
||||
<span class="fc-kpi__subtext">{:__('较昨日')}</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fc-kpi__total">
|
||||
{:__('累计曝光')} <span>{$metrics.totalViews}</span> {:__('次')}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<div class="fc-dash__main">
|
||||
<!-- 中区:多维趋势与业务构成 -->
|
||||
<div class="fc-dash__middle-grid">
|
||||
<!-- 左侧:多维趋势分析图 -->
|
||||
<section class="fc-card fc-card--trend">
|
||||
<header class="fc-card__head">
|
||||
<div>
|
||||
<h2 class="fc-card__title">{:__('业务多维趋势透视')}</h2>
|
||||
<p class="fc-card__desc">{:__('发布量按发布时间归属,订单额按实际支付时间归属')}</p>
|
||||
</div>
|
||||
<div class="fc-card__controls">
|
||||
<!-- 时段切换 -->
|
||||
<div class="fc-seg fc-seg--range" role="group" aria-label="{:__('选择时段')}">
|
||||
<button type="button" class="fc-seg__btn fc-range-btn" data-days="7">{:__('近 7 天')}</button>
|
||||
<button type="button" class="fc-seg__btn fc-range-btn is-active" data-days="14">{:__('近 14 天')}</button>
|
||||
<button type="button" class="fc-seg__btn fc-range-btn" data-days="30">{:__('近 30 天')}</button>
|
||||
</div>
|
||||
<!-- 指标切换 -->
|
||||
<div class="fc-seg fc-seg--metric" role="group" aria-label="{:__('切换趋势指标')}">
|
||||
<button type="button" class="fc-seg__btn fc-metric-btn is-active" data-metric="posts">{:__('发布量')}</button>
|
||||
<button type="button" class="fc-seg__btn fc-metric-btn" data-metric="orders">{:__('订单额')}</button>
|
||||
<button type="button" class="fc-seg__btn fc-metric-btn" data-metric="users">{:__('新增用户')}</button>
|
||||
<button type="button" class="fc-seg__btn fc-metric-btn" data-metric="views">{:__('信息曝光')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 趋势小计看板 -->
|
||||
<div class="fc-trend-summary">
|
||||
<div class="fc-trend-summary__item">
|
||||
<span class="fc-trend-summary__label">{:__('时段累计总量')}</span>
|
||||
<strong id="fc-trend-sum" class="fc-trend-summary__num">—</strong>
|
||||
</div>
|
||||
<div class="fc-trend-summary__item">
|
||||
<span class="fc-trend-summary__label">{:__('日均水平')}</span>
|
||||
<strong id="fc-trend-avg" class="fc-trend-summary__num">—</strong>
|
||||
</div>
|
||||
<div class="fc-trend-summary__item">
|
||||
<span class="fc-trend-summary__label">{:__('单日最高峰值')}</span>
|
||||
<strong id="fc-trend-peak" class="fc-trend-summary__num">—</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Echarts 折线走势容器 -->
|
||||
<div id="fc-trend-chart" class="fc-chart" role="img" aria-label="{:__('业务趋势图')}"></div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:营收构成分析 & 热门分类分布 -->
|
||||
<div class="fc-side-stack">
|
||||
<!-- 营收构成分析 -->
|
||||
<section class="fc-card fc-card--compact">
|
||||
<header class="fc-card__head">
|
||||
<h2 class="fc-card__title">{:__('营收来源构成')}</h2>
|
||||
<span class="fc-card__tag">{:__('实收合计')} ¥{$metrics.totalOrderAmount|number_format=2}</span>
|
||||
</header>
|
||||
<div class="fc-card__body">
|
||||
<div class="fc-pie-wrap">
|
||||
<div id="fc-revenue-chart" class="fc-pie-chart" role="img" aria-label="{:__('营收构成环形图')}"></div>
|
||||
</div>
|
||||
<div class="fc-revenue-list">
|
||||
{if count($revenueTypes) > 0}
|
||||
{foreach $revenueTypes as $rev}
|
||||
<div class="fc-rev-item">
|
||||
<div class="fc-rev-item__info">
|
||||
<span class="fc-rev-item__name">{$rev.label}</span>
|
||||
<span class="fc-rev-item__cnt">({$rev.count}{:__('笔')})</span>
|
||||
</div>
|
||||
<div class="fc-rev-item__num">
|
||||
<strong>¥{$rev.amount|number_format=2}</strong>
|
||||
<small>{$rev.percent}%</small>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{else}
|
||||
<div class="fc-empty-hint">{:__('暂无已支付订单数据')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 热门分类发帖分布 -->
|
||||
<section class="fc-card fc-card--compact">
|
||||
<header class="fc-card__head">
|
||||
<h2 class="fc-card__title">{:__('热门分类发帖分布')}</h2>
|
||||
<span class="fc-card__tag">{:__('累计发帖')} {$metrics.totalPost} {:__('篇')}</span>
|
||||
</header>
|
||||
<div class="fc-card__body fc-cat-ranks">
|
||||
{if count($categoryRanks) > 0}
|
||||
{foreach $categoryRanks as $cat}
|
||||
<div class="fc-rank-row">
|
||||
<div class="fc-rank-row__head">
|
||||
<span class="fc-rank-row__title">{$cat.name}</span>
|
||||
<span class="fc-rank-row__count"><b>{$cat.count}</b> {:__('篇')} ({$cat.percent}%)</span>
|
||||
</div>
|
||||
<div class="fc-rank-bar">
|
||||
<div class="fc-rank-bar__inner" style="width: {$cat.percent}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{else}
|
||||
<div class="fc-empty-hint">{:__('暂无分类数据')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下区:同城活跃区域排行与生态资产概览 -->
|
||||
<div class="fc-dash__bottom-grid">
|
||||
<!-- 1. 同城活跃区域排行 -->
|
||||
<section class="fc-card">
|
||||
<header class="fc-card__head">
|
||||
<div>
|
||||
<h2 class="fc-card__title">{:__('近 14 天趋势')}</h2>
|
||||
<p class="fc-card__desc">{:__('发布量按发布时间统计,订单额按支付时间统计')}</p>
|
||||
</div>
|
||||
<div class="fc-seg" role="group" aria-label="{:__('切换趋势指标')}">
|
||||
<button type="button" class="fc-seg__btn is-active" data-metric="posts" aria-pressed="true">{:__('发布量')}</button>
|
||||
<button type="button" class="fc-seg__btn" data-metric="orders" aria-pressed="false">{:__('订单额')}</button>
|
||||
<button type="button" class="fc-seg__btn" data-metric="members" aria-pressed="false">{:__('新增用户')}</button>
|
||||
<h2 class="fc-card__title">{:__('同城活跃区域 Top 5')}</h2>
|
||||
<p class="fc-card__desc">{:__('基于信息发布所处行政区/县的热度分布')}</p>
|
||||
</div>
|
||||
<a href="fc/post" title="{:__('查看帖子列表')}" class="fc-card__more addtabsit">{:__('全部帖子')} <i class="fa fa-angle-right" aria-hidden="true"></i></a>
|
||||
</header>
|
||||
<div id="fc-trend-chart" class="fc-chart" role="img" aria-label="{:__('近 14 天发布量趋势图')}"></div>
|
||||
<p class="fc-chart__total">
|
||||
<span>{:__('近 14 天合计')}</span>
|
||||
<strong id="fc-trend-total">—</strong>
|
||||
</p>
|
||||
<div class="fc-card__body">
|
||||
{if count($areaRanks) > 0}
|
||||
<div class="fc-area-list">
|
||||
{foreach $areaRanks as $idx => $area}
|
||||
<div class="fc-area-item">
|
||||
<span class="fc-area-item__badge {if $idx < 3}is-top{/if}">{$idx + 1}</span>
|
||||
<span class="fc-area-item__name">{$area.region_name}</span>
|
||||
<div class="fc-area-item__bar-wrap">
|
||||
<div class="fc-area-item__bar" style="width: {$area.percent}%;"></div>
|
||||
</div>
|
||||
<span class="fc-area-item__num"><b>{$area.count}</b> {:__('篇')}</span>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
{else}
|
||||
<div class="fc-empty-hint">{:__('暂无地区发布数据')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. 会员、商家与分销裂变生态 -->
|
||||
<section class="fc-card">
|
||||
<header class="fc-card__head">
|
||||
<h2 class="fc-card__title">{:__('平台累计')}</h2>
|
||||
<div>
|
||||
<h2 class="fc-card__title">{:__('平台商业与生态资产')}</h2>
|
||||
<p class="fc-card__desc">{:__('会员沉淀、商户入驻及分销裂变体系')}</p>
|
||||
</div>
|
||||
</header>
|
||||
<dl class="fc-stats">
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('累计发布量')}</dt>
|
||||
<dd>{$metrics.totalPost}</dd>
|
||||
<div class="fc-card__body">
|
||||
<div class="fc-eco-grid">
|
||||
<!-- 会员体系 -->
|
||||
<div class="fc-eco-block">
|
||||
<div class="fc-eco-block__title"><i class="fa fa-diamond text-warning" aria-hidden="true"></i> {:__('VIP会员体系')}</div>
|
||||
<div class="fc-eco-block__stat">
|
||||
<div>
|
||||
<span class="fc-eco-block__val">{$metrics.memberCount}</span>
|
||||
<span class="fc-eco-block__desc">{:__('有效VIP会员')}</span>
|
||||
</div>
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('累计订单额')}</dt>
|
||||
<dd class="is-money">¥{$metrics.totalOrderAmount|number_format=2}</dd>
|
||||
<div>
|
||||
<span class="fc-eco-block__val">{$ecosystem.memberRate}%</span>
|
||||
<span class="fc-eco-block__desc">{:__('会员转化率')}</span>
|
||||
</div>
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('注册用户数')}</dt>
|
||||
<dd>{$metrics.userCount}</dd>
|
||||
</div>
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('会员数')}</dt>
|
||||
<dd>{$metrics.memberCount}</dd>
|
||||
</div>
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('商家数')}</dt>
|
||||
<dd>{$metrics.merchantCount}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<header class="fc-card__head fc-card__head--sub">
|
||||
<h2 class="fc-card__title">{:__('分销佣金')}</h2>
|
||||
</header>
|
||||
<dl class="fc-stats">
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('待结算佣金')}</dt>
|
||||
<dd class="is-money">¥{$metrics.commissionPending|number_format=2}</dd>
|
||||
<!-- 商户生态 -->
|
||||
<div class="fc-eco-block">
|
||||
<div class="fc-eco-block__title"><i class="fa fa-shopping-bag text-success" aria-hidden="true"></i> {:__('商户入驻生态')}</div>
|
||||
<div class="fc-eco-block__stat">
|
||||
<div>
|
||||
<span class="fc-eco-block__val">{$metrics.merchantCount}</span>
|
||||
<span class="fc-eco-block__desc">{:__('入驻商户数')}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fc-eco-block__val">{$metrics.recommendMerchant}</span>
|
||||
<span class="fc-eco-block__desc">{:__('推荐商家数')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分销体系 -->
|
||||
<div class="fc-eco-block">
|
||||
<div class="fc-eco-block__title"><i class="fa fa-sitemap text-primary" aria-hidden="true"></i> {:__('二级分销裂变')}</div>
|
||||
<div class="fc-eco-block__stat">
|
||||
<div>
|
||||
<span class="fc-eco-block__val">{$ecosystem.distributorCount}</span>
|
||||
<span class="fc-eco-block__desc">{:__('推广分销员')}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fc-eco-block__val is-gold">¥{$ecosystem.commissionSettled|number_format=2}</span>
|
||||
<span class="fc-eco-block__desc">{:__('已结佣金')}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fc-eco-block__val is-muted">¥{$ecosystem.commissionPending|number_format=2}</span>
|
||||
<span class="fc-eco-block__desc">{:__('待结佣金')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fc-stat">
|
||||
<dt>{:__('已结算佣金')}</dt>
|
||||
<dd class="is-muted">¥{$metrics.commissionSettled|number_format=2}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<details class="fc-card fc-detail">
|
||||
<!-- 底区:详细数据明细表(支持折叠和多时段联动) -->
|
||||
<details class="fc-card fc-detail" open>
|
||||
<summary class="fc-detail__summary">
|
||||
<span class="fc-card__title">{:__('近 14 天明细')}</span>
|
||||
<div class="fc-detail__title-wrap">
|
||||
<i class="fa fa-table" aria-hidden="true"></i>
|
||||
<span class="fc-card__title" id="fc-table-title">{:__('每日数据明细 (近 14 天)')}</span>
|
||||
</div>
|
||||
<span class="fc-detail__hint">
|
||||
<span class="fc-detail__hint-open">{:__('展开查看每日数据')}</span>
|
||||
<span class="fc-detail__hint-open">{:__('展开明细')}</span>
|
||||
<span class="fc-detail__hint-close">{:__('收起明细')}</span>
|
||||
</span>
|
||||
<i class="fa fa-angle-down fc-detail__chevron" aria-hidden="true"></i>
|
||||
</summary>
|
||||
<div class="fc-detail__body">
|
||||
<div class="fc-table-wrap">
|
||||
<table class="fc-table">
|
||||
<caption class="sr-only">{:__('近 14 天每日发布量、订单额与新增注册用户')}</caption>
|
||||
<table class="fc-table" id="fc-trend-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{:__('日期')}</th>
|
||||
<th scope="col" class="is-num">{:__('发布量')}</th>
|
||||
<th scope="col" class="is-num">{:__('订单额')}</th>
|
||||
<th scope="col" class="is-num">{:__('新增注册用户')}</th>
|
||||
<th scope="col" class="is-num">{:__('发布量 (篇)')}</th>
|
||||
<th scope="col" class="is-num">{:__('订单实收 (元)')}</th>
|
||||
<th scope="col" class="is-num">{:__('新增注册用户 (人)')}</th>
|
||||
<th scope="col" class="is-num">{:__('信息曝光量 (次)')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="fc-table-body">
|
||||
{foreach $trendRows as $item}
|
||||
<tr>
|
||||
<td>{$item.date}</td>
|
||||
<td><strong>{$item.date}</strong></td>
|
||||
<td class="is-num">{$item.posts}</td>
|
||||
<td class="is-num">¥{$item.orders|number_format=2}</td>
|
||||
<td class="is-num">{$item.members}</td>
|
||||
<td class="is-num fc-td-money">¥{$item.orders|number_format=2}</td>
|
||||
<td class="is-num">{$item.users}</td>
|
||||
<td class="is-num">{$item.views}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
|
||||
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
二进制文件未显示。
|
之后 宽度: | 高度: | 大小: 62 KiB |
@@ -1,164 +1,374 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'echarts', 'echarts-theme'], function ($, undefined, Backend, Echarts) {
|
||||
|
||||
/**
|
||||
* 同城分类信息 - 数据总览与运营看板
|
||||
*/
|
||||
var Controller = {
|
||||
/**
|
||||
* 看板首页初始化与图表联动逻辑
|
||||
*/
|
||||
index: function () {
|
||||
var trend = (typeof Config !== 'undefined' && Config.trend) ? Config.trend : null;
|
||||
var allTrends = (typeof Config !== 'undefined' && Config.allTrends) ? Config.allTrends : null;
|
||||
var revenueTypes = (typeof Config !== 'undefined' && Config.revenueTypes) ? Config.revenueTypes : [];
|
||||
var categoryRanks = (typeof Config !== 'undefined' && Config.categoryRanks) ? Config.categoryRanks : [];
|
||||
|
||||
// 刷新当前看板
|
||||
// 页面刷新按钮
|
||||
$(document).on('click', '.fc-dash__refresh', function () {
|
||||
$(this).find('.fa').addClass('fa-spin');
|
||||
var $btn = $(this);
|
||||
$btn.find('.fa').addClass('fa-spin');
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
// 没有趋势数据(或 echarts 未加载)时静默降级:下方明细表已展示同样数据
|
||||
if (!trend || !trend.dates || !trend.dates.length || !Echarts) {
|
||||
return;
|
||||
// 当前状态
|
||||
var currentDays = 14;
|
||||
var currentMetric = 'posts';
|
||||
|
||||
// 工具:格式化千分位数值
|
||||
var formatNum = function (n, isMoney) {
|
||||
var val = Number(n) || 0;
|
||||
if (isMoney) {
|
||||
return '¥' + val.toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
}
|
||||
return val.toLocaleString('zh-CN');
|
||||
};
|
||||
|
||||
var dates = $.map(trend.dates, function (day) {
|
||||
return day.slice(5); // 2026-10-09 => 10-09
|
||||
});
|
||||
|
||||
var total = function (list) {
|
||||
// 工具:数组求和
|
||||
var sumArray = function (arr) {
|
||||
var sum = 0;
|
||||
$.each(list || [], function (i, n) {
|
||||
sum += Number(n) || 0;
|
||||
$.each(arr || [], function (i, v) {
|
||||
sum += (Number(v) || 0);
|
||||
});
|
||||
return sum;
|
||||
};
|
||||
|
||||
var formatNum = function (n) {
|
||||
var value = Number(n) || 0;
|
||||
var options = value % 1 === 0 ? {} : {minimumFractionDigits: 2, maximumFractionDigits: 2};
|
||||
try {
|
||||
return value.toLocaleString('zh-CN', options);
|
||||
} catch (e) {
|
||||
return value.toFixed(value % 1 === 0 ? 0 : 2);
|
||||
// 工具:数组求峰值
|
||||
var peakArray = function (arr) {
|
||||
var max = 0;
|
||||
$.each(arr || [], function (i, v) {
|
||||
var n = Number(v) || 0;
|
||||
if (n > max) {
|
||||
max = n;
|
||||
}
|
||||
});
|
||||
return max;
|
||||
};
|
||||
|
||||
var METRICS = {
|
||||
// 指标元数据配置(符合轻氧浅绿与品牌规范)
|
||||
var METRIC_CONFIG = {
|
||||
posts: {
|
||||
label: __('发布量'),
|
||||
color: '#44507A',
|
||||
fill: 'rgba(68, 80, 122, .18)',
|
||||
axis: '{value}',
|
||||
money: false,
|
||||
data: trend.posts || []
|
||||
unit: __('篇'),
|
||||
color: '#00D26A',
|
||||
fillStart: 'rgba(0, 210, 106, 0.28)',
|
||||
fillEnd: 'rgba(0, 210, 106, 0.01)',
|
||||
isMoney: false
|
||||
},
|
||||
orders: {
|
||||
label: __('订单额'),
|
||||
color: '#B7793A',
|
||||
fill: 'rgba(183, 121, 58, .18)',
|
||||
axis: '¥{value}',
|
||||
money: true,
|
||||
data: trend.orders || []
|
||||
unit: __('元'),
|
||||
color: '#F5A623',
|
||||
fillStart: 'rgba(245, 166, 35, 0.28)',
|
||||
fillEnd: 'rgba(245, 166, 35, 0.01)',
|
||||
isMoney: true
|
||||
},
|
||||
members: {
|
||||
label: __('新增注册用户'),
|
||||
color: '#2E8A66',
|
||||
fill: 'rgba(46, 138, 102, .16)',
|
||||
axis: '{value}',
|
||||
money: false,
|
||||
data: trend.members || []
|
||||
users: {
|
||||
label: __('新增用户'),
|
||||
unit: __('人'),
|
||||
color: '#3B82F6',
|
||||
fillStart: 'rgba(59, 130, 246, 0.25)',
|
||||
fillEnd: 'rgba(59, 130, 246, 0.01)',
|
||||
isMoney: false
|
||||
},
|
||||
views: {
|
||||
label: __('信息曝光'),
|
||||
unit: __('次'),
|
||||
color: '#8B5CF6',
|
||||
fillStart: 'rgba(139, 92, 246, 0.25)',
|
||||
fillEnd: 'rgba(139, 92, 246, 0.01)',
|
||||
isMoney: false
|
||||
}
|
||||
};
|
||||
|
||||
var reduceMotion = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
||||
var $chart = $('#fc-trend-chart');
|
||||
var chart = null;
|
||||
var trendChartInstance = null;
|
||||
var revenueChartInstance = null;
|
||||
var $trendChartEl = $('#fc-trend-chart');
|
||||
var $revenueChartEl = $('#fc-revenue-chart');
|
||||
|
||||
/**
|
||||
* 构建趋势折线/面积图配置项
|
||||
*
|
||||
* @param {Object} trendData
|
||||
* @param {string} metricKey
|
||||
* @return {Object}
|
||||
*/
|
||||
var buildTrendOption = function (trendData, metricKey) {
|
||||
var meta = METRIC_CONFIG[metricKey];
|
||||
var rawDates = trendData.dates || [];
|
||||
var dates = $.map(rawDates, function (d) {
|
||||
return d.slice(5); // 截取 MM-DD
|
||||
});
|
||||
var data = trendData[metricKey] || [];
|
||||
|
||||
var buildOption = function (metric) {
|
||||
return {
|
||||
color: [metric.color],
|
||||
grid: {left: 6, right: 16, top: 18, bottom: 4, containLabel: true},
|
||||
color: [meta.color],
|
||||
grid: {
|
||||
left: 12,
|
||||
right: 20,
|
||||
top: 24,
|
||||
bottom: 8,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E7EBF1',
|
||||
borderColor: '#E2E8F0',
|
||||
borderWidth: 1,
|
||||
padding: [8, 12],
|
||||
textStyle: {color: '#232A38', fontSize: 12},
|
||||
extraCssText: 'border-radius:8px;box-shadow:0 6px 18px rgba(23,35,61,.10);',
|
||||
axisPointer: {type: 'line', lineStyle: {color: '#C9D2DF', type: 'dashed'}},
|
||||
padding: [10, 14],
|
||||
textStyle: {color: '#1E293B', fontSize: 13},
|
||||
extraCssText: 'border-radius:10px;box-shadow:0 8px 24px rgba(15,23,42,0.08);',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {color: meta.color, width: 1.5, type: 'dashed'}
|
||||
},
|
||||
formatter: function (params) {
|
||||
var item = params[0];
|
||||
var prefix = metric.money ? '¥' : '';
|
||||
var dot = '<span style="display:inline-block;width:7px;height:7px;border-radius:50%;'
|
||||
+ 'background:' + metric.color + ';margin-right:6px;"></span>';
|
||||
return item.axisValue + '<br/>' + dot + metric.label + ':<b>'
|
||||
+ prefix + formatNum(item.data) + '</b>';
|
||||
var fullDate = rawDates[item.dataIndex] || item.axisValue;
|
||||
var valStr = meta.isMoney ? ('¥' + Number(item.data).toFixed(2)) : (item.data + ' ' + meta.unit);
|
||||
var dot = '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;'
|
||||
+ 'background:' + meta.color + ';margin-right:6px;"></span>';
|
||||
return '<div style="font-size:12px;color:#64748B;margin-bottom:4px;">' + fullDate + '</div>'
|
||||
+ dot + meta.label + ':<b style="font-size:14px;color:#1E293B;">' + valStr + '</b>';
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
axisLine: {lineStyle: {color: '#E7EBF1'}},
|
||||
axisLine: {lineStyle: {color: '#E2E8F0'}},
|
||||
axisTick: {show: false},
|
||||
axisLabel: {color: '#6B7684', fontSize: 11, margin: 10}
|
||||
axisLabel: {color: '#64748B', fontSize: 12, margin: 12}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: metric.money ? 0 : 1,
|
||||
minInterval: meta.isMoney ? 0 : 1,
|
||||
axisLine: {show: false},
|
||||
axisTick: {show: false},
|
||||
axisLabel: {color: '#6B7684', fontSize: 11, formatter: metric.axis},
|
||||
splitLine: {lineStyle: {color: '#EFF2F6', type: 'dashed'}}
|
||||
axisLabel: {
|
||||
color: '#64748B',
|
||||
fontSize: 12,
|
||||
formatter: function (v) {
|
||||
return meta.isMoney ? ('¥' + v) : v;
|
||||
}
|
||||
},
|
||||
splitLine: {lineStyle: {color: '#F1F5F9', type: 'dashed'}}
|
||||
},
|
||||
series: [{
|
||||
name: metric.label,
|
||||
name: meta.label,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
smooth: 0.35,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
showSymbol: false,
|
||||
lineStyle: {width: 2},
|
||||
itemStyle: {color: metric.color, borderColor: '#FFFFFF', borderWidth: 2},
|
||||
emphasis: {focus: 'series'},
|
||||
lineStyle: {width: 2.5, color: meta.color},
|
||||
itemStyle: {color: meta.color, borderColor: '#FFFFFF', borderWidth: 2},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
scale: true,
|
||||
itemStyle: {color: meta.color, borderColor: '#FFFFFF', borderWidth: 3}
|
||||
},
|
||||
areaStyle: {
|
||||
color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{offset: 0, color: metric.fill},
|
||||
{offset: 1, color: 'rgba(255, 255, 255, 0)'}
|
||||
{offset: 0, color: meta.fillStart},
|
||||
{offset: 1, color: meta.fillEnd}
|
||||
])
|
||||
},
|
||||
data: metric.data
|
||||
data: data
|
||||
}],
|
||||
animation: !reduceMotion,
|
||||
animationDuration: 500
|
||||
animationDuration: 400
|
||||
};
|
||||
};
|
||||
|
||||
var applyMetric = function (key) {
|
||||
var metric = METRICS[key];
|
||||
|
||||
$('.fc-seg__btn')
|
||||
.removeClass('is-active')
|
||||
.attr('aria-pressed', 'false')
|
||||
.filter('[data-metric="' + key + '"]')
|
||||
.addClass('is-active')
|
||||
.attr('aria-pressed', 'true');
|
||||
|
||||
$chart.attr('aria-label', __('近 14 天') + metric.label + __('趋势图'));
|
||||
$('#fc-trend-total').text((metric.money ? '¥' : '') + formatNum(total(metric.data)));
|
||||
|
||||
if (chart) {
|
||||
chart.setOption(buildOption(metric), true);
|
||||
/**
|
||||
* 更新图表及上方统计卡
|
||||
*/
|
||||
var renderTrend = function () {
|
||||
if (!allTrends || !allTrends[currentDays]) {
|
||||
return;
|
||||
}
|
||||
var trendData = allTrends[currentDays];
|
||||
var meta = METRIC_CONFIG[currentMetric];
|
||||
var data = trendData[currentMetric] || [];
|
||||
|
||||
// 1. 计算时段总计、日均、峰值
|
||||
var total = sumArray(data);
|
||||
var avg = data.length > 0 ? (total / data.length) : 0;
|
||||
var peak = peakArray(data);
|
||||
|
||||
if (meta.isMoney) {
|
||||
$('#fc-trend-sum').text(formatNum(total, true));
|
||||
$('#fc-trend-avg').text(formatNum(avg, true));
|
||||
$('#fc-trend-peak').text(formatNum(peak, true));
|
||||
} else {
|
||||
$('#fc-trend-sum').text(formatNum(total, false) + ' ' + meta.unit);
|
||||
$('#fc-trend-avg').text(avg.toFixed(1) + ' ' + meta.unit);
|
||||
$('#fc-trend-peak').text(formatNum(peak, false) + ' ' + meta.unit);
|
||||
}
|
||||
|
||||
// 2. 更新 Echarts
|
||||
if (trendChartInstance) {
|
||||
trendChartInstance.setOption(buildTrendOption(trendData, currentMetric), true);
|
||||
}
|
||||
|
||||
// 3. 同步更新底表明细
|
||||
updateDetailTable(trendData.rows || []);
|
||||
};
|
||||
|
||||
if ($chart.length) {
|
||||
chart = Echarts.init($chart[0], 'walden');
|
||||
applyMetric('posts');
|
||||
/**
|
||||
* 同步更新底部每日明细表内容
|
||||
*
|
||||
* @param {Array} rows
|
||||
*/
|
||||
var updateDetailTable = function (rows) {
|
||||
$('#fc-table-title').text(__('每日数据明细 (近 %s 天)', currentDays));
|
||||
var $tbody = $('#fc-table-body');
|
||||
$tbody.empty();
|
||||
|
||||
if (!rows || rows.length === 0) {
|
||||
$tbody.append('<tr><td colspan="5" class="text-center text-muted" style="padding: 24px;">' + __('暂无明细数据') + '</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
$.each(rows, function (idx, item) {
|
||||
var orderStr = '¥' + Number(item.orders || 0).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
var tr = '<tr>'
|
||||
+ '<td><strong>' + item.date + '</strong></td>'
|
||||
+ '<td class="is-num">' + (item.posts || 0) + '</td>'
|
||||
+ '<td class="is-num fc-td-money">' + orderStr + '</td>'
|
||||
+ '<td class="is-num">' + (item.users || 0) + '</td>'
|
||||
+ '<td class="is-num">' + (item.views || 0) + '</td>'
|
||||
+ '</tr>';
|
||||
$tbody.append(tr);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化营收构成环形饼图
|
||||
*/
|
||||
var initRevenueChart = function () {
|
||||
if (!$revenueChartEl.length || !Echarts) {
|
||||
return;
|
||||
}
|
||||
|
||||
revenueChartInstance = Echarts.init($revenueChartEl[0]);
|
||||
|
||||
if (!revenueTypes || revenueTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chartData = $.map(revenueTypes, function (item) {
|
||||
return {
|
||||
name: item.label,
|
||||
value: item.amount
|
||||
};
|
||||
});
|
||||
|
||||
var palette = ['#00D26A', '#F5A623', '#3B82F6', '#8B5CF6', '#EC4899', '#14B8A6'];
|
||||
|
||||
var option = {
|
||||
color: palette,
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E2E8F0',
|
||||
borderWidth: 1,
|
||||
padding: [8, 12],
|
||||
textStyle: {color: '#1E293B', fontSize: 12},
|
||||
extraCssText: 'border-radius:8px;box-shadow:0 6px 18px rgba(15,23,42,0.08);',
|
||||
formatter: function (p) {
|
||||
return p.name + '<br/><b>¥' + Number(p.value).toFixed(2) + '</b> (' + p.percent + '%)';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['52%', '76%'],
|
||||
center: ['50%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 6,
|
||||
borderColor: '#FFFFFF',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
scale: true,
|
||||
scaleSize: 6,
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 13,
|
||||
fontWeight: 'bold',
|
||||
formatter: '{b}\n{d}%'
|
||||
}
|
||||
},
|
||||
labelLine: {show: false},
|
||||
data: chartData
|
||||
}]
|
||||
};
|
||||
|
||||
revenueChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 初始化趋势主图
|
||||
if ($trendChartEl.length && Echarts) {
|
||||
trendChartInstance = Echarts.init($trendChartEl[0]);
|
||||
renderTrend();
|
||||
}
|
||||
|
||||
// 初始化营收环形图
|
||||
initRevenueChart();
|
||||
|
||||
// 监听窗口尺寸变化自适应
|
||||
$(window).on('resize', function () {
|
||||
chart.resize();
|
||||
if (trendChartInstance) {
|
||||
trendChartInstance.resize();
|
||||
}
|
||||
if (revenueChartInstance) {
|
||||
revenueChartInstance.resize();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.fc-seg__btn', function () {
|
||||
applyMetric($(this).data('metric'));
|
||||
});
|
||||
// 时段切换点击(近 7 / 14 / 30 天)
|
||||
$(document).on('click', '.fc-range-btn', function () {
|
||||
var $btn = $(this);
|
||||
var days = parseInt($btn.data('days'), 10) || 14;
|
||||
if (days === currentDays) {
|
||||
return;
|
||||
}
|
||||
currentDays = days;
|
||||
$('.fc-range-btn').removeClass('is-active');
|
||||
$btn.addClass('is-active');
|
||||
renderTrend();
|
||||
});
|
||||
|
||||
// 指标切换点击(发布量 / 订单额 / 新增用户 / 信息曝光)
|
||||
$(document).on('click', '.fc-metric-btn', function () {
|
||||
var $btn = $(this);
|
||||
var metric = $btn.data('metric');
|
||||
if (metric === currentMetric) {
|
||||
return;
|
||||
}
|
||||
currentMetric = metric;
|
||||
$('.fc-metric-btn').removeClass('is-active');
|
||||
$btn.addClass('is-active');
|
||||
renderTrend();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户