|
|
@@ -27,10 +27,14 @@
|
|
|
|
|
|
// 拦截 openPreview 函数,记录预览行为
|
|
|
var originalOpenPreview = window.openPreview;
|
|
|
+ console.log('[DetailTrack] 初始化: originalOpenPreview =', typeof originalOpenPreview, originalOpenPreview);
|
|
|
+ console.log('[DetailTrack] 初始化: auditStatus=${entity.auditStatus}, entityId=${entity.id}');
|
|
|
window.openPreview = function(url, type) {
|
|
|
- // 首次查看附件时开始计时
|
|
|
+ console.log('[DetailTrack] 拦截器被调用, url=', url, 'type=', type);
|
|
|
+ // 首次查看附件时开始计时(仅首次,后续点击不重置)
|
|
|
if (entryTime === null) {
|
|
|
entryTime = Date.now();
|
|
|
+ startAuditTimer();
|
|
|
}
|
|
|
// 记录预览行为
|
|
|
var attachName = '';
|
|
|
@@ -49,7 +53,11 @@
|
|
|
});
|
|
|
// 调用原始函数
|
|
|
if (originalOpenPreview) {
|
|
|
- originalOpenPreview(url, type);
|
|
|
+ var result = originalOpenPreview(url, type);
|
|
|
+ console.log('[DetailTrack] 原始openPreview返回值:', result ? 'Window对象' : 'null/undefined');
|
|
|
+ return result;
|
|
|
+ } else {
|
|
|
+ console.log('[DetailTrack] 警告: originalOpenPreview 不存在!');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -57,9 +65,10 @@
|
|
|
$(document).on('click', '.op-btn-download', function(e) {
|
|
|
e.preventDefault(); // 阻止默认的链接行为
|
|
|
|
|
|
- // 首次查看附件时开始计时
|
|
|
+ // 首次查看附件时开始计时(仅首次,后续点击不重置)
|
|
|
if (entryTime === null) {
|
|
|
entryTime = Date.now();
|
|
|
+ startAuditTimer();
|
|
|
}
|
|
|
|
|
|
var $btn = $(this);
|
|
|
@@ -102,6 +111,99 @@
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ // ========== 审核倒计时(纯前端展示) ==========
|
|
|
+ var auditTimerFloat = null; // 审核计时浮层引用
|
|
|
+ var auditTimerInterval = null; // 审核计时定时器
|
|
|
+ var auditOpenWins = []; // 当前打开的预览窗口列表
|
|
|
+ var auditPollTimer = null; // 窗口关闭轮询定时器
|
|
|
+ var auditAccumulated = 0; // 累计已看秒数(暂停时保留)
|
|
|
+ var auditTimerRunning = false; // 计时器是否正在运行
|
|
|
+
|
|
|
+ // 注册新打开的预览窗口
|
|
|
+ function registerAuditWin(win) {
|
|
|
+ if (!win) return;
|
|
|
+ auditOpenWins.push(win);
|
|
|
+ if (!auditTimerRunning && entryTime) {
|
|
|
+ resumeAuditTimer();
|
|
|
+ }
|
|
|
+ startAuditWinPolling();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 轮询检测所有窗口是否关闭
|
|
|
+ function startAuditWinPolling() {
|
|
|
+ if (auditPollTimer) return;
|
|
|
+ auditPollTimer = setInterval(function() {
|
|
|
+ // 过滤掉已关闭的窗口
|
|
|
+ auditOpenWins = auditOpenWins.filter(function(w) {
|
|
|
+ try { return !w.closed; } catch(e) { return false; }
|
|
|
+ });
|
|
|
+ if (auditOpenWins.length === 0 && auditTimerRunning) {
|
|
|
+ // 所有窗口都关闭了,暂停计时
|
|
|
+ pauseAuditTimer();
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function pauseAuditTimer() {
|
|
|
+ if (auditTimerInterval) {
|
|
|
+ clearInterval(auditTimerInterval);
|
|
|
+ auditTimerInterval = null;
|
|
|
+ }
|
|
|
+ auditTimerRunning = false;
|
|
|
+ if (auditTimerFloat) {
|
|
|
+ var minStay = ${minStaySeconds != null ? minStaySeconds : 180};
|
|
|
+ var elapsedStr = fmtAuditTime(auditAccumulated);
|
|
|
+ var remaining = Math.max(0, minStay - auditAccumulated);
|
|
|
+ if (remaining > 0) {
|
|
|
+ auditTimerFloat.innerHTML = '<div style="font-weight:bold;margin-bottom:4px;">\u23f8 \u5ba1\u6838\u8ba1\u65f6\uff08\u5df2\u6682\u505c\uff09</div>'
|
|
|
+ + '<div>\u5df2\u770b\uff1a<span style="color:#4caf50;">' + elapsedStr + '</span></div>'
|
|
|
+ + '<div>\u8fd8\u9700\uff1a<span style="color:#ff9800;">' + fmtAuditTime(remaining) + '</span></div>'
|
|
|
+ + '<div style="color:#999;font-size:12px;margin-top:2px;">\u6253\u5f00\u6587\u4ef6\u7ee7\u7eed\u8ba1\u65f6</div>';
|
|
|
+ } else {
|
|
|
+ auditTimerFloat.innerHTML = '<div style="font-weight:bold;margin-bottom:4px;">\u23f8 \u5ba1\u6838\u8ba1\u65f6\uff08\u5df2\u6682\u505c\uff09</div>'
|
|
|
+ + '<div>\u5df2\u770b\uff1a<span style="color:#4caf50;">' + elapsedStr + '</span></div>'
|
|
|
+ + '<div style="color:#4caf50;font-weight:bold;">\u2714 \u5df2\u8fbe\u6807\uff0c\u53ef\u4ee5\u5ba1\u6838</div>';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function resumeAuditTimer() {
|
|
|
+ if (auditTimerInterval) return;
|
|
|
+ auditTimerRunning = true;
|
|
|
+ var minStay = ${minStaySeconds != null ? minStaySeconds : 180};
|
|
|
+ auditTimerInterval = setInterval(function() {
|
|
|
+ if (!entryTime) return;
|
|
|
+ auditAccumulated++;
|
|
|
+ var remaining = Math.max(0, minStay - auditAccumulated);
|
|
|
+ var elapsedStr = fmtAuditTime(auditAccumulated);
|
|
|
+ if (remaining > 0) {
|
|
|
+ auditTimerFloat.innerHTML = '<div style="font-weight:bold;margin-bottom:4px;">\ud83d\udd52 \u5ba1\u6838\u8ba1\u65f6</div>'
|
|
|
+ + '<div>\u5df2\u770b\uff1a<span style="color:#4caf50;">' + elapsedStr + '</span></div>'
|
|
|
+ + '<div>\u8fd8\u9700\uff1a<span style="color:#ff9800;">' + fmtAuditTime(remaining) + '</span></div>';
|
|
|
+ } else {
|
|
|
+ auditTimerFloat.innerHTML = '<div style="font-weight:bold;margin-bottom:4px;">\u2705 \u5ba1\u6838\u8ba1\u65f6</div>'
|
|
|
+ + '<div>\u5df2\u770b\uff1a<span style="color:#4caf50;">' + elapsedStr + '</span></div>'
|
|
|
+ + '<div style="color:#4caf50;font-weight:bold;">\u2714 \u5df2\u8fbe\u6807\uff0c\u53ef\u4ee5\u5ba1\u6838</div>';
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function startAuditTimer() {
|
|
|
+ if (auditTimerRunning) return; // 已在运行
|
|
|
+ auditAccumulated = 0; // 首次启动,重置累计
|
|
|
+ resumeAuditTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ function fmtAuditTime(seconds) {
|
|
|
+ if (seconds < 60) return seconds + '\u79d2';
|
|
|
+ var m = Math.floor(seconds / 60);
|
|
|
+ var s = seconds % 60;
|
|
|
+ if (m < 60) return (s === 0 ? m + '\u5206' : m + '\u5206' + s + '\u79d2');
|
|
|
+ var h = Math.floor(m / 60);
|
|
|
+ m = m % 60;
|
|
|
+ return h + '\u65f6' + (m > 0 ? m + '\u5206' : '') + (s > 0 ? s + '\u79d2' : '');
|
|
|
+ }
|
|
|
+
|
|
|
// 时间格式化:秒 -> "X秒" / "X分Y秒" / "X时Y分Z秒"
|
|
|
function formatDuration(seconds) {
|
|
|
if (seconds < 60) {
|
|
|
@@ -140,8 +242,8 @@
|
|
|
$('#inputForm input[name="attachmentViewDetail"]').remove();
|
|
|
$('#inputForm input[name="isViewedAttachment"]').remove();
|
|
|
|
|
|
- // 计算附件查看时长(秒):从首次查看附件开始计时,未查看过则为0
|
|
|
- var viewDurationSeconds = entryTime !== null ? Math.floor((Date.now() - entryTime) / 1000) : 0;
|
|
|
+ // 计算附件查看时长(秒):使用累计计时(暂停时不计入)
|
|
|
+ var viewDurationSeconds = auditAccumulated || 0;
|
|
|
// 是否查看过附件
|
|
|
var isViewedAttachment = attachmentViewLog.length > 0 ? 1 : 0;
|
|
|
// 附件查看行为 JSON
|
|
|
@@ -547,16 +649,16 @@
|
|
|
or fn:containsIgnoreCase(attach.attachmentName,'gif')
|
|
|
or fn:containsIgnoreCase(attach.attachmentName,'jpeg')}">
|
|
|
<img src="${attach.temporaryUrl}" width="50" height="50"
|
|
|
- onclick="openPreview('${attach.temporaryUrl}',5)"
|
|
|
+ onclick="openPreviewWithRead('${attach.temporaryUrl}',5)"
|
|
|
alt="${attach.attachmentName}"/>
|
|
|
</c:when>
|
|
|
<c:when test="${fn:containsIgnoreCase(attach.attachmentName,'pdf')}">
|
|
|
<a class="attention-info" href="javascript:void(0)"
|
|
|
- onclick="openPreview('${attach.temporaryUrl}',1)">${attach.attachmentName}</a>
|
|
|
+ onclick="openPreviewWithRead('${attach.temporaryUrl}',1)">${attach.attachmentName}</a>
|
|
|
</c:when>
|
|
|
<c:otherwise>
|
|
|
<a class="attention-info" href="javascript:void(0)"
|
|
|
- onclick="openPreview('${attach.temporaryUrl}',2)">${attach.attachmentName}</a>
|
|
|
+ onclick="openPreviewWithRead('${attach.temporaryUrl}',2)">${attach.attachmentName}</a>
|
|
|
</c:otherwise>
|
|
|
</c:choose>
|
|
|
</td>
|
|
|
@@ -593,7 +695,7 @@
|
|
|
<tr>
|
|
|
<td>${link.linkDesc}</td>
|
|
|
<td>
|
|
|
- <a href="${link.linkUrl}" target="_blank" class="op-btn" style="color: #1E9FFF;">
|
|
|
+ <a href="javascript:void(0)" onclick="openLinkWithRead('${link.linkUrl}')" class="op-btn" style="color: #1E9FFF;">
|
|
|
<i class="fa fa-external-link"></i> ${link.linkUrl}
|
|
|
</a>
|
|
|
</td>
|
|
|
@@ -1019,6 +1121,265 @@
|
|
|
loadAnswers();
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ // ========== 阅读计时追踪(详情页独立) ==========
|
|
|
+ var readTracker = {
|
|
|
+ currentFileId: '${entity.id}',
|
|
|
+ startTime: null,
|
|
|
+ previewWin: null,
|
|
|
+ totalDuration: 0,
|
|
|
+ timer: null,
|
|
|
+ pollTimer: null,
|
|
|
+ threshold: 180,
|
|
|
+ originalTitle: document.title
|
|
|
+ };
|
|
|
+
|
|
|
+ function fmtReadDur(seconds) {
|
|
|
+ var m = Math.floor(seconds / 60);
|
|
|
+ var s = seconds % 60;
|
|
|
+ return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
|
|
|
+ }
|
|
|
+
|
|
|
+ function showReadBar(pct) {
|
|
|
+ var bar = document.getElementById('readProgressBar');
|
|
|
+ if (bar) { bar.style.display = 'block'; bar.style.width = Math.min(pct, 100) + '%'; }
|
|
|
+ }
|
|
|
+ function hideReadBar() {
|
|
|
+ var bar = document.getElementById('readProgressBar');
|
|
|
+ if (bar) { bar.style.display = 'none'; bar.style.width = '0%'; }
|
|
|
+ }
|
|
|
+
|
|
|
+ function showReadFloat(elapsed, threshold) {
|
|
|
+ var remaining = Math.max(0, threshold - elapsed);
|
|
|
+ var pct = Math.min(elapsed / threshold * 100, 100);
|
|
|
+ var el = document.getElementById('readFloatBox');
|
|
|
+ if (!el) return;
|
|
|
+ el.style.display = 'block';
|
|
|
+ el.innerHTML = '<div style="margin-bottom:4px;">📖 阅读计时</div>'
|
|
|
+ + '<div>已读: <b>' + fmtReadDur(elapsed) + '</b> / ' + fmtReadDur(threshold) + '</div>'
|
|
|
+ + '<div>剩余: <b style="color:' + (remaining > 0 ? '#ffeb3b' : '#4caf50') + '">'
|
|
|
+ + (remaining > 0 ? fmtReadDur(remaining) : '达标!') + '</b></div>'
|
|
|
+ + '<div style="margin-top:6px;height:4px;background:#555;border-radius:2px;overflow:hidden;">'
|
|
|
+ + '<div style="height:100%;width:' + pct + '%;background:linear-gradient(90deg,#ff9800,#4caf50);transition:width 1s;"></div></div>';
|
|
|
+ }
|
|
|
+ function hideReadFloat() {
|
|
|
+ var el = document.getElementById('readFloatBox');
|
|
|
+ if (el) el.style.display = 'none';
|
|
|
+ }
|
|
|
+
|
|
|
+ function doSettle(fid, duration, origTitle, granted, totalDuration, elapsed, alreadyGranted) {
|
|
|
+ var threshold = readTracker.threshold;
|
|
|
+ var accumulated = (totalDuration && totalDuration > 0) ? totalDuration : (elapsed || duration);
|
|
|
+ // 以服务端判定为准:本次新发放 or 之前已发放过
|
|
|
+ if (granted || alreadyGranted) {
|
|
|
+ document.title = '[\u2705 已获积分] ' + origTitle;
|
|
|
+ layer.msg('\u9605\u8bfb\u65f6\u95f4\u5df2\u8fbe\u6807\uff0c\u79ef\u5206\u5df2\u53d1\u653e\uff01', {icon: 1, time: 3000});
|
|
|
+ setTimeout(function() { document.title = origTitle; }, 5000);
|
|
|
+ } else {
|
|
|
+ document.title = origTitle;
|
|
|
+ var remaining = Math.max(0, threshold - accumulated);
|
|
|
+ var remainText = fmtReadDur(remaining);
|
|
|
+ layer.msg('\u672c\u6b21\u9605\u8bfb ' + fmtReadDur(duration) + '\uff0c\u7d2f\u8ba1 ' + fmtReadDur(accumulated) + '\uff0c\u672a\u8fbe\u6807\u3002\u8fd8\u9700\u9605\u8bfb ' + remainText + ' \u624d\u80fd\u83b7\u5f97\u79ef\u5206\u3002', {icon: 0, time: 5000});
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function settleCurrentRead() {
|
|
|
+ console.log('[DetailTrack] settleCurrentRead 被调用', { fileId: readTracker.currentFileId, startTime: readTracker.startTime });
|
|
|
+ if (!readTracker.currentFileId || !readTracker.startTime) return;
|
|
|
+ var duration = Math.max(1, Math.floor((Date.now() - readTracker.startTime) / 1000));
|
|
|
+ var fid = readTracker.currentFileId;
|
|
|
+ var origTitle = readTracker.originalTitle;
|
|
|
+ var elapsed = readTracker.totalDuration + duration;
|
|
|
+ console.log('[DetailTrack] 结算时长:', duration, '秒, 累计:', elapsed, '秒');
|
|
|
+ if (readTracker.timer) { clearInterval(readTracker.timer); readTracker.timer = null; }
|
|
|
+ if (readTracker.pollTimer) { clearInterval(readTracker.pollTimer); readTracker.pollTimer = null; }
|
|
|
+ hideReadBar();
|
|
|
+ hideReadFloat();
|
|
|
+ readTracker.currentFileId = null;
|
|
|
+ readTracker.startTime = null;
|
|
|
+ readTracker.previewWin = null;
|
|
|
+ readTracker.totalDuration = 0;
|
|
|
+ document.title = origTitle;
|
|
|
+ $.ajax({
|
|
|
+ type: 'POST',
|
|
|
+ url: '${ctx}/workKnowledgeBase/readLike/updateReadDuration',
|
|
|
+ data: { fileId: fid, duration: duration },
|
|
|
+ success: function(res) { console.log('[DetailTrack] 结算API返回:', res); doSettle(fid, duration, origTitle, res && res.granted, res && res.totalDuration, elapsed, res && res.alreadyGranted); },
|
|
|
+ error: function(xhr, status, err) { console.log('[DetailTrack] 结算API失败:', status, err); document.title = origTitle; }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function startReadTimer() {
|
|
|
+ console.log('[DetailTrack] startReadTimer 启动, totalDuration=', readTracker.totalDuration);
|
|
|
+ if (readTracker.timer) clearInterval(readTracker.timer);
|
|
|
+ readTracker.timer = setInterval(function() {
|
|
|
+ if (!readTracker.currentFileId || !readTracker.startTime) {
|
|
|
+ console.log('[DetailTrack] timer tick: 已停止(currentFileId或startTime为空)');
|
|
|
+ clearInterval(readTracker.timer); readTracker.timer = null; return;
|
|
|
+ }
|
|
|
+ var elapsed = readTracker.totalDuration + Math.floor((Date.now() - readTracker.startTime) / 1000);
|
|
|
+ var pct = Math.min(elapsed / readTracker.threshold * 100, 100);
|
|
|
+ document.title = '[ ' + fmtReadDur(elapsed) + '/' + fmtReadDur(readTracker.threshold) + '] ' + readTracker.originalTitle;
|
|
|
+ showReadBar(pct);
|
|
|
+ showReadFloat(elapsed, readTracker.threshold);
|
|
|
+ // 不再自动结算,等用户关闭窗口时统一处理
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function startClosePolling() {
|
|
|
+ console.log('[DetailTrack] startClosePolling 启动, previewWin=', readTracker.previewWin ? '有' : '无');
|
|
|
+ if (readTracker.pollTimer) clearInterval(readTracker.pollTimer);
|
|
|
+ readTracker.pollTimer = setInterval(function() {
|
|
|
+ if (!readTracker.currentFileId) {
|
|
|
+ clearInterval(readTracker.pollTimer); readTracker.pollTimer = null; return;
|
|
|
+ }
|
|
|
+ if (readTracker.previewWin) {
|
|
|
+ try {
|
|
|
+ if (readTracker.previewWin.closed) {
|
|
|
+ console.log('[DetailTrack] 轮询检测到窗口已关闭');
|
|
|
+ clearInterval(readTracker.pollTimer); readTracker.pollTimer = null;
|
|
|
+ settleCurrentRead();
|
|
|
+ }
|
|
|
+ } catch(e) {
|
|
|
+ console.log('[DetailTrack] 轮询异常:', e.message);
|
|
|
+ clearInterval(readTracker.pollTimer); readTracker.pollTimer = null;
|
|
|
+ settleCurrentRead();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function trackRead(fileId, openFn) {
|
|
|
+ console.log('[DetailTrack] trackRead 被调用, fileId=', fileId);
|
|
|
+ if (readTracker.currentFileId && readTracker.startTime) {
|
|
|
+ settleCurrentRead();
|
|
|
+ }
|
|
|
+ var openResult = openFn();
|
|
|
+ console.log('[DetailTrack] trackRead: openFn() 返回=', openResult ? 'Window对象' : 'null/undefined');
|
|
|
+ $.ajax({
|
|
|
+ type: 'POST',
|
|
|
+ url: '${ctx}/workKnowledgeBase/readLike/recordRead',
|
|
|
+ data: { fileId: fileId },
|
|
|
+ success: function(result) {
|
|
|
+ console.log('[DetailTrack] recordRead API返回:', result);
|
|
|
+ var accumulated = (result && result.totalReadDuration) || 0;
|
|
|
+ readTracker.totalDuration = accumulated;
|
|
|
+ readTracker.startTime = Date.now();
|
|
|
+ startReadTimer();
|
|
|
+ },
|
|
|
+ error: function(xhr, status, err) {
|
|
|
+ console.log('[DetailTrack] recordRead API失败:', status, err);
|
|
|
+ readTracker.startTime = Date.now();
|
|
|
+ startReadTimer();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ $.ajax({ type: 'POST', url: '${ctx}/workKnowledgeBase/readLike/incrementClick', data: { fileId: fileId } });
|
|
|
+ readTracker.currentFileId = fileId;
|
|
|
+ readTracker.previewWin = openResult;
|
|
|
+ console.log('[DetailTrack] trackRead: previewWin已保存, currentFileId=', fileId);
|
|
|
+ startClosePolling();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 详情页附件预览(仅审核通过才追踪阅读时长) */
|
|
|
+ function openPreviewWithRead(url, type) {
|
|
|
+ console.log('[DetailTrack] ===== openPreviewWithRead 被调用 =====', { url: url.substring(0, 80), type: type, auditStatus: '${entity.auditStatus}', entityId: '${entity.id}' });
|
|
|
+
|
|
|
+ // 1. 先处理URL(复用 common.js 的逻辑)
|
|
|
+ var processedUrl = url;
|
|
|
+ if (type === 1 || type === 3 || type === 5) {
|
|
|
+ processedUrl = 'https://view.xdocin.com/view?src=' + encodeURIComponent(url);
|
|
|
+ } else if (type === 2) {
|
|
|
+ if (url.indexOf('.xml') !== -1) {
|
|
|
+ processedUrl = 'https://view.xdocin.com/view?src=' + encodeURIComponent(url);
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ var uObj = new URL(url);
|
|
|
+ var pathname = uObj.pathname.toLowerCase();
|
|
|
+ var officeExts = ['.doc','.docx','.xls','.xlsx','.ppt','.pptx','.mdb','.accdb','.msg','.ost','.pst'];
|
|
|
+ var isOffice = officeExts.some(function(ext) { return pathname.endsWith(ext); });
|
|
|
+ if (isOffice) {
|
|
|
+ processedUrl = 'https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(url);
|
|
|
+ } else {
|
|
|
+ processedUrl = 'https://view.xdocin.com/view?src=' + encodeURIComponent(url);
|
|
|
+ }
|
|
|
+ } catch(e) {
|
|
|
+ processedUrl = 'https://view.xdocin.com/view?src=' + encodeURIComponent(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 立即打开窗口(同步,避免弹窗拦截)
|
|
|
+ var win = window.open(processedUrl, '_blank');
|
|
|
+ console.log('[DetailTrack] window.open 结果:', win ? 'Window对象' : 'null(被拦截)');
|
|
|
+
|
|
|
+ // 3. 记录审核行为追踪(仅首次设置 entryTime,不重置)
|
|
|
+ if (entryTime === null) {
|
|
|
+ entryTime = Date.now();
|
|
|
+ startAuditTimer();
|
|
|
+ }
|
|
|
+ // 注册窗口到跟踪列表(关闭时暂停计时)
|
|
|
+ registerAuditWin(win);
|
|
|
+ var attachName = '';
|
|
|
+ try { var urlObj2 = new URL(url); attachName = decodeURIComponent(urlObj2.pathname.split('/').pop() || ''); } catch(e) { attachName = url.substring(0, 50); }
|
|
|
+ attachmentViewLog.push({ name: attachName, action: 'preview', time: new Date().toLocaleString('zh-CN', {hour12: false}) });
|
|
|
+
|
|
|
+ // 4. 审核通过启动阅读积分计时,否则仅审核计时
|
|
|
+ if ('${entity.auditStatus}' == '5') {
|
|
|
+ console.log('[DetailTrack] 审核通过,启动阅读积分追踪');
|
|
|
+ trackRead('${entity.id}', function() { return win; });
|
|
|
+ } else {
|
|
|
+ console.log('[DetailTrack] 非审核通过(auditStatus=${entity.auditStatus}),仅审核计时');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 外部链接点击(带阅读追踪) */
|
|
|
+ function openLinkWithRead(linkUrl) {
|
|
|
+ console.log('[DetailTrack] openLinkWithRead:', linkUrl);
|
|
|
+ // 1. 同步打开窗口(避免弹窗拦截)
|
|
|
+ var win = window.open(linkUrl, '_blank');
|
|
|
+ console.log('[DetailTrack] window.open 结果:', win ? 'Window对象' : 'null(被拦截)');
|
|
|
+
|
|
|
+ // 2. 记录审核行为追踪(仅首次设置 entryTime,不重置)
|
|
|
+ if (entryTime === null) {
|
|
|
+ entryTime = Date.now();
|
|
|
+ startAuditTimer();
|
|
|
+ }
|
|
|
+ // 注册窗口到跟踪列表(关闭时暂停计时)
|
|
|
+ registerAuditWin(win);
|
|
|
+ var linkName = '';
|
|
|
+ try { var uObj = new URL(linkUrl); linkName = uObj.hostname + uObj.pathname.substring(0, 30); } catch(e) { linkName = linkUrl.substring(0, 50); }
|
|
|
+ attachmentViewLog.push({ name: linkName, action: 'link_click', time: new Date().toLocaleString('zh-CN', {hour12: false}) });
|
|
|
+
|
|
|
+ // 3. 只有审核通过才启动阅读计时
|
|
|
+ if ('${entity.auditStatus}' == '5') {
|
|
|
+ console.log('[DetailTrack] 链接-审核通过,启动追踪');
|
|
|
+ trackRead('${entity.id}', function() { return win; });
|
|
|
+ } else {
|
|
|
+ console.log('[DetailTrack] 链接-未审核通过,不追踪');
|
|
|
+ }
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<!-- 阅读计时进度条 -->
|
|
|
+<div id="readProgressBar" style="position:fixed;top:0;left:0;height:3px;background:linear-gradient(90deg,#ff9800,#4caf50);z-index:999999;width:0%;transition:width 1s;display:none;"></div>
|
|
|
+
|
|
|
+<!-- 阅读计时浮层(右下角) -->
|
|
|
+<div id="readFloatBox" style="position:fixed;right:20px;bottom:20px;z-index:999999;background:rgba(0,0,0,0.75);color:#fff;padding:10px 16px;border-radius:8px;font-size:13px;line-height:1.6;min-width:180px;box-shadow:0 2px 8px rgba(0,0,0,0.3);display:none;"></div>
|
|
|
+
|
|
|
+<!-- 审核计时浮层(右下角) -->
|
|
|
+<div id="auditTimerFloat" style="position:fixed;right:20px;bottom:20px;z-index:999999;background:rgba(0,0,0,0.75);color:#fff;padding:10px 16px;border-radius:8px;font-size:13px;line-height:1.6;min-width:180px;box-shadow:0 2px 8px rgba(0,0,0,0.3);display:none;"></div>
|
|
|
+<script>
|
|
|
+ // 页面加载时检查是否需要显示审核计时浮层
|
|
|
+ (function() {
|
|
|
+ var canAudit = ${canAudit != null ? canAudit : false};
|
|
|
+ var auditStatus = '${entity.auditStatus}';
|
|
|
+ if (canAudit && (auditStatus === '1' || auditStatus === '2')) {
|
|
|
+ auditTimerFloat = document.getElementById('auditTimerFloat');
|
|
|
+ auditTimerFloat.style.display = 'block';
|
|
|
+ auditTimerFloat.innerHTML = '<div style="font-weight:bold;margin-bottom:4px;">\ud83d\udd52 \u5ba1\u6838\u8ba1\u65f6</div>'
|
|
|
+ + '<div style="color:#999;">\u70b9\u51fb\u6587\u4ef6\u9884\u89c8\u6216\u94fe\u63a5\u5f00\u59cb\u8ba1\u65f6...</div>';
|
|
|
+ }
|
|
|
+ })();
|
|
|
</script>
|
|
|
|
|
|
</body>
|