This commit is contained in:
2025-05-20 08:36:54 +08:00
90 changed files with 2070 additions and 17380 deletions
@@ -11,7 +11,7 @@ use think\facade\Request;
use app\admin\controller\Log;
use app\admin\controller\BaseController;
class Article extends BaseController
class ArticlesController extends BaseController
{
/**
* 获取控制器名称
@@ -428,16 +428,64 @@ class Article extends BaseController
//统计文章数量
public function counts() {
$total = Articles::where('delete_time', null)
->where('status', '<>', 3)
->count();
try {
// 获取文章总数
$total = Articles::where('delete_time', null)
->where('status', '<>', 3)
->count();
// 获取今日新增文章数
$today = strtotime(date('Y-m-d'));
$todayNew = Articles::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '>=', $today)
->count();
// 获取最近7天的文章数据
$dates = [];
$counts = [];
$totalCounts = []; // 存储每天的总文章数
$totalSoFar = 0; // 用于累计总文章数
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'total' => $total
]
]);
for ($i = 6; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$start = strtotime($date);
$end = $start + 86400;
// 获取当天新增文章数
$count = Articles::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '>=', $start)
->where('create_time', '<', $end)
->count();
// 获取截至当天的总文章数
$totalCount = Articles::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '<', $end)
->count();
$dates[] = $date;
$counts[] = $count;
$totalCounts[] = $totalCount;
}
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'total' => $total,
'todayNew' => $todayNew,
'dates' => $dates,
'counts' => $counts,
'totalCounts' => $totalCounts
]
]);
} catch (\Exception $e) {
return json([
'code' => 1,
'msg' => '获取失败:' . $e->getMessage()
]);
}
}
}
@@ -4,18 +4,25 @@
*/
namespace app\admin\controller;
use app\admin\controller\Base;
use app\admin\model\DailyStats;
use app\admin\model\Log\LogsOperation;
use app\index\model\Attachments;
use think\facade\Db;
use think\facade\View;
use think\facade\Env;
use think\facade\Config;
use app\admin\controller\Log;
class Index extends Base{
use app\admin\model\AdminUserGroup;
use app\admin\model\AdminSysMenu;
class IndexController extends Base{
# 首页
public function index(){
$menus = [];
$menu = [];
$where = ['group_id'=>$this->aUser['group_id']];
$role = Db::name('admin_user_group')->where($where)->find();
$role = AdminUserGroup::where($where)->find();
if($role){
$role['rights'] = (isset($role['rights']) && $role['rights']) ? json_decode($role['rights'],true) : [];
}
@@ -25,7 +32,7 @@ class Index extends Base{
['status','=',1]
];
// 获取所有菜单
$menus = Db::name('admin_sys_menu')->order('type,sort desc')->where($where)->select()->toArray();
$menus = AdminSysMenu::order('type,sort desc')->where($where)->select()->toArray();
// 构建树形结构菜单
$menuTree = [];
@@ -61,118 +68,168 @@ class Index extends Base{
}
# 欢迎页面
public function welcome(){
// 获取今日统计数据
$today = date('Y-m-d');
$todayStats = Db::name('daily_stats')
->where('date', $today)
->find();
try {
// 获取最近7天的日期
$dates = [];
for ($i = 6; $i >= 0; $i--) {
$dates[] = date('Y-m-d', strtotime("-$i day"));
}
// 获取最近7天的访问趋势
$last7Days = Db::name('daily_stats')
->where('date', '>=', date('Y-m-d', strtotime('-7 days')))
->where('date', '<=', $today)
->order('date', 'asc')
->select()
->toArray();
// 初始化数据数组
$visitData = [];
$userData = [];
$resourceData = [];
$articleData = [];
// 获取用户增长趋势
$userGrowth = Db::name('daily_stats')
->where('date', '>=', date('Y-m-d', strtotime('-30 days')))
->where('date', '<=', $today)
->field('date, new_users, total_users')
->order('date', 'asc')
->select()
->toArray();
// 直接查询每天的数据
foreach ($dates as $date) {
$dayStats = Db::name('daily_stats')
->where('date', $date)
->find();
// 获取资源下载统计
$resourceStats = Db::name('daily_stats')
->where('date', '>=', date('Y-m-d', strtotime('-7 days')))
->where('date', '<=', $today)
->field('date, daily_resources, resource_downloads')
->order('date', 'asc')
->select()
->toArray();
// 访问数据
$visitData[] = [
'date' => $date,
'visits' => $dayStats ? intval($dayStats['daily_visits']) : 0,
'uv' => $dayStats ? intval($dayStats['unique_visitors']) : 0
];
// 获取文章访问统计
$articleStats = Db::name('daily_stats')
->where('date', '>=', date('Y-m-d', strtotime('-7 days')))
->where('date', '<=', $today)
->field('date, daily_articles, article_views')
->order('date', 'asc')
->select()
->toArray();
// 用户数据
$userData[] = [
'date' => $date,
'total' => $dayStats ? intval($dayStats['total_users']) : 0,
'new' => $dayStats ? intval($dayStats['new_users']) : 0
];
// 获取最近的活动记录
$recentActivities = $this->getRecentActivities();
// 资源数据
$resourceData[] = [
'date' => $date,
'total' => $dayStats ? intval($dayStats['total_resources']) : 0,
'new' => $dayStats ? intval($dayStats['daily_resources']) : 0,
'downloads' => $dayStats ? intval($dayStats['resource_downloads']) : 0
];
// 准备图表数据
$chartData = [
'visitTrend' => $this->formatVisitTrendData($last7Days),
'userGrowth' => $this->formatUserGrowthData($userGrowth),
'resourceStats' => $this->formatResourceStatsData($resourceStats),
'articleStats' => $this->formatArticleStatsData($articleStats)
];
// 文章数据
$articleData[] = [
'date' => $date,
'total' => $dayStats ? intval($dayStats['total_articles']) : 0,
'new' => $dayStats ? intval($dayStats['daily_articles']) : 0,
'views' => $dayStats ? intval($dayStats['article_views']) : 0
];
}
// 准备统计数据
$stats = [
'total_users' => $todayStats['total_users'] ?? 0,
'daily_visits' => $todayStats['daily_visits'] ?? 0,
'total_articles' => $todayStats['total_articles'] ?? 0,
'total_resources' => $todayStats['total_resources'] ?? 0,
];
// 获取今日统计数据
$today = date('Y-m-d');
$todayStats = Db::name('daily_stats')
->where('date', $today)
->find();
return View::fetch('', [
'stats' => $stats,
'chartData' => $chartData,
'recentActivities' => $recentActivities
]);
// 获取最近的操作日志
$recentActivities = Db::name('logs_operation')
->field('operation_time, module, operation')
->order('operation_time DESC')
->limit(5)
->select()
->each(function($item) {
$item['content'] = date('Y年m月d日 H:i:s', strtotime($item['operation_time'])) . ' 在 ' .
($item['module'] ?: '未知模块') . ' ' .
($item['operation'] ?: '未知操作');
$item['icon'] = $this->getActivityIcon($item['module'] ?: '其他');
return $item;
});
// 处理图表数据
$chartData = [
'visitTrend' => [
'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $visitData),
'visits' => array_column($visitData, 'visits'),
'uvs' => array_column($visitData, 'uv')
],
'userGrowth' => [
'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $userData),
'newUsers' => array_column($userData, 'new'),
'totalUsers' => array_column($userData, 'total')
],
'resourceStats' => [
'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $resourceData),
'newResources' => array_column($resourceData, 'new'),
'totalResources' => array_column($resourceData, 'total'),
'downloads' => array_column($resourceData, 'downloads')
],
'articleStats' => [
'dates' => array_map(function($item) { return date('m-d', strtotime($item['date'])); }, $articleData),
'newArticles' => array_column($articleData, 'new'),
'totalArticles' => array_column($articleData, 'total'),
'views' => array_column($articleData, 'views')
]
];
// 传递给视图
View::assign([
'todayStats' => $todayStats ?: [
'total_users' => 0,
'new_users' => 0,
'total_visits' => 0,
'daily_visits' => 0,
'unique_visitors' => 0,
'total_articles' => 0,
'daily_articles' => 0,
'article_views' => 0,
'total_resources' => 0,
'daily_resources' => 0,
'resource_downloads' => 0
],
'recentActivities' => $recentActivities,
'chartData' => $chartData
]);
return View::fetch();
} catch (\Exception $e) {
// 记录错误日志
\think\facade\Log::error('获取统计数据失败:' . $e->getMessage());
// 返回空数据
View::assign([
'todayStats' => [
'total_users' => 0,
'new_users' => 0,
'total_visits' => 0,
'daily_visits' => 0,
'unique_visitors' => 0,
'total_articles' => 0,
'daily_articles' => 0,
'article_views' => 0,
'total_resources' => 0,
'daily_resources' => 0,
'resource_downloads' => 0
],
'recentActivities' => [],
'chartData' => [
'visitTrend' => ['dates' => [], 'visits' => [], 'uvs' => []],
'userGrowth' => ['dates' => [], 'newUsers' => [], 'totalUsers' => []],
'resourceStats' => ['dates' => [], 'newResources' => [], 'totalResources' => [], 'downloads' => []],
'articleStats' => ['dates' => [], 'newArticles' => [], 'totalArticles' => [], 'views' => []]
]
]);
return View::fetch();
}
}
/**
* 获取最近的活动记录
* 根据操作类型获取对应的图标
*/
private function getRecentActivities()
private function getActivityIcon($type)
{
$today = date('Y-m-d');
$activities = [];
// 获取今日新用户
$newUsers = Db::name('daily_stats')
->where('date', $today)
->value('new_users');
if ($newUsers > 0) {
$activities[] = [
'icon' => '👥',
'title' => '新增用户 ' . $newUsers . '',
'time' => '今日'
];
}
// 获取今日文章
$newArticles = Db::name('daily_stats')
->where('date', $today)
->value('daily_articles');
if ($newArticles > 0) {
$activities[] = [
'icon' => '📝',
'title' => '发布文章 ' . $newArticles . ' 篇',
'time' => '今日'
];
}
// 获取今日资源
$newResources = Db::name('daily_stats')
->where('date', $today)
->value('daily_resources');
if ($newResources > 0) {
$activities[] = [
'icon' => '📦',
'title' => '上传资源 ' . $newResources . ' 个',
'time' => '今日'
];
}
return $activities;
$icons = [
'用户管理' => '👥',
'文章管理' => '📝',
'资源管理' => '📦',
'系统设置' => '⚙️',
'登录' => '🔑',
'退出' => '🚪',
'其他' => '📌'
];
return $icons[$type] ?? '📌';
}
/**
@@ -280,7 +337,7 @@ class Index extends Base{
'create_time' => time(),
'update_time' => time()
];
return Db::name('attachments')->insertGetId($data);
return Attachments::insertGetId($data);
}
# 图片上传
@@ -9,7 +9,7 @@ use think\facade\Cookie;
use app\admin\model\Log\LogsLogin;
use app\admin\model\Log\LogsOperation;
class Log extends Base
class LogController extends Base
{
/**
* 登录日志列表
@@ -13,10 +13,10 @@ use app\admin\model\YzAdminConfig;
use app\admin\model\AdminUser;
use app\admin\model\Log\LogsLogin;
class Login
class LoginController extends Base
{
protected $app;
protected $config;
public $app;
public $config;
public function __construct(App $app)
{
@@ -36,7 +36,7 @@ class Login
}
// 记录登录日志
protected function recordLoginLog($username, $status, $reason = '')
public function recordLoginLog($username, $status, $reason = '')
{
$data = [
'username' => $username,
@@ -52,14 +52,14 @@ class Login
}
// 获取IP地址位置
protected function getLocation($ip)
public function getLocation($ip)
{
// 这里可以接入IP地址库或第三方API
return '未知';
}
// 获取设备类型
protected function getDeviceType()
public function getDeviceType()
{
$agent = Request::header('user-agent');
if (preg_match('/(iPhone|iPod|Android|ios|iPad|Mobile)/i', $agent)) {
@@ -12,7 +12,7 @@ use think\facade\Db;
use app\admin\controller\Log;
use think\App;
class Resources extends BaseController
class ResourcesController extends BaseController
{
// 资源列表
public function lists()
@@ -370,16 +370,64 @@ class Resources extends BaseController
//统计资源数量
public function counts()
{
$total = Resource::where('delete_time', null)
->where('status', '<>', 3)
->count();
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'total' => $total
]
]);
try {
// 获取资源总数
$total = Resource::where('delete_time', null)
->where('status', '<>', 3)
->count();
// 获取今日新增资源数
$today = strtotime(date('Y-m-d'));
$todayNew = Resource::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '>=', $today)
->count();
// 获取最近7天的资源数据
$dates = [];
$counts = [];
$totalCounts = []; // 存储每天的总资源数
for ($i = 6; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$start = strtotime($date);
$end = $start + 86400;
// 获取当天新增资源数
$count = Resource::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '>=', $start)
->where('create_time', '<', $end)
->count();
// 获取截至当天的总资源数
$totalCount = Resource::where('delete_time', null)
->where('status', '<>', 3)
->where('create_time', '<', $end)
->count();
$dates[] = $date;
$counts[] = $count;
$totalCounts[] = $totalCount;
}
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'total' => $total,
'todayNew' => $todayNew,
'dates' => $dates,
'counts' => $counts,
'totalCounts' => $totalCounts
]
]);
} catch (\Exception $e) {
return json([
'code' => 1,
'msg' => '获取失败:' . $e->getMessage()
]);
}
}
// 构建树形结构
+81
View File
@@ -0,0 +1,81 @@
<?php
/**
* 后台管理系统-用户管理
*/
namespace app\admin\controller;
use app\admin\controller\BaseController;
use think\facade\View;
use think\facade\Request;
use think\facade\Db;
use app\admin\controller\Log;
use think\App;
use app\admin\model\User\Users;
class UsersController extends BaseController
{
/**
* 统计用户数量
*/
public function counts()
{
try {
// 统计用户总数
$total = Users::where('delete_time', 0)
->where('status', 1)
->count();
// 获取今日新增用户数
$today = strtotime(date('Y-m-d'));
$todayNew = Users::where('delete_time', 0)
->where('status', 1)
->where('create_time', '>=', $today)
->count();
// 获取最近7天的用户数据
$dates = [];
$counts = [];
$totalCounts = []; // 存储每天的总用户数
for ($i = 6; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$start = strtotime($date);
$end = $start + 86400;
// 获取当天新增用户数
$count = Users::where('delete_time', 0)
->where('status', 1)
->where('create_time', '>=', $start)
->where('create_time', '<', $end)
->count();
// 获取截至当天的总用户数
$totalCount = Users::where('delete_time', 0)
->where('status', 1)
->where('create_time', '<', $end)
->count();
$dates[] = $date;
$counts[] = $count;
$totalCounts[] = $totalCount;
}
return json([
'code' => 0,
'msg' => '获取成功',
'data' => [
'total' => $total,
'todayNew' => $todayNew,
'dates' => $dates,
'counts' => $counts,
'totalCounts' => $totalCounts
]
]);
} catch (\Exception $e) {
return json([
'code' => 1,
'msg' => '获取失败:' . $e->getMessage()
]);
}
}
}
@@ -14,7 +14,7 @@ use app\admin\model\AdminUserGroup;
use app\admin\model\AdminConfig;
use app\admin\model\ZIconfont;
class Yunzer extends Base{
class YunzerController extends Base{
# 菜单列表
public function menuinfo(){
$lists = AdminSysMenu::where('parent_id', 0)->order('sort DESC,smid DESC')->select();
@@ -11,7 +11,7 @@ use app\admin\model\AdminUser;
use app\admin\model\Banner;
class Yunzeradmin extends Base
class YunzeradminController extends Base
{
// 角色列表
public function groupinfo()
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace app\admin\model;
use think\Model;
class ApiKey extends Model
{
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace app\admin\model;
use think\Model;
class DailyStats extends Model
{
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace app\admin\model\User;
use think\Model;
class Users extends Model
{
}
@@ -322,7 +322,7 @@
// 初始化分类列表
that.initCategoryList = function () {
$.ajax({
url: '/admin/article/articlecate',
url: '/admin/articles/articlecate',
type: 'POST',
success: function (res) {
if (res.code === 0) {
@@ -387,7 +387,7 @@
// 加载分类信息
that.loadCategoryInfo = function (id) {
$.get('/admin/article/cateedit?id=' + id, function (res) {
$.get('/admin/articles/cateedit?id=' + id, function (res) {
if (res.code === 0) {
that.showCategoryForm(0, res.data);
}
@@ -413,7 +413,7 @@
$select.empty().append('<option value="0">顶级分类</option>');
// 获取所有分类作为父级选项
$.ajax({
url: '/admin/article/articlecate',
url: '/admin/articles/articlecate',
type: 'POST',
async: false,
success: function (res) {
@@ -472,7 +472,7 @@
// 监听表单提交
form.on('submit(saveCategory)', function (data) {
var url = data.field.id ? '/admin/article/cateedit' : '/admin/article/cateadd';
var url = data.field.id ? '/admin/articles/cateedit' : '/admin/articles/cateadd';
$.post(url, data.field, function (res) {
if (res.code === 0) {
layer.msg(res.msg, { icon: 1 });
@@ -490,7 +490,7 @@
if (!id) return;
layer.confirm('确定要删除该分类吗?', function (index) {
$.post('/admin/article/catedel', { id: id }, function (res) {
$.post('/admin/articles/catedel', { id: id }, function (res) {
if (res.code === 0) {
layer.msg(res.msg, { icon: 1 });
that.initCategoryList();
@@ -87,7 +87,7 @@
// 初始化表格
table.render({
elem: '#articleTable',
url: '/admin/article/articlelist',
url: '/admin/articles/articlelist',
method: 'post',
cols: [[
{ field: 'id', title: 'ID', align: 'center', width: 80 },
@@ -174,18 +174,18 @@
}
function add() {
window.location.href = '/admin/article/add';
window.location.href = '/admin/articles/add';
}
function edit(id) {
window.location.href = '/admin/article/edit?id=' + id;
window.location.href = '/admin/articles/edit?id=' + id;
}
function del(id) {
layer.confirm('确定要删除该文章吗?', {
btn: ['确定', '取消']
}, function () {
$.post('/admin/article/delete', { id: id }, function (res) {
$.post('/admin/articles/delete', { id: id }, function (res) {
if (res.code == 0) {
layer.msg(res.msg, { icon: 1 });
setTimeout(function () {
+145 -27
View File
@@ -161,8 +161,7 @@
}
.activity-title {
font-weight: 500;
color: #1e293b;
margin-bottom: 4px;
color: #9b9b9b;
}
.activity-time {
font-size: 12px;
@@ -212,22 +211,22 @@
<div class="stats-container">
<div class="stat-card">
<div class="stat-title">用户总数</div>
<div class="stat-value">{$stats.total_users|number_format}</div>
<div class="stat-value">{$todayStats.total_users|number_format}</div>
<div class="stat-icon">👥</div>
</div>
<div class="stat-card">
<div class="stat-title">今日访问</div>
<div class="stat-value">{$stats.daily_visits|number_format}</div>
<div class="stat-value">{$todayStats.daily_visits|number_format}</div>
<div class="stat-icon">📊</div>
</div>
<div class="stat-card">
<div class="stat-title">文章总数</div>
<div class="stat-value">{$stats.total_articles|number_format}</div>
<div class="stat-value">{$todayStats.total_articles|number_format}</div>
<div class="stat-icon">📝</div>
</div>
<div class="stat-card">
<div class="stat-title">资源总数</div>
<div class="stat-value">{$stats.total_resources|number_format}</div>
<div class="stat-value">{$todayStats.total_resources|number_format}</div>
<div class="stat-icon">📦</div>
</div>
</div>
@@ -258,10 +257,9 @@
<div class="activity-list">
{volist name="recentActivities" id="activity"}
<div class="activity-item">
<div class="activity-icon">{$activity.icon}</div>
<div class="activity-icon">{$activity.icon|default='📌'}</div>
<div class="activity-content">
<div class="activity-title">{$activity.title}</div>
<div class="activity-time">{$activity.time}</div>
<div class="activity-title">{$activity.content}</div>
</div>
</div>
{/volist}
@@ -314,6 +312,42 @@ function updateTime() {
document.getElementById('current-time').innerHTML = timeString;
}
// 获取用户统计数据
function getUserCounts() {
fetch('{:url("users/counts")}')
.then(response => response.json())
.then(res => {
console.log('用户统计接口返回数据:', res);
if (res.code === 0 && res.data) {
// 更新用户总数
document.querySelector('.stat-card:nth-child(1) .stat-value').textContent = res.data.total.toLocaleString();
// 更新用户增长图表
if (window.userChart) {
window.userChart.setOption({
xAxis: {
data: res.data.dates
},
series: [{
name: '新增用户',
data: res.data.counts
}, {
name: '总用户数',
data: res.data.totalCounts
}]
});
}
} else {
console.warn('用户统计接口返回异常:', res);
document.querySelector('.stat-card:nth-child(1) .stat-value').textContent = '0';
}
})
.catch(error => {
console.error('获取用户统计失败:', error);
document.querySelector('.stat-card:nth-child(1) .stat-value').textContent = '0';
});
}
// 获取文章统计数据
function getArticleCounts() {
fetch('{:url("articles/counts")}')
@@ -323,12 +357,30 @@ function getArticleCounts() {
if (res.code === 0 && res.data) {
// 更新文章总数
document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = res.data.total.toLocaleString();
// 更新文章统计图表
if (window.articleChart) {
window.articleChart.setOption({
xAxis: {
data: res.data.dates
},
series: [{
name: '新增文章',
data: res.data.counts
}, {
name: '总文章数',
data: res.data.totalCounts
}]
});
}
} else {
console.warn('文章统计接口返回异常:', res);
document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = '0';
}
})
.catch(error => {
console.error('获取文章统计失败:', error);
document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = '0';
});
}
@@ -341,12 +393,30 @@ function getResourcesCounts() {
if (res.code === 0 && res.data) {
// 更新资源总数
document.querySelector('.stat-card:nth-child(4) .stat-value').textContent = res.data.total.toLocaleString();
// 更新资源统计图表
if (window.resourceChart) {
window.resourceChart.setOption({
xAxis: {
data: res.data.dates
},
series: [{
name: '新增资源',
data: res.data.counts
}, {
name: '总资源数',
data: res.data.totalCounts
}]
});
}
} else {
console.warn('资源统计接口返回异常:', res);
document.querySelector('.stat-card:nth-child(4) .stat-value').textContent = '0';
}
})
.catch(error => {
console.error('获取资源统计失败:', error);
document.querySelector('.stat-card:nth-child(4) .stat-value').textContent = '0';
});
}
@@ -355,6 +425,7 @@ setInterval(updateTime, 1000);
// 页面加载完成后获取统计数据
document.addEventListener('DOMContentLoaded', function() {
getUserCounts();
getArticleCounts();
getResourcesCounts();
});
@@ -476,6 +547,9 @@ function initUserGrowth() {
data: {$chartData.userGrowth.totalUsers|json_encode},
itemStyle: {
color: '#10b981'
},
lineStyle: {
width: 3
}
}
]
@@ -497,7 +571,7 @@ function initResourceStats() {
}
},
legend: {
data: ['新增资源', '下载量']
data: ['新增资源', '总资源数', '下载量']
},
grid: {
left: '3%',
@@ -517,18 +591,33 @@ function initResourceStats() {
{
name: '新增资源',
type: 'bar',
data: {$chartData.resourceStats.resources|json_encode},
data: {$chartData.resourceStats.newResources|json_encode},
itemStyle: {
color: '#3881fd'
}
},
{
name: '总资源数',
type: 'line',
smooth: true,
data: {$chartData.resourceStats.totalResources|json_encode},
itemStyle: {
color: '#10b981'
},
lineStyle: {
width: 3
}
},
{
name: '下载量',
type: 'line',
smooth: true,
data: {$chartData.resourceStats.downloads|json_encode},
itemStyle: {
color: '#10b981'
color: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
@@ -550,7 +639,7 @@ function initArticleStats() {
}
},
legend: {
data: ['新增文章', '访问量']
data: ['新增文章', '总文章数', '浏览量']
},
grid: {
left: '3%',
@@ -570,18 +659,33 @@ function initArticleStats() {
{
name: '新增文章',
type: 'bar',
data: {$chartData.articleStats.articles|json_encode},
data: {$chartData.articleStats.newArticles|json_encode},
itemStyle: {
color: '#3881fd'
}
},
{
name: '访问量',
name: '总文章数',
type: 'line',
smooth: true,
data: {$chartData.articleStats.totalArticles|json_encode},
itemStyle: {
color: '#10b981'
},
lineStyle: {
width: 3
}
},
{
name: '浏览量',
type: 'line',
smooth: true,
data: {$chartData.articleStats.views|json_encode},
itemStyle: {
color: '#10b981'
color: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
@@ -591,18 +695,32 @@ function initArticleStats() {
// 初始化所有图表
document.addEventListener('DOMContentLoaded', function() {
initVisitTrend();
initUserGrowth();
initResourceStats();
initArticleStats();
// 监听窗口大小变化,重绘图表
window.addEventListener('resize', function() {
var charts = document.querySelectorAll('.chart-container');
charts.forEach(function(chart) {
echarts.getInstanceByDom(chart)?.resize();
// 确保ECharts已加载
if (typeof echarts === 'undefined') {
console.error('ECharts未加载');
return;
}
// 初始化图表
try {
initVisitTrend();
initUserGrowth();
initResourceStats();
initArticleStats();
// 监听窗口大小变化,重绘图表
window.addEventListener('resize', function() {
var charts = document.querySelectorAll('.chart-container');
charts.forEach(function(chart) {
var instance = echarts.getInstanceByDom(chart);
if (instance) {
instance.resize();
}
});
});
});
} catch (error) {
console.error('初始化图表失败:', error);
}
});
</script>
+21 -13
View File
@@ -6,7 +6,8 @@ namespace app\index\controller;
use think\App;
use think\facade\View;
use think\facade\Request;
use think\facade\Config;
use think\facade\Db;
use app\service\VisitStatsService;
/**
* 前台控制器基础类
@@ -18,6 +19,7 @@ abstract class BaseController
* @var \think\Request
*/
protected $request;
protected $visitStats;
/**
* 应用实例
@@ -34,6 +36,7 @@ abstract class BaseController
{
$this->app = $app;
$this->request = $this->app->request;
$this->visitStats = new VisitStatsService();
// 控制器初始化
$this->initialize();
@@ -44,20 +47,25 @@ abstract class BaseController
*/
protected function initialize()
{
// 记录访问
$this->visitStats->recordVisit($this->getControllerName());
// 获取配置
$configList = Db::table('yz_admin_config')
->where('config_status', 1)
->order('config_sort DESC')
->select()
->toArray();
// 将配置数据转换为键值对形式
$config = [];
foreach ($configList as $item) {
$config[$item['config_name']] = $item['config_value'];
}
// 设置通用变量
View::assign([
'site_name' => '网站名称',
'site_description' => '网站描述',
'site_keywords' => '网站关键词',
'config' => [
'admin_name' => Config::get('site.name', '云泽科技'),
'admin_phone' => Config::get('site.phone', '400-123-4567'),
'admin_email' => Config::get('site.email', 'admin@example.com'),
'admin_wechat' => Config::get('site.wechat_qrcode', '/static/images/wechat_qrcode.jpg'),
'logo' => Config::get('site.logo', '/static/images/logo.png'),
'logo1' => Config::get('site.logo1', '/static/images/logo1.png'),
'admin_route' => Config::get('site.admin_route', '/admin/')
]
'config' => $config
]);
}
+191
View File
@@ -0,0 +1,191 @@
<?php
namespace app\service;
use think\facade\Cache;
use think\facade\Request;
use think\facade\Db;
use think\facade\Log;
class VisitStatsService
{
// Redis实例
protected $redis;
// 键名前缀
protected $prefix = 'stats:';
public function __construct()
{
try {
// 获取Redis处理器
$this->redis = Cache::store('redis')->handler();
} catch (\Exception $e) {
// Redis连接失败,使用文件缓存
$this->redis = Cache::store('file')->handler();
Log::error('Redis连接失败,已切换到文件缓存:' . $e->getMessage());
}
}
/**
* 记录访问并更新统计数据
*/
public function recordVisit(string $page = 'home', string $userId = null): array
{
try {
$date = date('Y-m-d');
$hour = date('H');
$userId = $userId ?? Request::ip();
// 使用管道提高性能
$pipe = $this->redis->multi();
// 总访问量(PV)
$pipe->incr($this->prefix.'total_visits');
// 每日访问量
$pipe->incr($this->prefix.'daily:'.$date);
// 页面统计
$pipe->zIncrBy($this->prefix.'page_views', 1, $page);
// UV统计(使用HyperLogLog节省内存)
$pipe->pfAdd($this->prefix.'uv:'.$date, [$userId]);
// 时段统计
$pipe->hIncrBy($this->prefix.'hourly:'.$date, $hour, 1);
// 执行所有命令
$result = $pipe->exec();
// 更新数据库统计
$this->updateDailyStats($date, [
'total_visits' => $result[0],
'daily_visits' => $result[1],
'unique_visitors' => $this->getUniqueVisitors($date)
]);
return [
'total' => $result[0],
'daily' => $result[1],
'page' => $result[2],
'uv' => $result[3],
'hourly'=> $result[4]
];
} catch (\Exception $e) {
Log::error('访问统计失败:' . $e->getMessage());
return [
'total' => 0,
'daily' => 0,
'page' => 0,
'uv' => 0,
'hourly'=> 0
];
}
}
/**
* 更新每日统计数据
*/
protected function updateDailyStats(string $date, array $stats)
{
try {
// 获取其他统计数据
$otherStats = [
'total_users' => Db::name('users')->count(), // 移除 delete_time 条件
'new_users' => Db::name('users')->whereDay('create_time', $date)->count(),
'total_articles' => Db::name('articles')->where('delete_time', null)->count(),
'daily_articles' => Db::name('articles')->whereDay('create_time', $date)->count(),
'article_views' => Db::name('articles')->whereDay('update_time', $date)->sum('views'),
'total_resources' => Db::name('resources')->where('delete_time', null)->count(),
'daily_resources' => Db::name('resources')->whereDay('create_time', $date)->count(),
'resource_downloads' => Db::name('resources')->whereDay('update_time', $date)->sum('downloads')
];
// 记录日志,方便调试
Log::info('统计数据:' . json_encode($otherStats, JSON_UNESCAPED_UNICODE));
// 合并统计数据
$stats = array_merge($stats, $otherStats);
// 检查记录是否存在
$exists = Db::name('daily_stats')->where('date', $date)->find();
if ($exists) {
// 更新已存在的记录
Db::name('daily_stats')->where('date', $date)->update([
'total_users' => $stats['total_users'],
'new_users' => $stats['new_users'],
'total_visits' => $stats['total_visits'],
'daily_visits' => $stats['daily_visits'],
'unique_visitors' => $stats['unique_visitors'],
'total_articles' => $stats['total_articles'],
'daily_articles' => $stats['daily_articles'],
'article_views' => $stats['article_views'],
'total_resources' => $stats['total_resources'],
'daily_resources' => $stats['daily_resources'],
'resource_downloads' => $stats['resource_downloads']
]);
} else {
// 插入新记录
Db::name('daily_stats')->insert([
'date' => $date,
'total_users' => $stats['total_users'],
'new_users' => $stats['new_users'],
'total_visits' => $stats['total_visits'],
'daily_visits' => $stats['daily_visits'],
'unique_visitors' => $stats['unique_visitors'],
'total_articles' => $stats['total_articles'],
'daily_articles' => $stats['daily_articles'],
'article_views' => $stats['article_views'],
'total_resources' => $stats['total_resources'],
'daily_resources' => $stats['daily_resources'],
'resource_downloads' => $stats['resource_downloads']
]);
}
} catch (\Exception $e) {
Log::error('更新统计数据失败:' . $e->getMessage());
}
}
/**
* 获取总访问量
*/
public function getTotalVisits(): int
{
try {
return (int)$this->redis->get($this->prefix.'total_visits');
} catch (\Exception $e) {
Log::error('获取总访问量失败:' . $e->getMessage());
return 0;
}
}
/**
* 获取当日访问量
*/
public function getDailyVisits(string $date = null): int
{
try {
$date = $date ?? date('Y-m-d');
return (int)$this->redis->get($this->prefix.'daily:'.$date);
} catch (\Exception $e) {
Log::error('获取当日访问量失败:' . $e->getMessage());
return 0;
}
}
/**
* 获取独立访客数(UV)
*/
public function getUniqueVisitors(string $date = null): int
{
try {
$date = $date ?? date('Y-m-d');
return $this->redis->pfCount($this->prefix.'uv:'.$date);
} catch (\Exception $e) {
Log::error('获取独立访客数失败:' . $e->getMessage());
return 0;
}
}
}