更新后端
This commit is contained in:
@@ -7,6 +7,7 @@ use app\admin\controller\Base;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use think\facade\Request;
|
||||
use app\common\service\LogService;
|
||||
|
||||
class Article extends Base
|
||||
{
|
||||
@@ -411,4 +412,20 @@ class Article extends Base
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '删除成功', 'data' => []]);
|
||||
}
|
||||
|
||||
//统计文章数量
|
||||
public function counts() {
|
||||
$total = Db::table('yz_article')
|
||||
->where('delete_time', null)
|
||||
->where('status', '<>', 3)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'total' => $total
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,206 @@ class Index extends Base{
|
||||
}
|
||||
# 欢迎页面
|
||||
public function welcome(){
|
||||
View::assign([
|
||||
'time' => date('Y-m-d',$_SERVER['REQUEST_TIME']),
|
||||
// 获取今日统计数据
|
||||
$today = date('Y-m-d');
|
||||
$todayStats = Db::name('yz_daily_stats')
|
||||
->where('date', $today)
|
||||
->find();
|
||||
|
||||
// 获取最近7天的访问趋势
|
||||
$last7Days = Db::name('yz_daily_stats')
|
||||
->where('date', '>=', date('Y-m-d', strtotime('-7 days')))
|
||||
->where('date', '<=', $today)
|
||||
->order('date', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 获取用户增长趋势
|
||||
$userGrowth = Db::name('yz_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();
|
||||
|
||||
// 获取资源下载统计
|
||||
$resourceStats = Db::name('yz_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();
|
||||
|
||||
// 获取文章访问统计
|
||||
$articleStats = Db::name('yz_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();
|
||||
|
||||
// 获取最近的活动记录
|
||||
$recentActivities = $this->getRecentActivities();
|
||||
|
||||
// 准备图表数据
|
||||
$chartData = [
|
||||
'visitTrend' => $this->formatVisitTrendData($last7Days),
|
||||
'userGrowth' => $this->formatUserGrowthData($userGrowth),
|
||||
'resourceStats' => $this->formatResourceStatsData($resourceStats),
|
||||
'articleStats' => $this->formatArticleStatsData($articleStats)
|
||||
];
|
||||
|
||||
// 准备统计数据
|
||||
$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,
|
||||
];
|
||||
|
||||
return View::fetch('', [
|
||||
'stats' => $stats,
|
||||
'chartData' => $chartData,
|
||||
'recentActivities' => $recentActivities
|
||||
]);
|
||||
return View::fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的活动记录
|
||||
*/
|
||||
private function getRecentActivities()
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$activities = [];
|
||||
|
||||
// 获取今日新用户
|
||||
$newUsers = Db::name('yz_daily_stats')
|
||||
->where('date', $today)
|
||||
->value('new_users');
|
||||
if ($newUsers > 0) {
|
||||
$activities[] = [
|
||||
'icon' => '👥',
|
||||
'title' => '新增用户 ' . $newUsers . ' 人',
|
||||
'time' => '今日'
|
||||
];
|
||||
}
|
||||
|
||||
// 获取今日文章
|
||||
$newArticles = Db::name('yz_daily_stats')
|
||||
->where('date', $today)
|
||||
->value('daily_articles');
|
||||
if ($newArticles > 0) {
|
||||
$activities[] = [
|
||||
'icon' => '📝',
|
||||
'title' => '发布文章 ' . $newArticles . ' 篇',
|
||||
'time' => '今日'
|
||||
];
|
||||
}
|
||||
|
||||
// 获取今日资源
|
||||
$newResources = Db::name('yz_daily_stats')
|
||||
->where('date', $today)
|
||||
->value('daily_resources');
|
||||
if ($newResources > 0) {
|
||||
$activities[] = [
|
||||
'icon' => '📦',
|
||||
'title' => '上传资源 ' . $newResources . ' 个',
|
||||
'time' => '今日'
|
||||
];
|
||||
}
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化访问趋势数据
|
||||
*/
|
||||
private function formatVisitTrendData($data)
|
||||
{
|
||||
$dates = [];
|
||||
$visits = [];
|
||||
$uvs = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$dates[] = date('m-d', strtotime($item['date']));
|
||||
$visits[] = $item['daily_visits'];
|
||||
$uvs[] = $item['unique_visitors'];
|
||||
}
|
||||
|
||||
return [
|
||||
'dates' => $dates,
|
||||
'visits' => $visits,
|
||||
'uvs' => $uvs
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化用户增长数据
|
||||
*/
|
||||
private function formatUserGrowthData($data)
|
||||
{
|
||||
$dates = [];
|
||||
$newUsers = [];
|
||||
$totalUsers = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$dates[] = date('m-d', strtotime($item['date']));
|
||||
$newUsers[] = $item['new_users'];
|
||||
$totalUsers[] = $item['total_users'];
|
||||
}
|
||||
|
||||
return [
|
||||
'dates' => $dates,
|
||||
'newUsers' => $newUsers,
|
||||
'totalUsers' => $totalUsers
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化资源统计数据
|
||||
*/
|
||||
private function formatResourceStatsData($data)
|
||||
{
|
||||
$dates = [];
|
||||
$resources = [];
|
||||
$downloads = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$dates[] = date('m-d', strtotime($item['date']));
|
||||
$resources[] = $item['daily_resources'];
|
||||
$downloads[] = $item['resource_downloads'];
|
||||
}
|
||||
|
||||
return [
|
||||
'dates' => $dates,
|
||||
'resources' => $resources,
|
||||
'downloads' => $downloads
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文章统计数据
|
||||
*/
|
||||
private function formatArticleStatsData($data)
|
||||
{
|
||||
$dates = [];
|
||||
$articles = [];
|
||||
$views = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$dates[] = date('m-d', strtotime($item['date']));
|
||||
$articles[] = $item['daily_articles'];
|
||||
$views[] = $item['article_views'];
|
||||
}
|
||||
|
||||
return [
|
||||
'dates' => $dates,
|
||||
'articles' => $articles,
|
||||
'views' => $views
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,4 +439,5 @@ class Index extends Base{
|
||||
return json(['code'=>1, 'msg'=>$e->getMessage()])->send();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\controller\Base;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use think\facade\View;
|
||||
|
||||
class Log extends Base
|
||||
{
|
||||
/**
|
||||
* 登录日志列表
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if (Request::isPost()) {
|
||||
$page = input('post.page', 1);
|
||||
$limit = input('post.limit', 10);
|
||||
$username = input('post.username');
|
||||
$ip = input('post.ip');
|
||||
$status = input('post.status');
|
||||
$startTime = input('post.start_time');
|
||||
$endTime = input('post.end_time');
|
||||
|
||||
$query = Db::name('yz_logs_login');
|
||||
|
||||
// 搜索条件
|
||||
if ($username) {
|
||||
$query = $query->where('username', 'like', "%{$username}%");
|
||||
}
|
||||
if ($ip) {
|
||||
$query = $query->where('ip_address', 'like', "%{$ip}%");
|
||||
}
|
||||
if ($status !== '') {
|
||||
$query = $query->where('login_status', $status);
|
||||
}
|
||||
if ($startTime) {
|
||||
$query = $query->where('login_time', '>=', $startTime);
|
||||
}
|
||||
if ($endTime) {
|
||||
$query = $query->where('login_time', '<=', $endTime);
|
||||
}
|
||||
|
||||
$count = $query->count();
|
||||
$list = $query->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'msg' => '获取成功',
|
||||
'count' => $count,
|
||||
'data' => $list
|
||||
]);
|
||||
}
|
||||
|
||||
return View::fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志列表
|
||||
*/
|
||||
public function operation()
|
||||
{
|
||||
if (Request::isPost()) {
|
||||
$page = input('post.page', 1);
|
||||
$limit = input('post.limit', 10);
|
||||
$username = input('post.username');
|
||||
$module = input('post.module');
|
||||
$operation = input('post.operation');
|
||||
$status = input('post.status');
|
||||
$startTime = input('post.start_time');
|
||||
$endTime = input('post.end_time');
|
||||
|
||||
$query = Db::name('yz_logs_operation');
|
||||
|
||||
// 搜索条件
|
||||
if ($username) {
|
||||
$query = $query->where('username', 'like', "%{$username}%");
|
||||
}
|
||||
if ($module) {
|
||||
$query = $query->where('module', 'like', "%{$module}%");
|
||||
}
|
||||
if ($operation) {
|
||||
$query = $query->where('operation', 'like', "%{$operation}%");
|
||||
}
|
||||
if ($status !== '') {
|
||||
$query = $query->where('status', $status);
|
||||
}
|
||||
if ($startTime) {
|
||||
$query = $query->where('operation_time', '>=', $startTime);
|
||||
}
|
||||
if ($endTime) {
|
||||
$query = $query->where('operation_time', '<=', $endTime);
|
||||
}
|
||||
|
||||
$count = $query->count();
|
||||
$list = $query->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'msg' => '获取成功',
|
||||
'count' => $count,
|
||||
'data' => $list
|
||||
]);
|
||||
}
|
||||
|
||||
return View::fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
*/
|
||||
protected function recordOperation($operation, $status = 1, $error_message = '')
|
||||
{
|
||||
$data = [
|
||||
'username' => session('admin_username'),
|
||||
'module' => '日志管理',
|
||||
'operation' => $operation,
|
||||
'request_method' => Request::method(),
|
||||
'request_url' => Request::url(true),
|
||||
'request_params' => json_encode(Request::param(), JSON_UNESCAPED_UNICODE),
|
||||
'ip_address' => Request::ip(),
|
||||
'status' => $status,
|
||||
'error_message' => $error_message,
|
||||
'operation_time' => date('Y-m-d H:i:s'),
|
||||
'execution_time' => 0
|
||||
];
|
||||
|
||||
Db::name('yz_logs_operation')->insert($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除登录日志
|
||||
*/
|
||||
public function deleteLogin()
|
||||
{
|
||||
$id = input('post.id');
|
||||
try {
|
||||
if (Db::name('yz_logs_login')->delete($id)) {
|
||||
$this->recordOperation('删除登录日志');
|
||||
return json(['code' => 0, 'msg' => '删除成功']);
|
||||
}
|
||||
$this->recordOperation('删除登录日志', 0, '删除失败');
|
||||
return json(['code' => 1, 'msg' => '删除失败']);
|
||||
} catch (\Exception $e) {
|
||||
$this->recordOperation('删除登录日志', 0, $e->getMessage());
|
||||
return json(['code' => 1, 'msg' => '删除失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除操作日志
|
||||
*/
|
||||
public function deleteOperation()
|
||||
{
|
||||
$id = input('post.id');
|
||||
try {
|
||||
if (Db::name('yz_logs_operation')->delete($id)) {
|
||||
$this->recordOperation('删除操作日志');
|
||||
return json(['code' => 0, 'msg' => '删除成功']);
|
||||
}
|
||||
$this->recordOperation('删除操作日志', 0, '删除失败');
|
||||
return json(['code' => 1, 'msg' => '删除失败']);
|
||||
} catch (\Exception $e) {
|
||||
$this->recordOperation('删除操作日志', 0, $e->getMessage());
|
||||
return json(['code' => 1, 'msg' => '删除失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空登录日志
|
||||
*/
|
||||
public function clearLogin()
|
||||
{
|
||||
try {
|
||||
if (Db::name('yz_logs_login')->where('1=1')->delete()) {
|
||||
$this->recordOperation('清空登录日志');
|
||||
return json(['code' => 0, 'msg' => '清空成功']);
|
||||
}
|
||||
$this->recordOperation('清空登录日志', 0, '清空失败');
|
||||
return json(['code' => 1, 'msg' => '清空失败']);
|
||||
} catch (\Exception $e) {
|
||||
$this->recordOperation('清空登录日志', 0, $e->getMessage());
|
||||
return json(['code' => 1, 'msg' => '清空失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空操作日志
|
||||
*/
|
||||
public function clearOperation()
|
||||
{
|
||||
try {
|
||||
if (Db::name('yz_logs_operation')->where('1=1')->delete()) {
|
||||
$this->recordOperation('清空操作日志');
|
||||
return json(['code' => 0, 'msg' => '清空成功']);
|
||||
}
|
||||
$this->recordOperation('清空操作日志', 0, '清空失败');
|
||||
return json(['code' => 1, 'msg' => '清空失败']);
|
||||
} catch (\Exception $e) {
|
||||
$this->recordOperation('清空操作日志', 0, $e->getMessage());
|
||||
return json(['code' => 1, 'msg' => '清空失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,4 +445,20 @@ class Resources extends Base
|
||||
return View::fetch();
|
||||
}
|
||||
|
||||
//统计资源数量
|
||||
public function counts() {
|
||||
$total = Db::table('yz_resources')
|
||||
->where('delete_time', null)
|
||||
->where('status', '<>', 3)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'total' => $total
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,7 +109,7 @@
|
||||
<li class="layui-nav-item" data-name="index/welcome">
|
||||
<a href="javascript:;" lay-tips="工作台" lay-direction="2"
|
||||
onclick="menuFire('index/welcome',1)">
|
||||
<i class="layui-icon layui-icon-home" style="margin-top: -30px;"></i>
|
||||
<i class="layui-icon layui-icon-home" style="margin-top: -20px;"></i>
|
||||
<cite>工作台</cite>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,78 +1,205 @@
|
||||
{include file="public/header" /}
|
||||
<script src="__STATIC__/js/jquery.min.js"></script>
|
||||
<style>
|
||||
.dashboard-container {
|
||||
padding: 20px;
|
||||
font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||
padding: 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
/* background-color: #f5f7fa; */
|
||||
/* min-height: calc(100vh - 60px); */
|
||||
}
|
||||
.welcome-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: linear-gradient(135deg, #3881fd 0%, #2c5fd9 100%);
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
color: white;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 20px rgba(56, 129, 253, 0.15);
|
||||
}
|
||||
.welcome-header h1 {
|
||||
color: #3881fd;
|
||||
font-weight: 300;
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.welcome-header p {
|
||||
font-size: 15px;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
.stats-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
padding: 20px;
|
||||
min-width: 200px;
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: #3881fd;
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.stat-card .stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #3881fd;
|
||||
margin: 10px 0;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 12px 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.stat-card .stat-title {
|
||||
color: #7f8c8d;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.stat-card .stat-icon {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 48px;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.quick-actions {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.quick-actions h2 {
|
||||
color: #2c3e50;
|
||||
color: #1e293b;
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.quick-actions h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #3881fd;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.action-button {
|
||||
background-color: #f8f9fa;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
color: #3881fd;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
.action-button:hover {
|
||||
background-color: #e9ecef;
|
||||
background: #3881fd;
|
||||
color: white;
|
||||
border-color: #3881fd;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.action-button i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.recent-activity {
|
||||
margin-top: 24px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.activity-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
.activity-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.activity-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.activity-content {
|
||||
flex: 1;
|
||||
}
|
||||
.activity-title {
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.activity-time {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.charts-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 24px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.chart-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.chart-card h2 {
|
||||
color: #1e293b;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.chart-card h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #3881fd;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -85,34 +212,84 @@
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">用户总数</div>
|
||||
<div class="stat-value">1,234</div>
|
||||
<div class="stat-value">{$stats.total_users|number_format}</div>
|
||||
<div class="stat-icon">👥</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">今日访问</div>
|
||||
<div class="stat-value">256</div>
|
||||
<div class="stat-value">{$stats.daily_visits|number_format}</div>
|
||||
<div class="stat-icon">📊</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">数据总量</div>
|
||||
<div class="stat-value">8,642</div>
|
||||
<div class="stat-title">文章总数</div>
|
||||
<div class="stat-value">{$stats.total_articles|number_format}</div>
|
||||
<div class="stat-icon">📝</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">系统消息</div>
|
||||
<div class="stat-value">12</div>
|
||||
<div class="stat-title">资源总数</div>
|
||||
<div class="stat-value">{$stats.total_resources|number_format}</div>
|
||||
<div class="stat-icon">📦</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<h2>快捷操作</h2>
|
||||
<div class="action-buttons">
|
||||
<button class="action-button">用户管理</button>
|
||||
<button class="action-button">内容发布</button>
|
||||
<button class="action-button">数据统计</button>
|
||||
<button class="action-button">系统设置</button>
|
||||
<button class="action-button">清除缓存</button>
|
||||
<a href="{:url('user/index')}" class="action-button">
|
||||
<i class="fas fa-users"></i>用户管理
|
||||
</a>
|
||||
<a href="{:url('content/publish')}" class="action-button">
|
||||
<i class="fas fa-edit"></i>内容发布
|
||||
</a>
|
||||
<a href="{:url('statistics/index')}" class="action-button">
|
||||
<i class="fas fa-chart-bar"></i>数据统计
|
||||
</a>
|
||||
<a href="{:url('system/settings')}" class="action-button">
|
||||
<i class="fas fa-cog"></i>系统设置
|
||||
</a>
|
||||
<a href="{:url('system/clear_cache')}" class="action-button">
|
||||
<i class="fas fa-broom"></i>清除缓存
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent-activity">
|
||||
<h2>最近动态</h2>
|
||||
<div class="activity-list">
|
||||
{volist name="recentActivities" id="activity"}
|
||||
<div class="activity-item">
|
||||
<div class="activity-icon">{$activity.icon}</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">{$activity.title}</div>
|
||||
<div class="activity-time">{$activity.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/volist}
|
||||
</div>
|
||||
</div>
|
||||
<div class="charts-container">
|
||||
<div class="chart-card">
|
||||
<h2>访问趋势</h2>
|
||||
<div id="visitTrend" class="chart-container"></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h2>用户增长</h2>
|
||||
<div id="userGrowth" class="chart-container"></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h2>资源统计</h2>
|
||||
<div id="resourceStats" class="chart-container"></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<h2>文章统计</h2>
|
||||
<div id="articleStats" class="chart-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script src="__JS__/echarts.min.js"></script>
|
||||
<script>
|
||||
function updateTime() {
|
||||
var now = new Date();
|
||||
@@ -123,12 +300,10 @@ function updateTime() {
|
||||
var minutes = now.getMinutes();
|
||||
var seconds = now.getSeconds();
|
||||
|
||||
// 补零函数
|
||||
var padZero = function(num) {
|
||||
return num < 10 ? '0' + num : num;
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
var timeString = year + '年' +
|
||||
padZero(month) + '月' +
|
||||
padZero(date) + '日 ' +
|
||||
@@ -139,11 +314,296 @@ function updateTime() {
|
||||
document.getElementById('current-time').innerHTML = timeString;
|
||||
}
|
||||
|
||||
// 页面加载完立即执行一次
|
||||
updateTime();
|
||||
// 获取文章统计数据
|
||||
function getArticleCounts() {
|
||||
fetch('{:url("article/counts")}')
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
console.log('文章统计接口返回数据:', res);
|
||||
if (res.code === 0 && res.data) {
|
||||
// 更新文章总数
|
||||
document.querySelector('.stat-card:nth-child(3) .stat-value').textContent = res.data.total.toLocaleString();
|
||||
} else {
|
||||
console.warn('文章统计接口返回异常:', res);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取文章统计失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// 每秒更新一次时间
|
||||
// 获取资源统计数据
|
||||
function getResourcesCounts() {
|
||||
fetch('{:url("resources/counts")}')
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
console.log('资源统计接口返回数据:', res);
|
||||
if (res.code === 0 && res.data) {
|
||||
// 更新资源总数
|
||||
document.querySelector('.stat-card:nth-child(4) .stat-value').textContent = res.data.total.toLocaleString();
|
||||
} else {
|
||||
console.warn('资源统计接口返回异常:', res);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取资源统计失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
updateTime();
|
||||
setInterval(updateTime, 1000);
|
||||
|
||||
// 页面加载完成后获取统计数据
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
getArticleCounts();
|
||||
getResourcesCounts();
|
||||
});
|
||||
|
||||
// 访问趋势图表
|
||||
function initVisitTrend() {
|
||||
var chart = echarts.init(document.getElementById('visitTrend'));
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['访问量', '独立访客']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: {$chartData.visitTrend.dates|json_encode},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e2e8f0'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e2e8f0'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f1f5f9'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '访问量',
|
||||
data: {$chartData.visitTrend.visits|json_encode},
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.1
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}, {
|
||||
name: '独立访客',
|
||||
data: {$chartData.visitTrend.uvs|json_encode},
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}]
|
||||
};
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
// 用户增长图表
|
||||
function initUserGrowth() {
|
||||
var chart = echarts.init(document.getElementById('userGrowth'));
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增用户', '总用户数']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: {$chartData.userGrowth.dates|json_encode}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'bar',
|
||||
data: {$chartData.userGrowth.newUsers|json_encode},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '总用户数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.userGrowth.totalUsers|json_encode},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
// 资源统计图表
|
||||
function initResourceStats() {
|
||||
var chart = echarts.init(document.getElementById('resourceStats'));
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增资源', '下载量']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: {$chartData.resourceStats.dates|json_encode}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增资源',
|
||||
type: 'bar',
|
||||
data: {$chartData.resourceStats.resources|json_encode},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '下载量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.resourceStats.downloads|json_encode},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
// 文章统计图表
|
||||
function initArticleStats() {
|
||||
var chart = echarts.init(document.getElementById('articleStats'));
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['新增文章', '访问量']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: {$chartData.articleStats.dates|json_encode}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增文章',
|
||||
type: 'bar',
|
||||
data: {$chartData.articleStats.articles|json_encode},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.articleStats.views|json_encode},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
// 初始化所有图表
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{include file="public/tail" /}
|
||||
@@ -0,0 +1,166 @@
|
||||
{include file="public/header" /}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span class="layui-badge layui-bg-blue">登录日志</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" action="">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">IP地址</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="ip" placeholder="请输入IP地址" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="1">成功</option>
|
||||
<option value="0">失败</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">时间范围</label>
|
||||
<div class="layui-input-inline" style="width: 300px;">
|
||||
<input type="text" name="time_range" class="layui-input" id="timeRange" placeholder="请选择时间范围">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button class="layui-btn" lay-submit lay-filter="searchForm">
|
||||
<i class="layui-icon layui-icon-search"></i> 搜索
|
||||
</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table id="loginLogTable" lay-filter="loginLogTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="tableToolbar">
|
||||
<div class="layui-btn-container">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="clearAll">
|
||||
<i class="layui-icon layui-icon-delete"></i> 清空日志
|
||||
</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="tableBar">
|
||||
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
|
||||
</script>
|
||||
|
||||
<script src="/static/layui/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['table', 'form', 'laydate'], function(){
|
||||
var table = layui.table;
|
||||
var form = layui.form;
|
||||
var laydate = layui.laydate;
|
||||
|
||||
// 初始化时间范围选择器
|
||||
laydate.render({
|
||||
elem: '#timeRange',
|
||||
type: 'datetime',
|
||||
range: true
|
||||
});
|
||||
|
||||
// 初始化表格
|
||||
table.render({
|
||||
elem: '#loginLogTable',
|
||||
url: '{:url("log/login")}',
|
||||
method: 'post',
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
parseData: function(res) {
|
||||
return {
|
||||
"code": res.code === 0 ? 0 : 1,
|
||||
"msg": res.msg,
|
||||
"count": res.count,
|
||||
"data": res.data
|
||||
};
|
||||
},
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'username', title: '用户名', width: 120},
|
||||
{field: 'ip_address', title: 'IP地址', width: 130},
|
||||
{field: 'location', title: '登录地点', width: 120},
|
||||
{field: 'device_type', title: '设备类型', width: 100},
|
||||
{field: 'user_agent', title: '浏览器', width: 200},
|
||||
{field: 'login_status', title: '状态', width: 100, templet: function(d){
|
||||
return d.login_status == 1 ?
|
||||
'<span class="layui-badge layui-bg-green">成功</span>' :
|
||||
'<span class="layui-badge layui-bg-red">失败</span>';
|
||||
}},
|
||||
{field: 'failure_reason', title: '失败原因', width: 150},
|
||||
{field: 'login_time', title: '登录时间', width: 180, sort: true},
|
||||
{title: '操作', toolbar: '#tableBar', width: 80, fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 10,
|
||||
limits: [10, 20, 50, 100]
|
||||
});
|
||||
|
||||
// 监听搜索表单提交
|
||||
form.on('submit(searchForm)', function(data){
|
||||
var timeRange = data.field.time_range;
|
||||
if(timeRange){
|
||||
var times = timeRange.split(' - ');
|
||||
data.field.start_time = times[0];
|
||||
data.field.end_time = times[1];
|
||||
}
|
||||
delete data.field.time_range;
|
||||
|
||||
table.reload('loginLogTable', {
|
||||
where: data.field,
|
||||
page: {curr: 1}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// 监听工具条
|
||||
table.on('tool(loginLogTable)', function(obj){
|
||||
var data = obj.data;
|
||||
if(obj.event === 'del'){
|
||||
layer.confirm('确定删除这条日志吗?', function(index){
|
||||
$.post('{:url("log/deleteLogin")}', {id: data.id}, function(res){
|
||||
if(res.code === 0){
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
obj.del();
|
||||
}else{
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听头工具栏事件
|
||||
table.on('toolbar(loginLogTable)', function(obj){
|
||||
if(obj.event === 'clearAll'){
|
||||
layer.confirm('确定要清空所有登录日志吗?', function(index){
|
||||
$.post('{:url("log/clearLogin")}', function(res){
|
||||
if(res.code === 0){
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
table.reload('loginLogTable');
|
||||
}else{
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,175 @@
|
||||
{include file="public/header" /}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">
|
||||
<span class="layui-badge layui-bg-blue">操作日志</span>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" action="">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">模块</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="module" placeholder="请输入模块" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">操作</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="operation" placeholder="请输入操作" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="1">成功</option>
|
||||
<option value="0">失败</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">时间范围</label>
|
||||
<div class="layui-input-inline" style="width: 300px;">
|
||||
<input type="text" name="time_range" class="layui-input" id="timeRange" placeholder="请选择时间范围">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button class="layui-btn" lay-submit lay-filter="searchForm">
|
||||
<i class="layui-icon layui-icon-search"></i> 搜索
|
||||
</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table id="operationLogTable" lay-filter="operationLogTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="tableToolbar">
|
||||
<div class="layui-btn-container">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-event="clearAll">
|
||||
<i class="layui-icon layui-icon-delete"></i> 清空日志
|
||||
</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="tableBar">
|
||||
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
|
||||
</script>
|
||||
|
||||
<script src="/static/layui/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['table', 'form', 'laydate'], function(){
|
||||
var table = layui.table;
|
||||
var form = layui.form;
|
||||
var laydate = layui.laydate;
|
||||
|
||||
// 初始化时间范围选择器
|
||||
laydate.render({
|
||||
elem: '#timeRange',
|
||||
type: 'datetime',
|
||||
range: true
|
||||
});
|
||||
|
||||
// 初始化表格
|
||||
table.render({
|
||||
elem: '#operationLogTable',
|
||||
url: '{:url("log/operation")}',
|
||||
method: 'post',
|
||||
toolbar: '#tableToolbar',
|
||||
defaultToolbar: ['filter', 'exports', 'print'],
|
||||
parseData: function(res) {
|
||||
return {
|
||||
"code": res.code === 0 ? 0 : 1,
|
||||
"msg": res.msg,
|
||||
"count": res.count,
|
||||
"data": res.data
|
||||
};
|
||||
},
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'username', title: '用户名', width: 120},
|
||||
{field: 'module', title: '模块', width: 120},
|
||||
{field: 'operation', title: '操作', width: 120},
|
||||
{field: 'request_method', title: '请求方法', width: 100},
|
||||
{field: 'request_url', title: '请求URL', width: 200},
|
||||
{field: 'request_params', title: '请求参数', width: 200},
|
||||
{field: 'ip_address', title: 'IP地址', width: 130},
|
||||
{field: 'status', title: '状态', width: 100, templet: function(d){
|
||||
return d.status == 1 ?
|
||||
'<span class="layui-badge layui-bg-green">成功</span>' :
|
||||
'<span class="layui-badge layui-bg-red">失败</span>';
|
||||
}},
|
||||
{field: 'error_message', title: '错误信息', width: 150},
|
||||
{field: 'operation_time', title: '操作时间', width: 180, sort: true},
|
||||
{field: 'execution_time', title: '执行时间(ms)', width: 120, sort: true},
|
||||
{title: '操作', toolbar: '#tableBar', width: 80, fixed: 'right'}
|
||||
]],
|
||||
page: true,
|
||||
limit: 10,
|
||||
limits: [10, 20, 50, 100]
|
||||
});
|
||||
|
||||
// 监听搜索表单提交
|
||||
form.on('submit(searchForm)', function(data){
|
||||
var timeRange = data.field.time_range;
|
||||
if(timeRange){
|
||||
var times = timeRange.split(' - ');
|
||||
data.field.start_time = times[0];
|
||||
data.field.end_time = times[1];
|
||||
}
|
||||
delete data.field.time_range;
|
||||
|
||||
table.reload('operationLogTable', {
|
||||
where: data.field,
|
||||
page: {curr: 1}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// 监听工具条
|
||||
table.on('tool(operationLogTable)', function(obj){
|
||||
var data = obj.data;
|
||||
if(obj.event === 'del'){
|
||||
layer.confirm('确定删除这条日志吗?', function(index){
|
||||
$.post('{:url("log/deleteOperation")}', {id: data.id}, function(res){
|
||||
if(res.code === 0){
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
obj.del();
|
||||
}else{
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听头工具栏事件
|
||||
table.on('toolbar(operationLogTable)', function(obj){
|
||||
if(obj.event === 'clearAll'){
|
||||
layer.confirm('确定要清空所有操作日志吗?', function(index){
|
||||
$.post('{:url("log/clearOperation")}', function(res){
|
||||
if(res.code === 0){
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
table.reload('operationLogTable');
|
||||
}else{
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="layui-col-xs5">
|
||||
<div style="margin-left:10px;">
|
||||
<img src="{:captcha_src()}" class="layadmin-user-login-codeimg" id="img"
|
||||
<img src="{:captcha_src()}?t={:time()}" class="layadmin-user-login-codeimg" id="img"
|
||||
onclick="reloadImg()">
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +75,8 @@
|
||||
});
|
||||
// 重新生成验证码
|
||||
function reloadImg() {
|
||||
$('#img').attr('src', '{:captcha_src()}?rand=' + Math.random());
|
||||
var timestamp = new Date().getTime();
|
||||
$('#img').attr('src', '{:captcha_src()}?t=' + timestamp);
|
||||
}
|
||||
|
||||
// 登录处理函数
|
||||
|
||||
Reference in New Issue
Block a user