一、功能说明
本功能用于为媒塔CMS生成的静态新闻内容页添加访问量统计,支持以下特性:
支持主站域名与父栏目独立绑定域名(站群) 同时统计
自动统计 PV(访问量)、UV(独立访客)、IP(独立IP)
支持按 今日 / 本周 / 本月 / 总计 维度查询
前端防重复提交(30秒内同一文章不重复计数)
跨域请求已由后端开启 CORS,站群无需额外配置
二、部署步骤
第一步:创建统计脚本文件
将以下代码保存为 stats.js,放置于主站及所有站群目录的 /skin/ 目录下(例如:/skin/stats.js)。
// ==================== 媒塔CMS新闻访问统计(静态页面版 - 防重复) ====================
(function() {
'use strict';
// ============================================================
// 【配置区】主站域名 + 统计接口路径
// 只需修改此处的 MAIN_HOST 为您的主站域名人,所有站群自动生效
// ============================================================
var MAIN_HOST = 'http://www.example.com'; // 请修改为您的主站域名
var STATS_PATH = '/api/ajax_news_stats.php'; // 统计接口路径(默认 /api/)
// 自动拼接完整接口地址
var STATS_API_BASE = MAIN_HOST.replace(/\/+$/, '') + STATS_PATH;
window.STATS_API_BASE = STATS_API_BASE;
console.log('\u{1F4CA} 新闻统计: 接口地址 = ' + STATS_API_BASE);
// 全局标志,防止重复统计
if (window._newsStatsRecorded === true) {
console.log('\u{1F4CA} 新闻统计: 已记录过,跳过');
return;
}
window._newsStatsRecorded = false;
/**
* 从当前URL中获取文章ID
*/
function getNewsIdFromUrl() {
var url = window.location.href;
var newsId = 0;
var match = url.match(/[?&]id=([^&]+)/);
if (match && match[1]) {
newsId = parseInt(match[1]);
if (newsId > 0) return newsId;
}
match = url.match(/\/show\/(\d+)\.html/);
if (match && match[1]) {
newsId = parseInt(match[1]);
if (newsId > 0) return newsId;
}
match = url.match(/\/(\d+)\.html/);
if (match && match[1]) {
newsId = parseInt(match[1]);
if (newsId > 0) return newsId;
}
match = url.match(/\/(\d+)\/$/);
if (match && match[1]) {
newsId = parseInt(match[1]);
if (newsId > 0) return newsId;
}
return newsId;
}
/**
* 记录新闻访问统计
*/
function recordNewsVisit(newsId) {
if (!newsId || newsId <= 0) {
console.log('\u{1F4CA} 新闻统计: 无效的文章ID,跳过记录');
return;
}
var cacheKey = 'news_stats_' + newsId;
var lastRecord = sessionStorage.getItem(cacheKey);
var now = Date.now();
if (lastRecord && (now - parseInt(lastRecord)) < 30000) {
console.log('\u{1F4CA} 新闻统计: 30秒内已记录过,跳过 (ID=' + newsId + ')');
return;
}
if (window._newsStatsRecorded === true) {
console.log('\u{1F4CA} 新闻统计: 已记录过,跳过 (ID=' + newsId + ')');
return;
}
window._newsStatsRecorded = true;
console.log('\u{1F4CA} 新闻统计: 开始记录 (ID=' + newsId + ')');
var xhr = new XMLHttpRequest();
var url = STATS_API_BASE + '?news_id=' + newsId + '&t=' + now;
xhr.open('GET', url, true);
xhr.timeout = 5000;
xhr.onload = function() {
if (xhr.status === 200) {
try {
var response = JSON.parse(xhr.responseText);
if (response.success) {
console.log('\u{1F4CA} 新闻统计: 文章ID=' + newsId + ' 记录成功');
sessionStorage.setItem(cacheKey, String(now));
loadNewsStats(newsId);
} else {
console.log('\u{1F4CA} 新闻统计: 记录失败 - ' + response.message);
window._newsStatsRecorded = false;
}
} catch(e) {
console.log('\u{1F4CA} 新闻统计: 响应解析失败');
window._newsStatsRecorded = false;
}
} else {
console.log('\u{1F4CA} 新闻统计: 请求失败 - HTTP ' + xhr.status);
window._newsStatsRecorded = false;
}
};
xhr.onerror = function() {
console.log('\u{1F4CA} 新闻统计: 网络错误');
window._newsStatsRecorded = false;
};
xhr.ontimeout = function() {
console.log('\u{1F4CA} 新闻统计: 请求超时');
window._newsStatsRecorded = false;
};
xhr.send();
}
/**
* 获取并显示文章统计数据
*/
function loadNewsStats(newsId) {
if (!newsId || newsId <= 0) {
return;
}
console.log('\u{1F4CA} 新闻统计: 获取统计数据 (ID=' + newsId + ')');
var xhr = new XMLHttpRequest();
var url = STATS_API_BASE + '?action=get_stats&news_id=' + newsId + '&t=' + Date.now();
xhr.open('GET', url, true);
xhr.timeout = 5000;
xhr.onload = function() {
if (xhr.status === 200) {
try {
var response = JSON.parse(xhr.responseText);
if (response.success && response.data) {
var stats = response.data;
var statsHtml = (stats.total_pv !== undefined) ? stats.total_pv : '0';
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = statsHtml;
statsElement.title = '今日: ' + (stats.today_pv || 0) +
' | 本周: ' + (stats.week_pv || 0) +
' | 本月: ' + (stats.month_pv || 0) +
' | UV: ' + (stats.total_uv || 0) +
' | IP: ' + (stats.total_ip || 0);
}
console.log('\u{1F4CA} 新闻统计: 统计数据显示成功 - PV=' + statsHtml);
} else {
console.log('\u{1F4CA} 新闻统计: 获取统计数据失败');
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
}
} catch(e) {
console.log('\u{1F4CA} 新闻统计: 统计响应解析失败');
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
}
} else {
console.log('\u{1F4CA} 新闻统计: 获取统计请求失败 - HTTP ' + xhr.status);
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
}
};
xhr.onerror = function() {
console.log('\u{1F4CA} 新闻统计: 获取统计网络错误');
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
};
xhr.ontimeout = function() {
console.log('\u{1F4CA} 新闻统计: 获取统计请求超时');
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
};
xhr.send();
}
/**
* 执行统计的主函数
*/
function executeStats() {
if (window._newsStatsRecorded === true) {
console.log('\u{1F4CA} 新闻统计: 已记录,跳过执行');
return;
}
var newsId = getNewsIdFromUrl();
console.log('\u{1F4CA} 新闻统计: 检测到文章ID=' + newsId);
if (newsId > 0) {
loadNewsStats(newsId);
recordNewsVisit(newsId);
} else {
console.log('\u{1F4CA} 新闻统计: 未能从URL中提取文章ID');
var statsElement = document.getElementById('newsStats');
if (statsElement) {
statsElement.textContent = '0';
}
}
}
// ========== 事件绑定 ==========
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
console.log('\u{1F4CA} 新闻统计: DOMContentLoaded 触发');
executeStats();
});
} else {
console.log('\u{1F4CA} 新闻统计: DOM 已加载,直接执行');
executeStats();
}
if (typeof window._loadEventBound === 'undefined') {
window._loadEventBound = true;
window.addEventListener('load', function() {
if (window._newsStatsRecorded === true) {
console.log('\u{1F4CA} 新闻统计: load事件触发,但已记录过,跳过');
return;
}
console.log('\u{1F4CA} 新闻统计: load事件触发(兜底)');
executeStats();
});
}
console.log('\u{1F4CA} 新闻统计: 初始化完成');
})();
// ==================== 统计结束 ====================
配置说明
MAIN_HOST:填写您的主站域名(含协议,如 http://n.newsds.cn 或 https://n.newsds.cn)
STATS_PATH:统计接口路径,默认 /api/ajax_news_stats.php。若接口实际部署在 /api/ 目录,则改为 /api/ajax_news_stats.php
第二步:在内容页模板中引入脚本
在新闻内容页模板的 </head> 前一行,或 </body> 前一行,添加:
<script src="/skin/stats.js"></script>
第三步:在需要显示统计数值的位置插入占位标签
在文章信息区(如发布时间、来源旁)添加以下代码:
访问:<span id="newsStats" style="color:#E74C3C; font-weight:bold;">加载中...</span>
脚本加载后会自动将 加载中... 替换为实际 PV 数值,鼠标悬停可查看今日、本周、本月、UV、IP 等明细。
第四步:站群目录同步
若您的站点使用父栏目独立域名(站群) 结构,请将 stats.js 复制到每个站群目录的 /skin/ 目录下(例如:/sanya/skin/stats.js)。
由于 MAIN_HOST 已统一配置为主站域名,所有站群无需再单独修改域名,统计请求会自动跨域提交至主站接口。
三、数据统计说明
指标 说明
PV 页面访问总次数,每次刷新计数
UV 独立访客数,基于 Cookie + IP + UA 指纹识别
IP 独立 IP 数,基于客户端真实 IP 统计
今日 / 本周 / 本月 按访问日期实时计算
总计 优先读取文章表缓存字段(stats_pv/stats_uv/stats_ip),提升查询性能
四、常见问题
Q1:统计数值一直显示 0 或“加载中...”?
检查 stats.js 中的 MAIN_HOST 是否为主站正确域名(含协议)
检查 STATS_PATH 是否与实际接口路径一致(/api/)
打开浏览器控制台查看 \u{1F4CA} 新闻统计 日志,确认请求地址与响应状态
Q2:站群域名下无法统计?
确认主站接口已开启 CORS(响应头 Access-Control-Allow-Origin: *)
确认站群页面引入的是本站群目录下的 /skin/stats.js
确认该 stats.js 中的 MAIN_HOST 指向主站域名
Q3:更换主站域名后需要改哪些文件?
只需修改主站及各站群目录下 stats.js 中的 MAIN_HOST 一处人,无需改动其他配置
Q4:接口返回 404?
确认 ajax_news_stats.php 与 news_stats.php 已正确部署,且路径与 STATS_PATH 一致
确认服务器已启用 PHP 及 mysqli 扩展
五、版本记录
版本 日期 说明
v3.0及以上版本 — 优化配置项,统一主站域名入口,简化站群部署
本网声明:功能更新发布此文仅出于信息分享,并非赞同其观点或证实其描述,不承担侵权行为的连带责任。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。版权归原作者所有,如涉版权或来源标注有误,请联系我们删除。本网原创内容未经书面授权,任何单位或个人不得转载和摘编。
