当前位置: 首页 > news >正文

Fluid主题美化-访问量曲线图

我的Fluid博客是基于LeanCloud来统计访问量和访客的,但是LeanCloud的Counter记录的是pv和uv的总量,无法记录每一天的历史值,不方便展示曲线图。这个配置起来比较麻烦,本篇博客记录一下分享给大家。

image

具体展示效果参考统计数据。

我们需要记录每一天的值,而LeanCloud的Counter记录的是历史总值,所以需要开一个定时任务,在每天的23:59分上报当前的pv和uv。

创建定时任务

  1. 登录LeanCloud,打开云引擎->管理部署页面
  2. 创建一个函数(如果没有分组,需要先创建分组)
  3. 点击部署tab下的在线编辑页面,把以下代码粘贴进去并部署到生产环境
console.log("--- 统计存档任务开始 ---");// 1. 查询 Counter 表
const query = new AV.Query('Counter');
query.containedIn('target', ['site-pv', 'site-uv']);try {const results = await query.find();let pv = 0, uv = 0;// 2. 解析 Counter 表中的数据results.forEach(item => {const target = item.get('target');const time = item.get('time') || 0; // 对应截图中的 time 字段if (target === 'site-pv') pv = time;if (target === 'site-uv') uv = time;});// 3. 获取北京时间日期 (YYYY-MM-DD)const today = new Date().toLocaleDateString('zh-CN', {timeZone: 'Asia/Shanghai',year: 'numeric', month: '2-digit', day: '2-digit'}).replace(/\//g, '-');console.log(`读取到数据 -> 日期: ${today}, PV: ${pv}, UV: ${uv}`);// 4. 写入或更新 DailyStat 表const DailyStat = AV.Object.extend('DailyStat');const queryExist = new AV.Query('DailyStat');queryExist.equalTo('date', today);const existing = await queryExist.first();const stat = existing || new DailyStat();stat.set('pv', pv);stat.set('uv', uv);stat.set('date', today); // 对应截图中的 date, pv, uv 字段// 5. 设置权限,确保前端可见const acl = new AV.ACL();acl.setPublicReadAccess(true);acl.setPublicWriteAccess(true);stat.setACL(acl);const saved = await stat.save();console.log(`存档成功! ObjectId: ${saved.id}`);return { status: "Success", date: today, pv, uv };
} catch (error) {console.error("存档失败:", error);throw new AV.Cloud.Error("存档过程出错: " + error.message);
}
  1. 打开定时任务tab,创建定时任务,函数选择刚刚创建的函数名,定时表达式为"0 59 23 * * *",启动定时任务

数据拉取

把以下文件打包成js文件,并放在展示页的同名文件夹下,

window.initBlogStats = async function(config) {// 1. 初始化 LeanCloudif (typeof AV !== 'undefined' && !AV.applicationId) {AV.init({appId: config.appId,appKey: config.appKey,serverURL: config.serverURL});}try {// 2. 同时查询流量趋势和文章数据const lineQuery = new AV.Query('DailyStat').ascending('date').limit(7);const barQuery = new AV.Query('Counter').descending('time').limit(20);const [lineResults, barResults] = await Promise.all([lineQuery.find().catch(() => []), barQuery.find()]);// --- A. 处理折线图数据 ---const lineDates = lineResults.map(i => (i.get('date') || "").substring(5));const linePVs = lineResults.map(i => i.get('pv') || 0);const lineUVs = lineResults.map(i => i.get('uv') || 0);// --- B. 处理柱状图数据 ---const barTitles = [], barViews = [];barResults.forEach(item => {let target = item.get('target') || "";if (target.includes('/20')) { // 仅匹配文章路径let title = target.split('/').filter(Boolean).pop();barTitles.push(title);barViews.push(item.get('time') || 0);}});// --- C. 渲染 ---renderLine(config.lineId, lineDates, linePVs, lineUVs);renderBar(config.barId, barTitles.slice(0, 10).reverse(), barViews.slice(0, 10).reverse());} catch (error) {console.error("数据加载失败:", error);}
};function renderLine(id, dates, pvs, uvs) {const dom = document.getElementById(id); if (!dom) return;const chart = echarts.init(dom);chart.setOption({title: { text: '近 7 日访问趋势', left: 'center' },tooltip: { trigger: 'axis' },legend: { bottom: 0, data: ['访问量', '访客数'] },xAxis: { type: 'category', boundaryGap: false, data: dates },yAxis: { type: 'value' },series: [{ name: '访问量', type: 'line', smooth: true, data: pvs, itemStyle: { color: '#1890ff' } },{ name: '访客数', type: 'line', smooth: true, data: uvs, itemStyle: { color: '#2fc25b' } }]});
}function renderBar(id, titles, views) {const dom = document.getElementById(id); if (!dom) return;const chart = echarts.init(dom);chart.setOption({title: { text: '文章阅读量排行', left: 'center' },tooltip: { trigger: 'axis' },grid: { left: '3%', right: '12%', bottom: '5%', containLabel: true },xAxis: { type: 'value' },yAxis: { type: 'category', data: titles },series: [{name: '次数', type: 'bar', data: views, itemStyle: { color: '#1890ff', borderRadius: [0, 4, 4, 0] },label: { show: true, position: 'right' }}]});
}

以下html代码插入想要展示的页面md文件

<script src="https://cdn.jsdelivr.net/npm/leancloud-storage@4.15.0/dist/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script src="/data/index/show.js"></script><div id="stats-line-chart" style="width: 100%; height: 400px; margin-bottom: 30px; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"></div><div id="stats-bar-chart" style="width: 100%; height: 500px; margin-bottom: 30px; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"></div><script>function startStats() {// 检查依赖是否加载if (typeof AV !== 'undefined' && window.initBlogStats) {window.initBlogStats({appId: "2HT5rmC5XQuRugoXS6jx93oG-gzGzoHsz",appKey: "34FVPpMZGQcLXWdGF6FnOx9T",serverURL: "https://2ht5rmc5.lc-cn-n1-shared.com",lineId: "stats-line-chart",barId: "stats-bar-chart"});} else {setTimeout(startStats, 500); }}document.addEventListener('DOMContentLoaded', startStats);
</script>
http://www.jsqmd.com/news/172100/

相关文章:

  • YOLOv8备份策略:重要模型文件异地保存
  • gnet未来展望:从网络框架到云原生基础设施
  • YOLOv8能否做实例分割?segment模式使用说明
  • 涂膜划痕试验仪|涂层质量的精准试金石 - 品牌推荐大师1
  • YOLOv8能否识别文本?OCR扩展应用场景探讨
  • 2026十大图库藏宝图:高清免费正版素材网站推荐,找图不踩坑 - 品牌2026
  • YOLOv8轻量级模型yolov8n优势解析:适合边缘设备部署
  • 模拟运输振动台哪个品牌好?十大行业标杆厂家盘点,定制+高售后+高性价比必看! - 品牌推荐大师1
  • 如何快速搭建免费的 QQ 音乐数据采集系统
  • 关于 RTP/AVPF 的简单讨论
  • 分享几个好用的在线破解md5的网站_md5在线解密
  • YOLOv8镜像是否支持Windows系统?跨平台使用答疑
  • 如何用数字化技术重构汽车产业链?
  • YOLOv8 + Linux系统优化:最大化GPU利用率技巧汇总
  • 超越FTP:实现企业级安全管控与无缝集成的文件传输平台如何选
  • 【PHP 8.7性能飞跃揭秘】:实测新特性带来的3倍执行效率提升
  • C#利用OCR实现车牌识别(包含模型及源码)
  • 视频直播点播平台EasyDSS如何为大型活动直播提供技术基石
  • 打气泵PCBA方案——智能充气设备开发
  • C#跨平台项目调试难?资深工程师亲授6招快速诊断法
  • YOLOv8社区活跃度分析:GitHub Star趋势观察
  • YOLOv8标注格式要求:必须是COCO还是支持VOC?
  • YOLOv8计费系统对接:token消耗统计与扣减逻辑
  • 如何快速掌握 Python 语法?
  • YOLOv8机器人导航避障:实时感知系统集成方案
  • 算法竞赛备考冲刺必刷题(C++) | 洛谷 P3379 【模板】最近公共祖先(LCA)
  • 2025年终麻将机品牌推荐:五大主流品牌性能与服务实力深度评测 - 速递信息
  • 数电学习笔记
  • RTMP推流平台EasyDSS无人机推流直播技术在交通视频监测场景的智能应用
  • PHP与MQTT协议融合实践,构建低功耗智能控制系统的终极指南