fix: correct local datetime display and remove false devtools detection

This commit is contained in:
2026-02-18 11:23:34 +08:00
parent 751428a29a
commit 3ea17db971
3 changed files with 104 additions and 159 deletions

View File

@@ -13,101 +13,32 @@
} }
})(); })();
</script> </script>
<!-- 开发者工具检测 - 必须放在最前面,优先执行 --> <!-- 安全快捷键拦截(避免误判开发者工具) -->
<script> <script>
(function() { (function() {
'use strict'; 'use strict';
// 检查调试模式
const isDebugMode = localStorage.getItem('debugMode') === 'true'; const isDebugMode = localStorage.getItem('debugMode') === 'true';
if (isDebugMode) return; // 调试模式下跳过检测 if (isDebugMode) return;
let devtoolsOpen = false;
let checkCount = 0; // 检测次数计数器
// 方法1: Console对象toString检测最可靠
const checkElement = /./;
checkElement.toString = function() {
devtoolsOpen = true;
};
// 方法2: debugger暂停检测可能误判需要多次确认
function detectDebugger() {
const start = performance.now();
debugger;
const end = performance.now();
return (end - start) > 100;
}
// 方法3: 窗口尺寸检测(辅助判断)
function detectWindowSize() {
const widthDiff = window.outerWidth - window.innerWidth;
const heightDiff = window.outerHeight - window.innerHeight;
// 提高阈值,避免误判
return widthDiff > 200 || heightDiff > 200;
}
// 综合检测(需要多个条件同时满足才判定)
function checkDevTools() {
devtoolsOpen = false;
console.log('%c', checkElement);
console.clear();
const consoleOpen = devtoolsOpen;
const debuggerPause = detectDebugger();
const windowSizeAbnormal = detectWindowSize();
// 严格判定console检测为主其他为辅助
// 只有console明确检测到才触发警告
return consoleOpen && (debuggerPause || windowSizeAbnormal);
}
// 立即检测(页面加载时)
const initialCheck = checkDevTools();
if (initialCheck) {
// 检测到开发者工具,显示警告
document.write('<style>body{margin:0;overflow:hidden;}</style>');
document.write('<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);display:flex;align-items:center;justify-content:center;font-family:Arial,sans-serif;z-index:999999;">');
document.write('<div style="background:white;padding:60px;border-radius:20px;text-align:center;max-width:500px;box-shadow:0 20px 60px rgba(0,0,0,0.3);">');
document.write('<div style="font-size:80px;color:#667eea;margin-bottom:30px;">🛡️</div>');
document.write('<h1 style="color:#333;margin-bottom:20px;font-size:32px;">检测到开发者工具</h1>');
document.write('<p style="color:#666;font-size:18px;margin-bottom:30px;line-height:1.6;">为保护系统安全,请关闭浏览器开发者工具后访问</p>');
document.write('<button onclick="location.reload()" style="background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;border:none;padding:15px 40px;font-size:16px;border-radius:10px;cursor:pointer;font-weight:600;box-shadow:0 4px 15px rgba(102,126,234,0.4);">🔄 刷新页面</button>');
document.write('<p style="color:#999;font-size:14px;margin-top:20px;">如需使用开发者工具,请联系管理员开启调试模式</p>');
document.write('</div></div>');
document.close();
throw new Error('DevTools detected');
}
// 禁用右键和快捷键
document.addEventListener('contextmenu', e => e.preventDefault()); document.addEventListener('contextmenu', e => e.preventDefault());
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
// F12, Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U, Ctrl+Shift+C const key = String(e.key || '').toLowerCase();
if (e.keyCode === 123 || const blocked = e.keyCode === 123
(e.ctrlKey && e.shiftKey && (e.keyCode === 73 || e.keyCode === 74 || e.keyCode === 67)) || || (e.ctrlKey && e.shiftKey && ['i', 'j', 'c'].includes(key))
(e.ctrlKey && e.keyCode === 85)) { || (e.ctrlKey && key === 'u');
if (blocked) {
e.preventDefault(); e.preventDefault();
return false; return false;
} }
}); });
// 持续监控每2秒检测一次避免频繁检测
setInterval(function() {
checkCount++;
// 每次检测都要确认
if (checkDevTools()) {
// 检测到开发者工具打开,刷新页面
location.reload();
}
}, 2000);
// 禁用console非localhost
if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
const noop = function() {}; const noop = function() {};
['log', 'info', 'warn', 'error', 'debug'].forEach(method => { ['log', 'info', 'warn', 'error', 'debug'].forEach(method => {
try {
console[method] = noop; console[method] = noop;
} catch (_) {}
}); });
} }
})(); })();
@@ -7599,6 +7530,6 @@
} }
</style> </style>
<script src="app.js?v=20260218001"></script> <script src="app.js?v=20260218002"></script>
</body> </body>
</html> </html>

View File

@@ -623,8 +623,14 @@ createApp({
} }
list.sort((a, b) => { list.sort((a, b) => {
const getTime = s => s.created_at ? new Date(s.created_at).getTime() : 0; const getTime = (s) => {
const getExpire = s => s.expires_at ? new Date(s.expires_at).getTime() : Number.MAX_SAFE_INTEGER; const date = this.parseDateValue(s?.created_at);
return date ? date.getTime() : 0;
};
const getExpire = (s) => {
const date = this.parseDateValue(s?.expires_at);
return date ? date.getTime() : Number.MAX_SAFE_INTEGER;
};
switch (this.shareFilters.sort) { switch (this.shareFilters.sort) {
case 'created_asc': case 'created_asc':
@@ -657,8 +663,10 @@ createApp({
} }
list.sort((a, b) => { list.sort((a, b) => {
const ta = a.created_at ? new Date(a.created_at).getTime() : 0; const da = this.parseDateValue(a?.created_at);
const tb = b.created_at ? new Date(b.created_at).getTime() : 0; const db = this.parseDateValue(b?.created_at);
const ta = da ? da.getTime() : 0;
const tb = db ? db.getTime() : 0;
return tb - ta; return tb - ta;
}); });
@@ -3006,7 +3014,8 @@ handleDragLeave(e) {
formatExpireTime(expiresAt) { formatExpireTime(expiresAt) {
if (!expiresAt) return '永久有效'; if (!expiresAt) return '永久有效';
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return String(expiresAt);
const now = new Date(); const now = new Date();
const diffMs = expireDate - now; const diffMs = expireDate - now;
const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60));
@@ -3040,7 +3049,8 @@ handleDragLeave(e) {
// 判断是否即将过期3天内 // 判断是否即将过期3天内
isExpiringSoon(expiresAt) { isExpiringSoon(expiresAt) {
if (!expiresAt) return false; if (!expiresAt) return false;
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return false;
const now = new Date(); const now = new Date();
const diffMs = expireDate - now; const diffMs = expireDate - now;
const diffDays = diffMs / (1000 * 60 * 60 * 24); const diffDays = diffMs / (1000 * 60 * 60 * 24);
@@ -3050,7 +3060,8 @@ handleDragLeave(e) {
// 判断是否已过期 // 判断是否已过期
isExpired(expiresAt) { isExpired(expiresAt) {
if (!expiresAt) return false; if (!expiresAt) return false;
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return false;
const now = new Date(); const now = new Date();
return expireDate <= now; return expireDate <= now;
}, },
@@ -3148,8 +3159,8 @@ handleDragLeave(e) {
// 格式化时间 // 格式化时间
formatDateTime(value) { formatDateTime(value) {
if (!value) return '--'; if (!value) return '--';
const d = new Date(value); const d = this.parseDateValue(value);
if (Number.isNaN(d.getTime())) return value; if (!d) return value;
return d.toLocaleString(); return d.toLocaleString();
}, },
@@ -4141,14 +4152,41 @@ handleDragLeave(e) {
return '不自动重置'; return '不自动重置';
}, },
parseDateValue(value) {
if (!value) return null;
if (value instanceof Date) {
return Number.isNaN(value.getTime()) ? null : value;
}
const raw = String(value).trim();
if (!raw) return null;
const localMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
if (localMatch) {
const year = Number(localMatch[1]);
const month = Number(localMatch[2]);
const day = Number(localMatch[3]);
const hour = Number(localMatch[4] || 0);
const minute = Number(localMatch[5] || 0);
const second = Number(localMatch[6] || 0);
const localDate = new Date(year, month - 1, day, hour, minute, second);
return Number.isNaN(localDate.getTime()) ? null : localDate;
}
const normalized = raw.includes('T') ? raw : raw.replace(' ', 'T');
const parsed = new Date(normalized);
return Number.isNaN(parsed.getTime()) ? null : parsed;
},
toDateTimeLocalInput(dateString) { toDateTimeLocalInput(dateString) {
if (!dateString) return ''; if (!dateString) return '';
const normalized = String(dateString).trim().replace(' ', 'T'); const normalized = String(dateString).trim().replace(' ', 'T');
const match = normalized.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})/); const match = normalized.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})/);
if (match) return match[1]; if (match) return match[1];
const parsed = new Date(normalized); const parsed = this.parseDateValue(dateString);
if (Number.isNaN(parsed.getTime())) return ''; if (!parsed) return '';
const year = parsed.getFullYear(); const year = parsed.getFullYear();
const month = String(parsed.getMonth() + 1).padStart(2, '0'); const month = String(parsed.getMonth() + 1).padStart(2, '0');
const day = String(parsed.getDate()).padStart(2, '0'); const day = String(parsed.getDate()).padStart(2, '0');
@@ -4170,15 +4208,8 @@ handleDragLeave(e) {
formatDate(dateString) { formatDate(dateString) {
if (!dateString) return '-'; if (!dateString) return '-';
// SQLite 返回的是 UTC 时间字符串,需要显式处理 const date = this.parseDateValue(dateString);
// 如果字符串不包含时区信息,手动添加 'Z' 标记为 UTC if (!date) return String(dateString);
let dateStr = dateString;
if (!dateStr.includes('Z') && !dateStr.includes('+') && !dateStr.includes('T')) {
// SQLite 格式: "2025-11-13 16:37:19" -> ISO格式: "2025-11-13T16:37:19Z"
dateStr = dateStr.replace(' ', 'T') + 'Z';
}
const date = new Date(dateStr);
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
@@ -4511,7 +4542,8 @@ handleDragLeave(e) {
formatLogTime(timestamp) { formatLogTime(timestamp) {
if (!timestamp) return ''; if (!timestamp) return '';
const date = new Date(timestamp); const date = this.parseDateValue(timestamp);
if (!date) return String(timestamp);
return date.toLocaleString('zh-CN', { return date.toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
month: '2-digit', month: '2-digit',

View File

@@ -1369,17 +1369,36 @@
return 'color: #9E9E9E;'; return 'color: #9E9E9E;';
}, },
formatDate(dateString) { parseDateValue(value) {
if (!dateString) return ''; if (!value) return null;
if (value instanceof Date) {
// SQLite 返回的是 UTC 时间字符串,需要显式处理 return Number.isNaN(value.getTime()) ? null : value;
let dateStr = dateString;
if (!dateStr.includes('Z') && !dateStr.includes('+') && !dateStr.includes('T')) {
// SQLite 格式: "2025-11-13 16:37:19" -> ISO格式: "2025-11-13T16:37:19Z"
dateStr = dateStr.replace(' ', 'T') + 'Z';
} }
const date = new Date(dateStr); const raw = String(value).trim();
if (!raw) return null;
const localMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/);
if (localMatch) {
const year = Number(localMatch[1]);
const month = Number(localMatch[2]);
const day = Number(localMatch[3]);
const hour = Number(localMatch[4] || 0);
const minute = Number(localMatch[5] || 0);
const second = Number(localMatch[6] || 0);
const localDate = new Date(year, month - 1, day, hour, minute, second);
return Number.isNaN(localDate.getTime()) ? null : localDate;
}
const normalized = raw.includes('T') ? raw : raw.replace(' ', 'T');
const parsed = new Date(normalized);
return Number.isNaN(parsed.getTime()) ? null : parsed;
},
formatDate(dateString) {
if (!dateString) return '';
const date = this.parseDateValue(dateString);
if (!date) return String(dateString);
return date.toLocaleString('zh-CN', { return date.toLocaleString('zh-CN', {
year: 'numeric', year: 'numeric',
month: '2-digit', month: '2-digit',
@@ -1395,7 +1414,8 @@
formatExpireTime(expiresAt) { formatExpireTime(expiresAt) {
if (!expiresAt) return '永久有效'; if (!expiresAt) return '永久有效';
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return String(expiresAt);
const now = new Date(); const now = new Date();
const diffMs = expireDate - now; const diffMs = expireDate - now;
const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60));
@@ -1429,7 +1449,8 @@
// 判断是否即将过期3天内 // 判断是否即将过期3天内
isExpiringSoon(expiresAt) { isExpiringSoon(expiresAt) {
if (!expiresAt) return false; if (!expiresAt) return false;
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return false;
const now = new Date(); const now = new Date();
const diffMs = expireDate - now; const diffMs = expireDate - now;
const diffDays = diffMs / (1000 * 60 * 60 * 24); const diffDays = diffMs / (1000 * 60 * 60 * 24);
@@ -1439,7 +1460,8 @@
// 判断是否已过期 // 判断是否已过期
isExpired(expiresAt) { isExpired(expiresAt) {
if (!expiresAt) return false; if (!expiresAt) return false;
const expireDate = new Date(expiresAt); const expireDate = this.parseDateValue(expiresAt);
if (!expireDate) return false;
const now = new Date(); const now = new Date();
return expireDate <= now; return expireDate <= now;
} }
@@ -1494,57 +1516,17 @@
// 禁用F12和常见开发者工具快捷键调试模式下不禁用 // 禁用F12和常见开发者工具快捷键调试模式下不禁用
if (!isDebugMode) { if (!isDebugMode) {
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
// F12 const key = String(e.key || '').toLowerCase();
if (e.key === 'F12' || e.keyCode === 123) { const blocked = e.keyCode === 123
e.preventDefault(); || (e.ctrlKey && e.shiftKey && ['i', 'j', 'c'].includes(key))
return false; || (e.ctrlKey && key === 'u');
} if (blocked) {
// Ctrl+Shift+I (开发者工具)
if (e.ctrlKey && e.shiftKey && (e.key === 'I' || e.keyCode === 73)) {
e.preventDefault();
return false;
}
// Ctrl+Shift+J (控制台)
if (e.ctrlKey && e.shiftKey && (e.key === 'J' || e.keyCode === 74)) {
e.preventDefault();
return false;
}
// Ctrl+U (查看源代码)
if (e.ctrlKey && (e.key === 'U' || e.keyCode === 85)) {
e.preventDefault();
return false;
}
// Ctrl+Shift+C (元素选择器)
if (e.ctrlKey && e.shiftKey && (e.key === 'C' || e.keyCode === 67)) {
e.preventDefault(); e.preventDefault();
return false; return false;
} }
}); });
} }
// 检测开发者工具是否打开(调试模式下不检测)
if (!isDebugMode) {
(function() {
const threshold = 160;
let isDevToolsOpen = false;
setInterval(function() {
const widthThreshold = window.outerWidth - window.innerWidth > threshold;
const heightThreshold = window.outerHeight - window.innerHeight > threshold;
if (!(heightThreshold && widthThreshold) &&
((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) || widthThreshold || heightThreshold)) {
if (!isDevToolsOpen) {
isDevToolsOpen = true;
console.clear();
}
} else {
isDevToolsOpen = false;
}
}, 500);
})();
}
// 禁用console输出调试模式下不禁用 // 禁用console输出调试模式下不禁用
if (!isDebugMode && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { if (!isDebugMode && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
console.log = function() {}; console.log = function() {};