first commit
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{$config['web_title']}</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/third/layui/css/layui.css" media="all">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/index.css" media="all">
|
||||
<script type="text/javascript" src="/static/third/layui/layui.js"></script>
|
||||
<script type="text/javascript" src="/static/js/admin.js"></script>
|
||||
<style>
|
||||
.layadmin-side-shrink .layui-layout-admin .layui-logo {
|
||||
width: 60px;
|
||||
background-image: url("/static/images/logob32.jpg");
|
||||
}
|
||||
.main-content {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
#mainWorkspace {
|
||||
height: 100%;
|
||||
}
|
||||
#mainTabs.layui-tab {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.layui-tab-content {
|
||||
padding: 0;
|
||||
height: calc(100% - 41px);
|
||||
}
|
||||
.layui-tab-item {
|
||||
height: 100%;
|
||||
}
|
||||
.main-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
/* background-color: #f2f2f2; */
|
||||
}
|
||||
.layui-tab-title {
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
.layui-tab-title .layui-this:after {
|
||||
border-bottom-color: #009688;
|
||||
}
|
||||
#LAY_app_body {
|
||||
overflow: hidden;
|
||||
}
|
||||
.layui-tab-content .layui-tab-item {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body layadmin-themealias="default" class="layui-layout-body">
|
||||
<div id="LAY_app" class="layadmin-tabspage-none">
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<div class="layui-header">
|
||||
<!-- 头部区域 -->
|
||||
<div style="display: flex;align-items: center;height:70px;">
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="layui-nav-item layadmin-flexible" lay-unselect onclick="shrink()">
|
||||
<a href="javascript:;" layadmin-event="flexible" title="侧边伸缩">
|
||||
<i class="layui-icon layui-icon-shrink-right" id="LAY_app_flexible"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="layui-nav layui-layout-right" lay-filter="layadmin-layout-right">
|
||||
<li class="layui-nav-item layui-hide-xs" lay-unselect title="前端站点" onclick="gotoFront()">
|
||||
<a href="javascript:;" layadmin-event="gotoFront">
|
||||
<i class="layui-icon layui-icon-website"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="layui-nav-item layui-hide-xs" lay-unselect title="全屏" onclick="fullScreen()">
|
||||
<a href="javascript:;" layadmin-event="fullscreen">
|
||||
<i class="layui-icon layui-icon-screen-full"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;">
|
||||
<cite>{$aUser['name']}</cite>
|
||||
</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a lay-href="" onclick="menuFire('yunzeradmin/admininfo',1)">个人中心</a></dd>
|
||||
<hr>
|
||||
<dd layadmin-event="logout" style="text-align:center;" onclick="logout()">
|
||||
<a>退出</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 侧边菜单 -->
|
||||
<div class="layui-side layui-side-menu">
|
||||
<div class="layui-side-scroll">
|
||||
<!-- <div class="layui-logo" lay-href="" style="display: flex;align-items: center;">
|
||||
<img src="/static/images/logo-l-w.png" alt="{$config['web_title']}"
|
||||
style="max-width: 100%; max-height: 100%;">
|
||||
</div> -->
|
||||
<ul class="layui-nav layui-nav-tree" lay-shrink="all" id="LAY-system-side-menu"
|
||||
lay-filter="layadmin-system-side-menu">
|
||||
<!-- 固定的工作台菜单项 -->
|
||||
<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: -20px;"></i>
|
||||
<cite>工作台</cite>
|
||||
</a>
|
||||
</li>
|
||||
<!-- 下面是原有的菜单循环 -->
|
||||
{volist name="menu" id="vo"}
|
||||
<li data-name="{$vo.src}" data-jump="" class="layui-nav-item">
|
||||
<!-- 修改一级菜单的点击事件,只有当有src时才跳转 -->
|
||||
<a href="javascript:;" lay-tips="{$vo['label']}" lay-direction="2" {if isset($vo['src']) &&
|
||||
$vo['src']}onclick="menuFire('{$vo.src}',1)" {/if}>
|
||||
<i class="layui-icon layui-icons {$vo['icon_class']}"></i>
|
||||
<cite>{$vo['label']}</cite>
|
||||
</a>
|
||||
{if (isset($vo['children']) && $vo['children'])}
|
||||
<dl class="layui-nav-child">
|
||||
{volist name="vo.children" id="cvo"}
|
||||
<dd data-name="" data-jump="/">
|
||||
{if $cvo['type'] == 1}
|
||||
<a href="javascript:;" onclick="menuFire('{$cvo.src}',1)">
|
||||
<i class="layui-icon layui-icons {$cvo.icon_class}"></i>{$cvo.label}
|
||||
</a>
|
||||
{elseif $cvo['type'] == 2 /}
|
||||
<a href="{$cvo.src}" target="_blank">
|
||||
<i class="layui-icon layui-icons {$cvo.icon_class}"></i>{$cvo.label}
|
||||
</a>
|
||||
{/if}
|
||||
</dd>
|
||||
{/volist}
|
||||
</dl>
|
||||
{/if}
|
||||
</li>
|
||||
{/volist}
|
||||
</ul>
|
||||
<div style="position: absolute;bottom:20px;width:200px;display:flex;justify-content: center;">
|
||||
<a style="color:#848484" href="https://www.yunzer.cn/">POWER BY 云泽网</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主体内容 -->
|
||||
<div class="layui-body" id="LAY_app_body">
|
||||
<div class="main-content">
|
||||
<!-- 默认工作台界面 -->
|
||||
<div id="mainWorkspace">
|
||||
<iframe src="{$config['admin_route']}index/welcome" class="main-iframe" frameborder="0" scrolling="0"></iframe>
|
||||
</div>
|
||||
<!-- 动态标签页 -->
|
||||
<div id="mainTabs" class="layui-tab" lay-allowClose="true" lay-filter="mainTabs" style="margin-top: 10px;">
|
||||
<ul class="layui-tab-title"></ul>
|
||||
<div class="layui-tab-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 辅助元素,一般用于移动设备下遮罩 -->
|
||||
<div class="layadmin-body-shade" layadmin-event="shade" onclick="shrink()"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 辅助元素,一般用于移动设备下遮罩 -->
|
||||
<div class="layadmin-body-shade" layadmin-event="shade"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
layui.use(['element', 'layer', 'jquery'], function () {
|
||||
var element = layui.element;
|
||||
$ = layui.jquery;
|
||||
layer = layui.layer;
|
||||
setter = layui.setter;
|
||||
|
||||
// 保存标签状态
|
||||
function saveTabState(layid, title, url) {
|
||||
var tabState = {
|
||||
layid: layid,
|
||||
title: title,
|
||||
url: url
|
||||
};
|
||||
sessionStorage.setItem('currentTab', JSON.stringify(tabState));
|
||||
}
|
||||
|
||||
// 恢复标签状态
|
||||
function restoreTabState() {
|
||||
var tabState = sessionStorage.getItem('currentTab');
|
||||
if (tabState) {
|
||||
try {
|
||||
var tab = JSON.parse(tabState);
|
||||
if (tab.url && tab.title) {
|
||||
addTab(tab.title, tab.url, tab.layid);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to restore tab state:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在页面加载完成后执行
|
||||
$(document).ready(function () {
|
||||
// 获取URL参数
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var page = urlParams.get('page');
|
||||
|
||||
if (page) {
|
||||
// 如果有page参数,加载对应页面到iframe
|
||||
$('iframe').attr('src', "{$config['admin_route']}" + page);
|
||||
} else {
|
||||
// 否则加载默认页面
|
||||
$('iframe').attr('src', "{$config['admin_route']}index/welcome");
|
||||
}
|
||||
|
||||
// 恢复标签状态
|
||||
restoreTabState();
|
||||
});
|
||||
|
||||
// 添加或切换到标签页
|
||||
window.addTab = function(title, url, id) {
|
||||
var element = layui.element;
|
||||
var $ = layui.jquery;
|
||||
var layid = id || url.replace(/\//g, '_');
|
||||
|
||||
// 如果是首页/工作台,直接显示主工作区
|
||||
if(url.indexOf('index/welcome') > -1 || url.indexOf('welcome') > -1) {
|
||||
$('#mainTabs').hide();
|
||||
$('#mainWorkspace').show();
|
||||
$('#mainWorkspace iframe').attr('src', url);
|
||||
sessionStorage.removeItem('currentTab');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示标签区域
|
||||
$('#mainWorkspace').hide();
|
||||
$('#mainTabs').show();
|
||||
|
||||
// 如果标签已存在,直接切换
|
||||
if($('.layui-tab-title li[lay-id="'+ layid +'"]').length > 0) {
|
||||
element.tabChange('mainTabs', layid);
|
||||
saveTabState(layid, title, url);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加新标签
|
||||
element.tabAdd('mainTabs', {
|
||||
title: title,
|
||||
content: '<iframe src="' + url + '" class="main-iframe" frameborder="0"></iframe>',
|
||||
id: layid
|
||||
});
|
||||
|
||||
// 切换到新标签页
|
||||
element.tabChange('mainTabs', layid);
|
||||
|
||||
// 保存当前标签状态
|
||||
saveTabState(layid, title, url);
|
||||
};
|
||||
|
||||
// 监听标签切换事件
|
||||
element.on('tab(mainTabs)', function(data){
|
||||
var layid = $(this).attr('lay-id');
|
||||
// 确保当前标签页内容可见
|
||||
$('.layui-tab-content .layui-tab-item').eq(data.index).addClass('layui-show')
|
||||
.siblings().removeClass('layui-show');
|
||||
|
||||
// 更新保存的标签状态
|
||||
var title = $(this).text();
|
||||
var url = $('.layui-tab-content .layui-tab-item').eq(data.index).find('iframe').attr('src');
|
||||
saveTabState(layid, title, url);
|
||||
});
|
||||
|
||||
// 监听标签删除事件
|
||||
element.on('tabDelete(mainTabs)', function(data){
|
||||
// 如果没有标签了,显示工作台
|
||||
if($('.layui-tab-title li').length === 0) {
|
||||
$('#mainTabs').hide();
|
||||
$('#mainWorkspace').show();
|
||||
sessionStorage.removeItem('currentTab');
|
||||
}
|
||||
});
|
||||
|
||||
// 左侧菜单点击事件
|
||||
$('.left-nav #nav li').click(function (event) {
|
||||
if ($(this).children('.sub-menu').length) {
|
||||
if ($(this).hasClass('open')) {
|
||||
$(this).removeClass('open');
|
||||
$(this).find('.nav_right').html('');
|
||||
$(this).children('.sub-menu').stop().slideUp();
|
||||
$(this).siblings().children('.sub-menu').slideUp();
|
||||
} else {
|
||||
$(this).addClass('open');
|
||||
$(this).children('a').find('.nav_right').html('');
|
||||
$(this).children('.sub-menu').stop().slideDown();
|
||||
$(this).siblings().children('.sub-menu').stop().slideUp();
|
||||
$(this).siblings().find('.nav_right').html('');
|
||||
$(this).siblings().removeClass('open');
|
||||
}
|
||||
} else {
|
||||
var url = $(this).children('a').attr('_href');
|
||||
var title = $(this).children('a').html();
|
||||
title = title.replace(/<[^>]+>/g, "").trim(); // 移除HTML标签
|
||||
|
||||
if (url) {
|
||||
window.addTab(title, url);
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
})
|
||||
|
||||
// 修改resetMainHeight函数
|
||||
function resetMainHeight(iframe) {
|
||||
if (!iframe) return;
|
||||
try {
|
||||
// 获取视口高度
|
||||
var clientHeight = document.documentElement.clientHeight;
|
||||
// 计算iframe应该的高度(减去头部和tab标签的高度)
|
||||
var iframeHeight = clientHeight - 60 - 40; // 60px是头部高度,40px是tab标签高度
|
||||
$(iframe).css({
|
||||
'height': iframeHeight + 'px',
|
||||
'width': '100%',
|
||||
'display': 'block'
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('Reset iframe height failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 在窗口大小改变时重置所有iframe高度
|
||||
$(window).on('resize', function() {
|
||||
$('.main-iframe').each(function() {
|
||||
resetMainHeight(this);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 菜单点击
|
||||
function menuFire(obj, num) {
|
||||
if (num == 1) {
|
||||
var title = '';
|
||||
// 获取菜单标题
|
||||
$('.layui-nav-item a').each(function() {
|
||||
if($(this).attr('onclick') && $(this).attr('onclick').indexOf(obj) > -1) {
|
||||
title = $(this).text().trim();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没找到标题,使用子菜单查找
|
||||
if(!title) {
|
||||
$('.layui-nav-child a').each(function() {
|
||||
if($(this).attr('onclick') && $(this).attr('onclick').indexOf(obj) > -1) {
|
||||
title = $(this).text().trim();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加或切换到标签页
|
||||
window.addTab(title || '新页面', "{$config['admin_route']}" + obj, obj.replace(/\//g, '_'));
|
||||
|
||||
// 更新浏览器URL,但保持在index页面
|
||||
window.history.pushState({}, '', "{$config['admin_route']}index/index?page=" + obj);
|
||||
|
||||
// 如果是子菜单,确保父菜单展开
|
||||
if (obj.indexOf('/') > -1) {
|
||||
var parentMenu = obj.split('/')[0];
|
||||
$('.layui-nav-item').each(function () {
|
||||
var menuSrc = $(this).find('a').attr('onclick');
|
||||
if (menuSrc && menuSrc.indexOf(parentMenu) > -1) {
|
||||
$(this).addClass('layui-nav-itemed');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
var width = screen();
|
||||
if (width < 2) {
|
||||
shrink();
|
||||
}
|
||||
}
|
||||
|
||||
// 监听浏览器前进后退按钮
|
||||
window.addEventListener('popstate', function (event) {
|
||||
// 获取URL参数
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var page = urlParams.get('page');
|
||||
|
||||
if (page) {
|
||||
// 如果有page参数,通过标签系统加载页面
|
||||
var title = '新页面';
|
||||
window.addTab(title, "{$config['admin_route']}" + page, page.replace(/\//g, '_'));
|
||||
} else {
|
||||
// 否则加载默认页面
|
||||
element.tabChange('mainTabs', 'welcome');
|
||||
}
|
||||
});
|
||||
|
||||
// 退出
|
||||
function logout() {
|
||||
layer.confirm('确定要退出吗?', {
|
||||
icon: 3,
|
||||
btn: ['确定', '取消']
|
||||
}, function () {
|
||||
$.get("{$config['admin_route']}login/logout", function (res) {
|
||||
if (res.code > 0) {
|
||||
layer.msg(res.msg, { 'icon': 2 });
|
||||
} else {
|
||||
layer.msg(res.msg, { 'icon': 1 });
|
||||
setTimeout(function () { window.location.href = "{$config['admin_route']}login/index"; }, 1000);
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
}
|
||||
|
||||
//跳转前端站点
|
||||
function gotoFront() {
|
||||
window.open("//{$config['admin_domain']}", "_blank");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,725 @@
|
||||
{include file="public/header" /}
|
||||
<div class="dashboard-container">
|
||||
<div class="welcome-header">
|
||||
<h1>欢迎使用{$config['web_title']}</h1>
|
||||
<p>今天是 <span id="current-time"></span>,祝您工作愉快</p>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">用户总数</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">{$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">{$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">{$todayStats.total_resources|number_format}</div>
|
||||
<div class="stat-icon">📦</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<h2>快捷操作</h2>
|
||||
<div class="action-buttons">
|
||||
<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|default='📌'}</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">{$activity.content}</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();
|
||||
var year = now.getFullYear();
|
||||
var month = now.getMonth() + 1;
|
||||
var date = now.getDate();
|
||||
var hours = now.getHours();
|
||||
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) + '日 ' +
|
||||
padZero(hours) + ':' +
|
||||
padZero(minutes) + ':' +
|
||||
padZero(seconds);
|
||||
|
||||
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")}')
|
||||
.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();
|
||||
|
||||
// 更新文章统计图表
|
||||
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';
|
||||
});
|
||||
}
|
||||
|
||||
// // 获取资源统计数据
|
||||
// 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();
|
||||
|
||||
// // 更新资源统计图表
|
||||
// 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';
|
||||
// });
|
||||
// }
|
||||
|
||||
updateTime();
|
||||
setInterval(updateTime, 1000);
|
||||
|
||||
// 页面加载完成后获取统计数据
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
getUserCounts();
|
||||
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|raw},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e2e8f0'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e2e8f0'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f1f5f9'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '访问量',
|
||||
data: {$chartData.visitTrend.visits|json_encode|raw},
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.1
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}, {
|
||||
name: '独立访客',
|
||||
data: {$chartData.visitTrend.uvs|json_encode|raw},
|
||||
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|raw}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'bar',
|
||||
data: {$chartData.userGrowth.newUsers|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '总用户数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.userGrowth.totalUsers|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
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|raw}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增资源',
|
||||
type: 'bar',
|
||||
data: {$chartData.resourceStats.newResources|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '总资源数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.resourceStats.totalResources|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '下载量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.resourceStats.downloads|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#f59e0b'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
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|raw}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增文章',
|
||||
type: 'bar',
|
||||
data: {$chartData.articleStats.newArticles|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#3881fd'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '总文章数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.articleStats.totalArticles|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#10b981'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '浏览量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: {$chartData.articleStats.views|json_encode|raw},
|
||||
itemStyle: {
|
||||
color: '#f59e0b'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
chart.setOption(option);
|
||||
}
|
||||
|
||||
// 初始化所有图表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 确保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><script src="__STATIC__/js/jquery.min.js"></script>
|
||||
<style>
|
||||
.dashboard-container {
|
||||
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 {
|
||||
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 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.welcome-header p {
|
||||
font-size: 15px;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.stat-card {
|
||||
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 8px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.stat-card .stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin: 12px 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.stat-card .stat-title {
|
||||
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: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.quick-actions h2 {
|
||||
color: #1e293b;
|
||||
font-size: 18px;
|
||||
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: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.action-button {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
.action-button:hover {
|
||||
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: #9b9b9b;
|
||||
}
|
||||
.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>
|
||||
|
||||
{include file="public/tail" /}
|
||||
Reference in New Issue
Block a user