更新welcome

This commit is contained in:
云泽网 2025-05-19 22:38:27 +08:00
parent e65f150df7
commit 1840d6f8aa
5 changed files with 926 additions and 122 deletions

View File

@ -68,77 +68,150 @@ class IndexController extends Base{
}
# 欢迎页面
public function welcome(){
// 获取今日统计数据
$today = date('Y-m-d');
$todayStats = DailyStats::where('date', $today)
->find();
try {
// 获取最近7天的日期
$dates = [];
for ($i = 6; $i >= 0; $i--) {
$dates[] = date('Y-m-d', strtotime("-$i day"));
}
// 获取最近7天的访问趋势
$last7Days = DailyStats::where('date', '>=', date('Y-m-d', strtotime('-7 days')))
->where('date', '<=', $today)
->order('date', 'asc')
->select()
->toArray();
// 初始化数据数组
$visitData = [];
$userData = [];
$resourceData = [];
$articleData = [];
// 获取用户增长趋势
$userGrowth = DailyStats::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 = DailyStats::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 = DailyStats::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 = LogsOperation::field('operation_time, module, operation')
->order('operation_time desc')
->limit(10)
->select()
->each(function($item) {
// 格式化时间
$item['time'] = date('Y年m月d日 H:i:s', strtotime($item['operation_time']));
// 格式化操作内容
$item['content'] = date('Y年m月d日 H:i:s', strtotime($item['operation_time'])) . '在【' . $item['module'] . '】模块进行操作:' . $item['operation'];
return $item;
});
// 资源数据
$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();
}
}
/**

View File

@ -211,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>
@ -504,7 +504,6 @@ function initVisitTrend() {
// 用户增长图表
function initUserGrowth() {
var chart = echarts.init(document.getElementById('userGrowth'));
window.userChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -527,7 +526,7 @@ function initUserGrowth() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: {$chartData.userGrowth.dates|json_encode}
},
yAxis: {
type: 'value'
@ -536,7 +535,7 @@ function initUserGrowth() {
{
name: '新增用户',
type: 'bar',
data: [],
data: {$chartData.userGrowth.newUsers|json_encode},
itemStyle: {
color: '#3881fd'
}
@ -545,7 +544,7 @@ function initUserGrowth() {
name: '总用户数',
type: 'line',
smooth: true,
data: [],
data: {$chartData.userGrowth.totalUsers|json_encode},
itemStyle: {
color: '#10b981'
},
@ -561,7 +560,6 @@ function initUserGrowth() {
// 资源统计图表
function initResourceStats() {
var chart = echarts.init(document.getElementById('resourceStats'));
window.resourceChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -573,7 +571,7 @@ function initResourceStats() {
}
},
legend: {
data: ['新增资源', '总资源数']
data: ['新增资源', '总资源数', '下载量']
},
grid: {
left: '3%',
@ -584,7 +582,7 @@ function initResourceStats() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: {$chartData.resourceStats.dates|json_encode}
},
yAxis: {
type: 'value'
@ -593,7 +591,7 @@ function initResourceStats() {
{
name: '新增资源',
type: 'bar',
data: [],
data: {$chartData.resourceStats.newResources|json_encode},
itemStyle: {
color: '#3881fd'
}
@ -602,13 +600,25 @@ function initResourceStats() {
name: '总资源数',
type: 'line',
smooth: true,
data: [],
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: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
};
@ -618,7 +628,6 @@ function initResourceStats() {
// 文章统计图表
function initArticleStats() {
var chart = echarts.init(document.getElementById('articleStats'));
window.articleChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -630,7 +639,7 @@ function initArticleStats() {
}
},
legend: {
data: ['新增文章', '总文章数']
data: ['新增文章', '总文章数', '浏览量']
},
grid: {
left: '3%',
@ -641,7 +650,7 @@ function initArticleStats() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: {$chartData.articleStats.dates|json_encode}
},
yAxis: {
type: 'value'
@ -650,7 +659,7 @@ function initArticleStats() {
{
name: '新增文章',
type: 'bar',
data: [],
data: {$chartData.articleStats.newArticles|json_encode},
itemStyle: {
color: '#3881fd'
}
@ -659,13 +668,25 @@ function initArticleStats() {
name: '总文章数',
type: 'line',
smooth: true,
data: [],
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: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
};
@ -674,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>

View File

@ -0,0 +1,298 @@
<?php /*a:2:{s:58:"E:\Demo\PHP\yunzer\app\admin\view\articles\articlelist.php";i:1747649468;s:51:"E:\Demo\PHP\yunzer\app\admin\view\public\header.php";i:1746890051;}*/ ?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo htmlentities((string) $config['admin_name']); ?></title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" type="text/css" href="/static/layui/css/layui.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/static/css/moban.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/static/css/wangeditor.css" media="all"/>
<style type="text/css">
.header span{background:#009688;margin-left:30px;padding:10px;color:#ffffff;}
.header div{border-bottom:solid 2px #009688;margin-top: 8px;}
.header button{float:right;margin-top:-5px;}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
}
.pagination > li > a,
.pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #337ab7;
text-decoration: none;
background-color: #fff;
border: 1px solid #ddd;
}
.pagination > li:first-child > a,
.pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination > li:last-child > a,
.pagination > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
z-index: 2;
color: #23527c;
background-color: #eee;
border-color: #ddd;
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 3;
color: #fff;
cursor: default;
background-color: #337ab7;
border-color: #337ab7;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #777;
cursor: not-allowed;
background-color: #fff;
border-color: #ddd;
}
.close-img { background: url(/static/images/close_img.png); background-size: 20px 20px; width:20px; height: 20px; position: absolute; right: 5px; top: 5px; z-index: 2;}
</style>
<script type="text/javascript" src="/static/layui/layui.js"></script>
<script type="text/javascript">
layui.use(['layer','form','table','laydate','element','upload'],function(){
layer = layui.layer; // layui 弹框
form = layui.form; // layui form表单
table = layui.table; // layui 表格
laydate = layui.laydate; // layui 时间框
element = layui.element; // layui element
upload = layui.upload; // layui 上传
$ = layui.jquery; // layui jquery
})
</script>
</head>
<body style="padding:10px; box-sizing: border-box;">
<div class="config-container">
<!-- 页面头部样式 -->
<div class="config-header" style="display: flex;flex-direction: column;flex-wrap: wrap;align-items: flex-start;">
<div class="maintitle">
<i class="layui-icon layui-icon-list"></i>
<span>文章列表</span>
</div>
<div style="display: flex;align-items: flex-start;flex-direction: column;gap: 15px;margin-bottom: 10px;">
<div class="shaixuan">
<label>筛选:</label>
<div class="layui-form" style="display: flex; gap: 10px;">
<div class="layui-input-inline">
<select id="categoryFilter" lay-filter="categoryFilter" lay-verify="">
<option value="">全部分类</option>
<?php if(is_array($categories) || $categories instanceof \think\Collection || $categories instanceof \think\Paginator): $i = 0; $__LIST__ = $categories;if( count($__LIST__)==0 ) : echo "" ;else: foreach($__LIST__ as $key=>$category): $mod = ($i % 2 );++$i;?>
<optgroup label="<?php echo htmlentities((string) $category['name']); ?>">
<?php if(is_array($category['children']) || $category['children'] instanceof \think\Collection || $category['children'] instanceof \think\Paginator): $i = 0; $__LIST__ = $category['children'];if( count($__LIST__)==0 ) : echo "" ;else: foreach($__LIST__ as $key=>$subCategory): $mod = ($i % 2 );++$i;?>
<option value="<?php echo htmlentities((string) $subCategory['id']); ?>"><?php echo htmlentities((string) $subCategory['name']); ?></option>
<?php endforeach; endif; else: echo "" ;endif; ?>
</optgroup>
<?php endforeach; endif; else: echo "" ;endif; ?>
</select>
</div>
<div class="layui-input-inline">
<input type="text" id="titleSearch" placeholder="搜索标题" class="layui-input">
</div>
<div class="layui-input-inline">
<input type="text" id="authorSearch" placeholder="搜索作者" class="layui-input">
</div>
<button type="button" class="layui-btn layui-btn-normal" onclick="doSearch()">
<i class="layui-icon layui-icon-search"></i>搜索
</button>
<button type="button" class="layui-btn layui-btn-primary layui-border-blue" onclick="doRefresh()">
<i class="layui-icon layui-icon-refresh"></i>重置
</button>
</div>
</div>
<div>
<button type="button" class="layui-btn layui-btn-normal" onclick="add()">
<i class="layui-icon layui-icon-add-1"></i>添加文章
</button>
<button type="button" class="layui-btn layui-btn-primary layui-border-blue" onclick="refresh()">
<i class="layui-icon layui-icon-refresh"></i>刷新
</button>
</div>
</div>
</div>
<table id="articleTable" lay-filter="articleTable"></table>
</div>
<script type="text/html" id="imageTemplate">
{{# if(d.image){ }}
<img src="{{ d.image }}" style="max-width: 50px; max-height: 50px;">
{{# } }}
</script>
<script type="text/html" id="statusTemplate">
{{# if(d.status === 0){ }}
<span style="color:red;">草稿</span>
{{# } else if(d.status === 1){ }}
<span style="color:orange;">待审核</span>
{{# } else if(d.status === 2){ }}
<span style="color:green;">已发布</span>
{{# } else if(d.status === 3){ }}
<span style="color:gray;">隐藏</span>
{{# } }}
</script>
<script type="text/html" id="operationBar">
<button type="button" class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit">
<i class="layui-icon layui-icon-edit"></i>编辑
</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-xs" lay-event="del">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</script>
<script type="text/javascript">
layui.use(['layer', 'form', 'table'], function () {
var layer = layui.layer;
var $ = layui.jquery;
var form = layui.form;
var table = layui.table;
// 初始化表格
table.render({
elem: '#articleTable',
url: '/admin/articles/articlelist',
method: 'post',
cols: [[
{ field: 'id', title: 'ID', align: 'center', width: 80 },
{ field: 'title', title: '标题' },
{ field: 'cate', title: '分类', align: 'center', width: 180 },
{ field: 'image', title: '封面', templet: '#imageTemplate', align: 'center', width: 180 },
{ field: 'author', title: '作者', align: 'center', width: 120 },
{ field: 'status', title: '状态', templet: '#statusTemplate', align: 'center', width: 80 },
{ field: 'publishdate', title: '发布时间', align: 'center', width: 180 },
{ title: '操作', toolbar: '#operationBar', align: 'center', width: 150 }
]],
page: true,
limit: 10,
limits: [10, 50, 100],
//height: 'full-220'
});
// 监听工具条事件
table.on('tool(articleTable)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
edit(data.id);
} else if (obj.event === 'del') {
del(data.id);
}
});
// 监听分类筛选变化
form.on('select(categoryFilter)', function (data) {
filterByCategory(data.value);
});
});
function filterByCategory(categoryId) {
reloadTable();
}
function doSearch() {
var titleKeyword = $('#titleSearch').val().trim();
var authorKeyword = $('#authorSearch').val().trim();
if (!titleKeyword && !authorKeyword && !$('#categoryFilter').val()) {
layer.msg('请输入搜索条件', { icon: 0 });
return;
}
reloadTable();
}
function doRefresh() {
// 清空搜索条件
$('#titleSearch').val('');
$('#authorSearch').val('');
$('#categoryFilter').val('');
layui.form.render('select'); // 重新渲染select
// 重新加载表格,不带任何筛选条件
layui.table.reload('articleTable', {
where: {},
page: {
curr: 1
}
});
layer.msg('已重置', { icon: 1 });
}
function reloadTable() {
var categoryId = $('#categoryFilter').val();
var categoryName = categoryId ? $('#categoryFilter option[value="' + categoryId + '"]').text() : '';
var titleKeyword = $('#titleSearch').val().trim();
var authorKeyword = $('#authorSearch').val().trim();
layui.table.reload('articleTable', {
where: {
category: categoryName,
title: titleKeyword,
author: authorKeyword
},
page: {
curr: 1
}
});
}
function add() {
window.location.href = '/admin/articles/add';
}
function edit(id) {
window.location.href = '/admin/articles/edit?id=' + id;
}
function del(id) {
layer.confirm('确定要删除该文章吗?', {
btn: ['确定', '取消']
}, function () {
$.post('/admin/articles/delete', { id: id }, function (res) {
if (res.code == 0) {
layer.msg(res.msg, { icon: 1 });
setTimeout(function () {
layui.table.reload('articleTable');
}, 1000);
} else {
layer.msg(res.msg, { icon: 2 });
}
});
});
}
function refresh() {
layui.table.reload('articleTable');
}
</script>

View File

@ -0,0 +1,363 @@
<?php /*a:2:{s:50:"E:\Demo\PHP\yunzer\app\admin\view\articles\add.php";i:1747649140;s:51:"E:\Demo\PHP\yunzer\app\admin\view\public\header.php";i:1746890051;}*/ ?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo htmlentities((string) $config['admin_name']); ?></title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" type="text/css" href="/static/layui/css/layui.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/static/css/moban.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/static/css/wangeditor.css" media="all"/>
<style type="text/css">
.header span{background:#009688;margin-left:30px;padding:10px;color:#ffffff;}
.header div{border-bottom:solid 2px #009688;margin-top: 8px;}
.header button{float:right;margin-top:-5px;}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
}
.pagination > li > a,
.pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #337ab7;
text-decoration: none;
background-color: #fff;
border: 1px solid #ddd;
}
.pagination > li:first-child > a,
.pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination > li:last-child > a,
.pagination > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
z-index: 2;
color: #23527c;
background-color: #eee;
border-color: #ddd;
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 3;
color: #fff;
cursor: default;
background-color: #337ab7;
border-color: #337ab7;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #777;
cursor: not-allowed;
background-color: #fff;
border-color: #ddd;
}
.close-img { background: url(/static/images/close_img.png); background-size: 20px 20px; width:20px; height: 20px; position: absolute; right: 5px; top: 5px; z-index: 2;}
</style>
<script type="text/javascript" src="/static/layui/layui.js"></script>
<script type="text/javascript">
layui.use(['layer','form','table','laydate','element','upload'],function(){
layer = layui.layer; // layui 弹框
form = layui.form; // layui form表单
table = layui.table; // layui 表格
laydate = layui.laydate; // layui 时间框
element = layui.element; // layui element
upload = layui.upload; // layui 上传
$ = layui.jquery; // layui jquery
})
</script>
</head>
<body style="padding:10px; box-sizing: border-box;">
<div class="config-container">
<div class="config-header" style="display:flex;justify-content: space-between;">
<div>
<span>添加文章</span>
</div>
<div>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" onclick="goBack()">
<i class="layui-icon layui-icon-return"></i>返回
</button>
</div>
</div>
<form class="layui-form" action="" method="post">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input type="text" name="title" required lay-verify="required" placeholder="请输入文章标题" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<select name="cate" lay-verify="required">
<option value="">请选择分类</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">描述</label>
<div class="layui-input-block">
<textarea name="desc" placeholder="请输入描述内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">作者</label>
<div class="layui-input-block">
<input type="text" name="author" required lay-verify="required" placeholder="请输入作者" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">封面</label>
<div class="layui-input-block">
<button type="button" class="layui-btn" id="upload-btn">
<i class="layui-icon layui-icon-upload"></i> 图片上传
</button>
<div style="width: 120px;">
<div class="layui-upload-list">
<img class="layui-upload-img" id="upload-img"
style="width: 118px; height: 118px;object-fit: cover;">
<div id="upload-text"></div>
</div>
<div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="filter-demo">
<div class="layui-progress-bar" lay-percent=""></div>
</div>
<input type="hidden" name="image" id="image" value="">
</div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<div id="editor—wrapper" id="content" name="content" style="border: 1px solid #ccc;">
<div id="toolbar-container" style="border-bottom: 1px solid #ccc;"><!-- 工具栏 --></div>
<div id="editor-container" style="height: 800px;"><!-- 编辑器 --></div>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formSubmit">立即提交</button>
<button class="layui-btn layui-btn-primary" lay-submit lay-filter="formDraft">存草稿</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
<script src="/static/js/wangeditor.js"></script>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
var $ = layui.jquery;
var upload = layui.upload;
var element = layui.element;
// 图片上传
var uploadInst = upload.render({
elem: '#upload-btn',
url: '<?php echo url("index/upload_img"); ?>', // 上传图片接口
before: function (obj) {
// 预读本地文件示例不支持ie8
obj.preview(function (index, file, result) {
$('#upload-img').attr('src', result); // 图片链接base64
});
element.progress('filter-demo', '0%'); // 进度条复位
layer.msg('上传中', { icon: 16, time: 0 });
},
done: function (res) {
// 若上传失败
if (res.code > 0) {
return layer.msg('上传失败');
}
// 上传成功
$('#image').val(res.data); // 设置图片路径到隐藏输入框
$('#upload-text').html(''); // 置空上传失败的状态
layer.msg('上传成功', { icon: 1 });
},
uploadError: function () { // 这里改为 uploadError
// 演示失败状态,并实现重传
var demoText = $('#upload-text');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function () {
uploadInst.upload();
});
},
// 进度条
progress: function (n, elem, e) {
element.progress('filter-demo', n + '%'); // 可配合 layui 进度条元素使用
if (n == 100) {
layer.msg('上传完毕', { icon: 1 });
}
}
});
// 获取分类列表
$.get('<?php echo url("articles/getcate"); ?>', function (res) {
if (res.code == 0) {
var html = '<option value="">请选择分类</option>';
res.data.forEach(function (item) {
// 如果cid为0则设置为禁用
var disabled = item.cid == 0 ? 'disabled' : '';
html += '<option value="' + item.id + '" ' + disabled + '>' + item.name + '</option>';
// 如果有子分类,添加子分类选项
if (item.children && item.children.length > 0) {
item.children.forEach(function (child) {
html += '<option value="' + child.id + '">├─ ' + child.name + '</option>';
});
}
});
$('select[name="cate"]').html(html);
form.render('select');
} else {
layer.msg(res.msg, { icon: 2 });
}
});
// 表单提交
form.on('submit(formSubmit)', function (data) {
// 获取编辑器内容
var content = editor.getHtml();
if (!content || content === '<p><br></p>') {
layer.msg('请输入文章内容', { icon: 2 });
return false;
}
var loadIndex = layer.load(2);
data.field.content = content;
$.ajax({
url: '<?php echo url("articles/add"); ?>',
type: 'POST',
data: data.field,
success: function (res) {
layer.close(loadIndex);
if (res.code == 0) {
layer.msg(res.msg, { icon: 1 });
setTimeout(function () {
window.location.href = '<?php echo url("articles/articlelist"); ?>';
}, 1000);
} else {
layer.msg(res.msg, { icon: 2 });
}
}
});
return false;
});
});
</script>
<!-- wangeditor编辑器脚本 -->
<script>
const { createEditor, createToolbar } = window.wangEditor
const editorConfig = {
MENU_CONF: {},
placeholder: '请输入内容...',
onChange(editor) {
const html = editor.getHtml()
},
}
// 配置图片上传
editorConfig.MENU_CONF['uploadImage'] = {
server: '<?php echo url("index/upload_img"); ?>',
fieldName: 'file',
maxFileSize: 10 * 1024 * 1024, // 10M
maxNumberOfFiles: 10,
allowedFileTypes: ['image/*'],
meta: {
token: 'xxx'
},
metaWithUrl: true,
headers: {
Accept: 'text/x-json'
},
timeout: 5 * 1000, // 5s
onBeforeUpload(file) {
console.log('准备上传图片', file)
return file
},
onProgress(progress) {
console.log('上传进度', progress)
},
onSuccess(file, res) {
console.log('上传成功', file, res)
},
onFailed(file, res) {
layer.msg('上传失败:' + res.msg, { icon: 2 })
console.log('上传失败', file, res)
},
onError(file, err, res) {
layer.msg('上传出错:' + err.message, { icon: 2 })
console.error('上传出错', file, err, res)
},
customInsert(res, insertFn) {
// res 即服务端的返回结果
if (res.code === 0 && res.data) {
// 直接使用res.data作为图片地址
insertFn(res.data);
} else {
layer.msg('图片上传失败', { icon: 2 });
}
}
}
const editor = createEditor({
selector: '#editor-container',
html: '<p><br></p>',
config: editorConfig,
mode: 'default', // or 'simple'
})
const toolbarConfig = {}
const toolbar = createToolbar({
editor,
selector: '#toolbar-container',
config: toolbarConfig,
mode: 'default', // or 'simple'
})
</script>
<script>
//返回文章列表
function goBack() {
window.location.href = '<?php echo url("articles/articlelist"); ?>';
}
</script>

View File

@ -1,4 +1,4 @@
<?php /*a:3:{s:51:"E:\Demo\PHP\yunzer\app\admin\view\index\welcome.php";i:1747663514;s:51:"E:\Demo\PHP\yunzer\app\admin\view\public\header.php";i:1746890051;s:49:"E:\Demo\PHP\yunzer\app\admin\view\public\tail.php";i:1745855804;}*/ ?>
<?php /*a:3:{s:51:"E:\Demo\PHP\yunzer\app\admin\view\index\welcome.php";i:1747665416;s:51:"E:\Demo\PHP\yunzer\app\admin\view\public\header.php";i:1746890051;s:49:"E:\Demo\PHP\yunzer\app\admin\view\public\tail.php";i:1745855804;}*/ ?>
<!DOCTYPE html>
<html>
<head>
@ -305,22 +305,22 @@
<div class="stats-container">
<div class="stat-card">
<div class="stat-title">用户总数</div>
<div class="stat-value"><?php echo htmlentities((string) number_format($stats['total_users'])); ?></div>
<div class="stat-value"><?php echo htmlentities((string) number_format($todayStats['total_users'])); ?></div>
<div class="stat-icon">👥</div>
</div>
<div class="stat-card">
<div class="stat-title">今日访问</div>
<div class="stat-value"><?php echo htmlentities((string) number_format($stats['daily_visits'])); ?></div>
<div class="stat-value"><?php echo htmlentities((string) number_format($todayStats['daily_visits'])); ?></div>
<div class="stat-icon">📊</div>
</div>
<div class="stat-card">
<div class="stat-title">文章总数</div>
<div class="stat-value"><?php echo htmlentities((string) number_format($stats['total_articles'])); ?></div>
<div class="stat-value"><?php echo htmlentities((string) number_format($todayStats['total_articles'])); ?></div>
<div class="stat-icon">📝</div>
</div>
<div class="stat-card">
<div class="stat-title">资源总数</div>
<div class="stat-value"><?php echo htmlentities((string) number_format($stats['total_resources'])); ?></div>
<div class="stat-value"><?php echo htmlentities((string) number_format($todayStats['total_resources'])); ?></div>
<div class="stat-icon">📦</div>
</div>
</div>
@ -598,7 +598,6 @@ function initVisitTrend() {
// 用户增长图表
function initUserGrowth() {
var chart = echarts.init(document.getElementById('userGrowth'));
window.userChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -621,7 +620,7 @@ function initUserGrowth() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: <?php echo htmlentities((string) json_encode($chartData['userGrowth']['dates'])); ?>
},
yAxis: {
type: 'value'
@ -630,7 +629,7 @@ function initUserGrowth() {
{
name: '新增用户',
type: 'bar',
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['userGrowth']['newUsers'])); ?>,
itemStyle: {
color: '#3881fd'
}
@ -639,7 +638,7 @@ function initUserGrowth() {
name: '总用户数',
type: 'line',
smooth: true,
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['userGrowth']['totalUsers'])); ?>,
itemStyle: {
color: '#10b981'
},
@ -655,7 +654,6 @@ function initUserGrowth() {
// 资源统计图表
function initResourceStats() {
var chart = echarts.init(document.getElementById('resourceStats'));
window.resourceChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -667,7 +665,7 @@ function initResourceStats() {
}
},
legend: {
data: ['新增资源', '总资源数']
data: ['新增资源', '总资源数', '下载量']
},
grid: {
left: '3%',
@ -678,7 +676,7 @@ function initResourceStats() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: <?php echo htmlentities((string) json_encode($chartData['resourceStats']['dates'])); ?>
},
yAxis: {
type: 'value'
@ -687,7 +685,7 @@ function initResourceStats() {
{
name: '新增资源',
type: 'bar',
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['resourceStats']['newResources'])); ?>,
itemStyle: {
color: '#3881fd'
}
@ -696,13 +694,25 @@ function initResourceStats() {
name: '总资源数',
type: 'line',
smooth: true,
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['resourceStats']['totalResources'])); ?>,
itemStyle: {
color: '#10b981'
},
lineStyle: {
width: 3
}
},
{
name: '下载量',
type: 'line',
smooth: true,
data: <?php echo htmlentities((string) json_encode($chartData['resourceStats']['downloads'])); ?>,
itemStyle: {
color: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
};
@ -712,7 +722,6 @@ function initResourceStats() {
// 文章统计图表
function initArticleStats() {
var chart = echarts.init(document.getElementById('articleStats'));
window.articleChart = chart;
var option = {
tooltip: {
trigger: 'axis',
@ -724,7 +733,7 @@ function initArticleStats() {
}
},
legend: {
data: ['新增文章', '总文章数']
data: ['新增文章', '总文章数', '浏览量']
},
grid: {
left: '3%',
@ -735,7 +744,7 @@ function initArticleStats() {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
data: <?php echo htmlentities((string) json_encode($chartData['articleStats']['dates'])); ?>
},
yAxis: {
type: 'value'
@ -744,7 +753,7 @@ function initArticleStats() {
{
name: '新增文章',
type: 'bar',
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['articleStats']['newArticles'])); ?>,
itemStyle: {
color: '#3881fd'
}
@ -753,13 +762,25 @@ function initArticleStats() {
name: '总文章数',
type: 'line',
smooth: true,
data: [],
data: <?php echo htmlentities((string) json_encode($chartData['articleStats']['totalArticles'])); ?>,
itemStyle: {
color: '#10b981'
},
lineStyle: {
width: 3
}
},
{
name: '浏览量',
type: 'line',
smooth: true,
data: <?php echo htmlentities((string) json_encode($chartData['articleStats']['views'])); ?>,
itemStyle: {
color: '#f59e0b'
},
lineStyle: {
width: 3
}
}
]
};
@ -768,18 +789,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>