Files
YwxAppThink/addon/haonav/controller/backend/Dashboard.php
T

146 lines
5.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace addon\haonav\controller\backend;
use think\facade\Db;
use ywxapp\model\BaseModel;
use addon\haonav\model\Links as LinksModel;
use addon\haonav\model\Category as CategoryModel;
/**
* 后台数据概览仪表盘
*
* @author ywxapp <admin@ywxapp.cn>
*/
class Dashboard extends HaonavBackend
{
protected $noNeedVerify = ['*'];
protected function initialize()
{
}
public function index()
{
if ($this->request->isAjax()) {
return $this->stats();
}
// 顺带为存量链接回填拼音搜索串(每次最多 300 条,幂等、失败静默)
LinksModel::backfillPinyin(300);
$this->view->assign('title', '数据概览');
return $this->view->fetch('dashboard/index');
}
/**
* 汇总统计
*/
protected function stats()
{
// 基础计数
$summary = [
'links' => LinksModel::count(),
'online' => LinksModel::where('status', 1)->count(),
'pending' => LinksModel::where('status', 2)->count(),
'disabled' => LinksModel::where('status', 0)->count(),
'categories' => CategoryModel::count(),
'hot' => LinksModel::where('is_hot', 1)->count(),
'recommend' => LinksModel::where('is_recommend', 1)->count(),
'clicks' => (int)LinksModel::sum('click_count'),
];
// Top 分类(按链接数)
$cates = CategoryModel::field('id,title,icon')->select();
$catStat = [];
foreach ($cates as $c) {
$cnt = LinksModel::where('cid', $c->id)->count();
$catStat[] = ['title' => $c->title, 'icon' => $c->icon, 'count' => $cnt];
}
usort($catStat, function ($a, $b) {
return $b['count'] <=> $a['count'];
});
$topCategories = array_slice($catStat, 0, 10);
// Top 点击
$topClicks = LinksModel::field('id,title,url,click_count,cid')
->where('status', 1)
->order('click_count', 'desc')
->limit(10)
->select();
// 死链概览(status_code 已检测且非 2xx/3xx,或为 0
$deadCount = LinksModel::whereNotNull('status_code')
->where(function ($q) {
$q->where('status_code', 0)->whereOr('status_code', '>=', 400);
})
->count();
$deadList = LinksModel::field('id,title,url,status_code,last_check_at')
->whereNotNull('status_code')
->where(function ($q) {
$q->where('status_code', 0)->whereOr('status_code', '>=', 400);
})
->limit(20)
->select();
$lastCheckAt = (int)LinksModel::max('last_check_at');
// 分类点击热度(基于 links.click_count 聚合,零额外表)
$catHeat = [];
$allCates = CategoryModel::field('id,title,icon')->select();
foreach ($allCates as $c) {
$catHeat[] = [
'cid' => $c->id,
'title' => $c->title,
'icon' => $c->icon,
'clicks' => (int)LinksModel::where('cid', $c->id)->sum('click_count'),
];
}
usort($catHeat, function ($a, $b) { return $b['clicks'] <=> $a['clicks']; });
// 访问时段热力图(星期×小时),取最近 30 天,按 date 归并星期;表不存在或查询失败均静默兜底
$timeHeat = [];
for ($d = 0; $d < 7; $d++) { $timeHeat[$d] = array_fill(0, 24, 0); }
$timeTotal = 0;
$timeRange = date('Y-m-d', strtotime('-30 days')) . ' ~ ' . date('Y-m-d');
try {
if (BaseModel::tableExists('wxapp_haonav_click_stats')) {
$rows = Db::name('haonav_click_stats')
->where('date', '>=', date('Y-m-d', strtotime('-30 days')))
->field('date,hour,clicks')
->select();
foreach ($rows as $r) {
$w = (int)date('w', strtotime($r['date'])); // 0=周日..6=周六
$h = (int)$r['hour'];
if ($w >= 0 && $w < 7 && $h >= 0 && $h < 24) {
$timeHeat[$w][$h] += (int)$r['clicks'];
$timeTotal += (int)$r['clicks'];
}
}
}
} catch (\Throwable $e) {
// 统计查询失败不阻断概览
}
return $this->result->success([
'summary' => $summary,
'topCategories' => $topCategories,
'topClicks' => $topClicks,
'dead' => ['count' => $deadCount, 'list' => $deadList, 'last_check_at' => $lastCheckAt ? date('Y-m-d H:i', $lastCheckAt) : ''],
'heatmap' => [
'category' => $catHeat,
'time' => $timeHeat,
'timeRange' => $timeRange,
'timeTotal' => $timeTotal,
],
]);
}
}