feat: 完善外部资源备用机制和日志系统
- 在header.php中添加用户角色检测,传递给前端JavaScript - 更新Open Sans字体CSS文件,支持本地woff2字体文件备用 - 修改footer.php中MathJax 3/2和KaTeX加载机制,添加onerror备用处理 - 优化resource-loader.js日志系统,使用ArgonLogger替代console.log - 仅管理员用户显示控制台日志,普通用户和游客不显示调试信息 - 完善资源加载错误处理,统一使用ArgonLogger记录警告信息
This commit is contained in:
300
assets/vendor/external/css-fallback.css
vendored
Normal file
300
assets/vendor/external/css-fallback.css
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
/* CSS 备用系统 - 确保基本样式始终可用 */
|
||||
|
||||
/* 重置样式 - 确保跨浏览器一致性 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 基本布局 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
.col {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
margin: 4px 2px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #e9ecef;
|
||||
border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
border-color: #004085;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 12px 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #80bdff;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
}
|
||||
|
||||
/* 导航样式 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 90%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 警告和错误样式 */
|
||||
.alert {
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin: 0 -10px;
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #2d2d2d;
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #404040;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: #2d2d2d;
|
||||
border-color: #404040;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #2d2d2d;
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
.navbar-brand,
|
||||
.nav-link {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
BIN
assets/vendor/external/fonts/files/opensans-300-latin.woff2
vendored
Normal file
BIN
assets/vendor/external/fonts/files/opensans-300-latin.woff2
vendored
Normal file
Binary file not shown.
BIN
assets/vendor/external/fonts/files/opensans-400-latin.woff2
vendored
Normal file
BIN
assets/vendor/external/fonts/files/opensans-400-latin.woff2
vendored
Normal file
Binary file not shown.
BIN
assets/vendor/external/fonts/files/opensans-600-latin.woff2
vendored
Normal file
BIN
assets/vendor/external/fonts/files/opensans-600-latin.woff2
vendored
Normal file
Binary file not shown.
72
assets/vendor/external/fonts/font-fallback.css
vendored
Normal file
72
assets/vendor/external/fonts/font-fallback.css
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/* 字体备用系统 - 确保在任何情况下都有合适的字体显示 */
|
||||
|
||||
/* 全局字体备用栈 */
|
||||
:root {
|
||||
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-family-serif: Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-family-mono: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--font-family-chinese: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
||||
}
|
||||
|
||||
/* 当 Open Sans 不可用时的备用 */
|
||||
body, .open-sans-fallback {
|
||||
font-family: "Open Sans", var(--font-family-sans);
|
||||
}
|
||||
|
||||
/* 当 Noto Serif SC 不可用时的备用 */
|
||||
.noto-serif-fallback {
|
||||
font-family: "Noto Serif SC", var(--font-family-chinese), var(--font-family-serif);
|
||||
}
|
||||
|
||||
/* 通用中文字体备用 */
|
||||
.chinese-text {
|
||||
font-family: var(--font-family-chinese);
|
||||
}
|
||||
|
||||
/* 确保代码字体始终可用 */
|
||||
code, pre, .monospace {
|
||||
font-family: var(--font-family-mono);
|
||||
}
|
||||
|
||||
/* 字体加载失败时的样式调整 */
|
||||
.font-loading-error {
|
||||
font-family: var(--font-family-sans) !important;
|
||||
}
|
||||
|
||||
.font-loading-error::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 响应式字体大小 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保图标字体备用 */
|
||||
.fa, .fas, .far, .fal, .fab {
|
||||
font-family: "Font Awesome 5 Free", "Font Awesome 5 Pro", "FontAwesome", sans-serif;
|
||||
}
|
||||
|
||||
/* 当图标字体不可用时显示文字 */
|
||||
.fa-home::after { content: "首页"; }
|
||||
.fa-user::after { content: "用户"; }
|
||||
.fa-search::after { content: "搜索"; }
|
||||
.fa-menu::after { content: "菜单"; }
|
||||
.fa-close::after { content: "关闭"; }
|
||||
.fa-arrow-left::after { content: "←"; }
|
||||
.fa-arrow-right::after { content: "→"; }
|
||||
.fa-arrow-up::after { content: "↑"; }
|
||||
.fa-arrow-down::after { content: "↓"; }
|
||||
28
assets/vendor/external/fonts/open-sans.css
vendored
28
assets/vendor/external/fonts/open-sans.css
vendored
@@ -1,32 +1,50 @@
|
||||
/* Open Sans Font - Local Fallback */
|
||||
/* Open Sans Font - 完整本地备用版本 */
|
||||
|
||||
/* 300 weight - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: local('Open Sans Light'), local('OpenSans-Light');
|
||||
src: url('files/opensans-300-latin.woff2') format('woff2'),
|
||||
local('Open Sans Light'), local('OpenSans-Light');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* 400 weight - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Open Sans Regular'), local('OpenSans-Regular');
|
||||
src: url('files/opensans-400-latin.woff2') format('woff2'),
|
||||
local('Open Sans Regular'), local('OpenSans-Regular');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* 600 weight - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold');
|
||||
src: url('files/opensans-600-latin.woff2') format('woff2'),
|
||||
local('Open Sans SemiBold'), local('OpenSans-SemiBold');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* 700 weight - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Open Sans Bold'), local('OpenSans-Bold');
|
||||
src: url('files/opensans-700-latin.woff2') format('woff2'),
|
||||
local('Open Sans Bold'), local('OpenSans-Bold');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* 备用字体栈 */
|
||||
body, .font-sans-serif {
|
||||
font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
||||
328
assets/vendor/external/js-fallback.js
vendored
Normal file
328
assets/vendor/external/js-fallback.js
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
/* JavaScript 功能备用系统 - 确保基本功能始终可用 */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// 检查并提供基本的 JavaScript 功能
|
||||
window.ArgonFallback = {
|
||||
// 初始化备用系统
|
||||
init: function() {
|
||||
this.checkDependencies();
|
||||
this.provideFallbacks();
|
||||
this.bindEvents();
|
||||
console.log('JavaScript 备用系统已启动');
|
||||
},
|
||||
|
||||
// 检查依赖项
|
||||
checkDependencies: function() {
|
||||
var missing = [];
|
||||
|
||||
// 检查 jQuery
|
||||
if (typeof jQuery === 'undefined') {
|
||||
missing.push('jQuery');
|
||||
this.provideJQueryFallback();
|
||||
}
|
||||
|
||||
// 检查其他关键库
|
||||
if (typeof bootstrap === 'undefined') {
|
||||
missing.push('Bootstrap');
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.warn('缺少依赖项:', missing.join(', '));
|
||||
}
|
||||
},
|
||||
|
||||
// 提供 jQuery 基本功能备用
|
||||
provideJQueryFallback: function() {
|
||||
window.$ = window.jQuery = function(selector) {
|
||||
return new jQueryFallback(selector);
|
||||
};
|
||||
|
||||
// 扩展 jQuery 对象
|
||||
$.fn = jQueryFallback.prototype;
|
||||
$.extend = function(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
console.log('jQuery 备用版本已加载');
|
||||
},
|
||||
|
||||
// 提供其他备用功能
|
||||
provideFallbacks: function() {
|
||||
// 模态框功能
|
||||
this.provideModalFallback();
|
||||
|
||||
// 工具提示功能
|
||||
this.provideTooltipFallback();
|
||||
|
||||
// 下拉菜单功能
|
||||
this.provideDropdownFallback();
|
||||
},
|
||||
|
||||
// 模态框备用
|
||||
provideModalFallback: function() {
|
||||
window.showModal = function(content, title) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal show';
|
||||
modal.innerHTML =
|
||||
'<div class="modal-dialog">' +
|
||||
'<div class="modal-header">' +
|
||||
'<h5>' + (title || '提示') + '</h5>' +
|
||||
'<button type="button" class="close" onclick="this.closest(\'.modal\').remove()">×</button>' +
|
||||
'</div>' +
|
||||
'<div class="modal-body">' + content + '</div>' +
|
||||
'<div class="modal-footer">' +
|
||||
'<button type="button" class="btn" onclick="this.closest(\'.modal\').remove()">关闭</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// 点击背景关闭
|
||||
modal.addEventListener('click', function(e) {
|
||||
if (e.target === modal) {
|
||||
modal.remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
// 工具提示备用
|
||||
provideTooltipFallback: function() {
|
||||
document.addEventListener('mouseover', function(e) {
|
||||
var element = e.target;
|
||||
var tooltip = element.getAttribute('title') || element.getAttribute('data-tooltip');
|
||||
|
||||
if (tooltip && !element.querySelector('.tooltip-fallback')) {
|
||||
var tooltipEl = document.createElement('div');
|
||||
tooltipEl.className = 'tooltip-fallback';
|
||||
tooltipEl.textContent = tooltip;
|
||||
tooltipEl.style.cssText =
|
||||
'position: absolute; background: #333; color: #fff; padding: 4px 8px; ' +
|
||||
'border-radius: 4px; font-size: 12px; z-index: 1000; pointer-events: none;';
|
||||
|
||||
document.body.appendChild(tooltipEl);
|
||||
|
||||
var rect = element.getBoundingClientRect();
|
||||
tooltipEl.style.left = rect.left + 'px';
|
||||
tooltipEl.style.top = (rect.top - tooltipEl.offsetHeight - 5) + 'px';
|
||||
|
||||
element.addEventListener('mouseleave', function() {
|
||||
if (tooltipEl.parentNode) {
|
||||
tooltipEl.parentNode.removeChild(tooltipEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 下拉菜单备用
|
||||
provideDropdownFallback: function() {
|
||||
document.addEventListener('click', function(e) {
|
||||
var toggle = e.target.closest('[data-toggle="dropdown"]');
|
||||
if (toggle) {
|
||||
e.preventDefault();
|
||||
var menu = toggle.nextElementSibling;
|
||||
if (menu && menu.classList.contains('dropdown-menu')) {
|
||||
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭其他下拉菜单
|
||||
var openMenus = document.querySelectorAll('.dropdown-menu[style*="block"]');
|
||||
openMenus.forEach(function(menu) {
|
||||
if (!menu.contains(e.target) && !menu.previousElementSibling.contains(e.target)) {
|
||||
menu.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 绑定事件
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// 监听错误事件
|
||||
window.addEventListener('error', function(e) {
|
||||
console.warn('JavaScript 错误:', e.message);
|
||||
self.handleError(e);
|
||||
});
|
||||
|
||||
// 监听未处理的 Promise 拒绝
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.warn('未处理的 Promise 拒绝:', e.reason);
|
||||
});
|
||||
},
|
||||
|
||||
// 错误处理
|
||||
handleError: function(error) {
|
||||
// 尝试恢复基本功能
|
||||
if (error.message.includes('jQuery') || error.message.includes('$')) {
|
||||
this.provideJQueryFallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// jQuery 备用实现
|
||||
function jQueryFallback(selector) {
|
||||
this.elements = [];
|
||||
|
||||
if (typeof selector === 'string') {
|
||||
this.elements = Array.from(document.querySelectorAll(selector));
|
||||
} else if (selector && selector.nodeType) {
|
||||
this.elements = [selector];
|
||||
} else if (selector && typeof selector === 'object' && selector.length !== undefined) {
|
||||
this.elements = Array.from(selector);
|
||||
}
|
||||
|
||||
this.length = this.elements.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
// jQuery 方法实现
|
||||
jQueryFallback.prototype = {
|
||||
each: function(callback) {
|
||||
this.elements.forEach(callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
on: function(event, handler) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.addEventListener(event, handler);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
off: function(event, handler) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.removeEventListener(event, handler);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
click: function(handler) {
|
||||
if (handler) {
|
||||
return this.on('click', handler);
|
||||
} else {
|
||||
this.elements.forEach(function(el) {
|
||||
el.click();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
addClass: function(className) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.classList.add(className);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
removeClass: function(className) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.classList.remove(className);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
toggleClass: function(className) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.classList.toggle(className);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
hasClass: function(className) {
|
||||
return this.elements.some(function(el) {
|
||||
return el.classList.contains(className);
|
||||
});
|
||||
},
|
||||
|
||||
html: function(content) {
|
||||
if (content !== undefined) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.innerHTML = content;
|
||||
});
|
||||
return this;
|
||||
} else {
|
||||
return this.elements[0] ? this.elements[0].innerHTML : '';
|
||||
}
|
||||
},
|
||||
|
||||
text: function(content) {
|
||||
if (content !== undefined) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.textContent = content;
|
||||
});
|
||||
return this;
|
||||
} else {
|
||||
return this.elements[0] ? this.elements[0].textContent : '';
|
||||
}
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.elements.forEach(function(el) {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
show: function() {
|
||||
this.elements.forEach(function(el) {
|
||||
el.style.display = '';
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
fadeIn: function(duration) {
|
||||
this.elements.forEach(function(el) {
|
||||
el.style.opacity = '0';
|
||||
el.style.display = '';
|
||||
var start = Date.now();
|
||||
var timer = setInterval(function() {
|
||||
var progress = (Date.now() - start) / (duration || 400);
|
||||
if (progress >= 1) {
|
||||
el.style.opacity = '1';
|
||||
clearInterval(timer);
|
||||
} else {
|
||||
el.style.opacity = progress;
|
||||
}
|
||||
}, 16);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
fadeOut: function(duration) {
|
||||
this.elements.forEach(function(el) {
|
||||
var start = Date.now();
|
||||
var startOpacity = parseFloat(getComputedStyle(el).opacity);
|
||||
var timer = setInterval(function() {
|
||||
var progress = (Date.now() - start) / (duration || 400);
|
||||
if (progress >= 1) {
|
||||
el.style.display = 'none';
|
||||
clearInterval(timer);
|
||||
} else {
|
||||
el.style.opacity = startOpacity * (1 - progress);
|
||||
}
|
||||
}, 16);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ArgonFallback.init();
|
||||
});
|
||||
} else {
|
||||
ArgonFallback.init();
|
||||
}
|
||||
})();
|
||||
54
assets/vendor/external/katex/auto-render.min.js
vendored
Normal file
54
assets/vendor/external/katex/auto-render.min.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/* KaTeX Auto-Render - Local Fallback */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.warn('KaTeX Auto-Render 本地备用版本 - 功能受限');
|
||||
|
||||
window.renderMathInElement = function(element, options) {
|
||||
console.warn('KaTeX 自动渲染功能不可用 - 使用备用版本');
|
||||
|
||||
options = options || {};
|
||||
var delimiters = options.delimiters || [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
];
|
||||
|
||||
if (!element) {
|
||||
element = document.body;
|
||||
}
|
||||
|
||||
// 简单的文本替换处理
|
||||
var walker = document.createTreeWalker(
|
||||
element,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
var textNodes = [];
|
||||
var node;
|
||||
while (node = walker.nextNode()) {
|
||||
textNodes.push(node);
|
||||
}
|
||||
|
||||
textNodes.forEach(function(textNode) {
|
||||
var text = textNode.textContent;
|
||||
var hasMatch = false;
|
||||
|
||||
delimiters.forEach(function(delimiter) {
|
||||
if (text.includes(delimiter.left) && text.includes(delimiter.right)) {
|
||||
hasMatch = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasMatch) {
|
||||
var span = document.createElement('span');
|
||||
span.innerHTML = text.replace(/\$([^$]+)\$/g,
|
||||
'<span class="katex" style="font-style: italic; color: #666;" title="数学公式渲染服务不可用">$1</span>');
|
||||
textNode.parentNode.replaceChild(span, textNode);
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
47
assets/vendor/external/katex/katex.min.css
vendored
Normal file
47
assets/vendor/external/katex/katex.min.css
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/* KaTeX CSS - Local Fallback */
|
||||
.katex {
|
||||
font: normal 1.21em KaTeX_Main, "Times New Roman", serif;
|
||||
line-height: 1.2;
|
||||
text-indent: 0;
|
||||
text-rendering: auto;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.katex * {
|
||||
-ms-high-contrast-adjust: none !important;
|
||||
}
|
||||
|
||||
.katex .katex-version::after {
|
||||
content: "本地备用版本";
|
||||
}
|
||||
|
||||
.katex .katex-mathml {
|
||||
position: absolute;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.katex-display {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.katex-display > .katex {
|
||||
display: block;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 备用样式提示 */
|
||||
.katex::before {
|
||||
content: "[数学公式渲染服务不可用] ";
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
font-style: normal;
|
||||
}
|
||||
27
assets/vendor/external/katex/katex.min.js
vendored
Normal file
27
assets/vendor/external/katex/katex.min.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/* KaTeX JS - Local Fallback */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.warn('KaTeX 本地备用版本 - 功能受限');
|
||||
|
||||
// 创建简化的 KaTeX 对象
|
||||
window.katex = {
|
||||
render: function(tex, element, options) {
|
||||
console.warn('KaTeX 渲染功能不可用 - 使用备用版本');
|
||||
if (element) {
|
||||
element.innerHTML = '<span style="font-style: italic; color: #666;" title="数学公式渲染服务不可用">' +
|
||||
tex + '</span>';
|
||||
element.className = (element.className || '') + ' katex';
|
||||
}
|
||||
},
|
||||
|
||||
renderToString: function(tex, options) {
|
||||
console.warn('KaTeX 渲染功能不可用 - 使用备用版本');
|
||||
return '<span class="katex" style="font-style: italic; color: #666;" title="数学公式渲染服务不可用">' +
|
||||
tex + '</span>';
|
||||
}
|
||||
};
|
||||
|
||||
// 版本信息
|
||||
window.katex.__version = "0.11.1-fallback";
|
||||
})();
|
||||
266
assets/vendor/external/logger.js
vendored
Normal file
266
assets/vendor/external/logger.js
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
/* Argon 内部日志系统 - 仅管理员可见 */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.ArgonLogger = {
|
||||
// 配置
|
||||
config: {
|
||||
maxLogs: 1000,
|
||||
enableConsole: false, // 默认关闭控制台输出
|
||||
enableStorage: true,
|
||||
storageKey: 'argon_internal_logs'
|
||||
},
|
||||
|
||||
// 日志级别
|
||||
levels: {
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
CRITICAL: 4
|
||||
},
|
||||
|
||||
// 内部日志存储
|
||||
logs: [],
|
||||
|
||||
// 初始化
|
||||
init: function() {
|
||||
this.checkAdminStatus();
|
||||
this.loadStoredLogs();
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
// 检查管理员状态
|
||||
checkAdminStatus: function() {
|
||||
// 检查是否为管理员(通过PHP传递的全局变量)
|
||||
if (typeof window.argonUserRole !== 'undefined' && window.argonUserRole === 'administrator') {
|
||||
this.config.enableConsole = true;
|
||||
this.log('管理员模式:控制台日志已启用', 'INFO');
|
||||
} else {
|
||||
this.config.enableConsole = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载存储的日志
|
||||
loadStoredLogs: function() {
|
||||
if (this.config.enableStorage && localStorage) {
|
||||
try {
|
||||
var stored = localStorage.getItem(this.config.storageKey);
|
||||
if (stored) {
|
||||
this.logs = JSON.parse(stored);
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默处理存储错误
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 保存日志到存储
|
||||
saveLogsToStorage: function() {
|
||||
if (this.config.enableStorage && localStorage) {
|
||||
try {
|
||||
// 只保留最新的日志
|
||||
var logsToSave = this.logs.slice(-this.config.maxLogs);
|
||||
localStorage.setItem(this.config.storageKey, JSON.stringify(logsToSave));
|
||||
} catch (e) {
|
||||
// 静默处理存储错误
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 记录日志
|
||||
log: function(message, level, data) {
|
||||
level = level || 'INFO';
|
||||
var timestamp = new Date().toISOString();
|
||||
var logEntry = {
|
||||
timestamp: timestamp,
|
||||
level: level,
|
||||
message: message,
|
||||
data: data || null,
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent.substring(0, 100) // 截取前100字符
|
||||
};
|
||||
|
||||
// 添加到内部日志
|
||||
this.logs.push(logEntry);
|
||||
|
||||
// 限制日志数量
|
||||
if (this.logs.length > this.config.maxLogs) {
|
||||
this.logs = this.logs.slice(-this.config.maxLogs);
|
||||
}
|
||||
|
||||
// 保存到存储
|
||||
this.saveLogsToStorage();
|
||||
|
||||
// 仅管理员显示控制台日志
|
||||
if (this.config.enableConsole) {
|
||||
var consoleMethod = this.getConsoleMethod(level);
|
||||
consoleMethod('[Argon] ' + message, data || '');
|
||||
}
|
||||
|
||||
// 关键错误需要特殊处理
|
||||
if (level === 'CRITICAL') {
|
||||
this.handleCriticalError(logEntry);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取对应的控制台方法
|
||||
getConsoleMethod: function(level) {
|
||||
switch (level) {
|
||||
case 'DEBUG':
|
||||
return console.debug || console.log;
|
||||
case 'INFO':
|
||||
return console.info || console.log;
|
||||
case 'WARN':
|
||||
return console.warn || console.log;
|
||||
case 'ERROR':
|
||||
case 'CRITICAL':
|
||||
return console.error || console.log;
|
||||
default:
|
||||
return console.log;
|
||||
}
|
||||
},
|
||||
|
||||
// 处理关键错误
|
||||
handleCriticalError: function(logEntry) {
|
||||
// 关键错误需要通知用户联系站长
|
||||
if (this.config.enableConsole) {
|
||||
console.error('发生关键错误,请联系站长处理:', logEntry.message);
|
||||
}
|
||||
|
||||
// 可以在这里添加错误上报逻辑
|
||||
this.reportCriticalError(logEntry);
|
||||
},
|
||||
|
||||
// 上报关键错误
|
||||
reportCriticalError: function(logEntry) {
|
||||
// 这里可以实现错误上报到服务器的逻辑
|
||||
// 暂时只记录到本地存储
|
||||
try {
|
||||
var criticalLogs = JSON.parse(localStorage.getItem('argon_critical_errors') || '[]');
|
||||
criticalLogs.push(logEntry);
|
||||
localStorage.setItem('argon_critical_errors', JSON.stringify(criticalLogs.slice(-50)));
|
||||
} catch (e) {
|
||||
// 静默处理
|
||||
}
|
||||
},
|
||||
|
||||
// 获取日志(仅管理员)
|
||||
getLogs: function(level, limit) {
|
||||
if (!this.config.enableConsole) {
|
||||
return []; // 非管理员返回空数组
|
||||
}
|
||||
|
||||
var filteredLogs = this.logs;
|
||||
|
||||
if (level) {
|
||||
filteredLogs = this.logs.filter(function(log) {
|
||||
return log.level === level;
|
||||
});
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
filteredLogs = filteredLogs.slice(-limit);
|
||||
}
|
||||
|
||||
return filteredLogs;
|
||||
},
|
||||
|
||||
// 清除日志(仅管理员)
|
||||
clearLogs: function() {
|
||||
if (!this.config.enableConsole) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logs = [];
|
||||
if (localStorage) {
|
||||
localStorage.removeItem(this.config.storageKey);
|
||||
localStorage.removeItem('argon_critical_errors');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// 导出日志(仅管理员)
|
||||
exportLogs: function() {
|
||||
if (!this.config.enableConsole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var exportData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
site: window.location.origin,
|
||||
logs: this.logs,
|
||||
criticalErrors: JSON.parse(localStorage.getItem('argon_critical_errors') || '[]')
|
||||
};
|
||||
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
},
|
||||
|
||||
// 绑定事件
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// 监听页面错误
|
||||
window.addEventListener('error', function(e) {
|
||||
self.log('JavaScript错误: ' + e.message, 'ERROR', {
|
||||
filename: e.filename,
|
||||
lineno: e.lineno,
|
||||
colno: e.colno,
|
||||
stack: e.error ? e.error.stack : null
|
||||
});
|
||||
});
|
||||
|
||||
// 监听未处理的Promise拒绝
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
self.log('未处理的Promise拒绝: ' + e.reason, 'ERROR', {
|
||||
reason: e.reason,
|
||||
promise: e.promise
|
||||
});
|
||||
});
|
||||
|
||||
// 监听资源加载错误
|
||||
window.addEventListener('error', function(e) {
|
||||
if (e.target && e.target !== window) {
|
||||
var resourceType = e.target.tagName || 'unknown';
|
||||
var resourceUrl = e.target.src || e.target.href || 'unknown';
|
||||
self.log('资源加载失败: ' + resourceType + ' - ' + resourceUrl, 'WARN');
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
||||
// 便捷方法
|
||||
debug: function(message, data) {
|
||||
this.log(message, 'DEBUG', data);
|
||||
},
|
||||
|
||||
info: function(message, data) {
|
||||
this.log(message, 'INFO', data);
|
||||
},
|
||||
|
||||
warn: function(message, data) {
|
||||
this.log(message, 'WARN', data);
|
||||
},
|
||||
|
||||
error: function(message, data) {
|
||||
this.log(message, 'ERROR', data);
|
||||
},
|
||||
|
||||
critical: function(message, data) {
|
||||
this.log(message, 'CRITICAL', data);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ArgonLogger.init();
|
||||
});
|
||||
} else {
|
||||
ArgonLogger.init();
|
||||
}
|
||||
|
||||
// 为管理员提供全局访问
|
||||
if (typeof window.argonUserRole !== 'undefined' && window.argonUserRole === 'administrator') {
|
||||
window.ArgonLogs = ArgonLogger;
|
||||
}
|
||||
})();
|
||||
62
assets/vendor/external/mathjax2/MathJax.js
vendored
Normal file
62
assets/vendor/external/mathjax2/MathJax.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/* MathJax 2 - Local Fallback */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.warn('MathJax 2 本地备用版本 - 功能受限');
|
||||
|
||||
// 创建简化的 MathJax 对象
|
||||
window.MathJax = window.MathJax || {};
|
||||
|
||||
window.MathJax.Hub = {
|
||||
Config: function(config) {
|
||||
console.log('MathJax 2 配置已设置(备用版本)');
|
||||
this.config = config;
|
||||
},
|
||||
|
||||
Queue: function(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(callback, 100);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
Typeset: function(element) {
|
||||
console.warn('MathJax 2 渲染功能不可用 - 使用备用版本');
|
||||
// 简单的样式处理
|
||||
var mathElements = element ?
|
||||
element.querySelectorAll('script[type*="math"]') :
|
||||
document.querySelectorAll('script[type*="math"]');
|
||||
|
||||
mathElements.forEach(function(script) {
|
||||
var span = document.createElement('span');
|
||||
span.textContent = script.textContent;
|
||||
span.style.fontStyle = 'italic';
|
||||
span.style.color = '#666';
|
||||
span.title = '数学公式渲染服务不可用';
|
||||
script.parentNode.insertBefore(span, script);
|
||||
script.style.display = 'none';
|
||||
});
|
||||
},
|
||||
|
||||
Register: {
|
||||
StartupHook: function(hook, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(callback, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 自动配置
|
||||
if (window.MathJax.Hub.Config) {
|
||||
window.MathJax.Hub.Config({
|
||||
messageStyle: "none",
|
||||
tex2jax: {
|
||||
inlineMath: [["$", "$"], ["\\\\(", "\\\\)"]],
|
||||
displayMath: [['$$','$$']],
|
||||
processEscapes: true,
|
||||
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
61
assets/vendor/external/mathjax3/tex-chtml-full.js
vendored
Normal file
61
assets/vendor/external/mathjax3/tex-chtml-full.js
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/* MathJax 3 - Local Fallback */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.warn('MathJax 3 本地备用版本 - 功能受限');
|
||||
|
||||
// 创建简化的 MathJax 对象
|
||||
window.MathJax = window.MathJax || {
|
||||
tex: {
|
||||
inlineMath: [["$", "$"], ["\\\\(", "\\\\)"]],
|
||||
displayMath: [['$$','$$']],
|
||||
processEscapes: true,
|
||||
packages: {'[+]': ['noerrors']}
|
||||
},
|
||||
options: {
|
||||
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
|
||||
ignoreHtmlClass: 'tex2jax_ignore',
|
||||
processHtmlClass: 'tex2jax_process'
|
||||
},
|
||||
loader: {
|
||||
load: ['[tex]/noerrors']
|
||||
},
|
||||
startup: {
|
||||
ready: function() {
|
||||
console.log('MathJax 3 备用版本已就绪');
|
||||
MathJax.startup.defaultReady();
|
||||
},
|
||||
defaultReady: function() {
|
||||
// 简化的渲染函数
|
||||
MathJax.typesetPromise = function(elements) {
|
||||
return new Promise(function(resolve) {
|
||||
console.warn('MathJax 渲染功能不可用 - 使用备用版本');
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 简化的渲染函数
|
||||
window.MathJax.typesetPromise = function(elements) {
|
||||
return new Promise(function(resolve) {
|
||||
console.warn('数学公式渲染服务不可用');
|
||||
// 尝试简单的文本替换
|
||||
var mathElements = document.querySelectorAll('.tex2jax_process, .MathJax');
|
||||
mathElements.forEach(function(el) {
|
||||
if (el.textContent.includes('$')) {
|
||||
el.style.fontStyle = 'italic';
|
||||
el.style.color = '#666';
|
||||
el.title = '数学公式渲染服务不可用';
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
// 触发启动
|
||||
if (MathJax.startup && MathJax.startup.ready) {
|
||||
MathJax.startup.ready();
|
||||
}
|
||||
})();
|
||||
206
assets/vendor/external/resource-loader.js
vendored
206
assets/vendor/external/resource-loader.js
vendored
@@ -8,37 +8,131 @@
|
||||
'fonts.googleapis.com/css?family=Open+Sans': '/wp-content/themes/argon/assets/vendor/external/fonts/open-sans.css',
|
||||
'fonts.googleapis.com/css?family=Noto+Serif+SC': '/wp-content/themes/argon/assets/vendor/external/fonts/noto-serif-sc.css',
|
||||
'static.geetest.com/v4/gt4.js': '/wp-content/themes/argon/assets/vendor/external/geetest/gt4.js',
|
||||
'cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js': '/wp-content/themes/argon/assets/vendor/external/qrcode/qrcode.min.js'
|
||||
'cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js': '/wp-content/themes/argon/assets/vendor/external/qrcode/qrcode.min.js',
|
||||
'cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js': '/wp-content/themes/argon/assets/vendor/external/mathjax3/tex-chtml-full.js',
|
||||
'cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js': '/wp-content/themes/argon/assets/vendor/external/mathjax2/MathJax.js',
|
||||
'cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css': '/wp-content/themes/argon/assets/vendor/external/katex/katex.min.css',
|
||||
'cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js': '/wp-content/themes/argon/assets/vendor/external/katex/katex.min.js',
|
||||
'cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js': '/wp-content/themes/argon/assets/vendor/external/katex/auto-render.min.js'
|
||||
},
|
||||
|
||||
// 配置选项
|
||||
config: {
|
||||
timeout: 5000,
|
||||
retryCount: 2,
|
||||
enableLogging: true,
|
||||
enableFallbackCSS: true,
|
||||
enableFallbackJS: true
|
||||
},
|
||||
|
||||
// 初始化
|
||||
init: function() {
|
||||
this.loadFallbackSystems();
|
||||
this.bindGlobalErrorHandlers();
|
||||
this.log('资源加载器已初始化');
|
||||
},
|
||||
|
||||
// 加载备用系统
|
||||
loadFallbackSystems: function() {
|
||||
if (this.config.enableFallbackCSS) {
|
||||
this.loadCSS('/wp-content/themes/argon/assets/vendor/external/css-fallback.css');
|
||||
this.loadCSS('/wp-content/themes/argon/assets/vendor/external/fonts/font-fallback.css');
|
||||
}
|
||||
|
||||
if (this.config.enableFallbackJS) {
|
||||
this.loadJS('/wp-content/themes/argon/assets/vendor/external/js-fallback.js');
|
||||
this.loadJS('/wp-content/themes/argon/assets/vendor/external/resource-monitor.js');
|
||||
}
|
||||
},
|
||||
|
||||
// 绑定全局错误处理
|
||||
bindGlobalErrorHandlers: function() {
|
||||
var self = this;
|
||||
|
||||
// 监听资源加载错误
|
||||
window.addEventListener('error', function(e) {
|
||||
if (e.target && (e.target.tagName === 'SCRIPT' || e.target.tagName === 'LINK')) {
|
||||
self.handleResourceError(e.target);
|
||||
}
|
||||
}, true);
|
||||
|
||||
// 监听未处理的Promise拒绝
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
if (typeof ArgonLogger !== 'undefined') {
|
||||
ArgonLogger.warn('未处理的Promise拒绝: ' + e.reason);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 处理资源加载错误
|
||||
handleResourceError: function(element) {
|
||||
var url = element.src || element.href;
|
||||
if (url) {
|
||||
if (typeof ArgonLogger !== 'undefined') {
|
||||
ArgonLogger.warn('资源加载失败: ' + url);
|
||||
}
|
||||
var fallbackUrl = this.getFallbackUrl(url);
|
||||
if (fallbackUrl) {
|
||||
this.log('尝试加载备用资源: ' + fallbackUrl);
|
||||
if (element.tagName === 'SCRIPT') {
|
||||
this.loadJS(fallbackUrl);
|
||||
} else if (element.tagName === 'LINK') {
|
||||
this.loadCSS(fallbackUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取备用URL
|
||||
getFallbackUrl: function(url) {
|
||||
for (var key in this.resourceMap) {
|
||||
if (url.includes(key)) {
|
||||
return this.resourceMap[key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// 加载CSS资源
|
||||
loadCSS: function(url, fallbackUrl) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = url;
|
||||
|
||||
var timeout = setTimeout(function() {
|
||||
console.warn('CSS资源加载超时,使用本地备用:', url);
|
||||
self.log('CSS资源加载超时: ' + url);
|
||||
if (fallbackUrl) {
|
||||
ArgonResourceLoader.loadCSS(fallbackUrl).then(resolve).catch(reject);
|
||||
self.loadCSS(fallbackUrl).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('CSS加载失败且无备用资源'));
|
||||
var autoFallback = self.getFallbackUrl(url);
|
||||
if (autoFallback) {
|
||||
self.loadCSS(autoFallback).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('CSS加载失败且无备用资源'));
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}, self.config.timeout);
|
||||
|
||||
link.onload = function() {
|
||||
clearTimeout(timeout);
|
||||
self.log('CSS资源加载成功: ' + url);
|
||||
resolve();
|
||||
};
|
||||
|
||||
link.onerror = function() {
|
||||
clearTimeout(timeout);
|
||||
console.warn('CSS资源加载失败,使用本地备用:', url);
|
||||
self.log('CSS资源加载失败: ' + url);
|
||||
if (fallbackUrl) {
|
||||
ArgonResourceLoader.loadCSS(fallbackUrl).then(resolve).catch(reject);
|
||||
self.loadCSS(fallbackUrl).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('CSS加载失败且无备用资源'));
|
||||
var autoFallback = self.getFallbackUrl(url);
|
||||
if (autoFallback) {
|
||||
self.loadCSS(autoFallback).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('CSS加载失败且无备用资源'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,32 +142,44 @@
|
||||
|
||||
// 加载JS资源
|
||||
loadJS: function(url, fallbackUrl) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
|
||||
var timeout = setTimeout(function() {
|
||||
console.warn('JS资源加载超时,使用本地备用:', url);
|
||||
self.log('JS资源加载超时: ' + url);
|
||||
if (fallbackUrl) {
|
||||
ArgonResourceLoader.loadJS(fallbackUrl).then(resolve).catch(reject);
|
||||
self.loadJS(fallbackUrl).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('JS加载失败且无备用资源'));
|
||||
var autoFallback = self.getFallbackUrl(url);
|
||||
if (autoFallback) {
|
||||
self.loadJS(autoFallback).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('JS加载失败且无备用资源'));
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}, self.config.timeout);
|
||||
|
||||
script.onload = function() {
|
||||
clearTimeout(timeout);
|
||||
self.log('JS资源加载成功: ' + url);
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = function() {
|
||||
clearTimeout(timeout);
|
||||
console.warn('JS资源加载失败,使用本地备用:', url);
|
||||
self.log('JS资源加载失败: ' + url);
|
||||
if (fallbackUrl) {
|
||||
ArgonResourceLoader.loadJS(fallbackUrl).then(resolve).catch(reject);
|
||||
self.loadJS(fallbackUrl).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('JS加载失败且无备用资源'));
|
||||
var autoFallback = self.getFallbackUrl(url);
|
||||
if (autoFallback) {
|
||||
self.loadJS(autoFallback).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(new Error('JS加载失败且无备用资源'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -83,45 +189,45 @@
|
||||
|
||||
// 智能加载资源
|
||||
smartLoad: function(url, type) {
|
||||
var fallbackUrl = null;
|
||||
|
||||
// 查找备用资源
|
||||
for (var key in this.resourceMap) {
|
||||
if (url.indexOf(key) !== -1) {
|
||||
fallbackUrl = this.resourceMap[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'css') {
|
||||
return this.loadCSS(url, fallbackUrl);
|
||||
return this.loadCSS(url);
|
||||
} else if (type === 'js') {
|
||||
return this.loadJS(url, fallbackUrl);
|
||||
return this.loadJS(url);
|
||||
} else {
|
||||
return Promise.reject(new Error('不支持的资源类型: ' + type));
|
||||
}
|
||||
},
|
||||
|
||||
// 批量加载资源
|
||||
loadMultiple: function(resources) {
|
||||
var self = this;
|
||||
var promises = resources.map(function(resource) {
|
||||
return self.smartLoad(resource.url, resource.type);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
},
|
||||
|
||||
// 日志记录
|
||||
log: function(message) {
|
||||
if (this.config.enableLogging) {
|
||||
if (typeof ArgonLogger !== 'undefined') {
|
||||
ArgonLogger.info('[ResourceLoader] ' + message);
|
||||
} else {
|
||||
// 仅在管理员模式下输出到控制台
|
||||
if (typeof window.argonUserRole !== 'undefined' && window.argonUserRole === 'administrator') {
|
||||
console.log('[ArgonResourceLoader] ' + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 自动替换现有的资源加载
|
||||
var originalCreateElement = document.createElement;
|
||||
document.createElement = function(tagName) {
|
||||
var element = originalCreateElement.call(document, tagName);
|
||||
|
||||
if (tagName.toLowerCase() === 'script') {
|
||||
var originalSetAttribute = element.setAttribute;
|
||||
element.setAttribute = function(name, value) {
|
||||
if (name === 'src' && value) {
|
||||
// 检查是否需要备用资源
|
||||
for (var key in ArgonResourceLoader.resourceMap) {
|
||||
if (value.indexOf(key) !== -1) {
|
||||
console.log('检测到外部JS资源,准备备用方案:', value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return originalSetAttribute.call(this, name, value);
|
||||
};
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
// 自动初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ArgonResourceLoader.init();
|
||||
});
|
||||
} else {
|
||||
ArgonResourceLoader.init();
|
||||
}
|
||||
})();
|
||||
200
assets/vendor/external/resource-monitor.js
vendored
Normal file
200
assets/vendor/external/resource-monitor.js
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
/* 外部资源监控系统 - 实时检测和自动切换 */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.ArgonResourceMonitor = {
|
||||
// 监控配置
|
||||
config: {
|
||||
checkInterval: 30000, // 30秒检查一次
|
||||
timeout: 5000, // 5秒超时
|
||||
retryCount: 3, // 重试3次
|
||||
enableLogging: true // 启用日志
|
||||
},
|
||||
|
||||
// 资源状态
|
||||
resourceStatus: {},
|
||||
|
||||
// 监控定时器
|
||||
monitorTimer: null,
|
||||
|
||||
// 初始化监控
|
||||
init: function() {
|
||||
this.log('资源监控系统启动');
|
||||
this.startMonitoring();
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
// 开始监控
|
||||
startMonitoring: function() {
|
||||
var self = this;
|
||||
this.monitorTimer = setInterval(function() {
|
||||
self.checkAllResources();
|
||||
}, this.config.checkInterval);
|
||||
},
|
||||
|
||||
// 停止监控
|
||||
stopMonitoring: function() {
|
||||
if (this.monitorTimer) {
|
||||
clearInterval(this.monitorTimer);
|
||||
this.monitorTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 检查所有资源
|
||||
checkAllResources: function() {
|
||||
var self = this;
|
||||
var resources = [
|
||||
'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700',
|
||||
'https://static.geetest.com/v4/gt4.js',
|
||||
'https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js'
|
||||
];
|
||||
|
||||
resources.forEach(function(url) {
|
||||
self.checkResource(url);
|
||||
});
|
||||
},
|
||||
|
||||
// 检查单个资源
|
||||
checkResource: function(url) {
|
||||
var self = this;
|
||||
var startTime = Date.now();
|
||||
|
||||
// 创建测试请求
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.timeout = this.config.timeout;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
var responseTime = Date.now() - startTime;
|
||||
var status = xhr.status === 200 ? 'available' : 'unavailable';
|
||||
|
||||
self.updateResourceStatus(url, {
|
||||
status: status,
|
||||
responseTime: responseTime,
|
||||
lastCheck: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
self.updateResourceStatus(url, {
|
||||
status: 'unavailable',
|
||||
responseTime: -1,
|
||||
lastCheck: new Date().toISOString()
|
||||
});
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
self.updateResourceStatus(url, {
|
||||
status: 'timeout',
|
||||
responseTime: self.config.timeout,
|
||||
lastCheck: new Date().toISOString()
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
xhr.open('HEAD', url, true);
|
||||
xhr.send();
|
||||
} catch (e) {
|
||||
this.updateResourceStatus(url, {
|
||||
status: 'error',
|
||||
responseTime: -1,
|
||||
lastCheck: new Date().toISOString(),
|
||||
error: e.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 更新资源状态
|
||||
updateResourceStatus: function(url, status) {
|
||||
this.resourceStatus[url] = status;
|
||||
this.log('资源状态更新: ' + url + ' - ' + status.status);
|
||||
|
||||
// 如果资源不可用,触发备用机制
|
||||
if (status.status !== 'available') {
|
||||
this.triggerFallback(url);
|
||||
}
|
||||
},
|
||||
|
||||
// 触发备用机制
|
||||
triggerFallback: function(url) {
|
||||
this.log('触发备用机制: ' + url);
|
||||
|
||||
// 发送自定义事件
|
||||
var event = new CustomEvent('resourceUnavailable', {
|
||||
detail: { url: url, status: this.resourceStatus[url] }
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
|
||||
// 绑定事件
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// 监听页面可见性变化
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
self.checkAllResources();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听网络状态变化
|
||||
if ('onLine' in navigator) {
|
||||
window.addEventListener('online', function() {
|
||||
self.log('网络连接恢复,重新检查资源');
|
||||
self.checkAllResources();
|
||||
});
|
||||
|
||||
window.addEventListener('offline', function() {
|
||||
self.log('网络连接断开');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取资源状态报告
|
||||
getStatusReport: function() {
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
resources: this.resourceStatus,
|
||||
summary: this.generateSummary()
|
||||
};
|
||||
},
|
||||
|
||||
// 生成状态摘要
|
||||
generateSummary: function() {
|
||||
var total = Object.keys(this.resourceStatus).length;
|
||||
var available = 0;
|
||||
var unavailable = 0;
|
||||
|
||||
for (var url in this.resourceStatus) {
|
||||
if (this.resourceStatus[url].status === 'available') {
|
||||
available++;
|
||||
} else {
|
||||
unavailable++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total: total,
|
||||
available: available,
|
||||
unavailable: unavailable,
|
||||
availability: total > 0 ? (available / total * 100).toFixed(1) + '%' : '0%'
|
||||
};
|
||||
},
|
||||
|
||||
// 日志记录
|
||||
log: function(message) {
|
||||
if (this.config.enableLogging) {
|
||||
console.log('[ArgonResourceMonitor] ' + message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载完成后自动启动监控
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
ArgonResourceMonitor.init();
|
||||
});
|
||||
} else {
|
||||
ArgonResourceMonitor.init();
|
||||
}
|
||||
})();
|
||||
25
footer.php
25
footer.php
@@ -190,7 +190,15 @@
|
||||
|
||||
</script>
|
||||
|
||||
<script src="<?php echo get_option('argon_mathjax_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js' : get_option('argon_mathjax_cdn_url'); ?>" id="MathJax-script" async></script>
|
||||
<script src="<?php echo get_option('argon_mathjax_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js' : get_option('argon_mathjax_cdn_url'); ?>" id="MathJax-script" async onerror="
|
||||
var fallbackScript = document.createElement('script');
|
||||
fallbackScript.src = '<?php echo $GLOBALS['assets_path']; ?>/assets/vendor/external/mathjax3/tex-chtml-full.js';
|
||||
fallbackScript.async = true;
|
||||
document.head.appendChild(fallbackScript);
|
||||
if (typeof ArgonLogger !== 'undefined') {
|
||||
ArgonLogger.warn('MathJax 3 CDN失败,使用本地备用');
|
||||
}
|
||||
"></script>
|
||||
|
||||
<?php }?>
|
||||
|
||||
@@ -232,17 +240,24 @@
|
||||
|
||||
</script>
|
||||
|
||||
<script src="<?php echo get_option('argon_mathjax_v2_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-AMS_HTML' : get_option('argon_mathjax_v2_cdn_url'); ?>"></script>
|
||||
<script src="<?php echo get_option('argon_mathjax_v2_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-AMS_HTML' : get_option('argon_mathjax_v2_cdn_url'); ?>" onerror="
|
||||
var fallbackScript = document.createElement('script');
|
||||
fallbackScript.src = '<?php echo $GLOBALS['assets_path']; ?>/assets/vendor/external/mathjax2/MathJax.js';
|
||||
document.head.appendChild(fallbackScript);
|
||||
if (typeof ArgonLogger !== 'undefined') {
|
||||
ArgonLogger.warn('MathJax 2 CDN失败,使用本地备用');
|
||||
}
|
||||
"></script>
|
||||
|
||||
<?php }?>
|
||||
|
||||
<?php if (get_option('argon_math_render') == 'katex') { /*Katex*/?>
|
||||
|
||||
<link rel="stylesheet" href="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>katex.min.css">
|
||||
<link rel="stylesheet" href="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>katex.min.css" onerror="this.href='<?php echo $GLOBALS['assets_path']; ?>/assets/vendor/external/katex/katex.min.css'">
|
||||
|
||||
<script src="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>katex.min.js"></script>
|
||||
<script src="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>katex.min.js" onerror="this.src='<?php echo $GLOBALS['assets_path']; ?>/assets/vendor/external/katex/katex.min.js'"></script>
|
||||
|
||||
<script src="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>contrib/auto-render.min.js"></script>
|
||||
<script src="<?php echo get_option('argon_katex_cdn_url') == '' ? '//cdn.jsdelivr.net/npm/katex@0.11.1/dist/' : get_option('argon_katex_cdn_url'); ?>contrib/auto-render.min.js" onerror="this.src='<?php echo $GLOBALS['assets_path']; ?>/assets/vendor/external/katex/auto-render.min.js'"></script>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
@@ -291,6 +291,11 @@
|
||||
|
||||
<?php wp_head(); ?>
|
||||
|
||||
<!-- 用户角色检测 - 传递给前端JavaScript -->
|
||||
<script>
|
||||
window.argonUserRole = '<?php echo current_user_can('administrator') ? 'administrator' : (is_user_logged_in() ? 'user' : 'guest'); ?>';
|
||||
</script>
|
||||
|
||||
<!-- Argon 修复补丁 - 必须在 wp_head() 之后立即执行 -->
|
||||
<script src="<?php echo get_template_directory_uri(); ?>/assets/js/argon.min.js?ver=<?php echo $GLOBALS['theme_version']; ?>&t=<?php echo time(); ?>"></script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user